1 | /* |
---|
2 | * Copyright (C) 2006 Michael Brown <mbrown@fensystems.co.uk>. |
---|
3 | * |
---|
4 | * This program is free software; you can redistribute it and/or |
---|
5 | * modify it under the terms of the GNU General Public License as |
---|
6 | * published by the Free Software Foundation; either version 2 of the |
---|
7 | * License, or any later version. |
---|
8 | * |
---|
9 | * This program is distributed in the hope that it will be useful, but |
---|
10 | * WITHOUT ANY WARRANTY; without even the implied warranty of |
---|
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
---|
12 | * General Public License for more details. |
---|
13 | * |
---|
14 | * You should have received a copy of the GNU General Public License |
---|
15 | * along with this program; if not, write to the Free Software |
---|
16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |
---|
17 | */ |
---|
18 | |
---|
19 | FILE_LICENCE ( GPL2_OR_LATER ); |
---|
20 | |
---|
21 | #include <stdint.h> |
---|
22 | #include <limits.h> |
---|
23 | #include <byteswap.h> |
---|
24 | #include <errno.h> |
---|
25 | #include <assert.h> |
---|
26 | #include <gpxe/list.h> |
---|
27 | #include <gpxe/blockdev.h> |
---|
28 | #include <gpxe/memmap.h> |
---|
29 | #include <realmode.h> |
---|
30 | #include <bios.h> |
---|
31 | #include <biosint.h> |
---|
32 | #include <bootsector.h> |
---|
33 | #include <int13.h> |
---|
34 | |
---|
35 | /** @file |
---|
36 | * |
---|
37 | * INT 13 emulation |
---|
38 | * |
---|
39 | * This module provides a mechanism for exporting block devices via |
---|
40 | * the BIOS INT 13 disk interrupt interface. |
---|
41 | * |
---|
42 | */ |
---|
43 | |
---|
44 | /** Vector for chaining to other INT 13 handlers */ |
---|
45 | static struct segoff __text16 ( int13_vector ); |
---|
46 | #define int13_vector __use_text16 ( int13_vector ) |
---|
47 | |
---|
48 | /** Assembly wrapper */ |
---|
49 | extern void int13_wrapper ( void ); |
---|
50 | |
---|
51 | /** List of registered emulated drives */ |
---|
52 | static LIST_HEAD ( drives ); |
---|
53 | |
---|
54 | /** |
---|
55 | * Number of BIOS drives |
---|
56 | * |
---|
57 | * Note that this is the number of drives in the system as a whole |
---|
58 | * (i.e. a mirror of the counter at 40:75), rather than a count of the |
---|
59 | * number of emulated drives. |
---|
60 | */ |
---|
61 | static uint8_t num_drives; |
---|
62 | |
---|
63 | /** |
---|
64 | * Update BIOS drive count |
---|
65 | */ |
---|
66 | static void int13_set_num_drives ( void ) { |
---|
67 | struct int13_drive *drive; |
---|
68 | |
---|
69 | /* Get current drive count */ |
---|
70 | get_real ( num_drives, BDA_SEG, BDA_NUM_DRIVES ); |
---|
71 | |
---|
72 | /* Ensure count is large enough to cover all of our emulated drives */ |
---|
73 | list_for_each_entry ( drive, &drives, list ) { |
---|
74 | if ( num_drives <= ( drive->drive & 0x7f ) ) |
---|
75 | num_drives = ( ( drive->drive & 0x7f ) + 1 ); |
---|
76 | } |
---|
77 | |
---|
78 | /* Update current drive count */ |
---|
79 | put_real ( num_drives, BDA_SEG, BDA_NUM_DRIVES ); |
---|
80 | } |
---|
81 | |
---|
82 | /** |
---|
83 | * Check number of drives |
---|
84 | */ |
---|
85 | static void int13_check_num_drives ( void ) { |
---|
86 | uint8_t check_num_drives; |
---|
87 | |
---|
88 | get_real ( check_num_drives, BDA_SEG, BDA_NUM_DRIVES ); |
---|
89 | if ( check_num_drives != num_drives ) { |
---|
90 | int13_set_num_drives(); |
---|
91 | DBG ( "INT13 fixing up number of drives from %d to %d\n", |
---|
92 | check_num_drives, num_drives ); |
---|
93 | } |
---|
94 | } |
---|
95 | |
---|
96 | /** |
---|
97 | * INT 13, 00 - Reset disk system |
---|
98 | * |
---|
99 | * @v drive Emulated drive |
---|
100 | * @ret status Status code |
---|
101 | */ |
---|
102 | static int int13_reset ( struct int13_drive *drive __unused, |
---|
103 | struct i386_all_regs *ix86 __unused ) { |
---|
104 | DBG ( "Reset drive\n" ); |
---|
105 | return 0; |
---|
106 | } |
---|
107 | |
---|
108 | /** |
---|
109 | * INT 13, 01 - Get status of last operation |
---|
110 | * |
---|
111 | * @v drive Emulated drive |
---|
112 | * @ret status Status code |
---|
113 | */ |
---|
114 | static int int13_get_last_status ( struct int13_drive *drive, |
---|
115 | struct i386_all_regs *ix86 __unused ) { |
---|
116 | DBG ( "Get status of last operation\n" ); |
---|
117 | return drive->last_status; |
---|
118 | } |
---|
119 | |
---|
120 | /** |
---|
121 | * Read / write sectors |
---|
122 | * |
---|
123 | * @v drive Emulated drive |
---|
124 | * @v al Number of sectors to read or write (must be nonzero) |
---|
125 | * @v ch Low bits of cylinder number |
---|
126 | * @v cl (bits 7:6) High bits of cylinder number |
---|
127 | * @v cl (bits 5:0) Sector number |
---|
128 | * @v dh Head number |
---|
129 | * @v es:bx Data buffer |
---|
130 | * @v io Read / write method |
---|
131 | * @ret status Status code |
---|
132 | * @ret al Number of sectors read or written |
---|
133 | */ |
---|
134 | static int int13_rw_sectors ( struct int13_drive *drive, |
---|
135 | struct i386_all_regs *ix86, |
---|
136 | int ( * io ) ( struct block_device *blockdev, |
---|
137 | uint64_t block, |
---|
138 | unsigned long count, |
---|
139 | userptr_t buffer ) ) { |
---|
140 | struct block_device *blockdev = drive->blockdev; |
---|
141 | unsigned int cylinder, head, sector; |
---|
142 | unsigned long lba; |
---|
143 | unsigned int count; |
---|
144 | userptr_t buffer; |
---|
145 | int rc; |
---|
146 | |
---|
147 | /* Validate blocksize */ |
---|
148 | if ( blockdev->blksize != INT13_BLKSIZE ) { |
---|
149 | DBG ( "Invalid blocksize (%zd) for non-extended read/write\n", |
---|
150 | blockdev->blksize ); |
---|
151 | return -INT13_STATUS_INVALID; |
---|
152 | } |
---|
153 | |
---|
154 | /* Calculate parameters */ |
---|
155 | cylinder = ( ( ( ix86->regs.cl & 0xc0 ) << 2 ) | ix86->regs.ch ); |
---|
156 | assert ( cylinder < drive->cylinders ); |
---|
157 | head = ix86->regs.dh; |
---|
158 | assert ( head < drive->heads ); |
---|
159 | sector = ( ix86->regs.cl & 0x3f ); |
---|
160 | assert ( ( sector >= 1 ) && ( sector <= drive->sectors_per_track ) ); |
---|
161 | lba = ( ( ( ( cylinder * drive->heads ) + head ) |
---|
162 | * drive->sectors_per_track ) + sector - 1 ); |
---|
163 | count = ix86->regs.al; |
---|
164 | buffer = real_to_user ( ix86->segs.es, ix86->regs.bx ); |
---|
165 | |
---|
166 | DBG ( "C/H/S %d/%d/%d = LBA %#lx <-> %04x:%04x (count %d)\n", cylinder, |
---|
167 | head, sector, lba, ix86->segs.es, ix86->regs.bx, count ); |
---|
168 | |
---|
169 | /* Read from / write to block device */ |
---|
170 | if ( ( rc = io ( blockdev, lba, count, buffer ) ) != 0 ) { |
---|
171 | DBG ( "INT 13 failed: %s\n", strerror ( rc ) ); |
---|
172 | return -INT13_STATUS_READ_ERROR; |
---|
173 | } |
---|
174 | |
---|
175 | return 0; |
---|
176 | } |
---|
177 | |
---|
178 | /** |
---|
179 | * INT 13, 02 - Read sectors |
---|
180 | * |
---|
181 | * @v drive Emulated drive |
---|
182 | * @v al Number of sectors to read (must be nonzero) |
---|
183 | * @v ch Low bits of cylinder number |
---|
184 | * @v cl (bits 7:6) High bits of cylinder number |
---|
185 | * @v cl (bits 5:0) Sector number |
---|
186 | * @v dh Head number |
---|
187 | * @v es:bx Data buffer |
---|
188 | * @ret status Status code |
---|
189 | * @ret al Number of sectors read |
---|
190 | */ |
---|
191 | static int int13_read_sectors ( struct int13_drive *drive, |
---|
192 | struct i386_all_regs *ix86 ) { |
---|
193 | DBG ( "Read: " ); |
---|
194 | return int13_rw_sectors ( drive, ix86, drive->blockdev->op->read ); |
---|
195 | } |
---|
196 | |
---|
197 | /** |
---|
198 | * INT 13, 03 - Write sectors |
---|
199 | * |
---|
200 | * @v drive Emulated drive |
---|
201 | * @v al Number of sectors to write (must be nonzero) |
---|
202 | * @v ch Low bits of cylinder number |
---|
203 | * @v cl (bits 7:6) High bits of cylinder number |
---|
204 | * @v cl (bits 5:0) Sector number |
---|
205 | * @v dh Head number |
---|
206 | * @v es:bx Data buffer |
---|
207 | * @ret status Status code |
---|
208 | * @ret al Number of sectors written |
---|
209 | */ |
---|
210 | static int int13_write_sectors ( struct int13_drive *drive, |
---|
211 | struct i386_all_regs *ix86 ) { |
---|
212 | DBG ( "Write: " ); |
---|
213 | return int13_rw_sectors ( drive, ix86, drive->blockdev->op->write ); |
---|
214 | } |
---|
215 | |
---|
216 | /** |
---|
217 | * INT 13, 08 - Get drive parameters |
---|
218 | * |
---|
219 | * @v drive Emulated drive |
---|
220 | * @ret status Status code |
---|
221 | * @ret ch Low bits of maximum cylinder number |
---|
222 | * @ret cl (bits 7:6) High bits of maximum cylinder number |
---|
223 | * @ret cl (bits 5:0) Maximum sector number |
---|
224 | * @ret dh Maximum head number |
---|
225 | * @ret dl Number of drives |
---|
226 | */ |
---|
227 | static int int13_get_parameters ( struct int13_drive *drive, |
---|
228 | struct i386_all_regs *ix86 ) { |
---|
229 | unsigned int max_cylinder = drive->cylinders - 1; |
---|
230 | unsigned int max_head = drive->heads - 1; |
---|
231 | unsigned int max_sector = drive->sectors_per_track; /* sic */ |
---|
232 | |
---|
233 | DBG ( "Get drive parameters\n" ); |
---|
234 | |
---|
235 | ix86->regs.ch = ( max_cylinder & 0xff ); |
---|
236 | ix86->regs.cl = ( ( ( max_cylinder >> 8 ) << 6 ) | max_sector ); |
---|
237 | ix86->regs.dh = max_head; |
---|
238 | get_real ( ix86->regs.dl, BDA_SEG, BDA_NUM_DRIVES ); |
---|
239 | return 0; |
---|
240 | } |
---|
241 | |
---|
242 | /** |
---|
243 | * INT 13, 15 - Get disk type |
---|
244 | * |
---|
245 | * @v drive Emulated drive |
---|
246 | * @ret ah Type code |
---|
247 | * @ret cx:dx Sector count |
---|
248 | * @ret status Status code / disk type |
---|
249 | */ |
---|
250 | static int int13_get_disk_type ( struct int13_drive *drive, |
---|
251 | struct i386_all_regs *ix86 ) { |
---|
252 | uint32_t blocks; |
---|
253 | |
---|
254 | DBG ( "Get disk type\n" ); |
---|
255 | blocks = ( ( drive->blockdev->blocks <= 0xffffffffUL ) ? |
---|
256 | drive->blockdev->blocks : 0xffffffffUL ); |
---|
257 | ix86->regs.cx = ( blocks >> 16 ); |
---|
258 | ix86->regs.dx = ( blocks & 0xffff ); |
---|
259 | return INT13_DISK_TYPE_HDD; |
---|
260 | } |
---|
261 | |
---|
262 | /** |
---|
263 | * INT 13, 41 - Extensions installation check |
---|
264 | * |
---|
265 | * @v drive Emulated drive |
---|
266 | * @v bx 0x55aa |
---|
267 | * @ret bx 0xaa55 |
---|
268 | * @ret cx Extensions API support bitmap |
---|
269 | * @ret status Status code / API version |
---|
270 | */ |
---|
271 | static int int13_extension_check ( struct int13_drive *drive __unused, |
---|
272 | struct i386_all_regs *ix86 ) { |
---|
273 | if ( ix86->regs.bx == 0x55aa ) { |
---|
274 | DBG ( "INT 13 extensions installation check\n" ); |
---|
275 | ix86->regs.bx = 0xaa55; |
---|
276 | ix86->regs.cx = INT13_EXTENSION_LINEAR; |
---|
277 | return INT13_EXTENSION_VER_1_X; |
---|
278 | } else { |
---|
279 | return -INT13_STATUS_INVALID; |
---|
280 | } |
---|
281 | } |
---|
282 | |
---|
283 | /** |
---|
284 | * Extended read / write |
---|
285 | * |
---|
286 | * @v drive Emulated drive |
---|
287 | * @v ds:si Disk address packet |
---|
288 | * @v io Read / write method |
---|
289 | * @ret status Status code |
---|
290 | */ |
---|
291 | static int int13_extended_rw ( struct int13_drive *drive, |
---|
292 | struct i386_all_regs *ix86, |
---|
293 | int ( * io ) ( struct block_device *blockdev, |
---|
294 | uint64_t block, |
---|
295 | unsigned long count, |
---|
296 | userptr_t buffer ) ) { |
---|
297 | struct block_device *blockdev = drive->blockdev; |
---|
298 | struct int13_disk_address addr; |
---|
299 | uint64_t lba; |
---|
300 | unsigned long count; |
---|
301 | userptr_t buffer; |
---|
302 | int rc; |
---|
303 | |
---|
304 | /* Read parameters from disk address structure */ |
---|
305 | copy_from_real ( &addr, ix86->segs.ds, ix86->regs.si, sizeof ( addr )); |
---|
306 | lba = addr.lba; |
---|
307 | count = addr.count; |
---|
308 | buffer = real_to_user ( addr.buffer.segment, addr.buffer.offset ); |
---|
309 | |
---|
310 | DBG ( "LBA %#llx <-> %04x:%04x (count %ld)\n", (unsigned long long)lba, |
---|
311 | addr.buffer.segment, addr.buffer.offset, count ); |
---|
312 | |
---|
313 | /* Read from / write to block device */ |
---|
314 | if ( ( rc = io ( blockdev, lba, count, buffer ) ) != 0 ) { |
---|
315 | DBG ( "INT 13 failed: %s\n", strerror ( rc ) ); |
---|
316 | return -INT13_STATUS_READ_ERROR; |
---|
317 | } |
---|
318 | |
---|
319 | return 0; |
---|
320 | } |
---|
321 | |
---|
322 | /** |
---|
323 | * INT 13, 42 - Extended read |
---|
324 | * |
---|
325 | * @v drive Emulated drive |
---|
326 | * @v ds:si Disk address packet |
---|
327 | * @ret status Status code |
---|
328 | */ |
---|
329 | static int int13_extended_read ( struct int13_drive *drive, |
---|
330 | struct i386_all_regs *ix86 ) { |
---|
331 | DBG ( "Extended read: " ); |
---|
332 | return int13_extended_rw ( drive, ix86, drive->blockdev->op->read ); |
---|
333 | } |
---|
334 | |
---|
335 | /** |
---|
336 | * INT 13, 43 - Extended write |
---|
337 | * |
---|
338 | * @v drive Emulated drive |
---|
339 | * @v ds:si Disk address packet |
---|
340 | * @ret status Status code |
---|
341 | */ |
---|
342 | static int int13_extended_write ( struct int13_drive *drive, |
---|
343 | struct i386_all_regs *ix86 ) { |
---|
344 | DBG ( "Extended write: " ); |
---|
345 | return int13_extended_rw ( drive, ix86, drive->blockdev->op->write ); |
---|
346 | } |
---|
347 | |
---|
348 | /** |
---|
349 | * INT 13, 48 - Get extended parameters |
---|
350 | * |
---|
351 | * @v drive Emulated drive |
---|
352 | * @v ds:si Drive parameter table |
---|
353 | * @ret status Status code |
---|
354 | */ |
---|
355 | static int int13_get_extended_parameters ( struct int13_drive *drive, |
---|
356 | struct i386_all_regs *ix86 ) { |
---|
357 | struct int13_disk_parameters params = { |
---|
358 | .bufsize = sizeof ( params ), |
---|
359 | .flags = INT13_FL_DMA_TRANSPARENT, |
---|
360 | .cylinders = drive->cylinders, |
---|
361 | .heads = drive->heads, |
---|
362 | .sectors_per_track = drive->sectors_per_track, |
---|
363 | .sectors = drive->blockdev->blocks, |
---|
364 | .sector_size = drive->blockdev->blksize, |
---|
365 | }; |
---|
366 | |
---|
367 | DBG ( "Get extended drive parameters to %04x:%04x\n", |
---|
368 | ix86->segs.ds, ix86->regs.si ); |
---|
369 | |
---|
370 | copy_to_real ( ix86->segs.ds, ix86->regs.si, ¶ms, |
---|
371 | sizeof ( params ) ); |
---|
372 | return 0; |
---|
373 | } |
---|
374 | |
---|
375 | /** |
---|
376 | * INT 13 handler |
---|
377 | * |
---|
378 | */ |
---|
379 | static __asmcall void int13 ( struct i386_all_regs *ix86 ) { |
---|
380 | int command = ix86->regs.ah; |
---|
381 | unsigned int bios_drive = ix86->regs.dl; |
---|
382 | struct int13_drive *drive; |
---|
383 | int status; |
---|
384 | |
---|
385 | /* Check BIOS hasn't killed off our drive */ |
---|
386 | int13_check_num_drives(); |
---|
387 | |
---|
388 | list_for_each_entry ( drive, &drives, list ) { |
---|
389 | |
---|
390 | if ( bios_drive != drive->drive ) { |
---|
391 | /* Remap any accesses to this drive's natural number */ |
---|
392 | if ( bios_drive == drive->natural_drive ) { |
---|
393 | DBG ( "INT 13,%04x (%02x) remapped to " |
---|
394 | "(%02x)\n", ix86->regs.ax, |
---|
395 | bios_drive, drive->drive ); |
---|
396 | ix86->regs.dl = drive->drive; |
---|
397 | return; |
---|
398 | } |
---|
399 | continue; |
---|
400 | } |
---|
401 | |
---|
402 | DBG ( "INT 13,%04x (%02x): ", ix86->regs.ax, drive->drive ); |
---|
403 | |
---|
404 | switch ( command ) { |
---|
405 | case INT13_RESET: |
---|
406 | status = int13_reset ( drive, ix86 ); |
---|
407 | break; |
---|
408 | case INT13_GET_LAST_STATUS: |
---|
409 | status = int13_get_last_status ( drive, ix86 ); |
---|
410 | break; |
---|
411 | case INT13_READ_SECTORS: |
---|
412 | status = int13_read_sectors ( drive, ix86 ); |
---|
413 | break; |
---|
414 | case INT13_WRITE_SECTORS: |
---|
415 | status = int13_write_sectors ( drive, ix86 ); |
---|
416 | break; |
---|
417 | case INT13_GET_PARAMETERS: |
---|
418 | status = int13_get_parameters ( drive, ix86 ); |
---|
419 | break; |
---|
420 | case INT13_GET_DISK_TYPE: |
---|
421 | status = int13_get_disk_type ( drive, ix86 ); |
---|
422 | break; |
---|
423 | case INT13_EXTENSION_CHECK: |
---|
424 | status = int13_extension_check ( drive, ix86 ); |
---|
425 | break; |
---|
426 | case INT13_EXTENDED_READ: |
---|
427 | status = int13_extended_read ( drive, ix86 ); |
---|
428 | break; |
---|
429 | case INT13_EXTENDED_WRITE: |
---|
430 | status = int13_extended_write ( drive, ix86 ); |
---|
431 | break; |
---|
432 | case INT13_GET_EXTENDED_PARAMETERS: |
---|
433 | status = int13_get_extended_parameters ( drive, ix86 ); |
---|
434 | break; |
---|
435 | default: |
---|
436 | DBG ( "*** Unrecognised INT 13 ***\n" ); |
---|
437 | status = -INT13_STATUS_INVALID; |
---|
438 | break; |
---|
439 | } |
---|
440 | |
---|
441 | /* Store status for INT 13,01 */ |
---|
442 | drive->last_status = status; |
---|
443 | |
---|
444 | /* Negative status indicates an error */ |
---|
445 | if ( status < 0 ) { |
---|
446 | status = -status; |
---|
447 | DBG ( "INT 13 returning failure status %x\n", status ); |
---|
448 | } else { |
---|
449 | ix86->flags &= ~CF; |
---|
450 | } |
---|
451 | ix86->regs.ah = status; |
---|
452 | |
---|
453 | /* Set OF to indicate to wrapper not to chain this call */ |
---|
454 | ix86->flags |= OF; |
---|
455 | |
---|
456 | return; |
---|
457 | } |
---|
458 | } |
---|
459 | |
---|
460 | /** |
---|
461 | * Hook INT 13 handler |
---|
462 | * |
---|
463 | */ |
---|
464 | static void hook_int13 ( void ) { |
---|
465 | /* Assembly wrapper to call int13(). int13() sets OF if we |
---|
466 | * should not chain to the previous handler. (The wrapper |
---|
467 | * clears CF and OF before calling int13()). |
---|
468 | */ |
---|
469 | __asm__ __volatile__ ( |
---|
470 | TEXT16_CODE ( "\nint13_wrapper:\n\t" |
---|
471 | /* Preserve %ax and %dx for future reference */ |
---|
472 | "pushw %%bp\n\t" |
---|
473 | "movw %%sp, %%bp\n\t" |
---|
474 | "pushw %%ax\n\t" |
---|
475 | "pushw %%dx\n\t" |
---|
476 | /* Clear OF, set CF, call int13() */ |
---|
477 | "orb $0, %%al\n\t" |
---|
478 | "stc\n\t" |
---|
479 | "pushl %0\n\t" |
---|
480 | "pushw %%cs\n\t" |
---|
481 | "call prot_call\n\t" |
---|
482 | /* Chain if OF not set */ |
---|
483 | "jo 1f\n\t" |
---|
484 | "pushfw\n\t" |
---|
485 | "lcall *%%cs:int13_vector\n\t" |
---|
486 | "\n1:\n\t" |
---|
487 | /* Overwrite flags for iret */ |
---|
488 | "pushfw\n\t" |
---|
489 | "popw 6(%%bp)\n\t" |
---|
490 | /* Fix up %dl: |
---|
491 | * |
---|
492 | * INT 13,15 : do nothing |
---|
493 | * INT 13,08 : load with number of drives |
---|
494 | * all others: restore original value |
---|
495 | */ |
---|
496 | "cmpb $0x15, -1(%%bp)\n\t" |
---|
497 | "je 2f\n\t" |
---|
498 | "movb -4(%%bp), %%dl\n\t" |
---|
499 | "cmpb $0x08, -1(%%bp)\n\t" |
---|
500 | "jne 2f\n\t" |
---|
501 | "pushw %%ds\n\t" |
---|
502 | "pushw %1\n\t" |
---|
503 | "popw %%ds\n\t" |
---|
504 | "movb %c2, %%dl\n\t" |
---|
505 | "popw %%ds\n\t" |
---|
506 | /* Return */ |
---|
507 | "\n2:\n\t" |
---|
508 | "movw %%bp, %%sp\n\t" |
---|
509 | "popw %%bp\n\t" |
---|
510 | "iret\n\t" ) |
---|
511 | : : "i" ( int13 ), "i" ( BDA_SEG ), "i" ( BDA_NUM_DRIVES ) ); |
---|
512 | |
---|
513 | hook_bios_interrupt ( 0x13, ( unsigned int ) int13_wrapper, |
---|
514 | &int13_vector ); |
---|
515 | } |
---|
516 | |
---|
517 | /** |
---|
518 | * Unhook INT 13 handler |
---|
519 | */ |
---|
520 | static void unhook_int13 ( void ) { |
---|
521 | unhook_bios_interrupt ( 0x13, ( unsigned int ) int13_wrapper, |
---|
522 | &int13_vector ); |
---|
523 | } |
---|
524 | |
---|
525 | /** |
---|
526 | * Guess INT 13 drive geometry |
---|
527 | * |
---|
528 | * @v drive Emulated drive |
---|
529 | * |
---|
530 | * Guesses the drive geometry by inspecting the partition table. |
---|
531 | */ |
---|
532 | static void guess_int13_geometry ( struct int13_drive *drive ) { |
---|
533 | struct master_boot_record mbr; |
---|
534 | struct partition_table_entry *partition; |
---|
535 | unsigned int guessed_heads = 255; |
---|
536 | unsigned int guessed_sectors_per_track = 63; |
---|
537 | unsigned long blocks; |
---|
538 | unsigned long blocks_per_cyl; |
---|
539 | unsigned int i; |
---|
540 | |
---|
541 | /* Don't even try when the blksize is invalid for C/H/S access */ |
---|
542 | if ( drive->blockdev->blksize != INT13_BLKSIZE ) |
---|
543 | return; |
---|
544 | |
---|
545 | /* Scan through partition table and modify guesses for heads |
---|
546 | * and sectors_per_track if we find any used partitions. |
---|
547 | */ |
---|
548 | if ( drive->blockdev->op->read ( drive->blockdev, 0, 1, |
---|
549 | virt_to_user ( &mbr ) ) == 0 ) { |
---|
550 | for ( i = 0 ; i < 4 ; i++ ) { |
---|
551 | partition = &mbr.partitions[i]; |
---|
552 | if ( ! partition->type ) |
---|
553 | continue; |
---|
554 | guessed_heads = |
---|
555 | ( PART_HEAD ( partition->chs_end ) + 1 ); |
---|
556 | guessed_sectors_per_track = |
---|
557 | PART_SECTOR ( partition->chs_end ); |
---|
558 | DBG ( "Guessing C/H/S xx/%d/%d based on partition " |
---|
559 | "%d\n", guessed_heads, |
---|
560 | guessed_sectors_per_track, ( i + 1 ) ); |
---|
561 | } |
---|
562 | } else { |
---|
563 | DBG ( "Could not read partition table to guess geometry\n" ); |
---|
564 | } |
---|
565 | |
---|
566 | /* Apply guesses if no geometry already specified */ |
---|
567 | if ( ! drive->heads ) |
---|
568 | drive->heads = guessed_heads; |
---|
569 | if ( ! drive->sectors_per_track ) |
---|
570 | drive->sectors_per_track = guessed_sectors_per_track; |
---|
571 | if ( ! drive->cylinders ) { |
---|
572 | /* Avoid attempting a 64-bit divide on a 32-bit system */ |
---|
573 | blocks = ( ( drive->blockdev->blocks <= ULONG_MAX ) ? |
---|
574 | drive->blockdev->blocks : ULONG_MAX ); |
---|
575 | blocks_per_cyl = ( drive->heads * drive->sectors_per_track ); |
---|
576 | assert ( blocks_per_cyl != 0 ); |
---|
577 | drive->cylinders = ( blocks / blocks_per_cyl ); |
---|
578 | if ( drive->cylinders > 1024 ) |
---|
579 | drive->cylinders = 1024; |
---|
580 | } |
---|
581 | } |
---|
582 | |
---|
583 | /** |
---|
584 | * Register INT 13 emulated drive |
---|
585 | * |
---|
586 | * @v drive Emulated drive |
---|
587 | * |
---|
588 | * Registers the drive with the INT 13 emulation subsystem, and hooks |
---|
589 | * the INT 13 interrupt vector (if not already hooked). |
---|
590 | * |
---|
591 | * The underlying block device must be valid. A drive number and |
---|
592 | * geometry will be assigned if left blank. |
---|
593 | */ |
---|
594 | void register_int13_drive ( struct int13_drive *drive ) { |
---|
595 | uint8_t num_drives; |
---|
596 | |
---|
597 | /* Give drive a default geometry if none specified */ |
---|
598 | guess_int13_geometry ( drive ); |
---|
599 | |
---|
600 | /* Assign natural drive number */ |
---|
601 | get_real ( num_drives, BDA_SEG, BDA_NUM_DRIVES ); |
---|
602 | drive->natural_drive = ( num_drives | 0x80 ); |
---|
603 | |
---|
604 | /* Assign drive number */ |
---|
605 | if ( ( drive->drive & 0xff ) == 0xff ) { |
---|
606 | /* Drive number == -1 => use natural drive number */ |
---|
607 | drive->drive = drive->natural_drive; |
---|
608 | } else { |
---|
609 | /* Use specified drive number (+0x80 if necessary) */ |
---|
610 | drive->drive |= 0x80; |
---|
611 | } |
---|
612 | |
---|
613 | DBG ( "Registered INT13 drive %02x (naturally %02x) with C/H/S " |
---|
614 | "geometry %d/%d/%d\n", drive->drive, drive->natural_drive, |
---|
615 | drive->cylinders, drive->heads, drive->sectors_per_track ); |
---|
616 | |
---|
617 | /* Hook INT 13 vector if not already hooked */ |
---|
618 | if ( list_empty ( &drives ) ) |
---|
619 | hook_int13(); |
---|
620 | |
---|
621 | /* Add to list of emulated drives */ |
---|
622 | list_add ( &drive->list, &drives ); |
---|
623 | |
---|
624 | /* Update BIOS drive count */ |
---|
625 | int13_set_num_drives(); |
---|
626 | } |
---|
627 | |
---|
628 | /** |
---|
629 | * Unregister INT 13 emulated drive |
---|
630 | * |
---|
631 | * @v drive Emulated drive |
---|
632 | * |
---|
633 | * Unregisters the drive from the INT 13 emulation subsystem. If this |
---|
634 | * is the last emulated drive, the INT 13 vector is unhooked (if |
---|
635 | * possible). |
---|
636 | */ |
---|
637 | void unregister_int13_drive ( struct int13_drive *drive ) { |
---|
638 | /* Remove from list of emulated drives */ |
---|
639 | list_del ( &drive->list ); |
---|
640 | |
---|
641 | /* Should adjust BIOS drive count, but it's difficult to do so |
---|
642 | * reliably. |
---|
643 | */ |
---|
644 | |
---|
645 | DBG ( "Unregistered INT13 drive %02x\n", drive->drive ); |
---|
646 | |
---|
647 | /* Unhook INT 13 vector if no more drives */ |
---|
648 | if ( list_empty ( &drives ) ) |
---|
649 | unhook_int13(); |
---|
650 | } |
---|
651 | |
---|
652 | /** |
---|
653 | * Attempt to boot from an INT 13 drive |
---|
654 | * |
---|
655 | * @v drive Drive number |
---|
656 | * @ret rc Return status code |
---|
657 | * |
---|
658 | * This boots from the specified INT 13 drive by loading the Master |
---|
659 | * Boot Record to 0000:7c00 and jumping to it. INT 18 is hooked to |
---|
660 | * capture an attempt by the MBR to boot the next device. (This is |
---|
661 | * the closest thing to a return path from an MBR). |
---|
662 | * |
---|
663 | * Note that this function can never return success, by definition. |
---|
664 | */ |
---|
665 | int int13_boot ( unsigned int drive ) { |
---|
666 | struct memory_map memmap; |
---|
667 | int status, signature; |
---|
668 | int discard_c, discard_d; |
---|
669 | int rc; |
---|
670 | |
---|
671 | DBG ( "Booting from INT 13 drive %02x\n", drive ); |
---|
672 | |
---|
673 | /* Use INT 13 to read the boot sector */ |
---|
674 | __asm__ __volatile__ ( REAL_CODE ( "pushw %%es\n\t" |
---|
675 | "pushw $0\n\t" |
---|
676 | "popw %%es\n\t" |
---|
677 | "stc\n\t" |
---|
678 | "sti\n\t" |
---|
679 | "int $0x13\n\t" |
---|
680 | "sti\n\t" /* BIOS bugs */ |
---|
681 | "jc 1f\n\t" |
---|
682 | "xorl %%eax, %%eax\n\t" |
---|
683 | "\n1:\n\t" |
---|
684 | "movzwl %%es:0x7dfe, %%ebx\n\t" |
---|
685 | "popw %%es\n\t" ) |
---|
686 | : "=a" ( status ), "=b" ( signature ), |
---|
687 | "=c" ( discard_c ), "=d" ( discard_d ) |
---|
688 | : "a" ( 0x0201 ), "b" ( 0x7c00 ), |
---|
689 | "c" ( 1 ), "d" ( drive ) ); |
---|
690 | if ( status ) |
---|
691 | return -EIO; |
---|
692 | |
---|
693 | /* Check signature is correct */ |
---|
694 | if ( signature != be16_to_cpu ( 0x55aa ) ) { |
---|
695 | DBG ( "Invalid disk signature %#04x (should be 0x55aa)\n", |
---|
696 | cpu_to_be16 ( signature ) ); |
---|
697 | return -ENOEXEC; |
---|
698 | } |
---|
699 | |
---|
700 | /* Dump out memory map prior to boot, if memmap debugging is |
---|
701 | * enabled. Not required for program flow, but we have so |
---|
702 | * many problems that turn out to be memory-map related that |
---|
703 | * it's worth doing. |
---|
704 | */ |
---|
705 | get_memmap ( &memmap ); |
---|
706 | |
---|
707 | /* Jump to boot sector */ |
---|
708 | if ( ( rc = call_bootsector ( 0x0, 0x7c00, drive ) ) != 0 ) { |
---|
709 | DBG ( "INT 13 drive %02x boot returned: %s\n", |
---|
710 | drive, strerror ( rc ) ); |
---|
711 | return rc; |
---|
712 | } |
---|
713 | |
---|
714 | return -ECANCELED; /* -EIMPOSSIBLE */ |
---|
715 | } |
---|