backported more fixes from 256boss
[bootcensus] / src / bootdev.c
1 /*
2 pcboot - bootable PC demo/game kernel
3 Copyright (C) 2018-2019  John Tsiombikas <nuclear@member.fsf.org>
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY, without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program.  If not, see <https://www.gnu.org/licenses/>.
17 */
18 #include <stdio.h>
19 #include <string.h>
20 #include "bootdev.h"
21 #include "boot.h"
22 #include "int86.h"
23 #include "panic.h"
24 #include "timer.h"
25 #include "floppy.h"
26
27 #define FLOPPY_MOTOR_OFF_TIMEOUT        4000
28 #define DBG_RESET_ON_FAIL
29
30 struct chs {
31         unsigned int cyl, head, tsect;
32 };
33
34 struct disk_access {
35         unsigned char pktsize;
36         unsigned char zero;
37         uint16_t num_sectors;   /* max 127 on some implementations */
38         uint16_t boffs, bseg;
39         uint32_t lba_low, lba_high;
40 } __attribute__((packed));
41
42 enum {OP_READ, OP_WRITE};
43
44 #ifdef DBG_RESET_ON_FAIL
45 static int bios_reset_dev(int dev);
46 #endif
47 static int bios_rw_sect_lba(int dev, uint64_t lba, int nsect, int op, void *buf);
48 static int bios_rw_sect_chs(int dev, struct chs *chs, int nsect, int op, void *buf);
49 static int get_drive_chs(int dev, struct chs *chs);
50 static void calc_chs(uint64_t lba, struct chs *chs);
51
52 static int have_bios_ext;
53 static int bdev_is_floppy;
54 static int num_cyl, num_heads, num_track_sect;
55
56 void bdev_init(void)
57 {
58         struct chs chs;
59         struct int86regs regs;
60
61         memset(&regs, 0, sizeof regs);
62         regs.eax = 0x4100;      /* function 41h: check int 13h extensions */
63         regs.ebx = 0x55aa;
64         regs.edx = boot_drive_number;
65
66         int86(0x13, &regs);
67         if((regs.flags & FLAGS_CARRY) || (regs.ecx & 1) == 0) {
68                 printf("BIOS does not support int13h extensions (LBA access)\n");
69                 have_bios_ext = 0;
70
71                 if(get_drive_chs(boot_drive_number, &chs) == -1) {
72                         panic("drive (%d) parameter query failed\n", boot_drive_number);
73                 }
74
75                 num_cyl = chs.cyl;
76                 num_heads = chs.head;
77                 num_track_sect = chs.tsect;
78
79                 printf("Drive params: %d cyl, %d heads, %d sect/track\n", num_cyl,
80                                 num_heads, num_track_sect);
81         } else {
82                 printf("BIOS supports int13h extensions (LBA access)\n");
83                 have_bios_ext = 1;
84         }
85
86         bdev_is_floppy = !(boot_drive_number & 0x80);
87 }
88
89 #define NRETRIES        3
90
91 int bdev_read_sect(uint64_t lba, void *buf)
92 {
93         int i;
94         struct chs chs;
95
96         if(bdev_is_floppy) {
97                 cancel_alarm(floppy_motors_off);
98                 set_alarm(FLOPPY_MOTOR_OFF_TIMEOUT, floppy_motors_off);
99         }
100
101         if(have_bios_ext) {
102                 return bios_rw_sect_lba(boot_drive_number, lba, 1, OP_READ, buf);
103         }
104
105         calc_chs(lba, &chs);
106
107         for(i=0; i<NRETRIES; i++) {
108                 if(bios_rw_sect_chs(boot_drive_number, &chs, 1, OP_READ, buf) != -1) {
109                         return 0;
110                 }
111 #ifdef DBG_RESET_ON_FAIL
112                 bios_reset_dev(boot_drive_number);
113 #endif
114         }
115         return -1;
116 }
117
118 int bdev_write_sect(uint64_t lba, void *buf)
119 {
120         struct chs chs;
121
122         if(bdev_is_floppy) {
123                 cancel_alarm(floppy_motors_off);
124                 set_alarm(FLOPPY_MOTOR_OFF_TIMEOUT, floppy_motors_off);
125         }
126
127         if(have_bios_ext) {
128                 return bios_rw_sect_lba(boot_drive_number, lba, 1, OP_WRITE, buf);
129         }
130
131         calc_chs(lba, &chs);
132         return bios_rw_sect_chs(boot_drive_number, &chs, 1, OP_WRITE, buf);
133 }
134
135 int bdev_read_range(uint64_t lba, int nsect, void *buf)
136 {
137         int i;
138         struct chs chs;
139
140         if(bdev_is_floppy) {
141                 cancel_alarm(floppy_motors_off);
142                 set_alarm(FLOPPY_MOTOR_OFF_TIMEOUT, floppy_motors_off);
143         }
144
145         if(have_bios_ext) {
146                 return bios_rw_sect_lba(boot_drive_number, lba, nsect, OP_READ, buf);
147         }
148
149         calc_chs(lba, &chs);
150
151         for(i=0; i<NRETRIES; i++) {
152                 int rd;
153                 if((rd = bios_rw_sect_chs(boot_drive_number, &chs, nsect, OP_READ, buf)) == nsect) {
154                         return 0;
155                 }
156
157                 if(rd > 0) {
158                         nsect -= rd;
159                         buf = (char*)buf + rd * 512;
160                         lba += rd;
161                         calc_chs(lba, &chs);
162                 }
163
164 #ifdef DBG_RESET_ON_FAIL
165                 bios_reset_dev(boot_drive_number);
166 #endif
167         }
168         return -1;
169 }
170
171 int bdev_write_range(uint64_t lba, int nsect, void *buf)
172 {
173         struct chs chs;
174
175         if(bdev_is_floppy) {
176                 cancel_alarm(floppy_motors_off);
177                 set_alarm(FLOPPY_MOTOR_OFF_TIMEOUT, floppy_motors_off);
178         }
179
180         if(have_bios_ext) {
181                 return bios_rw_sect_lba(boot_drive_number, lba, nsect, OP_WRITE, buf);
182         }
183
184         calc_chs(lba, &chs);
185         return bios_rw_sect_chs(boot_drive_number, &chs, nsect, OP_WRITE, buf);
186 }
187
188
189 #ifdef DBG_RESET_ON_FAIL
190 static int bios_reset_dev(int dev)
191 {
192         struct int86regs regs;
193
194         printf("resetting drive %d ...", dev);
195
196         memset(&regs, 0, sizeof regs);
197         regs.edx = dev;
198
199         int86(0x13, &regs);
200
201         printf("%s\n", (regs.flags & FLAGS_CARRY) ? "failed" : "done");
202
203         return (regs.flags & FLAGS_CARRY) ? -1 : 0;
204 }
205 #endif
206
207 static int bios_rw_sect_lba(int dev, uint64_t lba, int nsect, int op, void *buf)
208 {
209         struct int86regs regs;
210         struct disk_access *dap = (struct disk_access*)low_mem_buffer;
211         uint32_t addr = (uint32_t)low_mem_buffer;
212         uint32_t xaddr = (addr + sizeof *dap + 15) & 0xfffffff0;
213         void *xbuf = (void*)xaddr;
214         int func;
215
216         if(op == OP_READ) {
217                 func = 0x42;    /* function 42h: extended read sector (LBA) */
218         } else {
219                 func = 0x43;    /* function 43h: extended write sector (LBA) */
220                 memcpy(xbuf, buf, nsect * 512);
221         }
222
223         dap->pktsize = sizeof *dap;
224         dap->zero = 0;
225         dap->num_sectors = nsect;
226         dap->boffs = 0;
227         dap->bseg = xaddr >> 4;
228         dap->lba_low = (uint32_t)lba;
229         dap->lba_high = (uint32_t)(lba >> 32);
230
231         memset(&regs, 0, sizeof regs);
232         regs.eax = func << 8;
233         regs.ds = addr >> 4;
234         regs.esi = 0;
235         regs.edx = dev;
236
237         int86(0x13, &regs);
238
239         if(regs.flags & FLAGS_CARRY) {
240                 return -1;
241         }
242
243         if(op == OP_READ) {
244                 memcpy(buf, xbuf, nsect * 512);
245         }
246         return dap->num_sectors;
247 }
248
249 /* TODO: fix: probably can't cross track boundaries, clamp nsect accordingly
250  * and return a short count
251  */
252 static int bios_rw_sect_chs(int dev, struct chs *chs, int nsect, int op, void *buf)
253 {
254         struct int86regs regs;
255         uint32_t xaddr = (uint32_t)low_mem_buffer;
256         int func;
257
258         if(op == OP_READ) {
259                 func = 2;
260         } else {
261                 func = 3;
262                 memcpy(low_mem_buffer, buf, nsect * 512);
263         }
264
265         memset(&regs, 0, sizeof regs);
266         regs.eax = (func << 8) | nsect; /* 1 sector */
267         regs.es = xaddr >> 4;   /* es:bx buffer */
268         regs.ecx = ((chs->cyl << 8) & 0xff00) | ((chs->cyl >> 10) & 0xc0) | chs->tsect;
269         regs.edx = dev | (chs->head << 8);
270
271         int86(0x13, &regs);
272
273         if(regs.flags & FLAGS_CARRY) {
274                 return -1;
275         }
276
277         if(op == OP_READ) {
278                 memcpy(buf, low_mem_buffer, nsect * 512);
279         }
280         return nsect;
281 }
282
283 static int get_drive_chs(int dev, struct chs *chs)
284 {
285         struct int86regs regs;
286
287         memset(&regs, 0, sizeof regs);
288         regs.eax = 0x800;
289         regs.edx = dev;
290
291         int86(0x13, &regs);
292
293         if(regs.flags & FLAGS_CARRY) {
294                 return -1;
295         }
296
297         chs->cyl = (((regs.ecx >> 8) & 0xff) | ((regs.ecx << 2) & 0x300)) + 1;
298         chs->head = (regs.edx >> 8) + 1;
299         chs->tsect = regs.ecx & 0x3f;
300         return 0;
301 }
302
303 static void calc_chs(uint64_t lba, struct chs *chs)
304 {
305         uint32_t lba32, trk;
306
307         if(lba >> 32) {
308                 /* XXX: 64bit ops require libgcc, and I don't want to link that for
309                  * this. CHS I/O is only going to be for floppies and really old systems
310                  * anyway, so there's no point in supporting anything more than LBA32 in
311                  * the translation.
312                  */
313                 const char *fmt = "calc_chs only supports 32bit LBA. requested: %llx\n"
314                         "If you see this message, please file a bug report at:\n"
315                         "  https://github.com/jtsiomb/256boss/issues\n"
316                         "  or by email: nuclear@member.fsf.org\n";
317                 panic(fmt, lba);
318         }
319
320         lba32 = (uint32_t)lba;
321         trk = lba32 / num_track_sect;
322         chs->tsect = (lba32 % num_track_sect) + 1;
323         chs->cyl = trk / num_heads;
324         chs->head = trk % num_heads;
325 }