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.
24 #include <gpxe/list.h>
25 #include <gpxe/blockdev.h>
35 * This module provides a mechanism for exporting block devices via
36 * the BIOS INT 13 disk interrupt interface.
40 /** Vector for chaining to other INT 13 handlers */
41 static struct segoff __text16 ( int13_vector );
42 #define int13_vector __use_text16 ( int13_vector )
44 /** Assembly wrapper */
45 extern void int13_wrapper ( void );
47 /** Vector for storing original INT 18 handler
49 * We do not chain to this vector, so there is no need to place it in
52 static struct segoff int18_vector;
54 /** Restart point for INT 18 */
55 extern void int13_exec_fail ( void );
57 /** List of registered emulated drives */
58 static LIST_HEAD ( drives );
61 * Convert CHS address to linear address
63 * @v drive Emulated drive
64 * @v ch Low bits of cylinder number
65 * @v cl (bits 7:6) High bits of cylinder number
66 * @v cl (bits 5:0) Sector number
68 * @ret lba LBA address
71 static unsigned long chs_to_lba ( struct int13_drive *drive,
72 struct i386_all_regs *ix86 ) {
73 unsigned int cylinder;
78 cylinder = ( ( ( ix86->regs.cl & 0xc0 ) << 8 ) | ix86->regs.ch );
80 sector = ( ix86->regs.cl & 0x3f );
82 assert ( cylinder < drive->cylinders );
83 assert ( head < drive->heads );
84 assert ( ( sector >= 1 ) && ( sector <= drive->sectors_per_track ) );
86 lba = ( ( ( ( cylinder * drive->heads ) + head )
87 * drive->sectors_per_track ) + sector - 1 );
89 DBG ( "C/H/S address %x/%x/%x -> LBA %x\n",
90 cylinder, head, sector, lba );
96 * Read from drive to real-mode data buffer
98 * @v drive Emulated drive
99 * @v lba LBA starting sector number
100 * @v data Data buffer
101 * @v count Number of sectors to read
102 * @ret status Status code
104 static int int13_read ( struct int13_drive *drive, uint64_t lba,
105 struct segoff data, unsigned long count ) {
106 struct block_device *blockdev = drive->blockdev;
107 size_t blksize = blockdev->blksize;
108 uint8_t buffer[blksize];
111 DBG ( "Read %lx sectors from %llx to %04x:%04x\n", count,
112 ( unsigned long long ) lba, data.segment, data.offset );
114 if ( ( rc = blockdev->read ( blockdev, lba, buffer ) ) != 0 )
115 return INT13_STATUS_READ_ERROR;
116 copy_to_real ( data.segment, data.offset, buffer, blksize );
117 data.offset += blksize;
124 * Write from real-mode data buffer to drive
126 * @v drive Emulated drive
127 * @v lba LBA starting sector number
128 * @v data Data buffer
129 * @v count Number of sectors to read
130 * @ret status Status code
132 static int int13_write ( struct int13_drive *drive, uint64_t lba,
133 struct segoff data, unsigned long count ) {
134 struct block_device *blockdev = drive->blockdev;
135 size_t blksize = blockdev->blksize;
136 uint8_t buffer[blksize];
139 DBG ( "Write %lx sectors from %04x:%04x to %llx\n", count,
140 data.segment, data.offset, ( unsigned long long ) lba );
142 copy_from_real ( buffer, data.segment, data.offset, blksize );
143 if ( ( rc = blockdev->write ( blockdev, lba, buffer ) ) != 0 )
144 return INT13_STATUS_WRITE_ERROR;
145 data.offset += blksize;
152 * INT 13, 00 - Reset disk system
154 * @v drive Emulated drive
155 * @ret status Status code
157 static int int13_reset ( struct int13_drive *drive __unused,
158 struct i386_all_regs *ix86 __unused ) {
159 DBG ( "Reset drive\n" );
164 * INT 13, 01 - Get status of last operation
166 * @v drive Emulated drive
167 * @ret status Status code
169 static int int13_get_last_status ( struct int13_drive *drive,
170 struct i386_all_regs *ix86 __unused ) {
171 DBG ( "Get status of last operation\n" );
172 return drive->last_status;
176 * INT 13, 02 - Read sectors
178 * @v drive Emulated drive
179 * @v al Number of sectors to read (must be nonzero)
180 * @v ch Low bits of cylinder number
181 * @v cl (bits 7:6) High bits of cylinder number
182 * @v cl (bits 5:0) Sector number
184 * @v es:bx Data buffer
185 * @ret status Status code
186 * @ret al Number of sectors read
188 static int int13_read_sectors ( struct int13_drive *drive,
189 struct i386_all_regs *ix86 ) {
190 unsigned long lba = chs_to_lba ( drive, ix86 );
191 unsigned int count = ix86->regs.al;
192 struct segoff data = {
193 .segment = ix86->segs.es,
194 .offset = ix86->regs.bx,
197 if ( drive->blockdev->blksize != INT13_BLKSIZE ) {
198 DBG ( "Invalid blocksize (%d) for non-extended read\n",
199 drive->blockdev->blksize );
200 return INT13_STATUS_INVALID;
203 return int13_read ( drive, lba, data, count );
207 * INT 13, 03 - Write sectors
209 * @v drive Emulated drive
210 * @v al Number of sectors to write (must be nonzero)
211 * @v ch Low bits of cylinder number
212 * @v cl (bits 7:6) High bits of cylinder number
213 * @v cl (bits 5:0) Sector number
215 * @v es:bx Data buffer
216 * @ret status Status code
217 * @ret al Number of sectors written
219 static int int13_write_sectors ( struct int13_drive *drive,
220 struct i386_all_regs *ix86 ) {
221 unsigned long lba = chs_to_lba ( drive, ix86 );
222 unsigned int count = ix86->regs.al;
223 struct segoff data = {
224 .segment = ix86->segs.es,
225 .offset = ix86->regs.bx,
228 if ( drive->blockdev->blksize != INT13_BLKSIZE ) {
229 DBG ( "Invalid blocksize (%d) for non-extended write\n",
230 drive->blockdev->blksize );
231 return INT13_STATUS_INVALID;
234 return int13_write ( drive, lba, data, count );
238 * INT 13, 08 - Get drive parameters
240 * @v drive Emulated drive
241 * @ret status Status code
242 * @ret ch Low bits of maximum cylinder number
243 * @ret cl (bits 7:6) High bits of maximum cylinder number
244 * @ret cl (bits 5:0) Maximum sector number
245 * @ret dh Maximum head number
246 * @ret dl Number of drives
248 static int int13_get_parameters ( struct int13_drive *drive,
249 struct i386_all_regs *ix86 ) {
250 unsigned int max_cylinder = drive->cylinders - 1;
251 unsigned int max_head = drive->heads - 1;
252 unsigned int max_sector = drive->sectors_per_track; /* sic */
254 DBG ( "Get drive parameters\n" );
256 ix86->regs.ch = ( max_cylinder & 0xff );
257 ix86->regs.cl = ( ( ( max_cylinder >> 8 ) << 6 ) | max_sector );
258 ix86->regs.dh = max_head;
259 get_real ( ix86->regs.dl, BDA_SEG, BDA_NUM_DRIVES );
264 * INT 13, 42 - Extended read
266 * @v drive Emulated drive
267 * @v ds:si Disk address packet
268 * @ret status Status code
270 static int int13_extended_read ( struct int13_drive *drive,
271 struct i386_all_regs *ix86 ) {
272 struct int13_disk_address addr;
274 copy_from_real ( &addr, ix86->segs.ds, ix86->regs.si,
276 return int13_read ( drive, addr.lba, addr.buffer, addr.count );
280 * INT 13, 43 - Extended write
282 * @v drive Emulated drive
283 * @v ds:si Disk address packet
284 * @ret status Status code
286 static int int13_extended_write ( struct int13_drive *drive,
287 struct i386_all_regs *ix86 ) {
288 struct int13_disk_address addr;
290 copy_from_real ( &addr, ix86->segs.ds, ix86->regs.si,
292 return int13_write ( drive, addr.lba, addr.buffer, addr.count );
296 * INT 13, 48 - Get extended parameters
298 * @v drive Emulated drive
299 * @v ds:si Drive parameter table
300 * @ret status Status code
302 static int int13_get_extended_parameters ( struct int13_drive *drive,
303 struct i386_all_regs *ix86 ) {
304 struct int13_disk_parameters params = {
305 .bufsize = sizeof ( params ),
306 .flags = INT13_FL_DMA_TRANSPARENT,
307 .cylinders = drive->cylinders,
308 .heads = drive->heads,
309 .sectors_per_track = drive->sectors_per_track,
310 .sectors = drive->blockdev->blocks,
311 .sector_size = drive->blockdev->blksize,
314 DBG ( "Get extended drive parameters to %04x:%04x\n",
315 ix86->segs.ds, ix86->regs.si );
317 copy_to_real ( ix86->segs.ds, ix86->regs.si, ¶ms,
326 static void int13 ( struct i386_all_regs *ix86 ) {
327 struct int13_drive *drive;
330 list_for_each_entry ( drive, &drives, list ) {
331 if ( drive->drive != ix86->regs.dl )
334 DBG ( "INT 13, %02x on drive %02x\n", ix86->regs.ah,
337 switch ( ix86->regs.ah ) {
339 status = int13_reset ( drive, ix86 );
341 case INT13_GET_LAST_STATUS:
342 status = int13_get_last_status ( drive, ix86 );
344 case INT13_READ_SECTORS:
345 status = int13_read_sectors ( drive, ix86 );
347 case INT13_WRITE_SECTORS:
348 status = int13_write_sectors ( drive, ix86 );
350 case INT13_GET_PARAMETERS:
351 status = int13_get_parameters ( drive, ix86 );
353 case INT13_EXTENDED_READ:
354 status = int13_extended_read ( drive, ix86 );
356 case INT13_EXTENDED_WRITE:
357 status = int13_extended_write ( drive, ix86 );
359 case INT13_GET_EXTENDED_PARAMETERS:
360 status = int13_get_extended_parameters ( drive, ix86 );
363 DBG ( "Unrecognised INT 13\n" );
364 status = INT13_STATUS_INVALID;
368 /* Store status for INT 13,01 */
369 drive->last_status = status;
370 /* All functions return status via %ah and CF */
371 ix86->regs.ah = status;
374 DBG ( "INT13 failed with status %x\n", status );
376 /* Set OF to indicate to wrapper not to chain this call */
382 * Hook INT 13 handler
385 static void hook_int13 ( void ) {
386 /* Assembly wrapper to call int13(). int13() sets OF if we
387 * should not chain to the previous handler. (The wrapper
388 * clears CF and OF before calling int13()).
390 __asm__ __volatile__ ( ".section \".text16\", \"ax\", @progbits\n\t"
392 "\nint13_wrapper:\n\t"
393 "orb $0, %%al\n\t" /* clear CF and OF */
394 "pushl %0\n\t" /* call int13() */
395 "data32 call prot_call\n\t"
396 "jo 1f\n\t" /* chain if OF not set */
398 "lcall *%%cs:int13_vector\n\t"
400 "call 2f\n\t" /* return with flags intact */
408 hook_bios_interrupt ( 0x13, ( unsigned int ) int13_wrapper,
413 * Unhook INT 13 handler
415 static void unhook_int13 ( void ) {
416 unhook_bios_interrupt ( 0x13, ( unsigned int ) int13_wrapper,
421 * Register INT 13 emulated drive
423 * @v drive Emulated drive
425 * Registers the drive with the INT 13 emulation subsystem, and hooks
426 * the INT 13 interrupt vector (if not already hooked).
428 * The underlying block device must be valid. A drive number and
429 * geometry will be assigned if left blank.
431 void register_int13_drive ( struct int13_drive *drive ) {
433 unsigned long blocks;
434 unsigned long blocks_per_cyl;
436 /* Give drive a default geometry if none specified */
437 if ( ! drive->heads )
439 if ( ! drive->sectors_per_track )
440 drive->sectors_per_track = 63;
441 if ( ! drive->cylinders ) {
442 /* Avoid attempting a 64-bit divide on a 32-bit system */
443 blocks = ( ( drive->blockdev->blocks <= ULONG_MAX ) ?
444 drive->blockdev->blocks : ULONG_MAX );
445 blocks_per_cyl = ( drive->heads * drive->sectors_per_track );
446 assert ( blocks_per_cyl != 0 );
447 drive->cylinders = ( blocks / blocks_per_cyl );
450 /* Assign drive number if none specified, update BIOS drive count */
451 get_real ( num_drives, BDA_SEG, BDA_NUM_DRIVES );
452 if ( ! drive->drive )
453 drive->drive = ( num_drives | 0x80 );
454 if ( num_drives <= ( drive->drive & 0x7f ) )
455 num_drives = ( ( drive->drive & 0x7f ) + 1 );
456 put_real ( num_drives, BDA_SEG, BDA_NUM_DRIVES );
458 DBG ( "Registered INT13 drive %02x with C/H/S geometry %d/%d/%d\n",
459 drive->drive, drive->cylinders, drive->heads,
460 drive->sectors_per_track );
462 /* Hook INT 13 vector if not already hooked */
463 if ( list_empty ( &drives ) )
466 /* Add to list of emulated drives */
467 list_add ( &drive->list, &drives );
471 * Unregister INT 13 emulated drive
473 * @v drive Emulated drive
475 * Unregisters the drive from the INT 13 emulation subsystem. If this
476 * is the last emulated drive, the INT 13 vector is unhooked (if
479 void unregister_int13_drive ( struct int13_drive *drive ) {
480 /* Remove from list of emulated drives */
481 list_del ( &drive->list );
483 DBG ( "Unregistered INT13 drive %02x\n", drive->drive );
485 /* Unhook INT 13 vector if no more drives */
486 if ( list_empty ( &drives ) )
491 * Attempt to boot from an INT 13 drive
493 * @v drive Drive number
494 * @ret rc Return status code
496 * This boots from the specified INT 13 drive by loading the Master
497 * Boot Record to 0000:7c00 and jumping to it. INT 18 is hooked to
498 * capture an attempt by the MBR to boot the next device. (This is
499 * the closest thing to a return path from an MBR).
501 * Note that this function can never return success, by definition.
503 int int13_boot ( unsigned int drive ) {
504 int status, signature;
507 DBG ( "Booting from INT 13 drive %02x\n", drive );
509 /* Use INT 13 to read the boot sector */
510 REAL_EXEC ( rm_int13_boot,
515 "xorl %%eax, %%eax\n\t"
517 "movzwl %%es:0x7dfe, %%ebx\n\t",
519 OUT_CONSTRAINTS ( "=a" ( status ), "=b" ( signature ),
520 "=c" ( d0 ), "=d" ( drive ) ),
521 IN_CONSTRAINTS ( "0" ( 0x0201 ), "1" ( 0x7c00 ),
522 "2" ( 0x0001 ), "3" ( drive ) ),
527 /* Check signature is correct */
528 if ( signature != be16_to_cpu ( 0x55aa ) ) {
529 DBG ( "Invalid disk signature %#04x (should be 0x55aa)\n",
530 cpu_to_be16 ( signature ) );
534 /* Hook INT 18 to capture failure path */
535 hook_bios_interrupt ( 0x18, ( unsigned int ) int13_exec_fail,
538 /* Boot the loaded sector */
539 REAL_EXEC ( rm_int13_exec,
540 "movw %%ss, %%ax\n\t" /* Preserve stack pointer */
541 "movw %%ax, %%cs:int13_exec_saved_ss\n\t"
542 "movw %%sp, %%cs:int13_exec_saved_sp\n\t"
543 "ljmp $0, $0x7c00\n\t"
544 "\nint13_exec_saved_ss: .word 0\n\t"
545 "\nint13_exec_saved_sp: .word 0\n\t"
546 "\nint13_exec_fail:\n\t"
547 "movw %%cs:int13_exec_saved_ss, %%ax\n\t"
548 "movw %%ax, %%ss\n\t"
549 "movw %%cs:int13_exec_saved_sp, %%sp\n\t"
552 OUT_CONSTRAINTS ( "=d" ( d1 ) ),
553 IN_CONSTRAINTS ( "d" ( drive ) ),
554 CLOBBER ( "eax", "ebx", "ecx", "esi", "edi", "ebp" ) );
556 DBG ( "Booted disk returned via INT 18\n" );
559 unhook_bios_interrupt ( 0x18, ( unsigned int ) int13_exec_fail,