// Disk setup and access // // Copyright (C) 2008,2009 Kevin O'Connor // Copyright (C) 2002 MandrakeSoft S.A. // // This file may be distributed under the terms of the GNU LGPLv3 license. #include "disk.h" // struct ata_s #include "biosvar.h" // GET_GLOBAL #include "cmos.h" // inb_cmos #include "util.h" // dprintf #include "ata.h" // process_ata_op #include "ahci.h" // process_ahci_op #include "virtio-blk.h" // process_virtio_blk_op #include "blockcmd.h" // cdb_* u8 FloppyCount VARFSEG; u8 CDCount; struct drive_s *IDMap[3][BUILD_MAX_EXTDRIVE] VARFSEG; u8 *bounce_buf_fl VARFSEG; struct dpte_s DefaultDPTE VARLOW; struct drive_s * getDrive(u8 exttype, u8 extdriveoffset) { if (extdriveoffset >= ARRAY_SIZE(IDMap[0])) return NULL; struct drive_s *drive_gf = GET_GLOBAL(IDMap[exttype][extdriveoffset]); if (!drive_gf) return NULL; return GLOBALFLAT2GLOBAL(drive_gf); } int getDriveId(u8 exttype, struct drive_s *drive_g) { int i; for (i = 0; i < ARRAY_SIZE(IDMap[0]); i++) if (getDrive(exttype, i) == drive_g) return i; return -1; } int create_bounce_buf(void) { if (bounce_buf_fl) return 0; u8 *buf = malloc_low(CDROM_SECTOR_SIZE); if (!buf) { warn_noalloc(); return -1; } bounce_buf_fl = buf; return 0; } /**************************************************************** * Disk geometry translation ****************************************************************/ static u8 get_translation(struct drive_s *drive_g) { u8 type = GET_GLOBAL(drive_g->type); if (CONFIG_QEMU && type == DTYPE_ATA) { // Emulators pass in the translation info via nvram. u8 ataid = GET_GLOBAL(drive_g->cntl_id); u8 channel = ataid / 2; u8 translation = inb_cmos(CMOS_BIOS_DISKTRANSFLAG + channel/2); translation >>= 2 * (ataid % 4); translation &= 0x03; return translation; } // Otherwise use a heuristic to determine translation type. u16 heads = GET_GLOBAL(drive_g->pchs.heads); u16 cylinders = GET_GLOBAL(drive_g->pchs.cylinders); u16 spt = GET_GLOBAL(drive_g->pchs.spt); u64 sectors = GET_GLOBAL(drive_g->sectors); u64 psectors = (u64)heads * cylinders * spt; if (!heads || !cylinders || !spt || psectors > sectors) // pchs doesn't look valid - use LBA. return TRANSLATION_LBA; if (cylinders <= 1024 && heads <= 16 && spt <= 63) return TRANSLATION_NONE; if (cylinders * heads <= 131072) return TRANSLATION_LARGE; return TRANSLATION_LBA; } static void setup_translation(struct drive_s *drive_g) { u8 translation = get_translation(drive_g); SET_GLOBAL(drive_g->translation, translation); u16 heads = GET_GLOBAL(drive_g->pchs.heads); u16 cylinders = GET_GLOBAL(drive_g->pchs.cylinders); u16 spt = GET_GLOBAL(drive_g->pchs.spt); u64 sectors = GET_GLOBAL(drive_g->sectors); const char *desc = NULL; switch (translation) { default: case TRANSLATION_NONE: desc = "none"; break; case TRANSLATION_LBA: desc = "lba"; spt = 63; if (sectors > 63*255*1024) { heads = 255; cylinders = 1024; break; } u32 sect = (u32)sectors / 63; heads = sect / 1024; if (heads>128) heads = 255; else if (heads>64) heads = 128; else if (heads>32) heads = 64; else if (heads>16) heads = 32; else heads = 16; cylinders = sect / heads; break; case TRANSLATION_RECHS: desc = "r-echs"; // Take care not to overflow if (heads==16) { if (cylinders>61439) cylinders=61439; heads=15; cylinders = (u16)((u32)(cylinders)*16/15); } // then go through the large bitshift process case TRANSLATION_LARGE: if (translation == TRANSLATION_LARGE) desc = "large"; while (cylinders > 1024) { cylinders >>= 1; heads <<= 1; // If we max out the head count if (heads > 127) break; } break; } // clip to 1024 cylinders in lchs if (cylinders > 1024) cylinders = 1024; dprintf(1, "drive %p: PCHS=%u/%d/%d translation=%s LCHS=%d/%d/%d s=%d\n" , drive_g , drive_g->pchs.cylinders, drive_g->pchs.heads, drive_g->pchs.spt , desc , cylinders, heads, spt , (u32)sectors); SET_GLOBAL(drive_g->lchs.heads, heads); SET_GLOBAL(drive_g->lchs.cylinders, cylinders); SET_GLOBAL(drive_g->lchs.spt, spt); } /**************************************************************** * Drive mapping ****************************************************************/ // Fill in Fixed Disk Parameter Table (located in ebda). static void fill_fdpt(struct drive_s *drive_g, int hdid) { if (hdid > 1) return; u16 nlc = GET_GLOBAL(drive_g->lchs.cylinders); u16 nlh = GET_GLOBAL(drive_g->lchs.heads); u16 nlspt = GET_GLOBAL(drive_g->lchs.spt); u16 npc = GET_GLOBAL(drive_g->pchs.cylinders); u16 nph = GET_GLOBAL(drive_g->pchs.heads); u16 npspt = GET_GLOBAL(drive_g->pchs.spt); struct fdpt_s *fdpt = &get_ebda_ptr()->fdpt[hdid]; fdpt->precompensation = 0xffff; fdpt->drive_control_byte = 0xc0 | ((nph > 8) << 3); fdpt->landing_zone = npc; fdpt->cylinders = nlc; fdpt->heads = nlh; fdpt->sectors = nlspt; if (nlc != npc || nlh != nph || nlspt != npspt) { // Logical mapping present - use extended structure. // complies with Phoenix style Translated Fixed Disk Parameter // Table (FDPT) fdpt->phys_cylinders = npc; fdpt->phys_heads = nph; fdpt->phys_sectors = npspt; fdpt->a0h_signature = 0xa0; // Checksum structure. fdpt->checksum -= checksum(fdpt, sizeof(*fdpt)); } if (hdid == 0) SET_IVT(0x41, SEGOFF(get_ebda_seg(), offsetof( struct extended_bios_data_area_s, fdpt[0]))); else SET_IVT(0x46, SEGOFF(get_ebda_seg(), offsetof( struct extended_bios_data_area_s, fdpt[1]))); } // Find spot to add a drive static void add_drive(struct drive_s **idmap, u8 *count, struct drive_s *drive_g) { if (*count >= ARRAY_SIZE(IDMap[0])) { warn_noalloc(); return; } idmap[*count] = drive_g; *count = *count + 1; } // Map a hard drive void map_hd_drive(struct drive_s *drive_g) { ASSERT32FLAT(); struct bios_data_area_s *bda = MAKE_FLATPTR(SEG_BDA, 0); int hdid = bda->hdcount; dprintf(3, "Mapping hd drive %p to %d\n", drive_g, hdid); add_drive(IDMap[EXTTYPE_HD], &bda->hdcount, drive_g); // Setup disk geometry translation. setup_translation(drive_g); // Fill "fdpt" structure. fill_fdpt(drive_g, hdid); } // Map a cd void map_cd_drive(struct drive_s *drive_g) { dprintf(3, "Mapping cd drive %p\n", drive_g); add_drive(IDMap[EXTTYPE_CD], &CDCount, drive_g); } // Map a floppy void map_floppy_drive(struct drive_s *drive_g) { dprintf(3, "Mapping floppy drive %p\n", drive_g); add_drive(IDMap[EXTTYPE_FLOPPY], &FloppyCount, drive_g); // Update equipment word bits for floppy if (FloppyCount == 1) { // 1 drive, ready for boot set_equipment_flags(0x41, 0x01); SET_BDA(floppy_harddisk_info, 0x07); } else if (FloppyCount >= 2) { // 2 drives, ready for boot set_equipment_flags(0x41, 0x41); SET_BDA(floppy_harddisk_info, 0x77); } } /**************************************************************** * 16bit calling interface ****************************************************************/ static int process_scsi_op(struct disk_op_s *op) { switch (op->command) { case CMD_READ: return cdb_read(op); case CMD_WRITE: return cdb_write(op); case CMD_FORMAT: case CMD_RESET: case CMD_ISREADY: case CMD_VERIFY: case CMD_SEEK: return DISK_RET_SUCCESS; default: op->count = 0; return DISK_RET_EPARAM; } } static int process_atapi_op(struct disk_op_s *op) { switch (op->command) { case CMD_WRITE: case CMD_FORMAT: return DISK_RET_EWRITEPROTECT; default: return process_scsi_op(op); } } // Execute a disk_op request. int process_op(struct disk_op_s *op) { ASSERT16(); u8 type = GET_GLOBAL(op->drive_g->type); switch (type) { case DTYPE_FLOPPY: return process_floppy_op(op); case DTYPE_ATA: return process_ata_op(op); case DTYPE_RAMDISK: return process_ramdisk_op(op); case DTYPE_CDEMU: return process_cdemu_op(op); case DTYPE_VIRTIO_BLK: return process_virtio_blk_op(op); case DTYPE_AHCI: return process_ahci_op(op); case DTYPE_ATA_ATAPI: case DTYPE_AHCI_ATAPI: return process_atapi_op(op); case DTYPE_USB: case DTYPE_UAS: case DTYPE_VIRTIO_SCSI: case DTYPE_LSI_SCSI: case DTYPE_ESP_SCSI: case DTYPE_MEGASAS: return process_scsi_op(op); default: op->count = 0; return DISK_RET_EPARAM; } } // Execute a "disk_op_s" request - this runs on the extra stack. static int __send_disk_op(struct disk_op_s *op_far, u16 op_seg) { struct disk_op_s dop; memcpy_far(GET_SEG(SS), &dop , op_seg, op_far , sizeof(dop)); dprintf(DEBUG_HDL_13, "disk_op d=%p lba=%d buf=%p count=%d cmd=%d\n" , dop.drive_g, (u32)dop.lba, dop.buf_fl , dop.count, dop.command); int status = process_op(&dop); // Update count with total sectors transferred. SET_FARVAR(op_seg, op_far->count, dop.count); return status; } // Execute a "disk_op_s" request by jumping to the extra 16bit stack. int send_disk_op(struct disk_op_s *op) { ASSERT16(); if (! CONFIG_DRIVES) return -1; return stack_hop((u32)op, GET_SEG(SS), __send_disk_op); }