//+++2004-09-11 // Copyright (C) 2004 Mike Rieker, Beverly, MA USA // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; version 2 of the License. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA //---2004-09-11 /************************************************************************/ /* */ /* PCI IDE disk controller driver using SFF 8038i DMA standard */ /* Will also process generic IDE PIO interface */ /* */ /* Creates devices named ide_CD */ /* */ /* C = controller: p for primary, s for secondary */ /* D = drive: m for master, s for slave */ /* */ /* For ATA devices, the created device is a raw disk drive. */ /* */ /* For ATAPI devices, the created device appears as a scsi controller */ /* either processing only scsi-id 0 (master) or 1 (slave). */ /* */ /* Extra parameter values: */ /* */ /* nodma : do not use any form of DMA */ /* noudma : do not use any UDMA mode (regular DMA is ok) */ /* no48bit : do not use 48-bit addressing mode */ /* */ /************************************************************************/ #include "ozone.h" #include "oz_dev_disk.h" #include "oz_dev_isa.h" #include "oz_dev_pci.h" #include "oz_dev_scsi.h" #include "oz_dev_timer.h" #include "oz_io_disk.h" #include "oz_io_scsi.h" #include "oz_knl_devio.h" #include "oz_knl_hw.h" #include "oz_knl_kmalloc.h" #include "oz_knl_lowipl.h" #include "oz_knl_misc.h" #include "oz_knl_procmode.h" #include "oz_knl_sdata.h" #include "oz_knl_section.h" #include "oz_knl_spte.h" #include "oz_knl_status.h" #include "oz_sys_dateconv.h" #include "oz_sys_xprintf.h" #define CMD_INB(reg) (*(ctrlr -> inb)) (ctrlr -> atacmd + reg) #define CMD_INW(reg) (*(ctrlr -> inw)) (ctrlr -> atacmd + reg) #define CMD_OUTB(val,reg) (*(ctrlr -> outb)) (val, ctrlr -> atacmd + reg) #define CMD_OUTW(val,reg) (*(ctrlr -> outw)) (val, ctrlr -> atacmd + reg) #define CTL_INB(reg) (*(ctrlr -> inb)) (ctrlr -> atactl + reg) #define CTL_INW(reg) (*(ctrlr -> inw)) (ctrlr -> atactl + reg) #define CTL_OUTB(val,reg) (*(ctrlr -> outb)) (val, ctrlr -> atactl + reg) #define CTL_OUTW(val,reg) (*(ctrlr -> outw)) (val, ctrlr -> atactl + reg) #define BMIBA_INB(reg) (*(ctrlr -> inb)) (ctrlr -> bmiba + reg) #define BMIBA_OUTB(val,reg) (*(ctrlr -> outb)) (val, ctrlr -> bmiba + reg) #ifdef OZ_HW_TYPE_486 #define DATA_INSW(nw,va) oz_hw486_insw (nw, ctrlr -> atacmd + ATACMD_W_DATA, va) #define DATA_OUTSW(nw,va) oz_hw486_outsw (nw, va, ctrlr -> atacmd + ATACMD_W_DATA) #else #define DATA_INSW(nw,va) do { Long __nw = nw; uWord *__va = (uWord *)va; while (-- __nw >= 0) *(__va ++) = CMD_INW (ATACMD_W_DATA); } while (0) #define DATA_OUTSW(nw,va) do { Long __nw = nw; uWord *__va = (uWord *)va; while (-- __nw >= 0) CMD_OUTW (*(__va ++), ATACMD_W_DATA); } while (0) #endif #define DISK_BLOCK_SIZE (512) #define BUFFER_ALIGNMENT (3) #define L2DISK_BLOCK_SIZE (9) #define MAX_RW_RETRIES (8) #define ATAPI_CMDMAX (16) #define ATA_TIMEOUT 1 /* timeout (in seconds) - don't put parentheses on this */ #define MAX_SEC_COUNT (256) /* max sectors that the controller can transfer at a time */ #define DMATBLMAX (((MAX_SEC_COUNT*DISK_BLOCK_SIZE)>>OZ_HW_L2PAGESIZE)+1) // max number of descriptors allowed for a DMA transfer #define DMATBLSIZ (DMATBLMAX * sizeof (Dmatbl)) // number of bytes required for the DMA table #define FORTYEIGHTBIT(devex) ((devex) -> totalblocks > 0x0FFFFFFF) // non-zero if 48-bit mode required for this drive #define LBAMODE(devex) ((devex) -> ident[ATA_IDENT_W_CAPABILITIES] & 0x200) // non-zero if drive cabable of LBA addressing /* "IDENTIFY DRIVE" command return buffer (just stuff that we use) */ /* Note: these are word indicies, not byte offsets */ #define ATA_IDENT_W_DEFCYLINDERS (1) #define ATA_IDENT_W_DEFTRACKS (3) #define ATA_IDENT_W_DEFSECTORS (6) #define ATA_IDENT_W20_MODEL (27) #define ATA_IDENT_W_RWMULTIPLE (47) #define ATA_IDENT_W_CAPABILITIES (49) #define ATA_IDENT_W_CURCYLINDERS (54) /* only use for non-lba mode, ie, IDENT_W2_TOTALBLOCKS is zero */ #define ATA_IDENT_W_CURTRACKS (55) #define ATA_IDENT_W_CURSECTORS (56) #define ATA_IDENT_W2_TOTALBLOCKS (60) /* only valid if drive supports LBA, zero if it doesn't */ #define ATA_IDENT_W_MULTIWORDMA (63) #define ATA_IDENT_W_PIOMODES (64) #define ATA_IDENT_W_MWORDMACYCLE (66) #define ATA_IDENT_W_COMMANDSETS1 (83) #define ATA_IDENT_W_UDMAMODES (88) #define ATA_IDENT_W4_TOTALBLOCKS (100) #define ATA_IDENT__SIZE (128) #define ATAPI_IDENT_W20_MODEL (27) /* Struct defs */ typedef struct Chnex Chnex; typedef struct Ctrlr Ctrlr; typedef struct Devex Devex; typedef struct Iopex Iopex; /* Channel extension area (ATAPI channels only) */ struct Chnex { char drive_id; /* -1: not open, 0: master, 1: slave */ }; /* Controller data (one per cable) */ struct Ctrlr { OZ_Smplock *smplock; /* pointer to irq level smp lock */ Iopex *iopex_qh; /* requests waiting to be processed, NULL if none */ Iopex **iopex_qt; Iopex *iopex_ip; /* request currently being processed, NULL if none */ OZ_Timer *timer; /* interrupt timeout timer */ OZ_Lowipl *lowipl; /* used if controller is found to be busy */ OZ_Datebin timerwhen; /* set to date/time when we want the actual timeout */ int timerqueued; /* set to 1 when timer request queued, set to 0 when timer request dequeued */ int requestcount; /* number of iopex's queued but not yet iodone'd (master only) */ uLong atacmd; /* I/O address of ata command registers */ uLong atactl; /* I/O address of ata control registers */ uLong bmiba; /* Bus Master Interface Base Address */ /* or ZERO for generic PIO-style interface */ uByte (*inb) (uLong ioaddr); uWord (*inw) (uLong ioaddr); void (*outb) (uByte data, uLong ioaddr); void (*outw) (uWord data, uLong ioaddr); int irqlevel; /* ISA: irq level */ /* PCI: int pin (or 8+irq level) */ int cablesel; /* 0=primary; 1=secondary */ const char *suffix; /* "" for standard controllers, else "__" */ int chipidx; /* controller chip type index */ OZ_Dev_Pci_Conf *pciconf; /* NULL: ISA controller */ /* else: controller config register pci address */ OZ_Dev_Isa_Irq *isairq; // ISA style interrupt request block OZ_Dev_Pci_Irq *pciirq; // PCI style interrupt request block OZ_Dev_Pci_Dma32map *pcidma; // PCI 32-bit DMA mapping block char unitname[OZ_DEVUNIT_NAMESIZE]; /* unit name string temp buffer */ const char *cable; /* "primary" or "secondary" */ Devex *devexs[2]; /* devices attached to controller or NULL if none there */ uWord seccount[2], tracount[2]; /* default sector and track counts for the possible two devices */ uWord secbuf[256]; /* temporary sector buffer used for identing drives */ }; /* Device extension structure */ struct Devex { OZ_Devunit *devunit; /* devunit pointer */ const char *name; /* device unit name (for messages) */ Ctrlr *ctrlr; /* pointer to controller struct */ int usedma; /* 0: use PIO transfer modes, 1: use DMA transfer modes */ /* atapi device only (atapimsk != 0) */ uLong atapimsk; /* <0>: master is ATAPI; <1>: slave is ATAPI */ uLong atapiopn; /* which ATAPI device(s) are open */ uWord atapiidentword0; /* atapi ident word 0 */ /* ata devices only (atapimsk == 0) */ uLong secpertrk; /* number of sectors in a track */ uLong trkpercyl; /* number of tracks per cylinder (number of heads) */ uLong secpercyl; /* number of sectors in a cylinder */ uLong totalblocks; /* total number of sectors on the drive */ uWord ident[ATA_IDENT__SIZE]; /* 'Identify Drive' command results */ uByte atacmd_read; /* read/write command code bytes */ uByte atacmd_read_inh; uByte atacmd_write; uByte multsize; /* multiple sector transfer size (or 0 if disabled) */ char drive_id; /* 0: this is a master drive, 1: this is a slave drive */ }; /* I/O operation extension structure */ struct Iopex { Iopex *next; /* next in ctrlr -> iopex_qh/qt */ OZ_Ioop *ioop; /* pointer to io operation node */ OZ_Procmode procmode; /* requestor's processor mode */ Devex *devex; /* device extension data pointer */ int writedisk; /* 0: data transfer from device to memory; 1: data transfer from memory to device */ uLong status; /* completion status */ const OZ_Mempage *phypages; /* physical page array pointer */ uLong byteoffs; /* starting physical page byte offset */ uLong amount_done; /* amount already written to / read from disk within the whole request */ uLong amount_xfrd; /* amount transferred within this disk command */ uLong amount_to_xfer; /* total amount to transfer within this disk command */ /* ATA style commands only */ uByte atacmdcode; /* ATA command code */ uLong size; /* buffer size */ uLong seccount; /* sector count for transfer */ OZ_Dbn slbn; /* starting logical block number */ int timedout; /* set if request timed out somewhere */ int retries; /* retry counter */ int ix4kbuk; /* reading an IX database 4k-byte sized bucket */ /* ATAPI style commands only */ OZ_IO_scsi_doiopp doiopp; /* scsi parameter block */ uLong dmatblmx; // number of pages in pcidma table OZ_Dev_Pci_Dma32map *pcidma; // PCI 32-bit DMA mapping block uByte atapicmd[ATAPI_CMDMAX]; /* scsi command buffer */ char drive_id; /* drive number (0 or 1) */ uByte atapists; /* scsi status byte (0=success, 2=error) */ }; /* Function tables - one for ATA devices, one for ATAPI devices */ static int shutdown (OZ_Devunit *devunit, void *devexv); static uLong ata_start (OZ_Devunit *devunit, void *devexv, OZ_Iochan *iochan, void *chnexv, OZ_Procmode procmode, OZ_Ioop *ioop, void *iopexv, uLong funcode, uLong as, void *ap); static const OZ_Devfunc ata_functable = { sizeof (Devex), 0, sizeof (Iopex), 0, shutdown, NULL, NULL, NULL, NULL, NULL, ata_start, NULL }; static uLong atapi_assign (OZ_Devunit *devunit, void *devexv, OZ_Iochan *iochan, void *chnexv, OZ_Procmode procmode); static int atapi_deassign (OZ_Devunit *devunit, void *devexv, OZ_Iochan *iochan, void *chnexv); static uLong atapi_start (OZ_Devunit *devunit, void *devexv, OZ_Iochan *iochan, void *chnexv, OZ_Procmode procmode, OZ_Ioop *ioop, void *iopexv, uLong funcode, uLong as, void *ap); static const OZ_Devfunc atapi_functable = { sizeof (Devex), sizeof (Chnex), sizeof (Iopex), 0, shutdown, NULL, NULL, atapi_assign, atapi_deassign, NULL, atapi_start, NULL }; /* Internal static data */ static int initialized = 0; static OZ_Devclass *devclass_disk, *devclass_scsi; static OZ_Devdriver *devdriver_disk, *devdriver_scsi; static Devex *crash_devex = NULL; static int crash_inprog = 0; static Iopex crash_iopex; static OZ_Devunit *crash_devunit = NULL; /* A block of zeroes to use when writing less than a full block */ /* (Reading partial blocks goes to phypage zero) */ /* Hopefully using uLong will longword align it */ static const uLong zeroes[DISK_BLOCK_SIZE/4]; /* Internal routines */ static int foundpci (void *ivv, uLong didvid, int func, OZ_Dev_Pci_Conf *pciconf, char const *addrsuffix, char const *addrdescrip); static int config (uLong atacmd, uLong atactl, uLong bmiba, int irqlevel, int cablesel, const char *suffix, int chipidx, OZ_Dev_Pci_Conf *pciconf); static void enable_drives (Ctrlr *ctrlr); static void enable_interrupts (Ctrlr *ctrlr); static int probe_atadrive (Ctrlr *ctrlr, int drive_id); static void init_atadrive (Ctrlr *ctrlr, int drive_id); static int probe_atapidrv (Ctrlr *ctrlr, int drive_id); static void init_atapidrv (Ctrlr *ctrlr, int drive_id); static void setup_null (Ctrlr *ctrlr); static void setup_piix4 (Ctrlr *ctrlr); static void setup_piix4_drive (OZ_Dev_Pci_Conf *pciconf, int cablesel, Ctrlr *ctrlr, Devex *devex); static void setup_amd768 (Ctrlr *ctrlr); static void setup_amd768_drive (OZ_Dev_Pci_Conf *pciconf, int cablesel, Ctrlr *ctrlr, Devex *devex); static void setup_via686 (Ctrlr *ctrlr); static void setup_via686_drive (OZ_Dev_Pci_Conf *pciconf, int cablesel, Ctrlr *ctrlr, Devex *devex); static int get_drive_udma_mode (Devex *devex, int maxudmamode); static int set_drive_udma_mode (Devex *devex, int udmamode); static uLong waitfornotbusy (void *ctrlrv); static uLong getidentbuff (void *ctrlrv); static void ctrlr_fillin (Ctrlr *ctrlr); static int shutdown (OZ_Devunit *devunit, void *devexv); static uLong ata_crash (void *dummy, OZ_Dbn lbn, uLong size, OZ_Mempage phypage, uLong offset); static uLong ata_queuereq (uLong size, OZ_Dbn slbn, const OZ_Mempage *phypages, uLong byteoffs, Iopex *iopex); static uLong atapi_queuereq (Iopex *iopex); static void queuereq (Iopex *iopex); static void startreq (Ctrlr *ctrlr); static void startdma (Iopex *iopex, uLong xfersize, uLong blocksize); static void stopdma (Iopex *iopex); static void recalibrate (Devex *devex); static uLong checkrecaldone (void *ctrlrv); static void ctrlrhung (void *ctrlrv, OZ_Lowipl *lowipl); static void reqtimedout (void *ctrlrv, OZ_Timer *timer); static void intserv (void *ctrlrv, OZ_Mchargs *mchargs); static void dma_intserv (Ctrlr *ctrlr, Devex *devex, Iopex *iopex, uByte status, uByte bmisx); static void pio_ata_intserv (Iopex *iopex, uByte status); static void pio_atapi_intserv (Iopex *iopex, uByte status); static int atapi_command_packet (Iopex *iopex, uByte status); static void do_pio_transfer (int nbytes, Iopex *iopex); static void ata_finish (Iopex *iopex, uByte status); static void atapi_finish (Iopex *iopex, uByte status); static void reqdone (Iopex *iopex); static void atapi_reqdone (void *iopexv, uLong *status_r); static void validaterequestcount (Ctrlr *ctrlr, int line); /************************************************************************/ /* */ /* I/O space registers */ /* */ /************************************************************************/ /* These are biased by Ctrlr field atacmd */ #define ATACMD_W_DATA (0) /* RW: data */ #define ATACMD_BR_ERROR (1) /* RO: error */ /* <0> : AMNF - address mark not found */ /* <1> : TK0NF - track zero not found (recal command) */ /* <2> : ABRT - command aborted due to drive status error */ /* <3> : MCR - media change requested */ /* <4> : IDNF - requested sector's ID field cont not be found */ /* <5> : MC - media changed */ /* <6> : UNC - uncorrectable data error */ /* <7> : BBK - bad block detected */ #define ATACMD_BW_FEATURES (1) /* WO: features */ #define ATACMD_B_SECCOUNT (2) /* RW: sector count */ #define ATACMD_B_LBALOW (3) /* RW: sector number / LBA <00:07> */ #define ATACMD_B_SECNUM (3) #define ATACMD_B_LBAMID (4) /* RW: cylno <00:07> / LBA <08:15> */ #define ATACMD_B_CYL_LO (4) #define ATACMD_B_LBAHIGH (5) /* RW: cylno <08:15> / LBA <16:23> */ #define ATACMD_B_CYL_HI (5) #define ATACMD_B_DRHEAD (6) /* RW: */ /* <0:3> : head select */ /* <4> : drive select */ /* <5> : 1 */ /* <6> : 0 = CHS mode */ /* 1 = LBA mode */ /* <7> : 1 */ #define ATACMD_BR_STATUS (7) /* RO: status */ /* <0> : ERR - an error occurred during the command */ /* <1> : IDX - set once per revolution */ /* <2> : CORR - correctable data error occurred */ /* <3> : DRQ - ready to transfer a word of data */ /* <4> : DSC - drive seek complete */ /* <5> : DWF - drive write fault */ /* <6> : DRDY - drive ready - able to respond to a command */ /* <7> : BSY - controller busy */ #define ATACMD_BW_COMMAND (7) /* WO: command */ /* These are biased by Ctrlr field atactl */ #define ATACTL_BR_ALTSTS (2) #define ATACTL_BW_DEVCTL (2) /* <0> : must be zero */ /* <1> : 0: interrupts enabled; 1: interrupts disabled */ /* <2> : software reset when set */ /* <3> : must be one */ /* <4> : must be zero */ /* <5> : must be zero */ /* <6> : must be zero */ /* <7> : must be zero */ /* These are biased by Ctrlr field bmiba */ #define BMIBA_B_BMICX (0) /* bus master ide command register */ #define BMIBA_B_BMISX (2) /* bus master ide status register */ #define BMIBA_L_BMIDTPX (4) /* bus master ide descriptor table pointer register */ /************************************************************************/ /* */ /* Boot-time initialization routine */ /* */ /************************************************************************/ /* Here is how this all fits together: oz_dev_ide8038i_init () { scan for PCI controller chips { get atacmd, atactl, bmiba from pci config regs config (PCI primary) config (PCI secondary) } config (ISA default primary) config (ISA default secondary) } // Check out and configure everything about a given controller and its drives config () { create ctrlr struct & fill in what we can scan both drives on cable { if !probe_atadrive, try probe_atapidrv } software reset controller if any drive(s) found, enable_drives enable_interrupts else, free ctrlr struct } // See if a given drive exists and is an ATA drive probe_atadrive () { software reset controller select drive to be identified send ATA identify command to see if ATA drive is there if error, return failure status create and fill in devunit/devex struct, link to ctrlr struct finish filling in ctrlr struct return success status } // See if a given drive exists and is an ATAPI drive probe_atapidrv () { software reset controller select drive to be identified send ATAPI identify command if error, return failure status create and fill in devunit/devex struct, link to ctrlr struct finish filling in ctrlr struct return success status } // Enable the drives that we found enable_drives () { release the software reset call controller-specific setup routine for each drive found during probing { select it call either init_atadrive or init_atapidrv } } // Get ATA drive ready to go init_atadrive () { send 'init drive params' command set up what types of commands to use for read/write (DMA, 48-bit, LBA, multiblock) set up autogen to find partitions and mount filesystem } // Get ATAPI drive ready to go init_atapidrv () { send 'identify drive' command set up autogen to access disk via scsi } // Enable interrupts for drives on the cable enable_interrupts () { for each drive found during probing { select it enable interrupt } } */ /* Table of chips we do special setup for (like UDMA) */ static const struct { uLong didvid; int pcifunc; const char *name; void (*setup) (Ctrlr *ctrlr); } chips[] = { 0x70108086, 1, "Intel PIIX3", setup_null, 0x71118086, 1, "Intel PIIX4", setup_piix4, 0x74411022, 1, "AMD 768", setup_amd768, 0x05711106, 1, "VIA 686", setup_via686, 0, 0, NULL }; typedef struct { int defpriused, defsecused, index; } Initvars; void oz_dev_ide8038i_init () { Initvars iv; if (initialized) return; oz_knl_printk ("oz_dev_ide8038i_init\n"); initialized = 1; devclass_disk = oz_knl_devclass_create (OZ_IO_DISK_CLASSNAME, OZ_IO_DISK_BASE, OZ_IO_DISK_MASK, "ide8038i_ata"); devdriver_disk = oz_knl_devdriver_create (devclass_disk, "ide8038i_ata"); devclass_scsi = oz_knl_devclass_create (OZ_IO_SCSI_CLASSNAME, OZ_IO_SCSI_BASE, OZ_IO_SCSI_MASK, "ide8038i_atapi"); devdriver_scsi = oz_knl_devdriver_create (devclass_scsi, "ide8038i_atapi"); /* Scan for PCI-style controller chips so we can use (U)DMA functionality */ iv.defpriused = 0; iv.defsecused = 0; for (iv.index = 0; chips[iv.index].didvid != 0; iv.index ++) { oz_dev_pci_find_didvid (chips[iv.index].didvid, chips[iv.index].pcifunc, OZ_DEV_PCI_FINDFLAG_HASBASADR0 // primary command I/O registers | OZ_DEV_PCI_FINDFLAG_HASBASADR1 // primary control I/O registers | OZ_DEV_PCI_FINDFLAG_HASBASADR2 // secondary command I/O regs | OZ_DEV_PCI_FINDFLAG_HASBASADR3 // secondary control I/O regs | OZ_DEV_PCI_FINDFLAG_HASBASADR4, // bus master interface registers foundpci, &iv); } /* If defaults not found, try generic ISA PIO-style controller */ if (!iv.defpriused) config (0x01F0, 0x03F4, 0, 14, 0, "", -1, NULL); // try config primary controller if (!iv.defsecused) config (0x0170, 0x0374, 0, 15, 1, "", -1, NULL); // try config secondary controller } static int foundpci (void *ivv, uLong didvid, int func, OZ_Dev_Pci_Conf *pciconf, char const *addrsuffix, char const *addrdescrip) { char const *suffix; Initvars *iv; int init, priirqnm, secirqnm, started; uByte progintf; uLong bmiba, pricmdba, prictlba, seccmdba, secctlba; iv = ivv; oz_knl_printk ("oz_dev_ide8038i: found %s ide controller at %s\n", chips[iv->index].name, addrdescrip); /* Get primary/secondary command/control register base addresses */ pricmdba = 0x01F0; // set up default port numbers prictlba = 0x03F4; priirqnm = 8 + 14; // 8+irq for oz_dev_pci_irq_alloc to indicate // specific IRQ number instead of INTPIN number seccmdba = 0x0170; secctlba = 0x0374; secirqnm = 8 + 15; suffix = ""; progintf = oz_dev_pci_conf_inb (pciconf, OZ_DEV_PCI_CONF_B_PI); if (progintf & 1) { // see if there are explicit primary ports // - piix4 is hardwired to zero // - amd768 is programmable pricmdba = oz_dev_pci_conf_inl (pciconf, OZ_DEV_PCI_CONF_L_BASADR0); // if so, read them prictlba = oz_dev_pci_conf_inl (pciconf, OZ_DEV_PCI_CONF_L_BASADR1); if (((pricmdba & 3) != 1) || ((prictlba & 3) != 1)) { // they must be in I/O space oz_knl_printk ("oz_dev_ide8038i: bad primary reg bus address %X/%X\n", pricmdba, prictlba); return (1); } pricmdba --; // clear the I/O space bit prictlba --; suffix = addrsuffix; // set up suffix string } else { if (iv -> defpriused) { oz_knl_printk ("oz_dev_ide8038i: default primary already defined\n"); return (1); } iv -> defpriused = 1; } if (progintf & 4) { // see if there are explicit secondary ports // - piix4 is hardwired to zero // - amd768 is programmable seccmdba = oz_dev_pci_conf_inl (pciconf, OZ_DEV_PCI_CONF_L_BASADR2); // if so, read them secctlba = oz_dev_pci_conf_inl (pciconf, OZ_DEV_PCI_CONF_L_BASADR3); if (((seccmdba & 3) != 1) || ((secctlba & 3) != 1)) { // they must be in I/O space oz_knl_printk ("oz_dev_ide8038i: bad secondary reg bus address %X/%X\n", seccmdba, secctlba); return (1); } seccmdba --; // clear the I/O space bit secctlba --; suffix = addrsuffix; // set up suffix string } else { if (iv -> defsecused) { oz_knl_printk ("oz_dev_ide8038i: default secondary already defined\n"); return (1); } iv -> defsecused = 1; } bmiba = oz_dev_pci_conf_inl (pciconf, OZ_DEV_PCI_CONF_L_BASADR4); // get dma controller base I/O address // - same on piix4 and amd768 if ((bmiba & 0xF) != 1) { // verify the bits we assume to be as indicated really are // bmiba<00> = 1 : means the bmiba stuff is in I/O space oz_knl_printk ("oz_dev_ide8038i: bad bus master reg bus address %X\n", bmiba); return (1); } started = config (pricmdba, prictlba, bmiba - 1, priirqnm, 0, suffix, iv -> index, pciconf); started |= config (seccmdba, secctlba, bmiba + 7, secirqnm, 1, suffix, iv -> index, pciconf); if (started) { oz_dev_pci_conf_outw (0x0005, pciconf, OZ_DEV_PCI_CONF_W_PCICMD); // set bus master enable to enable dma // set I/O space enable to access dma registers oz_dev_pci_conf_outw (0x3800, pciconf, OZ_DEV_PCI_CONF_W_PCISTS); // reset error status bits } return (1); } /************************************************************************/ /* */ /* Try to configure drives connected to a particular cable */ /* */ /* Input: */ /* */ /* atacmd = command register base address */ /* atactl = control register base address */ /* bmiba = 0 : non-DMA controller */ /* else : dma register base address */ /* irqlevel = irq level number */ /* cablesel = 0 : primary cable */ /* 1 : secondary cable */ /* suffix = device name suffix or "" for standard device names */ /* chipidx = index in 'chips[]' array for this controller */ /* pciconf = NULL : generic non-DMA controller */ /* else : controller's PCI config space */ /* */ /************************************************************************/ static int config (uLong atacmd, uLong atactl, uLong bmiba, int irqlevel, int cablesel, const char *suffix, int chipidx, OZ_Dev_Pci_Conf *pciconf) { Ctrlr *ctrlr; int drive_id; /* Create and fill in initial controller struct */ ctrlr = OZ_KNL_NPPMALLOC (sizeof *ctrlr); memset (ctrlr, 0, sizeof *ctrlr); ctrlr -> atacmd = atacmd; ctrlr -> atactl = atactl; ctrlr -> bmiba = bmiba; ctrlr -> irqlevel = irqlevel; ctrlr -> cablesel = cablesel; ctrlr -> suffix = suffix; ctrlr -> chipidx = chipidx; ctrlr -> pciconf = pciconf; if (pciconf != NULL) { ctrlr -> inb = oz_dev_pci_inb; ctrlr -> inw = oz_dev_pci_inw; ctrlr -> outb = oz_dev_pci_outb; ctrlr -> outw = oz_dev_pci_outw; } else { ctrlr -> inb = oz_dev_isa_inb; ctrlr -> inw = oz_dev_isa_inw; ctrlr -> outb = oz_dev_isa_outb; ctrlr -> outw = oz_dev_isa_outw; } ctrlr -> cable = "primary"; if (cablesel) ctrlr -> cable = "secondary"; oz_knl_printk ("oz_dev_ide8038i: probing %s drives (%X/%X/%X)\n", ctrlr -> cable, atacmd, atactl, bmiba); /* Probe master (drive_id 0) then slave (drive_id 1) drive */ for (drive_id = 0; drive_id < 2; drive_id ++) { /* Make up unit name string - */ /* unitname = ide_ */ /* cable = p for primary, s for secondary */ /* drive = m for master, s for slave */ /* suffix = _bus_dev if non-standard */ strcpy (ctrlr -> unitname, "ide_xx"); ctrlr -> unitname[4] = ctrlr -> cable[0]; ctrlr -> unitname[5] = drive_id ? 's' : 'm'; strcat (ctrlr -> unitname, suffix); oz_knl_printk ("oz_dev_ide8038i: identifying %s %s drive\n", ctrlr -> cable, drive_id ? "slave" : "master"); if (!probe_atadrive (ctrlr, drive_id)) probe_atapidrv (ctrlr, drive_id); } /* If we found any drives, enable them */ CTL_OUTB (0x0E, ATACTL_BW_DEVCTL); // software reset the drives, disable interrupts if (ctrlr -> iopex_qt != NULL) { // see if master and/or slave was found enable_drives (ctrlr); // ok, enable it/them enable_interrupts (ctrlr); // also enable interrupts } else { OZ_KNL_NPPFREE (ctrlr); // no, free struct ctrlr = NULL; // nothing found } return (ctrlr != NULL); // return whether or not we found something } /************************************************************************/ /* */ /* The controller has just been software reset. Enable it then */ /* enable any drives that were found during probe. */ /* */ /************************************************************************/ static void enable_drives (Ctrlr *ctrlr) { int drive_id; oz_hw_stl_nanowait (50000000); // wait 50mS CTL_OUTB (0x0A, ATACTL_BW_DEVCTL); // release the drives oz_hw_stl_nanowait (50000000); // wait 50mS /* Set up data transfer mode and other controller stuff */ if (ctrlr -> chipidx >= 0) (*(chips[ctrlr->chipidx].setup)) (ctrlr); /* Enable the drives */ for (drive_id = 0; drive_id < 2; drive_id ++) { if (ctrlr -> devexs[drive_id] == NULL) continue; // see if we found anything in the scan CMD_OUTB ((drive_id << 4) | 0xA0, ATACMD_B_DRHEAD); // select the drive to be initialized oz_hw_stl_nanowait (400); CTL_OUTB (0x0A, ATACTL_BW_DEVCTL); // disable interrupts if (ctrlr -> devexs[drive_id] -> atapimsk == 0) init_atadrive (ctrlr, drive_id); else init_atapidrv (ctrlr, drive_id); } } /* Enable interrupts for drives on the cable */ static void enable_interrupts (Ctrlr *ctrlr) { int drive_id; for (drive_id = 0; drive_id < 2; drive_id ++) { if (ctrlr -> devexs[drive_id] == NULL) continue; // see if we found anything in the scan CMD_OUTB ((drive_id << 4) | 0xA0, ATACMD_B_DRHEAD); // select the drive to be enabled oz_hw_stl_nanowait (400); CTL_OUTB (0x08, ATACTL_BW_DEVCTL); // enable interrupts } } /************************************************************************/ /* */ /* Probe for an ATA drive */ /* */ /************************************************************************/ static int probe_atadrive (Ctrlr *ctrlr, int drive_id) { char *p, unitdesc[OZ_DEVUNIT_DESCSIZE]; const char *extra; Devex *devex; int i; OZ_Devunit *devunit; uByte status; uLong sts; /* Reset both drives on cable so they can't be screwed up by previous probing */ CTL_OUTB (0x0E, ATACTL_BW_DEVCTL); // software reset the drives oz_hw_stl_nanowait (50000000); // wait 50mS CTL_OUTB (0x0A, ATACTL_BW_DEVCTL); // release the drives oz_hw_stl_nanowait (50000000); // wait 50mS CMD_OUTB ((drive_id << 4) | 0xA0, ATACMD_B_DRHEAD); // select the drive to be identified oz_hw_stl_nanowait (400); CTL_OUTB (0x0A, ATACTL_BW_DEVCTL); // disable its interrupts sts = oz_hw_stl_microwait (50000, waitfornotbusy, ctrlr); // give it 50mS to finish if (sts == 0) { oz_knl_printk ("oz_dev_ide8038i: - timed out resetting\n"); return (0); } status = sts; // check for error if ((status & 0xD9) != 0x50) { oz_knl_printk ("oz_dev_ide8038i: - reset status 0x%X\n", status); if (status & 0x01) { status = CMD_INB (ATACMD_BR_ERROR); oz_knl_printk ("oz_dev_ide8038i: - error code 0x%X\n", status); } return (0); } /* Now find out about the drive */ CMD_OUTB (0xEC, ATACMD_BW_COMMAND); // tell it to identify itself oz_hw_stl_nanowait (400); // give the drive 400nS to start processing the command memset (ctrlr -> secbuf, 0, sizeof ctrlr -> secbuf); // zero fill secbuf sts = oz_hw_stl_microwait (50000, getidentbuff, ctrlr); // give it 50mS to finish identifying itself if (sts == 0) { oz_knl_printk ("oz_dev_ide8038i: - timed out reading identification\n"); return (0); } status = sts; // check for error if ((status & 0xD9) != 0x50) { oz_knl_printk ("oz_dev_ide8038i: - identify status 0x%X\n", status); if (status & 0x01) { status = CMD_INB (ATACMD_BR_ERROR); oz_knl_printk ("oz_dev_ide8038i: - error code 0x%X\n", status); } return (0); } /* Fix up the model name string */ for (i = ATA_IDENT_W20_MODEL; i < ATA_IDENT_W20_MODEL + 20; i ++) { ctrlr -> secbuf[i] = (ctrlr -> secbuf[i] << 8) | (ctrlr -> secbuf[i] >> 8); } for (p = (char *)(ctrlr -> secbuf + ATA_IDENT_W20_MODEL + 20); *(-- p) == ' ';) {} *(++ p) = 0; oz_knl_printk ("oz_dev_ide8038i: - model %s\n", ctrlr -> secbuf + ATA_IDENT_W20_MODEL); /* Get and validate drive init params */ ctrlr -> seccount[drive_id] = ctrlr -> secbuf[ATA_IDENT_W_DEFSECTORS]; if ((ctrlr -> seccount[drive_id] == 0) || (ctrlr -> seccount[drive_id] > 256)) { oz_knl_printk ("oz_dev_ide8038i: - can't init drive params because default sector count is %u\n", ctrlr -> seccount[drive_id]); return (1); } ctrlr -> tracount[drive_id] = ctrlr -> secbuf[ATA_IDENT_W_DEFTRACKS]; if ((ctrlr -> tracount[drive_id] == 0) || (ctrlr -> tracount[drive_id] > 16)) { oz_knl_printk ("oz_dev_ide8038i: - can't init drive params because default track count is %u\n", ctrlr -> tracount[drive_id]); return (1); } /* Unit description is the drive model */ strncpyz (unitdesc, (char *)(ctrlr -> secbuf + ATA_IDENT_W20_MODEL), sizeof unitdesc); /* Create the device unit struct */ devunit = oz_knl_devunit_create (devdriver_disk, ctrlr -> unitname, unitdesc, &ata_functable, 0, oz_s_secattr_sysdev); if (devunit == NULL) return (1); /* Fill in the device unit extension info */ ctrlr -> devexs[drive_id] = devex = oz_knl_devunit_ex (devunit); memset (devex, 0, sizeof *devex); ctrlr_fillin (ctrlr); devex -> devunit = devunit; devex -> name = oz_knl_devunit_devname (devunit); devex -> ctrlr = ctrlr; devex -> drive_id = drive_id; devex -> usedma = (ctrlr -> bmiba != 0) && (ctrlr -> secbuf[ATA_IDENT_W_CAPABILITIES] & 0x0100); memcpy (devex -> ident, ctrlr -> secbuf, sizeof devex -> ident); devex -> secpertrk = devex -> ident[ATA_IDENT_W_CURSECTORS]; devex -> trkpercyl = devex -> ident[ATA_IDENT_W_CURTRACKS]; devex -> secpercyl = (OZ_Dbn)(devex -> ident[ATA_IDENT_W_CURSECTORS]) * (OZ_Dbn)(devex -> ident[ATA_IDENT_W_CURTRACKS]); devex -> totalblocks = (OZ_Dbn)(devex -> ident[ATA_IDENT_W_CURCYLINDERS]) * devex -> secpercyl; if (LBAMODE (devex)) { devex -> totalblocks = devex -> ident[ATA_IDENT_W2_TOTALBLOCKS+0] + (devex -> ident[ATA_IDENT_W2_TOTALBLOCKS+1] << 16); if ((devex -> ident[ATA_IDENT_W_COMMANDSETS1] & 0xC400) == 0x4400) { extra = oz_knl_misc_getextra (devex -> name, ""); if (strstr (extra, "no48bit") != NULL) { oz_knl_printk ("oz_dev_ide8038i: - >128GB disabled by extra parameter\n"); } else { devex -> totalblocks = 0xFFFFFFFF; if ((devex -> ident[ATA_IDENT_W4_TOTALBLOCKS+2] == 0) && (devex -> ident[ATA_IDENT_W4_TOTALBLOCKS+3] == 0)) { devex -> totalblocks = devex -> ident[ATA_IDENT_W4_TOTALBLOCKS+0] + (devex -> ident[ATA_IDENT_W4_TOTALBLOCKS+1] << 16); } } } } oz_knl_printk ("oz_dev_ide8038i: - sec/trk %u, trk/cyl %u, totalblocks %u\n", devex -> secpertrk, devex -> trkpercyl, devex -> totalblocks); if (devex -> usedma) { extra = oz_knl_misc_getextra (devex -> name, ""); if (strstr (extra, "nodma") != NULL) { devex -> usedma = 0; oz_knl_printk ("oz_dev_ide8038i: - DMA disabled by extra parameter\n"); } } return (1); } /************************************************************************/ /* */ /* Initialize ATA drive */ /* */ /************************************************************************/ static void init_atadrive (Ctrlr *ctrlr, int drive_id) { Devex *devex; int i; uByte status; uLong sts; devex = ctrlr -> devexs[drive_id]; CMD_OUTB ((drive_id << 4) | 0xA0 | (ctrlr -> tracount[drive_id] - 1), ATACMD_B_DRHEAD); // set number of heads CMD_OUTB (ctrlr -> seccount[drive_id], ATACMD_B_SECCOUNT); // set number of sectors CMD_OUTB (0x91, ATACMD_BW_COMMAND); // initialize drive parameters oz_hw_stl_nanowait (400); // give the drive 400nS to start processing the command oz_hw_stl_microwait (50000, waitfornotbusy, ctrlr); // give it 50mS to finish status = CMD_INB (ATACMD_BR_STATUS); if ((status & 0xD9) != 0x50) { // check for error oz_knl_printk ("oz_dev_ide8038i: - initialize drive parameters %X %X status 0x%X\n", (drive_id << 4) | 0xA0 | (ctrlr -> tracount[drive_id] - 1), ctrlr -> seccount[drive_id], status); if (status & 0x01) { status = CMD_INB (ATACMD_BR_ERROR); oz_knl_printk ("oz_dev_ide8038i: - error code 0x%X\n", status); } return; } /* Maybe it is DMA */ if (devex -> usedma) { devex -> atacmd_read = 0xC8; devex -> atacmd_read_inh = 0xC9; devex -> atacmd_write = 0xCA; if (FORTYEIGHTBIT (devex)) { devex -> atacmd_read = 0x25; devex -> atacmd_read_inh = 0x25; devex -> atacmd_write = 0x35; } } /* Else, try to set up multiple sector count = one page (for cache transfers) */ else { devex -> multsize = 0; devex -> atacmd_read = 0x20; devex -> atacmd_read_inh = 0x21; devex -> atacmd_write = 0x30; if (FORTYEIGHTBIT (devex)) { devex -> atacmd_read = 0x24; devex -> atacmd_read_inh = 0x24; devex -> atacmd_write = 0x34; } i = (1 << OZ_HW_L2PAGESIZE) / DISK_BLOCK_SIZE; if ((devex -> ident[ATA_IDENT_W_RWMULTIPLE] & 0xFF) < i) i = devex -> ident[ATA_IDENT_W_RWMULTIPLE] & 0xFF; if (i > 1) { CMD_OUTB ((drive_id << 4) | 0xA0, ATACMD_B_DRHEAD); /* select the drive to be set up */ CMD_OUTB ((1 << OZ_HW_L2PAGESIZE) / DISK_BLOCK_SIZE, ATACMD_B_SECCOUNT); /* set up number of sectors we want to do at a time */ CMD_OUTB (0xC6, ATACMD_BW_COMMAND); /* tell it to set the multiple block count */ oz_hw_stl_nanowait (400); /* give the drive 400nS to start processing the command */ sts = oz_hw_stl_microwait (50000, waitfornotbusy, ctrlr); /* wait for it to execute (max 50mS) */ if (sts == 0) sts = CTL_INB (ATACTL_BR_ALTSTS); if ((sts & 0xC9) == 0x40) { devex -> multsize = (1 << OZ_HW_L2PAGESIZE) / DISK_BLOCK_SIZE; devex -> atacmd_read = 0xC4; devex -> atacmd_read_inh = 0xC4; devex -> atacmd_write = 0xC5; if (FORTYEIGHTBIT (devex)) { devex -> atacmd_read = 0x29; devex -> atacmd_read_inh = 0x29; devex -> atacmd_write = 0x39; } } else { oz_knl_printk ("oz_dev_ide8038i: - status %2.2X error %2.2X setting multiple sector transfers\n", sts & 0xFF, CMD_INB (ATACMD_BR_ERROR)); } } } /* Check for partitions */ oz_knl_devunit_autogen (devex -> devunit, oz_dev_disk_auto, NULL); } /************************************************************************/ /* */ /* Probe for an ATAPI drive */ /* */ /************************************************************************/ static int probe_atapidrv (Ctrlr *ctrlr, int drive_id) { char *p, unitdesc[OZ_DEVUNIT_DESCSIZE]; const char *extra; Devex *devex; int i; OZ_Devunit *devunit; uByte status; uLong sts; uWord ident0; /* Reset both drives on cable so they can't be screwed up by previous probing */ CTL_OUTB (0x0E, ATACTL_BW_DEVCTL); // software reset the drives oz_hw_stl_nanowait (50000000); // wait 50mS CTL_OUTB (0x0A, ATACTL_BW_DEVCTL); // release the drives oz_hw_stl_nanowait (50000000); // wait 50mS CMD_OUTB ((drive_id << 4) | 0xA0, ATACMD_B_DRHEAD); // select the drive to be identified oz_hw_stl_nanowait (400); CTL_OUTB (0x0A, ATACTL_BW_DEVCTL); // disable its interrupts /* Try to tell ATAPI drive to identify itself */ CMD_OUTB (0xA1, ATACMD_BW_COMMAND); // tell it to identify itself oz_hw_stl_nanowait (400); // give the drive 400nS to start processing the command memset (ctrlr -> secbuf, 0, sizeof ctrlr -> secbuf); // zero fill secbuf status = oz_hw_stl_microwait (50000, getidentbuff, ctrlr); // read it from drive if ((status & 0xC9) != 0x40) { oz_knl_printk ("oz_dev_ide8038i: - atapi ident status 0x%X\n", status); if (status & 0x01) oz_knl_printk ("oz_dev_ide8038i: - error code 0x%X\n", CMD_INB (ATACMD_BR_ERROR)); return (0); } ident0 = ctrlr -> secbuf[0]; if ((ident0 & 0xC002) != 0x8000) { oz_knl_printk ("oz_dev_ide8038i: - atapi ident word[0] %4.4X not supported\n", ident0); return (0); } /* We got something back, so assume it is an ATAPI device - */ /* So we set up an device that looks like an scsi controller */ /* but it will only process scsi id 0 or 1. Then we call the scsi */ /* routines to create the appropriate class device for the unit. */ for (i = ATAPI_IDENT_W20_MODEL; i < ATAPI_IDENT_W20_MODEL + 20; i ++) { ctrlr -> secbuf[i] = (ctrlr -> secbuf[i] << 8) | (ctrlr -> secbuf[i] >> 8); } for (p = (char *)(ctrlr -> secbuf + ATAPI_IDENT_W20_MODEL + 20); *(-- p) == ' ';) {} *(++ p) = 0; oz_knl_printk ("oz_dev_ide8038i: - atapi %s\n", ctrlr -> secbuf + ATAPI_IDENT_W20_MODEL); oz_sys_sprintf (sizeof unitdesc, unitdesc, "access via %s.%u", ctrlr -> unitname, drive_id); devunit = oz_knl_devunit_create (devdriver_scsi, ctrlr -> unitname, unitdesc, &atapi_functable, 0, oz_s_secattr_sysdev); ctrlr -> devexs[drive_id] = devex = oz_knl_devunit_ex (devunit); memset (devex, 0, sizeof *devex); ctrlr_fillin (ctrlr); devex -> devunit = devunit; devex -> name = oz_knl_devunit_devname (devunit); devex -> ctrlr = ctrlr; devex -> drive_id = drive_id; devex -> usedma = (ctrlr -> bmiba != 0); devex -> atapimsk = 1 << drive_id; devex -> atapiidentword0 = ctrlr -> secbuf[0]; if (devex -> usedma) { extra = oz_knl_misc_getextra (devex -> name, ""); if (strstr (extra, "nodma") != NULL) { devex -> usedma = 0; oz_knl_printk ("oz_dev_ide8038i: - DMA disabled by extra parameter\n"); } } return (1); } /************************************************************************/ /* */ /* Initialize ATAPI drive */ /* */ /************************************************************************/ static void init_atapidrv (Ctrlr *ctrlr, int drive_id) { Devex *devex; uByte status; uWord ident0; devex = ctrlr -> devexs[drive_id]; /* Try to tell ATAPI drive to identify itself again after the reset */ /* Otherwise, its status is funny */ CMD_OUTB (0xA1, ATACMD_BW_COMMAND); // tell it to identify itself oz_hw_stl_nanowait (400); // give the drive 400nS to start processing the command memset (ctrlr -> secbuf, 0, sizeof ctrlr -> secbuf); // zero fill secbuf status = oz_hw_stl_microwait (50000, getidentbuff, ctrlr); // read it from drive if ((status & 0xC9) != 0x40) { oz_knl_printk ("oz_dev_ide8038i: %s atapi ident status 0x%X\n", devex -> name, status); if (status & 0x01) oz_knl_printk ("oz_dev_ide8038i: - error code 0x%X\n", CMD_INB (ATACMD_BR_ERROR)); return; } ident0 = ctrlr -> secbuf[0]; if ((ident0 & 0xC002) != 0x8000) { oz_knl_printk ("oz_dev_ide8038i: %s atapi ident word[0] %4.4X not supported\n", devex -> name, ident0); return; } /* Set up autogen routine. When someone tries to access the . device, this will create it. */ /* Can't create it directly via oz_dev_scsi_scan1 now because the scsi class driver probably isn't there yet. */ oz_knl_devunit_autogen (devex -> devunit, oz_dev_scsi_auto, NULL); } /************************************************************************/ /* */ /* Controller specific setup routines */ /* */ /************************************************************************/ static void setup_null (Ctrlr *ctrlr) { } /* UDMA mode : Speed (MHz) : Cycle (nS) */ /* 0 ATA-16 120 */ /* 1 ATA-22 90 */ /* 2 ATA-33 60 */ /* 3 ATA-44 45 */ /* 4 ATA-66 30 */ /* 5 ATA-100 20 */ /* PIIX4 */ static void setup_piix4 (Ctrlr *ctrlr) { if (ctrlr -> devexs[0] != NULL) setup_piix4_drive (ctrlr -> pciconf, ctrlr -> cablesel, ctrlr, ctrlr -> devexs[0]); if (ctrlr -> devexs[1] != NULL) setup_piix4_drive (ctrlr -> pciconf, ctrlr -> cablesel, ctrlr, ctrlr -> devexs[1]); } static void setup_piix4_drive (OZ_Dev_Pci_Conf *pciconf, int cablesel, Ctrlr *ctrlr, Devex *devex) { int offset, udmamode; uByte ub; uWord uw; offset = cablesel * 2 + devex -> drive_id; // 0=primary_master; 1=primary_slave; 2=secondary_master; 3=secondary_slave /* If drive is capable of UDMA, set up UDMA mode */ udmamode = get_drive_udma_mode (devex, 2); // see if drive capable of UDMA mode if ((udmamode >= 0) && set_drive_udma_mode (devex, udmamode)) { // put drive in UDMA mode uw = oz_dev_pci_conf_inw (pciconf, 0x4A); // set controller's UDMA mode (0, 1, 2) uw &= ~(3 << (offset * 4)); uw |= udmamode << (offset * 4); oz_dev_pci_conf_outw (uw, pciconf, 0x4A); ub = oz_dev_pci_conf_inb (pciconf, 0x48); // set controller's UDMA enable bit ub |= 1 << offset; oz_dev_pci_conf_outb (ub, pciconf, 0x48); } } /* AMD 768 */ static void setup_amd768 (Ctrlr *ctrlr) { if (ctrlr -> devexs[0] != NULL) setup_amd768_drive (ctrlr -> pciconf, ctrlr -> cablesel, ctrlr, ctrlr -> devexs[0]); if (ctrlr -> devexs[1] != NULL) setup_amd768_drive (ctrlr -> pciconf, ctrlr -> cablesel, ctrlr, ctrlr -> devexs[1]); } static const uByte setup_amd768_udmatiming[6] = { 0xC2, 0xC1, 0xC0, 0xC4, 0xC5, 0xC6 }; static void setup_amd768_drive (OZ_Dev_Pci_Conf *pciconf, int cablesel, Ctrlr *ctrlr, Devex *devex) { int offset, udmamode; uByte ub; offset = cablesel * 2 + devex -> drive_id; // 0=primary_master; 1=primary_slave; 2=secondary_master; 3=secondary_slave /* If drive is capable of UDMA, set up UDMA mode */ udmamode = get_drive_udma_mode (devex, 5); if (udmamode >= 0) { if (udmamode > 2) { // can't do .gt. mode 2 (ATA33) with slow cable ub = oz_dev_pci_conf_inb (pciconf, 0x42); if (!(ub & (1 << offset))) { udmamode = 2; oz_knl_printk ("oz_dev_ide8038i: - but using mode 2 (ATA33) because fast cable not present\n"); } } if (set_drive_udma_mode (devex, udmamode)) { // put drive in UDMA mode oz_dev_pci_conf_outb (setup_amd768_udmatiming[udmamode], pciconf, 0x53 - offset); // ok, put controller in UDMA mode } } } /* VIA 686 */ static void setup_via686 (Ctrlr *ctrlr) { if (ctrlr -> devexs[0] != NULL) setup_via686_drive (ctrlr -> pciconf, ctrlr -> cablesel, ctrlr, ctrlr -> devexs[0]); if (ctrlr -> devexs[1] != NULL) setup_via686_drive (ctrlr -> pciconf, ctrlr -> cablesel, ctrlr, ctrlr -> devexs[1]); } static void setup_via686_drive (OZ_Dev_Pci_Conf *pciconf, int cablesel, Ctrlr *ctrlr, Devex *devex) { int offset, udmamode; uByte ub; offset = cablesel * 2 + devex -> drive_id; // 0=primary_master; 1=primary_slave; 2=secondary_master; 3=secondary_slave /* If drive is capable of UDMA, set up UDMA mode */ udmamode = get_drive_udma_mode (devex, 2); if (udmamode >= 0) { if (set_drive_udma_mode (devex, udmamode)) { // ok, try to put drive in UDMA mode ub = oz_dev_pci_conf_inb (pciconf, 0x53 - offset) & 0x18; // success, put controller in UDMA mode oz_dev_pci_conf_outb (ub + 0x62 - udmamode, pciconf, 0x53 - offset); // <7> = 0: use bits 5:6 to enable // 1: enable via 'set features' (didn't work) // <6> = 0: UDMA disable // 1: UDMA enable // <5> = 0: use DMA/PIO // 1: use UDMA // <4> = reserved // <3> = 0: 33MHz pci bus (30nS pcicycletime) // 1: 66MHz pci bus (15nS pcicycletime) // <2:0> = (udmacycletime/pcicycletime)-2 } } } /* See what UDMA mode the drive is capable of */ static int get_drive_udma_mode (Devex *devex, int maxudmamode) { const char *extra; int udmamode; if (!(devex -> usedma)) return (-1); // maybe DMA is disabled for the device if ((devex -> ident[ATA_IDENT_W_UDMAMODES] & 0xFF) == 0) return (-1); // see what the drive is capable of for (udmamode = 8; !(devex -> ident[ATA_IDENT_W_UDMAMODES] & (1 << (-- udmamode)));) {} oz_knl_printk ("oz_dev_ide8038i: %s capable of UDMA mode %d\n", devex -> name, udmamode); extra = oz_knl_misc_getextra (devex -> name, ""); // maybe UDMA is disabled for this device if (strstr (extra, "noudma") != NULL) { oz_knl_printk ("oz_dev_ide8038i: - but UDMA disabled by extra parameter\n"); return (-1); } if (udmamode <= maxudmamode) return (udmamode); // make sure the controller can do it, too oz_knl_printk ("oz_dev_ide8038i: - but chip is only capable of UDMA mode %d\n", maxudmamode); return (maxudmamode); } /* Tell drive to enter UDMA transfer mode */ static int set_drive_udma_mode (Devex *devex, int udmamode) { Ctrlr *ctrlr; uByte status; ctrlr = devex -> ctrlr; CMD_OUTB ((devex -> drive_id << 4) | 0xA0, ATACMD_B_DRHEAD); // select the drive to be set CMD_OUTB (0x03, ATACMD_BW_FEATURES); // set subcommand = set transfer mode CMD_OUTB (0x40 + udmamode, ATACMD_B_SECCOUNT); // set parameter = UDMA mode 'udmamode' CMD_OUTB (0xEF, ATACMD_BW_COMMAND); // start setting the features oz_hw_stl_nanowait (400); // give the drive 400nS to start processing oz_hw_stl_microwait (50000, waitfornotbusy, devex -> ctrlr); // give it 50mS to finish status = CMD_INB (ATACMD_BR_STATUS); // check for error if (!(status & 0x81)) return (1); // if all ok, return success status oz_knl_printk ("oz_dev_ide8038i: - set UDMA mode %d status 0x%X\n", udmamode, status); if (status & 0x01) { status = CMD_INB (ATACMD_BR_ERROR); oz_knl_printk ("oz_dev_ide8038i: - error code 0x%X\n", status); } return (0); // some error, return failure status } /* Routine used by oz_hw_stl_microwait to wait for not busy */ static uLong waitfornotbusy (void *ctrlrv) { Ctrlr *ctrlr; uByte status; ctrlr = ctrlrv; status = CMD_INB (ATACMD_BR_STATUS); // read status and ack interrupt if (status & 0x80) return (0); // if still bussy, continue waiting oz_hw_stl_nanowait (400); // give it a chance to update status status = CMD_INB (ATACMD_BR_STATUS); // get updated status return (((uLong)status) | 0x100); // return non-bussy status } /* Routine used by oz_hw_stl_microwait to read the ident buffer */ static uLong getidentbuff (void *ctrlrv) { Ctrlr *ctrlr; uByte status; ctrlr = ctrlrv; status = CMD_INB (ATACMD_BR_STATUS); // get current status if (status & 0x80) return (0); // if busy, nothing else is valid oz_hw_stl_nanowait (400); // give it a chance to update status status = CMD_INB (ATACMD_BR_STATUS); // get updated status if (status & 0x08) { // see if it has data for us DATA_INSW (256, ctrlr -> secbuf); // if so, get it oz_hw_stl_nanowait (400); // give it a chance to update status status = CMD_INB (ATACMD_BR_STATUS); // get updated status if (status & 0x80) return (0); // if still bussy, keep waiting } return (((uLong)status) | 0x100); // not bussy, all done } /* Routine to finish filling in a ctrlr struct */ static void ctrlr_fillin (Ctrlr *ctrlr) { if (ctrlr -> iopex_qt != NULL) return; // only do it once ctrlr -> iopex_qt = &(ctrlr -> iopex_qh); if (ctrlr -> pciconf != NULL) { // connect to the interrupt vector ctrlr -> pciirq = oz_dev_pci_irq_alloc (ctrlr -> pciconf, ctrlr -> irqlevel, intserv, ctrlr); ctrlr -> smplock = oz_dev_pci_irq_smplock (ctrlr -> pciirq); } else { ctrlr -> isairq = oz_dev_isa_irq_alloc (ctrlr -> irqlevel, intserv, ctrlr); ctrlr -> smplock = oz_dev_isa_irq_smplock (ctrlr -> isairq); } ctrlr -> timer = oz_knl_timer_alloc (); // allocate a timer for interrupt timeouts ctrlr -> lowipl = oz_knl_lowipl_alloc (); // used if controller is found to be busy ctrlr -> pcidma = NULL; // don't bother with DMA crap if not a DMA controller if (ctrlr -> bmiba != 0) { ctrlr -> pcidma = oz_dev_pci_dma32map_alloc (ctrlr -> pciconf, DMATBLMAX, OZ_DEV_PCI_DMAFLAG_64K); if (ctrlr -> pcidma == NULL) { ctrlr -> bmiba = 0; // and we were doing so well with it! oz_knl_printk ("oz_dev_ide8038i: no memory for dma struct\n"); } } } /************************************************************************/ /* */ /* Shutdown device (ATA or ATAPI) */ /* */ /************************************************************************/ static int shutdown (OZ_Devunit *devunit, void *devexv) { Ctrlr *ctrlr; Devex *devex; devex = devexv; ctrlr = devex -> ctrlr; if (ctrlr -> timer != NULL) oz_knl_timer_remove (ctrlr -> timer); /* cancel any interrupt timer */ CTL_OUTB (0x0E, ATACTL_BW_DEVCTL); /* disable interrupts for the drives on this cable and reset drives */ if (ctrlr -> bmiba != 0) BMIBA_OUTB (0, BMIBA_B_BMICX); /* abort any dma transfer */ return (1); } /************************************************************************/ /* */ /* Start performing an ATA-style disk i/o function */ /* */ /************************************************************************/ static uLong ata_start (OZ_Devunit *devunit, void *devexv, OZ_Iochan *iochan, void *chnexv, OZ_Procmode procmode, OZ_Ioop *ioop, void *iopexv, uLong funcode, uLong as, void *ap) { Devex *devex; Iopex *iopex; uLong sts; devex = devexv; iopex = iopexv; iopex -> ioop = ioop; iopex -> devex = devex; iopex -> procmode = procmode; iopex -> pcidma = NULL; iopex -> ix4kbuk = 0; /* Process individual functions */ switch (funcode) { /* Set volume valid bit one way or the other (noop for us) */ case OZ_IO_DISK_SETVOLVALID: { /* ?? have it process disk changed status bit */ return (OZ_SUCCESS); } /* Write blocks to the disk from virtual memory */ case OZ_IO_DISK_WRITEBLOCKS: { const OZ_Mempage *phypages; OZ_IO_disk_writeblocks disk_writeblocks; uLong byteoffs; /* Lock I/O buffer in memory */ movc4 (as, ap, sizeof disk_writeblocks, &disk_writeblocks); sts = oz_knl_ioop_lockr (ioop, disk_writeblocks.size, disk_writeblocks.buff, &phypages, NULL, &byteoffs); /* If that was successful, queue the request to the drive for processing */ iopex -> atacmdcode = devex -> atacmd_write; iopex -> writedisk = 1; if (sts == OZ_SUCCESS) sts = ata_queuereq (disk_writeblocks.size, disk_writeblocks.slbn, phypages, byteoffs, iopex); return (sts); } /* Read blocks from the disk into virtual memory */ case OZ_IO_DISK_READBLOCKS: { const OZ_Mempage *phypages; OZ_IO_disk_readblocks disk_readblocks; uLong byteoffs; /* Lock I/O buffer in memory */ movc4 (as, ap, sizeof disk_readblocks, &disk_readblocks); sts = oz_knl_ioop_lockw (ioop, disk_readblocks.size, disk_readblocks.buff, &phypages, NULL, &byteoffs); /* If that was successful, queue the request to the drive for processing */ iopex -> atacmdcode = (disk_readblocks.inhretries & 1) ? devex -> atacmd_read_inh : devex -> atacmd_read; iopex -> writedisk = 0; if (sts == OZ_SUCCESS) sts = ata_queuereq (disk_readblocks.size, disk_readblocks.slbn, phypages, byteoffs, iopex); return (sts); } /* Get info part 1 */ case OZ_IO_DISK_GETINFO1: { OZ_IO_disk_getinfo1 disk_getinfo1; if (!OZ_HW_WRITABLE (as, ap, procmode)) return (OZ_ACCVIO); memset (&disk_getinfo1, 0, sizeof disk_getinfo1); disk_getinfo1.blocksize = DISK_BLOCK_SIZE; disk_getinfo1.totalblocks = devex -> totalblocks; disk_getinfo1.secpertrk = devex -> secpertrk; disk_getinfo1.trkpercyl = devex -> trkpercyl; disk_getinfo1.cylinders = devex -> totalblocks / devex -> secpercyl; disk_getinfo1.bufalign = BUFFER_ALIGNMENT; movc4 (sizeof disk_getinfo1, &disk_getinfo1, as, ap); return (OZ_SUCCESS); } /* Write blocks to the disk from physical pages (kernel only) */ case OZ_IO_DISK_WRITEPAGES: { OZ_IO_disk_writepages disk_writepages; if (procmode != OZ_PROCMODE_KNL) return (OZ_KERNELONLY); movc4 (as, ap, sizeof disk_writepages, &disk_writepages); /* Queue the request to the drive for processing */ iopex -> atacmdcode = devex -> atacmd_write; iopex -> writedisk = 1; sts = ata_queuereq (disk_writepages.size, disk_writepages.slbn, disk_writepages.pages, disk_writepages.offset, iopex); return (sts); } /* Read blocks from the disk into physical pages (kernel only) */ case OZ_IO_DISK_READPAGES: { OZ_IO_disk_readpages disk_readpages; if (procmode != OZ_PROCMODE_KNL) return (OZ_KERNELONLY); movc4 (as, ap, sizeof disk_readpages, &disk_readpages); /* Queue the request to the drive for processing */ iopex -> atacmdcode = (disk_readpages.inhretries & 1) ? devex -> atacmd_read_inh : devex -> atacmd_read; iopex -> writedisk = 0; iopex -> ix4kbuk = disk_readpages.ix4kbuk; sts = ata_queuereq (disk_readpages.size, disk_readpages.slbn, disk_readpages.pages, disk_readpages.offset, iopex); return (sts); } /* Set crash dump device */ case OZ_IO_DISK_CRASH: { if (procmode != OZ_PROCMODE_KNL) return (OZ_KERNELONLY); /* caller must be in kernel mode */ if (crash_devunit != NULL) { /* get rid of old crash stuff, if any */ oz_knl_devunit_increfc (crash_devunit, -1); crash_devex = NULL; crash_devunit = NULL; } if (ap != NULL) { if (as != sizeof (OZ_IO_disk_crash)) return (OZ_BADBUFFERSIZE); /* param block must be exact size */ ((OZ_IO_disk_crash *)ap) -> crashentry = ata_crash; /* return pointer to crash routine */ ((OZ_IO_disk_crash *)ap) -> crashparam = NULL; /* we don't require a parameter */ ((OZ_IO_disk_crash *)ap) -> blocksize = DISK_BLOCK_SIZE; /* tell them our blocksize */ crash_devex = devex; /* save the device we will write to */ crash_devunit = devunit; oz_knl_devunit_increfc (crash_devunit, 1); /* make sure it doesn't get deleted */ } return (OZ_SUCCESS); } /* Who knows what */ default: { return (OZ_BADIOFUNC); } } } /************************************************************************/ /* */ /* ATA-style crash dump routine - write logical blocks with */ /* interrupts disabled */ /* */ /* Input: */ /* */ /* lbn = block to start writing at */ /* size = number of bytes to write (multiple of blocksize) */ /* phypage = physical page to start writing from */ /* offset = offset in first physical page */ /* */ /* Output: */ /* */ /* ata_crash = OZ_SUCCESS : successful */ /* else : error status */ /* */ /************************************************************************/ static uLong ata_crash (void *dummy, OZ_Dbn lbn, uLong size, OZ_Mempage phypage, uLong offset) { Ctrlr *ctrlr; OZ_Datebin now; uByte status; uLong sts, wsize; if ((size | offset) & BUFFER_ALIGNMENT) return (OZ_UNALIGNEDBUFF); ctrlr = crash_devex -> ctrlr; if (!crash_inprog) { recalibrate (crash_devex); /* reset drive */ crash_inprog = 1; /* ... the first time only */ } ctrlr -> iopex_ip = NULL; /* make sure the 'inprogress' queue is empty */ ctrlr -> iopex_qh = NULL; /* make sure the 'pending' queue is empty */ ctrlr -> iopex_qt = &(ctrlr -> iopex_qh); ctrlr -> smplock = NULL; /* we can't use the smp lock anymore */ ctrlr -> timer = NULL; /* make sure we don't use the timer anymore */ ctrlr -> timerqueued = 0; /* Repeat as long as there is stuff to write */ while (size > 0) { /* See how much we can write (up to end of current physical page) */ wsize = (1 << OZ_HW_L2PAGESIZE) - offset; if (wsize > size) wsize = size; /* Queue write request and start processing it - since the queue is empty, it should start right away */ ctrlr -> requestcount = 0; memset (&crash_iopex, 0, sizeof crash_iopex); crash_iopex.procmode = OZ_PROCMODE_KNL; crash_iopex.devex = crash_devex; crash_iopex.status = OZ_PENDING; crash_iopex.writedisk = 1; crash_iopex.atacmdcode = crash_devex -> atacmd_write; sts = ata_queuereq (wsize, lbn, &phypage, offset, &crash_iopex); if (sts == OZ_STARTED) { /* Now keep calling the interrupt service routine until the request completes */ while (ctrlr -> requestcount != 0) { status = CMD_INB (ATACMD_BR_STATUS); /* get drive's status byte */ if (status & 0x80) goto itsbussy; // - if busy set, continue waiting if (status & 0x01) goto itsdone; // - if error set, it's all done if (!(status & 0x08)) goto itsdone; // - if it doesn't want data, it's done if (crash_devex -> usedma) goto itsbussy; // - if dma transfer, continue waiting itsdone: intserv (ctrlr, NULL); /* ... call interrupt service routine */ continue; itsbussy: now = oz_hw_tod_getnow (); /* still busy, get current time */ if (OZ_HW_DATEBIN_CMP (ctrlr -> timerwhen, now) <= 0) { /* see if timer has expired */ ctrlr -> timerqueued = 0; /* if so, say fake timer no longer queued */ intserv (ctrlr, NULL); /* ... and call interrupt service routine */ } } sts = crash_iopex.status; /* get completion status */ } /* Check the completion status */ if (sts != OZ_SUCCESS) { oz_knl_printk ("oz_dev_ide8038i crash: error %u writing to lbn %u\n", sts, lbn); return (sts); } /* Ok, on to next physical page */ size -= wsize; offset += wsize; phypage += offset >> OZ_HW_L2PAGESIZE; offset &= (1 << OZ_HW_L2PAGESIZE) - 1; lbn += wsize / DISK_BLOCK_SIZE; } return (OZ_SUCCESS); } /************************************************************************/ /* */ /* Queue ATA-style I/O request */ /* */ /* Input: */ /* */ /* size = size of transfer in bytes */ /* slbn = starting logical block number */ /* phypages = pointer to array of physical page numbers */ /* byteoffs = byte offset in first physical page */ /* iopex = iopex block to use for operation */ /* iopex -> writedisk = set if writing to disk */ /* iopex -> atacmdcode = read/write ATA command code byte */ /* */ /* Output: */ /* */ /* ata_queuereq = OZ_STARTED : requeust queued to disk drive and */ /* drive started if it was idle */ /* else : error status */ /* */ /************************************************************************/ static uLong ata_queuereq (uLong size, OZ_Dbn slbn, const OZ_Mempage *phypages, uLong byteoffs, Iopex *iopex) { Ctrlr *ctrlr; Devex *devex; uLong hd; OZ_Dbn elbn; devex = iopex -> devex; ctrlr = devex -> ctrlr; /* If no buffer, instant success */ if (size == 0) return (OZ_SUCCESS); /* The buffer must be long aligned for DMA since it is done a long at a time */ if ((size | byteoffs) & BUFFER_ALIGNMENT) return (OZ_UNALIGNEDBUFF); /* Make sure request doesn't run off end of disk */ elbn = (size + DISK_BLOCK_SIZE - 1) / DISK_BLOCK_SIZE + slbn; if (elbn < slbn) return (OZ_BADBLOCKNUMBER); if (elbn > devex -> totalblocks) return (OZ_BADBLOCKNUMBER); /* Make up a read/write request struct */ iopex -> size = size; /* save buffer size */ iopex -> slbn = slbn; /* save starting logical block number */ iopex -> phypages = phypages; /* save physical page array pointer */ iopex -> byteoffs = byteoffs; /* save starting physical page byte offset */ iopex -> timedout = 0; /* hasn't timed out yet */ iopex -> amount_done = 0; /* nothing has been transferred yet */ iopex -> drive_id = devex -> drive_id; /* set up drive id number */ iopex -> retries = MAX_RW_RETRIES; /* init retry counter */ if ((iopex -> writedisk | iopex -> atacmdcode) & 1) iopex -> retries = 1; /* (no retries for writes or if told so) */ if (ctrlr -> smplock != NULL) { hd = oz_hw_smplock_wait (ctrlr -> smplock); /* inhibit hardware interrupts */ } validaterequestcount (ctrlr, __LINE__); /* make sure queues are ok as they stand */ ctrlr -> requestcount ++; /* ok, there is now one more request */ queuereq (iopex); /* queue the request to the controller */ validaterequestcount (ctrlr, __LINE__); /* make sure queues are ok before releasing lock */ if (ctrlr -> smplock != NULL) { oz_hw_smplock_clr (ctrlr -> smplock, hd); /* restore hardware interrupts */ } return (OZ_STARTED); /* the request has been started */ } /************************************************************************/ /* */ /* Assign and deassign channel to ATAPI controller */ /* */ /************************************************************************/ static uLong atapi_assign (OZ_Devunit *devunit, void *devexv, OZ_Iochan *iochan, void *chnexv, OZ_Procmode procmode) { ((Chnex *)chnexv) -> drive_id = -1; // init chnex saying no scsi device opened return (OZ_SUCCESS); // always successful } static int atapi_deassign (OZ_Devunit *devunit, void *devexv, OZ_Iochan *iochan, void *chnexv) { char drive_id; Chnex *chnex; chnex = chnexv; drive_id = chnex -> drive_id; // see if anything open on channel if (drive_id >= 0) { chnex -> drive_id = -1; // if so, say it is closed now oz_hw_atomic_and_long (&(((Devex *)devexv) -> atapiopn), ~ (1 << drive_id)); // let someone else open it now } return (0); } /************************************************************************/ /* */ /* Start performing an ATAPI i/o function */ /* */ /************************************************************************/ static uLong atapi_start (OZ_Devunit *devunit, void *devexv, OZ_Iochan *iochan, void *chnexv, OZ_Procmode procmode, OZ_Ioop *ioop, void *iopexv, uLong funcode, uLong as, void *ap) { Chnex *chnex; Devex *devex; Iopex *iopex; uLong sts; chnex = chnexv; devex = devexv; iopex = iopexv; iopex -> ioop = ioop; iopex -> devex = devex; iopex -> procmode = procmode; iopex -> pcidma = NULL; /* Process individual functions */ switch (funcode) { /* Open one of the drives on this channel */ case OZ_IO_SCSI_OPEN: { Long drive_id_mask; OZ_IO_scsi_open scsi_open; uByte drive_id; movc4 (as, ap, sizeof scsi_open, &scsi_open); drive_id = scsi_open.scsi_id; // get drive id (0=master, 1=slave) if (drive_id > 1) return (OZ_BADSCSIID); // allow only 0 or 1 drive_id_mask = (1 << drive_id); // make a mask for it if (!(devex -> atapimsk & drive_id_mask)) return (OZ_BADSCSIID); // make sure that it was found to be ATAPI in init routine if (chnex -> drive_id >= 0) return (OZ_FILEALREADYOPEN); // make sure nothing already open on this channel if (oz_hw_atomic_or_long (&(devex -> atapiopn), drive_id_mask) & drive_id_mask) return (OZ_ACCONFLICT); // mark it open now chnex -> drive_id = drive_id; // remember which one we opened on this channel return (OZ_SUCCESS); // successful } /* Perform an scsi command on the drive, with virtual buffer address */ case OZ_IO_SCSI_DOIO: { OZ_IO_scsi_doio scsi_doio; uLong sts; movc4 (as, ap, sizeof scsi_doio, &scsi_doio); sts = oz_dev_scsi_cvtdoio2pp (ioop, procmode, &scsi_doio, &(iopex -> doiopp)); iopex -> drive_id = chnex -> drive_id; if (sts == OZ_SUCCESS) sts = atapi_queuereq (iopex); return (sts); } /* Perform an scsi command on the drive, with physical buffer address (kernel mode only) */ case OZ_IO_SCSI_DOIOPP: { if (procmode != OZ_PROCMODE_KNL) return (OZ_KERNELONLY); movc4 (as, ap, sizeof iopex -> doiopp, &(iopex -> doiopp)); iopex -> drive_id = chnex -> drive_id; sts = atapi_queuereq (iopex); return (sts); } /* Get scsi controller info */ case OZ_IO_SCSI_GETINFO1: { OZ_IO_scsi_getinfo1 scsi_getinfo1; if (!OZ_HW_WRITABLE (as, ap, procmode)) return (OZ_ACCVIO); memset (&scsi_getinfo1, 0, sizeof scsi_getinfo1); // clear stuff we don't know about scsi_getinfo1.max_scsi_id = 2; // allow only scsi id 0 and 1 scsi_getinfo1.ctrl_scsi_id = devex -> atapimsk & 1; // controller is whichever the device isn't // atapimsk 1 -> ctrl_scsi_id 1 // atapimsk 2 -> ctrl_scsi_id 0 scsi_getinfo1.open_scsi_id = chnex -> drive_id; // tell caller what scsi-id is open on channel (-1 if closed) scsi_getinfo1.open_width = 1; // we're always 16-bits wide movc4 (sizeof scsi_getinfo1, &scsi_getinfo1, as, ap); // copy info back to caller return (OZ_SUCCESS); } /* We don't support these, so let them fall through to the error */ case OZ_IO_SCSI_RESET: case OZ_IO_SCSI_CRASH: /* Who knows what */ default: { return (OZ_BADIOFUNC); } } } /************************************************************************/ /* */ /* Queue ATAPI-style request to the controller */ /* */ /* Input: */ /* */ /* iopex = request to be queued */ /* iopex -> ioop = request's ioop */ /* iopex -> procmode = requestor's processor mode */ /* iopex -> devex = device (atapi controller device) */ /* iopex -> doiopp = function parameter block */ /* iopex -> drive_id = 0: master, 1: slave */ /* */ /* Output: */ /* */ /* atapi_queuereq = queuing status */ /* */ /************************************************************************/ static uLong atapi_queuereq (Iopex *iopex) { Ctrlr *ctrlr; Devex *devex; OZ_Mempage ppn; uLong cl, hd, ppo, sts; devex = iopex -> devex; ctrlr = devex -> ctrlr; if (iopex -> drive_id > 1) return (OZ_BADSCSIID); /* Copy command into atapicmd and zero pad */ cl = iopex -> doiopp.cmdlen; // get length of supplied command hd = 12; // maybe device takes 12 byte packets if (devex -> atapiidentword0 & 1) hd = 16; // maybe device takes 16 byte packets if (cl > hd) return (OZ_BUFFEROVF); // if command too long, return error sts = oz_knl_section_uget (iopex -> procmode, cl, iopex -> doiopp.cmdbuf, iopex -> atapicmd); // ok, copy in the command if (sts != OZ_SUCCESS) return (sts); memset (iopex -> atapicmd + cl, 0, hd - cl); // zero pad it /* The data buffer must be long aligned for DMA since it is done a long at a time */ if ((iopex -> doiopp.datasize | iopex -> doiopp.databyteoffs) & BUFFER_ALIGNMENT) return (OZ_UNALIGNEDBUFF); /* If transfer request is large, maybe we need a big dmatbl */ iopex -> doiopp.dataphypages += iopex -> doiopp.databyteoffs >> OZ_HW_L2PAGESIZE; iopex -> doiopp.databyteoffs %= 1 << OZ_HW_L2PAGESIZE; iopex -> dmatblmx = ((iopex -> doiopp.datasize + iopex -> doiopp.databyteoffs) >> OZ_HW_L2PAGESIZE) + 1; if (devex -> usedma && (iopex -> dmatblmx >= DMATBLMAX)) { iopex -> pcidma = oz_dev_pci_dma32map_alloc (ctrlr -> pciconf, iopex -> dmatblmx, OZ_DEV_PCI_DMAFLAG_64K); if (iopex -> pcidma == NULL) return (OZ_EXQUOTANPP); } /* Put request on queue */ iopex -> writedisk = ((iopex -> doiopp.optflags & OZ_IO_SCSI_OPTFLAG_WRITE) != 0); // set whether or not we are writing to device iopex -> timedout = 0; // hasn't timed out yet iopex -> retries = 0; // ATAPI's never retry iopex -> atapists = 0; // assume successful iopex -> size = iopex -> doiopp.datasize; // for timeout's error message iopex -> byteoffs = iopex -> doiopp.databyteoffs; // get offset in first page for data transfer iopex -> phypages = iopex -> doiopp.dataphypages; // get pointer to array of physical page numbers iopex -> amount_xfrd = 0; // haven't transferred anything yet iopex -> amount_done = 0; iopex -> slbn = 0; // make all ATAPI request go in order received hd = oz_hw_smplock_wait (ctrlr -> smplock); // inhibit hardware interrupts validaterequestcount (ctrlr, __LINE__); // make sure queues are ok as they stand ctrlr -> requestcount ++; // ok, there is now one more request queuereq (iopex); // queue the request to the controller validaterequestcount (ctrlr, __LINE__); // make sure queues are ok before releasing lock oz_hw_smplock_clr (ctrlr -> smplock, hd); // restore hardware interrupts return (OZ_STARTED); // the request has been started } /************************************************************************/ /* */ /* This routine (re-)queues a request to the controller */ /* */ /* Input: */ /* */ /* iopex = pointer to request to be queued */ /* smplock = drives irq smplock */ /* */ /* Output: */ /* */ /* request queued to controller */ /* drive started if not already busy */ /* */ /************************************************************************/ static void queuereq (Iopex *iopex) { Ctrlr *ctrlr; Iopex **liopex, *xiopex; iopex -> status = OZ_PENDING; /* reset status (for crash routine) */ /* Point to the controller struct */ ctrlr = iopex -> devex -> ctrlr; /* Link it on to end of controller's request queue */ *(ctrlr -> iopex_qt) = iopex; iopex -> next = NULL; ctrlr -> iopex_qt = &(iopex -> next); validaterequestcount (ctrlr, __LINE__); /* If there is no request being processed, start it going */ if ((ctrlr -> iopex_ip == NULL) && (ctrlr -> iopex_qh == iopex)) startreq (ctrlr); } /************************************************************************/ /* */ /* Start processing the request that's on top of queue */ /* */ /* Input: */ /* */ /* ctrlr = pointer to controller */ /* ctrlr -> iopex_qh/qt = read/write request queue */ /* ctrlr -> iopex_ip = assumed to be NULL */ /* */ /* smplock = device's irq smp lock */ /* */ /* Output: */ /* */ /* ctrlr -> iopex_ip = filled in with top request */ /* ctrlr -> iopex_qh/qt = top request removed */ /* disk operation started */ /* */ /************************************************************************/ static void startreq (Ctrlr *ctrlr) { Devex *devex; int i, j; Iopex *iopex; OZ_Datebin now, delta; OZ_Dbn lba; uByte status; uLong bytecount, seccount, sts; /* Dequeue the request that's on the top */ dequeue: validaterequestcount (ctrlr, __LINE__); iopex = ctrlr -> iopex_qh; /* get pointer to top request */ if (iopex == NULL) return; /* just return if queue was empty */ ctrlr -> iopex_ip = iopex; /* got one, mark it 'in progress' */ if ((ctrlr -> iopex_qh = iopex -> next) == NULL) ctrlr -> iopex_qt = &(ctrlr -> iopex_qh); /* unlink it from queue */ devex = iopex -> devex; /* get which device the request is for */ validaterequestcount (ctrlr, __LINE__); /* Make sure the drive is ready to accept a new command */ if (ctrlr -> bmiba != 0) BMIBA_OUTB (0, BMIBA_B_BMICX); /* shut off any old dma stuff */ CMD_OUTB ((iopex -> drive_id << 4) | 0xE0, ATACMD_B_DRHEAD); /* select the drive and LBA mode */ status = CMD_INB (ATACMD_BR_STATUS); /* make sure drive is ready and not requesting data */ if ((status & 0xC8) != 0x40) { oz_hw_stl_nanowait (400); status = CMD_INB (ATACMD_BR_STATUS); /* give it a second chance (to clear error bits) */ if ((status & 0xC8) != 0x40) { oz_knl_printk ("oz_dev_ide8038i: controller hung, status 0x%X, error 0x%X\n", status, CMD_INB (ATACMD_BR_ERROR)); if (crash_inprog) { iopex -> status = OZ_IOFAILED; reqdone (iopex); goto dequeue; } CTL_OUTB (0x0E, ATACTL_BW_DEVCTL); // start a software reset of drives on the cable validaterequestcount (ctrlr, __LINE__); iopex = ctrlr -> iopex_ip; // push hung request back on queue so in case we ... ctrlr -> iopex_ip = NULL; // ... get an int, isr won't think it's in progress iopex -> next = ctrlr -> iopex_qh; ctrlr -> iopex_qh = iopex; if (iopex -> next == NULL) ctrlr -> iopex_qt = &(iopex -> next); validaterequestcount (ctrlr, __LINE__); oz_knl_lowipl_call (ctrlr -> lowipl, ctrlrhung, ctrlr); /* unstick it via lowipl routine */ ctrlr -> lowipl = NULL; /* don't use lowipl again until reset complete */ return; } } /* If ATAPI request, do it differently */ if (devex -> atapimsk != 0) goto start_atapi; /*********************/ /* Start ATA request */ /*********************/ /* Determine number of sectors we have yet to transfer */ if ((iopex -> amount_done % DISK_BLOCK_SIZE) != 0) oz_crash ("oz_dev_ide8038i startreq: amount_done %u not multiple of block size", iopex -> amount_done); seccount = (iopex -> size - iopex -> amount_done + DISK_BLOCK_SIZE - 1) / DISK_BLOCK_SIZE; /* determine number of sectors to process */ if (seccount > MAX_SEC_COUNT) seccount = MAX_SEC_COUNT; /* max number of sectors at a time */ iopex -> seccount = seccount; iopex -> amount_to_xfer = seccount * DISK_BLOCK_SIZE; /* Calculate starting lbn */ lba = iopex -> slbn + iopex -> amount_done / DISK_BLOCK_SIZE; /* Start dma controller */ iopex -> amount_xfrd = 0; /* haven't transferred anything yet (in case of PIO) */ if (devex -> usedma) startdma (iopex, iopex -> amount_to_xfer, DISK_BLOCK_SIZE); /* set up dma to transfer the whole thing */ /* Fill in device registers to start the request */ if (FORTYEIGHTBIT (devex)) { CMD_OUTB ((devex -> drive_id << 4) | 0xE0, ATACMD_B_DRHEAD); /* select the drive, LBA mode */ CMD_OUTB (seccount >> 8, ATACMD_B_SECCOUNT); /* store sector count<15:08> */ CMD_OUTB (lba >> 24, ATACMD_B_LBALOW); /* store lba<31:24> */ CMD_OUTB (0, ATACMD_B_LBAMID); /* store lba<39:32> */ CMD_OUTB (0, ATACMD_B_LBAHIGH); /* store lba<47:40> */ CMD_OUTB (seccount, ATACMD_B_SECCOUNT); /* store sector count<07:00> */ CMD_OUTB (lba, ATACMD_B_LBALOW); /* store lba<07:00> */ CMD_OUTB (lba >> 8, ATACMD_B_LBAMID); /* store lba<15:08> */ CMD_OUTB (lba >> 16, ATACMD_B_LBAHIGH); /* store lba<23:16> */ } else if (LBAMODE (devex)) { CMD_OUTB ((devex -> drive_id << 4) | (lba >> 24) | 0xE0, ATACMD_B_DRHEAD); /* select the drive, lba<27:24>, LBA mode */ CMD_OUTB (0, ATACMD_BW_FEATURES); //?? CMD_OUTB (seccount, ATACMD_B_SECCOUNT); /* store sector count (let it wrap to 0 for 256) */ CMD_OUTB (lba, ATACMD_B_LBALOW); /* store lba<07:00> */ CMD_OUTB (lba >> 8, ATACMD_B_LBAMID); /* store lba<15:08> */ CMD_OUTB (lba >> 16, ATACMD_B_LBAHIGH); /* store lba<23:16> */ } else { uLong cylinder, sector, track; cylinder = lba / devex -> secpercyl; track = (lba / devex -> secpertrk) % devex -> trkpercyl; sector = (lba % devex -> secpertrk) + 1; CMD_OUTB ((devex -> drive_id << 4) | track | 0xA0, ATACMD_B_DRHEAD); /* select the drive, CHS mode */ CMD_OUTB (seccount, ATACMD_B_SECCOUNT); /* store sector count (let it wrap to 0 for 256) */ CMD_OUTB (cylinder, ATACMD_B_CYL_LO); /* store lba<07:00> */ CMD_OUTB (cylinder >> 8, ATACMD_B_CYL_HI); /* store lba<15:08> */ CMD_OUTB (sector, ATACMD_B_SECNUM); /* store sector number */ } CMD_OUTB (iopex -> atacmdcode, ATACMD_BW_COMMAND); /* start the request */ delta = OZ_TIMER_RESOLUTION * ATA_TIMEOUT; goto finishup; /***********************/ /* Start ATAPI request */ /***********************/ start_atapi: /* Start dma stuff going */ iopex -> amount_to_xfer = iopex -> doiopp.datasize; if (devex -> usedma && (iopex -> amount_to_xfer != 0)) { startdma (iopex, iopex -> doiopp.datasize, 1); if (iopex -> amount_xfrd != iopex -> doiopp.datasize) { oz_knl_printk ("oz_dev_ide8038i startreq: dma can only handle %u out of %u byte ATAPI data transfer\n", iopex -> amount_xfrd, iopex -> doiopp.datasize); iopex -> status = OZ_BADBUFFERSIZE; reqdone (iopex); goto dequeue; } } /* Fill in registers to start command (drive was already selected above) */ if (devex -> usedma) { CMD_OUTB (1, ATACMD_BW_FEATURES); // we're doing DMA data transfer CMD_OUTB (0xFE, ATACMD_B_CYL_LO); // these only apply for PIO mode supposedly CMD_OUTB (0xFF, ATACMD_B_CYL_HI); } else { bytecount = iopex -> doiopp.datasize; // get amount to be PIO'd if (bytecount > 0xFFFE) bytecount = 0xFFFE; // ... but it can only do this much at once CMD_OUTB (0, ATACMD_BW_FEATURES); // we're doing PIO data transfer CMD_OUTB (bytecount, ATACMD_B_CYL_LO); // set bytecount to transfer CMD_OUTB (bytecount >> 8, ATACMD_B_CYL_HI); } CMD_OUTB (0, ATACMD_B_SECCOUNT); // no command queuing tag CMD_OUTB (0xA0, ATACMD_BW_COMMAND); // start the ATAPI request /* The command packet always gets sent via PIO */ oz_hw_stl_nanowait (400); /* wait 400nS before checking BUSY & DRQ */ if (!(devex -> atapiidentword0 & 0x20)) { /* if set, it interrupts us to get command */ j = 3000; /* else, we have to wait up to 3mS for some old clunkers */ if (devex -> atapiidentword0 & 0x40) j = 50; /* wait up to 50uS for modren clunkers to be ready */ sts = oz_hw_stl_microwait (j, waitfornotbusy, ctrlr); if (sts == 0) sts = CTL_INB (ATACTL_BR_ALTSTS); if ((sts & 0x89) != 0x08) { /* BUSY<7> off, DRQ<3> on, ERR<0> off */ oz_knl_printk ("oz_dev_ide8038i: %s status %2.2X (error %2.2X) waiting to send command packet\n", devex -> name, (uByte)sts, CMD_INB (ATACMD_BR_ERROR)); iopex -> status = OZ_IOFAILED; reqdone (iopex); goto dequeue; } j = CMD_INB (ATACMD_B_SECCOUNT); /* it should have the low bit set indicating command transfer */ if (!(j & 1)) { oz_knl_printk ("oz_dev_ide8038i: %s didn't enter command transfer state (seccount %2.2X)\n", devex -> name, j); iopex -> status = OZ_IOFAILED; reqdone (iopex); goto dequeue; } j = 6; /* some drives take 12-byte command packets */ if (devex -> atapiidentword0 & 1) j = 8; /* some drives take 16-byte command packets */ DATA_OUTSW (j, iopex -> atapicmd); /* copy command packet out to drive */ } /* Determine timeout required */ ctrlr -> timerwhen = -1LL; if (iopex -> doiopp.timeout == 0) goto finishup_notimeout; delta = iopex -> doiopp.timeout * (OZ_TIMER_RESOLUTION / 1000); /*****************************************/ /* Common (both ATA and ATAPI) finish up */ /*****************************************/ finishup: now = oz_hw_tod_getnow (); /* get current date/time */ OZ_HW_DATEBIN_ADD (ctrlr -> timerwhen, delta, now); /* add the timeout delta value */ if (ctrlr -> timer != NULL) { /* (fake it if crash dumping) */ if (!(ctrlr -> timerqueued) || oz_knl_timer_remove (ctrlr -> timer)) { ctrlr -> timerqueued = 1; oz_knl_timer_insert (ctrlr -> timer, ctrlr -> timerwhen, reqtimedout, ctrlr); /* stick new request in queue */ } } /* If PIO write, start by writing the first chunk */ finishup_notimeout: if (iopex -> writedisk && !(devex -> usedma)) { // see if a PIO write operation oz_hw_stl_nanowait (400); // wait 400nS before checking BUSY & DRQ if (oz_hw_stl_microwait (5, waitfornotbusy, ctrlr) != 0) { // wait up to 5uS for it to ask for data intserv (ctrlr, NULL); // write a chunk of data to the drive } } } /************************************************************************/ /* */ /* Fill in a controller's dma table for a request or set up PIO stuff */ /* */ /* Input: */ /* */ /* iopex -> phypages = physical page number array */ /* iopex -> byteoffs = byte offset in first physical page */ /* iopex -> amount_done = amount of buffer previously completed */ /* iopex -> size = total buffer length */ /* iopex -> writedisk & 1 = 0 : disk-to-memory transfer */ /* 1 : memory-to-disk transfer */ /* xfersize = number of bytes to transfer this time */ /* blocksize = pad to multiple of this size */ /* ctrlr -> bmiba = dma controller I/O port base address */ /* ctrlr -> pcidma = dma mapping struct */ /* */ /* Output: */ /* */ /* *(ctrlr -> pcidma) = table all set up for transfer */ /* iopex -> amount_xfrd = number of bytes set up to transfer */ /* dma controller started */ /* */ /************************************************************************/ static void startdma (Iopex *iopex, uLong xfersize, uLong blocksize) { const OZ_Mempage *phypages; Ctrlr *ctrlr; int i, n; OZ_Dev_Pci_Dma32map *pcidma; OZ_Ieeedma32 *mapvirtadr; uLong bytecount, bytelimit, byteoffs, bytesindma, mappciaddr, sts; ctrlr = iopex -> devex -> ctrlr; /* Fill in pointer table */ pcidma = iopex -> pcidma; if (pcidma == NULL) pcidma = ctrlr -> pcidma; byteoffs = iopex -> byteoffs; /* this is the offset in the first phys page */ phypages = iopex -> phypages; /* this is pointer to physical page array */ byteoffs += iopex -> amount_done; /* offset by how much we did last time through */ phypages += byteoffs >> OZ_HW_L2PAGESIZE; byteoffs &= (1 << OZ_HW_L2PAGESIZE) - 1; bytelimit = xfersize; /* this is the number of bytes to transfer */ if (bytelimit > iopex -> size - iopex -> amount_done) bytelimit = iopex -> size - iopex -> amount_done; /* slightly less if partial sector transfer */ iopex -> amount_xfrd = bytelimit; /* save how much we are doing this time */ n = oz_dev_pci_dma32map_start (pcidma, /* point to dma struct */ iopex -> writedisk & 1, /* 0:read; 1:write */ bytelimit, /* number of bytes */ phypages, /* physical page number array */ byteoffs, /* byte offset in first page */ &mapvirtadr, /* virt addr of dma struct */ &mappciaddr); /* pci addr of dma struct */ if (n <= 0) oz_crash ("oz_dev_8038i startdma: dma mapping error, size %u, rc %d", bytelimit, n); for (i = 0; i < n; i ++) { if (mapvirtadr[i].bytecnt > 0x10000) oz_crash ("oz_dev_ide8038i startdma: segment too large %X", mapvirtadr[i].bytecnt); if ((mapvirtadr[i].phyaddr & 0x0FFFF) + mapvirtadr[i].bytecnt > 0x10000) oz_crash ("oz_dev_ide8038i startdma: segment crosses 64K boundary %X+%X", mapvirtadr[i].phyaddr, mapvirtadr[i].bytecnt); } if ((((mappciaddr + (n - 1) * sizeof *mapvirtadr) ^ mappciaddr) >> 16) != 0) { oz_crash ("oz_dev_ide8038i startdma: dmatable %X[%d] spans 64K boundary", mappciaddr, n); } mapvirtadr[n-1].bytecnt |= 0x80000000; /* write terminator on struct */ /* Start the DMA controller */ oz_dev_pci_outb (0x66, ctrlr -> bmiba + BMIBA_B_BMISX); /* reset dma error status bits */ oz_dev_pci_outl (mappciaddr, ctrlr -> bmiba + BMIBA_L_BMIDTPX); /* set descriptor table base address */ oz_dev_pci_outb ((((iopex -> writedisk & 1) ^ 1) << 3) | 0x01, ctrlr -> bmiba + BMIBA_B_BMICX); /* start the dma controller */ } /* We're done doing transfer */ static void stopdma (Iopex *iopex) { OZ_Dev_Pci_Dma32map *pcidma; pcidma = iopex -> pcidma; if (pcidma == NULL) pcidma = iopex -> devex -> ctrlr -> pcidma; oz_dev_pci_dma32map_stop (pcidma); } /* Process recalibrate command - ATA drives only */ static void recalibrate (Devex *devex) { Ctrlr *ctrlr; uByte status; ctrlr = devex -> ctrlr; CMD_OUTB ((devex -> drive_id << 4) | 0xE0, ATACMD_B_DRHEAD); /* store drive number */ CMD_OUTB (0x10, ATACMD_BW_COMMAND); /* start the recalibrate */ status = oz_hw_stl_microwait (200000, checkrecaldone, ctrlr); /* give it up to 200mS to finish */ if (status == 0) { oz_knl_printk ("oz_dev_ide8038i recalibrate: status 0x%x, error 0x%x\n", CMD_INB (ATACMD_BR_STATUS), CMD_INB (ATACMD_BR_ERROR)); } } /* Check to see if recalibrate command has finished */ static uLong checkrecaldone (void *ctrlrv) { Ctrlr *ctrlr; uByte status; ctrlr = ctrlrv; status = CMD_INB (ATACMD_BR_STATUS); if ((status & 0xC0) != 0x40) status = 0; return (status); } /************************************************************************/ /* */ /* This routine is called via lowipl when a request is about to be */ /* started but the controller is showing a busy status. The caller */ /* has already initiated a software reset for the controller. */ /* */ /* Input: */ /* */ /* ctrlrv = points to controller struct */ /* lowipl = points to lowipl struct to restore */ /* smplevel = softint */ /* */ /************************************************************************/ static void ctrlrhung (void *ctrlrv, OZ_Lowipl *lowipl) { Ctrlr *ctrlr; Iopex *iopex; uLong hd; ctrlr = ctrlrv; oz_knl_printk ("oz_dev_ide8038i: resetting %X\n", ctrlr -> atacmd); enable_drives (ctrlr); // get drives enabled and ready to process requests again oz_knl_printk ("oz_dev_ide8038i: restarting %X\n", ctrlr -> atacmd); hd = oz_hw_smplock_wait (ctrlr -> smplock); ctrlr -> lowipl = lowipl; // re-arm this routine enable_interrupts (ctrlr); // enable interrupts on the drives if (ctrlr -> iopex_ip == NULL) startreq (ctrlr); // try to start the request again oz_hw_smplock_clr (ctrlr -> smplock, hd); } /************************************************************************/ /* */ /* Timer ran out waiting for interrupt */ /* */ /* If there is a request going, call the interrupt service routine */ /* and restart timer */ /* */ /************************************************************************/ static void reqtimedout (void *ctrlrv, OZ_Timer *timer) { Ctrlr *ctrlr; Devex *devex; Iopex *iopex; OZ_Datebin now; uLong hd; ctrlr = ctrlrv; hd = oz_hw_smplock_wait (ctrlr -> smplock); /* set interrupt lock */ ctrlr -> timerqueued = 0; /* say the timer is no longer queued */ iopex = ctrlr -> iopex_ip; /* make sure a request is in progress */ if (iopex != NULL) { now = oz_hw_tod_getnow (); /* get current date/time */ if (OZ_HW_DATEBIN_CMP (ctrlr -> timerwhen, now) <= 0) { /* make sure it's really up (it may have been bumped since it was queued) */ devex = iopex -> devex; if (devex -> atapimsk != 0) { oz_knl_printk ("oz_dev_ide8038i: %s atapi command %2.2X timed out, datasize %u, transferred %u\n", devex -> name, iopex -> atapicmd[0], iopex -> size, iopex -> amount_xfrd); } else { oz_knl_printk ("oz_dev_ide8038i: %s atacmd %2.2X timed out, lbn %u, size %u\n", devex -> name, iopex -> atacmdcode, iopex -> slbn, iopex -> size); oz_knl_printk (" amount_done %u, amount_xfrd %u\n", iopex -> amount_done, iopex -> amount_xfrd); } iopex -> timedout = 1; /* remember it got a timeout */ intserv (ctrlr, NULL); /* call the interrupt routine to process whatever is left of request */ if (!(ctrlr -> timerqueued) && (ctrlr -> iopex_ip != NULL)) { ctrlr -> timerqueued = 1; /* restart timer if there is still a request in progress */ ctrlr -> timerwhen = now + (OZ_TIMER_RESOLUTION * ATA_TIMEOUT); oz_hw_smplock_clr (ctrlr -> smplock, hd); oz_knl_timer_insert (ctrlr -> timer, ctrlr -> timerwhen, reqtimedout, ctrlr); return; } } else { ctrlr -> timerqueued = 1; /* it got bumped since it was queued, re-queue at new date/time */ oz_hw_smplock_clr (ctrlr -> smplock, hd); /* release interrupt lock */ oz_knl_timer_insert (ctrlr -> timer, ctrlr -> timerwhen, reqtimedout, ctrlr); return; } } oz_hw_smplock_clr (ctrlr -> smplock, hd); } /************************************************************************/ /* */ /* Interrupt service routine entrypoint */ /* */ /* Input: */ /* */ /* ctrlrv = controller that caused the interrupt */ /* mchargs = cpu state at point of interrupt (not used) */ /* */ /************************************************************************/ static void intserv (void *ctrlrv, OZ_Mchargs *mchargs) { Ctrlr *ctrlr; Devex *devex; Iopex *iopex; uByte bmisx, status; ctrlr = ctrlrv; validaterequestcount (ctrlr, __LINE__); /* Clear the interrupt bit in the DMA controller */ if (ctrlr -> bmiba != 0) { bmisx = BMIBA_INB (BMIBA_B_BMISX); BMIBA_OUTB (0x64, BMIBA_B_BMISX); } /* Read the status and clear interrupt */ status = CMD_INB (ATACMD_BR_STATUS); /* Get current request pointer */ iopex = ctrlr -> iopex_ip; /* Process interrupt */ if (iopex != NULL) { devex = iopex -> devex; if (devex -> usedma) dma_intserv (ctrlr, devex, iopex, status, bmisx); else if (devex -> atapimsk == 0) pio_ata_intserv (iopex, status); else pio_atapi_intserv (iopex, status); } validaterequestcount (ctrlr, __LINE__); } /************************************************************************/ /* */ /* This is the DMA interrupt routine - it is called at irq level when */ /* the drive completes an operation */ /* */ /* It is also called when the timeout happens */ /* */ /* Input: */ /* */ /* ctrlrv = pointer to controller that interrupted */ /* smplock = the associated irq's smplock is set */ /* */ /************************************************************************/ static void dma_intserv (Ctrlr *ctrlr, Devex *devex, Iopex *iopex, uByte status, uByte bmisx) { /* Check for ATAPI command delivery - some drives interrupt when ready to receive command packet */ if (atapi_command_packet (iopex, status)) return; /* Make sure drive is no longer busy - if so and timer is still going, let it interrupt again */ /* If timer is no longer going, it means it has timed out, so abort the request */ if ((status & 0x80) || (!(status & 0x01) && (status & 0x08) && !(bmisx & 0x02))) { oz_knl_printk ("oz_dev_ide8038i: %s busy or drq still set (status 0x%x, bmisx 0x%x)\n", devex -> name, status, bmisx); if (ctrlr -> timerqueued) { validaterequestcount (ctrlr, __LINE__); return; } } /* Make sure dma controller no longer busy */ if (bmisx & 0x01) { oz_knl_printk ("oz_dev_ide8038i: %s dma busy still set (0x%x)\n", devex -> name, bmisx); oz_knl_printk ("oz_dev_ide8038i: - transfer size %u\n", iopex -> amount_xfrd); BMIBA_OUTB (0, BMIBA_B_BMICX); } /* Process the interrupt */ if (devex -> atapimsk == 0) ata_finish (iopex, status); else atapi_finish (iopex, status); } /************************************************************************/ /* */ /* This is the PIO interrupt routine - it is called at irq level when */ /* the drive completes an operation or wants to transfer data */ /* */ /* It is also called when the timeout happens */ /* */ /* Input: */ /* */ /* ctrlrv = pointer to controller that interrupted */ /* smplock = the associated irq's smplock is set */ /* */ /************************************************************************/ static void pio_ata_intserv (Iopex *iopex, uByte status) { Ctrlr *ctrlr; Devex *devex; int j; uByte error; devex = iopex -> devex; ctrlr = devex -> ctrlr; /* Maybe it needs some data - ATA devices get a block */ while ((status & 0x89) == 0x08) { /* BUSY<7> off, DRQ<3> on, ERR<0> off */ if (((iopex -> atacmdcode & 0xFE) == 0xC4) && (devex -> multsize != 0)) { /* ATA read/write multiple : do the group of blocks */ j = devex -> multsize * DISK_BLOCK_SIZE; if (j + iopex -> amount_xfrd > iopex -> seccount * DISK_BLOCK_SIZE) { // ... or maybe just to the end of the transfer size j = iopex -> seccount * DISK_BLOCK_SIZE - iopex -> amount_xfrd; } } else { j = DISK_BLOCK_SIZE; /* standard ATA requests : do exactly one block */ } do_pio_transfer (j, iopex); // transfer to/from caller's buffer if ((iopex -> writedisk) || (iopex -> amount_xfrd < iopex -> amount_to_xfer)) return; /* note: we must return after transferring just one block of data */ /* if we try to be efficient by looping, we get timeout errors */ CTL_INB (ATACTL_BR_ALTSTS); /* give controller time to come up with post-transfer status */ status = CMD_INB (ATACMD_BR_STATUS); /* re-check the status */ if (status & 0x80) return; /* wait for another interrupt if it's busy again */ } ata_finish (iopex, status); } static void pio_atapi_intserv (Iopex *iopex, uByte status) { Ctrlr *ctrlr; int j; ctrlr = iopex -> devex -> ctrlr; /* Check for ATAPI command delivery - some drives interrupt when ready to receive command packet */ if (atapi_command_packet (iopex, status)) { CTL_INB (ATACTL_BR_ALTSTS); /* give controller time to come up with post-transfer status */ status = CMD_INB (ATACMD_BR_STATUS); /* re-check the status */ if (status & 0x80) return; /* wait for another interrupt if it's busy again */ } /* Maybe it needs some data - ATAPI devices get bytecount in cyllo/cylhi registers */ while ((status & 0x89) == 0x08) { /* BUSY<7> off, DRQ<3> on, ERR<0> off */ j = CMD_INB (ATACMD_B_CYL_HI) << 8; // get transfer bytecount j += CMD_INB (ATACMD_B_CYL_LO); do_pio_transfer (j, iopex); // transfer to/from caller's buffer CTL_INB (ATACTL_BR_ALTSTS); /* give controller time to come up with post-transfer status */ status = CMD_INB (ATACMD_BR_STATUS); /* re-check the status */ if (status & 0x80) return; /* wait for another interrupt if it's busy again */ } atapi_finish (iopex, status); } static int atapi_command_packet (Iopex *iopex, uByte status) { Ctrlr *ctrlr; Devex *devex; int cmdlen; devex = iopex -> devex; ctrlr = devex -> ctrlr; if ((devex -> atapiidentword0 & 0x60) != 0x20) return (0); // only these drives interrupt for command packet transfer if ((status & 0x89) != 0x08) return (0); // BUSY<7> off, DRQ<3> on, ERR<0> off status = CMD_INB (ATACMD_B_SECCOUNT); // it has the low bit set indicating command transfer if (!(status & 1)) return (0); cmdlen = 6; // some drives take 12-byte command packets if (devex -> atapiidentword0 & 1) cmdlen = 8; // some drives take 16-byte command packets DATA_OUTSW (cmdlen, iopex -> atapicmd); // copy command packet out to drive return (1); // tell caller we sent command out } /************************************************************************/ /* */ /* Do a PIO transfer */ /* */ /* Input: */ /* */ /* nbytes = number of bytes to transfer */ /* iopex = I/O request being processed */ /* -> byteoffs = byte offset in original requestor's buffer */ /* -> phypages = physical page array of original reqestor's buffer /* -> amount_done = amount of whole original transfer done by previous commands /* -> amount_xfrd = amount previously done for this command */ /* */ /* Output: */ /* */ /* data transferred */ /* iopex -> amount_xfrd = incremented */ /* */ /************************************************************************/ static void do_pio_transfer (int nbytes, Iopex *iopex) { const OZ_Mempage *phypages; Ctrlr *ctrlr; int i, pad; OZ_Pagentry oldpte; uLong byteoffs; uWord *vad; ctrlr = iopex -> devex -> ctrlr; byteoffs = iopex -> byteoffs; /* get offset of first byte in original page */ phypages = iopex -> phypages; /* get pointer to first element of physical page array */ byteoffs += iopex -> amount_done + iopex -> amount_xfrd; /* increment offset in first page by the amount already done and transferred */ phypages += (byteoffs >> OZ_HW_L2PAGESIZE); /* normalize with physical page array pointer */ byteoffs %= 1 << OZ_HW_L2PAGESIZE; vad = oz_hw_phys_mappage (*phypages, &oldpte); /* map physical page to virtual address */ (OZ_Pointer)vad += byteoffs; i = iopex -> size - iopex -> amount_done - iopex -> amount_xfrd; /* how many bytes left in the whole request to process */ pad = nbytes - i; /* any left over is padding */ if (pad > 0) nbytes = i; /* just transfer this much to/from data buffer */ pad /= 2; /* make pad a word count */ if (iopex -> writedisk) { while (1) { i = (1 << OZ_HW_L2PAGESIZE) - byteoffs; /* get number of bytes to end of page */ if (i > nbytes) i = nbytes; /* but don't do more than drive wants */ DATA_OUTSW (i / 2, vad); /* give it the data */ iopex -> amount_xfrd += i; /* this much more has been transferred */ nbytes -= i; /* this much less to do */ if (nbytes == 0) break; /* stop if all done */ byteoffs += i; /* more to transfer, get offset to it */ phypages += (byteoffs >> OZ_HW_L2PAGESIZE); /* normalize with physical page array pointer */ byteoffs %= 1 << OZ_HW_L2PAGESIZE; vad = oz_hw_phys_mappage (*phypages, NULL); /* map physical page to virtual address */ (OZ_Pointer)vad += byteoffs; } while (-- pad >= 0) CMD_OUTW (0, ATACMD_W_DATA); /* zero fill padding */ } else { while (1) { i = (1 << OZ_HW_L2PAGESIZE) - byteoffs; /* get number of bytes to end of page */ if (i > nbytes) i = nbytes; /* but don't do more than drive has */ DATA_INSW (i / 2, vad); /* get the data from drive */ iopex -> amount_xfrd += i; /* this much more has been transferred */ nbytes -= i; /* this much less to do */ if (nbytes == 0) break; /* stop if all done */ byteoffs += i; /* more to transfer, get offset to it */ phypages += (byteoffs >> OZ_HW_L2PAGESIZE); /* normalize with physical page array pointer */ byteoffs %= 1 << OZ_HW_L2PAGESIZE; vad = oz_hw_phys_mappage (*phypages, NULL); /* map physical page to virtual address */ (OZ_Pointer)vad += byteoffs; } while (-- pad >= 0) CMD_INW (ATACMD_W_DATA); /* discard padding */ } oz_hw_phys_unmappage (oldpte); /* unmap the physical page */ } /************************************************************************/ /* */ /* Finish up an (PIO or DMA) ATA request */ /* */ /************************************************************************/ static void ata_finish (Iopex *iopex, uByte status) { Ctrlr *ctrlr; Devex *devex; int j; uByte error; devex = iopex -> devex; ctrlr = devex -> ctrlr; /* Make sure drive is no longer busy - if so and timer is still going, let it interrupt again */ /* If timer is no longer going, it means it has timed out, so abort the request */ if ((status & 0x80) || ((status & 0x89) == 0x08)) { oz_knl_printk ("oz_dev_ide8038i: %s busy or drq still set (status 0x%x)\n", devex -> name, status); if (ctrlr -> timerqueued) { validaterequestcount (ctrlr, __LINE__); return; } } /* Request is complete, stop DMA and check the status bits for error */ if (devex -> usedma) stopdma (iopex); iopex -> status = OZ_SUCCESS; // assume it was successful if (status & 0xA1) { // see if any error bits are set iopex -> status = OZ_WRITELOCKED; // maybe it's just writelocked if (status & 0x81) { // check for any other error bits error = CMD_INB (ATACMD_BR_ERROR); // ok, get the error status oz_knl_printk ("oz_dev_ide8038i: %s status 0x%x, error 0x%x\n", devex -> name, status, error); // output message iopex -> status = OZ_IOFAILED; // return generic error status if (!(status & 0x80) || (error & 0x80)) iopex -> status = OZ_BADMEDIA; // maybe it's bad media } } /* If IOFAILED or BADMEDIA, maybe retry */ if (((iopex -> status == OZ_IOFAILED) || (iopex -> status == OZ_BADMEDIA)) && (-- (iopex -> retries) > 0)) goto requeue; /* If successful, accumulate amount transferred so far. Then if not complete, re-queue it. */ if (iopex -> status == OZ_SUCCESS) { iopex -> amount_done += iopex -> amount_xfrd; /* if successful, increment amount done */ if (iopex -> amount_done > iopex -> size) iopex -> amount_done = iopex -> size; /* (padded out a short block) */ if (iopex -> amount_done < iopex -> size) goto requeue; /* if more to go, re-queue request to finish up */ if (iopex -> ix4kbuk) { ix4kbuk_validate_phypage (iopex -> phypages, iopex -> byteoffs, __FILE__, __LINE__); } } /* It is complete (good or bad) */ reqdone (iopex); /* put completed request on completion queue */ startreq (ctrlr); /* start another request */ validaterequestcount (ctrlr, __LINE__); return; /* Either continue or re-try the request */ requeue: ctrlr -> iopex_ip = NULL; queuereq (iopex); validaterequestcount (ctrlr, __LINE__); } /************************************************************************/ /* */ /* Finish up an (PIO or DMA) ATAPI request */ /* */ /************************************************************************/ static void atapi_finish (Iopex *iopex, uByte status) { Ctrlr *ctrlr; Devex *devex; uByte error; devex = iopex -> devex; ctrlr = devex -> ctrlr; /* Make sure drive is no longer busy - if so and timer is still going, let it interrupt again */ /* If timer is no longer going, it means it has timed out, so abort the request */ if (status & 0x80) { oz_knl_printk ("oz_dev_ide8038i: %s busy (status 0x%X)\n", devex -> name, status); if (ctrlr -> timerqueued) { validaterequestcount (ctrlr, __LINE__); return; } } /* Request is complete, stop DMA and check the status bits for error */ if (devex -> usedma && (iopex -> amount_to_xfer != 0)) stopdma (iopex); iopex -> atapists = 0; iopex -> status = OZ_SUCCESS; if ((status & 0xC9) != 0x40) { // BSY=0; DRDY=1; DRQ=0; ERR=0 if ((status & 0xC9) == 0x41) { error = CMD_INB (ATACMD_BR_ERROR); if (error & 0x04) { oz_knl_printk ("oz_dev_ide8038i: %s status 0x%X, error 0x%X\n", devex -> name, status, error); iopex -> status = OZ_IOFAILED; } else { iopex -> atapists = error >> 4; } } else { oz_knl_printk ("oz_dev_ide8038i: %s status 0x%X\n", devex -> name, status); iopex -> status = OZ_IOFAILED; } } /* It is complete (good or bad) */ reqdone (iopex); /* put completed request on completion queue */ startreq (ctrlr); /* start another request */ validaterequestcount (ctrlr, __LINE__); } /************************************************************************/ /* */ /* An request is done */ /* */ /* Input: */ /* */ /* iopex = the completed request with status filled in */ /* smplock = irq level */ /* */ /* Output: */ /* */ /* request posted for completion */ /* */ /************************************************************************/ static void reqdone (Iopex *iopex) { Ctrlr *ctrlr; Devex *devex; devex = iopex -> devex; ctrlr = devex -> ctrlr; ctrlr -> iopex_ip = NULL; ctrlr -> requestcount --; if (iopex -> timedout) oz_knl_printk ("oz_dev_ide8038i: %s request complete, status %u\n", iopex -> devex -> name, iopex -> status); if (iopex -> ioop != NULL) { if ((devex -> atapimsk != 0) && ((iopex -> doiopp.datarlen != NULL) || (iopex -> doiopp.status != NULL) || (iopex -> pcidma != NULL))) { oz_knl_iodonehi (iopex -> ioop, iopex -> status, NULL, atapi_reqdone, iopex); } else oz_knl_iodonehi (iopex -> ioop, iopex -> status, NULL, NULL, NULL); } } /* Back in requestor's memory space and requestor wants the ATAPI data result */ /* length and/or status byte returned, or there is a temp dma table to free off */ static void atapi_reqdone (void *iopexv, uLong *status_r) { Iopex *iopex; uLong sts; iopex = iopexv; if (iopex -> pcidma != NULL) oz_dev_pci_dma32map_free (iopex -> pcidma); if (iopex -> doiopp.datarlen != NULL) { sts = oz_knl_section_uput (iopex -> procmode, sizeof *(iopex -> doiopp.datarlen), &(iopex -> amount_xfrd), iopex -> doiopp.datarlen); if (*status_r == OZ_SUCCESS) *status_r = sts; } if (iopex -> doiopp.status != NULL) { sts = oz_knl_section_uput (iopex -> procmode, sizeof *(iopex -> doiopp.status), &(iopex -> atapists), iopex -> doiopp.status); if (*status_r == OZ_SUCCESS) *status_r = sts; } } /************************************************************************/ /* */ /* Make sure we haven't lost an request somewhere */ /* */ /* Input: */ /* */ /* ctrlr -> requestcount = number of pending I/O requests */ /* smplevel = hd */ /* */ /************************************************************************/ static void validaterequestcount (Ctrlr *ctrlr, int line) { int reqcount; Iopex *iopex, **liopex; reqcount = 0; if (ctrlr -> iopex_ip != NULL) reqcount ++; for (liopex = &(ctrlr -> iopex_qh); (iopex = *liopex) != NULL; liopex = &(iopex -> next)) reqcount ++; if (liopex != ctrlr -> iopex_qt) { oz_crash ("oz_dev_ide8038i validaterequestcount %d: liopex %p, ctrlr %p -> iopex_qt %p", line, liopex, ctrlr, ctrlr -> iopex_qt); } if (reqcount != ctrlr -> requestcount) { oz_crash ("oz_dev_ide8038i validaterequestcount %d: found %u, should have %u requests", line, reqcount, ctrlr -> requestcount); } }