summaryrefslogtreecommitdiff
path: root/core/diskio.c
diff options
context:
space:
mode:
Diffstat (limited to 'core/diskio.c')
-rw-r--r--core/diskio.c352
1 files changed, 352 insertions, 0 deletions
diff --git a/core/diskio.c b/core/diskio.c
new file mode 100644
index 00000000..c51f7225
--- /dev/null
+++ b/core/diskio.c
@@ -0,0 +1,352 @@
+#include <stdio.h>
+#include <string.h>
+#include <stdbool.h>
+#include <klibc/compiler.h>
+#include "core.h"
+#include "fs.h"
+#include "disk.h"
+
+#define RETRY_COUNT 6
+
+static uint16_t MaxTransfer = 1 << (16 - 9);
+
+static int chs_rdwr_sectors(struct disk *disk, void *buf,
+ sector_t lba, size_t count, bool is_write)
+{
+ char *ptr = buf;
+ char *tptr;
+ size_t chunk, freeseg;
+ int sector_shift = disk->sector_shift;
+ uint32_t xlba = lba + disk->part_start; /* Truncated LBA (CHS is << 2 TB) */
+ uint32_t t;
+ uint16_t c, h, s;
+ com32sys_t ireg, oreg;
+ size_t done = 0;
+ size_t bytes;
+ int retry;
+
+ memset(&ireg, 0, sizeof ireg);
+
+ ireg.eax.b[1] = 0x02 + is_write;
+ ireg.edx.b[0] = disk->disk_number;
+
+ while (count) {
+ chunk = count;
+ if (chunk > MaxTransfer)
+ chunk = MaxTransfer;
+
+ freeseg = (0x10000 - ((size_t)ptr & 0xffff)) >> sector_shift;
+
+ if ((size_t)buf <= 0xf0000 && freeseg) {
+ /* Can do a direct load */
+ tptr = ptr;
+ } else {
+ /* Either accessing high memory or we're crossing a 64K line */
+ tptr = core_xfer_buf;
+ freeseg = (0x10000 - ((size_t)tptr & 0xffff)) >> sector_shift;
+ }
+ if (chunk > freeseg)
+ chunk = freeseg;
+
+ bytes = chunk << sector_shift;
+
+ if (tptr != ptr && is_write)
+ memcpy(tptr, ptr, bytes);
+
+ s = xlba % disk->s;
+ t = xlba / disk->s;
+ h = t % disk->h;
+ c = t / disk->h;
+
+ ireg.eax.b[0] = chunk;
+ ireg.ecx.b[1] = c;
+ ireg.ecx.b[0] = ((c & 0x300) >> 2) | (s+1);
+ ireg.edx.b[1] = h;
+ ireg.ebx.w[0] = OFFS(tptr);
+ ireg.es = SEG(tptr);
+
+ retry = RETRY_COUNT;
+
+ for (;;) {
+ __intcall(0x13, &ireg, &oreg);
+ if (!(oreg.eflags.l & EFLAGS_CF))
+ break;
+ if (retry--)
+ continue;
+
+ /* if we are reading ONE sector and go here, just make it _faile_ */
+ chunk = chunk == 1 ? 0 : ((chunk+1) >> 1);
+ if (chunk) {
+ MaxTransfer = chunk;
+ retry = RETRY_COUNT;
+ ireg.eax.b[0] = chunk;
+ continue;
+ }
+ return done; /* Failure */
+ }
+
+ bytes = chunk << sector_shift;
+
+ if (tptr != ptr && !is_write)
+ memcpy(ptr, tptr, bytes);
+
+ ptr += bytes;
+ xlba += chunk;
+ count -= chunk;
+ done += chunk;
+ }
+
+ return done;
+}
+
+struct edd_rdwr_packet {
+ uint16_t size;
+ uint16_t blocks;
+ far_ptr_t buf;
+ uint64_t lba;
+};
+
+static int edd_rdwr_sectors(struct disk *disk, void *buf,
+ sector_t lba, size_t count, bool is_write)
+{
+ static __lowmem struct edd_rdwr_packet pkt;
+ char *ptr = buf;
+ char *tptr;
+ size_t chunk, freeseg;
+ int sector_shift = disk->sector_shift;
+ com32sys_t ireg, oreg;
+ size_t done = 0;
+ size_t bytes;
+ int retry;
+
+ memset(&ireg, 0, sizeof ireg);
+
+ ireg.eax.b[1] = 0x42 + is_write;
+ ireg.edx.b[0] = disk->disk_number;
+ ireg.ds = SEG(&pkt);
+ ireg.esi.w[0] = OFFS(&pkt);
+
+ lba += disk->part_start;
+ while (count) {
+ chunk = count;
+ if (chunk > MaxTransfer)
+ chunk = MaxTransfer;
+
+ freeseg = (0x10000 - ((size_t)ptr & 0xffff)) >> sector_shift;
+
+ if ((size_t)buf <= 0xf0000 && freeseg) {
+ /* Can do a direct load */
+ tptr = ptr;
+ } else {
+ /* Either accessing high memory or we're crossing a 64K line */
+ tptr = core_xfer_buf;
+ freeseg = (0x10000 - ((size_t)tptr & 0xffff)) >> sector_shift;
+ }
+ if (chunk > freeseg)
+ chunk = freeseg;
+
+ bytes = chunk << sector_shift;
+
+ if (tptr != ptr && is_write)
+ memcpy(tptr, ptr, bytes);
+
+ pkt.size = sizeof pkt;
+ pkt.blocks = chunk;
+ pkt.buf = FAR_PTR(tptr);
+ pkt.lba = lba;
+
+ retry = RETRY_COUNT;
+
+ for (;;) {
+ __intcall(0x13, &ireg, &oreg);
+ if (!(oreg.eflags.l & EFLAGS_CF))
+ break;
+ if (retry--)
+ continue;
+ chunk = chunk == 1 ? 0 : ((chunk+1) >> 1);
+ if (chunk) {
+ MaxTransfer = chunk;
+ retry = RETRY_COUNT;
+ pkt.blocks = chunk;
+ continue;
+ }
+ /*** XXX: Consider falling back to CHS here?! ***/
+ printf("reading sectors error(EDD)\n");
+ return done; /* Failure */
+ }
+
+ bytes = chunk << sector_shift;
+
+ if (tptr != ptr && !is_write)
+ memcpy(ptr, tptr, bytes);
+
+ ptr += bytes;
+ lba += chunk;
+ count -= chunk;
+ done += chunk;
+ }
+ return done;
+}
+struct edd_disk_params {
+ uint16_t len;
+ uint16_t flags;
+ uint32_t phys_c;
+ uint32_t phys_h;
+ uint32_t phys_s;
+ uint64_t sectors;
+ uint16_t sector_size;
+ far_ptr_t dpte;
+ uint16_t devpath_key;
+ uint8_t devpath_len;
+ uint8_t _pad1[3];
+ char bus_type[4];
+ char if_type[8];
+ uint8_t if_path[8];
+ uint8_t dev_path[8];
+ uint8_t _pad2;
+ uint8_t devpath_csum;
+} __attribute__((packed));
+
+static inline bool is_power_of_2(uint32_t x)
+{
+ return !(x & (x-1));
+}
+
+static int ilog2(uint32_t num)
+{
+ int i = 0;
+
+ if (!is_power_of_2(num)) {
+ printf("ERROR: the num must be power of 2 when conveting to log2\n");
+ return 0;
+ }
+ while (num >>= 1)
+ i++;
+ return i;
+}
+
+void getoneblk(struct disk *disk, char *buf, block_t block, int block_size)
+{
+ int sec_per_block = block_size >> SECTOR_SHIFT;
+
+ disk->rdwr_sectors(disk, buf, block * sec_per_block, sec_per_block, 0);
+}
+
+static void dump_disk(struct disk *disk)
+{
+ printf("drive number: 0x%x\n", disk->disk_number);
+ printf("disk type: %s(%d)\n", disk->type ? "EDD" : "CHS", disk->type);
+ printf("sector size: %d(%d)\n", disk->sector_size, disk->sector_shift);
+ printf("h: %d\ts: %d\n", disk->h, disk->s);
+ printf("offset: %d\n", disk->part_start);
+ printf("%s\n", disk->rdwr_sectors == edd_rdwr_sectors ? "EDD_RDWR_SECTORS" :
+ "CHS_RDWR_SECTORS");
+ printf("--------------------------------\n");
+ printf("disk->rdwr_sectors@: %p\n", disk->rdwr_sectors);
+ printf("edd_rdwr_sectors @: %p\n", edd_rdwr_sectors);
+ printf("chs_rdwr_sectors @: %p\n", chs_rdwr_sectors);
+}
+
+struct disk *disk_init(uint8_t devno, bool cdrom, sector_t part_start,
+ uint16_t bsHeads, uint16_t bsSecPerTrack)
+{
+ static struct disk disk;
+ static __lowmem struct edd_disk_params edd_params;
+ com32sys_t ireg, oreg;
+ bool ebios = cdrom;
+ int sector_size = cdrom ? 2048 : 512;
+
+ memset(&ireg, 0, sizeof ireg);
+
+ /* Get EBIOS support */
+ ireg.eax.b[1] = 0x41;
+ ireg.ebx.w[0] = 0x55aa;
+ ireg.edx.b[0] = devno;
+ ireg.eflags.b[0] = 0x3; /* CF set */
+
+ __intcall(0x13, &ireg, &oreg);
+
+ if (cdrom || (!(oreg.eflags.l & EFLAGS_CF) &&
+ oreg.ebx.w[0] == 0xaa55 && (oreg.ecx.b[0] & 1))) {
+ /* Query EBIOS parameters */
+ ireg.eax.b[1] = 0x48;
+ ireg.ds = SEG(&edd_params);
+ ireg.esi.w[0] = OFFS(&edd_params);
+ __intcall(0x13, &ireg, &oreg);
+
+ if (!(oreg.eflags.l & EFLAGS_CF) && oreg.eax.b[1] == 0) {
+ ebios = true;
+ if (edd_params.sector_size >= 512 &&
+ is_power_of_2(edd_params.sector_size))
+ sector_size = edd_params.sector_size;
+ }
+ }
+
+ /* CBIOS parameters */
+ disk.h = bsHeads;
+ disk.s = bsSecPerTrack;
+
+ if ((int8_t)devno < 0) {
+ /* Get hard disk geometry from BIOS */
+
+ ireg.eax.b[1] = 0x08;
+ __intcall(0x13, &ireg, &oreg);
+
+ if (!(oreg.eflags.l & EFLAGS_CF)) {
+ disk.h = oreg.edx.b[1] + 1;
+ disk.s = oreg.ecx.b[0] & 63;
+ }
+ }
+
+ disk.disk_number = devno;
+ disk.type = ebios;
+ disk.sector_size = sector_size;
+ disk.sector_shift = ilog2(sector_size);
+ disk.part_start = part_start;
+ disk.rdwr_sectors = ebios ? edd_rdwr_sectors : chs_rdwr_sectors;
+
+#if 0
+ dump_disk(&disk);
+#endif
+ return &disk;
+}
+
+
+/*
+ * initialize the device structure
+ */
+struct device * device_init(uint8_t devno, bool cdrom, sector_t part_start,
+ uint16_t bsHeads, uint16_t bsSecPerTrack)
+{
+ static struct device dev;
+
+ dev.disk = disk_init(devno, cdrom, part_start, bsHeads, bsSecPerTrack);
+
+ /* for now, isolinux doesn't use cache */
+ if (!cdrom) {
+ /*
+ * FIX!! I can't use __lowmem here, 'cause it will cause the error:
+ * "auxseg/lowmem region collides with xfer_buf_seg".
+ *
+ * static __lowmem char cache_buf[65536];
+ */
+ dev.cache_data = core_cache_buf;
+ dev.cache_size = sizeof core_cache_buf;
+ } else
+ dev.cache_data = NULL;
+
+ return &dev;
+}
+
+
+/* debug function */
+static void dump_dev(struct device *dev)
+{
+ printf("device type:%s\n", dev->disk->type ? "EDD" : "CHS");
+ printf("drive number: 0x%x\n", dev->disk->disk_number);
+ printf("cache_data: %p\n", dev->cache_data);
+ printf("cache_head: %p\n", dev->cache_head);
+ printf("cache_block_size: %d\n", dev->cache_block_size);
+ printf("cache_entries: %d\n", dev->cache_entries);
+ printf("cache_size: %d\n", dev->cache_size);
+}