2 * Copyright (C) 2006 Michael Brown <mbrown@fensystems.co.uk>.
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.
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.
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.
19 FILE_LICENCE ( GPL2_OR_LATER
);
26 #include <gpxe/list.h>
27 #include <gpxe/blockdev.h>
28 #include <gpxe/memmap.h>
32 #include <bootsector.h>
39 * This module provides a mechanism for exporting block devices via
40 * the BIOS INT 13 disk interrupt interface.
44 /** Vector for chaining to other INT 13 handlers */
45 static struct segoff
__text16 ( int13_vector
);
46 #define int13_vector __use_text16 ( int13_vector )
48 /** Assembly wrapper */
49 extern void int13_wrapper ( void );
51 /** List of registered emulated drives */
52 static LIST_HEAD ( drives
);
55 * Number of BIOS drives
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.
61 static uint8_t num_drives
;
64 * Update BIOS drive count
66 static void int13_set_num_drives ( void ) {
67 struct int13_drive
*drive
;
69 /* Get current drive count */
70 get_real ( num_drives
, BDA_SEG
, BDA_NUM_DRIVES
);
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 );
78 /* Update current drive count */
79 put_real ( num_drives
, BDA_SEG
, BDA_NUM_DRIVES
);
83 * Check number of drives
85 static void int13_check_num_drives ( void ) {
86 uint8_t check_num_drives
;
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
);
97 * INT 13, 00 - Reset disk system
99 * @v drive Emulated drive
100 * @ret status Status code
102 static int int13_reset ( struct int13_drive
*drive __unused
,
103 struct i386_all_regs
*ix86 __unused
) {
104 DBG ( "Reset drive\n" );
109 * INT 13, 01 - Get status of last operation
111 * @v drive Emulated drive
112 * @ret status Status code
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
;
121 * Read / write sectors
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
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
134 static int int13_rw_sectors ( struct int13_drive
*drive
,
135 struct i386_all_regs
*ix86
,
136 int ( * io
) ( struct block_device
*blockdev
,
139 userptr_t buffer
) ) {
140 struct block_device
*blockdev
= drive
->blockdev
;
141 unsigned int cylinder
, head
, sector
;
147 /* Validate blocksize */
148 if ( blockdev
->blksize
!= INT13_BLKSIZE
) {
149 DBG ( "Invalid blocksize (%zd) for non-extended read/write\n",
151 return -INT13_STATUS_INVALID
;
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
);
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
);
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
;
179 * INT 13, 02 - Read sectors
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
187 * @v es:bx Data buffer
188 * @ret status Status code
189 * @ret al Number of sectors read
191 static int int13_read_sectors ( struct int13_drive
*drive
,
192 struct i386_all_regs
*ix86
) {
194 return int13_rw_sectors ( drive
, ix86
, drive
->blockdev
->op
->read
);
198 * INT 13, 03 - Write sectors
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
206 * @v es:bx Data buffer
207 * @ret status Status code
208 * @ret al Number of sectors written
210 static int int13_write_sectors ( struct int13_drive
*drive
,
211 struct i386_all_regs
*ix86
) {
213 return int13_rw_sectors ( drive
, ix86
, drive
->blockdev
->op
->write
);
217 * INT 13, 08 - Get drive parameters
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
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 */
233 DBG ( "Get drive parameters\n" );
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
);
243 * INT 13, 15 - Get disk type
245 * @v drive Emulated drive
247 * @ret cx:dx Sector count
248 * @ret status Status code / disk type
250 static int int13_get_disk_type ( struct int13_drive
*drive
,
251 struct i386_all_regs
*ix86
) {
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
;
263 * INT 13, 41 - Extensions installation check
265 * @v drive Emulated drive
268 * @ret cx Extensions API support bitmap
269 * @ret status Status code / API version
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
;
279 return -INT13_STATUS_INVALID
;
284 * Extended read / write
286 * @v drive Emulated drive
287 * @v ds:si Disk address packet
288 * @v io Read / write method
289 * @ret status Status code
291 static int int13_extended_rw ( struct int13_drive
*drive
,
292 struct i386_all_regs
*ix86
,
293 int ( * io
) ( struct block_device
*blockdev
,
296 userptr_t buffer
) ) {
297 struct block_device
*blockdev
= drive
->blockdev
;
298 struct int13_disk_address addr
;
304 /* Read parameters from disk address structure */
305 copy_from_real ( &addr
, ix86
->segs
.ds
, ix86
->regs
.si
, sizeof ( addr
));
308 buffer
= real_to_user ( addr
.buffer
.segment
, addr
.buffer
.offset
);
310 DBG ( "LBA %#llx <-> %04x:%04x (count %ld)\n", (unsigned long long)lba
,
311 addr
.buffer
.segment
, addr
.buffer
.offset
, count
);
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
;
323 * INT 13, 42 - Extended read
325 * @v drive Emulated drive
326 * @v ds:si Disk address packet
327 * @ret status Status code
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
);
336 * INT 13, 43 - Extended write
338 * @v drive Emulated drive
339 * @v ds:si Disk address packet
340 * @ret status Status code
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
);
349 * INT 13, 48 - Get extended parameters
351 * @v drive Emulated drive
352 * @v ds:si Drive parameter table
353 * @ret status Status code
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
,
367 DBG ( "Get extended drive parameters to %04x:%04x\n",
368 ix86
->segs
.ds
, ix86
->regs
.si
);
370 copy_to_real ( ix86
->segs
.ds
, ix86
->regs
.si
, ¶ms
,
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
;
385 /* Check BIOS hasn't killed off our drive */
386 int13_check_num_drives();
388 list_for_each_entry ( drive
, &drives
, list
) {
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
;
402 DBG ( "INT 13,%04x (%02x): ", ix86
->regs
.ax
, drive
->drive
);
406 status
= int13_reset ( drive
, ix86
);
408 case INT13_GET_LAST_STATUS
:
409 status
= int13_get_last_status ( drive
, ix86
);
411 case INT13_READ_SECTORS
:
412 status
= int13_read_sectors ( drive
, ix86
);
414 case INT13_WRITE_SECTORS
:
415 status
= int13_write_sectors ( drive
, ix86
);
417 case INT13_GET_PARAMETERS
:
418 status
= int13_get_parameters ( drive
, ix86
);
420 case INT13_GET_DISK_TYPE
:
421 status
= int13_get_disk_type ( drive
, ix86
);
423 case INT13_EXTENSION_CHECK
:
424 status
= int13_extension_check ( drive
, ix86
);
426 case INT13_EXTENDED_READ
:
427 status
= int13_extended_read ( drive
, ix86
);
429 case INT13_EXTENDED_WRITE
:
430 status
= int13_extended_write ( drive
, ix86
);
432 case INT13_GET_EXTENDED_PARAMETERS
:
433 status
= int13_get_extended_parameters ( drive
, ix86
);
436 DBG ( "*** Unrecognised INT 13 ***\n" );
437 status
= -INT13_STATUS_INVALID
;
441 /* Store status for INT 13,01 */
442 drive
->last_status
= status
;
444 /* Negative status indicates an error */
447 DBG ( "INT 13 returning failure status %x\n", status
);
451 ix86
->regs
.ah
= status
;
453 /* Set OF to indicate to wrapper not to chain this call */
461 * Hook INT 13 handler
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()).
469 __asm__
__volatile__ (
470 TEXT16_CODE ( "\nint13_wrapper:\n\t"
471 /* Preserve %ax and %dx for future reference */
473 "movw %%sp, %%bp\n\t"
476 /* Clear OF, set CF, call int13() */
482 /* Chain if OF not set */
485 "lcall *%%cs:int13_vector\n\t"
487 /* Overwrite flags for iret */
492 * INT 13,15 : do nothing
493 * INT 13,08 : load with number of drives
494 * all others: restore original value
496 "cmpb $0x15, -1(%%bp)\n\t"
498 "movb -4(%%bp), %%dl\n\t"
499 "cmpb $0x08, -1(%%bp)\n\t"
508 "movw %%bp, %%sp\n\t"
511 : : "i" ( int13
), "i" ( BDA_SEG
), "i" ( BDA_NUM_DRIVES
) );
513 hook_bios_interrupt ( 0x13, ( unsigned int ) int13_wrapper
,
518 * Unhook INT 13 handler
520 static void unhook_int13 ( void ) {
521 unhook_bios_interrupt ( 0x13, ( unsigned int ) int13_wrapper
,
526 * Guess INT 13 drive geometry
528 * @v drive Emulated drive
530 * Guesses the drive geometry by inspecting the partition table.
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
;
541 /* Don't even try when the blksize is invalid for C/H/S access */
542 if ( drive
->blockdev
->blksize
!= INT13_BLKSIZE
)
545 /* Scan through partition table and modify guesses for heads
546 * and sectors_per_track if we find any used partitions.
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
)
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 ) );
563 DBG ( "Could not read partition table to guess geometry\n" );
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;
584 * Register INT 13 emulated drive
586 * @v drive Emulated drive
588 * Registers the drive with the INT 13 emulation subsystem, and hooks
589 * the INT 13 interrupt vector (if not already hooked).
591 * The underlying block device must be valid. A drive number and
592 * geometry will be assigned if left blank.
594 void register_int13_drive ( struct int13_drive
*drive
) {
597 /* Give drive a default geometry if none specified */
598 guess_int13_geometry ( drive
);
600 /* Assign natural drive number */
601 get_real ( num_drives
, BDA_SEG
, BDA_NUM_DRIVES
);
602 drive
->natural_drive
= ( num_drives
| 0x80 );
604 /* Assign drive number */
605 if ( ( drive
->drive
& 0xff ) == 0xff ) {
606 /* Drive number == -1 => use natural drive number */
607 drive
->drive
= drive
->natural_drive
;
609 /* Use specified drive number (+0x80 if necessary) */
610 drive
->drive
|= 0x80;
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
);
617 /* Hook INT 13 vector if not already hooked */
618 if ( list_empty ( &drives
) )
621 /* Add to list of emulated drives */
622 list_add ( &drive
->list
, &drives
);
624 /* Update BIOS drive count */
625 int13_set_num_drives();
629 * Unregister INT 13 emulated drive
631 * @v drive Emulated drive
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
637 void unregister_int13_drive ( struct int13_drive
*drive
) {
638 /* Remove from list of emulated drives */
639 list_del ( &drive
->list
);
641 /* Should adjust BIOS drive count, but it's difficult to do so
645 DBG ( "Unregistered INT13 drive %02x\n", drive
->drive
);
647 /* Unhook INT 13 vector if no more drives */
648 if ( list_empty ( &drives
) )
653 * Attempt to boot from an INT 13 drive
655 * @v drive Drive number
656 * @ret rc Return status code
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).
663 * Note that this function can never return success, by definition.
665 int int13_boot ( unsigned int drive
) {
666 struct memory_map memmap
;
667 int status
, signature
;
668 int discard_c
, discard_d
;
671 DBG ( "Booting from INT 13 drive %02x\n", drive
);
673 /* Use INT 13 to read the boot sector */
674 __asm__
__volatile__ ( REAL_CODE ( "pushw %%es\n\t"
680 "sti\n\t" /* BIOS bugs */
682 "xorl %%eax, %%eax\n\t"
684 "movzwl %%es:0x7dfe, %%ebx\n\t"
686 : "=a" ( status
), "=b" ( signature
),
687 "=c" ( discard_c
), "=d" ( discard_d
)
688 : "a" ( 0x0201 ), "b" ( 0x7c00 ),
689 "c" ( 1 ), "d" ( drive
) );
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
) );
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
705 get_memmap ( &memmap
);
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
) );
714 return -ECANCELED
; /* -EIMPOSSIBLE */