/* This file contains the "device dependent" part of a hard disk driver that * uses the ROM BIOS. It makes a call and just waits for the transfer to * happen. It is not interrupt driven and thus will (*) have poor performance. * The advantage is that it should work on virtually any PC, XT, 386, PS/2 * or clone. The demo disk uses this driver. It is suggested that all * MINIX users try the other drivers, and use this one only as a last resort, * if all else fails. * * (*) The performance is within 10% of the AT driver for reads on any disk * and writes on a 2:1 interleaved disk, it will be DMA_BUF_SIZE bytes * per revolution for a minimum of 60 kb/s for writes to 1:1 disks. * * The file contains one entry point: * * bios_winchester_task: main entry when system is brought up * * * Changes: * 30 Apr 1992 by Kees J. Bot: device dependent/independent split. * 14 May 2000 by Kees J. Bot: d-d/i rewrite. */ #include "../drivers.h" #include "../libdriver/driver.h" #include "../libdriver/drvlib.h" #include #include #include #include #include #define ME "BIOS_WINI" /* Error codes */ #define ERR (-1) /* general error */ /* Parameters for the disk drive. */ #define MAX_DRIVES 4 /* this driver supports 4 drives (d0 - d3)*/ #define MAX_SECS 255 /* bios can transfer this many sectors */ #define NR_DEVICES (MAX_DRIVES * DEV_PER_DRIVE) #define SUB_PER_DRIVE (NR_PARTITIONS * NR_PARTITIONS) #define NR_SUBDEVS (MAX_DRIVES * SUB_PER_DRIVE) PRIVATE int pc_at = 1; /* What about PC XTs? */ /* Variables. */ PRIVATE struct wini { /* main drive struct, one entry per drive */ unsigned cylinders; /* number of cylinders */ unsigned heads; /* number of heads */ unsigned sectors; /* number of sectors per track */ unsigned open_ct; /* in-use count */ int int13ext; /* IBM/MS INT 13 extensions supported? */ struct device part[DEV_PER_DRIVE]; /* disks and partitions */ struct device subpart[SUB_PER_DRIVE]; /* subpartitions */ } wini[MAX_DRIVES], *w_wn; PRIVATE int nr_drives = MAX_DRIVES; /* Number of drives */ PRIVATE int w_drive; /* selected drive */ PRIVATE struct device *w_dv; /* device's base and size */ PRIVATE vir_bytes bios_buf_vir, bios_buf_size; PRIVATE phys_bytes bios_buf_phys; _PROTOTYPE(int main, (void) ); FORWARD _PROTOTYPE( struct device *w_prepare, (int device) ); FORWARD _PROTOTYPE( char *w_name, (void) ); FORWARD _PROTOTYPE( int w_transfer, (int proc_nr, int opcode, off_t position, iovec_t *iov, unsigned nr_req) ); FORWARD _PROTOTYPE( int w_do_open, (struct driver *dp, message *m_ptr) ); FORWARD _PROTOTYPE( int w_do_close, (struct driver *dp, message *m_ptr) ); FORWARD _PROTOTYPE( void w_init, (void) ); FORWARD _PROTOTYPE( void w_geometry, (struct partition *entry)); FORWARD _PROTOTYPE( int nop_other, (struct driver *dp, message *m_ptr) ); /* Entry points to this driver. */ PRIVATE struct driver w_dtab = { w_name, /* current device's name */ w_do_open, /* open or mount request, initialize device */ w_do_close, /* release device */ do_diocntl, /* get or set a partition's geometry */ w_prepare, /* prepare for I/O on a given minor device */ w_transfer, /* do the I/O */ nop_cleanup, /* no cleanup needed */ w_geometry, /* tell the geometry of the disk */ nop_signal, /* no cleanup needed on shutdown */ nop_alarm, /* ignore leftover alarms */ nop_cancel, /* ignore CANCELs */ nop_select, /* ignore selects */ nop_other /* catch-all for unrecognized commands and ioctls */ }; /*===========================================================================* * bios_winchester_task * *===========================================================================*/ PUBLIC int main() { /* Set special disk parameters then call the generic main loop. */ driver_task(&w_dtab); } /*===========================================================================* * w_prepare * *===========================================================================*/ PRIVATE struct device *w_prepare(device) int device; { /* Prepare for I/O on a device. */ if (device < NR_DEVICES) { /* d0, d0p[0-3], d1, ... */ w_drive = device / DEV_PER_DRIVE; /* save drive number */ w_wn = &wini[w_drive]; w_dv = &w_wn->part[device % DEV_PER_DRIVE]; } else if ((unsigned) (device -= MINOR_d0p0s0) < NR_SUBDEVS) {/*d[0-7]p[0-3]s[0-3]*/ w_drive = device / SUB_PER_DRIVE; w_wn = &wini[w_drive]; w_dv = &w_wn->subpart[device % SUB_PER_DRIVE]; } else { return(NIL_DEV); } return(w_drive < nr_drives ? w_dv : NIL_DEV); } /*===========================================================================* * w_name * *===========================================================================*/ PRIVATE char *w_name() { /* Return a name for the current device. */ static char name[] = "bios-d0"; name[6] = '0' + w_drive; return name; } /*===========================================================================* * w_transfer * *===========================================================================*/ PRIVATE int w_transfer(proc_nr, opcode, position, iov, nr_req) int proc_nr; /* process doing the request */ int opcode; /* DEV_GATHER or DEV_SCATTER */ off_t position; /* offset on device to read or write */ iovec_t *iov; /* pointer to read or write request vector */ unsigned nr_req; /* length of request vector */ { struct wini *wn = w_wn; iovec_t *iop, *iov_end = iov + nr_req; int r, errors; unsigned nbytes, count, chunk; unsigned long block; vir_bytes i13e_rw_off, rem_buf_size; unsigned long dv_size = cv64ul(w_dv->dv_size); unsigned secspcyl = wn->heads * wn->sectors; struct int13ext_rw { u8_t len; u8_t res1; u16_t count; u16_t addr[2]; u32_t block[2]; } i13e_rw; struct reg86u reg86; /* Check disk address. */ if ((position & SECTOR_MASK) != 0) return(EINVAL); errors = 0; i13e_rw_off= bios_buf_size-sizeof(i13e_rw); rem_buf_size= (i13e_rw_off & ~SECTOR_MASK); assert(rem_buf_size != 0); while (nr_req > 0) { /* How many bytes to transfer? */ nbytes = 0; for (iop = iov; iop < iov_end; iop++) { if (nbytes + iop->iov_size > rem_buf_size) { /* Don't do half a segment if you can avoid it. */ if (nbytes == 0) nbytes = rem_buf_size; break; } nbytes += iop->iov_size; } if ((nbytes & SECTOR_MASK) != 0) return(EINVAL); /* Which block on disk and how close to EOF? */ if (position >= dv_size) return(OK); /* At EOF */ if (position + nbytes > dv_size) nbytes = dv_size - position; block = div64u(add64ul(w_dv->dv_base, position), SECTOR_SIZE); /* Degrade to per-sector mode if there were errors. */ if (errors > 0) nbytes = SECTOR_SIZE; if (opcode == DEV_SCATTER) { /* Copy from user space to the DMA buffer. */ count = 0; for (iop = iov; count < nbytes; iop++) { chunk = iov->iov_size; if (count + chunk > nbytes) chunk = nbytes - count; assert(chunk <= rem_buf_size); r= sys_vircopy(proc_nr, D, iop->iov_addr, SYSTEM, D, bios_buf_vir+count, chunk); if (r != OK) panic(ME, "sys_vircopy failed", r); count += chunk; } } /* Do the transfer */ if (wn->int13ext) { i13e_rw.len = 0x10; i13e_rw.res1 = 0; i13e_rw.count = nbytes >> SECTOR_SHIFT; i13e_rw.addr[0] = bios_buf_phys % HCLICK_SIZE; i13e_rw.addr[1] = bios_buf_phys / HCLICK_SIZE; i13e_rw.block[0] = block; i13e_rw.block[1] = 0; r= sys_vircopy(SELF, D, (vir_bytes)&i13e_rw, SYSTEM, D, (bios_buf_vir+i13e_rw_off), sizeof(i13e_rw)); if (r != OK) panic(ME, "sys_vircopy failed", r); /* Set up an extended read or write BIOS call. */ reg86.u.b.intno = 0x13; reg86.u.w.ax = opcode == DEV_SCATTER ? 0x4300 : 0x4200; reg86.u.b.dl = 0x80 + w_drive; reg86.u.w.si = (bios_buf_phys + i13e_rw_off) % HCLICK_SIZE; reg86.u.w.ds = (bios_buf_phys + i13e_rw_off) / HCLICK_SIZE; } else { /* Set up an ordinary read or write BIOS call. */ unsigned cylinder = block / secspcyl; unsigned sector = (block % wn->sectors) + 1; unsigned head = (block % secspcyl) / wn->sectors; reg86.u.b.intno = 0x13; reg86.u.b.ah = opcode == DEV_SCATTER ? 0x03 : 0x02; reg86.u.b.al = nbytes >> SECTOR_SHIFT; reg86.u.w.bx = (bios_buf_phys + i13e_rw_off) % HCLICK_SIZE; reg86.u.w.es = (bios_buf_phys + i13e_rw_off) / HCLICK_SIZE; reg86.u.b.ch = cylinder & 0xFF; reg86.u.b.cl = sector | ((cylinder & 0x300) >> 2); reg86.u.b.dh = head; reg86.u.b.dl = 0x80 + w_drive; } r= sys_int86(®86); if (r != OK) panic(ME, "BIOS call failed", r); if (reg86.u.w.f & 0x0001) { /* An error occurred, try again sector by sector unless */ if (++errors == 2) return(EIO); continue; } if (opcode == DEV_GATHER) { /* Copy from the DMA buffer to user space. */ count = 0; for (iop = iov; count < nbytes; iop++) { chunk = iov->iov_size; if (count + chunk > nbytes) chunk = nbytes - count; assert(chunk <= rem_buf_size); r= sys_vircopy(SYSTEM, D, bios_buf_vir+count, proc_nr, D, iop->iov_addr, chunk); if (r != OK) panic(ME, "sys_vircopy failed", r); count += chunk; } } /* Book the bytes successfully transferred. */ position += nbytes; for (;;) { if (nbytes < iov->iov_size) { /* Not done with this one yet. */ iov->iov_addr += nbytes; iov->iov_size -= nbytes; break; } nbytes -= iov->iov_size; iov->iov_addr += iov->iov_size; iov->iov_size = 0; if (nbytes == 0) { /* The rest is optional, so we return to give FS a * chance to think it over. */ return(OK); } iov++; nr_req--; } } return(OK); } /*============================================================================* * w_do_open * *============================================================================*/ PRIVATE int w_do_open(dp, m_ptr) struct driver *dp; message *m_ptr; { /* Device open: Initialize the controller and read the partition table. */ static int init_done = FALSE; if (!init_done) { w_init(); init_done = TRUE; } if (w_prepare(m_ptr->DEVICE) == NIL_DEV) return(ENXIO); if (w_wn->open_ct++ == 0) { /* Partition the disk. */ partition(&w_dtab, w_drive * DEV_PER_DRIVE, P_PRIMARY); } return(OK); } /*============================================================================* * w_do_close * *============================================================================*/ PRIVATE int w_do_close(dp, m_ptr) struct driver *dp; message *m_ptr; { /* Device close: Release a device. */ if (w_prepare(m_ptr->DEVICE) == NIL_DEV) return(ENXIO); w_wn->open_ct--; return(OK); } /*===========================================================================* * w_init * *===========================================================================*/ PRIVATE void w_init() { /* This routine is called at startup to initialize the drive parameters. */ int r, drive; struct wini *wn; unsigned long capacity; struct int13ext_params { u16_t len; u16_t flags; u32_t cylinders; u32_t heads; u32_t sectors; u32_t capacity[2]; u16_t bts_per_sec; u16_t config[2]; } i13e_par; struct reg86u reg86; /* Ask the system task for a suitable buffer */ r= sys_getbiosbuffer(&bios_buf_vir, &bios_buf_size); if (r != OK) panic(ME, "sys_getbiosbuffer failed", r); r= sys_umap(SYSTEM, D, (vir_bytes)bios_buf_vir, (phys_bytes)bios_buf_size, &bios_buf_phys); if (r != OK) panic(ME, "sys_umap failed", r); if (bios_buf_phys+bios_buf_size > 0x100000) panic(ME, "bad BIOS buffer, phys", bios_buf_phys); #if 0 printf("bios_wini: got buffer size %d, virtual 0x%x, phys 0x%x\n", bios_buf_size, bios_buf_vir, bios_buf_phys); #endif /* Get the geometry of the drives */ for (drive = 0; drive < nr_drives; drive++) { (void) w_prepare(drive * DEV_PER_DRIVE); wn = w_wn; reg86.u.b.intno = 0x13; reg86.u.b.ah = 0x08; /* Get drive parameters. */ reg86.u.b.dl = 0x80 + drive; r= sys_int86(®86); if (r != OK) panic(ME, "BIOS call failed", r); nr_drives = !(reg86.u.w.f & 0x0001) ? reg86.u.b.dl : drive; if (nr_drives > MAX_DRIVES) nr_drives = MAX_DRIVES; if (drive >= nr_drives) break; wn->heads = reg86.u.b.dh + 1; wn->sectors = reg86.u.b.cl & 0x3F; wn->cylinders = (reg86.u.b.ch | ((reg86.u.b.cl & 0xC0) << 2)) + 1; capacity = (unsigned long) wn->cylinders * wn->heads * wn->sectors; reg86.u.b.intno = 0x13; reg86.u.b.ah = 0x41; /* INT 13 Extensions - Installation check */ reg86.u.w.bx = 0x55AA; reg86.u.b.dl = 0x80 + drive; if (pc_at) { r= sys_int86(®86); if (r != OK) panic(ME, "BIOS call failed", r); } if (!(reg86.u.w.f & 0x0001) && reg86.u.w.bx == 0xAA55 && (reg86.u.w.cx & 0x0001)) { /* INT 13 Extensions available. */ i13e_par.len = 0x001E; /* Input size of parameter packet */ r= sys_vircopy(SELF, D, (vir_bytes)&i13e_par, SYSTEM, D, bios_buf_vir, sizeof(i13e_par)); if (r != OK) panic(ME, "sys_vircopy failed\n", r); reg86.u.b.intno = 0x13; reg86.u.b.ah = 0x48; /* Ext. Get drive parameters. */ reg86.u.b.dl = 0x80 + drive; reg86.u.w.si = bios_buf_phys % HCLICK_SIZE; reg86.u.w.ds = bios_buf_phys / HCLICK_SIZE; r= sys_int86(®86); if (r != OK) panic(ME, "BIOS call failed", r); r= sys_vircopy(SYSTEM, D, bios_buf_vir, SELF, D, (vir_bytes)&i13e_par, sizeof(i13e_par)); if (r != OK) panic(ME, "sys_vircopy failed\n", r); if (!(reg86.u.w.f & 0x0001)) { wn->int13ext = 1; /* Extensions can be used. */ capacity = i13e_par.capacity[0]; if (i13e_par.capacity[1] != 0) capacity = 0xFFFFFFFF; } } if (wn->int13ext) { printf("%s: %lu sectors\n", w_name(), capacity); } else { printf("%s: %d cylinders, %d heads, %d sectors per track\n", w_name(), wn->cylinders, wn->heads, wn->sectors); } wn->part[0].dv_size = mul64u(capacity, SECTOR_SIZE); } } /*============================================================================* * w_geometry * *============================================================================*/ PRIVATE void w_geometry(entry) struct partition *entry; { entry->cylinders = w_wn->cylinders; entry->heads = w_wn->heads; entry->sectors = w_wn->sectors; } /*============================================================================* * nop_other * *============================================================================*/ PRIVATE int nop_other(dp, m_ptr) struct driver *dp; message *m_ptr; { printf("bios_wini: in nop_other\n"); return OK; }