From 5e0b8d508ed51004bd836384293be00950ee62c9 Mon Sep 17 00:00:00 2001 From: Pasha Date: Tue, 20 Feb 2024 18:49:50 +0000 Subject: init gnumach copy --- linux/dev/drivers/block/ahci.c | 1038 +++++++++ linux/dev/drivers/block/floppy.c | 4288 ++++++++++++++++++++++++++++++++++++ linux/dev/drivers/block/genhd.c | 1080 +++++++++ linux/dev/drivers/net/Space.c | 582 +++++ linux/dev/drivers/net/auto_irq.c | 123 ++ linux/dev/drivers/net/net_init.c | 446 ++++ linux/dev/drivers/net/wavelan.p.h | 639 ++++++ linux/dev/drivers/scsi/eata_dma.c | 1607 ++++++++++++++ linux/dev/drivers/scsi/g_NCR5380.c | 735 ++++++ 9 files changed, 10538 insertions(+) create mode 100644 linux/dev/drivers/block/ahci.c create mode 100644 linux/dev/drivers/block/floppy.c create mode 100644 linux/dev/drivers/block/genhd.c create mode 100644 linux/dev/drivers/net/Space.c create mode 100644 linux/dev/drivers/net/auto_irq.c create mode 100644 linux/dev/drivers/net/net_init.c create mode 100644 linux/dev/drivers/net/wavelan.p.h create mode 100644 linux/dev/drivers/scsi/eata_dma.c create mode 100644 linux/dev/drivers/scsi/g_NCR5380.c (limited to 'linux/dev/drivers') diff --git a/linux/dev/drivers/block/ahci.c b/linux/dev/drivers/block/ahci.c new file mode 100644 index 0000000..751c7ca --- /dev/null +++ b/linux/dev/drivers/block/ahci.c @@ -0,0 +1,1038 @@ +/* + * Copyright (C) 2013 Free Software Foundation + * + * 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 ; either version 2 of the License, or + * (at your option) any later version. + * + * 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 the program ; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAJOR_NR SCSI_DISK_MAJOR +#include + +/* Standard AHCI BAR for mmio */ +#define AHCI_PCI_BAR 5 + +/* minor: 2 bits for device number, 6 bits for partition number. */ + +#define MAX_PORTS 8 +#define PARTN_BITS 5 +#define PARTN_MASK ((1<errors = 0; + if (!uptodate) { + if (!rq->quiet) + printk("end_request: I/O error, dev %s, sector %lu\n", + kdevname(rq->rq_dev), rq->sector); + } + + for (bh = rq->bh; bh; ) + { + struct buffer_head *next = bh->b_reqnext; + bh->b_reqnext = NULL; + mark_buffer_uptodate (bh, uptodate); + unlock_buffer (bh); + bh = next; + } + + CURRENT = rq->next; + if (rq->sem != NULL) + up(rq->sem); + rq->rq_status = RQ_INACTIVE; + wake_up(&wait_for_request); +} + +/* Push the request to the controler port */ +static int ahci_do_port_request(struct port *port, unsigned long long sector, struct request *rq) +{ + struct ahci_command *command = port->command; + struct ahci_cmd_tbl *prdtl = port->prdtl; + struct ahci_fis_h2d *fis_h2d; + unsigned slot = 0; + struct buffer_head *bh; + unsigned i; + + rq->rq_status = RQ_SCSI_BUSY; + + /* Shouldn't ever happen: the block glue is limited at 8 blocks */ + assert(rq->nr_sectors < 0x10000); + + fis_h2d = (void*) &prdtl[slot].cfis; + fis_h2d->fis_type = FIS_TYPE_REG_H2D; + fis_h2d->flags = 128; + if (port->lba48) { + if (sector >= 1ULL << 48) { + printk("sector %llu beyond LBA48\n", sector); + return -EOVERFLOW; + } + if (rq->cmd == READ) + fis_h2d->command = WIN_READDMA_EXT; + else + fis_h2d->command = WIN_WRITEDMA_EXT; + } else { + if (sector >= 1ULL << 28) { + printk("sector %llu beyond LBA28\n", sector); + return -EOVERFLOW; + } + if (rq->cmd == READ) + fis_h2d->command = WIN_READDMA; + else + fis_h2d->command = WIN_WRITEDMA; + } + + fis_h2d->device = 1<<6; /* LBA */ + + fis_h2d->lba0 = sector; + fis_h2d->lba1 = sector >> 8; + fis_h2d->lba2 = sector >> 16; + + fis_h2d->lba3 = sector >> 24; + fis_h2d->lba4 = sector >> 32; + fis_h2d->lba5 = sector >> 40; + + fis_h2d->countl = rq->nr_sectors; + fis_h2d->counth = rq->nr_sectors >> 8; + + command[slot].opts = sizeof(*fis_h2d) / sizeof(u32); + + if (rq->cmd == WRITE) + command[slot].opts |= AHCI_CMD_WRITE; + + for (i = 0, bh = rq->bh; bh; i++, bh = bh->b_reqnext) + { + assert(i < PRDTL_SIZE); + assert((((unsigned long) bh->b_data) & ~PAGE_MASK) == + (((unsigned long) bh->b_data + bh->b_size - 1) & ~PAGE_MASK)); + prdtl[slot].prdtl[i].dbau = 0; + prdtl[slot].prdtl[i].dba = vmtophys(bh->b_data); + prdtl[slot].prdtl[i].dbc = bh->b_size - 1; + } + + command[slot].opts |= i << 16; + + /* Make sure main memory buffers are up to date */ + mb(); + + /* Issue command */ + writel(1 << slot, &port->ahci_port->ci); + + /* TODO: IRQ timeout handler */ + return 0; +} + +/* Called by block core to push a request */ +/* TODO: ideally, would have one request queue per port */ +/* TODO: ideally, would use tags to process several requests at a time */ +static void ahci_do_request() /* invoked with cli() */ +{ + struct request *rq; + unsigned minor, unit; + unsigned long long block, blockend; + struct port *port; + + rq = CURRENT; + if (!rq) + return; + + if (rq->rq_status != RQ_ACTIVE) + /* Current one is already ongoing, let the interrupt handler + * push the new one when the current one is finished. */ + return; + + if (MAJOR(rq->rq_dev) != MAJOR_NR) { + printk("bad ahci major %u\n", MAJOR(rq->rq_dev)); + goto kill_rq; + } + + minor = MINOR(rq->rq_dev); + unit = minor >> PARTN_BITS; + if (unit >= MAX_PORTS) { + printk("bad ahci unit %u\n", unit); + goto kill_rq; + } + + port = &ports[unit]; + + /* Compute start sector */ + block = rq->sector; + block += port->part[minor & PARTN_MASK].start_sect; + + /* And check end */ + blockend = block + rq->nr_sectors; + if (blockend < block) { + if (!rq->quiet) + printk("bad blockend %lu vs %lu\n", (unsigned long) blockend, (unsigned long) block); + goto kill_rq; + } + if (blockend > port->capacity) { + if (!rq->quiet) + { + printk("offset for %u was %lu\n", minor, port->part[minor & PARTN_MASK].start_sect); + printk("bad access: block %lu, count= %lu\n", (unsigned long) blockend, (unsigned long) port->capacity); + } + goto kill_rq; + } + + /* Push this to the port */ + if (ahci_do_port_request(port, block, rq)) + goto kill_rq; + return; + +kill_rq: + ahci_end_request(0); +} + +/* The given port got an interrupt, terminate the current request if any */ +static void ahci_port_interrupt(struct port *port, u32 status) +{ + unsigned slot = 0; + + if (readl(&port->ahci_port->ci) & (1 << slot)) { + /* Command still pending */ + return; + } + + if (port->identify) { + port->status = status; + wake_up(&port->q); + return; + } + + if (!CURRENT || CURRENT->rq_status != RQ_SCSI_BUSY) { + /* No request currently running */ + return; + } + + if (status & (PORT_IRQ_TF_ERR | PORT_IRQ_HBUS_ERR | PORT_IRQ_HBUS_DATA_ERR | PORT_IRQ_IF_ERR | PORT_IRQ_IF_NONFATAL)) { + printk("ahci error %x %x\n", status, readl(&port->ahci_port->tfd)); + ahci_end_request(0); + return; + } + + ahci_end_request(1); +} + +/* Start of IRQ handler. Iterate over all ports for this host */ +static void ahci_interrupt (int irq, void *host, struct pt_regs *regs) +{ + struct port *port; + struct ahci_host *ahci_host = host; + u32 irq_mask; + u32 status; + + irq_mask = readl(&ahci_host->is); + + if (!irq_mask) + return; + + for (port = &ports[0]; port < &ports[MAX_PORTS]; port++) { + if (port->ahci_host == ahci_host && (irq_mask & (1 << (port->ahci_port - ahci_host->ports)))) { + status = readl(&port->ahci_port->is); + /* Clear interrupt before possibly triggering others */ + writel(status, &port->ahci_port->is); + ahci_port_interrupt (port, status); + } + } + + if (CURRENT) + /* Still some requests, queue another one */ + ahci_do_request(); + + /* Clear host after clearing ports */ + writel(irq_mask, &ahci_host->is); + + /* unlock */ +} + +static int ahci_ioctl (struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + int major, unit; + + if (!inode || !inode->i_rdev) + return -EINVAL; + + major = MAJOR(inode->i_rdev); + if (major != MAJOR_NR) + return -ENOTTY; + + unit = DEVICE_NR(inode->i_rdev); + if (unit >= MAX_PORTS) + return -EINVAL; + + switch (cmd) { + case BLKRRPART: + if (!suser()) return -EACCES; + if (!ports[unit].gd) + return -EINVAL; + resetup_one_dev(ports[unit].gd, unit); + return 0; + default: + return -EPERM; + } +} + +static int ahci_open (struct inode *inode, struct file *file) +{ + int target; + + if (MAJOR(inode->i_rdev) != MAJOR_NR) + return -ENXIO; + + target = MINOR(inode->i_rdev) >> PARTN_BITS; + if (target >= MAX_PORTS) + return -ENXIO; + + if (!ports[target].ahci_port) + return -ENXIO; + + return 0; +} + +static void ahci_release (struct inode *inode, struct file *file) +{ +} + +static int ahci_fsync (struct inode *inode, struct file *file) +{ + printk("fsync\n"); + return -ENOSYS; +} + +static struct file_operations ahci_fops = { + .lseek = NULL, + .read = block_read, + .write = block_write, + .readdir = NULL, + .select = NULL, + .ioctl = ahci_ioctl, + .mmap = NULL, + .open = ahci_open, + .release = ahci_release, + .fsync = ahci_fsync, + .fasync = NULL, + .check_media_change = NULL, + .revalidate = NULL, +}; + +/* Disk timed out while processing identify, interrupt ahci_probe_port */ +static void identify_timeout(unsigned long data) +{ + struct port *port = (void*) data; + + wake_up(&port->q); +} + +static struct timer_list identify_timer = { .function = identify_timeout }; + +static int ahci_identify(const volatile struct ahci_host *ahci_host, const volatile struct ahci_port *ahci_port, struct port *port, unsigned cmd) +{ + struct hd_driveid id; + struct ahci_fis_h2d *fis_h2d; + struct ahci_command *command = port->command; + struct ahci_cmd_tbl *prdtl = port->prdtl; + unsigned long flags; + unsigned slot; + unsigned long first_part; + unsigned long long timeout; + int ret = 0; + + /* Identify device */ + /* TODO: make this a request */ + slot = 0; + + fis_h2d = (void*) &prdtl[slot].cfis; + fis_h2d->fis_type = FIS_TYPE_REG_H2D; + fis_h2d->flags = 128; + fis_h2d->command = cmd; + fis_h2d->device = 0; + + /* Fetch the 512 identify data */ + memset(&id, 0, sizeof(id)); + + command[slot].opts = sizeof(*fis_h2d) / sizeof(u32); + + first_part = PAGE_ALIGN((unsigned long) &id) - (unsigned long) &id; + + if (first_part && first_part < sizeof(id)) { + /* split over two pages */ + + command[slot].opts |= (2 << 16); + + prdtl[slot].prdtl[0].dbau = 0; + prdtl[slot].prdtl[0].dba = vmtophys((void*) &id); + prdtl[slot].prdtl[0].dbc = first_part - 1; + prdtl[slot].prdtl[1].dbau = 0; + prdtl[slot].prdtl[1].dba = vmtophys((void*) &id + first_part); + prdtl[slot].prdtl[1].dbc = sizeof(id) - first_part - 1; + } + else + { + command[slot].opts |= (1 << 16); + + prdtl[slot].prdtl[0].dbau = 0; + prdtl[slot].prdtl[0].dba = vmtophys((void*) &id); + prdtl[slot].prdtl[0].dbc = sizeof(id) - 1; + } + + timeout = jiffies + WAIT_MAX; + while (readl(&ahci_port->tfd) & (BUSY_STAT | DRQ_STAT)) + if (jiffies > timeout) { + printk("sd%u: timeout waiting for ready\n", port-ports); + port->ahci_host = NULL; + port->ahci_port = NULL; + return 3; + } + + save_flags(flags); + cli(); + + port->identify = 1; + port->status = 0; + + /* Issue command */ + mb(); + writel(1 << slot, &ahci_port->ci); + + timeout = jiffies + WAIT_MAX; + identify_timer.expires = timeout; + identify_timer.data = (unsigned long) port; + add_timer(&identify_timer); + while (!port->status) { + if (jiffies >= timeout) { + printk("sd%u: timeout waiting for identify\n", port-ports); + port->ahci_host = NULL; + port->ahci_port = NULL; + del_timer(&identify_timer); + restore_flags(flags); + return 3; + } + sleep_on(&port->q); + } + del_timer(&identify_timer); + restore_flags(flags); + + if ((port->status & PORT_IRQ_TF_ERR) || readl(&ahci_port->is) & PORT_IRQ_TF_ERR) + { + /* Identify error */ + port->capacity = 0; + port->lba48 = 0; + ret = 2; + } else { + memcpy(&port->id, &id, sizeof(id)); + port->is_cd = 0; + + ide_fixstring(id.model, sizeof(id.model), 1); + ide_fixstring(id.fw_rev, sizeof(id.fw_rev), 1); + ide_fixstring(id.serial_no, sizeof(id.serial_no), 1); + if (cmd == WIN_PIDENTIFY) + { + unsigned char type = (id.config >> 8) & 0x1f; + + printk("sd%u: %s, ATAPI ", port - ports, id.model); + if (type == 5) + { + printk("unsupported CDROM drive\n"); + port->is_cd = 1; + port->lba48 = 0; + port->capacity = 0; + } + else + { + printk("unsupported type %d\n", type); + port->lba48 = 0; + port->capacity = 0; + return 2; + } + return 0; + } + + if (id.command_set_2 & (1U<<10)) + { + port->lba48 = 1; + port->capacity = id.lba_capacity_2; + if (port->capacity >= (1ULL << 32)) + { + port->capacity = (1ULL << 32) - 1; + printk("Warning: truncating disk size to 2TiB\n"); + } + } + else + { + port->lba48 = 0; + port->capacity = id.lba_capacity; + if (port->capacity > (1ULL << 24)) + { + port->capacity = (1ULL << 24); + printk("Warning: truncating disk size to 128GiB\n"); + } + } + if (port->capacity/2048 >= 10240) + printk("sd%u: %s, %uGB w/%dkB Cache\n", (unsigned) (port - ports), id.model, (unsigned) (port->capacity/(2048*1024)), id.buf_size/2); + else + printk("sd%u: %s, %uMB w/%dkB Cache\n", (unsigned) (port - ports), id.model, (unsigned) (port->capacity/2048), id.buf_size/2); + } + port->identify = 0; + + return ret; +} + +/* Probe one AHCI port */ +static void ahci_probe_port(const volatile struct ahci_host *ahci_host, const volatile struct ahci_port *ahci_port) +{ + struct port *port; + void *mem; + unsigned cls = ((readl(&ahci_host->cap) >> 8) & 0x1f) + 1; + struct ahci_command *command; + struct ahci_fis *fis; + struct ahci_cmd_tbl *prdtl; + vm_size_t size = + cls * sizeof(*command) + + sizeof(*fis) + + cls * sizeof(*prdtl); + unsigned i; + unsigned long long timeout; + + for (i = 0; i < MAX_PORTS; i++) { + if (!ports[i].ahci_port) + break; + } + if (i == MAX_PORTS) + return; + port = &ports[i]; + + /* Has to be 1K-aligned */ + mem = vmalloc (size); + if (!mem) + return; + assert (!(((unsigned long) mem) & (1024-1))); + memset (mem, 0, size); + + port->ahci_host = ahci_host; + port->ahci_port = ahci_port; + port->cls = cls; + + port->command = command = mem; + port->fis = fis = (void*) command + cls * sizeof(*command); + port->prdtl = prdtl = (void*) fis + sizeof(*fis); + + /* Stop commands */ + writel(readl(&ahci_port->cmd) & ~PORT_CMD_START, &ahci_port->cmd); + timeout = jiffies + WAIT_MAX; + while (readl(&ahci_port->cmd) & PORT_CMD_LIST_ON) + if (jiffies > timeout) { + printk("sd%u: timeout waiting for list completion\n", (unsigned) (port-ports)); + port->ahci_host = NULL; + port->ahci_port = NULL; + return; + } + + writel(readl(&ahci_port->cmd) & ~PORT_CMD_FIS_RX, &ahci_port->cmd); + timeout = jiffies + WAIT_MAX; + while (readl(&ahci_port->cmd) & PORT_CMD_FIS_ON) + if (jiffies > timeout) { + printk("sd%u: timeout waiting for FIS completion\n", (unsigned) (port-ports)); + port->ahci_host = NULL; + port->ahci_port = NULL; + return; + } + + /* We don't support 64bit */ + /* Point controller to our buffers */ + writel(0, &ahci_port->clbu); + writel(vmtophys((void*) command), &ahci_port->clb); + writel(0, &ahci_port->fbu); + writel(vmtophys((void*) fis), &ahci_port->fb); + + /* Clear any previous interrupts */ + writel(readl(&ahci_port->is), &ahci_port->is); + writel(1 << (ahci_port - ahci_host->ports), &ahci_host->is); + + /* And activate them */ + writel(DEF_PORT_IRQ, &ahci_port->ie); + writel(readl(&ahci_host->ghc) | HOST_IRQ_EN, &ahci_host->ghc); + + for (i = 0; i < cls; i++) + { + command[i].ctbau = 0; + command[i].ctba = vmtophys((void*) &prdtl[i]); + } + + /* Start commands */ + timeout = jiffies + WAIT_MAX; + while (readl(&ahci_port->cmd) & PORT_CMD_LIST_ON) + if (jiffies > timeout) { + printk("sd%u: timeout waiting for list completion\n", (unsigned) (port-ports)); + port->ahci_host = NULL; + port->ahci_port = NULL; + return; + } + + writel(readl(&ahci_port->cmd) | PORT_CMD_FIS_RX | PORT_CMD_START, &ahci_port->cmd); + + /* if PxCMD.ATAPI is set, try ATAPI identify; otherwise try AHCI, then ATAPI */ + if (readl(&ahci_port->cmd) & PORT_CMD_ATAPI || + ahci_identify(ahci_host, ahci_port, port, WIN_IDENTIFY) >= 2) + ahci_identify(ahci_host, ahci_port, port, WIN_PIDENTIFY); +} + +/* Probe one AHCI PCI device */ +static void ahci_probe_dev(unsigned char bus, unsigned char device) +{ + unsigned char hdrtype; + unsigned char dev, fun; + const volatile struct ahci_host *ahci_host; + const volatile struct ahci_port *ahci_port; + unsigned nports, n, i; + unsigned port_map; + unsigned bar; + unsigned char irq; + + dev = PCI_SLOT(device); + fun = PCI_FUNC(device); + + /* Get configuration */ + if (pcibios_read_config_byte(bus, device, PCI_HEADER_TYPE, &hdrtype) != PCIBIOS_SUCCESSFUL) { + printk("ahci: %02x:%02x.%x: Can not read configuration", bus, dev, fun); + return; + } + /* Ignore multifunction bit */ + hdrtype &= ~0x80; + + if (hdrtype != 0) { + printk("ahci: %02x:%02x.%x: Unknown hdrtype %d\n", bus, dev, fun, hdrtype); + return; + } + + if (pcibios_read_config_dword(bus, device, PCI_BASE_ADDRESS_5, &bar) != PCIBIOS_SUCCESSFUL) { + printk("ahci: %02x:%02x.%x: Can not read BAR 5", bus, dev, fun); + return; + } + if (bar & PCI_BASE_ADDRESS_SPACE_IO) { + printk("ahci: %02x:%02x.%x: BAR 5 is I/O?!", bus, dev, fun); + return; + } + bar &= PCI_BASE_ADDRESS_MEM_MASK; + + if (pcibios_read_config_byte(bus, device, PCI_INTERRUPT_LINE, &irq) != PCIBIOS_SUCCESSFUL) { + printk("ahci: %02x:%02x.%x: Can not read IRQ", bus, dev, fun); + return; + } + + printk("AHCI SATA %02x:%02x.%x BAR 0x%x IRQ %u\n", bus, dev, fun, bar, irq); + + /* Map mmio */ + ahci_host = vremap(bar, 0x2000); + + /* Request IRQ */ + if (request_irq(irq, &ahci_interrupt, SA_SHIRQ, "ahci", (void*) ahci_host)) { + printk("ahci: %02x:%02x.%x: Can not get irq %u\n", bus, dev, fun, irq); + return; + } + +#ifdef CONFIG_BLK_DEV_IDE + /* OK, we will handle it. Disable probing on legacy IDE ports it may have. */ + for (i = 0; i < 6; i++) + { + unsigned mybar; + if (pcibios_read_config_dword(bus, device, PCI_BASE_ADDRESS_0 + i*4, &mybar) == PCIBIOS_SUCCESSFUL) { + if (!(bar & PCI_BASE_ADDRESS_SPACE_IO)) + /* Memory, don't care */ + continue; + /* printk("ahci: %02x:%02x.%x: BAR %d is %x\n", bus, dev, fun, i, mybar); */ + ide_disable_base(bar & PCI_BASE_ADDRESS_IO_MASK); + } + } +#endif + + nports = (readl(&ahci_host->cap) & 0x1f) + 1; + port_map = readl(&ahci_host->pi); + + for (n = 0, i = 0; i < AHCI_MAX_PORTS; i++) + if (port_map & (1U << i)) + n++; + + if (nports != n) { + printk("ahci: %02x:%02x.%x: Odd number of ports %u, assuming %u is correct\n", bus, dev, fun, n, nports); + port_map = 0; + } + if (!port_map) { + port_map = (1U << nports) - 1; + } + + for (i = 0; i < AHCI_MAX_PORTS; i++) { + u32 ssts; + u8 det, ipm; + + if (!(port_map & (1U << i))) + continue; + + ahci_port = &ahci_host->ports[i]; + + ssts = readl(&ahci_port->ssts); + det = ssts & 0xf; + switch (det) + { + case 0x0: + /* Device not present */ + continue; + case 0x1: + printk("ahci: %02x:%02x.%x: Port %u communication not established. TODO: power on device\n", bus, dev, fun, i); + continue; + case 0x3: + /* Present and communication established */ + break; + case 0x4: + printk("ahci: %02x:%02x.%x: Port %u phy offline?!\n", bus, dev, fun, i); + continue; + default: + printk("ahci: %02x:%02x.%x: Unknown port %u DET %x\n", bus, dev, fun, i, det); + continue; + } + + ipm = (ssts >> 8) & 0xf; + switch (ipm) + { + case 0x0: + /* Device not present */ + continue; + case 0x1: + /* Active */ + break; + case 0x2: + printk("ahci: %02x:%02x.%x: Port %u in Partial power management. TODO: power on device\n", bus, dev, fun, i); + continue; + case 0x6: + printk("ahci: %02x:%02x.%x: Port %u in Slumber power management. TODO: power on device\n", bus, dev, fun, i); + continue; + default: + printk("ahci: %02x:%02x.%x: Unknown port %u IPM %x\n", bus, dev, fun, i, ipm); + continue; + } + + /* OK! Probe this port */ + ahci_probe_port(ahci_host, ahci_port); + } +} + +/* genhd callback to set size of disks */ +static void ahci_geninit(struct gendisk *gd) +{ + unsigned unit; + struct port *port; + + for (unit = 0; unit < gd->nr_real; unit++) { + port = &ports[unit]; + port->part[0].nr_sects = port->capacity; + if (!port->part[0].nr_sects) + port->part[0].nr_sects = -1; + } +} + +/* Probe all AHCI PCI devices */ +void ahci_probe_pci(void) +{ + unsigned char bus, device; + unsigned short index; + int ret; + unsigned nports, unit, nminors; + struct port *port; + struct gendisk *gd, **gdp; + int *bs; + + for (index = 0; + (ret = pcibios_find_class(PCI_CLASS_STORAGE_SATA_AHCI, index, &bus, &device)) == PCIBIOS_SUCCESSFUL; + index++) + { + /* Note: this prevents from also having a SCSI controler. + * It shouldn't harm too much until we have proper hardware + * enumeration. + */ + if (register_blkdev(MAJOR_NR, "sd", &ahci_fops) < 0) + printk("could not register ahci\n"); + ahci_probe_dev(bus, device); + } + + for (nports = 0, port = &ports[0]; port < &ports[MAX_PORTS]; port++) + if (port->ahci_port) + nports++; + + nminors = nports * (1<sizes = kmalloc(nminors * sizeof(*gd->sizes), GFP_KERNEL); + gd->part = kmalloc(nminors * sizeof(*gd->part), GFP_KERNEL); + bs = kmalloc(nminors * sizeof(*bs), GFP_KERNEL); + + blksize_size[MAJOR_NR] = bs; + for (unit = 0; unit < nminors; unit++) + /* We prefer to transfer whole pages */ + *bs++ = PAGE_SIZE; + + memset(gd->part, 0, nminors * sizeof(*gd->part)); + + for (unit = 0; unit < nports; unit++) { + ports[unit].gd = gd; + ports[unit].part = &gd->part[unit << PARTN_BITS]; + } + + gd->major = MAJOR_NR; + gd->major_name = "sd"; + gd->minor_shift = PARTN_BITS; + gd->max_p = 1<max_nr = nports; + gd->nr_real = nports; + gd->init = ahci_geninit; + gd->next = NULL; + + for (gdp = &gendisk_head; *gdp; gdp = &((*gdp)->next)) + ; + *gdp = gd; + + blk_dev[MAJOR_NR].request_fn = ahci_do_request; +} diff --git a/linux/dev/drivers/block/floppy.c b/linux/dev/drivers/block/floppy.c new file mode 100644 index 0000000..83d66f0 --- /dev/null +++ b/linux/dev/drivers/block/floppy.c @@ -0,0 +1,4288 @@ +/* + * linux/kernel/floppy.c + * + * Copyright (C) 1991, 1992 Linus Torvalds + * Copyright (C) 1993, 1994 Alain Knaff + */ +/* + * 02.12.91 - Changed to static variables to indicate need for reset + * and recalibrate. This makes some things easier (output_byte reset + * checking etc), and means less interrupt jumping in case of errors, + * so the code is hopefully easier to understand. + */ + +/* + * This file is certainly a mess. I've tried my best to get it working, + * but I don't like programming floppies, and I have only one anyway. + * Urgel. I should check for more errors, and do more graceful error + * recovery. Seems there are problems with several drives. I've tried to + * correct them. No promises. + */ + +/* + * As with hd.c, all routines within this file can (and will) be called + * by interrupts, so extreme caution is needed. A hardware interrupt + * handler may not sleep, or a kernel panic will happen. Thus I cannot + * call "floppy-on" directly, but have to set a special timer interrupt + * etc. + */ + +/* + * 28.02.92 - made track-buffering routines, based on the routines written + * by entropy@wintermute.wpi.edu (Lawrence Foard). Linus. + */ + +/* + * Automatic floppy-detection and formatting written by Werner Almesberger + * (almesber@nessie.cs.id.ethz.ch), who also corrected some problems with + * the floppy-change signal detection. + */ + +/* + * 1992/7/22 -- Hennus Bergman: Added better error reporting, fixed + * FDC data overrun bug, added some preliminary stuff for vertical + * recording support. + * + * 1992/9/17: Added DMA allocation & DMA functions. -- hhb. + * + * TODO: Errors are still not counted properly. + */ + +/* 1992/9/20 + * Modifications for ``Sector Shifting'' by Rob Hooft (hooft@chem.ruu.nl) + * modeled after the freeware MS-DOS program fdformat/88 V1.8 by + * Christoph H. Hochst\"atter. + * I have fixed the shift values to the ones I always use. Maybe a new + * ioctl() should be created to be able to modify them. + * There is a bug in the driver that makes it impossible to format a + * floppy as the first thing after bootup. + */ + +/* + * 1993/4/29 -- Linus -- cleaned up the timer handling in the kernel, and + * this helped the floppy driver as well. Much cleaner, and still seems to + * work. + */ + +/* 1994/6/24 --bbroad-- added the floppy table entries and made + * minor modifications to allow 2.88 floppies to be run. + */ + +/* 1994/7/13 -- Paul Vojta -- modified the probing code to allow three or more + * disk types. + */ + +/* + * 1994/8/8 -- Alain Knaff -- Switched to fdpatch driver: Support for bigger + * format bug fixes, but unfortunately some new bugs too... + */ + +/* 1994/9/17 -- Koen Holtman -- added logging of physical floppy write + * errors to allow safe writing by specialized programs. + */ + +/* 1995/4/24 -- Dan Fandrich -- added support for Commodore 1581 3.5" disks + * by defining bit 1 of the "stretch" parameter to mean put sectors on the + * opposite side of the disk, leaving the sector IDs alone (i.e. Commodore's + * drives are "upside-down"). + */ + +/* + * 1995/8/26 -- Andreas Busse -- added Mips support. + */ + +/* + * 1995/10/18 -- Ralf Baechle -- Portability cleanup; move machine dependent + * features to asm/floppy.h. + */ + + +#define FLOPPY_SANITY_CHECK +#undef FLOPPY_SILENT_DCL_CLEAR + +#define REALLY_SLOW_IO + +#define DEBUGT 2 +#define DCL_DEBUG /* debug disk change line */ + +/* do print messages for unexpected interrupts */ +static int print_unex=1; +#include +#include + +/* the following is the mask of allowed drives. By default units 2 and + * 3 of both floppy controllers are disabled, because switching on the + * motor of these drives causes system hangs on some PCI computers. drive + * 0 is the low bit (0x1), and drive 7 is the high bit (0x80). Bits are on if + * a drive is allowed. */ +static int FLOPPY_IRQ=6; +static int FLOPPY_DMA=2; +static int allowed_drive_mask = 0x33; + +static int irqdma_allocated = 0; + +#include +#include +#include +#include +#include +#define FDPATCHES +#include + + +#include + + +#define OLDFDRAWCMD 0x020d /* send a raw command to the FDC */ + +struct old_floppy_raw_cmd { + void *data; + long length; + + unsigned char rate; + unsigned char flags; + unsigned char cmd_count; + unsigned char cmd[9]; + unsigned char reply_count; + unsigned char reply[7]; + int track; +}; + +#include +#include +#include +#include +#include +#include +#include /* CMOS defines */ +#include +#include + +#include +#include +#include +#include +#include + +static int use_virtual_dma=0; /* virtual DMA for Intel */ +static unsigned short virtual_dma_port=0x3f0; +void floppy_interrupt(int irq, void *dev_id, struct pt_regs * regs); +static int set_dor(int fdc, char mask, char data); +static inline int __get_order(unsigned long size); +#include + + +#define MAJOR_NR FLOPPY_MAJOR + +#include +#include /* for the compatibility eject ioctl */ + +#include + + +#ifndef FLOPPY_MOTOR_MASK +#define FLOPPY_MOTOR_MASK 0xf0 +#endif + +#ifndef fd_get_dma_residue +#define fd_get_dma_residue() get_dma_residue(FLOPPY_DMA) +#endif + +/* Dma Memory related stuff */ + +/* Pure 2^n version of get_order */ +static inline int __get_order(unsigned long size) +{ + int order; + + size = (size-1) >> (PAGE_SHIFT-1); + order = -1; + do { + size >>= 1; + order++; + } while (size); + return order; +} + +#ifndef fd_dma_mem_free +#define fd_dma_mem_free(addr, size) free_pages(addr, __get_order(size)) +#endif + +#ifndef fd_dma_mem_alloc +#define fd_dma_mem_alloc(size) __get_dma_pages(GFP_KERNEL,__get_order(size)) +#endif + +/* End dma memory related stuff */ + +static unsigned int fake_change = 0; +static int initialising=1; + +static inline int TYPE(kdev_t x) { + return (MINOR(x)>>2) & 0x1f; +} +static inline int DRIVE(kdev_t x) { + return (MINOR(x)&0x03) | ((MINOR(x)&0x80) >> 5); +} +#define ITYPE(x) (((x)>>2) & 0x1f) +#define TOMINOR(x) ((x & 3) | ((x & 4) << 5)) +#define UNIT(x) ((x) & 0x03) /* drive on fdc */ +#define FDC(x) (((x) & 0x04) >> 2) /* fdc of drive */ +#define REVDRIVE(fdc, unit) ((unit) + ((fdc) << 2)) + /* reverse mapping from unit and fdc to drive */ +#define DP (&drive_params[current_drive]) +#define DRS (&drive_state[current_drive]) +#define DRWE (&write_errors[current_drive]) +#define FDCS (&fdc_state[fdc]) +#define CLEARF(x) (clear_bit(x##_BIT, &DRS->flags)) +#define SETF(x) (set_bit(x##_BIT, &DRS->flags)) +#define TESTF(x) (test_bit(x##_BIT, &DRS->flags)) + +#define UDP (&drive_params[drive]) +#define UDRS (&drive_state[drive]) +#define UDRWE (&write_errors[drive]) +#define UFDCS (&fdc_state[FDC(drive)]) +#define UCLEARF(x) (clear_bit(x##_BIT, &UDRS->flags)) +#define USETF(x) (set_bit(x##_BIT, &UDRS->flags)) +#define UTESTF(x) (test_bit(x##_BIT, &UDRS->flags)) + +#define DPRINT(format, args...) printk(DEVICE_NAME "%d: " format, current_drive , ## args) + +#define PH_HEAD(floppy,head) (((((floppy)->stretch & 2) >>1) ^ head) << 2) +#define STRETCH(floppy) ((floppy)->stretch & FD_STRETCH) + +#define CLEARSTRUCT(x) memset((x), 0, sizeof(*(x))) + +#define INT_OFF save_flags(flags); cli() +#define INT_ON restore_flags(flags) + +/* read/write */ +#define COMMAND raw_cmd->cmd[0] +#define DR_SELECT raw_cmd->cmd[1] +#define TRACK raw_cmd->cmd[2] +#define HEAD raw_cmd->cmd[3] +#define SECTOR raw_cmd->cmd[4] +#define SIZECODE raw_cmd->cmd[5] +#define SECT_PER_TRACK raw_cmd->cmd[6] +#define GAP raw_cmd->cmd[7] +#define SIZECODE2 raw_cmd->cmd[8] +#define NR_RW 9 + +/* format */ +#define F_SIZECODE raw_cmd->cmd[2] +#define F_SECT_PER_TRACK raw_cmd->cmd[3] +#define F_GAP raw_cmd->cmd[4] +#define F_FILL raw_cmd->cmd[5] +#define NR_F 6 + +/* + * Maximum disk size (in kilobytes). This default is used whenever the + * current disk size is unknown. + * [Now it is rather a minimum] + */ +#define MAX_DISK_SIZE 4 /* 3984*/ + +#define K_64 0x10000 /* 64KB */ + +/* + * globals used by 'result()' + */ +#define MAX_REPLIES 16 +static unsigned char reply_buffer[MAX_REPLIES]; +static int inr; /* size of reply buffer, when called from interrupt */ +#define ST0 (reply_buffer[0]) +#define ST1 (reply_buffer[1]) +#define ST2 (reply_buffer[2]) +#define ST3 (reply_buffer[0]) /* result of GETSTATUS */ +#define R_TRACK (reply_buffer[3]) +#define R_HEAD (reply_buffer[4]) +#define R_SECTOR (reply_buffer[5]) +#define R_SIZECODE (reply_buffer[6]) + +#define SEL_DLY (2*HZ/100) + +#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) +/* + * this struct defines the different floppy drive types. + */ +static struct { + struct floppy_drive_params params; + const char *name; /* name printed while booting */ +} default_drive_params[]= { +/* NOTE: the time values in jiffies should be in msec! + CMOS drive type + | Maximum data rate supported by drive type + | | Head load time, msec + | | | Head unload time, msec (not used) + | | | | Step rate interval, usec + | | | | | Time needed for spinup time (jiffies) + | | | | | | Timeout for spinning down (jiffies) + | | | | | | | Spindown offset (where disk stops) + | | | | | | | | Select delay + | | | | | | | | | RPS + | | | | | | | | | | Max number of tracks + | | | | | | | | | | | Interrupt timeout + | | | | | | | | | | | | Max nonintlv. sectors + | | | | | | | | | | | | | -Max Errors- flags */ +{{0, 500, 16, 16, 8000, 1*HZ, 3*HZ, 0, SEL_DLY, 5, 80, 3*HZ, 20, {3,1,2,0,2}, 0, + 0, { 7, 4, 8, 2, 1, 5, 3,10}, 3*HZ/2, 0 }, "unknown" }, + +{{1, 300, 16, 16, 8000, 1*HZ, 3*HZ, 0, SEL_DLY, 5, 40, 3*HZ, 17, {3,1,2,0,2}, 0, + 0, { 1, 0, 0, 0, 0, 0, 0, 0}, 3*HZ/2, 1 }, "360K PC" }, /*5 1/4 360 KB PC*/ + +{{2, 500, 16, 16, 6000, 4*HZ/10, 3*HZ, 14, SEL_DLY, 6, 83, 3*HZ, 17, {3,1,2,0,2}, 0, + 0, { 2, 5, 6,23,10,20,12, 0}, 3*HZ/2, 2 }, "1.2M" }, /*5 1/4 HD AT*/ + +{{3, 250, 16, 16, 3000, 1*HZ, 3*HZ, 0, SEL_DLY, 5, 83, 3*HZ, 20, {3,1,2,0,2}, 0, + 0, { 4,22,21,30, 3, 0, 0, 0}, 3*HZ/2, 4 }, "720k" }, /*3 1/2 DD*/ + +{{4, 500, 16, 16, 4000, 4*HZ/10, 3*HZ, 10, SEL_DLY, 5, 83, 3*HZ, 20, {3,1,2,0,2}, 0, + 0, { 7, 4,25,22,31,21,29,11}, 3*HZ/2, 7 }, "1.44M" }, /*3 1/2 HD*/ + +{{5, 1000, 15, 8, 3000, 4*HZ/10, 3*HZ, 10, SEL_DLY, 5, 83, 3*HZ, 40, {3,1,2,0,2}, 0, + 0, { 7, 8, 4,25,28,22,31,21}, 3*HZ/2, 8 }, "2.88M AMI BIOS" }, /*3 1/2 ED*/ + +{{6, 1000, 15, 8, 3000, 4*HZ/10, 3*HZ, 10, SEL_DLY, 5, 83, 3*HZ, 40, {3,1,2,0,2}, 0, + 0, { 7, 8, 4,25,28,22,31,21}, 3*HZ/2, 8 }, "2.88M" } /*3 1/2 ED*/ +/* | --autodetected formats--- | | | + * read_track | | Name printed when booting + * | Native format + * Frequency of disk change checks */ +}; + +static struct floppy_drive_params drive_params[N_DRIVE]; +static struct floppy_drive_struct drive_state[N_DRIVE]; +static struct floppy_write_errors write_errors[N_DRIVE]; +static struct floppy_raw_cmd *raw_cmd, default_raw_cmd; + +/* + * This struct defines the different floppy types. + * + * Bit 0 of 'stretch' tells if the tracks need to be doubled for some + * types (e.g. 360kB diskette in 1.2MB drive, etc.). Bit 1 of 'stretch' + * tells if the disk is in Commodore 1581 format, which means side 0 sectors + * are located on side 1 of the disk but with a side 0 ID, and vice-versa. + * This is the same as the Sharp MZ-80 5.25" CP/M disk format, except that the + * 1581's logical side 0 is on physical side 1, whereas the Sharp's logical + * side 0 is on physical side 0 (but with the misnamed sector IDs). + * 'stretch' should probably be renamed to something more general, like + * 'options'. Other parameters should be self-explanatory (see also + * setfdprm(8)). + */ +static struct floppy_struct floppy_type[32] = { + { 0, 0,0, 0,0,0x00,0x00,0x00,0x00,NULL }, /* 0 no testing */ + { 720, 9,2,40,0,0x2A,0x02,0xDF,0x50,"d360" }, /* 1 360KB PC */ + { 2400,15,2,80,0,0x1B,0x00,0xDF,0x54,"h1200" }, /* 2 1.2MB AT */ + { 720, 9,1,80,0,0x2A,0x02,0xDF,0x50,"D360" }, /* 3 360KB SS 3.5" */ + { 1440, 9,2,80,0,0x2A,0x02,0xDF,0x50,"D720" }, /* 4 720KB 3.5" */ + { 720, 9,2,40,1,0x23,0x01,0xDF,0x50,"h360" }, /* 5 360KB AT */ + { 1440, 9,2,80,0,0x23,0x01,0xDF,0x50,"h720" }, /* 6 720KB AT */ + { 2880,18,2,80,0,0x1B,0x00,0xCF,0x6C,"H1440" }, /* 7 1.44MB 3.5" */ + { 5760,36,2,80,0,0x1B,0x43,0xAF,0x54,"E2880" }, /* 8 2.88MB 3.5" */ + { 6240,39,2,80,0,0x1B,0x43,0xAF,0x28,"E3120"}, /* 9 3.12MB 3.5" */ + + { 2880,18,2,80,0,0x25,0x00,0xDF,0x02,"h1440" }, /* 10 1.44MB 5.25" */ + { 3360,21,2,80,0,0x1C,0x00,0xCF,0x0C,"H1680" }, /* 11 1.68MB 3.5" */ + { 820,10,2,41,1,0x25,0x01,0xDF,0x2E,"h410" }, /* 12 410KB 5.25" */ + { 1640,10,2,82,0,0x25,0x02,0xDF,0x2E,"H820" }, /* 13 820KB 3.5" */ + { 2952,18,2,82,0,0x25,0x00,0xDF,0x02,"h1476" }, /* 14 1.48MB 5.25" */ + { 3444,21,2,82,0,0x25,0x00,0xDF,0x0C,"H1722" }, /* 15 1.72MB 3.5" */ + { 840,10,2,42,1,0x25,0x01,0xDF,0x2E,"h420" }, /* 16 420KB 5.25" */ + { 1660,10,2,83,0,0x25,0x02,0xDF,0x2E,"H830" }, /* 17 830KB 3.5" */ + { 2988,18,2,83,0,0x25,0x00,0xDF,0x02,"h1494" }, /* 18 1.49MB 5.25" */ + { 3486,21,2,83,0,0x25,0x00,0xDF,0x0C,"H1743" }, /* 19 1.74 MB 3.5" */ + + { 1760,11,2,80,0,0x1C,0x09,0xCF,0x00,"h880" }, /* 20 880KB 5.25" */ + { 2080,13,2,80,0,0x1C,0x01,0xCF,0x00,"D1040" }, /* 21 1.04MB 3.5" */ + { 2240,14,2,80,0,0x1C,0x19,0xCF,0x00,"D1120" }, /* 22 1.12MB 3.5" */ + { 3200,20,2,80,0,0x1C,0x20,0xCF,0x2C,"h1600" }, /* 23 1.6MB 5.25" */ + { 3520,22,2,80,0,0x1C,0x08,0xCF,0x2e,"H1760" }, /* 24 1.76MB 3.5" */ + { 3840,24,2,80,0,0x1C,0x20,0xCF,0x00,"H1920" }, /* 25 1.92MB 3.5" */ + { 6400,40,2,80,0,0x25,0x5B,0xCF,0x00,"E3200" }, /* 26 3.20MB 3.5" */ + { 7040,44,2,80,0,0x25,0x5B,0xCF,0x00,"E3520" }, /* 27 3.52MB 3.5" */ + { 7680,48,2,80,0,0x25,0x63,0xCF,0x00,"E3840" }, /* 28 3.84MB 3.5" */ + + { 3680,23,2,80,0,0x1C,0x10,0xCF,0x00,"H1840" }, /* 29 1.84MB 3.5" */ + { 1600,10,2,80,0,0x25,0x02,0xDF,0x2E,"D800" }, /* 30 800KB 3.5" */ + { 3200,20,2,80,0,0x1C,0x00,0xCF,0x2C,"H1600" }, /* 31 1.6MB 3.5" */ +}; + +#define NUMBER(x) (sizeof(x) / sizeof(*(x))) +#define SECTSIZE (_FD_SECTSIZE(*floppy)) + +/* Auto-detection: Disk type used until the next media change occurs. */ +static struct floppy_struct *current_type[N_DRIVE] = { + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL +}; + +/* + * User-provided type information. current_type points to + * the respective entry of this array. + */ +static struct floppy_struct user_params[N_DRIVE]; + +static int floppy_sizes[256]; +static int floppy_blocksizes[256] = { 0, }; + +/* + * The driver is trying to determine the correct media format + * while probing is set. rw_interrupt() clears it after a + * successful access. + */ +static int probing = 0; + +/* Synchronization of FDC access. */ +#define FD_COMMAND_NONE -1 +#define FD_COMMAND_ERROR 2 +#define FD_COMMAND_OKAY 3 + +static volatile int command_status = FD_COMMAND_NONE, fdc_busy = 0; +static struct wait_queue *fdc_wait = NULL, *command_done = NULL; +#ifdef MACH +#define NO_SIGNAL (! issig () || ! interruptible) +#else +#define NO_SIGNAL (!(current->signal & ~current->blocked) || !interruptible) +#endif +#define CALL(x) if ((x) == -EINTR) return -EINTR +#define ECALL(x) if ((ret = (x))) return ret; +#define _WAIT(x,i) CALL(ret=wait_til_done((x),i)) +#define WAIT(x) _WAIT((x),interruptible) +#define IWAIT(x) _WAIT((x),1) + +/* Errors during formatting are counted here. */ +static int format_errors; + +/* Format request descriptor. */ +static struct format_descr format_req; + +/* + * Rate is 0 for 500kb/s, 1 for 300kbps, 2 for 250kbps + * Spec1 is 0xSH, where S is stepping rate (F=1ms, E=2ms, D=3ms etc), + * H is head unload time (1=16ms, 2=32ms, etc) + */ + +/* + * Track buffer + * Because these are written to by the DMA controller, they must + * not contain a 64k byte boundary crossing, or data will be + * corrupted/lost. + */ +static char *floppy_track_buffer=0; +static int max_buffer_sectors=0; + +static int *errors; +typedef void (*done_f)(int); +static struct cont_t { + void (*interrupt)(void); /* this is called after the interrupt of the + * main command */ + void (*redo)(void); /* this is called to retry the operation */ + void (*error)(void); /* this is called to tally an error */ + done_f done; /* this is called to say if the operation has + * succeeded/failed */ +} *cont=NULL; + +static void floppy_ready(void); +static void floppy_start(void); +static void process_fd_request(void); +static void recalibrate_floppy(void); +static void floppy_shutdown(void); + +static int floppy_grab_irq_and_dma(void); +static void floppy_release_irq_and_dma(void); + +/* + * The "reset" variable should be tested whenever an interrupt is scheduled, + * after the commands have been sent. This is to ensure that the driver doesn't + * get wedged when the interrupt doesn't come because of a failed command. + * reset doesn't need to be tested before sending commands, because + * output_byte is automatically disabled when reset is set. + */ +#define CHECK_RESET { if (FDCS->reset){ reset_fdc(); return; } } +static void reset_fdc(void); + +/* + * These are global variables, as that's the easiest way to give + * information to interrupts. They are the data used for the current + * request. + */ +#define NO_TRACK -1 +#define NEED_1_RECAL -2 +#define NEED_2_RECAL -3 + +/* */ +static int usage_count = 0; + + +/* buffer related variables */ +static int buffer_track = -1; +static int buffer_drive = -1; +static int buffer_min = -1; +static int buffer_max = -1; + +/* fdc related variables, should end up in a struct */ +static struct floppy_fdc_state fdc_state[N_FDC]; +static int fdc; /* current fdc */ + +static struct floppy_struct *_floppy = floppy_type; +static unsigned char current_drive = 0; +static long current_count_sectors = 0; +static unsigned char sector_t; /* sector in track */ + +#ifndef fd_eject +#define fd_eject(x) -EINVAL +#endif + + +#ifdef DEBUGT +static long unsigned debugtimer; +#endif + +/* + * Debugging + * ========= + */ +static inline void set_debugt(void) +{ +#ifdef DEBUGT + debugtimer = jiffies; +#endif +} + +static inline void debugt(const char *message) +{ +#ifdef DEBUGT + if (DP->flags & DEBUGT) + printk("%s dtime=%lu\n", message, jiffies-debugtimer); +#endif +} + +typedef void (*timeout_fn)(unsigned long); +static struct timer_list fd_timeout ={ NULL, NULL, 0, 0, + (timeout_fn) floppy_shutdown }; + +static const char *timeout_message; + +#ifdef FLOPPY_SANITY_CHECK +static void is_alive(const char *message) +{ + /* this routine checks whether the floppy driver is "alive" */ + if (fdc_busy && command_status < 2 && !fd_timeout.prev){ + DPRINT("timeout handler died: %s\n",message); + } +} +#endif + +#ifdef FLOPPY_SANITY_CHECK + +#define OLOGSIZE 20 + +static void (*lasthandler)(void) = NULL; +static int interruptjiffies=0; +static int resultjiffies=0; +static int resultsize=0; +static int lastredo=0; + +static struct output_log { + unsigned char data; + unsigned char status; + unsigned long jiffies; +} output_log[OLOGSIZE]; + +static int output_log_pos=0; +#endif + +#define CURRENTD -1 +#define MAXTIMEOUT -2 + +static void reschedule_timeout(int drive, const char *message, int marg) +{ + if (drive == CURRENTD) + drive = current_drive; + del_timer(&fd_timeout); + if (drive < 0 || drive > N_DRIVE) { + fd_timeout.expires = jiffies + 20*HZ; + drive=0; + } else + fd_timeout.expires = jiffies + UDP->timeout; + add_timer(&fd_timeout); + if (UDP->flags & FD_DEBUG){ + DPRINT("reschedule timeout "); + printk(message, marg); + printk("\n"); + } + timeout_message = message; +} + +static int maximum(int a, int b) +{ + if(a > b) + return a; + else + return b; +} +#define INFBOUND(a,b) (a)=maximum((a),(b)); + +static int minimum(int a, int b) +{ + if(a < b) + return a; + else + return b; +} +#define SUPBOUND(a,b) (a)=minimum((a),(b)); + + +/* + * Bottom half floppy driver. + * ========================== + * + * This part of the file contains the code talking directly to the hardware, + * and also the main service loop (seek-configure-spinup-command) + */ + +/* + * disk change. + * This routine is responsible for maintaining the FD_DISK_CHANGE flag, + * and the last_checked date. + * + * last_checked is the date of the last check which showed 'no disk change' + * FD_DISK_CHANGE is set under two conditions: + * 1. The floppy has been changed after some i/o to that floppy already + * took place. + * 2. No floppy disk is in the drive. This is done in order to ensure that + * requests are quickly flushed in case there is no disk in the drive. It + * follows that FD_DISK_CHANGE can only be cleared if there is a disk in + * the drive. + * + * For 1., maxblock is observed. Maxblock is 0 if no i/o has taken place yet. + * For 2., FD_DISK_NEWCHANGE is watched. FD_DISK_NEWCHANGE is cleared on + * each seek. If a disk is present, the disk change line should also be + * cleared on each seek. Thus, if FD_DISK_NEWCHANGE is clear, but the disk + * change line is set, this means either that no disk is in the drive, or + * that it has been removed since the last seek. + * + * This means that we really have a third possibility too: + * The floppy has been changed after the last seek. + */ + +static int disk_change(int drive) +{ + int fdc=FDC(drive); +#ifdef FLOPPY_SANITY_CHECK + if (jiffies - UDRS->select_date < UDP->select_delay) + DPRINT("WARNING disk change called early\n"); + if (!(FDCS->dor & (0x10 << UNIT(drive))) || + (FDCS->dor & 3) != UNIT(drive) || + fdc != FDC(drive)){ + DPRINT("probing disk change on unselected drive\n"); + DPRINT("drive=%d fdc=%d dor=%x\n",drive, FDC(drive), + FDCS->dor); + } +#endif + +#ifdef DCL_DEBUG + if (UDP->flags & FD_DEBUG){ + DPRINT("checking disk change line for drive %d\n",drive); + DPRINT("jiffies=%ld\n", jiffies); + DPRINT("disk change line=%x\n",fd_inb(FD_DIR)&0x80); + DPRINT("flags=%x\n",UDRS->flags); + } +#endif + if (UDP->flags & FD_BROKEN_DCL) + return UTESTF(FD_DISK_CHANGED); + if ((fd_inb(FD_DIR) ^ UDP->flags) & 0x80){ + USETF(FD_VERIFY); /* verify write protection */ + if (UDRS->maxblock){ + /* mark it changed */ + USETF(FD_DISK_CHANGED); + } + + /* invalidate its geometry */ + if (UDRS->keep_data >= 0) { + if ((UDP->flags & FTD_MSG) && + current_type[drive] != NULL) + DPRINT("Disk type is undefined after " + "disk change\n"); + current_type[drive] = NULL; + floppy_sizes[TOMINOR(drive)] = MAX_DISK_SIZE; + } + + /*USETF(FD_DISK_NEWCHANGE);*/ + return 1; + } else { + UDRS->last_checked=jiffies; + UCLEARF(FD_DISK_NEWCHANGE); + } + return 0; +} + +static inline int is_selected(int dor, int unit) +{ + return ((dor & (0x10 << unit)) && (dor &3) == unit); +} + +static int set_dor(int fdc, char mask, char data) +{ + register unsigned char drive, unit, newdor,olddor; + + if (FDCS->address == -1) + return -1; + + olddor = FDCS->dor; + newdor = (olddor & mask) | data; + if (newdor != olddor){ + unit = olddor & 0x3; + if (is_selected(olddor, unit) && !is_selected(newdor,unit)){ + drive = REVDRIVE(fdc,unit); +#ifdef DCL_DEBUG + if (UDP->flags & FD_DEBUG){ + DPRINT("calling disk change from set_dor\n"); + } +#endif + disk_change(drive); + } + FDCS->dor = newdor; + fd_outb(newdor, FD_DOR); + + unit = newdor & 0x3; + if (!is_selected(olddor, unit) && is_selected(newdor,unit)){ + drive = REVDRIVE(fdc,unit); + UDRS->select_date = jiffies; + } + } + + /* FIXME: we should be more graceful here */ + + if (newdor & FLOPPY_MOTOR_MASK) + floppy_grab_irq_and_dma(); + if (olddor & FLOPPY_MOTOR_MASK) + floppy_release_irq_and_dma(); + return olddor; +} + +static void twaddle(void) +{ + if (DP->select_delay) + return; + fd_outb(FDCS->dor & ~(0x10<dor, FD_DOR); + DRS->select_date = jiffies; +} + +/* reset all driver information about the current fdc. This is needed after + * a reset, and after a raw command. */ +static void reset_fdc_info(int mode) +{ + int drive; + + FDCS->spec1 = FDCS->spec2 = -1; + FDCS->need_configure = 1; + FDCS->perp_mode = 1; + FDCS->rawcmd = 0; + for (drive = 0; drive < N_DRIVE; drive++) + if (FDC(drive) == fdc && + (mode || UDRS->track != NEED_1_RECAL)) + UDRS->track = NEED_2_RECAL; +} + +/* selects the fdc and drive, and enables the fdc's input/dma. */ +static void set_fdc(int drive) +{ + if (drive >= 0 && drive < N_DRIVE){ + fdc = FDC(drive); + current_drive = drive; + } + if (fdc != 1 && fdc != 0) { + printk("bad fdc value\n"); + return; + } + set_dor(fdc,~0,8); +#if N_FDC > 1 + set_dor(1-fdc, ~8, 0); +#endif + if (FDCS->rawcmd == 2) + reset_fdc_info(1); + if (fd_inb(FD_STATUS) != STATUS_READY) + FDCS->reset = 1; +} + +/* locks the driver */ +static int lock_fdc(int drive, int interruptible) +{ + unsigned long flags; + + if (!usage_count){ + printk(KERN_ERR "trying to lock fdc while usage count=0\n"); + return -1; + } + if(floppy_grab_irq_and_dma()==-1) + return -EBUSY; + INT_OFF; + while (fdc_busy && NO_SIGNAL) + interruptible_sleep_on(&fdc_wait); + if (fdc_busy){ + INT_ON; + return -EINTR; + } + fdc_busy = 1; + INT_ON; + command_status = FD_COMMAND_NONE; + reschedule_timeout(drive, "lock fdc", 0); + set_fdc(drive); + return 0; +} + +#define LOCK_FDC(drive,interruptible) \ +if (lock_fdc(drive,interruptible)) return -EINTR; + + +/* unlocks the driver */ +static inline void unlock_fdc(void) +{ + raw_cmd = 0; + if (!fdc_busy) + DPRINT("FDC access conflict!\n"); + + if (DEVICE_INTR) + DPRINT("device interrupt still active at FDC release: %p!\n", + DEVICE_INTR); + command_status = FD_COMMAND_NONE; + del_timer(&fd_timeout); + cont = NULL; + fdc_busy = 0; + floppy_release_irq_and_dma(); + wake_up(&fdc_wait); +} + +/* switches the motor off after a given timeout */ +static void motor_off_callback(unsigned long nr) +{ + unsigned char mask = ~(0x10 << UNIT(nr)); + + set_dor(FDC(nr), mask, 0); +} + +static struct timer_list motor_off_timer[N_DRIVE] = { + { NULL, NULL, 0, 0, motor_off_callback }, + { NULL, NULL, 0, 1, motor_off_callback }, + { NULL, NULL, 0, 2, motor_off_callback }, + { NULL, NULL, 0, 3, motor_off_callback }, + { NULL, NULL, 0, 4, motor_off_callback }, + { NULL, NULL, 0, 5, motor_off_callback }, + { NULL, NULL, 0, 6, motor_off_callback }, + { NULL, NULL, 0, 7, motor_off_callback } +}; + +/* schedules motor off */ +static void floppy_off(unsigned int drive) +{ + unsigned long volatile delta; + register int fdc=FDC(drive); + + if (!(FDCS->dor & (0x10 << UNIT(drive)))) + return; + + del_timer(motor_off_timer+drive); + + /* make spindle stop in a position which minimizes spinup time + * next time */ + if (UDP->rps){ + delta = jiffies - UDRS->first_read_date + HZ - + UDP->spindown_offset; + delta = ((delta * UDP->rps) % HZ) / UDP->rps; + motor_off_timer[drive].expires = jiffies + UDP->spindown - delta; + } + add_timer(motor_off_timer+drive); +} + +/* + * cycle through all N_DRIVE floppy drives, for disk change testing. + * stopping at current drive. This is done before any long operation, to + * be sure to have up to date disk change information. + */ +static void scandrives(void) +{ + int i, drive, saved_drive; + + if (DP->select_delay) + return; + + saved_drive = current_drive; + for (i=0; i < N_DRIVE; i++){ + drive = (saved_drive + i + 1) % N_DRIVE; + if (UDRS->fd_ref == 0 || UDP->select_delay != 0) + continue; /* skip closed drives */ + set_fdc(drive); + if (!(set_dor(fdc, ~3, UNIT(drive) | (0x10 << UNIT(drive))) & + (0x10 << UNIT(drive)))) + /* switch the motor off again, if it was off to + * begin with */ + set_dor(fdc, ~(0x10 << UNIT(drive)), 0); + } + set_fdc(saved_drive); +} + +static void empty(void) +{ +} + +static struct tq_struct floppy_tq = +{ 0, 0, 0, 0 }; + +static struct timer_list fd_timer ={ NULL, NULL, 0, 0, 0 }; + +static void cancel_activity(void) +{ + CLEAR_INTR; + floppy_tq.routine = (void *)(void *) empty; + del_timer(&fd_timer); +} + +/* this function makes sure that the disk stays in the drive during the + * transfer */ +static void fd_watchdog(void) +{ +#ifdef DCL_DEBUG + if (DP->flags & FD_DEBUG){ + DPRINT("calling disk change from watchdog\n"); + } +#endif + + if (disk_change(current_drive)){ + DPRINT("disk removed during i/o\n"); + cancel_activity(); + cont->done(0); + reset_fdc(); + } else { + del_timer(&fd_timer); + fd_timer.function = (timeout_fn) fd_watchdog; + fd_timer.expires = jiffies + HZ / 10; + add_timer(&fd_timer); + } +} + +static void main_command_interrupt(void) +{ + del_timer(&fd_timer); + cont->interrupt(); +} + +/* waits for a delay (spinup or select) to pass */ +static int wait_for_completion(int delay, timeout_fn function) +{ + if (FDCS->reset){ + reset_fdc(); /* do the reset during sleep to win time + * if we don't need to sleep, it's a good + * occasion anyways */ + return 1; + } + + if ((signed) (jiffies - delay) < 0){ + del_timer(&fd_timer); + fd_timer.function = function; + fd_timer.expires = delay; + add_timer(&fd_timer); + return 1; + } + return 0; +} + +static int hlt_disabled=0; +static void floppy_disable_hlt(void) +{ + unsigned long flags; + + INT_OFF; + if (!hlt_disabled){ + hlt_disabled=1; +#ifdef HAVE_DISABLE_HLT + disable_hlt(); +#endif + } + INT_ON; +} + +static void floppy_enable_hlt(void) +{ + unsigned long flags; + + INT_OFF; + if (hlt_disabled){ + hlt_disabled=0; +#ifdef HAVE_DISABLE_HLT + enable_hlt(); +#endif + } + INT_ON; +} + + +static void setup_DMA(void) +{ + unsigned long flags; + +#ifdef FLOPPY_SANITY_CHECK + if (raw_cmd->length == 0){ + int i; + + printk("zero dma transfer size:"); + for (i=0; i < raw_cmd->cmd_count; i++) + printk("%x,", raw_cmd->cmd[i]); + printk("\n"); + cont->done(0); + FDCS->reset = 1; + return; + } + if ((long) raw_cmd->kernel_data % 512){ + printk("non aligned address: %p\n", raw_cmd->kernel_data); + cont->done(0); + FDCS->reset=1; + return; + } + if (CROSS_64KB(raw_cmd->kernel_data, raw_cmd->length)) { + printk("DMA crossing 64-K boundary %p-%p\n", + raw_cmd->kernel_data, + raw_cmd->kernel_data + raw_cmd->length); + cont->done(0); + FDCS->reset=1; + return; + } +#endif + INT_OFF; + fd_disable_dma(); + fd_clear_dma_ff(); + fd_cacheflush(raw_cmd->kernel_data, raw_cmd->length); + fd_set_dma_mode((raw_cmd->flags & FD_RAW_READ)? + DMA_MODE_READ : DMA_MODE_WRITE); + fd_set_dma_addr(virt_to_bus(raw_cmd->kernel_data)); + fd_set_dma_count(raw_cmd->length); + virtual_dma_port = FDCS->address; + fd_enable_dma(); + INT_ON; + floppy_disable_hlt(); +} + +void show_floppy(void); + +/* waits until the fdc becomes ready */ +static int wait_til_ready(void) +{ + int counter, status; + if(FDCS->reset) + return -1; + for (counter = 0; counter < 10000; counter++) { + status = fd_inb(FD_STATUS); + if (status & STATUS_READY) + return status; + } + if (!initialising) { + DPRINT("Getstatus times out (%x) on fdc %d\n", + status, fdc); + show_floppy(); + } + FDCS->reset = 1; + return -1; +} + +/* sends a command byte to the fdc */ +static int output_byte(char byte) +{ + int status; + + if ((status = wait_til_ready()) < 0) + return -1; + if ((status & (STATUS_READY|STATUS_DIR|STATUS_DMA)) == STATUS_READY){ + fd_outb(byte,FD_DATA); +#ifdef FLOPPY_SANITY_CHECK + output_log[output_log_pos].data = byte; + output_log[output_log_pos].status = status; + output_log[output_log_pos].jiffies = jiffies; + output_log_pos = (output_log_pos + 1) % OLOGSIZE; +#endif + return 0; + } + FDCS->reset = 1; + if (!initialising) { + DPRINT("Unable to send byte %x to FDC. Fdc=%x Status=%x\n", + byte, fdc, status); + show_floppy(); + } + return -1; +} +#define LAST_OUT(x) if (output_byte(x)<0){ reset_fdc();return;} + +/* gets the response from the fdc */ +static int result(void) +{ + int i, status; + + for(i=0; i < MAX_REPLIES; i++) { + if ((status = wait_til_ready()) < 0) + break; + status &= STATUS_DIR|STATUS_READY|STATUS_BUSY|STATUS_DMA; + if ((status & ~STATUS_BUSY) == STATUS_READY){ +#ifdef FLOPPY_SANITY_CHECK + resultjiffies = jiffies; + resultsize = i; +#endif + return i; + } + if (status == (STATUS_DIR|STATUS_READY|STATUS_BUSY)) + reply_buffer[i] = fd_inb(FD_DATA); + else + break; + } + if(!initialising) { + DPRINT("get result error. Fdc=%d Last status=%x Read bytes=%d\n", + fdc, status, i); + show_floppy(); + } + FDCS->reset = 1; + return -1; +} + +#define MORE_OUTPUT -2 +/* does the fdc need more output? */ +static int need_more_output(void) +{ + int status; + if( (status = wait_til_ready()) < 0) + return -1; + if ((status & (STATUS_READY|STATUS_DIR|STATUS_DMA)) == STATUS_READY) + return MORE_OUTPUT; + return result(); +} + +/* Set perpendicular mode as required, based on data rate, if supported. + * 82077 Now tested. 1Mbps data rate only possible with 82077-1. + */ +static inline void perpendicular_mode(void) +{ + unsigned char perp_mode; + + if (raw_cmd->rate & 0x40){ + switch(raw_cmd->rate & 3){ + case 0: + perp_mode=2; + break; + case 3: + perp_mode=3; + break; + default: + DPRINT("Invalid data rate for perpendicular mode!\n"); + cont->done(0); + FDCS->reset = 1; /* convenient way to return to + * redo without to much hassle (deep + * stack et al. */ + return; + } + } else + perp_mode = 0; + + if (FDCS->perp_mode == perp_mode) + return; + if (FDCS->version >= FDC_82077_ORIG) { + output_byte(FD_PERPENDICULAR); + output_byte(perp_mode); + FDCS->perp_mode = perp_mode; + } else if (perp_mode) { + DPRINT("perpendicular mode not supported by this FDC.\n"); + } +} /* perpendicular_mode */ + +static int fifo_depth = 0xa; +static int no_fifo = 0; + +static int fdc_configure(void) +{ + /* Turn on FIFO */ + output_byte(FD_CONFIGURE); + if(need_more_output() != MORE_OUTPUT) + return 0; + output_byte(0); + output_byte(0x10 | (no_fifo & 0x20) | (fifo_depth & 0xf)); + output_byte(0); /* pre-compensation from track + 0 upwards */ + return 1; +} + +#define NOMINAL_DTR 500 + +/* Issue a "SPECIFY" command to set the step rate time, head unload time, + * head load time, and DMA disable flag to values needed by floppy. + * + * The value "dtr" is the data transfer rate in Kbps. It is needed + * to account for the data rate-based scaling done by the 82072 and 82077 + * FDC types. This parameter is ignored for other types of FDCs (i.e. + * 8272a). + * + * Note that changing the data transfer rate has a (probably deleterious) + * effect on the parameters subject to scaling for 82072/82077 FDCs, so + * fdc_specify is called again after each data transfer rate + * change. + * + * srt: 1000 to 16000 in microseconds + * hut: 16 to 240 milliseconds + * hlt: 2 to 254 milliseconds + * + * These values are rounded up to the next highest available delay time. + */ +static void fdc_specify(void) +{ + unsigned char spec1, spec2; + int srt, hlt, hut; + unsigned long dtr = NOMINAL_DTR; + unsigned long scale_dtr = NOMINAL_DTR; + int hlt_max_code = 0x7f; + int hut_max_code = 0xf; + + if (FDCS->need_configure && FDCS->version >= FDC_82072A) { + fdc_configure(); + FDCS->need_configure = 0; + /*DPRINT("FIFO enabled\n");*/ + } + + switch (raw_cmd->rate & 0x03) { + case 3: + dtr = 1000; + break; + case 1: + dtr = 300; + if (FDCS->version >= FDC_82078) { + /* chose the default rate table, not the one + * where 1 = 2 Mbps */ + output_byte(FD_DRIVESPEC); + if(need_more_output() == MORE_OUTPUT) { + output_byte(UNIT(current_drive)); + output_byte(0xc0); + } + } + break; + case 2: + dtr = 250; + break; + } + + if (FDCS->version >= FDC_82072) { + scale_dtr = dtr; + hlt_max_code = 0x00; /* 0==256msec*dtr0/dtr (not linear!) */ + hut_max_code = 0x0; /* 0==256msec*dtr0/dtr (not linear!) */ + } + + /* Convert step rate from microseconds to milliseconds and 4 bits */ + srt = 16 - (DP->srt*scale_dtr/1000 + NOMINAL_DTR - 1)/NOMINAL_DTR; + SUPBOUND(srt, 0xf); + INFBOUND(srt, 0); + + hlt = (DP->hlt*scale_dtr/2 + NOMINAL_DTR - 1)/NOMINAL_DTR; + if (hlt < 0x01) + hlt = 0x01; + else if (hlt > 0x7f) + hlt = hlt_max_code; + + hut = (DP->hut*scale_dtr/16 + NOMINAL_DTR - 1)/NOMINAL_DTR; + if (hut < 0x1) + hut = 0x1; + else if (hut > 0xf) + hut = hut_max_code; + + spec1 = (srt << 4) | hut; + spec2 = (hlt << 1) | (use_virtual_dma & 1); + + /* If these parameters did not change, just return with success */ + if (FDCS->spec1 != spec1 || FDCS->spec2 != spec2) { + /* Go ahead and set spec1 and spec2 */ + output_byte(FD_SPECIFY); + output_byte(FDCS->spec1 = spec1); + output_byte(FDCS->spec2 = spec2); + } +} /* fdc_specify */ + +/* Set the FDC's data transfer rate on behalf of the specified drive. + * NOTE: with 82072/82077 FDCs, changing the data rate requires a reissue + * of the specify command (i.e. using the fdc_specify function). + */ +static int fdc_dtr(void) +{ + /* If data rate not already set to desired value, set it. */ + if ((raw_cmd->rate & 3) == FDCS->dtr) + return 0; + + /* Set dtr */ + fd_outb(raw_cmd->rate & 3, FD_DCR); + + /* TODO: some FDC/drive combinations (C&T 82C711 with TEAC 1.2MB) + * need a stabilization period of several milliseconds to be + * enforced after data rate changes before R/W operations. + * Pause 5 msec to avoid trouble. (Needs to be 2 jiffies) + */ + FDCS->dtr = raw_cmd->rate & 3; + return(wait_for_completion(jiffies+2*HZ/100, + (timeout_fn) floppy_ready)); +} /* fdc_dtr */ + +static void tell_sector(void) +{ + printk(": track %d, head %d, sector %d, size %d", + R_TRACK, R_HEAD, R_SECTOR, R_SIZECODE); +} /* tell_sector */ + + +/* + * OK, this error interpreting routine is called after a + * DMA read/write has succeeded + * or failed, so we check the results, and copy any buffers. + * hhb: Added better error reporting. + * ak: Made this into a separate routine. + */ +static int interpret_errors(void) +{ + char bad; + + if (inr!=7) { + DPRINT("-- FDC reply error"); + FDCS->reset = 1; + return 1; + } + + /* check IC to find cause of interrupt */ + switch (ST0 & ST0_INTR) { + case 0x40: /* error occurred during command execution */ + if (ST1 & ST1_EOC) + return 0; /* occurs with pseudo-DMA */ + bad = 1; + if (ST1 & ST1_WP) { + DPRINT("Drive is write protected\n"); + CLEARF(FD_DISK_WRITABLE); + cont->done(0); + bad = 2; + } else if (ST1 & ST1_ND) { + SETF(FD_NEED_TWADDLE); + } else if (ST1 & ST1_OR) { + if (DP->flags & FTD_MSG) + DPRINT("Over/Underrun - retrying\n"); + bad = 0; + }else if (*errors >= DP->max_errors.reporting){ + DPRINT(""); + if (ST0 & ST0_ECE) { + printk("Recalibrate failed!"); + } else if (ST2 & ST2_CRC) { + printk("data CRC error"); + tell_sector(); + } else if (ST1 & ST1_CRC) { + printk("CRC error"); + tell_sector(); + } else if ((ST1 & (ST1_MAM|ST1_ND)) || (ST2 & ST2_MAM)) { + if (!probing) { + printk("sector not found"); + tell_sector(); + } else + printk("probe failed..."); + } else if (ST2 & ST2_WC) { /* seek error */ + printk("wrong cylinder"); + } else if (ST2 & ST2_BC) { /* cylinder marked as bad */ + printk("bad cylinder"); + } else { + printk("unknown error. ST[0..2] are: 0x%x 0x%x 0x%x", ST0, ST1, ST2); + tell_sector(); + } + printk("\n"); + + } + if (ST2 & ST2_WC || ST2 & ST2_BC) + /* wrong cylinder => recal */ + DRS->track = NEED_2_RECAL; + return bad; + case 0x80: /* invalid command given */ + DPRINT("Invalid FDC command given!\n"); + cont->done(0); + return 2; + case 0xc0: + DPRINT("Abnormal termination caused by polling\n"); + cont->error(); + return 2; + default: /* (0) Normal command termination */ + return 0; + } +} + +/* + * This routine is called when everything should be correctly set up + * for the transfer (i.e. floppy motor is on, the correct floppy is + * selected, and the head is sitting on the right track). + */ +static void setup_rw_floppy(void) +{ + int i, ready_date, r, flags; + timeout_fn function; + + flags = raw_cmd->flags; + if (flags & (FD_RAW_READ | FD_RAW_WRITE)) + flags |= FD_RAW_INTR; + + if ((flags & FD_RAW_SPIN) && !(flags & FD_RAW_NO_MOTOR)){ + ready_date = DRS->spinup_date + DP->spinup; + /* If spinup will take a long time, rerun scandrives + * again just before spinup completion. Beware that + * after scandrives, we must again wait for selection. + */ + if ((signed) (ready_date - jiffies) > DP->select_delay){ + ready_date -= DP->select_delay; + function = (timeout_fn) floppy_start; + } else + function = (timeout_fn) setup_rw_floppy; + + /* wait until the floppy is spinning fast enough */ + if (wait_for_completion(ready_date,function)) + return; + } + + if ((flags & FD_RAW_READ) || (flags & FD_RAW_WRITE)) + setup_DMA(); + + if (flags & FD_RAW_INTR) + SET_INTR(main_command_interrupt); + + r=0; + for (i=0; i< raw_cmd->cmd_count; i++) + r|=output_byte(raw_cmd->cmd[i]); + +#ifdef DEBUGT + debugt("rw_command: "); +#endif + if (r){ + cont->error(); + reset_fdc(); + return; + } + + if (!(flags & FD_RAW_INTR)){ + inr = result(); + cont->interrupt(); + } else if (flags & FD_RAW_NEED_DISK) + fd_watchdog(); +} + +static int blind_seek; + +/* + * This is the routine called after every seek (or recalibrate) interrupt + * from the floppy controller. + */ +static void seek_interrupt(void) +{ +#ifdef DEBUGT + debugt("seek interrupt:"); +#endif + if (inr != 2 || (ST0 & 0xF8) != 0x20) { + DPRINT("seek failed\n"); + DRS->track = NEED_2_RECAL; + cont->error(); + cont->redo(); + return; + } + if (DRS->track >= 0 && DRS->track != ST1 && !blind_seek){ +#ifdef DCL_DEBUG + if (DP->flags & FD_DEBUG){ + DPRINT("clearing NEWCHANGE flag because of effective seek\n"); + DPRINT("jiffies=%ld\n", jiffies); + } +#endif + CLEARF(FD_DISK_NEWCHANGE); /* effective seek */ + DRS->select_date = jiffies; + } + DRS->track = ST1; + floppy_ready(); +} + +static void check_wp(void) +{ + if (TESTF(FD_VERIFY)) { + /* check write protection */ + output_byte(FD_GETSTATUS); + output_byte(UNIT(current_drive)); + if (result() != 1){ + FDCS->reset = 1; + return; + } + CLEARF(FD_VERIFY); + CLEARF(FD_NEED_TWADDLE); +#ifdef DCL_DEBUG + if (DP->flags & FD_DEBUG){ + DPRINT("checking whether disk is write protected\n"); + DPRINT("wp=%x\n",ST3 & 0x40); + } +#endif + if (!(ST3 & 0x40)) + SETF(FD_DISK_WRITABLE); + else + CLEARF(FD_DISK_WRITABLE); + } +} + +static void seek_floppy(void) +{ + int track; + + blind_seek=0; + +#ifdef DCL_DEBUG + if (DP->flags & FD_DEBUG){ + DPRINT("calling disk change from seek\n"); + } +#endif + + if (!TESTF(FD_DISK_NEWCHANGE) && + disk_change(current_drive) && + (raw_cmd->flags & FD_RAW_NEED_DISK)){ + /* the media changed flag should be cleared after the seek. + * If it isn't, this means that there is really no disk in + * the drive. + */ + SETF(FD_DISK_CHANGED); + cont->done(0); + cont->redo(); + return; + } + if (DRS->track <= NEED_1_RECAL){ + recalibrate_floppy(); + return; + } else if (TESTF(FD_DISK_NEWCHANGE) && + (raw_cmd->flags & FD_RAW_NEED_DISK) && + (DRS->track <= NO_TRACK || DRS->track == raw_cmd->track)) { + /* we seek to clear the media-changed condition. Does anybody + * know a more elegant way, which works on all drives? */ + if (raw_cmd->track) + track = raw_cmd->track - 1; + else { + if (DP->flags & FD_SILENT_DCL_CLEAR){ + set_dor(fdc, ~(0x10 << UNIT(current_drive)), 0); + blind_seek = 1; + raw_cmd->flags |= FD_RAW_NEED_SEEK; + } + track = 1; + } + } else { + check_wp(); + if (raw_cmd->track != DRS->track && + (raw_cmd->flags & FD_RAW_NEED_SEEK)) + track = raw_cmd->track; + else { + setup_rw_floppy(); + return; + } + } + + SET_INTR(seek_interrupt); + output_byte(FD_SEEK); + output_byte(UNIT(current_drive)); + LAST_OUT(track); +#ifdef DEBUGT + debugt("seek command:"); +#endif +} + +static void recal_interrupt(void) +{ +#ifdef DEBUGT + debugt("recal interrupt:"); +#endif + if (inr !=2) + FDCS->reset = 1; + else if (ST0 & ST0_ECE) { + switch(DRS->track){ + case NEED_1_RECAL: +#ifdef DEBUGT + debugt("recal interrupt need 1 recal:"); +#endif + /* after a second recalibrate, we still haven't + * reached track 0. Probably no drive. Raise an + * error, as failing immediately might upset + * computers possessed by the Devil :-) */ + cont->error(); + cont->redo(); + return; + case NEED_2_RECAL: +#ifdef DEBUGT + debugt("recal interrupt need 2 recal:"); +#endif + /* If we already did a recalibrate, + * and we are not at track 0, this + * means we have moved. (The only way + * not to move at recalibration is to + * be already at track 0.) Clear the + * new change flag */ +#ifdef DCL_DEBUG + if (DP->flags & FD_DEBUG){ + DPRINT("clearing NEWCHANGE flag because of second recalibrate\n"); + } +#endif + + CLEARF(FD_DISK_NEWCHANGE); + DRS->select_date = jiffies; + /* fall through */ + default: +#ifdef DEBUGT + debugt("recal interrupt default:"); +#endif + /* Recalibrate moves the head by at + * most 80 steps. If after one + * recalibrate we don't have reached + * track 0, this might mean that we + * started beyond track 80. Try + * again. */ + DRS->track = NEED_1_RECAL; + break; + } + } else + DRS->track = ST1; + floppy_ready(); +} + +static void print_result(char *message, int inr) +{ + int i; + + DPRINT("%s ", message); + if (inr >= 0) + for (i=0; i= N_FDC || FDCS->address == -1){ + /* we don't even know which FDC is the culprit */ + printk("DOR0=%x\n", fdc_state[0].dor); + printk("floppy interrupt on bizarre fdc %d\n",fdc); + printk("handler=%p\n", handler); + is_alive("bizarre fdc"); + return; + } + + FDCS->reset = 0; + /* We have to clear the reset flag here, because apparently on boxes + * with level triggered interrupts (PS/2, Sparc, ...), it is needed to + * emit SENSEI's to clear the interrupt line. And FDCS->reset blocks the + * emission of the SENSEI's. + * It is OK to emit floppy commands because we are in an interrupt + * handler here, and thus we have to fear no interference of other + * activity. + */ + + do_print = !handler && print_unex && !initialising; + + inr = result(); + if(do_print) + print_result("unexpected interrupt", inr); + if (inr == 0){ + int max_sensei = 4; + do { + output_byte(FD_SENSEI); + inr = result(); + if(do_print) + print_result("sensei", inr); + max_sensei--; + } while ((ST0 & 0x83) != UNIT(current_drive) && inr == 2 && max_sensei); + } + if (handler) { + if(intr_count >= 2) { + /* expected interrupt */ + floppy_tq.routine = (void *)(void *) handler; + queue_task_irq(&floppy_tq, &tq_immediate); + mark_bh(IMMEDIATE_BH); + } else + handler(); + } else + FDCS->reset = 1; + is_alive("normal interrupt end"); +} + +static void recalibrate_floppy(void) +{ +#ifdef DEBUGT + debugt("recalibrate floppy:"); +#endif + SET_INTR(recal_interrupt); + output_byte(FD_RECALIBRATE); + LAST_OUT(UNIT(current_drive)); +} + +/* + * Must do 4 FD_SENSEIs after reset because of ``drive polling''. + */ +static void reset_interrupt(void) +{ +#ifdef DEBUGT + debugt("reset interrupt:"); +#endif + result(); /* get the status ready for set_fdc */ + if (FDCS->reset) { + printk("reset set in interrupt, calling %p\n", cont->error); + cont->error(); /* a reset just after a reset. BAD! */ + } + cont->redo(); +} + +/* + * reset is done by pulling bit 2 of DOR low for a while (old FDCs), + * or by setting the self clearing bit 7 of STATUS (newer FDCs) + */ +static void reset_fdc(void) +{ + SET_INTR(reset_interrupt); + FDCS->reset = 0; + reset_fdc_info(0); + + /* Pseudo-DMA may intercept 'reset finished' interrupt. */ + /* Irrelevant for systems with true DMA (i386). */ + fd_disable_dma(); + + if (FDCS->version >= FDC_82072A) + fd_outb(0x80 | (FDCS->dtr &3), FD_STATUS); + else { + fd_outb(FDCS->dor & ~0x04, FD_DOR); + udelay(FD_RESET_DELAY); + fd_outb(FDCS->dor, FD_DOR); + } +} + +void show_floppy(void) +{ + int i; + + printk("\n"); + printk("floppy driver state\n"); + printk("-------------------\n"); + printk("now=%ld last interrupt=%d last called handler=%p\n", + jiffies, interruptjiffies, lasthandler); + + +#ifdef FLOPPY_SANITY_CHECK + printk("timeout_message=%s\n", timeout_message); + printk("last output bytes:\n"); + for (i=0; i < OLOGSIZE; i++) + printk("%2x %2x %ld\n", + output_log[(i+output_log_pos) % OLOGSIZE].data, + output_log[(i+output_log_pos) % OLOGSIZE].status, + output_log[(i+output_log_pos) % OLOGSIZE].jiffies); + printk("last result at %d\n", resultjiffies); + printk("last redo_fd_request at %d\n", lastredo); + for (i=0; ireset = 1; + if (cont){ + cont->done(0); + cont->redo(); /* this will recall reset when needed */ + } else { + printk("no cont in shutdown!\n"); + process_fd_request(); + } + is_alive("floppy shutdown"); +} +/*typedef void (*timeout_fn)(unsigned long);*/ + +/* start motor, check media-changed condition and write protection */ +static int start_motor(void (*function)(void) ) +{ + int mask, data; + + mask = 0xfc; + data = UNIT(current_drive); + if (!(raw_cmd->flags & FD_RAW_NO_MOTOR)){ + if (!(FDCS->dor & (0x10 << UNIT(current_drive)))){ + set_debugt(); + /* no read since this drive is running */ + DRS->first_read_date = 0; + /* note motor start time if motor is not yet running */ + DRS->spinup_date = jiffies; + data |= (0x10 << UNIT(current_drive)); + } + } else + if (FDCS->dor & (0x10 << UNIT(current_drive))) + mask &= ~(0x10 << UNIT(current_drive)); + + /* starts motor and selects floppy */ + del_timer(motor_off_timer + current_drive); + set_dor(fdc, mask, data); + + /* wait_for_completion also schedules reset if needed. */ + return(wait_for_completion(DRS->select_date+DP->select_delay, + (timeout_fn) function)); +} + +static void floppy_ready(void) +{ + CHECK_RESET; + if (start_motor(floppy_ready)) return; + if (fdc_dtr()) return; + +#ifdef DCL_DEBUG + if (DP->flags & FD_DEBUG){ + DPRINT("calling disk change from floppy_ready\n"); + } +#endif + + if (!(raw_cmd->flags & FD_RAW_NO_MOTOR) && + disk_change(current_drive) && + !DP->select_delay) + twaddle(); /* this clears the dcl on certain drive/controller + * combinations */ + + if (raw_cmd->flags & (FD_RAW_NEED_SEEK | FD_RAW_NEED_DISK)){ + perpendicular_mode(); + fdc_specify(); /* must be done here because of hut, hlt ... */ + seek_floppy(); + } else + setup_rw_floppy(); +} + +static void floppy_start(void) +{ + reschedule_timeout(CURRENTD, "floppy start", 0); + + scandrives(); +#ifdef DCL_DEBUG + if (DP->flags & FD_DEBUG){ + DPRINT("setting NEWCHANGE in floppy_start\n"); + } +#endif + SETF(FD_DISK_NEWCHANGE); + floppy_ready(); +} + +/* + * ======================================================================== + * here ends the bottom half. Exported routines are: + * floppy_start, floppy_off, floppy_ready, lock_fdc, unlock_fdc, set_fdc, + * start_motor, reset_fdc, reset_fdc_info, interpret_errors. + * Initialization also uses output_byte, result, set_dor, floppy_interrupt + * and set_dor. + * ======================================================================== + */ +/* + * General purpose continuations. + * ============================== + */ + +static void do_wakeup(void) +{ + reschedule_timeout(MAXTIMEOUT, "do wakeup", 0); + cont = 0; + command_status += 2; + wake_up(&command_done); +} + +static struct cont_t wakeup_cont={ + empty, + do_wakeup, + empty, + (done_f)empty +}; + + +static struct cont_t intr_cont={ + empty, + process_fd_request, + empty, + (done_f) empty +}; + +static int wait_til_done(void (*handler)(void), int interruptible) +{ + int ret; + unsigned long flags; + + floppy_tq.routine = (void *)(void *) handler; + queue_task(&floppy_tq, &tq_immediate); + mark_bh(IMMEDIATE_BH); + INT_OFF; + while(command_status < 2 && NO_SIGNAL){ + is_alive("wait_til_done"); + if (interruptible) + interruptible_sleep_on(&command_done); + else + sleep_on(&command_done); + } + if (command_status < 2){ + cancel_activity(); + cont = &intr_cont; + reset_fdc(); + INT_ON; + return -EINTR; + } + INT_ON; + + if (FDCS->reset) + command_status = FD_COMMAND_ERROR; + if (command_status == FD_COMMAND_OKAY) + ret=0; + else + ret=-EIO; + command_status = FD_COMMAND_NONE; + return ret; +} + +static void generic_done(int result) +{ + command_status = result; + cont = &wakeup_cont; +} + +static void generic_success(void) +{ + cont->done(1); +} + +static void generic_failure(void) +{ + cont->done(0); +} + +static void success_and_wakeup(void) +{ + generic_success(); + cont->redo(); +} + + +/* + * formatting and rw support. + * ========================== + */ + +static int next_valid_format(void) +{ + int probed_format; + + probed_format = DRS->probed_format; + while(1){ + if (probed_format >= 8 || + !DP->autodetect[probed_format]){ + DRS->probed_format = 0; + return 1; + } + if (floppy_type[DP->autodetect[probed_format]].sect){ + DRS->probed_format = probed_format; + return 0; + } + probed_format++; + } +} + +static void bad_flp_intr(void) +{ + if (probing){ + DRS->probed_format++; + if (!next_valid_format()) + return; + } + (*errors)++; + INFBOUND(DRWE->badness, *errors); + if (*errors > DP->max_errors.abort) + cont->done(0); + if (*errors > DP->max_errors.reset) + FDCS->reset = 1; + else if (*errors > DP->max_errors.recal) + DRS->track = NEED_2_RECAL; +} + +static void set_floppy(kdev_t device) +{ + if (TYPE(device)) + _floppy = TYPE(device) + floppy_type; + else + _floppy = current_type[ DRIVE(device) ]; +} + +/* + * formatting support. + * =================== + */ +static void format_interrupt(void) +{ + switch (interpret_errors()){ + case 1: + cont->error(); + case 2: + break; + case 0: + cont->done(1); + } + cont->redo(); +} + +#define CODE2SIZE (ssize = ((1 << SIZECODE) + 3) >> 2) +#define FM_MODE(x,y) ((y) & ~(((x)->rate & 0x80) >>1)) +#define CT(x) ((x) | 0x40) +static void setup_format_params(int track) +{ + struct fparm { + unsigned char track,head,sect,size; + } *here = (struct fparm *)floppy_track_buffer; + int il,n; + int count,head_shift,track_shift; + + raw_cmd = &default_raw_cmd; + raw_cmd->track = track; + + raw_cmd->flags = FD_RAW_WRITE | FD_RAW_INTR | FD_RAW_SPIN | + FD_RAW_NEED_DISK | FD_RAW_NEED_SEEK; + raw_cmd->rate = _floppy->rate & 0x43; + raw_cmd->cmd_count = NR_F; + COMMAND = FM_MODE(_floppy,FD_FORMAT); + DR_SELECT = UNIT(current_drive) + PH_HEAD(_floppy,format_req.head); + F_SIZECODE = FD_SIZECODE(_floppy); + F_SECT_PER_TRACK = _floppy->sect << 2 >> F_SIZECODE; + F_GAP = _floppy->fmt_gap; + F_FILL = FD_FILL_BYTE; + + raw_cmd->kernel_data = floppy_track_buffer; + raw_cmd->length = 4 * F_SECT_PER_TRACK; + + /* allow for about 30ms for data transport per track */ + head_shift = (F_SECT_PER_TRACK + 5) / 6; + + /* a ``cylinder'' is two tracks plus a little stepping time */ + track_shift = 2 * head_shift + 3; + + /* position of logical sector 1 on this track */ + n = (track_shift * format_req.track + head_shift * format_req.head) + % F_SECT_PER_TRACK; + + /* determine interleave */ + il = 1; + if (_floppy->fmt_gap < 0x22) + il++; + + /* initialize field */ + for (count = 0; count < F_SECT_PER_TRACK; ++count) { + here[count].track = format_req.track; + here[count].head = format_req.head; + here[count].sect = 0; + here[count].size = F_SIZECODE; + } + /* place logical sectors */ + for (count = 1; count <= F_SECT_PER_TRACK; ++count) { + here[n].sect = count; + n = (n+il) % F_SECT_PER_TRACK; + if (here[n].sect) { /* sector busy, find next free sector */ + ++n; + if (n>= F_SECT_PER_TRACK) { + n-=F_SECT_PER_TRACK; + while (here[n].sect) ++n; + } + } + } +} + +static void redo_format(void) +{ + buffer_track = -1; + setup_format_params(format_req.track << STRETCH(_floppy)); + floppy_start(); +#ifdef DEBUGT + debugt("queue format request"); +#endif +} + +static struct cont_t format_cont={ + format_interrupt, + redo_format, + bad_flp_intr, + generic_done }; + +static int do_format(kdev_t device, struct format_descr *tmp_format_req) +{ + int ret; + int drive=DRIVE(device); + + LOCK_FDC(drive,1); + set_floppy(device); + if (!_floppy || + _floppy->track > DP->tracks || + tmp_format_req->track >= _floppy->track || + tmp_format_req->head >= _floppy->head || + (_floppy->sect << 2) % (1 << FD_SIZECODE(_floppy)) || + !_floppy->fmt_gap) { + process_fd_request(); + return -EINVAL; + } + format_req = *tmp_format_req; + format_errors = 0; + cont = &format_cont; + errors = &format_errors; + IWAIT(redo_format); + process_fd_request(); + return ret; +} + +/* + * Buffer read/write and support + * ============================= + */ + +/* new request_done. Can handle physical sectors which are smaller than a + * logical buffer */ +static void request_done(int uptodate) +{ + int block; + + probing = 0; + reschedule_timeout(MAXTIMEOUT, "request done %d", uptodate); + + if (!CURRENT){ + DPRINT("request list destroyed in floppy request done\n"); + return; + } + + if (uptodate){ + /* maintain values for invalidation on geometry + * change */ + block = current_count_sectors + CURRENT->sector; + INFBOUND(DRS->maxblock, block); + if (block > _floppy->sect) + DRS->maxtrack = 1; + + /* unlock chained buffers */ + while (current_count_sectors && CURRENT && + current_count_sectors >= CURRENT->current_nr_sectors){ + current_count_sectors -= CURRENT->current_nr_sectors; + CURRENT->nr_sectors -= CURRENT->current_nr_sectors; + CURRENT->sector += CURRENT->current_nr_sectors; + end_request(1); + } + if (current_count_sectors && CURRENT){ + /* "unlock" last subsector */ + CURRENT->buffer += current_count_sectors <<9; + CURRENT->current_nr_sectors -= current_count_sectors; + CURRENT->nr_sectors -= current_count_sectors; + CURRENT->sector += current_count_sectors; + return; + } + + if (current_count_sectors && !CURRENT) + DPRINT("request list destroyed in floppy request done\n"); + + } else { + if (CURRENT->cmd == WRITE) { + /* record write error information */ + DRWE->write_errors++; + if (DRWE->write_errors == 1) { + DRWE->first_error_sector = CURRENT->sector; + DRWE->first_error_generation = DRS->generation; + } + DRWE->last_error_sector = CURRENT->sector; + DRWE->last_error_generation = DRS->generation; + } + end_request(0); + } +} + +/* Interrupt handler evaluating the result of the r/w operation */ +static void rw_interrupt(void) +{ + int nr_sectors, ssize, eoc; + + if (!DRS->first_read_date) + DRS->first_read_date = jiffies; + + nr_sectors = 0; + CODE2SIZE; + + if(ST1 & ST1_EOC) + eoc = 1; + else + eoc = 0; + nr_sectors = ((R_TRACK-TRACK)*_floppy->head+R_HEAD-HEAD) * + _floppy->sect + ((R_SECTOR-SECTOR+eoc) << SIZECODE >> 2) - + (sector_t % _floppy->sect) % ssize; + +#ifdef FLOPPY_SANITY_CHECK + if (nr_sectors > current_count_sectors + ssize - + (current_count_sectors + sector_t) % ssize + + sector_t % ssize){ + DPRINT("long rw: %x instead of %lx\n", + nr_sectors, current_count_sectors); + printk("rs=%d s=%d\n", R_SECTOR, SECTOR); + printk("rh=%d h=%d\n", R_HEAD, HEAD); + printk("rt=%d t=%d\n", R_TRACK, TRACK); + printk("spt=%d st=%d ss=%d\n", SECT_PER_TRACK, + sector_t, ssize); + } +#endif + INFBOUND(nr_sectors,0); + SUPBOUND(current_count_sectors, nr_sectors); + + switch (interpret_errors()){ + case 2: + cont->redo(); + return; + case 1: + if (!current_count_sectors){ + cont->error(); + cont->redo(); + return; + } + break; + case 0: + if (!current_count_sectors){ + cont->redo(); + return; + } + current_type[current_drive] = _floppy; + floppy_sizes[TOMINOR(current_drive) ]= _floppy->size>>1; + break; + } + + if (probing) { + if (DP->flags & FTD_MSG) + DPRINT("Auto-detected floppy type %s in fd%d\n", + _floppy->name,current_drive); + current_type[current_drive] = _floppy; + floppy_sizes[TOMINOR(current_drive)] = _floppy->size >> 1; + probing = 0; + } + + if (CT(COMMAND) != FD_READ || + raw_cmd->kernel_data == CURRENT->buffer){ + /* transfer directly from buffer */ + cont->done(1); + } else if (CT(COMMAND) == FD_READ){ + buffer_track = raw_cmd->track; + buffer_drive = current_drive; + INFBOUND(buffer_max, nr_sectors + sector_t); + } + cont->redo(); +} + +/* Compute maximal contiguous buffer size. */ +static int buffer_chain_size(void) +{ + struct buffer_head *bh; + int size; + char *base; + + base = CURRENT->buffer; + size = CURRENT->current_nr_sectors << 9; + bh = CURRENT->bh; + + if (bh){ + bh = bh->b_reqnext; + while (bh && bh->b_data == base + size){ + size += bh->b_size; + bh = bh->b_reqnext; + } + } + return size >> 9; +} + +/* Compute the maximal transfer size */ +static int transfer_size(int ssize, int max_sector, int max_size) +{ + SUPBOUND(max_sector, sector_t + max_size); + + /* alignment */ + max_sector -= (max_sector % _floppy->sect) % ssize; + + /* transfer size, beginning not aligned */ + current_count_sectors = max_sector - sector_t ; + + return max_sector; +} + +/* + * Move data from/to the track buffer to/from the buffer cache. + */ +static void copy_buffer(int ssize, int max_sector, int max_sector_2) +{ + int remaining; /* number of transferred 512-byte sectors */ + struct buffer_head *bh; + char *buffer, *dma_buffer; + int size; + + max_sector = transfer_size(ssize, + minimum(max_sector, max_sector_2), + CURRENT->nr_sectors); + + if (current_count_sectors <= 0 && CT(COMMAND) == FD_WRITE && + buffer_max > sector_t + CURRENT->nr_sectors) + current_count_sectors = minimum(buffer_max - sector_t, + CURRENT->nr_sectors); + + remaining = current_count_sectors << 9; +#ifdef FLOPPY_SANITY_CHECK + if ((remaining >> 9) > CURRENT->nr_sectors && + CT(COMMAND) == FD_WRITE){ + DPRINT("in copy buffer\n"); + printk("current_count_sectors=%ld\n", current_count_sectors); + printk("remaining=%d\n", remaining >> 9); + printk("CURRENT->nr_sectors=%ld\n",CURRENT->nr_sectors); + printk("CURRENT->current_nr_sectors=%ld\n", + CURRENT->current_nr_sectors); + printk("max_sector=%d\n", max_sector); + printk("ssize=%d\n", ssize); + } +#endif + + buffer_max = maximum(max_sector, buffer_max); + + dma_buffer = floppy_track_buffer + ((sector_t - buffer_min) << 9); + + bh = CURRENT->bh; + size = CURRENT->current_nr_sectors << 9; + buffer = CURRENT->buffer; + + while (remaining > 0){ + SUPBOUND(size, remaining); +#ifdef FLOPPY_SANITY_CHECK + if (dma_buffer + size > + floppy_track_buffer + (max_buffer_sectors << 10) || + dma_buffer < floppy_track_buffer){ + DPRINT("buffer overrun in copy buffer %d\n", + (int) ((floppy_track_buffer - dma_buffer) >>9)); + printk("sector_t=%d buffer_min=%d\n", + sector_t, buffer_min); + printk("current_count_sectors=%ld\n", + current_count_sectors); + if (CT(COMMAND) == FD_READ) + printk("read\n"); + if (CT(COMMAND) == FD_READ) + printk("write\n"); + break; + } + if (((unsigned long)buffer) % 512) + DPRINT("%p buffer not aligned\n", buffer); +#endif + if (CT(COMMAND) == FD_READ) + memcpy(buffer, dma_buffer, size); + else + memcpy(dma_buffer, buffer, size); + remaining -= size; + if (!remaining) + break; + + dma_buffer += size; + bh = bh->b_reqnext; +#ifdef FLOPPY_SANITY_CHECK + if (!bh){ + DPRINT("bh=null in copy buffer after copy\n"); + break; + } +#endif + size = bh->b_size; + buffer = bh->b_data; + } +#ifdef FLOPPY_SANITY_CHECK + if (remaining){ + if (remaining > 0) + max_sector -= remaining >> 9; + DPRINT("weirdness: remaining %d\n", remaining>>9); + } +#endif +} + +/* + * Formulate a read/write request. + * this routine decides where to load the data (directly to buffer, or to + * tmp floppy area), how much data to load (the size of the buffer, the whole + * track, or a single sector) + * All floppy_track_buffer handling goes in here. If we ever add track buffer + * allocation on the fly, it should be done here. No other part should need + * modification. + */ + +static int make_raw_rw_request(void) +{ + int aligned_sector_t; + int max_sector, max_size, tracksize, ssize; + + set_fdc(DRIVE(CURRENT->rq_dev)); + + raw_cmd = &default_raw_cmd; + raw_cmd->flags = FD_RAW_SPIN | FD_RAW_NEED_DISK | FD_RAW_NEED_DISK | + FD_RAW_NEED_SEEK; + raw_cmd->cmd_count = NR_RW; + if (CURRENT->cmd == READ){ + raw_cmd->flags |= FD_RAW_READ; + COMMAND = FM_MODE(_floppy,FD_READ); + } else if (CURRENT->cmd == WRITE){ + raw_cmd->flags |= FD_RAW_WRITE; + COMMAND = FM_MODE(_floppy,FD_WRITE); + } else { + DPRINT("make_raw_rw_request: unknown command\n"); + return 0; + } + + max_sector = _floppy->sect * _floppy->head; + + TRACK = CURRENT->sector / max_sector; + sector_t = CURRENT->sector % max_sector; + if (_floppy->track && TRACK >= _floppy->track) + return 0; + HEAD = sector_t / _floppy->sect; + + if (((_floppy->stretch & FD_SWAPSIDES) || TESTF(FD_NEED_TWADDLE)) && + sector_t < _floppy->sect) + max_sector = _floppy->sect; + + /* 2M disks have phantom sectors on the first track */ + if ((_floppy->rate & FD_2M) && (!TRACK) && (!HEAD)){ + max_sector = 2 * _floppy->sect / 3; + if (sector_t >= max_sector){ + current_count_sectors = minimum(_floppy->sect - sector_t, + CURRENT->nr_sectors); + return 1; + } + SIZECODE = 2; + } else + SIZECODE = FD_SIZECODE(_floppy); + raw_cmd->rate = _floppy->rate & 0x43; + if ((_floppy->rate & FD_2M) && + (TRACK || HEAD) && + raw_cmd->rate == 2) + raw_cmd->rate = 1; + + if (SIZECODE) + SIZECODE2 = 0xff; + else + SIZECODE2 = 0x80; + raw_cmd->track = TRACK << STRETCH(_floppy); + DR_SELECT = UNIT(current_drive) + PH_HEAD(_floppy,HEAD); + GAP = _floppy->gap; + CODE2SIZE; + SECT_PER_TRACK = _floppy->sect << 2 >> SIZECODE; + SECTOR = ((sector_t % _floppy->sect) << 2 >> SIZECODE) + 1; + tracksize = _floppy->sect - _floppy->sect % ssize; + if (tracksize < _floppy->sect){ + SECT_PER_TRACK ++; + if (tracksize <= sector_t % _floppy->sect) + SECTOR--; + while (tracksize <= sector_t % _floppy->sect){ + while(tracksize + ssize > _floppy->sect){ + SIZECODE--; + ssize >>= 1; + } + SECTOR++; SECT_PER_TRACK ++; + tracksize += ssize; + } + max_sector = HEAD * _floppy->sect + tracksize; + } else if (!TRACK && !HEAD && !(_floppy->rate & FD_2M) && probing) + max_sector = _floppy->sect; + + aligned_sector_t = sector_t - (sector_t % _floppy->sect) % ssize; + max_size = CURRENT->nr_sectors; + if ((raw_cmd->track == buffer_track) && + (current_drive == buffer_drive) && + (sector_t >= buffer_min) && (sector_t < buffer_max)) { + /* data already in track buffer */ + if (CT(COMMAND) == FD_READ) { + copy_buffer(1, max_sector, buffer_max); + return 1; + } + } else if (aligned_sector_t != sector_t || CURRENT->nr_sectors < ssize){ + if (CT(COMMAND) == FD_WRITE){ + if (sector_t + CURRENT->nr_sectors > ssize && + sector_t + CURRENT->nr_sectors < ssize + ssize) + max_size = ssize + ssize; + else + max_size = ssize; + } + raw_cmd->flags &= ~FD_RAW_WRITE; + raw_cmd->flags |= FD_RAW_READ; + COMMAND = FM_MODE(_floppy,FD_READ); + } else if ((unsigned long)CURRENT->buffer < MAX_DMA_ADDRESS) { + unsigned long dma_limit; + int direct, indirect; + + indirect= transfer_size(ssize,max_sector,max_buffer_sectors*2) - + sector_t; + + /* + * Do NOT use minimum() here---MAX_DMA_ADDRESS is 64 bits wide + * on a 64 bit machine! + */ + max_size = buffer_chain_size(); + dma_limit = (MAX_DMA_ADDRESS - ((unsigned long) CURRENT->buffer)) >> 9; + if ((unsigned long) max_size > dma_limit) { + max_size = dma_limit; + } + /* 64 kb boundaries */ + if (CROSS_64KB(CURRENT->buffer, max_size << 9)) + max_size = (K_64 - ((long) CURRENT->buffer) % K_64)>>9; + direct = transfer_size(ssize,max_sector,max_size) - sector_t; + /* + * We try to read tracks, but if we get too many errors, we + * go back to reading just one sector at a time. + * + * This means we should be able to read a sector even if there + * are other bad sectors on this track. + */ + if (!direct || + (indirect * 2 > direct * 3 && + *errors < DP->max_errors.read_track && + /*!TESTF(FD_NEED_TWADDLE) &&*/ + ((!probing || (DP->read_track&(1<probed_format)))))){ + max_size = CURRENT->nr_sectors; + } else { + raw_cmd->kernel_data = CURRENT->buffer; + raw_cmd->length = current_count_sectors << 9; + if (raw_cmd->length == 0){ + DPRINT("zero dma transfer attempted from make_raw_request\n"); + DPRINT("indirect=%d direct=%d sector_t=%d", + indirect, direct, sector_t); + return 0; + } + return 2; + } + } + + if (CT(COMMAND) == FD_READ) + max_size = max_sector; /* unbounded */ + + /* claim buffer track if needed */ + if (buffer_track != raw_cmd->track || /* bad track */ + buffer_drive !=current_drive || /* bad drive */ + sector_t > buffer_max || + sector_t < buffer_min || + ((CT(COMMAND) == FD_READ || + (aligned_sector_t == sector_t && CURRENT->nr_sectors >= ssize))&& + max_sector > 2 * max_buffer_sectors + buffer_min && + max_size + sector_t > 2 * max_buffer_sectors + buffer_min) + /* not enough space */){ + buffer_track = -1; + buffer_drive = current_drive; + buffer_max = buffer_min = aligned_sector_t; + } + raw_cmd->kernel_data = floppy_track_buffer + + ((aligned_sector_t-buffer_min)<<9); + + if (CT(COMMAND) == FD_WRITE){ + /* copy write buffer to track buffer. + * if we get here, we know that the write + * is either aligned or the data already in the buffer + * (buffer will be overwritten) */ +#ifdef FLOPPY_SANITY_CHECK + if (sector_t != aligned_sector_t && buffer_track == -1) + DPRINT("internal error offset !=0 on write\n"); +#endif + buffer_track = raw_cmd->track; + buffer_drive = current_drive; + copy_buffer(ssize, max_sector, 2*max_buffer_sectors+buffer_min); + } else + transfer_size(ssize, max_sector, + 2*max_buffer_sectors+buffer_min-aligned_sector_t); + + /* round up current_count_sectors to get dma xfer size */ + raw_cmd->length = sector_t+current_count_sectors-aligned_sector_t; + raw_cmd->length = ((raw_cmd->length -1)|(ssize-1))+1; + raw_cmd->length <<= 9; +#ifdef FLOPPY_SANITY_CHECK + if ((raw_cmd->length < current_count_sectors << 9) || + (raw_cmd->kernel_data != CURRENT->buffer && + CT(COMMAND) == FD_WRITE && + (aligned_sector_t + (raw_cmd->length >> 9) > buffer_max || + aligned_sector_t < buffer_min)) || + raw_cmd->length % (128 << SIZECODE) || + raw_cmd->length <= 0 || current_count_sectors <= 0){ + DPRINT("fractionary current count b=%lx s=%lx\n", + raw_cmd->length, current_count_sectors); + if (raw_cmd->kernel_data != CURRENT->buffer) + printk("addr=%d, length=%ld\n", + (int) ((raw_cmd->kernel_data - + floppy_track_buffer) >> 9), + current_count_sectors); + printk("st=%d ast=%d mse=%d msi=%d\n", + sector_t, aligned_sector_t, max_sector, max_size); + printk("ssize=%x SIZECODE=%d\n", ssize, SIZECODE); + printk("command=%x SECTOR=%d HEAD=%d, TRACK=%d\n", + COMMAND, SECTOR, HEAD, TRACK); + printk("buffer drive=%d\n", buffer_drive); + printk("buffer track=%d\n", buffer_track); + printk("buffer_min=%d\n", buffer_min); + printk("buffer_max=%d\n", buffer_max); + return 0; + } + + if (raw_cmd->kernel_data != CURRENT->buffer){ + if (raw_cmd->kernel_data < floppy_track_buffer || + current_count_sectors < 0 || + raw_cmd->length < 0 || + raw_cmd->kernel_data + raw_cmd->length > + floppy_track_buffer + (max_buffer_sectors << 10)){ + DPRINT("buffer overrun in schedule dma\n"); + printk("sector_t=%d buffer_min=%d current_count=%ld\n", + sector_t, buffer_min, + raw_cmd->length >> 9); + printk("current_count_sectors=%ld\n", + current_count_sectors); + if (CT(COMMAND) == FD_READ) + printk("read\n"); + if (CT(COMMAND) == FD_READ) + printk("write\n"); + return 0; + } + } else if (raw_cmd->length > CURRENT->nr_sectors << 9 || + current_count_sectors > CURRENT->nr_sectors){ + DPRINT("buffer overrun in direct transfer\n"); + return 0; + } else if (raw_cmd->length < current_count_sectors << 9){ + DPRINT("more sectors than bytes\n"); + printk("bytes=%ld\n", raw_cmd->length >> 9); + printk("sectors=%ld\n", current_count_sectors); + } + if (raw_cmd->length == 0){ + DPRINT("zero dma transfer attempted from make_raw_request\n"); + return 0; + } +#endif + return 2; +} + +static void redo_fd_request(void) +{ +#define REPEAT {request_done(0); continue; } + kdev_t device; + int tmp; + + lastredo = jiffies; + if (current_drive < N_DRIVE) + floppy_off(current_drive); + + if (CURRENT && CURRENT->rq_status == RQ_INACTIVE){ + CLEAR_INTR; + unlock_fdc(); + return; + } + + while(1){ + if (!CURRENT) { + CLEAR_INTR; + unlock_fdc(); + return; + } + if (MAJOR(CURRENT->rq_dev) != MAJOR_NR) + panic(DEVICE_NAME ": request list destroyed"); + if (CURRENT->bh && !buffer_locked(CURRENT->bh)) + panic(DEVICE_NAME ": block not locked"); + + device = CURRENT->rq_dev; + set_fdc(DRIVE(device)); + reschedule_timeout(CURRENTD, "redo fd request", 0); + + set_floppy(device); + raw_cmd = & default_raw_cmd; + raw_cmd->flags = 0; + if (start_motor(redo_fd_request)) return; + disk_change(current_drive); + if (test_bit(current_drive, &fake_change) || + TESTF(FD_DISK_CHANGED)){ + DPRINT("disk absent or changed during operation\n"); + REPEAT; + } + if (!_floppy) { /* Autodetection */ + if (!probing){ + DRS->probed_format = 0; + if (next_valid_format()){ + DPRINT("no autodetectable formats\n"); + _floppy = NULL; + REPEAT; + } + } + probing = 1; + _floppy = floppy_type+DP->autodetect[DRS->probed_format]; + } else + probing = 0; + errors = & (CURRENT->errors); + tmp = make_raw_rw_request(); + if (tmp < 2){ + request_done(tmp); + continue; + } + + if (TESTF(FD_NEED_TWADDLE)) + twaddle(); + floppy_tq.routine = (void *)(void *) floppy_start; + queue_task(&floppy_tq, &tq_immediate); + mark_bh(IMMEDIATE_BH); +#ifdef DEBUGT + debugt("queue fd request"); +#endif + return; + } +#undef REPEAT +} + +static struct cont_t rw_cont={ + rw_interrupt, + redo_fd_request, + bad_flp_intr, + request_done }; + +static struct tq_struct request_tq = +{ 0, 0, (void *) (void *) redo_fd_request, 0 }; + +static void process_fd_request(void) +{ + cont = &rw_cont; + queue_task(&request_tq, &tq_immediate); + mark_bh(IMMEDIATE_BH); +} + +static void do_fd_request(void) +{ + sti(); + if (fdc_busy){ + /* fdc busy, this new request will be treated when the + current one is done */ + is_alive("do fd request, old request running"); + return; + } + lock_fdc(MAXTIMEOUT,0); + process_fd_request(); + is_alive("do fd request"); +} + +static struct cont_t poll_cont={ + success_and_wakeup, + floppy_ready, + generic_failure, + generic_done }; + +static int poll_drive(int interruptible, int flag) +{ + int ret; + /* no auto-sense, just clear dcl */ + raw_cmd = &default_raw_cmd; + raw_cmd->flags= flag; + raw_cmd->track=0; + raw_cmd->cmd_count=0; + cont = &poll_cont; +#ifdef DCL_DEBUG + if (DP->flags & FD_DEBUG){ + DPRINT("setting NEWCHANGE in poll_drive\n"); + } +#endif + SETF(FD_DISK_NEWCHANGE); + WAIT(floppy_ready); + return ret; +} + +/* + * User triggered reset + * ==================== + */ + +static void reset_intr(void) +{ + printk("weird, reset interrupt called\n"); +} + +static struct cont_t reset_cont={ + reset_intr, + success_and_wakeup, + generic_failure, + generic_done }; + +static int user_reset_fdc(int drive, int arg, int interruptible) +{ + int ret; + + ret=0; + LOCK_FDC(drive,interruptible); + if (arg == FD_RESET_ALWAYS) + FDCS->reset=1; + if (FDCS->reset){ + cont = &reset_cont; + WAIT(reset_fdc); + } + process_fd_request(); + return ret; +} + +/* + * Misc Ioctl's and support + * ======================== + */ +static int fd_copyout(void *param, const void *address, int size) +{ + int ret; + + ECALL(verify_area(VERIFY_WRITE,param,size)); + memcpy_tofs(param,(void *) address, size); + return 0; +} + +static int fd_copyin(void *param, void *address, int size) +{ + int ret; + + ECALL(verify_area(VERIFY_READ,param,size)); + memcpy_fromfs((void *) address, param, size); + return 0; +} + +#define COPYOUT(x) ECALL(fd_copyout((void *)param, &(x), sizeof(x))) +#define COPYIN(x) ECALL(fd_copyin((void *)param, &(x), sizeof(x))) + +static inline const char *drive_name(int type, int drive) +{ + struct floppy_struct *floppy; + + if (type) + floppy = floppy_type + type; + else { + if (UDP->native_format) + floppy = floppy_type + UDP->native_format; + else + return "(null)"; + } + if (floppy->name) + return floppy->name; + else + return "(null)"; +} + + +/* raw commands */ +static void raw_cmd_done(int flag) +{ + int i; + + if (!flag) { + raw_cmd->flags |= FD_RAW_FAILURE; + raw_cmd->flags |= FD_RAW_HARDFAILURE; + } else { + raw_cmd->reply_count = inr; + for (i=0; i< raw_cmd->reply_count; i++) + raw_cmd->reply[i] = reply_buffer[i]; + + if (raw_cmd->flags & (FD_RAW_READ | FD_RAW_WRITE)) + raw_cmd->length = fd_get_dma_residue(); + + if ((raw_cmd->flags & FD_RAW_SOFTFAILURE) && + (!raw_cmd->reply_count || (raw_cmd->reply[0] & 0xc0))) + raw_cmd->flags |= FD_RAW_FAILURE; + + if (disk_change(current_drive)) + raw_cmd->flags |= FD_RAW_DISK_CHANGE; + else + raw_cmd->flags &= ~FD_RAW_DISK_CHANGE; + if (raw_cmd->flags & FD_RAW_NO_MOTOR_AFTER) + motor_off_callback(current_drive); + + if (raw_cmd->next && + (!(raw_cmd->flags & FD_RAW_FAILURE) || + !(raw_cmd->flags & FD_RAW_STOP_IF_FAILURE)) && + ((raw_cmd->flags & FD_RAW_FAILURE) || + !(raw_cmd->flags &FD_RAW_STOP_IF_SUCCESS))) { + raw_cmd = raw_cmd->next; + return; + } + } + generic_done(flag); +} + + +static struct cont_t raw_cmd_cont={ + success_and_wakeup, + floppy_start, + generic_failure, + raw_cmd_done +}; + +static inline int raw_cmd_copyout(int cmd, char *param, + struct floppy_raw_cmd *ptr) +{ + struct old_floppy_raw_cmd old_raw_cmd; + int ret; + + while(ptr) { + if (cmd == OLDFDRAWCMD) { + old_raw_cmd.flags = ptr->flags; + old_raw_cmd.data = ptr->data; + old_raw_cmd.length = ptr->length; + old_raw_cmd.rate = ptr->rate; + old_raw_cmd.reply_count = ptr->reply_count; + memcpy(old_raw_cmd.reply, ptr->reply, 7); + COPYOUT(old_raw_cmd); + param += sizeof(old_raw_cmd); + } else { + COPYOUT(*ptr); + param += sizeof(struct floppy_raw_cmd); + } + + if ((ptr->flags & FD_RAW_READ) && ptr->buffer_length){ + if (ptr->length>=0 && ptr->length<=ptr->buffer_length) + ECALL(fd_copyout(ptr->data, + ptr->kernel_data, + ptr->buffer_length - + ptr->length)); + } + ptr = ptr->next; + } + return 0; +} + + +static void raw_cmd_free(struct floppy_raw_cmd **ptr) +{ + struct floppy_raw_cmd *next,*this; + + this = *ptr; + *ptr = 0; + while(this) { + if (this->buffer_length) { + fd_dma_mem_free((unsigned long)this->kernel_data, + this->buffer_length); + this->buffer_length = 0; + } + next = this->next; + kfree(this); + this = next; + } +} + + +static inline int raw_cmd_copyin(int cmd, char *param, + struct floppy_raw_cmd **rcmd) +{ + struct floppy_raw_cmd *ptr; + struct old_floppy_raw_cmd old_raw_cmd; + int ret; + int i; + + *rcmd = 0; + while(1) { + ptr = (struct floppy_raw_cmd *) + kmalloc(sizeof(struct floppy_raw_cmd), GFP_USER); + if (!ptr) + return -ENOMEM; + *rcmd = ptr; + if (cmd == OLDFDRAWCMD){ + COPYIN(old_raw_cmd); + ptr->flags = old_raw_cmd.flags; + ptr->data = old_raw_cmd.data; + ptr->length = old_raw_cmd.length; + ptr->rate = old_raw_cmd.rate; + ptr->cmd_count = old_raw_cmd.cmd_count; + ptr->track = old_raw_cmd.track; + ptr->phys_length = 0; + ptr->next = 0; + ptr->buffer_length = 0; + memcpy(ptr->cmd, old_raw_cmd.cmd, 9); + param += sizeof(struct old_floppy_raw_cmd); + if (ptr->cmd_count > 9) + return -EINVAL; + } else { + COPYIN(*ptr); + ptr->next = 0; + ptr->buffer_length = 0; + param += sizeof(struct floppy_raw_cmd); + if (ptr->cmd_count > 33) + /* the command may now also take up the space + * initially intended for the reply & the + * reply count. Needed for long 82078 commands + * such as RESTORE, which takes ... 17 command + * bytes. Murphy's law #137: When you reserve + * 16 bytes for a structure, you'll one day + * discover that you really need 17... + */ + return -EINVAL; + } + + for (i=0; i< 16; i++) + ptr->reply[i] = 0; + ptr->resultcode = 0; + ptr->kernel_data = 0; + + if (ptr->flags & (FD_RAW_READ | FD_RAW_WRITE)) { + if (ptr->length <= 0) + return -EINVAL; + ptr->kernel_data =(char*)fd_dma_mem_alloc(ptr->length); + if (!ptr->kernel_data) + return -ENOMEM; + ptr->buffer_length = ptr->length; + } + if ( ptr->flags & FD_RAW_READ ) + ECALL( verify_area( VERIFY_WRITE, ptr->data, + ptr->length )); + if (ptr->flags & FD_RAW_WRITE) + ECALL(fd_copyin(ptr->data, ptr->kernel_data, + ptr->length)); + rcmd = & (ptr->next); + if (!(ptr->flags & FD_RAW_MORE)) + return 0; + ptr->rate &= 0x43; + } +} + + +static int raw_cmd_ioctl(int cmd, void *param) +{ + int drive, ret, ret2; + struct floppy_raw_cmd *my_raw_cmd; + + if (FDCS->rawcmd <= 1) + FDCS->rawcmd = 1; + for (drive= 0; drive < N_DRIVE; drive++){ + if (FDC(drive) != fdc) + continue; + if (drive == current_drive){ + if (UDRS->fd_ref > 1){ + FDCS->rawcmd = 2; + break; + } + } else if (UDRS->fd_ref){ + FDCS->rawcmd = 2; + break; + } + } + + if (FDCS->reset) + return -EIO; + + ret = raw_cmd_copyin(cmd, param, &my_raw_cmd); + if (ret) { + raw_cmd_free(&my_raw_cmd); + return ret; + } + + raw_cmd = my_raw_cmd; + cont = &raw_cmd_cont; + ret=wait_til_done(floppy_start,1); +#ifdef DCL_DEBUG + if (DP->flags & FD_DEBUG){ + DPRINT("calling disk change from raw_cmd ioctl\n"); + } +#endif + + if (ret != -EINTR && FDCS->reset) + ret = -EIO; + + DRS->track = NO_TRACK; + + ret2 = raw_cmd_copyout(cmd, param, my_raw_cmd); + if (!ret) + ret = ret2; + raw_cmd_free(&my_raw_cmd); + return ret; +} + +static int invalidate_drive(kdev_t rdev) +{ + /* invalidate the buffer track to force a reread */ + set_bit(DRIVE(rdev), &fake_change); + process_fd_request(); + check_disk_change(rdev); + return 0; +} + + +static inline void clear_write_error(int drive) +{ + CLEARSTRUCT(UDRWE); +} + +static inline int set_geometry(unsigned int cmd, struct floppy_struct *g, + int drive, int type, kdev_t device) +{ + int cnt; + + /* sanity checking for parameters.*/ + if (g->sect <= 0 || + g->head <= 0 || + g->track <= 0 || + g->track > UDP->tracks>>STRETCH(g) || + /* check if reserved bits are set */ + (g->stretch&~(FD_STRETCH|FD_SWAPSIDES)) != 0) + return -EINVAL; + if (type){ + if (!suser()) + return -EPERM; + LOCK_FDC(drive,1); + for (cnt = 0; cnt < N_DRIVE; cnt++){ + if (ITYPE(drive_state[cnt].fd_device) == type && + drive_state[cnt].fd_ref) + set_bit(drive, &fake_change); + } + floppy_type[type] = *g; + floppy_type[type].name="user format"; + for (cnt = type << 2; cnt < (type << 2) + 4; cnt++) + floppy_sizes[cnt]= floppy_sizes[cnt+0x80]= + floppy_type[type].size>>1; + process_fd_request(); + for (cnt = 0; cnt < N_DRIVE; cnt++){ + if (ITYPE(drive_state[cnt].fd_device) == type && + drive_state[cnt].fd_ref) + check_disk_change( + MKDEV(FLOPPY_MAJOR, + drive_state[cnt].fd_device)); + } + } else { + LOCK_FDC(drive,1); + if (cmd != FDDEFPRM) + /* notice a disk change immediately, else + * we loose our settings immediately*/ + CALL(poll_drive(1, FD_RAW_NEED_DISK)); + user_params[drive] = *g; + if (buffer_drive == drive) + SUPBOUND(buffer_max, user_params[drive].sect); + current_type[drive] = &user_params[drive]; + floppy_sizes[drive] = user_params[drive].size >> 1; + if (cmd == FDDEFPRM) + DRS->keep_data = -1; + else + DRS->keep_data = 1; + /* invalidation. Invalidate only when needed, i.e. + * when there are already sectors in the buffer cache + * whose number will change. This is useful, because + * mtools often changes the geometry of the disk after + * looking at the boot block */ + if (DRS->maxblock > user_params[drive].sect || DRS->maxtrack) + invalidate_drive(device); + else + process_fd_request(); + } + return 0; +} + +/* handle obsolete ioctl's */ +static struct translation_entry { + int newcmd; + int oldcmd; + int oldsize; /* size of 0x00xx-style ioctl. Reflects old structures, thus + * use numeric values. NO SIZEOFS */ +} translation_table[]= { + {FDCLRPRM, 0, 0}, + {FDSETPRM, 1, 28}, + {FDDEFPRM, 2, 28}, + {FDGETPRM, 3, 28}, + {FDMSGON, 4, 0}, + {FDMSGOFF, 5, 0}, + {FDFMTBEG, 6, 0}, + {FDFMTTRK, 7, 12}, + {FDFMTEND, 8, 0}, + {FDSETEMSGTRESH, 10, 0}, + {FDFLUSH, 11, 0}, + {FDSETMAXERRS, 12, 20}, + {OLDFDRAWCMD, 30, 0}, + {FDGETMAXERRS, 14, 20}, + {FDGETDRVTYP, 16, 16}, + {FDSETDRVPRM, 20, 88}, + {FDGETDRVPRM, 21, 88}, + {FDGETDRVSTAT, 22, 52}, + {FDPOLLDRVSTAT, 23, 52}, + {FDRESET, 24, 0}, + {FDGETFDCSTAT, 25, 40}, + {FDWERRORCLR, 27, 0}, + {FDWERRORGET, 28, 24}, + {FDRAWCMD, 0, 0}, + {FDEJECT, 0, 0}, + {FDTWADDLE, 40, 0} }; + +static inline int normalize_0x02xx_ioctl(int *cmd, int *size) +{ + int i; + + for (i=0; i < ARRAY_SIZE(translation_table); i++) { + if ((*cmd & 0xffff) == (translation_table[i].newcmd & 0xffff)){ + *size = _IOC_SIZE(*cmd); + *cmd = translation_table[i].newcmd; + if (*size > _IOC_SIZE(*cmd)) { + printk("ioctl not yet supported\n"); + return -EFAULT; + } + return 0; + } + } + return -EINVAL; +} + +static inline int xlate_0x00xx_ioctl(int *cmd, int *size) +{ + int i; + /* old ioctls' for kernels <= 1.3.33 */ + /* When the next even release will come around, we'll start + * warning against these. + * When the next odd release will come around, we'll fail with + * -EINVAL */ + if(strcmp(system_utsname.version, "1.4.0") >= 0) + printk("obsolete floppy ioctl %x\n", *cmd); + if((system_utsname.version[0] == '1' && + strcmp(system_utsname.version, "1.5.0") >= 0) || + (system_utsname.version[0] >= '2' && + strcmp(system_utsname.version, "2.1.0") >= 0)) + return -EINVAL; + for (i=0; i < ARRAY_SIZE(translation_table); i++) { + if (*cmd == translation_table[i].oldcmd) { + *size = translation_table[i].oldsize; + *cmd = translation_table[i].newcmd; + return 0; + } + } + return -EINVAL; +} + +static int fd_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, + unsigned long param) +{ +#define IOCTL_MODE_BIT 8 +#define OPEN_WRITE_BIT 16 +#define IOCTL_ALLOWED (filp && (filp->f_mode & IOCTL_MODE_BIT)) +#define OUT(c,x) case c: outparam = (const char *) (x); break +#define IN(c,x,tag) case c: *(x) = inparam. tag ; return 0 + + int i,drive,type; + kdev_t device; + int ret; + int size; + union inparam { + struct floppy_struct g; /* geometry */ + struct format_descr f; + struct floppy_max_errors max_errors; + struct floppy_drive_params dp; + } inparam; /* parameters coming from user space */ + const char *outparam; /* parameters passed back to user space */ + + device = inode->i_rdev; + switch (cmd) { + RO_IOCTLS(device,param); + } + type = TYPE(device); + drive = DRIVE(device); + + /* convert compatibility eject ioctls into floppy eject ioctl. + * We do this in order to provide a means to eject floppy disks before + * installing the new fdutils package */ + if(cmd == CDROMEJECT || /* CD-ROM eject */ + cmd == 0x6470 /* SunOS floppy eject */) { + DPRINT("obsolete eject ioctl\n"); + DPRINT("please use floppycontrol --eject\n"); + cmd = FDEJECT; + } + + /* convert the old style command into a new style command */ + if ((cmd & 0xff00) == 0x0200) { + ECALL(normalize_0x02xx_ioctl(&cmd, &size)); + } else if ((cmd & 0xff00) == 0x0000) { + ECALL(xlate_0x00xx_ioctl(&cmd, &size)); + } else + return -EINVAL; + + /* permission checks */ + if (((cmd & 0x80) && !suser()) || + ((cmd & 0x40) && !IOCTL_ALLOWED)) + return -EPERM; + + /* verify writability of result, and fail early */ + if (_IOC_DIR(cmd) & _IOC_READ) + ECALL(verify_area(VERIFY_WRITE,(void *) param, size)); + + /* copyin */ + CLEARSTRUCT(&inparam); + if (_IOC_DIR(cmd) & _IOC_WRITE) + ECALL(fd_copyin((void *)param, &inparam, size)) + + switch (cmd) { + case FDEJECT: + if(UDRS->fd_ref != 1) + /* somebody else has this drive open */ + return -EBUSY; + LOCK_FDC(drive,1); + + /* do the actual eject. Fails on + * non-Sparc architectures */ + ret=fd_eject(UNIT(drive)); + + USETF(FD_DISK_CHANGED); + USETF(FD_VERIFY); + process_fd_request(); + return ret; + case FDCLRPRM: + LOCK_FDC(drive,1); + current_type[drive] = NULL; + floppy_sizes[drive] = MAX_DISK_SIZE; + UDRS->keep_data = 0; + return invalidate_drive(device); + case FDSETPRM: + case FDDEFPRM: + return set_geometry(cmd, & inparam.g, + drive, type, device); + case FDGETPRM: + LOCK_FDC(drive,1); + CALL(poll_drive(1,0)); + process_fd_request(); + if (type) + outparam = (char *) &floppy_type[type]; + else + outparam = (char *) current_type[drive]; + if(!outparam) + return -ENODEV; + break; + + case FDMSGON: + UDP->flags |= FTD_MSG; + return 0; + case FDMSGOFF: + UDP->flags &= ~FTD_MSG; + return 0; + + case FDFMTBEG: + LOCK_FDC(drive,1); + CALL(poll_drive(1, FD_RAW_NEED_DISK)); + ret = UDRS->flags; + process_fd_request(); + if(ret & FD_VERIFY) + return -ENODEV; + if(!(ret & FD_DISK_WRITABLE)) + return -EROFS; + return 0; + case FDFMTTRK: + if (UDRS->fd_ref != 1) + return -EBUSY; + return do_format(device, &inparam.f); + case FDFMTEND: + case FDFLUSH: + LOCK_FDC(drive,1); + return invalidate_drive(device); + + case FDSETEMSGTRESH: + UDP->max_errors.reporting = + (unsigned short) (param & 0x0f); + return 0; + OUT(FDGETMAXERRS, &UDP->max_errors); + IN(FDSETMAXERRS, &UDP->max_errors, max_errors); + + case FDGETDRVTYP: + outparam = drive_name(type,drive); + SUPBOUND(size,strlen(outparam)+1); + break; + + IN(FDSETDRVPRM, UDP, dp); + OUT(FDGETDRVPRM, UDP); + + case FDPOLLDRVSTAT: + LOCK_FDC(drive,1); + CALL(poll_drive(1, FD_RAW_NEED_DISK)); + process_fd_request(); + /* fall through */ + OUT(FDGETDRVSTAT, UDRS); + + case FDRESET: + return user_reset_fdc(drive, (int)param, 1); + + OUT(FDGETFDCSTAT,UFDCS); + + case FDWERRORCLR: + CLEARSTRUCT(UDRWE); + return 0; + OUT(FDWERRORGET,UDRWE); + + case OLDFDRAWCMD: + case FDRAWCMD: + if (type) + return -EINVAL; + LOCK_FDC(drive,1); + set_floppy(device); + CALL(i = raw_cmd_ioctl(cmd,(void *) param)); + process_fd_request(); + return i; + + case FDTWADDLE: + LOCK_FDC(drive,1); + twaddle(); + process_fd_request(); + return 0; + + default: + return -EINVAL; + } + + if (_IOC_DIR(cmd) & _IOC_READ) + return fd_copyout((void *)param, outparam, size); + else + return 0; +#undef IOCTL_ALLOWED +#undef OUT +#undef IN +} + +static void config_types(void) +{ + int first=1; + int drive; + + /* read drive info out of physical CMOS */ + drive=0; + if (!UDP->cmos) + UDP->cmos= FLOPPY0_TYPE; + drive=1; + if (!UDP->cmos && FLOPPY1_TYPE) + UDP->cmos = FLOPPY1_TYPE; + + /* XXX */ + /* additional physical CMOS drive detection should go here */ + + for (drive=0; drive < N_DRIVE; drive++){ + if (UDP->cmos >= 16) + UDP->cmos = 0; + if (UDP->cmos >= 0 && UDP->cmos <= NUMBER(default_drive_params)) + memcpy((char *) UDP, + (char *) (&default_drive_params[(int)UDP->cmos].params), + sizeof(struct floppy_drive_params)); + if (UDP->cmos){ + if (first) + printk(KERN_INFO "Floppy drive(s): "); + else + printk(", "); + first=0; + if (UDP->cmos > 0){ + allowed_drive_mask |= 1 << drive; + printk("fd%d is %s", drive, + default_drive_params[(int)UDP->cmos].name); + } else + printk("fd%d is unknown type %d",drive, + UDP->cmos); + } + else + allowed_drive_mask &= ~(1 << drive); + } + if (!first) + printk("\n"); +} + +static int floppy_read(struct inode * inode, struct file * filp, + char * buf, int count) +{ + int drive = DRIVE(inode->i_rdev); + + check_disk_change(inode->i_rdev); + if (UTESTF(FD_DISK_CHANGED)) + return -ENXIO; + return block_read(inode, filp, buf, count); +} + +static int floppy_write(struct inode * inode, struct file * filp, + const char * buf, int count) +{ + int block; + int ret; + int drive = DRIVE(inode->i_rdev); + + if (!UDRS->maxblock) + UDRS->maxblock=1;/* make change detectable */ + check_disk_change(inode->i_rdev); + if (UTESTF(FD_DISK_CHANGED)) + return -ENXIO; + if (!UTESTF(FD_DISK_WRITABLE)) + return -EROFS; + block = (filp->f_pos + count) >> 9; + INFBOUND(UDRS->maxblock, block); + ret= block_write(inode, filp, buf, count); + return ret; +} + +static void floppy_release(struct inode * inode, struct file * filp) +{ + int drive; + + drive = DRIVE(inode->i_rdev); + + if (!filp || (filp->f_mode & (2 | OPEN_WRITE_BIT))) + /* if the file is mounted OR (writable now AND writable at + * open time) Linus: Does this cover all cases? */ + block_fsync(inode,filp); + + if (UDRS->fd_ref < 0) + UDRS->fd_ref=0; + else if (!UDRS->fd_ref--) { + DPRINT("floppy_release with fd_ref == 0"); + UDRS->fd_ref = 0; + } + floppy_release_irq_and_dma(); +} + +/* + * floppy_open check for aliasing (/dev/fd0 can be the same as + * /dev/PS0 etc), and disallows simultaneous access to the same + * drive with different device numbers. + */ +#define RETERR(x) do{floppy_release(inode,filp); return -(x);}while(0) + +static int floppy_open(struct inode * inode, struct file * filp) +{ + int drive; + int old_dev; + int try; + char *tmp; + + if (!filp) { + DPRINT("Weird, open called with filp=0\n"); + return -EIO; + } + + drive = DRIVE(inode->i_rdev); + if (drive >= N_DRIVE || + !(allowed_drive_mask & (1 << drive)) || + fdc_state[FDC(drive)].version == FDC_NONE) + return -ENXIO; + + if (TYPE(inode->i_rdev) >= NUMBER(floppy_type)) + return -ENXIO; + old_dev = UDRS->fd_device; + if (UDRS->fd_ref && old_dev != MINOR(inode->i_rdev)) + return -EBUSY; + + if (!UDRS->fd_ref && (UDP->flags & FD_BROKEN_DCL)){ + USETF(FD_DISK_CHANGED); + USETF(FD_VERIFY); + } + + if (UDRS->fd_ref == -1 || + (UDRS->fd_ref && (filp->f_flags & O_EXCL))) + return -EBUSY; + + if (floppy_grab_irq_and_dma()) + return -EBUSY; + + if (filp->f_flags & O_EXCL) + UDRS->fd_ref = -1; + else + UDRS->fd_ref++; + + if (!floppy_track_buffer){ + /* if opening an ED drive, reserve a big buffer, + * else reserve a small one */ + if ((UDP->cmos == 6) || (UDP->cmos == 5)) + try = 64; /* Only 48 actually useful */ + else + try = 32; /* Only 24 actually useful */ + + tmp=(char *)fd_dma_mem_alloc(1024 * try); + if (!tmp) { + try >>= 1; /* buffer only one side */ + INFBOUND(try, 16); + tmp= (char *)fd_dma_mem_alloc(1024*try); + } + if (!tmp) { + DPRINT("Unable to allocate DMA memory\n"); + RETERR(ENXIO); + } + if (floppy_track_buffer) + fd_dma_mem_free((unsigned long)tmp,try*1024); + else { + buffer_min = buffer_max = -1; + floppy_track_buffer = tmp; + max_buffer_sectors = try; + } + } + + UDRS->fd_device = MINOR(inode->i_rdev); + if (old_dev != -1 && old_dev != MINOR(inode->i_rdev)) { + if (buffer_drive == drive) + buffer_track = -1; + invalidate_buffers(MKDEV(FLOPPY_MAJOR,old_dev)); + } + + /* Allow ioctls if we have write-permissions even if read-only open */ + if ((filp->f_mode & 2) || (permission(inode,2) == 0)) + filp->f_mode |= IOCTL_MODE_BIT; + if (filp->f_mode & 2) + filp->f_mode |= OPEN_WRITE_BIT; + + if (UFDCS->rawcmd == 1) + UFDCS->rawcmd = 2; + + if (filp->f_flags & O_NDELAY) + return 0; + if (filp->f_mode & 3) { + UDRS->last_checked = 0; + check_disk_change(inode->i_rdev); + if (UTESTF(FD_DISK_CHANGED)) + RETERR(ENXIO); + } + if ((filp->f_mode & 2) && !(UTESTF(FD_DISK_WRITABLE))) + RETERR(EROFS); + return 0; +#undef RETERR +} + +/* + * Check if the disk has been changed or if a change has been faked. + */ +static int check_floppy_change(kdev_t dev) +{ + int drive = DRIVE(dev); + + if (MAJOR(dev) != MAJOR_NR) { + DPRINT("check_floppy_change: not a floppy\n"); + return 0; + } + + if (UTESTF(FD_DISK_CHANGED) || UTESTF(FD_VERIFY)) + return 1; + + if (UDP->checkfreq < jiffies - UDRS->last_checked){ + lock_fdc(drive,0); + poll_drive(0,0); + process_fd_request(); + } + + if (UTESTF(FD_DISK_CHANGED) || + UTESTF(FD_VERIFY) || + test_bit(drive, &fake_change) || + (!TYPE(dev) && !current_type[drive])) + return 1; + return 0; +} + +/* revalidate the floppy disk, i.e. trigger format autodetection by reading + * the bootblock (block 0). "Autodetection" is also needed to check whether + * there is a disk in the drive at all... Thus we also do it for fixed + * geometry formats */ +static int floppy_revalidate(kdev_t dev) +{ +#define NO_GEOM (!current_type[drive] && !TYPE(dev)) + struct buffer_head * bh; + int drive=DRIVE(dev); + int cf; + + if (UTESTF(FD_DISK_CHANGED) || + UTESTF(FD_VERIFY) || + test_bit(drive, &fake_change) || + NO_GEOM){ + lock_fdc(drive,0); + cf = UTESTF(FD_DISK_CHANGED) || UTESTF(FD_VERIFY); + if (!(cf || test_bit(drive, &fake_change) || NO_GEOM)){ + process_fd_request(); /*already done by another thread*/ + return 0; + } + UDRS->maxblock = 0; + UDRS->maxtrack = 0; + if (buffer_drive == drive) + buffer_track = -1; + clear_bit(drive, &fake_change); + UCLEARF(FD_DISK_CHANGED); + if (cf) + UDRS->generation++; + if (NO_GEOM){ + /* auto-sensing */ + int size = floppy_blocksizes[MINOR(dev)]; + if (!size) + size = 1024; + if (!(bh = getblk(dev,0,size))){ + process_fd_request(); + return 1; + } + if (bh && !buffer_uptodate(bh)) + ll_rw_block(READ, 1, &bh, 1); + process_fd_request(); + wait_on_buffer(bh); + brelse(bh); + return 0; + } + if (cf) + poll_drive(0, FD_RAW_NEED_DISK); + process_fd_request(); + } + return 0; +} + +static struct file_operations floppy_fops = { + NULL, /* lseek - default */ + floppy_read, /* read - general block-dev read */ + floppy_write, /* write - general block-dev write */ + NULL, /* readdir - bad */ + NULL, /* select */ + fd_ioctl, /* ioctl */ + NULL, /* mmap */ + floppy_open, /* open */ + floppy_release, /* release */ + block_fsync, /* fsync */ + NULL, /* fasync */ + check_floppy_change, /* media_change */ + floppy_revalidate, /* revalidate */ +}; + +/* + * Floppy Driver initialization + * ============================= + */ + +/* Determine the floppy disk controller type */ +/* This routine was written by David C. Niemi */ +static char get_fdc_version(void) +{ + int r; + + output_byte(FD_DUMPREGS); /* 82072 and better know DUMPREGS */ + if (FDCS->reset) + return FDC_NONE; + if ((r = result()) <= 0x00) + return FDC_NONE; /* No FDC present ??? */ + if ((r==1) && (reply_buffer[0] == 0x80)){ + printk(KERN_INFO "FDC %d is an 8272A\n",fdc); + return FDC_8272A; /* 8272a/765 don't know DUMPREGS */ + } + if (r != 10) { + printk("FDC %d init: DUMPREGS: unexpected return of %d bytes.\n", + fdc, r); + return FDC_UNKNOWN; + } + + if(!fdc_configure()) { + printk(KERN_INFO "FDC %d is an 82072\n",fdc); + return FDC_82072; /* 82072 doesn't know CONFIGURE */ + } + + output_byte(FD_PERPENDICULAR); + if(need_more_output() == MORE_OUTPUT) { + output_byte(0); + } else { + printk(KERN_INFO "FDC %d is an 82072A\n", fdc); + return FDC_82072A; /* 82072A as found on Sparcs. */ + } + + output_byte(FD_UNLOCK); + r = result(); + if ((r == 1) && (reply_buffer[0] == 0x80)){ + printk(KERN_INFO "FDC %d is a pre-1991 82077\n", fdc); + return FDC_82077_ORIG; /* Pre-1991 82077, doesn't know + * LOCK/UNLOCK */ + } + if ((r != 1) || (reply_buffer[0] != 0x00)) { + printk("FDC %d init: UNLOCK: unexpected return of %d bytes.\n", + fdc, r); + return FDC_UNKNOWN; + } + output_byte(FD_PARTID); + r = result(); + if (r != 1) { + printk("FDC %d init: PARTID: unexpected return of %d bytes.\n", + fdc, r); + return FDC_UNKNOWN; + } + if (reply_buffer[0] == 0x80) { + printk(KERN_INFO "FDC %d is a post-1991 82077\n",fdc); + return FDC_82077; /* Revised 82077AA passes all the tests */ + } + switch (reply_buffer[0] >> 5) { + case 0x0: + /* Either a 82078-1 or a 82078SL running at 5Volt */ + printk(KERN_INFO "FDC %d is an 82078.\n",fdc); + return FDC_82078; + case 0x1: + printk(KERN_INFO "FDC %d is a 44pin 82078\n",fdc); + return FDC_82078; + case 0x2: + printk(KERN_INFO "FDC %d is a S82078B\n", fdc); + return FDC_S82078B; + case 0x3: + printk(KERN_INFO "FDC %d is a National Semiconductor PC87306\n", fdc); + return FDC_87306; + default: + printk(KERN_INFO "FDC %d init: 82078 variant with unknown PARTID=%d.\n", + fdc, reply_buffer[0] >> 5); + return FDC_82078_UNKN; + } +} /* get_fdc_version */ + +/* lilo configuration */ + +/* we make the invert_dcl function global. One day, somebody might + * want to centralize all thinkpad related options into one lilo option, + * there are just so many thinkpad related quirks! */ +void floppy_invert_dcl(int *ints,int param) +{ + int i; + + for (i=0; i < ARRAY_SIZE(default_drive_params); i++){ + if (param) + default_drive_params[i].params.flags |= 0x80; + else + default_drive_params[i].params.flags &= ~0x80; + } + DPRINT("Configuring drives for inverted dcl\n"); +} + +static void daring(int *ints,int param) +{ + int i; + + for (i=0; i < ARRAY_SIZE(default_drive_params); i++){ + if (param){ + default_drive_params[i].params.select_delay = 0; + default_drive_params[i].params.flags |= FD_SILENT_DCL_CLEAR; + } else { + default_drive_params[i].params.select_delay = 2*HZ/100; + default_drive_params[i].params.flags &= ~FD_SILENT_DCL_CLEAR; + } + } + DPRINT("Assuming %s floppy hardware\n", param ? "standard" : "broken"); +} + +static void set_cmos(int *ints, int dummy) +{ + int current_drive=0; + + if (ints[0] != 2){ + DPRINT("wrong number of parameter for cmos\n"); + return; + } + current_drive = ints[1]; + if (current_drive < 0 || current_drive >= 8){ + DPRINT("bad drive for set_cmos\n"); + return; + } + if (current_drive >= 4 && !FDC2) + FDC2 = 0x370; + if (ints[2] <= 0 || + (ints[2] >= NUMBER(default_drive_params) && ints[2] != 16)){ + DPRINT("bad cmos code %d\n", ints[2]); + return; + } + DP->cmos = ints[2]; + DPRINT("setting cmos code to %d\n", ints[2]); +} + +static struct param_table { + const char *name; + void (*fn)(int *ints, int param); + int *var; + int def_param; +} config_params[]={ + { "allowed_drive_mask", 0, &allowed_drive_mask, 0xff }, + { "all_drives", 0, &allowed_drive_mask, 0xff }, + { "asus_pci", 0, &allowed_drive_mask, 0x33 }, + + { "daring", daring, 0, 1}, + + { "two_fdc", 0, &FDC2, 0x370 }, + { "one_fdc", 0, &FDC2, 0 }, + + { "thinkpad", floppy_invert_dcl, 0, 1 }, + + { "nodma", 0, &use_virtual_dma, 1 }, + { "omnibook", 0, &use_virtual_dma, 1 }, + { "dma", 0, &use_virtual_dma, 0 }, + + { "fifo_depth", 0, &fifo_depth, 0xa }, + { "nofifo", 0, &no_fifo, 0x20 }, + { "usefifo", 0, &no_fifo, 0 }, + + { "cmos", set_cmos, 0, 0 }, + + { "unexpected_interrupts", 0, &print_unex, 1 }, + { "no_unexpected_interrupts", 0, &print_unex, 0 }, + { "L40SX", 0, &print_unex, 0 } }; + +#define FLOPPY_SETUP +void floppy_setup(char *str, int *ints) +{ + int i; + int param; + if (str) + for (i=0; i< ARRAY_SIZE(config_params); i++){ + if (strcmp(str,config_params[i].name) == 0){ + if (ints[0]) + param = ints[1]; + else + param = config_params[i].def_param; + if(config_params[i].fn) + config_params[i].fn(ints,param); + if(config_params[i].var) { + DPRINT("%s=%d\n", str, param); + *config_params[i].var = param; + } + return; + } + } + if (str) { + DPRINT("unknown floppy option [%s]\n", str); + + DPRINT("allowed options are:"); + for (i=0; i< ARRAY_SIZE(config_params); i++) + printk(" %s",config_params[i].name); + printk("\n"); + } else + DPRINT("botched floppy option\n"); + DPRINT("Read linux/drivers/block/README.fd\n"); +} + +int floppy_init(void) +{ + int i,unit,drive; + int have_no_fdc= -EIO; + + raw_cmd = 0; + + if (register_blkdev(MAJOR_NR,"fd",&floppy_fops)) { + printk("Unable to get major %d for floppy\n",MAJOR_NR); + return -EBUSY; + } + + for (i=0; i<256; i++) + if (ITYPE(i)) + floppy_sizes[i] = floppy_type[ITYPE(i)].size >> 1; + else + floppy_sizes[i] = MAX_DISK_SIZE; + + blk_size[MAJOR_NR] = floppy_sizes; + blksize_size[MAJOR_NR] = floppy_blocksizes; + blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST; + reschedule_timeout(MAXTIMEOUT, "floppy init", MAXTIMEOUT); + config_types(); + + for (i = 0; i < N_FDC; i++) { + fdc = i; + CLEARSTRUCT(FDCS); + FDCS->dtr = -1; + FDCS->dor = 0x4; +#ifdef __sparc__ + /*sparcs don't have a DOR reset which we can fall back on to*/ + FDCS->version = FDC_82072A; +#endif + } + + fdc_state[0].address = FDC1; +#if N_FDC > 1 + fdc_state[1].address = FDC2; +#endif + + if (floppy_grab_irq_and_dma()){ + del_timer(&fd_timeout); + blk_dev[MAJOR_NR].request_fn = NULL; + unregister_blkdev(MAJOR_NR,"fd"); + return -EBUSY; + } + + /* initialise drive state */ + for (drive = 0; drive < N_DRIVE; drive++) { + CLEARSTRUCT(UDRS); + CLEARSTRUCT(UDRWE); + UDRS->flags = FD_VERIFY | FD_DISK_NEWCHANGE | FD_DISK_CHANGED; + UDRS->fd_device = -1; + floppy_track_buffer = NULL; + max_buffer_sectors = 0; + } + + for (i = 0; i < N_FDC; i++) { + fdc = i; + FDCS->driver_version = FD_DRIVER_VERSION; + for (unit=0; unit<4; unit++) + FDCS->track[unit] = 0; + if (FDCS->address == -1) + continue; + FDCS->rawcmd = 2; + if (user_reset_fdc(-1,FD_RESET_ALWAYS,0)){ + FDCS->address = -1; + FDCS->version = FDC_NONE; + continue; + } + /* Try to determine the floppy controller type */ + FDCS->version = get_fdc_version(); + if (FDCS->version == FDC_NONE){ + FDCS->address = -1; + continue; + } + + request_region(FDCS->address, 6, "floppy"); + request_region(FDCS->address+7, 1, "floppy DIR"); + /* address + 6 is reserved, and may be taken by IDE. + * Unfortunately, Adaptec doesn't know this :-(, */ + + have_no_fdc = 0; + /* Not all FDCs seem to be able to handle the version command + * properly, so force a reset for the standard FDC clones, + * to avoid interrupt garbage. + */ + user_reset_fdc(-1,FD_RESET_ALWAYS,0); + } + fdc=0; + del_timer(&fd_timeout); + current_drive = 0; + floppy_release_irq_and_dma(); + initialising=0; + if (have_no_fdc) { + DPRINT("no floppy controllers found\n"); + request_tq.routine = (void *)(void *) empty; + /* + * When we return we may be unloaded. This little + * trick forces the immediate_bh handler to have run + * before we unload it, lest we cause bad things. + */ + mark_bh(IMMEDIATE_BH); + schedule(); + if (usage_count) + floppy_release_irq_and_dma(); + blk_dev[MAJOR_NR].request_fn = NULL; + unregister_blkdev(MAJOR_NR,"fd"); + } + return have_no_fdc; +} + +static int floppy_grab_irq_and_dma(void) +{ + int i; + unsigned long flags; + + INT_OFF; + if (usage_count++){ + INT_ON; + return 0; + } + INT_ON; + MOD_INC_USE_COUNT; + for (i=0; i< N_FDC; i++){ + if (fdc_state[i].address != -1){ + fdc = i; + reset_fdc_info(1); + fd_outb(FDCS->dor, FD_DOR); + } + } + fdc = 0; + set_dor(0, ~0, 8); /* avoid immediate interrupt */ + + if (fd_request_irq()) { + DPRINT("Unable to grab IRQ%d for the floppy driver\n", + FLOPPY_IRQ); + MOD_DEC_USE_COUNT; + usage_count--; + return -1; + } + if (fd_request_dma()) { + DPRINT("Unable to grab DMA%d for the floppy driver\n", + FLOPPY_DMA); + fd_free_irq(); + MOD_DEC_USE_COUNT; + usage_count--; + return -1; + } + for (fdc = 0; fdc < N_FDC; fdc++) + if (FDCS->address != -1) + fd_outb(FDCS->dor, FD_DOR); + fdc = 0; + fd_enable_irq(); + irqdma_allocated=1; + return 0; +} + +static void floppy_release_irq_and_dma(void) +{ +#ifdef FLOPPY_SANITY_CHECK + int drive; +#endif + long tmpsize; + unsigned long tmpaddr; + unsigned long flags; + + INT_OFF; + if (--usage_count){ + INT_ON; + return; + } + INT_ON; + if(irqdma_allocated) + { + fd_disable_dma(); + fd_free_dma(); + fd_disable_irq(); + fd_free_irq(); + irqdma_allocated=0; + } + + set_dor(0, ~0, 8); +#if N_FDC > 1 + set_dor(1, ~8, 0); +#endif + floppy_enable_hlt(); + + if (floppy_track_buffer && max_buffer_sectors) { + tmpsize = max_buffer_sectors*1024; + tmpaddr = (unsigned long)floppy_track_buffer; + floppy_track_buffer = 0; + max_buffer_sectors = 0; + buffer_min = buffer_max = -1; + fd_dma_mem_free(tmpaddr, tmpsize); + } + +#ifdef FLOPPY_SANITY_CHECK +#ifndef __sparc__ + for (drive=0; drive < N_FDC * 4; drive++) + if (motor_off_timer[drive].next) + printk("motor off timer %d still active\n", drive); +#endif + + if (fd_timeout.next) + printk("floppy timer still active:%s\n", timeout_message); + if (fd_timer.next) + printk("auxiliary floppy timer still active\n"); + if (floppy_tq.sync) + printk("task queue still active\n"); +#endif + MOD_DEC_USE_COUNT; +} + + +#ifdef MODULE + +char *floppy=NULL; + +static void parse_floppy_cfg_string(char *cfg) +{ + char *ptr; + int ints[11]; + + while(*cfg) { + for(ptr = cfg;*cfg && *cfg != ' ' && *cfg != '\t'; cfg++); + if(*cfg) { + *cfg = '\0'; + cfg++; + } + if(*ptr) + floppy_setup(get_options(ptr,ints),ints); + } +} + +static void mod_setup(char *pattern, void (*setup)(char *, int *)) +{ + unsigned long i; + char c; + int j; + int match; + char buffer[100]; + int ints[11]; + int length = strlen(pattern)+1; + + match=0; + j=1; + + for (i=current->mm->env_start; i< current->mm->env_end; i ++){ + c= get_fs_byte(i); + if (match){ + if (j==99) + c='\0'; + buffer[j] = c; + if (!c || c == ' ' || c == '\t'){ + if (j){ + buffer[j] = '\0'; + setup(get_options(buffer,ints),ints); + } + j=0; + } else + j++; + if (!c) + break; + continue; + } + if ((!j && !c) || (j && c == pattern[j-1])) + j++; + else + j=0; + if (j==length){ + match=1; + j=0; + } + } +} + + +#ifdef __cplusplus +extern "C" { +#endif +int init_module(void) +{ + printk(KERN_INFO "inserting floppy driver for %s\n", kernel_version); + + if(floppy) + parse_floppy_cfg_string(floppy); + else + mod_setup("floppy=", floppy_setup); + + return floppy_init(); +} + +void cleanup_module(void) +{ + int fdc, dummy; + + for (fdc=0; fdc<2; fdc++) + if (FDCS->address != -1){ + release_region(FDCS->address, 6); + release_region(FDCS->address+7, 1); + } + + unregister_blkdev(MAJOR_NR, "fd"); + + blk_dev[MAJOR_NR].request_fn = 0; + /* eject disk, if any */ + dummy = fd_eject(0); +} + +#ifdef __cplusplus +} +#endif + +#else +/* eject the boot floppy (if we need the drive for a different root floppy) */ +/* This should only be called at boot time when we're sure that there's no + * resource contention. */ +void floppy_eject(void) +{ + if(floppy_grab_irq_and_dma()==0) + { + lock_fdc(MAXTIMEOUT,0); + fd_eject(0); + process_fd_request(); + floppy_release_irq_and_dma(); + } +} +#endif diff --git a/linux/dev/drivers/block/genhd.c b/linux/dev/drivers/block/genhd.c new file mode 100644 index 0000000..903135c --- /dev/null +++ b/linux/dev/drivers/block/genhd.c @@ -0,0 +1,1080 @@ +/* + * Code extracted from + * linux/kernel/hd.c + * + * Copyright (C) 1991, 1992 Linus Torvalds + * + * + * Thanks to Branko Lankester, lankeste@fwi.uva.nl, who found a bug + * in the early extended-partition checks and added DM partitions + * + * Support for DiskManager v6.0x added by Mark Lord, + * with information provided by OnTrack. This now works for linux fdisk + * and LILO, as well as loadlin and bootln. Note that disks other than + * /dev/hda *must* have a "DOS" type 0x51 partition in the first slot (hda1). + * + * More flexible handling of extended partitions - aeb, 950831 + * + * Check partition table on IDE disks for common CHS translations + */ + +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_BLK_DEV_INITRD +#include +#endif +#include +#include +#ifdef CONFIG_GPT_DISKLABEL +#include +#include +#include +#endif + +#include + +/* + * Many architectures don't like unaligned accesses, which is + * frequently the case with the nr_sects and start_sect partition + * table entries. + */ +#include + +#ifdef MACH +#include +#include +#endif + +#define SYS_IND(p) get_unaligned(&p->sys_ind) +#define NR_SECTS(p) get_unaligned(&p->nr_sects) +#define START_SECT(p) get_unaligned(&p->start_sect) + + +struct gendisk *gendisk_head = NULL; + +static int current_minor = 0; +extern int *blk_size[]; +extern void rd_load(void); +extern void initrd_load(void); + +extern int chr_dev_init(void); +extern int blk_dev_init(void); +extern int scsi_dev_init(void); +extern int net_dev_init(void); + +/* + * disk_name() is used by genhd.c and md.c. + * It formats the devicename of the indicated disk + * into the supplied buffer, and returns a pointer + * to that same buffer (for convenience). + */ +char *disk_name (struct gendisk *hd, int minor, char *buf) +{ + unsigned int part; + const char *maj = hd->major_name; +#ifdef MACH + char unit = (minor >> hd->minor_shift) + '0'; +#else + char unit = (minor >> hd->minor_shift) + 'a'; +#endif + +#ifdef CONFIG_BLK_DEV_IDE + /* + * IDE devices use multiple major numbers, but the drives + * are named as: {hda,hdb}, {hdc,hdd}, {hde,hdf}, {hdg,hdh}.. + * This requires special handling here. + */ + switch (hd->major) { + case IDE3_MAJOR: + unit += 2; + case IDE2_MAJOR: + unit += 2; + case IDE1_MAJOR: + unit += 2; + case IDE0_MAJOR: + maj = "hd"; + } +#endif + part = minor & ((1 << hd->minor_shift) - 1); + if (part) +#ifdef MACH + sprintf(buf, "%s%cs%d", maj, unit, part); +#else + sprintf(buf, "%s%c%d", maj, unit, part); +#endif + else + sprintf(buf, "%s%c", maj, unit); + return buf; +} + +static void add_partition (struct gendisk *hd, int minor, int start, int size) +{ + char buf[8]; + hd->part[minor].start_sect = start; + hd->part[minor].nr_sects = size; + printk(" %s", disk_name(hd, minor, buf)); +} + +#if defined (MACH) && defined (CONFIG_BSD_DISKLABEL) +static int mach_minor; +static void +add_bsd_partition (struct gendisk *hd, int minor, int slice, + int start, int size) +{ + char buf[16]; + hd->part[minor].start_sect = start; + hd->part[minor].nr_sects = size; + printk (" %s%c", disk_name (hd, mach_minor, buf), slice); +} +#endif + +static inline int is_extended_partition(struct partition *p) +{ + return (SYS_IND(p) == DOS_EXTENDED_PARTITION || + SYS_IND(p) == WIN98_EXTENDED_PARTITION || + SYS_IND(p) == LINUX_EXTENDED_PARTITION); +} + +#ifdef CONFIG_MSDOS_PARTITION +/* + * Create devices for each logical partition in an extended partition. + * The logical partitions form a linked list, with each entry being + * a partition table with two entries. The first entry + * is the real data partition (with a start relative to the partition + * table start). The second is a pointer to the next logical partition + * (with a start relative to the entire extended partition). + * We do not create a Linux partition for the partition tables, but + * only for the actual data partitions. + */ + +static void extended_partition(struct gendisk *hd, kdev_t dev) +{ + struct buffer_head *bh; + struct partition *p; + unsigned long first_sector, first_size, this_sector, this_size; + int mask = (1 << hd->minor_shift) - 1; + int i; + + first_sector = hd->part[MINOR(dev)].start_sect; + first_size = hd->part[MINOR(dev)].nr_sects; + this_sector = first_sector; + + while (1) { + if ((current_minor & mask) == 0) + return; + if (!(bh = bread(dev,0,1024))) + return; + /* + * This block is from a device that we're about to stomp on. + * So make sure nobody thinks this block is usable. + */ + bh->b_state = 0; + + if (*(unsigned short *) (bh->b_data+510) != 0xAA55) + goto done; + + p = (struct partition *) (0x1BE + bh->b_data); + + this_size = hd->part[MINOR(dev)].nr_sects; + + /* + * Usually, the first entry is the real data partition, + * the 2nd entry is the next extended partition, or empty, + * and the 3rd and 4th entries are unused. + * However, DRDOS sometimes has the extended partition as + * the first entry (when the data partition is empty), + * and OS/2 seems to use all four entries. + */ + + /* + * First process the data partition(s) + */ + for (i=0; i<4; i++, p++) { + if (!NR_SECTS(p) || is_extended_partition(p)) + continue; + + /* Check the 3rd and 4th entries - + these sometimes contain random garbage */ + if (i >= 2 + && START_SECT(p) + NR_SECTS(p) > this_size + && (this_sector + START_SECT(p) < first_sector || + this_sector + START_SECT(p) + NR_SECTS(p) > + first_sector + first_size)) + continue; + + add_partition(hd, current_minor, this_sector+START_SECT(p), NR_SECTS(p)); + current_minor++; + if ((current_minor & mask) == 0) + goto done; + } + /* + * Next, process the (first) extended partition, if present. + * (So far, there seems to be no reason to make + * extended_partition() recursive and allow a tree + * of extended partitions.) + * It should be a link to the next logical partition. + * Create a minor for this just long enough to get the next + * partition table. The minor will be reused for the next + * data partition. + */ + p -= 4; + for (i=0; i<4; i++, p++) + if(NR_SECTS(p) && is_extended_partition(p)) + break; + if (i == 4) + goto done; /* nothing left to do */ + + hd->part[current_minor].nr_sects = NR_SECTS(p); + hd->part[current_minor].start_sect = first_sector + START_SECT(p); + this_sector = first_sector + START_SECT(p); + dev = MKDEV(hd->major, current_minor); + brelse(bh); + } +done: + brelse(bh); +} + +#ifdef CONFIG_BSD_DISKLABEL +/* + * Create devices for BSD partitions listed in a disklabel, under a + * dos-like partition. See extended_partition() for more information. + */ +static void bsd_disklabel_partition(struct gendisk *hd, kdev_t dev) +{ + struct buffer_head *bh; + struct bsd_disklabel *l; + struct bsd_partition *p; + int mask = (1 << hd->minor_shift) - 1; + + if (!(bh = bread(dev,0,1024))) + return; + bh->b_state = 0; + l = (struct bsd_disklabel *) (bh->b_data+512); + if (l->d_magic != BSD_DISKMAGIC) { + brelse(bh); + return; + } + + p = &l->d_partitions[0]; + while (p - &l->d_partitions[0] <= BSD_MAXPARTITIONS) { + if ((current_minor & mask) >= (4 + hd->max_p)) + break; + + if (p->p_fstype != BSD_FS_UNUSED) { +#ifdef MACH + add_bsd_partition (hd, current_minor, + p - &l->d_partitions[0] + 'a', + p->p_offset, p->p_size); +#else + add_partition(hd, current_minor, p->p_offset, p->p_size); +#endif + current_minor++; + } + p++; + } + brelse(bh); + +} +#endif + +#ifdef CONFIG_GPT_DISKLABEL +/* + * Compute a CRC32 but treat some range as if it were zeros. + * + * Straight copy of ether_crc_le() from linux/pcmcia-cs/include/linux/crc32.h, except for the first if/else + */ +static inline unsigned ether_crc_le_hole(int length, unsigned char *data, unsigned int skip_offset, unsigned int skip_length) +{ + static unsigned const ethernet_polynomial_le = 0xedb88320U; + unsigned int crc = 0xffffffff; /* Initial value. */ + while(--length >= 0) { + unsigned char current_octet = *data++; + if(skip_offset == 0 && skip_length-- != 0) + current_octet = 0; + else + --skip_offset; + int bit; + for (bit = 8; --bit >= 0; current_octet >>= 1) { + if ((crc ^ current_octet) & 1) { + crc >>= 1; + crc ^= ethernet_polynomial_le; + } else + crc >>= 1; + } + } + return crc; +} + +/* + * Read in a full GPT array into a contiguous chunk, allocates *PP_S bytes into *PP. + * + * An attempt to do as few round-trips as possible is made by reading a PAGE_SIZE at a time, + * since that's the bread() maximum. + */ +static int gpt_read_part_table(void **pp, vm_size_t *pp_s, kdev_t dev, int bsize, __u64 first_sector, struct gpt_disklabel_header *h) +{ + __u64 lba = first_sector + h->h_part_table_lba; + __u32 bytes_left = *pp_s = h->h_part_table_len * h->h_part_table_entry_size; + struct buffer_head *bh; + void *cur = *pp = (void *)kalloc(*pp_s); + if (!cur) { + printk(" unable to allocate GPT partition table buffer"); + return -2; + } + + while (bytes_left) { + unsigned bytes_to_read = MIN(bytes_left, PAGE_SIZE); + if(!(bh = bread(dev, lba, bytes_to_read))) { + printk(" unable to read partition table array"); + return -3; + } + + memcpy(cur, bh->b_data, bytes_to_read); + cur += bytes_to_read; + bytes_left -= bytes_to_read; + lba += PAGE_SIZE / bsize; + + brelse(bh); + } + + return 0; +} + +/* + * Sequence from section 5.3.2 of spec 2.8A: + * signature, CRC, lba_current matches, partition table CRC, primary: check backup for validity + */ +static int gpt_verify_header(void **pp, vm_size_t *pp_s, kdev_t dev, int bsize, __u64 first_sector, __u64 lba, struct gpt_disklabel_header *h) +{ + int res; + __u32 crc; + + if (memcmp(h->h_signature, GPT_SIGNATURE, strlen(GPT_SIGNATURE)) != 0) { + printk(" bad GPT signature \"%c%c%c%c%c%c%c%c\";", + h->h_signature[0], h->h_signature[1], h->h_signature[2], h->h_signature[3], + h->h_signature[4], h->h_signature[5], h->h_signature[6], h->h_signature[7]); + return 1; + } + + crc = ether_crc_le_hole(h->h_header_size, (void *)h, + offsetof(struct gpt_disklabel_header, h_header_crc), sizeof(h->h_header_crc)) ^ ~0; + if (crc != h->h_header_crc) { + printk(" bad header CRC: %x != %x;", crc, h->h_header_crc); + return 2; + } + + if (h->h_lba_current != lba) { + printk(" current LBA mismatch: %lld != %lld;", h->h_lba_current, lba); + return 3; + } + + if (*pp) { + kfree((vm_offset_t)*pp, *pp_s); + *pp = NULL; + } + if ((res = gpt_read_part_table(pp, pp_s, dev, bsize, first_sector, h))) + return res; + + crc = ether_crc_le_hole(*pp_s, *pp, 0, 0) ^ ~0; + if (crc != h->h_part_table_crc) { + printk(" bad partition table CRC: %x != %x;", crc, h->h_part_table_crc); + return 4; + } + + for (int i = h->h_header_size; i < bsize; ++i) + res |= ((char*)h)[i]; + if (res) { + printk(" rest of GPT block dirty;"); + return 5; + } + + return 0; +} + +static void gpt_print_part_name(struct gpt_disklabel_part *p) +{ + for(int n = 0; n < sizeof(p->p_name) / sizeof(*p->p_name) && p->p_name[n]; ++n) + if(p->p_name[n] & ~0xFF) + printk("?"); /* Can't support all of Unicode, but don't print garbage at least... */ + else + printk("%c", p->p_name[n]); +} + +#ifdef DEBUG +static void gpt_print_guid(struct gpt_guid *guid) +{ + printk("%08X-%04X-%04X-%02X%02X-", guid->g_time_low, guid->g_time_mid, guid->g_time_high_version, guid->g_clock_sec_high, guid->g_clock_sec_low); + for (int i = 0; i < sizeof(guid->g_node_id); ++i) + printk("%02X", guid->g_node_id[i]); +} + +static void gpt_dump_header(struct gpt_disklabel_header *h) +{ + printk(" [h_signature: \"%c%c%c%c%c%c%c%c\"; ", + h->h_signature[0], h->h_signature[1], h->h_signature[2], h->h_signature[3], + h->h_signature[4], h->h_signature[5], h->h_signature[6], h->h_signature[7]); + printk("h_revision: %x; ", h->h_revision); + printk("h_header_size: %u; ", h->h_header_size); + printk("h_header_crc: %x; ", h->h_header_crc); + printk("h_reserved: %u; ", h->h_reserved); + printk("h_lba_current: %llu; ", h->h_lba_current); + printk("h_lba_backup: %llu; ", h->h_lba_backup); + printk("h_lba_usable_first: %llu; ", h->h_lba_usable_first); + printk("h_lba_usable_last: %llu; ", h->h_lba_usable_last); + printk("h_guid: "); gpt_print_guid(&h->h_guid); printk("; "); + printk("h_part_table_lba: %llu; ", h->h_part_table_lba); + printk("h_part_table_len: %u; ", h->h_part_table_len); + printk("h_part_table_crc: %x]", h->h_part_table_crc); +} + +static void gpt_dump_part(struct gpt_disklabel_part *p, int i) +{ + printk(" part#%d:[", i); + printk("p_type: "); gpt_print_guid(&p->p_type); + printk("; p_guid:"); gpt_print_guid(&p->p_guid); + printk("; p_lba_first: %llu", p->p_lba_first); + printk("; p_lba_last: %llu", p->p_lba_last); + printk("; p_attrs: %llx", p->p_attrs); + printk("; p_name: \""); gpt_print_part_name(p); printk("\"]"); +} +#else +static void gpt_dump_header(struct gpt_disklabel_header *h) {} +static void gpt_dump_part(struct gpt_disklabel_part *p, int i) {} +#endif + +static int gpt_partition(struct gendisk *hd, kdev_t dev, __u64 first_sector, int minor) +{ + struct buffer_head *bh; + struct gpt_disklabel_header *h; + void *pp = NULL; vm_size_t pp_s = 0; + int res, bsize = 512; + /* Note: this must be set by the driver; SCSI does -- + * only, in practice, it always sets this to 512, see sd_init() in sd.c */ + if (hardsect_size[MAJOR(dev)] && hardsect_size[MAJOR(dev)][MINOR(dev)]) + bsize = hardsect_size[MAJOR(dev)][MINOR(dev)]; + set_blocksize(dev,bsize); /* Must override read block size since GPT has pointers, stolen from amiga_partition(). */ + if (!(bh = bread(dev, first_sector + 1, bsize))) { + printk("unable to read GPT"); + res = -1; + goto done; + } + + h = (struct gpt_disklabel_header *)bh->b_data; + gpt_dump_header(h); + + res = gpt_verify_header(&pp, &pp_s, dev, bsize, first_sector, 1, h); + if (res < 0) + goto done; + else if (res > 0) { + printk(" main GPT dirty, trying backup at %llu;", h->h_lba_backup); + __u64 lba = h->h_lba_backup; + brelse(bh); + + if (!(bh = bread(dev, first_sector + lba, bsize))) { + printk("unable to read backup GPT"); + res = -4; + goto done; + } + + h = (struct gpt_disklabel_header *)bh->b_data; + gpt_dump_header(h); + + res = gpt_verify_header(&pp, &pp_s, dev, bsize, first_sector, lba, h); + if (res < 0) + goto done; + else if (res > 0) { + printk(" backup GPT dirty as well; cowardly refusing to continue"); + res = -5; + goto done; + } + } + + /* At least one good GPT+array */ + + for(int i = 0; i < h->h_part_table_len; ++i, ++minor) { + struct gpt_disklabel_part *p = + (struct gpt_disklabel_part *) (pp + i * h->h_part_table_entry_size); + if(memcmp(&p->p_type, &GPT_GUID_TYPE_UNUSED, sizeof(struct gpt_guid)) == 0) + continue; + gpt_dump_part(p, i); + + if (minor > hd->max_nr * hd->max_p) { + printk(" [ignoring GPT partition %d \"", i); gpt_print_part_name(p); printk("\": too many partitions (max %d)]", hd->max_p); + } else { + add_partition(hd, minor, first_sector + p->p_lba_first, p->p_lba_last - p->p_lba_first + 1); + if(p->p_name[0]) { + printk(" ("); gpt_print_part_name(p); printk(")"); + } + } + } + +done: + brelse(bh); + set_blocksize(dev,BLOCK_SIZE); + kfree((vm_offset_t)pp, pp_s); + printk("\n"); + return !res; +} +#endif + +static int msdos_partition(struct gendisk *hd, kdev_t dev, unsigned long first_sector) +{ + int i, minor = current_minor; + struct buffer_head *bh; + struct partition *p; + unsigned char *data; + int mask = (1 << hd->minor_shift) - 1; +#ifdef CONFIG_BLK_DEV_IDE + int tested_for_xlate = 0; + +read_mbr: +#endif + if (!(bh = bread(dev,0,1024))) { + printk(" unable to read partition table\n"); + return -1; + } + data = (unsigned char *)bh->b_data; + /* In some cases we modify the geometry */ + /* of the drive (below), so ensure that */ + /* nobody else tries to re-use this data. */ + bh->b_state = 0; +#ifdef CONFIG_BLK_DEV_IDE +check_table: +#endif + if (*(unsigned short *) (0x1fe + data) != 0xAA55) { + brelse(bh); + return 0; + } + p = (struct partition *) (0x1be + data); + +#ifdef CONFIG_BLK_DEV_IDE + if (!tested_for_xlate++) { /* Do this only once per disk */ + /* + * Look for various forms of IDE disk geometry translation + */ + extern int ide_xlate_1024(kdev_t, int, const char *); + unsigned int sig = *(unsigned short *)(data + 2); + if (SYS_IND(p) == EZD_PARTITION) { + /* + * The remainder of the disk must be accessed using + * a translated geometry that reduces the number of + * apparent cylinders to less than 1024 if possible. + * + * ide_xlate_1024() will take care of the necessary + * adjustments to fool fdisk/LILO and partition check. + */ + if (ide_xlate_1024(dev, -1, " [EZD]")) { + data += 512; + goto check_table; + } + } else if (SYS_IND(p) == DM6_PARTITION) { + + /* + * Everything on the disk is offset by 63 sectors, + * including a "new" MBR with its own partition table, + * and the remainder of the disk must be accessed using + * a translated geometry that reduces the number of + * apparent cylinders to less than 1024 if possible. + * + * ide_xlate_1024() will take care of the necessary + * adjustments to fool fdisk/LILO and partition check. + */ + if (ide_xlate_1024(dev, 1, " [DM6:DDO]")) { + brelse(bh); + goto read_mbr; /* start over with new MBR */ + } + } else if (sig <= 0x1ae && *(unsigned short *)(data + sig) == 0x55AA + && (1 & *(unsigned char *)(data + sig + 2)) ) + { + /* + * DM6 signature in MBR, courtesy of OnTrack + */ + (void) ide_xlate_1024 (dev, 0, " [DM6:MBR]"); + } else if (SYS_IND(p) == DM6_AUX1PARTITION || SYS_IND(p) == DM6_AUX3PARTITION) { + /* + * DM6 on other than the first (boot) drive + */ + (void) ide_xlate_1024(dev, 0, " [DM6:AUX]"); + } else { + /* + * Examine the partition table for common translations. + * This is necessary for drives for situations where + * the translated geometry is unavailable from the BIOS. + */ + for (i = 0; i < 4 ; i++) { + struct partition *q = &p[i]; + if (NR_SECTS(q) + && (q->sector & 63) == 1 + && (q->end_sector & 63) == 63) { + unsigned int heads = q->end_head + 1; + if (heads == 32 || heads == 64 || heads == 128 || heads == 255) { + + (void) ide_xlate_1024(dev, heads, " [PTBL]"); + break; + } + } + } + } + } +#endif /* CONFIG_BLK_DEV_IDE */ + + current_minor += 4; /* first "extra" minor (for extended partitions) */ + for (i=1 ; i<=4 ; minor++,i++,p++) { + if (!NR_SECTS(p)) + continue; +#ifdef CONFIG_GPT_DISKLABEL + if (SYS_IND(p) == GPT_PARTITION) { + brelse(bh); + return gpt_partition(hd, dev, first_sector, minor); + } else +#endif + add_partition(hd, minor, first_sector+START_SECT(p), NR_SECTS(p)); + if (is_extended_partition(p)) { + printk(" <"); + /* + * If we are rereading the partition table, we need + * to set the size of the partition so that we will + * be able to bread the block containing the extended + * partition info. + */ + hd->sizes[minor] = hd->part[minor].nr_sects + >> (BLOCK_SIZE_BITS - 9); + extended_partition(hd, MKDEV(hd->major, minor)); + printk(" >"); + /* prevent someone doing mkfs or mkswap on an + extended partition, but leave room for LILO */ + if (hd->part[minor].nr_sects > 2) + hd->part[minor].nr_sects = 2; + } +#ifdef CONFIG_BSD_DISKLABEL + if (SYS_IND(p) == BSD_PARTITION) { + printk(" <"); +#ifdef MACH + mach_minor = minor; +#endif + bsd_disklabel_partition(hd, MKDEV(hd->major, minor)); + printk(" >"); + } +#endif + } + /* + * Check for old-style Disk Manager partition table + */ + if (*(unsigned short *) (data+0xfc) == 0x55AA) { + p = (struct partition *) (0x1be + data); + for (i = 4 ; i < 16 ; i++, current_minor++) { + p--; + if ((current_minor & mask) == 0) + break; + if (!(START_SECT(p) && NR_SECTS(p))) + continue; + add_partition(hd, current_minor, START_SECT(p), NR_SECTS(p)); + } + } + printk("\n"); + brelse(bh); + return 1; +} + +#endif /* CONFIG_MSDOS_PARTITION */ + +#ifdef CONFIG_OSF_PARTITION + +static int osf_partition(struct gendisk *hd, unsigned int dev, unsigned long first_sector) +{ + int i; + int mask = (1 << hd->minor_shift) - 1; + struct buffer_head *bh; + struct disklabel { + u32 d_magic; + u16 d_type,d_subtype; + u8 d_typename[16]; + u8 d_packname[16]; + u32 d_secsize; + u32 d_nsectors; + u32 d_ntracks; + u32 d_ncylinders; + u32 d_secpercyl; + u32 d_secprtunit; + u16 d_sparespertrack; + u16 d_sparespercyl; + u32 d_acylinders; + u16 d_rpm, d_interleave, d_trackskew, d_cylskew; + u32 d_headswitch, d_trkseek, d_flags; + u32 d_drivedata[5]; + u32 d_spare[5]; + u32 d_magic2; + u16 d_checksum; + u16 d_npartitions; + u32 d_bbsize, d_sbsize; + struct d_partition { + u32 p_size; + u32 p_offset; + u32 p_fsize; + u8 p_fstype; + u8 p_frag; + u16 p_cpg; + } d_partitions[8]; + } * label; + struct d_partition * partition; +#define DISKLABELMAGIC (0x82564557UL) + + if (!(bh = bread(dev,0,1024))) { + printk("unable to read partition table\n"); + return -1; + } + label = (struct disklabel *) (bh->b_data+64); + partition = label->d_partitions; + if (label->d_magic != DISKLABELMAGIC) { + printk("magic: %08x\n", label->d_magic); + brelse(bh); + return 0; + } + if (label->d_magic2 != DISKLABELMAGIC) { + printk("magic2: %08x\n", label->d_magic2); + brelse(bh); + return 0; + } + for (i = 0 ; i < label->d_npartitions; i++, partition++) { + if ((current_minor & mask) == 0) + break; + if (partition->p_size) + add_partition(hd, current_minor, + first_sector+partition->p_offset, + partition->p_size); + current_minor++; + } + printk("\n"); + brelse(bh); + return 1; +} + +#endif /* CONFIG_OSF_PARTITION */ + +#ifdef CONFIG_SUN_PARTITION + +static int sun_partition(struct gendisk *hd, kdev_t dev, unsigned long first_sector) +{ + int i, csum; + unsigned short *ush; + struct buffer_head *bh; + struct sun_disklabel { + unsigned char info[128]; /* Informative text string */ + unsigned char spare[292]; /* Boot information etc. */ + unsigned short rspeed; /* Disk rotational speed */ + unsigned short pcylcount; /* Physical cylinder count */ + unsigned short sparecyl; /* extra sects per cylinder */ + unsigned char spare2[4]; /* More magic... */ + unsigned short ilfact; /* Interleave factor */ + unsigned short ncyl; /* Data cylinder count */ + unsigned short nacyl; /* Alt. cylinder count */ + unsigned short ntrks; /* Tracks per cylinder */ + unsigned short nsect; /* Sectors per track */ + unsigned char spare3[4]; /* Even more magic... */ + struct sun_partition { + __u32 start_cylinder; + __u32 num_sectors; + } partitions[8]; + unsigned short magic; /* Magic number */ + unsigned short csum; /* Label xor'd checksum */ + } * label; + struct sun_partition *p; + int other_endian; + unsigned long spc; +#define SUN_LABEL_MAGIC 0xDABE +#define SUN_LABEL_MAGIC_SWAPPED 0xBEDA +/* No need to optimize these macros since they are called only when reading + * the partition table. This occurs only at each disk change. */ +#define SWAP16(x) (other_endian ? (((__u16)(x) & 0xFF) << 8) \ + | (((__u16)(x) & 0xFF00) >> 8) \ + : (__u16)(x)) +#define SWAP32(x) (other_endian ? (((__u32)(x) & 0xFF) << 24) \ + | (((__u32)(x) & 0xFF00) << 8) \ + | (((__u32)(x) & 0xFF0000) >> 8) \ + | (((__u32)(x) & 0xFF000000) >> 24) \ + : (__u32)(x)) + + if(!(bh = bread(dev, 0, 1024))) { + printk("Dev %s: unable to read partition table\n", + kdevname(dev)); + return -1; + } + label = (struct sun_disklabel *) bh->b_data; + p = label->partitions; + if (label->magic != SUN_LABEL_MAGIC && label->magic != SUN_LABEL_MAGIC_SWAPPED) { + printk("Dev %s Sun disklabel: bad magic %04x\n", + kdevname(dev), label->magic); + brelse(bh); + return 0; + } + other_endian = (label->magic == SUN_LABEL_MAGIC_SWAPPED); + /* Look at the checksum */ + ush = ((unsigned short *) (label+1)) - 1; + for(csum = 0; ush >= ((unsigned short *) label);) + csum ^= *ush--; + if(csum) { + printk("Dev %s Sun disklabel: Csum bad, label corrupted\n", + kdevname(dev)); + brelse(bh); + return 0; + } + /* All Sun disks have 8 partition entries */ + spc = SWAP16(label->ntrks) * SWAP16(label->nsect); + for(i=0; i < 8; i++, p++) { + unsigned long st_sector; + + /* We register all partitions, even if zero size, so that + * the minor numbers end up ok as per SunOS interpretation. + */ + st_sector = first_sector + SWAP32(p->start_cylinder) * spc; + add_partition(hd, current_minor, st_sector, SWAP32(p->num_sectors)); + current_minor++; + } + printk("\n"); + brelse(bh); + return 1; +#undef SWAP16 +#undef SWAP32 +} + +#endif /* CONFIG_SUN_PARTITION */ + +#ifdef CONFIG_AMIGA_PARTITION +#include +#include + +static __inline__ __u32 +checksum_block(__u32 *m, int size) +{ + __u32 sum = 0; + + while (size--) + sum += htonl(*m++); + return sum; +} + +static int +amiga_partition(struct gendisk *hd, unsigned int dev, unsigned long first_sector) +{ + struct buffer_head *bh; + struct RigidDiskBlock *rdb; + struct PartitionBlock *pb; + int start_sect; + int nr_sects; + int blk; + int part, res; + + set_blocksize(dev,512); + res = 0; + + for (blk = 0; blk < RDB_ALLOCATION_LIMIT; blk++) { + if(!(bh = bread(dev,blk,512))) { + printk("Dev %d: unable to read RDB block %d\n",dev,blk); + goto rdb_done; + } + if (*(__u32 *)bh->b_data == htonl(IDNAME_RIGIDDISK)) { + rdb = (struct RigidDiskBlock *)bh->b_data; + if (checksum_block((__u32 *)bh->b_data,htonl(rdb->rdb_SummedLongs) & 0x7F)) { + printk("Dev %d: RDB in block %d has bad checksum\n",dev,blk); + brelse(bh); + continue; + } + printk(" RDSK"); + blk = htonl(rdb->rdb_PartitionList); + brelse(bh); + for (part = 1; blk > 0 && part <= 16; part++) { + if (!(bh = bread(dev,blk,512))) { + printk("Dev %d: unable to read partition block %d\n", + dev,blk); + goto rdb_done; + } + pb = (struct PartitionBlock *)bh->b_data; + blk = htonl(pb->pb_Next); + if (pb->pb_ID == htonl(IDNAME_PARTITION) && checksum_block( + (__u32 *)pb,htonl(pb->pb_SummedLongs) & 0x7F) == 0 ) { + + /* Tell Kernel about it */ + + if (!(nr_sects = (htonl(pb->pb_Environment[10]) + 1 - + htonl(pb->pb_Environment[9])) * + htonl(pb->pb_Environment[3]) * + htonl(pb->pb_Environment[5]))) { + continue; + } + start_sect = htonl(pb->pb_Environment[9]) * + htonl(pb->pb_Environment[3]) * + htonl(pb->pb_Environment[5]); + add_partition(hd,current_minor,start_sect,nr_sects); + current_minor++; + res = 1; + } + brelse(bh); + } + printk("\n"); + break; + } + } + +rdb_done: + set_blocksize(dev,BLOCK_SIZE); + return res; +} +#endif /* CONFIG_AMIGA_PARTITION */ + +static void check_partition(struct gendisk *hd, kdev_t dev) +{ + static int first_time = 1; + unsigned long first_sector; + char buf[8]; + + if (first_time) + printk("Partition check (DOS partitions):\n"); + first_time = 0; + first_sector = hd->part[MINOR(dev)].start_sect; + + /* + * This is a kludge to allow the partition check to be + * skipped for specific drives (e.g. IDE cd-rom drives) + */ + if ((int)first_sector == -1) { + hd->part[MINOR(dev)].start_sect = 0; + return; + } + + printk(" %s:", disk_name(hd, MINOR(dev), buf)); +#ifdef CONFIG_MSDOS_PARTITION + if (msdos_partition(hd, dev, first_sector)) + return; +#endif +#ifdef CONFIG_OSF_PARTITION + if (osf_partition(hd, dev, first_sector)) + return; +#endif +#ifdef CONFIG_SUN_PARTITION + if(sun_partition(hd, dev, first_sector)) + return; +#endif +#ifdef CONFIG_AMIGA_PARTITION + if(amiga_partition(hd, dev, first_sector)) + return; +#endif + printk(" unknown partition table\n"); +} + +/* This function is used to re-read partition tables for removable disks. + Much of the cleanup from the old partition tables should have already been + done */ + +/* This function will re-read the partition tables for a given device, +and set things back up again. There are some important caveats, +however. You must ensure that no one is using the device, and no one +can start using the device while this function is being executed. */ + +void resetup_one_dev(struct gendisk *dev, int drive) +{ + int i; + int first_minor = drive << dev->minor_shift; + int end_minor = first_minor + dev->max_p; + + blk_size[dev->major] = NULL; + current_minor = 1 + first_minor; + check_partition(dev, MKDEV(dev->major, first_minor)); + + /* + * We need to set the sizes array before we will be able to access + * any of the partitions on this device. + */ + if (dev->sizes != NULL) { /* optional safeguard in ll_rw_blk.c */ + for (i = first_minor; i < end_minor; i++) + dev->sizes[i] = dev->part[i].nr_sects >> (BLOCK_SIZE_BITS - 9); + blk_size[dev->major] = dev->sizes; + } +} + +static void setup_dev(struct gendisk *dev) +{ + int i, drive; + int end_minor = dev->max_nr * dev->max_p; + + blk_size[dev->major] = NULL; + for (i = 0 ; i < end_minor; i++) { + dev->part[i].start_sect = 0; + dev->part[i].nr_sects = 0; + } + dev->init(dev); + for (drive = 0 ; drive < dev->nr_real ; drive++) { + int first_minor = drive << dev->minor_shift; + current_minor = 1 + first_minor; + check_partition(dev, MKDEV(dev->major, first_minor)); + } + if (dev->sizes != NULL) { /* optional safeguard in ll_rw_blk.c */ + for (i = 0; i < end_minor; i++) + dev->sizes[i] = dev->part[i].nr_sects >> (BLOCK_SIZE_BITS - 9); + blk_size[dev->major] = dev->sizes; + } +} + +void device_setup(void) +{ + extern void console_map_init(void); + struct gendisk *p; + int nr=0; + +#ifdef CONFIG_BLK_DEV_IDE + extern char *kernel_cmdline; + char *c, *param, *white; + + for (c = kernel_cmdline; c; ) + { + param = strstr(c, " ide"); + if (!param) + param = strstr(c, " hd"); + if (!param) + break; + if (param) { + param++; + white = strchr(param, ' '); + if (!white) { + ide_setup(param); + c = NULL; + } else { + char *word = alloca(white - param + 1); + strncpy(word, param, white - param); + word[white-param] = '\0'; + ide_setup(word); + c = white + 1; + } + } + } +#endif +#ifndef MACH + chr_dev_init(); +#endif + blk_dev_init(); + sti(); +#ifdef CONFIG_SCSI + scsi_dev_init(); +#endif +#ifdef CONFIG_INET + net_dev_init(); +#endif +#ifndef MACH + console_map_init(); +#endif + + for (p = gendisk_head ; p ; p=p->next) { + setup_dev(p); + nr += p->nr_real; + } +#ifdef CONFIG_BLK_DEV_RAM +#ifdef CONFIG_BLK_DEV_INITRD + if (initrd_start && mount_initrd) initrd_load(); + else +#endif + rd_load(); +#endif +} diff --git a/linux/dev/drivers/net/Space.c b/linux/dev/drivers/net/Space.c new file mode 100644 index 0000000..213fa9b --- /dev/null +++ b/linux/dev/drivers/net/Space.c @@ -0,0 +1,582 @@ +/* + * INET An implementation of the TCP/IP protocol suite for the LINUX + * operating system. INET is implemented using the BSD Socket + * interface as the means of communication with the user level. + * + * Holds initial configuration information for devices. + * + * NOTE: This file is a nice idea, but its current format does not work + * well for drivers that support multiple units, like the SLIP + * driver. We should actually have only one pointer to a driver + * here, with the driver knowing how many units it supports. + * Currently, the SLIP driver abuses the "base_addr" integer + * field of the 'device' structure to store the unit number... + * -FvK + * + * Version: @(#)Space.c 1.0.8 07/31/96 + * + * Authors: Ross Biro, + * Fred N. van Kempen, + * Donald J. Becker, + * + * FIXME: + * Sort the device chain fastest first. + * + * 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; either version + * 2 of the License, or (at your option) any later version. + */ +#include +#include +#include + +#define NEXT_DEV NULL + + +/* A unified ethernet device probe. This is the easiest way to have every + ethernet adaptor have the name "eth[0123...]". + */ + +extern int tulip_probe(struct device *dev); +extern int hp100_probe(struct device *dev); +extern int ultra_probe(struct device *dev); +extern int ultra32_probe(struct device *dev); +extern int wd_probe(struct device *dev); +extern int el2_probe(struct device *dev); +extern int ne_probe(struct device *dev); +extern int ne2k_pci_probe(struct device *dev); +extern int hp_probe(struct device *dev); +extern int hp_plus_probe(struct device *dev); +extern int znet_probe(struct device *); +extern int express_probe(struct device *); +extern int eepro_probe(struct device *); +extern int el3_probe(struct device *); +extern int at1500_probe(struct device *); +extern int at1700_probe(struct device *); +extern int fmv18x_probe(struct device *); +extern int eth16i_probe(struct device *); +extern int depca_probe(struct device *); +extern int apricot_probe(struct device *); +extern int ewrk3_probe(struct device *); +extern int de4x5_probe(struct device *); +extern int el1_probe(struct device *); +extern int via_rhine_probe(struct device *); +extern int natsemi_probe(struct device *); +extern int ns820_probe(struct device *); +extern int winbond840_probe(struct device *); +extern int hamachi_probe(struct device *); +extern int sundance_probe(struct device *); +extern int starfire_probe(struct device *); +extern int myson803_probe(struct device *); +extern int igige_probe(struct device *); +#if defined(CONFIG_WAVELAN) +extern int wavelan_probe(struct device *); +#endif /* defined(CONFIG_WAVELAN) */ +extern int el16_probe(struct device *); +extern int elplus_probe(struct device *); +extern int ac3200_probe(struct device *); +extern int e2100_probe(struct device *); +extern int ni52_probe(struct device *); +extern int ni65_probe(struct device *); +extern int SK_init(struct device *); +extern int seeq8005_probe(struct device *); +extern int tc59x_probe(struct device *); +extern int dgrs_probe(struct device *); +extern int smc_init( struct device * ); +extern int sparc_lance_probe(struct device *); +extern int atarilance_probe(struct device *); +extern int a2065_probe(struct device *); +extern int ariadne_probe(struct device *); +extern int hydra_probe(struct device *); +extern int yellowfin_probe(struct device *); +extern int eepro100_probe(struct device *); +extern int epic100_probe(struct device *); +extern int rtl8139_probe(struct device *); +extern int sis900_probe(struct device *); +extern int tlan_probe(struct device *); +extern int isa515_probe(struct device *); +extern int pcnet32_probe(struct device *); +extern int lance_probe(struct device *); +/* Detachable devices ("pocket adaptors") */ +extern int atp_init(struct device *); +extern int de600_probe(struct device *); +extern int de620_probe(struct device *); +extern int tc515_probe(struct device *); + +static int +ethif_probe(struct device *dev) +{ + u_long base_addr = dev->base_addr; + + if ((base_addr == 0xffe0) || (base_addr == 1)) + return 1; /* ENXIO */ + + if (1 + /* All PCI probes are safe, and thus should be first. */ +#ifdef CONFIG_DE4X5 /* DEC DE425, DE434, DE435 adapters */ + && de4x5_probe(dev) +#endif +#ifdef CONFIG_DGRS + && dgrs_probe(dev) +#endif +#ifdef CONFIG_EEXPRESS_PRO100B /* Intel EtherExpress Pro100B */ + && eepro100_probe(dev) +#endif +#ifdef CONFIG_EPIC + && epic100_probe(dev) +#endif +#if defined(CONFIG_HP100) + && hp100_probe(dev) +#endif +#if defined(CONFIG_NE2K_PCI) + && ne2k_pci_probe(dev) +#endif +#ifdef CONFIG_PCNET32 + && pcnet32_probe(dev) +#endif +#ifdef CONFIG_RTL8139 + && rtl8139_probe(dev) +#endif +#ifdef CONFIG_SIS900 + && sis900_probe(dev) +#endif +#ifdef CONFIG_VIA_RHINE + && via_rhine_probe(dev) +#endif +#ifdef CONFIG_NATSEMI + && natsemi_probe(dev) +#endif +#ifdef CONFIG_NS820 + && ns820_probe(dev) +#endif +#ifdef CONFIG_WINBOND840 + && winbond840_probe(dev) +#endif +#ifdef CONFIG_HAMACHI + && hamachi_probe(dev) +#endif +#ifdef CONFIG_SUNDANCE + && sundance_probe(dev) +#endif +#ifdef CONFIG_STARFIRE + && starfire_probe(dev) +#endif +#ifdef CONFIG_MYSON803 + && myson803_probe(dev) +#endif +#ifdef CONFIG_INTEL_GIGE + && igige_probe(dev) +#endif +#if defined(CONFIG_DEC_ELCP) + && tulip_probe(dev) +#endif +#ifdef CONFIG_YELLOWFIN + && yellowfin_probe(dev) +#endif + /* Next mostly-safe EISA-only drivers. */ +#ifdef CONFIG_AC3200 /* Ansel Communications EISA 3200. */ + && ac3200_probe(dev) +#endif +#if defined(CONFIG_ULTRA32) + && ultra32_probe(dev) +#endif + /* Third, sensitive ISA boards. */ +#ifdef CONFIG_AT1700 + && at1700_probe(dev) +#endif +#if defined(CONFIG_ULTRA) + && ultra_probe(dev) +#endif +#if defined(CONFIG_SMC9194) + && smc_init(dev) +#endif +#if defined(CONFIG_WD80x3) + && wd_probe(dev) +#endif +#if defined(CONFIG_EL2) /* 3c503 */ + && el2_probe(dev) +#endif +#if defined(CONFIG_HPLAN) + && hp_probe(dev) +#endif +#if defined(CONFIG_HPLAN_PLUS) + && hp_plus_probe(dev) +#endif +#if defined(CONFIG_SEEQ8005) + && seeq8005_probe(dev) +#endif +#ifdef CONFIG_E2100 /* Cabletron E21xx series. */ + && e2100_probe(dev) +#endif +#if defined(CONFIG_NE2000) + && ne_probe(dev) +#endif +#ifdef CONFIG_AT1500 + && at1500_probe(dev) +#endif +#ifdef CONFIG_FMV18X /* Fujitsu FMV-181/182 */ + && fmv18x_probe(dev) +#endif +#ifdef CONFIG_ETH16I + && eth16i_probe(dev) /* ICL EtherTeam 16i/32 */ +#endif +#ifdef CONFIG_EL3 /* 3c509 */ + && el3_probe(dev) +#endif +#if defined(CONFIG_VORTEX) + && tc59x_probe(dev) +#endif +#ifdef CONFIG_3C515 /* 3c515 */ + && tc515_probe(dev) +#endif +#ifdef CONFIG_ZNET /* Zenith Z-Note and some IBM Thinkpads. */ + && znet_probe(dev) +#endif +#ifdef CONFIG_EEXPRESS /* Intel EtherExpress */ + && express_probe(dev) +#endif +#ifdef CONFIG_EEXPRESS_PRO /* Intel EtherExpress Pro/10 */ + && eepro_probe(dev) +#endif +#ifdef CONFIG_DEPCA /* DEC DEPCA */ + && depca_probe(dev) +#endif +#ifdef CONFIG_EWRK3 /* DEC EtherWORKS 3 */ + && ewrk3_probe(dev) +#endif +#ifdef CONFIG_APRICOT /* Apricot I82596 */ + && apricot_probe(dev) +#endif +#ifdef CONFIG_EL1 /* 3c501 */ + && el1_probe(dev) +#endif +#if defined(CONFIG_WAVELAN) /* WaveLAN */ + && wavelan_probe(dev) +#endif /* defined(CONFIG_WAVELAN) */ +#ifdef CONFIG_EL16 /* 3c507 */ + && el16_probe(dev) +#endif +#ifdef CONFIG_ELPLUS /* 3c505 */ + && elplus_probe(dev) +#endif +#ifdef CONFIG_DE600 /* D-Link DE-600 adapter */ + && de600_probe(dev) +#endif +#ifdef CONFIG_DE620 /* D-Link DE-620 adapter */ + && de620_probe(dev) +#endif +#if defined(CONFIG_SK_G16) + && SK_init(dev) +#endif +#ifdef CONFIG_NI52 + && ni52_probe(dev) +#endif +#ifdef CONFIG_NI65 + && ni65_probe(dev) +#endif +#ifdef CONFIG_LANCE /* ISA LANCE boards */ + && lance_probe(dev) +#endif +#ifdef CONFIG_ATARILANCE /* Lance-based Atari ethernet boards */ + && atarilance_probe(dev) +#endif +#ifdef CONFIG_A2065 /* Commodore/Ameristar A2065 Ethernet Board */ + && a2065_probe(dev) +#endif +#ifdef CONFIG_ARIADNE /* Village Tronic Ariadne Ethernet Board */ + && ariadne_probe(dev) +#endif +#ifdef CONFIG_HYDRA /* Hydra Systems Amiganet Ethernet board */ + && hydra_probe(dev) +#endif +#ifdef CONFIG_SUNLANCE + && sparc_lance_probe(dev) +#endif +#ifdef CONFIG_TLAN + && tlan_probe(dev) +#endif +#ifdef CONFIG_LANCE + && lance_probe(dev) +#endif + && 1 ) { + return 1; /* -ENODEV or -EAGAIN would be more accurate. */ + } + return 0; +} + +#ifdef CONFIG_SDLA + extern int sdla_init(struct device *); + static struct device sdla0_dev = { "sdla0", 0, 0, 0, 0, 0, 0, 0, 0, 0, NEXT_DEV, sdla_init, }; + +# undef NEXT_DEV +# define NEXT_DEV (&sdla0_dev) +#endif + +#ifdef CONFIG_NETROM + extern int nr_init(struct device *); + + static struct device nr3_dev = { "nr3", 0, 0, 0, 0, 0, 0, 0, 0, 0, NEXT_DEV, nr_init, }; + static struct device nr2_dev = { "nr2", 0, 0, 0, 0, 0, 0, 0, 0, 0, &nr3_dev, nr_init, }; + static struct device nr1_dev = { "nr1", 0, 0, 0, 0, 0, 0, 0, 0, 0, &nr2_dev, nr_init, }; + static struct device nr0_dev = { "nr0", 0, 0, 0, 0, 0, 0, 0, 0, 0, &nr1_dev, nr_init, }; + +# undef NEXT_DEV +# define NEXT_DEV (&nr0_dev) +#endif + +/* Run-time ATtachable (Pocket) devices have a different (not "eth#") name. */ +#ifdef CONFIG_ATP /* AT-LAN-TEC (RealTek) pocket adaptor. */ +static struct device atp_dev = { + "atp0", 0, 0, 0, 0, 0, 0, 0, 0, 0, NEXT_DEV, atp_init, /* ... */ }; +# undef NEXT_DEV +# define NEXT_DEV (&atp_dev) +#endif + +#ifdef CONFIG_ARCNET + extern int arcnet_probe(struct device *dev); + static struct device arcnet_dev = { + "arc0", 0x0, 0x0, 0x0, 0x0, 0, 0, 0, 0, 0, NEXT_DEV, arcnet_probe, }; +# undef NEXT_DEV +# define NEXT_DEV (&arcnet_dev) +#endif + +/* In Mach, by default allow at least 2 interfaces. */ +#ifdef MACH +#ifndef ETH1_ADDR +# define ETH1_ADDR 0 +#endif +#ifndef ETH1_IRQ +# define ETH1_IRQ 0 +#endif +#endif + +/* The first device defaults to I/O base '0', which means autoprobe. */ +#ifndef ETH0_ADDR +# define ETH0_ADDR 0 +#endif +#ifndef ETH0_IRQ +# define ETH0_IRQ 0 +#endif +/* "eth0" defaults to autoprobe (== 0), other use a base of 0xffe0 (== -0x20), + which means "don't probe". These entries exist to only to provide empty + slots which may be enabled at boot-time. */ + +static struct device eth7_dev = { + "eth7", 0,0,0,0,0xffe0 /* I/O base*/, 0,0,0,0, NEXT_DEV, ethif_probe }; +static struct device eth6_dev = { + "eth6", 0,0,0,0,0xffe0 /* I/O base*/, 0,0,0,0, ð7_dev, ethif_probe }; +static struct device eth5_dev = { + "eth5", 0,0,0,0,0xffe0 /* I/O base*/, 0,0,0,0, ð6_dev, ethif_probe }; +static struct device eth4_dev = { + "eth4", 0,0,0,0,0xffe0 /* I/O base*/, 0,0,0,0, ð5_dev, ethif_probe }; +static struct device eth3_dev = { + "eth3", 0,0,0,0,0xffe0 /* I/O base*/, 0,0,0,0, ð4_dev, ethif_probe }; +static struct device eth2_dev = { + "eth2", 0,0,0,0,0xffe0 /* I/O base*/, 0,0,0,0, ð3_dev, ethif_probe }; + +#ifdef MACH +static struct device eth1_dev = { + "eth1", 0, 0, 0, 0, ETH1_ADDR, ETH1_IRQ, 0, 0, 0, ð2_dev, ethif_probe }; +#else +static struct device eth1_dev = { + "eth1", 0,0,0,0,0xffe0 /* I/O base*/, 0,0,0,0, ð2_dev, ethif_probe }; +#endif + +static struct device eth0_dev = { + "eth0", 0, 0, 0, 0, ETH0_ADDR, ETH0_IRQ, 0, 0, 0, ð1_dev, ethif_probe }; + +# undef NEXT_DEV +# define NEXT_DEV (ð0_dev) + +#if defined(PLIP) || defined(CONFIG_PLIP) + extern int plip_init(struct device *); + static struct device plip2_dev = { + "plip2", 0, 0, 0, 0, 0x278, 2, 0, 0, 0, NEXT_DEV, plip_init, }; + static struct device plip1_dev = { + "plip1", 0, 0, 0, 0, 0x378, 7, 0, 0, 0, &plip2_dev, plip_init, }; + static struct device plip0_dev = { + "plip0", 0, 0, 0, 0, 0x3BC, 5, 0, 0, 0, &plip1_dev, plip_init, }; +# undef NEXT_DEV +# define NEXT_DEV (&plip0_dev) +#endif /* PLIP */ + +#if defined(SLIP) || defined(CONFIG_SLIP) + /* To be exact, this node just hooks the initialization + routines to the device structures. */ +extern int slip_init_ctrl_dev(struct device *); +static struct device slip_bootstrap = { + "slip_proto", 0x0, 0x0, 0x0, 0x0, 0, 0, 0, 0, 0, NEXT_DEV, slip_init_ctrl_dev, }; +#undef NEXT_DEV +#define NEXT_DEV (&slip_bootstrap) +#endif /* SLIP */ + +#if defined(CONFIG_STRIP) +extern int strip_init_ctrl_dev(struct device *); +static struct device strip_bootstrap = { + "strip_proto", 0x0, 0x0, 0x0, 0x0, 0, 0, 0, 0, 0, NEXT_DEV, strip_init_ctrl_dev, }; +#undef NEXT_DEV +#define NEXT_DEV (&strip_bootstrap) +#endif /* STRIP */ + +#if defined(CONFIG_PPP) +extern int ppp_init(struct device *); +static struct device ppp_bootstrap = { + "ppp_proto", 0x0, 0x0, 0x0, 0x0, 0, 0, 0, 0, 0, NEXT_DEV, ppp_init, }; +#undef NEXT_DEV +#define NEXT_DEV (&ppp_bootstrap) +#endif /* PPP */ + +#ifdef CONFIG_DUMMY + extern int dummy_init(struct device *dev); + static struct device dummy_dev = { + "dummy", 0x0, 0x0, 0x0, 0x0, 0, 0, 0, 0, 0, NEXT_DEV, dummy_init, }; +# undef NEXT_DEV +# define NEXT_DEV (&dummy_dev) +#endif + +#ifdef CONFIG_EQUALIZER +extern int eql_init(struct device *dev); +struct device eql_dev = { + "eql", /* Master device for IP traffic load + balancing */ + 0x0, 0x0, 0x0, 0x0, /* recv end/start; mem end/start */ + 0, /* base I/O address */ + 0, /* IRQ */ + 0, 0, 0, /* flags */ + NEXT_DEV, /* next device */ + eql_init /* set up the rest */ +}; +# undef NEXT_DEV +# define NEXT_DEV (&eql_dev) +#endif + +#ifdef CONFIG_IBMTR + + extern int tok_probe(struct device *dev); + static struct device ibmtr_dev1 = { + "tr1", /* IBM Token Ring (Non-DMA) Interface */ + 0x0, /* recv memory end */ + 0x0, /* recv memory start */ + 0x0, /* memory end */ + 0x0, /* memory start */ + 0xa24, /* base I/O address */ + 0, /* IRQ */ + 0, 0, 0, /* flags */ + NEXT_DEV, /* next device */ + tok_probe /* ??? Token_init should set up the rest */ + }; +# undef NEXT_DEV +# define NEXT_DEV (&ibmtr_dev1) + + + static struct device ibmtr_dev0 = { + "tr0", /* IBM Token Ring (Non-DMA) Interface */ + 0x0, /* recv memory end */ + 0x0, /* recv memory start */ + 0x0, /* memory end */ + 0x0, /* memory start */ + 0xa20, /* base I/O address */ + 0, /* IRQ */ + 0, 0, 0, /* flags */ + NEXT_DEV, /* next device */ + tok_probe /* ??? Token_init should set up the rest */ + }; +# undef NEXT_DEV +# define NEXT_DEV (&ibmtr_dev0) + +#endif + +#ifdef CONFIG_DEFXX + extern int dfx_probe(struct device *dev); + static struct device fddi7_dev = + {"fddi7", 0, 0, 0, 0, 0, 0, 0, 0, 0, NEXT_DEV, dfx_probe}; + static struct device fddi6_dev = + {"fddi6", 0, 0, 0, 0, 0, 0, 0, 0, 0, &fddi7_dev, dfx_probe}; + static struct device fddi5_dev = + {"fddi5", 0, 0, 0, 0, 0, 0, 0, 0, 0, &fddi6_dev, dfx_probe}; + static struct device fddi4_dev = + {"fddi4", 0, 0, 0, 0, 0, 0, 0, 0, 0, &fddi5_dev, dfx_probe}; + static struct device fddi3_dev = + {"fddi3", 0, 0, 0, 0, 0, 0, 0, 0, 0, &fddi4_dev, dfx_probe}; + static struct device fddi2_dev = + {"fddi2", 0, 0, 0, 0, 0, 0, 0, 0, 0, &fddi3_dev, dfx_probe}; + static struct device fddi1_dev = + {"fddi1", 0, 0, 0, 0, 0, 0, 0, 0, 0, &fddi2_dev, dfx_probe}; + static struct device fddi0_dev = + {"fddi0", 0, 0, 0, 0, 0, 0, 0, 0, 0, &fddi1_dev, dfx_probe}; + +#undef NEXT_DEV +#define NEXT_DEV (&fddi0_dev) +#endif + +#ifdef CONFIG_NET_IPIP + extern int tunnel_init(struct device *); + + static struct device tunnel_dev1 = + { + "tunl1", /* IPIP tunnel */ + 0x0, /* recv memory end */ + 0x0, /* recv memory start */ + 0x0, /* memory end */ + 0x0, /* memory start */ + 0x0, /* base I/O address */ + 0, /* IRQ */ + 0, 0, 0, /* flags */ + NEXT_DEV, /* next device */ + tunnel_init /* Fill in the details */ + }; + + static struct device tunnel_dev0 = + { + "tunl0", /* IPIP tunnel */ + 0x0, /* recv memory end */ + 0x0, /* recv memory start */ + 0x0, /* memory end */ + 0x0, /* memory start */ + 0x0, /* base I/O address */ + 0, /* IRQ */ + 0, 0, 0, /* flags */ + &tunnel_dev1, /* next device */ + tunnel_init /* Fill in the details */ + }; +# undef NEXT_DEV +# define NEXT_DEV (&tunnel_dev0) + +#endif + +#ifdef CONFIG_APFDDI + extern int apfddi_init(struct device *dev); + static struct device fddi_dev = { + "fddi", 0x0, 0x0, 0x0, 0x0, 0, 0, 0, 0, 0, NEXT_DEV, apfddi_init }; +# undef NEXT_DEV +# define NEXT_DEV (&fddi_dev) +#endif + +#ifdef CONFIG_APBIF + extern int bif_init(struct device *dev); + static struct device bif_dev = { + "bif", 0x0, 0x0, 0x0, 0x0, 0, 0, 0, 0, 0, NEXT_DEV, bif_init }; +# undef NEXT_DEV +# define NEXT_DEV (&bif_dev) +#endif + +#ifdef MACH +struct device *dev_base = ð0_dev; +#else +extern int loopback_init(struct device *dev); +struct device loopback_dev = { + "lo", /* Software Loopback interface */ + 0x0, /* recv memory end */ + 0x0, /* recv memory start */ + 0x0, /* memory end */ + 0x0, /* memory start */ + 0, /* base I/O address */ + 0, /* IRQ */ + 0, 0, 0, /* flags */ + NEXT_DEV, /* next device */ + loopback_init /* loopback_init should set up the rest */ +}; + +struct device *dev_base = &loopback_dev; +#endif diff --git a/linux/dev/drivers/net/auto_irq.c b/linux/dev/drivers/net/auto_irq.c new file mode 100644 index 0000000..73cfe34 --- /dev/null +++ b/linux/dev/drivers/net/auto_irq.c @@ -0,0 +1,123 @@ +/* auto_irq.c: Auto-configure IRQ lines for linux. */ +/* + Written 1994 by Donald Becker. + + The author may be reached as becker@CESDIS.gsfc.nasa.gov, or C/O + Center of Excellence in Space Data and Information Sciences + Code 930.5, Goddard Space Flight Center, Greenbelt MD 20771 + + This code is a general-purpose IRQ line detector for devices with + jumpered IRQ lines. If you can make the device raise an IRQ (and + that IRQ line isn't already being used), these routines will tell + you what IRQ line it's using -- perfect for those oh-so-cool boot-time + device probes! + + To use this, first call autoirq_setup(timeout). TIMEOUT is how many + 'jiffies' (1/100 sec.) to detect other devices that have active IRQ lines, + and can usually be zero at boot. 'autoirq_setup()' returns the bit + vector of nominally-available IRQ lines (lines may be physically in-use, + but not yet registered to a device). + Next, set up your device to trigger an interrupt. + Finally call autoirq_report(TIMEOUT) to find out which IRQ line was + most recently active. The TIMEOUT should usually be zero, but may + be set to the number of jiffies to wait for a slow device to raise an IRQ. + + The idea of using the setup timeout to filter out bogus IRQs came from + the serial driver. + */ + + +#ifdef version +static const char *version = +"auto_irq.c:v1.11 Donald Becker (becker@cesdis.gsfc.nasa.gov)"; +#endif + +#include +#include +#include + +#define MACH_INCLUDE +#include +#include +#include +#include +#include +#include + +void *irq2dev_map[NR_IRQS] = {0, 0, /* ... zeroed */ }; + +unsigned long irqs_busy = 0x2147; /* The set of fixed IRQs (keyboard, timer, etc) */ +unsigned long irqs_used = 0x0001; /* The set of fixed IRQs sometimes enabled. */ +unsigned long irqs_reserved = 0x0000; /* An advisory "reserved" table. */ +unsigned long irqs_shared = 0x0000; /* IRQ lines "shared" among conforming cards. */ + +static volatile unsigned long irq_bitmap; /* The irqs we actually found. */ +static unsigned long irq_handled; /* The irq lines we have a handler on. */ +static volatile int irq_number; /* The latest irq number we actually found. */ + +static void +autoirq_probe (int irq, void *dev_id, struct pt_regs *regs) +{ + irq_number = irq; + set_bit (irq, (void *) &irq_bitmap); /* irq_bitmap |= 1 << irq; */ + /* This code used to disable the irq. However, the interrupt stub + * would then re-enable the interrupt with (potentially) disastrous + * consequences + */ + free_irq (irq, dev_id); + return; +} + +int +autoirq_setup (int waittime) +{ + int i; + unsigned long timeout = jiffies + waittime; + unsigned long boguscount = (waittime * loops_per_sec) / 100; + + irq_handled = 0; + irq_bitmap = 0; + + for (i = 0; i < 16; i++) + { + if (test_bit (i, &irqs_busy) == 0 + && request_irq (i, autoirq_probe, SA_INTERRUPT, "irq probe", NULL) == 0) + set_bit (i, (void *) &irq_handled); /* irq_handled |= 1 << i; */ + } + /* Update our USED lists. */ + irqs_used |= ~irq_handled; + + /* Hang out at least jiffies waiting for bogus IRQ hits. */ + while (timeout > jiffies && --boguscount > 0) + ; + + irq_handled &= ~irq_bitmap; + + irq_number = 0; /* We are interested in new interrupts from now on */ + + return irq_handled; +} + +int +autoirq_report (int waittime) +{ + int i; + unsigned long timeout = jiffies + waittime; + unsigned long boguscount = (waittime * loops_per_sec) / 100; + + /* Hang out at least jiffies waiting for the IRQ. */ + + while (timeout > jiffies && --boguscount > 0) + if (irq_number) + break; + + irq_handled &= ~irq_bitmap; /* This eliminates the already reset handlers */ + + /* Retract the irq handlers that we installed. */ + for (i = 0; i < 16; i++) + { + if (test_bit (i, (void *) &irq_handled)) + free_irq (i, NULL); + } + return irq_number; +} diff --git a/linux/dev/drivers/net/net_init.c b/linux/dev/drivers/net/net_init.c new file mode 100644 index 0000000..46dbb17 --- /dev/null +++ b/linux/dev/drivers/net/net_init.c @@ -0,0 +1,446 @@ +/* netdrv_init.c: Initialization for network devices. */ +/* + Written 1993,1994,1995 by Donald Becker. + + The author may be reached as becker@cesdis.gsfc.nasa.gov or + C/O Center of Excellence in Space Data and Information Sciences + Code 930.5, Goddard Space Flight Center, Greenbelt MD 20771 + + This file contains the initialization for the "pl14+" style ethernet + drivers. It should eventually replace most of drivers/net/Space.c. + It's primary advantage is that it's able to allocate low-memory buffers. + A secondary advantage is that the dangerous NE*000 netcards can reserve + their I/O port region before the SCSI probes start. + + Modifications/additions by Bjorn Ekwall : + ethdev_index[MAX_ETH_CARDS] + register_netdev() / unregister_netdev() + + Modifications by Wolfgang Walter + Use dev_close cleanly so we always shut things down tidily. + + Changed 29/10/95, Alan Cox to pass sockaddr's around for mac addresses. + + 14/06/96 - Paul Gortmaker: Add generic eth_change_mtu() function. + + August 12, 1996 - Lawrence V. Stefani: Added fddi_change_mtu() and + fddi_setup() functions. + Sept. 10, 1996 - Lawrence V. Stefani: Increased hard_header_len to + include 3 pad bytes. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_NET_ALIAS +#include +#endif + +/* The network devices currently exist only in the socket namespace, so these + entries are unused. The only ones that make sense are + open start the ethercard + close stop the ethercard + ioctl To get statistics, perhaps set the interface port (AUI, BNC, etc.) + One can also imagine getting raw packets using + read & write + but this is probably better handled by a raw packet socket. + + Given that almost all of these functions are handled in the current + socket-based scheme, putting ethercard devices in /dev/ seems pointless. + + [Removed all support for /dev network devices. When someone adds + streams then by magic we get them, but otherwise they are un-needed + and a space waste] +*/ + +/* The list of used and available "eth" slots (for "eth0", "eth1", etc.) */ +#define MAX_ETH_CARDS 16 /* same as the number if irq's in irq2dev[] */ +static struct device *ethdev_index[MAX_ETH_CARDS]; + + +/* Fill in the fields of the device structure with ethernet-generic values. + + If no device structure is passed, a new one is constructed, complete with + a SIZEOF_PRIVATE private data area. + + If an empty string area is passed as dev->name, or a new structure is made, + a new name string is constructed. The passed string area should be 8 bytes + long. + */ + +struct device * +init_etherdev(struct device *dev, int sizeof_priv) +{ + int new_device = 0; + int i; + + /* Use an existing correctly named device in Space.c:dev_base. */ + if (dev == NULL) { + int alloc_size = sizeof(struct device) + sizeof("eth%d ") + + sizeof_priv + 3; + struct device *cur_dev; + char pname[8]; /* Putative name for the device. */ + + for (i = 0; i < MAX_ETH_CARDS; ++i) + if (ethdev_index[i] == NULL) { + sprintf(pname, "eth%d", i); + for (cur_dev = dev_base; cur_dev; cur_dev = cur_dev->next) + if (strcmp(pname, cur_dev->name) == 0) { + dev = cur_dev; + dev->init = NULL; + sizeof_priv = (sizeof_priv + 3) & ~3; + dev->priv = sizeof_priv + ? kmalloc(sizeof_priv, GFP_KERNEL) + : NULL; + if (dev->priv) memset(dev->priv, 0, sizeof_priv); + goto found; + } + } + + alloc_size &= ~3; /* Round to dword boundary. */ + + dev = (struct device *)kmalloc(alloc_size, GFP_KERNEL); + memset(dev, 0, alloc_size); + if (sizeof_priv) + dev->priv = (void *) (dev + 1); + dev->name = sizeof_priv + (char *)(dev + 1); + new_device = 1; + } + + found: /* From the double loop above. */ + + if (dev->name && + ((dev->name[0] == '\0') || (dev->name[0] == ' '))) { + for (i = 0; i < MAX_ETH_CARDS; ++i) + if (ethdev_index[i] == NULL) { + sprintf(dev->name, "eth%d", i); + ethdev_index[i] = dev; + break; + } + } + + ether_setup(dev); /* Hmmm, should this be called here? */ + + if (new_device) { + /* Append the device to the device queue. */ + struct device **old_devp = &dev_base; + while ((*old_devp)->next) + old_devp = & (*old_devp)->next; + (*old_devp)->next = dev; + dev->next = 0; + } + return dev; +} + + +static int eth_mac_addr(struct device *dev, void *p) +{ + struct sockaddr *addr=p; + if(dev->start) + return -EBUSY; + memcpy(dev->dev_addr, addr->sa_data,dev->addr_len); + return 0; +} + +static int eth_change_mtu(struct device *dev, int new_mtu) +{ + if ((new_mtu < 68) || (new_mtu > 1500)) + return -EINVAL; + dev->mtu = new_mtu; + return 0; +} + +#ifdef CONFIG_FDDI + +static int fddi_change_mtu(struct device *dev, int new_mtu) +{ + if ((new_mtu < FDDI_K_SNAP_HLEN) || (new_mtu > FDDI_K_SNAP_DLEN)) + return(-EINVAL); + dev->mtu = new_mtu; + return(0); +} + +#endif + +void ether_setup(struct device *dev) +{ + int i; + /* Fill in the fields of the device structure with ethernet-generic values. + This should be in a common file instead of per-driver. */ + for (i = 0; i < DEV_NUMBUFFS; i++) + skb_queue_head_init(&dev->buffs[i]); + + /* register boot-defined "eth" devices */ + if (dev->name && (strncmp(dev->name, "eth", 3) == 0)) { + i = simple_strtoul(dev->name + 3, NULL, 0); + if (ethdev_index[i] == NULL) { + ethdev_index[i] = dev; + } + else if (dev != ethdev_index[i]) { + /* Really shouldn't happen! */ +#ifdef MACH + panic("ether_setup: Ouch! Someone else took %s\n", + dev->name); +#else + printk("ether_setup: Ouch! Someone else took %s\n", + dev->name); +#endif + } + } + +#ifndef MACH + dev->change_mtu = eth_change_mtu; + dev->hard_header = eth_header; + dev->rebuild_header = eth_rebuild_header; + dev->set_mac_address = eth_mac_addr; + dev->header_cache_bind = eth_header_cache_bind; + dev->header_cache_update= eth_header_cache_update; +#endif + + dev->type = ARPHRD_ETHER; + dev->hard_header_len = ETH_HLEN; + dev->mtu = 1500; /* eth_mtu */ + dev->addr_len = ETH_ALEN; + dev->tx_queue_len = 100; /* Ethernet wants good queues */ + + memset(dev->broadcast,0xFF, ETH_ALEN); + + /* New-style flags. */ + dev->flags = IFF_BROADCAST|IFF_MULTICAST; + dev->family = AF_INET; + dev->pa_addr = 0; + dev->pa_brdaddr = 0; + dev->pa_mask = 0; + dev->pa_alen = 4; +} + +#ifdef CONFIG_TR + +void tr_setup(struct device *dev) +{ + int i; + /* Fill in the fields of the device structure with ethernet-generic values. + This should be in a common file instead of per-driver. */ + for (i = 0; i < DEV_NUMBUFFS; i++) + skb_queue_head_init(&dev->buffs[i]); + + dev->hard_header = tr_header; + dev->rebuild_header = tr_rebuild_header; + + dev->type = ARPHRD_IEEE802; + dev->hard_header_len = TR_HLEN; + dev->mtu = 2000; /* bug in fragmenter...*/ + dev->addr_len = TR_ALEN; + dev->tx_queue_len = 100; /* Long queues on tr */ + + memset(dev->broadcast,0xFF, TR_ALEN); + + /* New-style flags. */ + dev->flags = IFF_BROADCAST; + dev->family = AF_INET; + dev->pa_addr = 0; + dev->pa_brdaddr = 0; + dev->pa_mask = 0; + dev->pa_alen = 4; +} + +#endif + +#ifdef CONFIG_FDDI + +void fddi_setup(struct device *dev) + { + int i; + + /* + * Fill in the fields of the device structure with FDDI-generic values. + * This should be in a common file instead of per-driver. + */ + for (i=0; i < DEV_NUMBUFFS; i++) + skb_queue_head_init(&dev->buffs[i]); + + dev->change_mtu = fddi_change_mtu; + dev->hard_header = fddi_header; + dev->rebuild_header = fddi_rebuild_header; + + dev->type = ARPHRD_FDDI; + dev->hard_header_len = FDDI_K_SNAP_HLEN+3; /* Assume 802.2 SNAP hdr len + 3 pad bytes */ + dev->mtu = FDDI_K_SNAP_DLEN; /* Assume max payload of 802.2 SNAP frame */ + dev->addr_len = FDDI_K_ALEN; + dev->tx_queue_len = 100; /* Long queues on FDDI */ + + memset(dev->broadcast, 0xFF, FDDI_K_ALEN); + + /* New-style flags */ + dev->flags = IFF_BROADCAST | IFF_MULTICAST; + dev->family = AF_INET; + dev->pa_addr = 0; + dev->pa_brdaddr = 0; + dev->pa_mask = 0; + dev->pa_alen = 4; + return; + } + +#endif + +int ether_config(struct device *dev, struct ifmap *map) +{ + if (map->mem_start != (u_long)(-1)) + dev->mem_start = map->mem_start; + if (map->mem_end != (u_long)(-1)) + dev->mem_end = map->mem_end; + if (map->base_addr != (u_short)(-1)) + dev->base_addr = map->base_addr; + if (map->irq != (u_char)(-1)) + dev->irq = map->irq; + if (map->dma != (u_char)(-1)) + dev->dma = map->dma; + if (map->port != (u_char)(-1)) + dev->if_port = map->port; + return 0; +} + +int register_netdev(struct device *dev) +{ + struct device *d = dev_base; + unsigned long flags; + int i=MAX_ETH_CARDS; + + save_flags(flags); + cli(); + + if (dev && dev->init) { + if (dev->name && + ((dev->name[0] == '\0') || (dev->name[0] == ' '))) { + for (i = 0; i < MAX_ETH_CARDS; ++i) + if (ethdev_index[i] == NULL) { + sprintf(dev->name, "eth%d", i); + printk("loading device '%s'...\n", dev->name); + ethdev_index[i] = dev; + break; + } + } + + sti(); /* device probes assume interrupts enabled */ + if (dev->init(dev) != 0) { + if (i < MAX_ETH_CARDS) ethdev_index[i] = NULL; + restore_flags(flags); + return -EIO; + } + cli(); + + /* Add device to end of chain */ + if (dev_base) { + while (d->next) + d = d->next; + d->next = dev; + } + else + dev_base = dev; + dev->next = NULL; + } + restore_flags(flags); + return 0; +} + +void unregister_netdev(struct device *dev) +{ + struct device *d = dev_base; + unsigned long flags; + int i; + + save_flags(flags); + cli(); + + if (dev == NULL) + { + printk("was NULL\n"); + restore_flags(flags); + return; + } + /* else */ + if (dev->start) + printk("ERROR '%s' busy and not MOD_IN_USE.\n", dev->name); + + /* + * must jump over main_device+aliases + * avoid alias devices unregistration so that only + * net_alias module manages them + */ +#ifdef CONFIG_NET_ALIAS + if (dev_base == dev) + dev_base = net_alias_nextdev(dev); + else + { + while(d && (net_alias_nextdev(d) != dev)) /* skip aliases */ + d = net_alias_nextdev(d); + + if (d && (net_alias_nextdev(d) == dev)) + { + /* + * Critical: Bypass by consider devices as blocks (maindev+aliases) + */ + net_alias_nextdev_set(d, net_alias_nextdev(dev)); + } +#else + if (dev_base == dev) + dev_base = dev->next; + else + { + while (d && (d->next != dev)) + d = d->next; + + if (d && (d->next == dev)) + { + d->next = dev->next; + } +#endif + else + { + printk("unregister_netdev: '%s' not found\n", dev->name); + restore_flags(flags); + return; + } + } + for (i = 0; i < MAX_ETH_CARDS; ++i) + { + if (ethdev_index[i] == dev) + { + ethdev_index[i] = NULL; + break; + } + } + + restore_flags(flags); + + /* + * You can i.e use a interfaces in a route though it is not up. + * We call close_dev (which is changed: it will down a device even if + * dev->flags==0 (but it will not call dev->stop if IFF_UP + * is not set). + * This will call notifier_call_chain(&netdev_chain, NETDEV_DOWN, dev), + * dev_mc_discard(dev), .... + */ + + dev_close(dev); +} + + +/* + * Local variables: + * compile-command: "gcc -D__KERNEL__ -I/usr/src/linux/net/inet -Wall -Wstrict-prototypes -O6 -m486 -c net_init.c" + * version-control: t + * kept-new-versions: 5 + * tab-width: 4 + * End: + */ diff --git a/linux/dev/drivers/net/wavelan.p.h b/linux/dev/drivers/net/wavelan.p.h new file mode 100644 index 0000000..0549844 --- /dev/null +++ b/linux/dev/drivers/net/wavelan.p.h @@ -0,0 +1,639 @@ +/* + * Wavelan ISA driver + * + * Jean II - HPLB '96 + * + * Reorganisation and extension of the driver. + * + * This file contain all definition and declarations necessary for the + * wavelan isa driver. This file is a private header, so it should + * be included only on wavelan.c !!! + */ + +#ifndef WAVELAN_P_H +#define WAVELAN_P_H + +/************************** DOCUMENTATION **************************/ +/* + * This driver provide a Linux interface to the Wavelan ISA hardware + * The Wavelan is a product of Lucent ("http://wavelan.netland.nl/"). + * This division was formerly part of NCR and then AT&T. + * Wavelan are also distributed by DEC (RoamAbout), Digital Ocean and + * Aironet (Arlan). If you have one of those product, you will need to + * make some changes below... + * + * This driver is still a beta software. A lot of bugs have been corrected, + * a lot of functionalities are implemented, the whole appear pretty stable, + * but there is still some area of improvement (encryption, performance...). + * + * To know how to use this driver, read the NET3 HOWTO. + * If you want to exploit the many other fonctionalities, look comments + * in the code... + * + * This driver is the result of the effort of many peoples (see below). + */ + +/* ------------------------ SPECIFIC NOTES ------------------------ */ +/* + * wavelan.o is darn too big + * ------------------------- + * That's true ! There is a very simple way to reduce the driver + * object by 33% (yes !). Comment out the following line : + * #include + * + * MAC address and hardware detection : + * ---------------------------------- + * The detection code of the wavelan chech that the first 3 + * octets of the MAC address fit the company code. This type of + * detection work well for AT&T cards (because the AT&T code is + * hardcoded in wavelan.h), but of course will fail for other + * manufacturer. + * + * If you are sure that your card is derived from the wavelan, + * here is the way to configure it : + * 1) Get your MAC address + * a) With your card utilities (wfreqsel, instconf, ...) + * b) With the driver : + * o compile the kernel with DEBUG_CONFIG_INFO enabled + * o Boot and look the card messages + * 2) Set your MAC code (3 octets) in MAC_ADDRESSES[][3] (wavelan.h) + * 3) Compile & verify + * 4) Send me the MAC code - I will include it in the next version... + * + * "CU Inactive" message at boot up : + * ----------------------------------- + * It seem that there is some weird timings problems with the + * Intel microcontroler. In fact, this message is triggered by a + * bad reading of the on board ram the first time we read the + * control block. If you ignore this message, all is ok (but in + * fact, currently, it reset the wavelan hardware). + * + * To get rid of that problem, there is two solution. The first + * is to add a dummy read of the scb at the end of + * wv_82586_config. The second is to add the timers + * wv_synchronous_cmd and wv_ack (the udelay just after the + * waiting loops - seem that the controler is not totally ready + * when it say it is !). + * + * In the current code, I use the second solution (to be + * consistent with the original solution of Bruce Janson). + */ + +/* --------------------- WIRELESS EXTENSIONS --------------------- */ +/* + * This driver is the first one to support "wireless extensions". + * This set of extensions provide you some way to control the wireless + * caracteristics of the hardware in a standard way and support for + * applications for taking advantage of it (like Mobile IP). + * + * You will need to enable the CONFIG_NET_RADIO define in the kernel + * configuration to enable the wireless extensions (this is the one + * giving access to the radio network device choice). + * + * It might also be a good idea as well to fetch the wireless tools to + * configure the device and play a bit. + */ + +/* ---------------------------- FILES ---------------------------- */ +/* + * wavelan.c : The actual code for the driver - C functions + * + * wavelan.p.h : Private header : local types / vars for the driver + * + * wavelan.h : Description of the hardware interface & structs + * + * i82586.h : Description if the Ethernet controler + */ + +/* --------------------------- HISTORY --------------------------- */ +/* + * (Made with information in drivers headers. It may not be accurate, + * and I garantee nothing except my best effort...) + * + * The history of the Wavelan drivers is as complicated as history of + * the Wavelan itself (NCR -> AT&T -> Lucent). + * + * All started with Anders Klemets , + * writting a Wavelan ISA driver for the MACH microkernel. Girish + * Welling had also worked on it. + * Keith Moore modify this for the Pcmcia hardware. + * + * Robert Morris port these two drivers to BSDI + * and add specific Pcmcia support (there is currently no equivalent + * of the PCMCIA package under BSD...). + * + * Jim Binkley port both BSDI drivers to freeBSD. + * + * Bruce Janson port the BSDI ISA driver to Linux. + * + * Anthony D. Joseph started modify Bruce driver + * (with help of the BSDI PCMCIA driver) for PCMCIA. + * Yunzhou Li finished is work. + * Joe Finney patched the driver to start + * correctly 2.00 cards (2.4 GHz with frequency selection). + * David Hinds integrated the whole in his + * Pcmcia package (+ bug corrections). + * + * I (Jean Tourrilhes - jt@hplb.hpl.hp.com) then started to make some + * patchs to the Pcmcia driver. After, I added code in the ISA driver + * for Wireless Extensions and full support of frequency selection + * cards. Then, I've done the same to the Pcmcia driver + some + * reorganisation. Finally, I came back to the ISA driver to + * upgrade it at the same level as the Pcmcia one and reorganise + * the code + * Loeke Brederveld from Lucent has given me + * much needed informations on the Wavelan hardware. + */ + +/* The original copyrights and litteratures mention others names and + * credits. I don't know what there part in this development was... + */ + +/* By the way : for the copyright & legal stuff : + * Almost everybody wrote code under GNU or BSD license (or alike), + * and want that their original copyright remain somewhere in the + * code (for myself, I go with the GPL). + * Nobody want to take responsibility for anything, except the fame... + */ + +/* --------------------------- CREDITS --------------------------- */ +/* + * This software was developed as a component of the + * Linux operating system. + * It is based on other device drivers and information + * either written or supplied by: + * Ajay Bakre (bakre@paul.rutgers.edu), + * Donald Becker (becker@cesdis.gsfc.nasa.gov), + * Loeke Brederveld (Loeke.Brederveld@Utrecht.NCR.com), + * Brent Elphick , + * Anders Klemets (klemets@it.kth.se), + * Vladimir V. Kolpakov (w@stier.koenig.ru), + * Marc Meertens (Marc.Meertens@Utrecht.NCR.com), + * Pauline Middelink (middelin@polyware.iaf.nl), + * Robert Morris (rtm@das.harvard.edu), + * Jean Tourrilhes (jt@hplb.hpl.hp.com), + * Girish Welling (welling@paul.rutgers.edu), + * Clark Woodworth + * Yongguang Zhang ... + * + * Thanks go also to: + * James Ashton (jaa101@syseng.anu.edu.au), + * Alan Cox (iialan@iiit.swan.ac.uk), + * Allan Creighton (allanc@cs.usyd.edu.au), + * Matthew Geier (matthew@cs.usyd.edu.au), + * Remo di Giovanni (remo@cs.usyd.edu.au), + * Eckhard Grah (grah@wrcs1.urz.uni-wuppertal.de), + * Vipul Gupta (vgupta@cs.binghamton.edu), + * Mark Hagan (mhagan@wtcpost.daytonoh.NCR.COM), + * Tim Nicholson (tim@cs.usyd.edu.au), + * Ian Parkin (ian@cs.usyd.edu.au), + * John Rosenberg (johnr@cs.usyd.edu.au), + * George Rossi (george@phm.gov.au), + * Arthur Scott (arthur@cs.usyd.edu.au), + * Stanislav Sinyagin + * Peter Storey, + * for their assistance and advice. + * + * Additional Credits: + * + * My developpement has been done under Linux 2.0.x (Debian 1.1) with + * an HP Vectra XP/60. + * + */ + +/* ------------------------- IMPROVEMENTS ------------------------- */ +/* + * I proudly present : + * + * Changes mades in first pre-release : + * ---------------------------------- + * - Reorganisation of the code, function name change + * - Creation of private header (wavelan.p.h) + * - Reorganised debug messages + * - More comments, history, ... + * - mmc_init : configure the PSA if not done + * - mmc_init : correct default value of level threshold for pcmcia + * - mmc_init : 2.00 detection better code for 2.00 init + * - better info at startup + * - irq setting (note : this setting is permanent...) + * - Watchdog : change strategy (+ solve module removal problems) + * - add wireless extensions (ioctl & get_wireless_stats) + * get/set nwid/frequency on fly, info for /proc/net/wireless + * - More wireless extension : SETSPY and GETSPY + * - Make wireless extensions optional + * - Private ioctl to set/get quality & level threshold, histogram + * - Remove /proc/net/wavelan + * - Supress useless stuff from lp (net_local) + * - kernel 2.1 support (copy_to/from_user instead of memcpy_to/fromfs) + * - Add message level (debug stuff in /var/adm/debug & errors not + * displayed at console and still in /var/adm/messages) + * - multi device support + * - Start fixing the probe (init code) + * - More inlines + * - man page + * - Lot of others minor details & cleanups + * + * Changes made in second pre-release : + * ---------------------------------- + * - Cleanup init code (probe & module init) + * - Better multi device support (module) + * - name assignement (module) + * + * Changes made in third pre-release : + * --------------------------------- + * - Be more conservative on timers + * - Preliminary support for multicast (I still lack some details...) + * + * Changes made in fourth pre-release : + * ---------------------------------- + * - multicast (revisited and finished) + * - Avoid reset in set_multicast_list (a really big hack) + * if somebody could apply this code for other i82586 based driver... + * - Share on board memory 75% RU / 25% CU (instead of 50/50) + * + * Changes made for release in 2.1.15 : + * ---------------------------------- + * - Change the detection code for multi manufacturer code support + * + * Changes made for release in 2.1.17 : + * ---------------------------------- + * - Update to wireless extensions changes + * - Silly bug in card initial configuration (psa_conf_status) + * + * Changes made for release in 2.1.27 & 2.0.30 : + * ------------------------------------------- + * - Small bug in debug code (probably not the last one...) + * - Remove extern kerword for wavelan_probe() + * - Level threshold is now a standard wireless extension (version 4 !) + * + * Changes made for release in 2.1.36 : + * ---------------------------------- + * - Encryption setting from Brent Elphick (thanks a lot !) + * - 'ioaddr' to 'u_long' for the Alpha (thanks to Stanislav Sinyagin) + * + * Wishes & dreams : + * --------------- + * - Roaming + */ + +/***************************** INCLUDES *****************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include /* Wireless extensions */ + +/* Wavelan declarations */ +#ifdef MACH +#include +#else +#include "i82586.h" +#endif +#include "wavelan.h" + +/****************************** DEBUG ******************************/ + +#undef DEBUG_MODULE_TRACE /* Module insertion/removal */ +#undef DEBUG_CALLBACK_TRACE /* Calls made by Linux */ +#undef DEBUG_INTERRUPT_TRACE /* Calls to handler */ +#undef DEBUG_INTERRUPT_INFO /* type of interrupt & so on */ +#define DEBUG_INTERRUPT_ERROR /* problems */ +#undef DEBUG_CONFIG_TRACE /* Trace the config functions */ +#undef DEBUG_CONFIG_INFO /* What's going on... */ +#define DEBUG_CONFIG_ERRORS /* Errors on configuration */ +#undef DEBUG_TX_TRACE /* Transmission calls */ +#undef DEBUG_TX_INFO /* Header of the transmited packet */ +#define DEBUG_TX_ERROR /* unexpected conditions */ +#undef DEBUG_RX_TRACE /* Transmission calls */ +#undef DEBUG_RX_INFO /* Header of the transmited packet */ +#define DEBUG_RX_ERROR /* unexpected conditions */ +#undef DEBUG_PACKET_DUMP 16 /* Dump packet on the screen */ +#undef DEBUG_IOCTL_TRACE /* Misc call by Linux */ +#undef DEBUG_IOCTL_INFO /* Various debug info */ +#define DEBUG_IOCTL_ERROR /* What's going wrong */ +#define DEBUG_BASIC_SHOW /* Show basic startup info */ +#undef DEBUG_VERSION_SHOW /* Print version info */ +#undef DEBUG_PSA_SHOW /* Dump psa to screen */ +#undef DEBUG_MMC_SHOW /* Dump mmc to screen */ +#undef DEBUG_SHOW_UNUSED /* Show also unused fields */ +#undef DEBUG_I82586_SHOW /* Show i82586 status */ +#undef DEBUG_DEVICE_SHOW /* Show device parameters */ + +/* Options : */ +#define USE_PSA_CONFIG /* Use info from the PSA */ +#define IGNORE_NORMAL_XMIT_ERRS /* Don't bother with normal conditions */ +#undef STRUCT_CHECK /* Verify padding of structures */ +#undef PSA_CRC /* Check CRC in PSA */ +#undef OLDIES /* Old code (to redo) */ +#undef RECORD_SNR /* To redo */ +#undef EEPROM_IS_PROTECTED /* Doesn't seem to be necessary */ +#define MULTICAST_AVOID /* Avoid extra multicast (I'm sceptical) */ + +#ifdef WIRELESS_EXT /* If wireless extension exist in the kernel */ +/* Warning : these stuff will slow down the driver... */ +#define WIRELESS_SPY /* Enable spying addresses */ +#undef HISTOGRAM /* Enable histogram of sig level... */ +#endif + +/************************ CONSTANTS & MACROS ************************/ + +#ifdef DEBUG_VERSION_SHOW +static const char *version = "wavelan.c : v16 (wireless extensions) 17/4/97\n"; +#endif + +/* Watchdog temporisation */ +#define WATCHDOG_JIFFIES 32 /* TODO: express in HZ. */ + +/* Macro to get the number of elements in an array */ +#define NELS(a) (sizeof(a) / sizeof(a[0])) + +/* ------------------------ PRIVATE IOCTL ------------------------ */ + +#define SIOCSIPQTHR SIOCDEVPRIVATE /* Set quality threshold */ +#define SIOCGIPQTHR SIOCDEVPRIVATE + 1 /* Get quality threshold */ +#define SIOCSIPLTHR SIOCDEVPRIVATE + 2 /* Set level threshold */ +#define SIOCGIPLTHR SIOCDEVPRIVATE + 3 /* Get level threshold */ + +#define SIOCSIPHISTO SIOCDEVPRIVATE + 6 /* Set histogram ranges */ +#define SIOCGIPHISTO SIOCDEVPRIVATE + 7 /* Get histogram values */ + +/* ----------------------- VERSION SUPPORT ----------------------- */ + +/* This ugly patch is needed to cope with old version of the kernel */ +#ifndef copy_from_user +#define copy_from_user memcpy_fromfs +#define copy_to_user memcpy_tofs +#endif + +/****************************** TYPES ******************************/ + +/* Shortcuts */ +typedef struct device device; +typedef struct enet_statistics en_stats; +typedef struct iw_statistics iw_stats; +typedef struct iw_quality iw_qual; +typedef struct iw_freq iw_freq; +typedef struct net_local net_local; +typedef struct timer_list timer_list; + +/* Basic types */ +typedef u_char mac_addr[WAVELAN_ADDR_SIZE]; /* Hardware address */ + +/* + * Static specific data for the interface. + * + * For each network interface, Linux keep data in two structure. "device" + * keep the generic data (same format for everybody) and "net_local" keep + * the additional specific data. + * Note that some of this specific data is in fact generic (en_stats, for + * example). + */ +struct net_local +{ + net_local * next; /* Linked list of the devices */ + device * dev; /* Reverse link... */ + en_stats stats; /* Ethernet interface statistics */ + int nresets; /* Number of hw resets */ + u_char reconfig_82586; /* Need to reconfigure the controler */ + u_char promiscuous; /* Promiscuous mode */ + int mc_count; /* Number of multicast addresses */ + timer_list watchdog; /* To avoid blocking state */ + u_short hacr; /* Current host interface state */ + + int tx_n_in_use; + u_short rx_head; + u_short rx_last; + u_short tx_first_free; + u_short tx_first_in_use; + +#ifdef WIRELESS_EXT + iw_stats wstats; /* Wireless specific stats */ +#endif + +#ifdef WIRELESS_SPY + int spy_number; /* Number of addresses to spy */ + mac_addr spy_address[IW_MAX_SPY]; /* The addresses to spy */ + iw_qual spy_stat[IW_MAX_SPY]; /* Statistics gathered */ +#endif /* WIRELESS_SPY */ +#ifdef HISTOGRAM + int his_number; /* Number of intervals */ + u_char his_range[16]; /* Boundaries of interval ]n-1; n] */ + u_long his_sum[16]; /* Sum in interval */ +#endif /* HISTOGRAM */ +}; + +/**************************** PROTOTYPES ****************************/ + +/* ----------------------- MISC SUBROUTINES ------------------------ */ +static inline unsigned long /* flags */ + wv_splhi(void); /* Disable interrupts */ +static inline void + wv_splx(unsigned long); /* ReEnable interrupts : flags */ +static u_char + wv_irq_to_psa(int); +static int + wv_psa_to_irq(u_char); +/* ------------------- HOST ADAPTER SUBROUTINES ------------------- */ +static inline u_short /* data */ + hasr_read(u_long); /* Read the host interface : base address */ +static inline void + hacr_write(u_long, /* Write to host interface : base address */ + u_short), /* data */ + hacr_write_slow(u_long, + u_short), + set_chan_attn(u_long, /* ioaddr */ + u_short), /* hacr */ + wv_hacr_reset(u_long), /* ioaddr */ + wv_16_off(u_long, /* ioaddr */ + u_short), /* hacr */ + wv_16_on(u_long, /* ioaddr */ + u_short), /* hacr */ + wv_ints_off(device *), + wv_ints_on(device *); +/* ----------------- MODEM MANAGEMENT SUBROUTINES ----------------- */ +static void + psa_read(u_long, /* Read the Parameter Storage Area */ + u_short, /* hacr */ + int, /* offset in PSA */ + u_char *, /* buffer to fill */ + int), /* size to read */ + psa_write(u_long, /* Write to the PSA */ + u_short, /* hacr */ + int, /* Offset in psa */ + u_char *, /* Buffer in memory */ + int); /* Length of buffer */ +static inline void + mmc_out(u_long, /* Write 1 byte to the Modem Manag Control */ + u_short, + u_char), + mmc_write(u_long, /* Write n bytes to the MMC */ + u_char, + u_char *, + int); +static inline u_char /* Read 1 byte from the MMC */ + mmc_in(u_long, + u_short); +static inline void + mmc_read(u_long, /* Read n bytes from the MMC */ + u_char, + u_char *, + int), + fee_wait(u_long, /* Wait for frequency EEprom : base address */ + int, /* Base delay to wait for */ + int); /* Number of time to wait */ +static void + fee_read(u_long, /* Read the frequency EEprom : base address */ + u_short, /* destination offset */ + u_short *, /* data buffer */ + int); /* number of registers */ +/* ---------------------- I82586 SUBROUTINES ----------------------- */ +static /*inline*/ void + obram_read(u_long, /* ioaddr */ + u_short, /* o */ + u_char *, /* b */ + int); /* n */ +static inline void + obram_write(u_long, /* ioaddr */ + u_short, /* o */ + u_char *, /* b */ + int); /* n */ +static void + wv_ack(device *); +static inline int + wv_synchronous_cmd(device *, + const char *), + wv_config_complete(device *, + u_long, + net_local *); +static int + wv_complete(device *, + u_long, + net_local *); +static inline void + wv_82586_reconfig(device *); +/* ------------------- DEBUG & INFO SUBROUTINES ------------------- */ +#ifdef DEBUG_I82586_SHOW +static void + wv_scb_show(unsigned short); +#endif +static inline void + wv_init_info(device *); /* display startup info */ +/* ------------------- IOCTL, STATS & RECONFIG ------------------- */ +static en_stats * + wavelan_get_stats(device *); /* Give stats /proc/net/dev */ +static void + wavelan_set_multicast_list(device *); +/* ----------------------- PACKET RECEPTION ----------------------- */ +static inline void + wv_packet_read(device *, /* Read a packet from a frame */ + u_short, + int), + wv_receive(device *); /* Read all packets waiting */ +/* --------------------- PACKET TRANSMISSION --------------------- */ +static inline void + wv_packet_write(device *, /* Write a packet to the Tx buffer */ + void *, + short); +static int + wavelan_packet_xmit(struct sk_buff *, /* Send a packet */ + device *); +/* -------------------- HARDWARE CONFIGURATION -------------------- */ +static inline int + wv_mmc_init(device *), /* Initialize the modem */ + wv_ru_start(device *), /* Start the i82586 receiver unit */ + wv_cu_start(device *), /* Start the i82586 command unit */ + wv_82586_start(device *); /* Start the i82586 */ +static void + wv_82586_config(device *); /* Configure the i82586 */ +static inline void + wv_82586_stop(device *); +static int + wv_hw_reset(device *), /* Reset the wavelan hardware */ + wv_check_ioaddr(u_long, /* ioaddr */ + u_char *); /* mac address (read) */ +/* ---------------------- INTERRUPT HANDLING ---------------------- */ +static void + wavelan_interrupt(int, /* Interrupt handler */ + void *, + struct pt_regs *); +static void + wavelan_watchdog(u_long); /* Transmission watchdog */ +/* ------------------- CONFIGURATION CALLBACKS ------------------- */ +static int + wavelan_open(device *), /* Open the device */ + wavelan_close(device *), /* Close the device */ + wavelan_config(device *); /* Configure one device */ +extern int + wavelan_probe(device *); /* See Space.c */ + +/**************************** VARIABLES ****************************/ + +/* + * This is the root of the linked list of wavelan drivers + * It is use to verify that we don't reuse the same base address + * for two differents drivers and to make the cleanup when + * removing the module. + */ +static net_local * wavelan_list = (net_local *) NULL; + +/* + * This table is used to translate the psa value to irq number + * and vice versa... + */ +static u_char irqvals[] = +{ + 0, 0, 0, 0x01, + 0x02, 0x04, 0, 0x08, + 0, 0, 0x10, 0x20, + 0x40, 0, 0, 0x80, +}; + +/* + * Table of the available i/o address (base address) for wavelan + */ +static unsigned short iobase[] = +{ +#if 0 + /* Leave out 0x3C0 for now -- seems to clash with some video + * controllers. + * Leave out the others too -- we will always use 0x390 and leave + * 0x300 for the Ethernet device. + * Jean II : 0x3E0 is really fine as well... + */ + 0x300, 0x390, 0x3E0, 0x3C0 +#endif /* 0 */ + 0x390, 0x3E0 +}; + +#ifdef MODULE +/* Name of the devices (memory allocation) */ +static char devname[4][IFNAMSIZ] = { "", "", "", "" }; + +/* Parameters set by insmod */ +static int io[4] = { 0, 0, 0, 0 }; +static int irq[4] = { 0, 0, 0, 0 }; +static char * name[4] = { devname[0], devname[1], devname[2], devname[3] }; +#endif /* MODULE */ + +#endif /* WAVELAN_P_H */ diff --git a/linux/dev/drivers/scsi/eata_dma.c b/linux/dev/drivers/scsi/eata_dma.c new file mode 100644 index 0000000..e902ea1 --- /dev/null +++ b/linux/dev/drivers/scsi/eata_dma.c @@ -0,0 +1,1607 @@ +/************************************************************ + * * + * Linux EATA SCSI driver * + * * + * based on the CAM document CAM/89-004 rev. 2.0c, * + * DPT's driver kit, some internal documents and source, * + * and several other Linux scsi drivers and kernel docs. * + * * + * The driver currently: * + * -supports all ISA based EATA-DMA boards * + * like PM2011, PM2021, PM2041, PM3021 * + * -supports all EISA based EATA-DMA boards * + * like PM2012B, PM2022, PM2122, PM2322, PM2042, * + * PM3122, PM3222, PM3332 * + * -supports all PCI based EATA-DMA boards * + * like PM2024, PM2124, PM2044, PM2144, PM3224, * + * PM3334 * + * -supports the Wide, Ultra Wide and Differential * + * versions of the boards * + * -supports multiple HBAs with & without IRQ sharing * + * -supports all SCSI channels on multi channel boards * + * -supports ix86 and MIPS, untested on ALPHA * + * -needs identical IDs on all channels of a HBA * + * -can be loaded as module * + * -displays statistical and hardware information * + * in /proc/scsi/eata_dma * + * -provides rudimentary latency measurement * + * possibilities via /proc/scsi/eata_dma/ * + * * + * (c)1993-96 Michael Neuffer * + * mike@i-Connect.Net * + * neuffer@mail.uni-mainz.de * + * * + * 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; either version 2 of the License, or * + * (at your option) any later version. * + * * + * 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 kernel; if not, write to * + * the Free Software Foundation, Inc., 675 Mass Ave, * + * Cambridge, MA 02139, USA. * + * * + * I have to thank DPT for their excellent support. I took * + * me almost a year and a stopover at their HQ, on my first * + * trip to the USA, to get it, but since then they've been * + * very helpful and tried to give me all the infos and * + * support I need. * + * * + * Thanks also to Simon Shapiro, Greg Hosler and Mike * + * Jagdis who did a lot of testing and found quite a number * + * of bugs during the development. * + ************************************************************ + * last change: 96/10/21 OS: Linux 2.0.23 * + ************************************************************/ + +/* Look in eata_dma.h for configuration and revision information */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef MACH +#define flush_cache_all() +#else +#include +#endif +#ifdef __mips__ +#include +#endif +#include +#include "scsi.h" +#include "sd.h" +#include "hosts.h" +#include "eata_dma.h" +#include "eata_dma_proc.h" + +#include +#include /* for CONFIG_PCI */ + +struct proc_dir_entry proc_scsi_eata_dma = { + PROC_SCSI_EATA, 8, "eata_dma", + S_IFDIR | S_IRUGO | S_IXUGO, 2 +}; + +static u32 ISAbases[] = +{0x1F0, 0x170, 0x330, 0x230}; +static unchar EISAbases[] = +{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; +static uint registered_HBAs = 0; +static struct Scsi_Host *last_HBA = NULL; +static struct Scsi_Host *first_HBA = NULL; +static unchar reg_IRQ[] = +{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; +static unchar reg_IRQL[] = +{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; +static struct eata_sp *status = 0; /* Statuspacket array */ +static void *dma_scratch = 0; + +static struct eata_register *fake_int_base; +static int fake_int_result; +static int fake_int_happened; + +static ulong int_counter = 0; +static ulong queue_counter = 0; + +void eata_scsi_done (Scsi_Cmnd * scmd) +{ + scmd->request.rq_status = RQ_SCSI_DONE; + + if (scmd->request.sem != NULL) + up(scmd->request.sem); + + return; +} + +void eata_fake_int_handler(s32 irq, void *dev_id, struct pt_regs * regs) +{ + fake_int_result = inb((ulong)fake_int_base + HA_RSTATUS); + fake_int_happened = TRUE; + DBG(DBG_INTR3, printk("eata_fake_int_handler called irq%d base %p" + " res %#x\n", irq, fake_int_base, fake_int_result)); + return; +} + +#include "eata_dma_proc.c" + +#ifdef MODULE +int eata_release(struct Scsi_Host *sh) +{ + uint i; + if (sh->irq && reg_IRQ[sh->irq] == 1) free_irq(sh->irq, NULL); + else reg_IRQ[sh->irq]--; + + scsi_init_free((void *)status, 512); + scsi_init_free((void *)dma_scratch - 4, 1024); + for (i = 0; i < sh->can_queue; i++){ /* Free all SG arrays */ + if(SD(sh)->ccb[i].sg_list != NULL) + scsi_init_free((void *) SD(sh)->ccb[i].sg_list, + sh->sg_tablesize * sizeof(struct eata_sg_list)); + } + + if (SD(sh)->channel == 0) { + if (sh->dma_channel != BUSMASTER) free_dma(sh->dma_channel); + if (sh->io_port && sh->n_io_port) + release_region(sh->io_port, sh->n_io_port); + } + return(TRUE); +} +#endif + + +inline void eata_latency_in(struct eata_ccb *cp, hostdata *hd) +{ + uint time; + time = jiffies - cp->timestamp; + if(hd->all_lat[1] > time) + hd->all_lat[1] = time; + if(hd->all_lat[2] < time) + hd->all_lat[2] = time; + hd->all_lat[3] += time; + hd->all_lat[0]++; + if((cp->rw_latency) == WRITE) { /* was WRITE */ + if(hd->writes_lat[cp->sizeindex][1] > time) + hd->writes_lat[cp->sizeindex][1] = time; + if(hd->writes_lat[cp->sizeindex][2] < time) + hd->writes_lat[cp->sizeindex][2] = time; + hd->writes_lat[cp->sizeindex][3] += time; + hd->writes_lat[cp->sizeindex][0]++; + } else if((cp->rw_latency) == READ) { + if(hd->reads_lat[cp->sizeindex][1] > time) + hd->reads_lat[cp->sizeindex][1] = time; + if(hd->reads_lat[cp->sizeindex][2] < time) + hd->reads_lat[cp->sizeindex][2] = time; + hd->reads_lat[cp->sizeindex][3] += time; + hd->reads_lat[cp->sizeindex][0]++; + } +} + +inline void eata_latency_out(struct eata_ccb *cp, Scsi_Cmnd *cmd) +{ + int x, z; + short *sho; + long *lon; + x = 0; /* just to keep GCC quiet */ + cp->timestamp = jiffies; /* For latency measurements */ + switch(cmd->cmnd[0]) { + case WRITE_6: + x = cmd->cmnd[4]/2; + cp->rw_latency = WRITE; + break; + case READ_6: + x = cmd->cmnd[4]/2; + cp->rw_latency = READ; + break; + case WRITE_10: + sho = (short *) &cmd->cmnd[7]; + x = ntohs(*sho)/2; + cp->rw_latency = WRITE; + break; + case READ_10: + sho = (short *) &cmd->cmnd[7]; + x = ntohs(*sho)/2; + cp->rw_latency = READ; + break; + case WRITE_12: + lon = (long *) &cmd->cmnd[6]; + x = ntohl(*lon)/2; + cp->rw_latency = WRITE; + break; + case READ_12: + lon = (long *) &cmd->cmnd[6]; + x = ntohl(*lon)/2; + cp->rw_latency = READ; + break; + default: + cp->rw_latency = OTHER; + break; + } + if (cmd->cmnd[0] == WRITE_6 || cmd->cmnd[0] == WRITE_10 || + cmd->cmnd[0] == WRITE_12 || cmd->cmnd[0] == READ_6 || + cmd->cmnd[0] == READ_10 || cmd->cmnd[0] == READ_12) { + for(z = 0; (x > (1 << z)) && (z <= 11); z++) + /* nothing */; + cp->sizeindex = z; + } +} + + +void eata_int_handler(int irq, void *dev_id, struct pt_regs * regs) +{ + uint i, result = 0; + uint hba_stat, scsi_stat, eata_stat; + Scsi_Cmnd *cmd; + struct eata_ccb *ccb; + struct eata_sp *sp; + uint base; + uint x; + struct Scsi_Host *sh; + + for (x = 1, sh = first_HBA; x <= registered_HBAs; x++, sh = SD(sh)->next) { + if (sh->irq != irq) + continue; + + while(inb((uint)sh->base + HA_RAUXSTAT) & HA_AIRQ) { + + int_counter++; + + sp = &SD(sh)->sp; +#ifdef __mips__ + sys_cacheflush(sp, sizeof(struct eata_sp), 2); +#endif + ccb = sp->ccb; + + if(ccb == NULL) { + eata_stat = inb((uint)sh->base + HA_RSTATUS); + printk("eata_dma: int_handler, Spurious IRQ %d " + "received. CCB pointer not set.\n", irq); + break; + } + + cmd = ccb->cmd; + base = (uint) cmd->host->base; + hba_stat = sp->hba_stat; + + scsi_stat = (sp->scsi_stat >> 1) & 0x1f; + + if (sp->EOC == FALSE) { + eata_stat = inb(base + HA_RSTATUS); + printk(KERN_WARNING "eata_dma: int_handler, board: %x cmd %lx " + "returned unfinished.\n" + "EATA: %x HBA: %x SCSI: %x spadr %lx spadrirq %lx, " + "irq%d\n", base, (long)ccb, eata_stat, hba_stat, + scsi_stat,(long)&status, (long)&status[irq], irq); + cmd->result = DID_ERROR << 16; + ccb->status = FREE; + cmd->scsi_done(cmd); + break; + } + + sp->EOC = FALSE; /* Clean out this flag */ + + if (ccb->status == LOCKED || ccb->status == RESET) { + printk("eata_dma: int_handler, reseted command pid %ld returned" + "\n", cmd->pid); + DBG(DBG_INTR && DBG_DELAY, DELAY(1)); + } + + eata_stat = inb(base + HA_RSTATUS); + DBG(DBG_INTR, printk("IRQ %d received, base %#.4x, pid %ld, " + "target: %x, lun: %x, ea_s: %#.2x, hba_s: " + "%#.2x \n", irq, base, cmd->pid, cmd->target, + cmd->lun, eata_stat, hba_stat)); + + switch (hba_stat) { + case HA_NO_ERROR: /* NO Error */ + if(HD(cmd)->do_latency == TRUE && ccb->timestamp) + eata_latency_in(ccb, HD(cmd)); + result = DID_OK << 16; + break; + case HA_ERR_SEL_TO: /* Selection Timeout */ + case HA_ERR_CMD_TO: /* Command Timeout */ + result = DID_TIME_OUT << 16; + break; + case HA_BUS_RESET: /* SCSI Bus Reset Received */ + result = DID_RESET << 16; + DBG(DBG_STATUS, printk(KERN_WARNING "scsi%d: BUS RESET " + "received on cmd %ld\n", + HD(cmd)->HBA_number, cmd->pid)); + break; + case HA_INIT_POWERUP: /* Initial Controller Power-up */ + if (cmd->device->type != TYPE_TAPE) + result = DID_BUS_BUSY << 16; + else + result = DID_ERROR << 16; + + for (i = 0; i < MAXTARGET; i++) + DBG(DBG_STATUS, printk(KERN_DEBUG "scsi%d: cmd pid %ld " + "returned with INIT_POWERUP\n", + HD(cmd)->HBA_number, cmd->pid)); + break; + case HA_CP_ABORT_NA: + case HA_CP_ABORTED: + result = DID_ABORT << 16; + DBG(DBG_STATUS, printk(KERN_WARNING "scsi%d: aborted cmd " + "returned\n", HD(cmd)->HBA_number)); + break; + case HA_CP_RESET_NA: + case HA_CP_RESET: + HD(cmd)->resetlevel[cmd->channel] = 0; + result = DID_RESET << 16; + DBG(DBG_STATUS, printk(KERN_WARNING "scsi%d: reseted cmd " + "pid %ldreturned\n", + HD(cmd)->HBA_number, cmd->pid)); + case HA_SCSI_HUNG: /* SCSI Hung */ + printk(KERN_ERR "scsi%d: SCSI hung\n", HD(cmd)->HBA_number); + result = DID_ERROR << 16; + break; + case HA_RSENSE_FAIL: /* Auto Request-Sense Failed */ + DBG(DBG_STATUS, printk(KERN_ERR "scsi%d: Auto Request Sense " + "Failed\n", HD(cmd)->HBA_number)); + result = DID_ERROR << 16; + break; + case HA_UNX_BUSPHASE: /* Unexpected Bus Phase */ + case HA_UNX_BUS_FREE: /* Unexpected Bus Free */ + case HA_BUS_PARITY: /* Bus Parity Error */ + case HA_UNX_MSGRJCT: /* Unexpected Message Reject */ + case HA_RESET_STUCK: /* SCSI Bus Reset Stuck */ + case HA_PARITY_ERR: /* Controller Ram Parity */ + default: + result = DID_ERROR << 16; + break; + } + cmd->result = result | (scsi_stat << 1); + +#if DBG_INTR2 + if (scsi_stat || result || hba_stat || eata_stat != 0x50 + || cmd->scsi_done == NULL || cmd->device->id == 7) + printk("HBA: %d, channel %d, id: %d, lun %d, pid %ld:\n" + "eata_stat %#x, hba_stat %#.2x, scsi_stat %#.2x, " + "sense_key: %#x, result: %#.8x\n", x, + cmd->device->channel, cmd->device->id, cmd->device->lun, + cmd->pid, eata_stat, hba_stat, scsi_stat, + cmd->sense_buffer[2] & 0xf, cmd->result); + DBG(DBG_INTR&&DBG_DELAY,DELAY(1)); +#endif + + ccb->status = FREE; /* now we can release the slot */ + cmd->scsi_done(cmd); + } + } + + return; +} + +inline int eata_send_command(u32 addr, u32 base, u8 command) +{ + long loop = R_LIMIT; + + while (inb(base + HA_RAUXSTAT) & HA_ABUSY) + if (--loop == 0) + return(FALSE); + + if(addr != (u32) NULL) + addr = virt_to_bus((void *)addr); + + /* + * This is overkill.....but the MIPSen seem to need this + * and it will be optimized away for i86 and ALPHA machines. + */ + flush_cache_all(); + + /* And now the address in nice little byte chunks */ +#ifdef __LITTLE_ENDIAN + outb(addr, base + HA_WDMAADDR); + outb(addr >> 8, base + HA_WDMAADDR + 1); + outb(addr >> 16, base + HA_WDMAADDR + 2); + outb(addr >> 24, base + HA_WDMAADDR + 3); +#else + outb(addr >> 24, base + HA_WDMAADDR); + outb(addr >> 16, base + HA_WDMAADDR + 1); + outb(addr >> 8, base + HA_WDMAADDR + 2); + outb(addr, base + HA_WDMAADDR + 3); +#endif + outb(command, base + HA_WCOMMAND); + return(TRUE); +} + +inline int eata_send_immediate(u32 base, u32 addr, u8 ifc, u8 code, u8 code2) +{ + if(addr != (u32) NULL) + addr = virt_to_bus((void *)addr); + + /* + * This is overkill.....but the MIPSen seem to need this + * and it will be optimized away for i86 and ALPHA machines. + */ + flush_cache_all(); + + outb(0x0, base + HA_WDMAADDR - 1); + if(addr){ +#ifdef __LITTLE_ENDIAN + outb(addr, base + HA_WDMAADDR); + outb(addr >> 8, base + HA_WDMAADDR + 1); + outb(addr >> 16, base + HA_WDMAADDR + 2); + outb(addr >> 24, base + HA_WDMAADDR + 3); +#else + outb(addr >> 24, base + HA_WDMAADDR); + outb(addr >> 16, base + HA_WDMAADDR + 1); + outb(addr >> 8, base + HA_WDMAADDR + 2); + outb(addr, base + HA_WDMAADDR + 3); +#endif + } else { + outb(0x0, base + HA_WDMAADDR); + outb(0x0, base + HA_WDMAADDR + 1); + outb(code2, base + HA_WCODE2); + outb(code, base + HA_WCODE); + } + + outb(ifc, base + HA_WIFC); + outb(EATA_CMD_IMMEDIATE, base + HA_WCOMMAND); + return(TRUE); +} + +int eata_queue(Scsi_Cmnd * cmd, void (* done) (Scsi_Cmnd *)) +{ + unsigned int i, x, y; + ulong flags; + hostdata *hd; + struct Scsi_Host *sh; + struct eata_ccb *ccb; + struct scatterlist *sl; + + + save_flags(flags); + cli(); + +#if 0 + for (x = 1, sh = first_HBA; x <= registered_HBAs; x++, sh = SD(sh)->next) { + if(inb((uint)sh->base + HA_RAUXSTAT) & HA_AIRQ) { + printk("eata_dma: scsi%d interrupt pending in eata_queue.\n" + " Calling interrupt handler.\n", sh->host_no); + eata_int_handler(sh->irq, 0, 0); + } + } +#endif + + queue_counter++; + + hd = HD(cmd); + sh = cmd->host; + + if (cmd->cmnd[0] == REQUEST_SENSE && cmd->sense_buffer[0] != 0) { + DBG(DBG_REQSENSE, printk(KERN_DEBUG "Tried to REQUEST SENSE\n")); + cmd->result = DID_OK << 16; + done(cmd); + + return(0); + } + + /* check for free slot */ + for (y = hd->last_ccb + 1, x = 0; x < sh->can_queue; x++, y++) { + if (y >= sh->can_queue) + y = 0; + if (hd->ccb[y].status == FREE) + break; + } + + hd->last_ccb = y; + + if (x >= sh->can_queue) { + cmd->result = DID_BUS_BUSY << 16; + DBG(DBG_QUEUE && DBG_ABNORM, + printk(KERN_CRIT "eata_queue pid %ld, HBA QUEUE FULL..., " + "returning DID_BUS_BUSY\n", cmd->pid)); + done(cmd); + restore_flags(flags); + return(0); + } + ccb = &hd->ccb[y]; + + memset(ccb, 0, sizeof(struct eata_ccb) - sizeof(struct eata_sg_list *)); + + ccb->status = USED; /* claim free slot */ + + restore_flags(flags); + + DBG(DBG_QUEUE, printk("eata_queue pid %ld, target: %x, lun: %x, y %d\n", + cmd->pid, cmd->target, cmd->lun, y)); + DBG(DBG_QUEUE && DBG_DELAY, DELAY(1)); + + if(hd->do_latency == TRUE) + eata_latency_out(ccb, cmd); + + cmd->scsi_done = (void *)done; + + switch (cmd->cmnd[0]) { + case CHANGE_DEFINITION: case COMPARE: case COPY: + case COPY_VERIFY: case LOG_SELECT: case MODE_SELECT: + case MODE_SELECT_10: case SEND_DIAGNOSTIC: case WRITE_BUFFER: + case FORMAT_UNIT: case REASSIGN_BLOCKS: case RESERVE: + case SEARCH_EQUAL: case SEARCH_HIGH: case SEARCH_LOW: + case WRITE_6: case WRITE_10: case WRITE_VERIFY: + case UPDATE_BLOCK: case WRITE_LONG: case WRITE_SAME: + case SEARCH_HIGH_12: case SEARCH_EQUAL_12: case SEARCH_LOW_12: + case WRITE_12: case WRITE_VERIFY_12: case SET_WINDOW: + case MEDIUM_SCAN: case SEND_VOLUME_TAG: + case 0xea: /* alternate number for WRITE LONG */ + ccb->DataOut = TRUE; /* Output mode */ + break; + case TEST_UNIT_READY: + default: + ccb->DataIn = TRUE; /* Input mode */ + } + + /* FIXME: This will have to be changed once the midlevel driver + * allows different HBA IDs on every channel. + */ + if (cmd->target == sh->this_id) + ccb->Interpret = TRUE; /* Interpret command */ + + if (cmd->use_sg) { + ccb->scatter = TRUE; /* SG mode */ + if (ccb->sg_list == NULL) { + ccb->sg_list = kmalloc(sh->sg_tablesize * sizeof(struct eata_sg_list), + GFP_ATOMIC | GFP_DMA); + } + if (ccb->sg_list == NULL) + panic("eata_dma: Run out of DMA memory for SG lists !\n"); + ccb->cp_dataDMA = htonl(virt_to_bus(ccb->sg_list)); + + ccb->cp_datalen = htonl(cmd->use_sg * sizeof(struct eata_sg_list)); + sl=(struct scatterlist *)cmd->request_buffer; + for(i = 0; i < cmd->use_sg; i++, sl++){ + ccb->sg_list[i].data = htonl(virt_to_bus(sl->address)); + ccb->sg_list[i].len = htonl((u32) sl->length); + } + } else { + ccb->scatter = FALSE; + ccb->cp_datalen = htonl(cmd->request_bufflen); + ccb->cp_dataDMA = htonl(virt_to_bus(cmd->request_buffer)); + } + + ccb->Auto_Req_Sen = TRUE; + ccb->cp_reqDMA = htonl(virt_to_bus(cmd->sense_buffer)); + ccb->reqlen = sizeof(cmd->sense_buffer); + + ccb->cp_id = cmd->target; + ccb->cp_channel = cmd->channel; + ccb->cp_lun = cmd->lun; + ccb->cp_dispri = TRUE; + ccb->cp_identify = TRUE; + memcpy(ccb->cp_cdb, cmd->cmnd, cmd->cmd_len); + + ccb->cp_statDMA = htonl(virt_to_bus(&(hd->sp))); + + ccb->cp_viraddr = ccb; /* This will be passed thru, so we don't need to + * convert it */ + ccb->cmd = cmd; + cmd->host_scribble = (char *)&hd->ccb[y]; + + if(eata_send_command((u32) ccb, (u32) sh->base, EATA_CMD_DMA_SEND_CP) == FALSE) { + cmd->result = DID_BUS_BUSY << 16; + DBG(DBG_QUEUE && DBG_ABNORM, + printk("eata_queue target %d, pid %ld, HBA busy, " + "returning DID_BUS_BUSY\n",cmd->target, cmd->pid)); + ccb->status = FREE; + done(cmd); + return(0); + } + DBG(DBG_QUEUE, printk("Queued base %#.4x pid: %ld target: %x lun: %x " + "slot %d irq %d\n", (s32)sh->base, cmd->pid, + cmd->target, cmd->lun, y, sh->irq)); + DBG(DBG_QUEUE && DBG_DELAY, DELAY(1)); + + return(0); +} + + +int eata_abort(Scsi_Cmnd * cmd) +{ + ulong loop = HZ / 2; + ulong flags; + int x; + struct Scsi_Host *sh; + + save_flags(flags); + cli(); + + DBG(DBG_ABNORM, printk("eata_abort called pid: %ld target: %x lun: %x" + " reason %x\n", cmd->pid, cmd->target, cmd->lun, + cmd->abort_reason)); + DBG(DBG_ABNORM && DBG_DELAY, DELAY(1)); + + /* Some interrupt controllers seem to loose interrupts */ + for (x = 1, sh = first_HBA; x <= registered_HBAs; x++, sh = SD(sh)->next) { + if(inb((uint)sh->base + HA_RAUXSTAT) & HA_AIRQ) { + printk("eata_dma: scsi%d interrupt pending in eata_abort.\n" + " Calling interrupt handler.\n", sh->host_no); + eata_int_handler(sh->irq, 0, 0); + } + } + + while (inb((u32)(cmd->host->base) + HA_RAUXSTAT) & HA_ABUSY) { + if (--loop == 0) { + printk("eata_dma: abort, timeout error.\n"); + DBG(DBG_ABNORM && DBG_DELAY, DELAY(1)); + restore_flags(flags); + return (SCSI_ABORT_ERROR); + } + } + if (CD(cmd)->status == RESET) { + printk("eata_dma: abort, command reset error.\n"); + DBG(DBG_ABNORM && DBG_DELAY, DELAY(1)); + restore_flags(flags); + return (SCSI_ABORT_ERROR); + } + if (CD(cmd)->status == LOCKED) { + DBG(DBG_ABNORM, printk("eata_dma: abort, queue slot locked.\n")); + DBG(DBG_ABNORM && DBG_DELAY, DELAY(1)); + restore_flags(flags); + return (SCSI_ABORT_NOT_RUNNING); + } + if (CD(cmd)->status == USED) { + DBG(DBG_ABNORM, printk("Returning: SCSI_ABORT_BUSY\n")); + restore_flags(flags); + return (SCSI_ABORT_BUSY); /* SNOOZE */ + } + if (CD(cmd)->status == FREE) { + DBG(DBG_ABNORM, printk("Returning: SCSI_ABORT_NOT_RUNNING\n")); + restore_flags(flags); + return (SCSI_ABORT_NOT_RUNNING); + } + restore_flags(flags); + panic("eata_dma: abort: invalid slot status\n"); +} + +int eata_reset(Scsi_Cmnd * cmd, unsigned int resetflags) +{ + uint x; + ulong loop = loops_per_sec / 3; + ulong flags; + unchar success = FALSE; + Scsi_Cmnd *sp; + struct Scsi_Host *sh; + + save_flags(flags); + cli(); + + DBG(DBG_ABNORM, printk("eata_reset called pid:%ld target: %x lun: %x" + " reason %x\n", cmd->pid, cmd->target, cmd->lun, + cmd->abort_reason)); + + for (x = 1, sh = first_HBA; x <= registered_HBAs; x++, sh = SD(sh)->next) { + if(inb((uint)sh->base + HA_RAUXSTAT) & HA_AIRQ) { + printk("eata_dma: scsi%d interrupt pending in eata_reset.\n" + " Calling interrupt handler.\n", sh->host_no); + eata_int_handler(sh->irq, 0, 0); + } + } + + if (HD(cmd)->state == RESET) { + printk("eata_reset: exit, already in reset.\n"); + restore_flags(flags); + DBG(DBG_ABNORM && DBG_DELAY, DELAY(1)); + return (SCSI_RESET_ERROR); + } + + while (inb((u32)(cmd->host->base) + HA_RAUXSTAT) & HA_ABUSY) + if (--loop == 0) { + printk("eata_reset: exit, timeout error.\n"); + restore_flags(flags); + DBG(DBG_ABNORM && DBG_DELAY, DELAY(1)); + return (SCSI_RESET_ERROR); + } + + for (x = 0; x < cmd->host->can_queue; x++) { + if (HD(cmd)->ccb[x].status == FREE) + continue; + + if (HD(cmd)->ccb[x].status == LOCKED) { + HD(cmd)->ccb[x].status = FREE; + printk("eata_reset: locked slot %d forced free.\n", x); + DBG(DBG_ABNORM && DBG_DELAY, DELAY(1)); + continue; + } + + + sp = HD(cmd)->ccb[x].cmd; + HD(cmd)->ccb[x].status = RESET; + + if (sp == NULL) + panic("eata_reset: slot %d, sp==NULL.\n", x); + + printk("eata_reset: slot %d in reset, pid %ld.\n", x, sp->pid); + + DBG(DBG_ABNORM && DBG_DELAY, DELAY(1)); + + if (sp == cmd) + success = TRUE; + } + + /* hard reset the HBA */ + inb((u32) (cmd->host->base) + HA_RSTATUS); /* This might cause trouble */ + eata_send_command(0, (u32) cmd->host->base, EATA_CMD_RESET); + + HD(cmd)->state = RESET; + + DBG(DBG_ABNORM, printk("eata_reset: board reset done, enabling " + "interrupts.\n")); + + DELAY(2); /* In theorie we should get interrupts and set free all + * used queueslots */ + + DBG(DBG_ABNORM, printk("eata_reset: interrupts disabled again.\n")); + DBG(DBG_ABNORM && DBG_DELAY, DELAY(1)); + + for (x = 0; x < cmd->host->can_queue; x++) { + + /* Skip slots already set free by interrupt and those that + * are still LOCKED from the last reset */ + if (HD(cmd)->ccb[x].status != RESET) + continue; + + sp = HD(cmd)->ccb[x].cmd; + sp->result = DID_RESET << 16; + + /* This mailbox is still waiting for its interrupt */ + HD(cmd)->ccb[x].status = LOCKED; + + printk("eata_reset: slot %d locked, DID_RESET, pid %ld done.\n", + x, sp->pid); + DBG(DBG_ABNORM && DBG_DELAY, DELAY(1)); + + sp->scsi_done(sp); + } + + HD(cmd)->state = FALSE; + restore_flags(flags); + + if (success) { + DBG(DBG_ABNORM, printk("eata_reset: exit, pending.\n")); + DBG(DBG_ABNORM && DBG_DELAY, DELAY(1)); + return (SCSI_RESET_PENDING); + } else { + DBG(DBG_ABNORM, printk("eata_reset: exit, wakeup.\n")); + DBG(DBG_ABNORM && DBG_DELAY, DELAY(1)); + return (SCSI_RESET_PUNT); + } +} + +/* Here we try to determine the optimum queue depth for + * each attached device. + * + * At the moment the algorithm is rather simple + */ +static void eata_select_queue_depths(struct Scsi_Host *host, + Scsi_Device *devicelist) +{ + Scsi_Device *device; + int devcount = 0; + int factor = 0; + +#if CRIPPLE_QUEUE + for(device = devicelist; device != NULL; device = device->next) { + if(device->host == host) + device->queue_depth = 2; + } +#else + /* First we do a sample run go find out what we have */ + for(device = devicelist; device != NULL; device = device->next) { + if (device->host == host) { + devcount++; + switch(device->type) { + case TYPE_DISK: + case TYPE_MOD: + factor += TYPE_DISK_QUEUE; + break; + case TYPE_TAPE: + factor += TYPE_TAPE_QUEUE; + break; + case TYPE_WORM: + case TYPE_ROM: + factor += TYPE_ROM_QUEUE; + break; + case TYPE_PROCESSOR: + case TYPE_SCANNER: + default: + factor += TYPE_OTHER_QUEUE; + break; + } + } + } + + DBG(DBG_REGISTER, printk(KERN_DEBUG "scsi%d: needed queueslots %d\n", + host->host_no, factor)); + + if(factor == 0) /* We don't want to get a DIV BY ZERO error */ + factor = 1; + + factor = (SD(host)->queuesize * 10) / factor; + + DBG(DBG_REGISTER, printk(KERN_DEBUG "scsi%d: using factor %dE-1\n", + host->host_no, factor)); + + /* Now that have the factor we can set the individual queuesizes */ + for(device = devicelist; device != NULL; device = device->next) { + if(device->host == host) { + if(SD(device->host)->bustype != IS_ISA){ + switch(device->type) { + case TYPE_DISK: + case TYPE_MOD: + device->queue_depth = (TYPE_DISK_QUEUE * factor) / 10; + break; + case TYPE_TAPE: + device->queue_depth = (TYPE_TAPE_QUEUE * factor) / 10; + break; + case TYPE_WORM: + case TYPE_ROM: + device->queue_depth = (TYPE_ROM_QUEUE * factor) / 10; + break; + case TYPE_PROCESSOR: + case TYPE_SCANNER: + default: + device->queue_depth = (TYPE_OTHER_QUEUE * factor) / 10; + break; + } + } else /* ISA forces us to limit the queue depth because of the + * bounce buffer memory overhead. I know this is cruel */ + device->queue_depth = 2; + + /* + * It showed that we need to set an upper limit of commands + * we can allow to queue for a single device on the bus. + * If we get above that limit, the broken midlevel SCSI code + * will produce bogus timeouts and aborts en masse. :-( + */ + if(device->queue_depth > UPPER_DEVICE_QUEUE_LIMIT) + device->queue_depth = UPPER_DEVICE_QUEUE_LIMIT; + if(device->queue_depth == 0) + device->queue_depth = 1; + + printk(KERN_INFO "scsi%d: queue depth for target %d on channel %d " + "set to %d\n", host->host_no, device->id, device->channel, + device->queue_depth); + } + } +#endif +} + +#if CHECK_BLINK +int check_blink_state(long base) +{ + ushort loops = 10; + u32 blinkindicator; + u32 state = 0x12345678; + u32 oldstate = 0; + + blinkindicator = htonl(0x54504442); + while ((loops--) && (state != oldstate)) { + oldstate = state; + state = inl((uint) base + 1); + } + + DBG(DBG_BLINK, printk("Did Blink check. Status: %d\n", + (state == oldstate) && (state == blinkindicator))); + + if ((state == oldstate) && (state == blinkindicator)) + return(TRUE); + else + return (FALSE); +} +#endif + +char * get_board_data(u32 base, u32 irq, u32 id) +{ + struct eata_ccb *cp; + struct eata_sp *sp; + static char *buff; + ulong i; + + cp = (struct eata_ccb *) scsi_init_malloc(sizeof(struct eata_ccb), + GFP_ATOMIC | GFP_DMA); + sp = (struct eata_sp *) scsi_init_malloc(sizeof(struct eata_sp), + GFP_ATOMIC | GFP_DMA); + + buff = dma_scratch; + + memset(cp, 0, sizeof(struct eata_ccb)); + memset(sp, 0, sizeof(struct eata_sp)); + memset(buff, 0, 256); + + cp->DataIn = TRUE; + cp->Interpret = TRUE; /* Interpret command */ + cp->cp_dispri = TRUE; + cp->cp_identify = TRUE; + + cp->cp_datalen = htonl(56); + cp->cp_dataDMA = htonl(virt_to_bus(buff)); + cp->cp_statDMA = htonl(virt_to_bus(sp)); + cp->cp_viraddr = cp; + + cp->cp_id = id; + cp->cp_lun = 0; + + cp->cp_cdb[0] = INQUIRY; + cp->cp_cdb[1] = 0; + cp->cp_cdb[2] = 0; + cp->cp_cdb[3] = 0; + cp->cp_cdb[4] = 56; + cp->cp_cdb[5] = 0; + + fake_int_base = (struct eata_register *) base; + fake_int_result = FALSE; + fake_int_happened = FALSE; + + eata_send_command((u32) cp, (u32) base, EATA_CMD_DMA_SEND_CP); + + i = jiffies + (3 * HZ); + while (fake_int_happened == FALSE && jiffies <= i) + barrier(); + + DBG(DBG_INTR3, printk(KERN_DEBUG "fake_int_result: %#x hbastat %#x " + "scsistat %#x, buff %p sp %p\n", + fake_int_result, (u32) (sp->hba_stat /*& 0x7f*/), + (u32) sp->scsi_stat, buff, sp)); + + scsi_init_free((void *)cp, sizeof(struct eata_ccb)); + scsi_init_free((void *)sp, sizeof(struct eata_sp)); + + if ((fake_int_result & HA_SERROR) || jiffies > i){ + printk(KERN_WARNING "eata_dma: trying to reset HBA at %x to clear " + "possible blink state\n", base); + /* hard reset the HBA */ + inb((u32) (base) + HA_RSTATUS); + eata_send_command(0, base, EATA_CMD_RESET); + DELAY(1); + return (NULL); + } else + return (buff); +} + + +int get_conf_PIO(u32 base, struct get_conf *buf) +{ + ulong loop = R_LIMIT; + u16 *p; + + if(check_region(base, 9)) + return (FALSE); + + memset(buf, 0, sizeof(struct get_conf)); + + while (inb(base + HA_RSTATUS) & HA_SBUSY) + if (--loop == 0) + return (FALSE); + + fake_int_base = (struct eata_register *) base; + fake_int_result = FALSE; + fake_int_happened = FALSE; + + DBG(DBG_PIO && DBG_PROBE, + printk("Issuing PIO READ CONFIG to HBA at %#x\n", base)); + eata_send_command(0, base, EATA_CMD_PIO_READ_CONFIG); + + loop = R_LIMIT; + for (p = (u16 *) buf; + (long)p <= ((long)buf + (sizeof(struct get_conf) / 2)); p++) { + while (!(inb(base + HA_RSTATUS) & HA_SDRQ)) + if (--loop == 0) + return (FALSE); + + loop = R_LIMIT; + *p = inw(base + HA_RDATA); + } + + if (!(inb(base + HA_RSTATUS) & HA_SERROR)) { /* Error ? */ + if (htonl(EATA_SIGNATURE) == buf->signature) { + DBG(DBG_PIO&&DBG_PROBE, printk("EATA Controller found at %x " + "EATA Level: %x\n", (uint) base, + (uint) (buf->version))); + + while (inb(base + HA_RSTATUS) & HA_SDRQ) + inw(base + HA_RDATA); + return (TRUE); + } + } else { + DBG(DBG_PROBE, printk("eata_dma: get_conf_PIO, error during transfer " + "for HBA at %lx\n", (long)base)); + } + return (FALSE); +} + + +void print_config(struct get_conf *gc) +{ + printk("LEN: %d ver:%d OCS:%d TAR:%d TRNXFR:%d MORES:%d DMAS:%d\n", + (u32) ntohl(gc->len), gc->version, + gc->OCS_enabled, gc->TAR_support, gc->TRNXFR, gc->MORE_support, + gc->DMA_support); + printk("DMAV:%d HAAV:%d SCSIID0:%d ID1:%d ID2:%d QUEUE:%d SG:%d SEC:%d\n", + gc->DMA_valid, gc->HAA_valid, gc->scsi_id[3], gc->scsi_id[2], + gc->scsi_id[1], ntohs(gc->queuesiz), ntohs(gc->SGsiz), gc->SECOND); + printk("IRQ:%d IRQT:%d DMAC:%d FORCADR:%d SG_64K:%d SG_UAE:%d MID:%d " + "MCH:%d MLUN:%d\n", + gc->IRQ, gc->IRQ_TR, (8 - gc->DMA_channel) & 7, gc->FORCADR, + gc->SG_64K, gc->SG_UAE, gc->MAX_ID, gc->MAX_CHAN, gc->MAX_LUN); + printk("RIDQ:%d PCI:%d EISA:%d\n", + gc->ID_qest, gc->is_PCI, gc->is_EISA); + DBG(DPT_DEBUG, DELAY(14)); +} + +short register_HBA(u32 base, struct get_conf *gc, Scsi_Host_Template * tpnt, + u8 bustype) +{ + ulong size = 0; + unchar dma_channel = 0; + char *buff = 0; + unchar bugs = 0; + struct Scsi_Host *sh; + hostdata *hd; + int x; + + + DBG(DBG_REGISTER, print_config(gc)); + + if (gc->DMA_support == FALSE) { + printk("The EATA HBA at %#.4x does not support DMA.\n" + "Please use the EATA-PIO driver.\n", base); + return (FALSE); + } + if(gc->HAA_valid == FALSE || ntohl(gc->len) < 0x22) + gc->MAX_CHAN = 0; + + if (reg_IRQ[gc->IRQ] == FALSE) { /* Interrupt already registered ? */ + if (!request_irq(gc->IRQ, (void *) eata_fake_int_handler, SA_INTERRUPT, + "eata_dma", NULL)){ + reg_IRQ[gc->IRQ]++; + if (!gc->IRQ_TR) + reg_IRQL[gc->IRQ] = TRUE; /* IRQ is edge triggered */ + } else { + printk("Couldn't allocate IRQ %d, Sorry.", gc->IRQ); + return (FALSE); + } + } else { /* More than one HBA on this IRQ */ + if (reg_IRQL[gc->IRQ] == TRUE) { + printk("Can't support more than one HBA on this IRQ,\n" + " if the IRQ is edge triggered. Sorry.\n"); + return (FALSE); + } else + reg_IRQ[gc->IRQ]++; + } + + + /* If DMA is supported but DMA_valid isn't set to indicate that + * the channel number is given we must have pre 2.0 firmware (1.7?) + * which leaves us to guess since the "newer ones" also don't set the + * DMA_valid bit. + */ + if (gc->DMA_support && !gc->DMA_valid && gc->DMA_channel) { + printk(KERN_WARNING "eata_dma: If you are using a pre 2.0 firmware " + "please update it !\n" + " You can get new firmware releases from ftp.dpt.com\n"); + gc->DMA_channel = (base == 0x1f0 ? 3 /* DMA=5 */ : 2 /* DMA=6 */); + gc->DMA_valid = TRUE; + } + + /* if gc->DMA_valid it must be an ISA HBA and we have to register it */ + dma_channel = BUSMASTER; + if (gc->DMA_valid) { + if (request_dma(dma_channel = (8 - gc->DMA_channel) & 7, "eata_dma")) { + printk(KERN_WARNING "Unable to allocate DMA channel %d for ISA HBA" + " at %#.4x.\n", dma_channel, base); + reg_IRQ[gc->IRQ]--; + if (reg_IRQ[gc->IRQ] == 0) + free_irq(gc->IRQ, NULL); + if (gc->IRQ_TR == FALSE) + reg_IRQL[gc->IRQ] = FALSE; + return (FALSE); + } + } + + if (dma_channel != BUSMASTER) { + disable_dma(dma_channel); + clear_dma_ff(dma_channel); + set_dma_mode(dma_channel, DMA_MODE_CASCADE); + enable_dma(dma_channel); + } + + if (bustype != IS_EISA && bustype != IS_ISA) + buff = get_board_data(base, gc->IRQ, gc->scsi_id[3]); + + if (buff == NULL) { + if (bustype == IS_EISA || bustype == IS_ISA) { + bugs = bugs || BROKEN_INQUIRY; + } else { + if (gc->DMA_support == FALSE) + printk(KERN_WARNING "HBA at %#.4x doesn't support DMA. " + "Sorry\n", base); + else + printk(KERN_WARNING "HBA at %#.4x does not react on INQUIRY. " + "Sorry.\n", base); + if (gc->DMA_valid) + free_dma(dma_channel); + reg_IRQ[gc->IRQ]--; + if (reg_IRQ[gc->IRQ] == 0) + free_irq(gc->IRQ, NULL); + if (gc->IRQ_TR == FALSE) + reg_IRQL[gc->IRQ] = FALSE; + return (FALSE); + } + } + + if (gc->DMA_support == FALSE && buff != NULL) + printk(KERN_WARNING "HBA %.12sat %#.4x doesn't set the DMA_support " + "flag correctly.\n", &buff[16], base); + + request_region(base, 9, "eata_dma"); /* We already checked the + * availability, so this + * should not fail. + */ + + if(ntohs(gc->queuesiz) == 0) { + gc->queuesiz = ntohs(64); + printk(KERN_WARNING "Warning: Queue size has to be corrected. Assuming" + " 64 queueslots\n" + " This might be a PM2012B with a defective Firmware\n" + " Contact DPT support@dpt.com for an upgrade\n"); + } + + size = sizeof(hostdata) + ((sizeof(struct eata_ccb) + sizeof(long)) + * ntohs(gc->queuesiz)); + + DBG(DBG_REGISTER, printk("scsi_register size: %ld\n", size)); + + sh = scsi_register(tpnt, size); + + if(sh != NULL) { + + hd = SD(sh); + + memset(hd->reads, 0, sizeof(u32) * 26); + + sh->select_queue_depths = eata_select_queue_depths; + + hd->bustype = bustype; + + /* + * If we are using a ISA board, we can't use extended SG, + * because we would need excessive amounts of memory for + * bounce buffers. + */ + if (gc->SG_64K==TRUE && ntohs(gc->SGsiz)==64 && hd->bustype!=IS_ISA){ + sh->sg_tablesize = SG_SIZE_BIG; + } else { + sh->sg_tablesize = ntohs(gc->SGsiz); + if (sh->sg_tablesize > SG_SIZE || sh->sg_tablesize == 0) { + if (sh->sg_tablesize == 0) + printk(KERN_WARNING "Warning: SG size had to be fixed.\n" + "This might be a PM2012 with a defective Firmware" + "\nContact DPT support@dpt.com for an upgrade\n"); + sh->sg_tablesize = SG_SIZE; + } + } + hd->sgsize = sh->sg_tablesize; + } + + if(sh != NULL) { + sh->can_queue = hd->queuesize = ntohs(gc->queuesiz); + sh->cmd_per_lun = 0; + } + + if(sh == NULL) { + DBG(DBG_REGISTER, printk(KERN_NOTICE "eata_dma: couldn't register HBA" + " at%x \n", base)); + scsi_unregister(sh); + if (gc->DMA_valid) + free_dma(dma_channel); + + reg_IRQ[gc->IRQ]--; + if (reg_IRQ[gc->IRQ] == 0) + free_irq(gc->IRQ, NULL); + if (gc->IRQ_TR == FALSE) + reg_IRQL[gc->IRQ] = FALSE; + return (FALSE); + } + + + hd->broken_INQUIRY = (bugs & BROKEN_INQUIRY); + + if(hd->broken_INQUIRY == TRUE) { + strcpy(hd->vendor, "DPT"); + strcpy(hd->name, "??????????"); + strcpy(hd->revision, "???.?"); + hd->firmware_revision = 0; + } else { + strncpy(hd->vendor, &buff[8], 8); + hd->vendor[8] = 0; + strncpy(hd->name, &buff[16], 17); + hd->name[17] = 0; + hd->revision[0] = buff[32]; + hd->revision[1] = buff[33]; + hd->revision[2] = buff[34]; + hd->revision[3] = '.'; + hd->revision[4] = buff[35]; + hd->revision[5] = 0; + hd->firmware_revision = (buff[32] << 24) + (buff[33] << 16) + + (buff[34] << 8) + buff[35]; + } + + if (hd->firmware_revision >= (('0'<<24) + ('7'<<16) + ('G'<< 8) + '0')) + hd->immediate_support = 1; + else + hd->immediate_support = 0; + + switch (ntohl(gc->len)) { + case 0x1c: + hd->EATA_revision = 'a'; + break; + case 0x1e: + hd->EATA_revision = 'b'; + break; + case 0x22: + hd->EATA_revision = 'c'; + break; + case 0x24: + hd->EATA_revision = 'z'; + default: + hd->EATA_revision = '?'; + } + + + if(ntohl(gc->len) >= 0x22) { + sh->max_id = gc->MAX_ID + 1; + sh->max_lun = gc->MAX_LUN + 1; + } else { + sh->max_id = 8; + sh->max_lun = 8; + } + + hd->HBA_number = sh->host_no; + hd->channel = gc->MAX_CHAN; + sh->max_channel = gc->MAX_CHAN; + sh->unique_id = base; + sh->base = (char *) base; + sh->io_port = base; + sh->n_io_port = 9; + sh->irq = gc->IRQ; + sh->dma_channel = dma_channel; + + /* FIXME: + * SCSI midlevel code should support different HBA ids on every channel + */ + sh->this_id = gc->scsi_id[3]; + + if (gc->SECOND) + hd->primary = FALSE; + else + hd->primary = TRUE; + + sh->wish_block = FALSE; + + if (hd->bustype != IS_ISA) { + sh->unchecked_isa_dma = FALSE; + } else { + sh->unchecked_isa_dma = TRUE; /* We're doing ISA DMA */ + } + + for(x = 0; x <= 11; x++){ /* Initialize min. latency */ + hd->writes_lat[x][1] = 0xffffffff; + hd->reads_lat[x][1] = 0xffffffff; + } + hd->all_lat[1] = 0xffffffff; + + hd->next = NULL; /* build a linked list of all HBAs */ + hd->prev = last_HBA; + if(hd->prev != NULL) + SD(hd->prev)->next = sh; + last_HBA = sh; + if (first_HBA == NULL) + first_HBA = sh; + registered_HBAs++; + + return (TRUE); +} + + + +void find_EISA(struct get_conf *buf, Scsi_Host_Template * tpnt) +{ + u32 base; + int i; + +#if CHECKPAL + u8 pal1, pal2, pal3; +#endif + + for (i = 0; i < MAXEISA; i++) { + if (EISAbases[i] == TRUE) { /* Still a possibility ? */ + + base = 0x1c88 + (i * 0x1000); +#if CHECKPAL + pal1 = inb((u16)base - 8); + pal2 = inb((u16)base - 7); + pal3 = inb((u16)base - 6); + + if (((pal1 == DPT_ID1) && (pal2 == DPT_ID2)) || + ((pal1 == NEC_ID1) && (pal2 == NEC_ID2) && (pal3 == NEC_ID3))|| + ((pal1 == ATT_ID1) && (pal2 == ATT_ID2) && (pal3 == ATT_ID3))){ + DBG(DBG_PROBE, printk("EISA EATA id tags found: %x %x %x \n", + (int)pal1, (int)pal2, (int)pal3)); +#endif + if (get_conf_PIO(base, buf) == TRUE) { + if (buf->IRQ) { + DBG(DBG_EISA, printk("Registering EISA HBA\n")); + register_HBA(base, buf, tpnt, IS_EISA); + } else + printk("eata_dma: No valid IRQ. HBA removed from list\n"); + } +#if CHECK_BLINK + else { + if (check_blink_state(base)) + printk("HBA is in BLINK state. Consult your HBAs " + "Manual to correct this.\n"); + } +#endif + /* Nothing found here so we take it from the list */ + EISAbases[i] = 0; +#if CHECKPAL + } +#endif + } + } + return; +} + +void find_ISA(struct get_conf *buf, Scsi_Host_Template * tpnt) +{ + int i; + + for (i = 0; i < MAXISA; i++) { + if (ISAbases[i]) { + if (get_conf_PIO(ISAbases[i],buf) == TRUE){ + DBG(DBG_ISA, printk("Registering ISA HBA\n")); + register_HBA(ISAbases[i], buf, tpnt, IS_ISA); + } +#if CHECK_BLINK + else { + if (check_blink_state(ISAbases[i])) + printk("HBA is in BLINK state. Consult your HBAs " + "Manual to correct this.\n"); + } +#endif + ISAbases[i] = 0; + } + } + return; +} + +void find_PCI(struct get_conf *buf, Scsi_Host_Template * tpnt) +{ + +#ifndef CONFIG_PCI + printk("eata_dma: kernel PCI support not enabled. Skipping scan for PCI HBAs.\n"); +#else + + u8 pci_bus, pci_device_fn; + static s16 pci_index = 0; /* Device index to PCI BIOS calls */ + u32 base = 0; + u16 com_adr; + u16 rev_device; + u32 error, i, x; + u8 pal1, pal2, pal3; + + if (pcibios_present()) { + for (i = 0; i <= MAXPCI; ++i, ++pci_index) { + if (pcibios_find_device(PCI_VENDOR_ID_DPT, PCI_DEVICE_ID_DPT, + pci_index, &pci_bus, &pci_device_fn)) + break; + DBG(DBG_PROBE && DBG_PCI, + printk("eata_dma: find_PCI, HBA at bus %d, device %d," + " function %d, index %d\n", (s32)pci_bus, + (s32)((pci_device_fn & 0xf8) >> 3), + (s32)(pci_device_fn & 7), pci_index)); + + if (!(error = pcibios_read_config_word(pci_bus, pci_device_fn, + PCI_CLASS_DEVICE, &rev_device))) { + if (rev_device == PCI_CLASS_STORAGE_SCSI) { + if (!(error = pcibios_read_config_word(pci_bus, + pci_device_fn, PCI_COMMAND, + (u16 *) & com_adr))) { + if (!((com_adr & PCI_COMMAND_IO) && + (com_adr & PCI_COMMAND_MASTER))) { + printk("eata_dma: find_PCI, HBA has IO or" + " BUSMASTER mode disabled\n"); + continue; + } + } else + printk("eata_dma: find_PCI, error %x while reading " + "PCI_COMMAND\n", error); + } else + printk("eata_dma: find_PCI, DEVICECLASSID %x didn't match\n", + rev_device); + } else { + printk("eata_dma: find_PCI, error %x while reading " + "PCI_CLASS_BASE\n", + error); + continue; + } + + if (!(error = pcibios_read_config_dword(pci_bus, pci_device_fn, + PCI_BASE_ADDRESS_0, (int *) &base))){ + + /* Check if the address is valid */ + if (base & 0x01) { + base &= 0xfffffffe; + /* EISA tag there ? */ + pal1 = inb(base); + pal2 = inb(base + 1); + pal3 = inb(base + 2); + if (((pal1 == DPT_ID1) && (pal2 == DPT_ID2)) || + ((pal1 == NEC_ID1) && (pal2 == NEC_ID2) && + (pal3 == NEC_ID3)) || + ((pal1 == ATT_ID1) && (pal2 == ATT_ID2) && + (pal3 == ATT_ID3))) + base += 0x08; + else + base += 0x10; /* Now, THIS is the real address */ + + if (base != 0x1f8) { + /* We didn't find it in the primary search */ + if (get_conf_PIO(base, buf) == TRUE) { + + /* OK. We made it till here, so we can go now + * and register it. We only have to check and + * eventually remove it from the EISA and ISA list + */ + DBG(DBG_PCI, printk("Registering PCI HBA\n")); + register_HBA(base, buf, tpnt, IS_PCI); + + if (base < 0x1000) { + for (x = 0; x < MAXISA; ++x) { + if (ISAbases[x] == base) { + ISAbases[x] = 0; + break; + } + } + } else if ((base & 0x0fff) == 0x0c88) + EISAbases[(base >> 12) & 0x0f] = 0; + continue; /* break; */ + } +#if CHECK_BLINK + else if (check_blink_state(base) == TRUE) { + printk("eata_dma: HBA is in BLINK state.\n" + "Consult your HBAs manual to correct this.\n"); + } +#endif + } + } + } else { + printk("eata_dma: error %x while reading " + "PCI_BASE_ADDRESS_0\n", error); + } + } + } else { + printk("eata_dma: No BIOS32 extensions present. This driver release " + "still depends on it.\n" + " Skipping scan for PCI HBAs. \n"); + } +#endif /* #ifndef CONFIG_PCI */ + return; +} + +int eata_detect(Scsi_Host_Template * tpnt) +{ + struct Scsi_Host *HBA_ptr; + struct get_conf gc; + int i; + + DBG((DBG_PROBE && DBG_DELAY) || DPT_DEBUG, + printk("Using lots of delays to let you read the debugging output\n")); + + tpnt->proc_dir = &proc_scsi_eata_dma; + + status = scsi_init_malloc(512, GFP_ATOMIC | GFP_DMA); + dma_scratch = scsi_init_malloc(1024, GFP_ATOMIC | GFP_DMA); + + if(status == NULL || dma_scratch == NULL) { + printk("eata_dma: can't allocate enough memory to probe for hosts !\n"); + return(0); + } + + dma_scratch += 4; + + find_PCI(&gc, tpnt); + + find_EISA(&gc, tpnt); + + find_ISA(&gc, tpnt); + + for (i = 0; i < MAXIRQ; i++) { /* Now that we know what we have, we */ + if (reg_IRQ[i] >= 1){ /* exchange the interrupt handler which */ + free_irq(i, NULL); /* we used for probing with the real one */ + request_irq(i, (void *)(eata_int_handler), SA_INTERRUPT|SA_SHIRQ, + "eata_dma", NULL); + } + } + + HBA_ptr = first_HBA; + + if (registered_HBAs != 0) { + printk("EATA (Extended Attachment) driver version: %d.%d%s" + "\ndeveloped in co-operation with DPT\n" + "(c) 1993-96 Michael Neuffer, mike@i-Connect.Net\n", + VER_MAJOR, VER_MINOR, VER_SUB); + printk("Registered HBAs:"); + printk("\nHBA no. Boardtype Revis EATA Bus BaseIO IRQ" + " DMA Ch ID Pr QS S/G IS\n"); + for (i = 1; i <= registered_HBAs; i++) { + printk("scsi%-2d: %.12s v%s 2.0%c %s %#.4x %2d", + HBA_ptr->host_no, SD(HBA_ptr)->name, SD(HBA_ptr)->revision, + SD(HBA_ptr)->EATA_revision, (SD(HBA_ptr)->bustype == 'P')? + "PCI ":(SD(HBA_ptr)->bustype == 'E')?"EISA":"ISA ", + (u32) HBA_ptr->base, HBA_ptr->irq); + if(HBA_ptr->dma_channel != BUSMASTER) + printk(" %2x ", HBA_ptr->dma_channel); + else + printk(" %s", "BMST"); + printk(" %d %d %c %3d %3d %c\n", + SD(HBA_ptr)->channel+1, HBA_ptr->this_id, + (SD(HBA_ptr)->primary == TRUE)?'Y':'N', + HBA_ptr->can_queue, HBA_ptr->sg_tablesize, + (SD(HBA_ptr)->immediate_support == TRUE)?'Y':'N'); + HBA_ptr = SD(HBA_ptr)->next; + } + } else { + scsi_init_free((void *)status, 512); + } + + scsi_init_free((void *)dma_scratch - 4, 1024); + + DBG(DPT_DEBUG, DELAY(12)); + + return(registered_HBAs); +} + +#ifdef MODULE +/* Eventually this will go into an include file, but this will be later */ +Scsi_Host_Template driver_template = EATA_DMA; +#include "scsi_module.c" +#endif + +/* + * Overrides for Emacs so that we almost follow Linus's tabbing style. + * Emacs will notice this stuff at the end of the file and automatically + * adjust the settings for this buffer only. This must remain at the end + * of the file. + * --------------------------------------------------------------------------- + * Local variables: + * c-indent-level: 4 + * c-brace-imaginary-offset: 0 + * c-brace-offset: -4 + * c-argdecl-indent: 4 + * c-label-offset: -4 + * c-continued-statement-offset: 4 + * c-continued-brace-offset: 0 + * tab-width: 8 + * End: + */ diff --git a/linux/dev/drivers/scsi/g_NCR5380.c b/linux/dev/drivers/scsi/g_NCR5380.c new file mode 100644 index 0000000..687dd36 --- /dev/null +++ b/linux/dev/drivers/scsi/g_NCR5380.c @@ -0,0 +1,735 @@ +/* + * Generic Generic NCR5380 driver + * + * Copyright 1993, Drew Eckhardt + * Visionary Computing + * (Unix and Linux consulting and custom programming) + * drew@colorado.edu + * +1 (303) 440-4894 + * + * NCR53C400 extensions (c) 1994,1995,1996, Kevin Lentin + * K.Lentin@cs.monash.edu.au + * + * ALPHA RELEASE 1. + * + * For more information, please consult + * + * NCR 5380 Family + * SCSI Protocol Controller + * Databook + * + * NCR Microelectronics + * 1635 Aeroplaza Drive + * Colorado Springs, CO 80916 + * 1+ (719) 578-3400 + * 1+ (800) 334-5454 + */ + +/* + * TODO : flesh out DMA support, find some one actually using this (I have + * a memory mapped Trantor board that works fine) + */ + +/* + * Options : + * + * PARITY - enable parity checking. Not supported. + * + * SCSI2 - enable support for SCSI-II tagged queueing. Untested. + * + * USLEEP - enable support for devices that don't disconnect. Untested. + * + * The card is detected and initialized in one of several ways : + * 1. With command line overrides - NCR5380=port,irq may be + * used on the LILO command line to override the defaults. + * + * 2. With the GENERIC_NCR5380_OVERRIDE compile time define. This is + * specified as an array of address, irq, dma, board tuples. Ie, for + * one board at 0x350, IRQ5, no dma, I could say + * -DGENERIC_NCR5380_OVERRIDE={{0xcc000, 5, DMA_NONE, BOARD_NCR5380}} + * + * -1 should be specified for no or DMA interrupt, -2 to autoprobe for an + * IRQ line if overridden on the command line. + * + * 3. When included as a module, with arguments passed on the command line: + * ncr_irq=xx the interrupt + * ncr_addr=xx the port or base address (for port or memory + * mapped, resp.) + * ncr_dma=xx the DMA + * ncr_5380=1 to set up for a NCR5380 board + * ncr_53c400=1 to set up for a NCR53C400 board + * e.g. + * modprobe g_NCR5380 ncr_irq=5 ncr_addr=0x350 ncr_5380=1 + * for a port mapped NCR5380 board or + * modprobe g_NCR5380 ncr_irq=255 ncr_addr=0xc8000 ncr_53c400=1 + * for a memory mapped NCR53C400 board with interrupts disabled. + * + * 255 should be specified for no or DMA interrupt, 254 to autoprobe for an + * IRQ line if overridden on the command line. + * + */ + +#ifdef MACH +#define GENERIC_NCR5380_OVERRIDE {{(NCR5380_map_type)0x350,5,0,BOARD_NCR53C400}}; +#define CONFIG_SCSI_GENERIC_NCR53C400 +#define CONFIG_SCSI_G_NCR5380_MEM +#endif + +#define AUTOPROBE_IRQ +#define AUTOSENSE + +#include + +#ifdef CONFIG_SCSI_GENERIC_NCR53C400 +#define NCR53C400_PSEUDO_DMA 1 +#define PSEUDO_DMA +#define NCR53C400 +#define NCR5380_STATS +#undef NCR5380_STAT_LIMIT +#endif +#if defined(CONFIG_SCSI_G_NCR5380_PORT) && defined(CONFIG_SCSI_G_NCR5380_MEM) +#error You can not configure the Generic NCR 5380 SCSI Driver for memory mapped I/O and port mapped I/O at the same time (yet) +#endif +#if !defined(CONFIG_SCSI_G_NCR5380_PORT) && !defined(CONFIG_SCSI_G_NCR5380_MEM) +#error You must configure the Generic NCR 5380 SCSI Driver for one of memory mapped I/O and port mapped I/O. +#endif + +#include +#include +#include +#include +#include +#include "scsi.h" +#include "hosts.h" +#include "g_NCR5380.h" +#include "NCR5380.h" +#include "constants.h" +#include "sd.h" +#include + +struct proc_dir_entry proc_scsi_g_ncr5380 = { + PROC_SCSI_GENERIC_NCR5380, 9, "g_NCR5380", + S_IFDIR | S_IRUGO | S_IXUGO, 2 +}; + +#define NCR_NOT_SET 0 +static int ncr_irq=NCR_NOT_SET; +static int ncr_dma=NCR_NOT_SET; +static int ncr_addr=NCR_NOT_SET; +static int ncr_5380=NCR_NOT_SET; +static int ncr_53c400=NCR_NOT_SET; + +static struct override { + NCR5380_implementation_fields; + int irq; + int dma; + int board; /* Use NCR53c400, Ricoh, etc. extensions ? */ +} overrides +#ifdef GENERIC_NCR5380_OVERRIDE + [] = GENERIC_NCR5380_OVERRIDE +#else + [1] = {{0,},}; +#endif + +#define NO_OVERRIDES (sizeof(overrides) / sizeof(struct override)) + +/* + * Function : static internal_setup(int board, char *str, int *ints) + * + * Purpose : LILO command line initialization of the overrides array, + * + * Inputs : board - either BOARD_NCR5380 for a normal NCR5380 board, + * or BOARD_NCR53C400 for a NCR53C400 board. str - unused, ints - + * array of integer parameters with ints[0] equal to the number of ints. + * + */ + +static void internal_setup(int board, char *str, int *ints) { + static int commandline_current = 0; + switch (board) { + case BOARD_NCR5380: + if (ints[0] != 2 && ints[0] != 3) { + printk("generic_NCR5380_setup : usage ncr5380=" STRVAL(NCR5380_map_name) ",irq,dma\n"); + return; + } + case BOARD_NCR53C400: + if (ints[0] != 2) { + printk("generic_NCR53C400_setup : usage ncr53c400=" STRVAL(NCR5380_map_name) ",irq\n"); + return; + } + } + + if (commandline_current < NO_OVERRIDES) { + overrides[commandline_current].NCR5380_map_name = (NCR5380_map_type)ints[1]; + overrides[commandline_current].irq = ints[2]; + if (ints[0] == 3) + overrides[commandline_current].dma = ints[3]; + else + overrides[commandline_current].dma = DMA_NONE; + overrides[commandline_current].board = board; + ++commandline_current; + } +} + +/* + * Function : generic_NCR5380_setup (char *str, int *ints) + * + * Purpose : LILO command line initialization of the overrides array, + * + * Inputs : str - unused, ints - array of integer parameters with ints[0] + * equal to the number of ints. + */ + +void generic_NCR5380_setup (char *str, int *ints) { + internal_setup (BOARD_NCR5380, str, ints); +} + +/* + * Function : generic_NCR53C400_setup (char *str, int *ints) + * + * Purpose : LILO command line initialization of the overrides array, + * + * Inputs : str - unused, ints - array of integer parameters with ints[0] + * equal to the number of ints. + */ + +void generic_NCR53C400_setup (char *str, int *ints) { + internal_setup (BOARD_NCR53C400, str, ints); +} + +/* + * Function : int generic_NCR5380_detect(Scsi_Host_Template * tpnt) + * + * Purpose : initializes generic NCR5380 driver based on the + * command line / compile time port and irq definitions. + * + * Inputs : tpnt - template for this SCSI adapter. + * + * Returns : 1 if a host adapter was found, 0 if not. + * + */ + +int generic_NCR5380_detect(Scsi_Host_Template * tpnt) { + static int current_override = 0; + int count; + int flags = 0; + struct Scsi_Host *instance; + + if (ncr_irq != NCR_NOT_SET) + overrides[0].irq=ncr_irq; + if (ncr_dma != NCR_NOT_SET) + overrides[0].dma=ncr_dma; + if (ncr_addr != NCR_NOT_SET) + overrides[0].NCR5380_map_name=(NCR5380_map_type)ncr_addr; + if (ncr_5380 != NCR_NOT_SET) + overrides[0].board=BOARD_NCR5380; + else if (ncr_53c400 != NCR_NOT_SET) + overrides[0].board=BOARD_NCR53C400; + + tpnt->proc_dir = &proc_scsi_g_ncr5380; + + for (count = 0; current_override < NO_OVERRIDES; ++current_override) { + if (!(overrides[current_override].NCR5380_map_name)) + continue; + + switch (overrides[current_override].board) { + case BOARD_NCR5380: + flags = FLAG_NO_PSEUDO_DMA; + break; + case BOARD_NCR53C400: + flags = FLAG_NCR53C400; + break; + } + + instance = scsi_register (tpnt, sizeof(struct NCR5380_hostdata)); + instance->NCR5380_instance_name = overrides[current_override].NCR5380_map_name; + + NCR5380_init(instance, flags); + + if (overrides[current_override].irq != IRQ_AUTO) + instance->irq = overrides[current_override].irq; + else + instance->irq = NCR5380_probe_irq(instance, 0xffff); + + if (instance->irq != IRQ_NONE) + if (request_irq(instance->irq, generic_NCR5380_intr, SA_INTERRUPT, "NCR5380", NULL)) { + printk("scsi%d : IRQ%d not free, interrupts disabled\n", + instance->host_no, instance->irq); + instance->irq = IRQ_NONE; + } + + if (instance->irq == IRQ_NONE) { + printk("scsi%d : interrupts not enabled. for better interactive performance,\n", instance->host_no); + printk("scsi%d : please jumper the board for a free IRQ.\n", instance->host_no); + } + + printk("scsi%d : at " STRVAL(NCR5380_map_name) " 0x%x", instance->host_no, (unsigned int)instance->NCR5380_instance_name); + if (instance->irq == IRQ_NONE) + printk (" interrupts disabled"); + else + printk (" irq %d", instance->irq); + printk(" options CAN_QUEUE=%d CMD_PER_LUN=%d release=%d", + CAN_QUEUE, CMD_PER_LUN, GENERIC_NCR5380_PUBLIC_RELEASE); + NCR5380_print_options(instance); + printk("\n"); + + ++current_override; + ++count; + } + return count; +} + +const char * generic_NCR5380_info (struct Scsi_Host* host) { + static const char string[]="Generic NCR5380/53C400 Driver"; + return string; +} + +int generic_NCR5380_release_resources(struct Scsi_Host * instance) +{ + NCR5380_local_declare(); + + NCR5380_setup(instance); + + if (instance->irq != IRQ_NONE) + free_irq(instance->irq, NULL); + + return 0; +} + +#ifdef BIOSPARAM +/* + * Function : int generic_NCR5380_biosparam(Disk * disk, kdev_t dev, int *ip) + * + * Purpose : Generates a BIOS / DOS compatible H-C-S mapping for + * the specified device / size. + * + * Inputs : size = size of device in sectors (512 bytes), dev = block device + * major / minor, ip[] = {heads, sectors, cylinders} + * + * Returns : always 0 (success), initializes ip + * + */ + +/* + * XXX Most SCSI boards use this mapping, I could be incorrect. Some one + * using hard disks on a trantor should verify that this mapping corresponds + * to that used by the BIOS / ASPI driver by running the linux fdisk program + * and matching the H_C_S coordinates to what DOS uses. + */ + +int generic_NCR5380_biosparam(Disk * disk, kdev_t dev, int *ip) +{ + int size = disk->capacity; + ip[0] = 64; + ip[1] = 32; + ip[2] = size >> 11; + return 0; +} +#endif + +#if NCR53C400_PSEUDO_DMA +static inline int NCR5380_pread (struct Scsi_Host *instance, unsigned char *dst, int len) +{ + int blocks = len / 128; + int start = 0; + int i; + int bl; + NCR5380_local_declare(); + + NCR5380_setup(instance); + +#if (NDEBUG & NDEBUG_C400_PREAD) + printk("53C400r: About to read %d blocks for %d bytes\n", blocks, len); +#endif + + NCR5380_write(C400_CONTROL_STATUS_REG, CSR_BASE | CSR_TRANS_DIR); + NCR5380_write(C400_BLOCK_COUNTER_REG, blocks); + while (1) { + +#if (NDEBUG & NDEBUG_C400_PREAD) + printk("53C400r: %d blocks left\n", blocks); +#endif + + if ((bl=NCR5380_read(C400_BLOCK_COUNTER_REG)) == 0) { +#if (NDEBUG & NDEBUG_C400_PREAD) + if (blocks) + printk("53C400r: blocks still == %d\n", blocks); + else + printk("53C400r: Exiting loop\n"); +#endif + break; + } + +#if 1 + if (NCR5380_read(C400_CONTROL_STATUS_REG) & CSR_GATED_53C80_IRQ) { + printk("53C400r: Got 53C80_IRQ start=%d, blocks=%d\n", start, blocks); + return -1; + } +#endif + +#if (NDEBUG & NDEBUG_C400_PREAD) + printk("53C400r: Waiting for buffer, bl=%d\n", bl); +#endif + + while (NCR5380_read(C400_CONTROL_STATUS_REG) & CSR_HOST_BUF_NOT_RDY) + ; +#if (NDEBUG & NDEBUG_C400_PREAD) + printk("53C400r: Transferring 128 bytes\n"); +#endif + +#ifdef CONFIG_SCSI_G_NCR5380_PORT + for (i=0; i<128; i++) + dst[start+i] = NCR5380_read(C400_HOST_BUFFER); +#else + /* implies CONFIG_SCSI_G_NCR5380_MEM */ + memmove(dst+start,NCR53C400_host_buffer+NCR5380_map_name,128); +#endif + start+=128; + blocks--; + } + + if (blocks) { +#if (NDEBUG & NDEBUG_C400_PREAD) + printk("53C400r: EXTRA: Waiting for buffer\n"); +#endif + while (NCR5380_read(C400_CONTROL_STATUS_REG) & CSR_HOST_BUF_NOT_RDY) + ; + +#if (NDEBUG & NDEBUG_C400_PREAD) + printk("53C400r: Transferring EXTRA 128 bytes\n"); +#endif +#ifdef CONFIG_SCSI_G_NCR5380_PORT + for (i=0; i<128; i++) + dst[start+i] = NCR5380_read(C400_HOST_BUFFER); +#else + /* implies CONFIG_SCSI_G_NCR5380_MEM */ + memmove(dst+start,NCR53C400_host_buffer+NCR5380_map_name,128); +#endif + start+=128; + blocks--; + } +#if (NDEBUG & NDEBUG_C400_PREAD) + else + printk("53C400r: No EXTRA required\n"); +#endif + +#if (NDEBUG & NDEBUG_C400_PREAD) + printk("53C400r: Final values: blocks=%d start=%d\n", blocks, start); +#endif + + if (!(NCR5380_read(C400_CONTROL_STATUS_REG) & CSR_GATED_53C80_IRQ)) + printk("53C400r: no 53C80 gated irq after transfer"); +#if (NDEBUG & NDEBUG_C400_PREAD) + else + printk("53C400r: Got 53C80 interrupt and tried to clear it\n"); +#endif + +/* DON'T DO THIS - THEY NEVER ARRIVE! + printk("53C400r: Waiting for 53C80 registers\n"); + while (NCR5380_read(C400_CONTROL_STATUS_REG) & CSR_53C80_REG) + ; +*/ + + if (!(NCR5380_read(BUS_AND_STATUS_REG) & BASR_END_DMA_TRANSFER)) + printk("53C400r: no end dma signal\n"); +#if (NDEBUG & NDEBUG_C400_PREAD) + else + printk("53C400r: end dma as expected\n"); +#endif + + NCR5380_write(MODE_REG, MR_BASE); + NCR5380_read(RESET_PARITY_INTERRUPT_REG); + return 0; +} + +static inline int NCR5380_pwrite (struct Scsi_Host *instance, unsigned char *src, int len) +{ + int blocks = len / 128; + int start = 0; + int i; + int bl; + NCR5380_local_declare(); + + NCR5380_setup(instance); + +#if (NDEBUG & NDEBUG_C400_PWRITE) + printk("53C400w: About to write %d blocks for %d bytes\n", blocks, len); +#endif + + NCR5380_write(C400_CONTROL_STATUS_REG, CSR_BASE); + NCR5380_write(C400_BLOCK_COUNTER_REG, blocks); + while (1) { + if (NCR5380_read(C400_CONTROL_STATUS_REG) & CSR_GATED_53C80_IRQ) { + printk("53C400w: Got 53C80_IRQ start=%d, blocks=%d\n", start, blocks); + return -1; + } + + if ((bl=NCR5380_read(C400_BLOCK_COUNTER_REG)) == 0) { +#if (NDEBUG & NDEBUG_C400_PWRITE) + if (blocks) + printk("53C400w: exiting loop, blocks still == %d\n", blocks); + else + printk("53C400w: exiting loop\n"); +#endif + break; + } + +#if (NDEBUG & NDEBUG_C400_PWRITE) + printk("53C400w: %d blocks left\n", blocks); + + printk("53C400w: waiting for buffer, bl=%d\n", bl); +#endif + while (NCR5380_read(C400_CONTROL_STATUS_REG) & CSR_HOST_BUF_NOT_RDY) + ; + +#if (NDEBUG & NDEBUG_C400_PWRITE) + printk("53C400w: transferring 128 bytes\n"); +#endif +#ifdef CONFIG_SCSI_G_NCR5380_PORT + for (i=0; i<128; i++) + NCR5380_write(C400_HOST_BUFFER, src[start+i]); +#else + /* implies CONFIG_SCSI_G_NCR5380_MEM */ + memmove(NCR53C400_host_buffer+NCR5380_map_name,src+start,128); +#endif + start+=128; + blocks--; + } + if (blocks) { +#if (NDEBUG & NDEBUG_C400_PWRITE) + printk("53C400w: EXTRA waiting for buffer\n"); +#endif + while (NCR5380_read(C400_CONTROL_STATUS_REG) & CSR_HOST_BUF_NOT_RDY) + ; + +#if (NDEBUG & NDEBUG_C400_PWRITE) + printk("53C400w: transferring EXTRA 128 bytes\n"); +#endif +#ifdef CONFIG_SCSI_G_NCR5380_PORT + for (i=0; i<128; i++) + NCR5380_write(C400_HOST_BUFFER, src[start+i]); +#else + /* implies CONFIG_SCSI_G_NCR5380_MEM */ + memmove(NCR53C400_host_buffer+NCR5380_map_name,src+start,128); +#endif + start+=128; + blocks--; + } +#if (NDEBUG & NDEBUG_C400_PWRITE) + else + printk("53C400w: No EXTRA required\n"); +#endif + +#if (NDEBUG & NDEBUG_C400_PWRITE) + printk("53C400w: Final values: blocks=%d start=%d\n", blocks, start); +#endif + +#if 0 + printk("53C400w: waiting for registers to be available\n"); + THEY NEVER DO! + while (NCR5380_read(C400_CONTROL_STATUS_REG) & CSR_53C80_REG) + ; + printk("53C400w: Got em\n"); +#endif + + /* Let's wait for this instead - could be ugly */ + /* All documentation says to check for this. Maybe my hardware is too + * fast. Waiting for it seems to work fine! KLL + */ + while (!(i = NCR5380_read(C400_CONTROL_STATUS_REG) & CSR_GATED_53C80_IRQ)) + ; + + /* + * I know. i is certainly != 0 here but the loop is new. See previous + * comment. + */ + if (i) { +#if (NDEBUG & NDEBUG_C400_PWRITE) + printk("53C400w: got 53C80 gated irq (last block)\n"); +#endif + if (!((i=NCR5380_read(BUS_AND_STATUS_REG)) & BASR_END_DMA_TRANSFER)) + printk("53C400w: No END OF DMA bit - WHOOPS! BASR=%0x\n",i); +#if (NDEBUG & NDEBUG_C400_PWRITE) + else + printk("53C400w: Got END OF DMA\n"); +#endif + } + else + printk("53C400w: no 53C80 gated irq after transfer (last block)\n"); + +#if 0 + if (!(NCR5380_read(BUS_AND_STATUS_REG) & BASR_END_DMA_TRANSFER)) { + printk("53C400w: no end dma signal\n"); + } +#endif + +#if (NDEBUG & NDEBUG_C400_PWRITE) + printk("53C400w: waiting for last byte...\n"); +#endif + while (!(NCR5380_read(TARGET_COMMAND_REG) & TCR_LAST_BYTE_SENT)) + ; + +#if (NDEBUG & NDEBUG_C400_PWRITE) + printk("53C400w: got last byte.\n"); + printk("53C400w: pwrite exiting with status 0, whoopee!\n"); +#endif + return 0; +} +#endif /* PSEUDO_DMA */ + +#include "NCR5380.c" + +#define PRINTP(x) len += sprintf(buffer+len, x) +#define ANDP , + +static int sprint_opcode(char* buffer, int len, int opcode) { + int start = len; + PRINTP("0x%02x " ANDP opcode); + return len-start; +} + +static int sprint_command (char* buffer, int len, unsigned char *command) { + int i,s,start=len; + len += sprint_opcode(buffer, len, command[0]); + for ( i = 1, s = COMMAND_SIZE(command[0]); i < s; ++i) + PRINTP("%02x " ANDP command[i]); + PRINTP("\n"); + return len-start; +} + +static int sprint_Scsi_Cmnd (char* buffer, int len, Scsi_Cmnd *cmd) { + int start = len; + PRINTP("host number %d destination target %d, lun %d\n" ANDP + cmd->host->host_no ANDP + cmd->target ANDP + cmd->lun); + PRINTP(" command = "); + len += sprint_command (buffer, len, cmd->cmnd); + return len-start; +} + +int generic_NCR5380_proc_info(char* buffer, char** start, off_t offset, int length, int hostno, int inout) +{ + int len = 0; + NCR5380_local_declare(); + unsigned char status; + int i; + struct Scsi_Host *scsi_ptr; + Scsi_Cmnd *ptr; + Scsi_Device *dev; + struct NCR5380_hostdata *hostdata; + + cli(); + + for (scsi_ptr = first_instance; scsi_ptr; scsi_ptr=scsi_ptr->next) + if (scsi_ptr->host_no == hostno) + break; + NCR5380_setup(scsi_ptr); + hostdata = (struct NCR5380_hostdata *)scsi_ptr->hostdata; + + PRINTP("SCSI host number %d : %s\n" ANDP scsi_ptr->host_no ANDP scsi_ptr->hostt->name); + PRINTP("Generic NCR5380 driver version %d\n" ANDP GENERIC_NCR5380_PUBLIC_RELEASE); + PRINTP("NCR5380 core version %d\n" ANDP NCR5380_PUBLIC_RELEASE); +#ifdef NCR53C400 + PRINTP("NCR53C400 extension version %d\n" ANDP NCR53C400_PUBLIC_RELEASE); + PRINTP("NCR53C400 card%s detected\n" ANDP (((struct NCR5380_hostdata *)scsi_ptr->hostdata)->flags & FLAG_NCR53C400)?"":" not"); +# if NCR53C400_PSEUDO_DMA + PRINTP("NCR53C400 pseudo DMA used\n"); +# endif +#else + PRINTP("NO NCR53C400 driver extensions\n"); +#endif + PRINTP("Using %s mapping at %s 0x%x, " ANDP STRVAL(NCR5380_map_config) ANDP STRVAL(NCR5380_map_name) ANDP scsi_ptr->NCR5380_instance_name); + if (scsi_ptr->irq == IRQ_NONE) + PRINTP("no interrupt\n"); + else + PRINTP("on interrupt %d\n" ANDP scsi_ptr->irq); + +#ifdef NCR5380_STATS + if (hostdata->connected || hostdata->issue_queue || hostdata->disconnected_queue) + PRINTP("There are commands pending, transfer rates may be crud\n"); + if (hostdata->pendingr) + PRINTP(" %d pending reads" ANDP hostdata->pendingr); + if (hostdata->pendingw) + PRINTP(" %d pending writes" ANDP hostdata->pendingw); + if (hostdata->pendingr || hostdata->pendingw) + PRINTP("\n"); + for (dev = scsi_devices; dev; dev=dev->next) { + if (dev->host == scsi_ptr) { + unsigned long br = hostdata->bytes_read[dev->id]; + unsigned long bw = hostdata->bytes_write[dev->id]; + long tr = hostdata->time_read[dev->id] / HZ; + long tw = hostdata->time_write[dev->id] / HZ; + + PRINTP(" T:%d %s " ANDP dev->id ANDP (dev->type < MAX_SCSI_DEVICE_CODE) ? scsi_device_types[(int)dev->type] : "Unknown"); + for (i=0; i<8; i++) + if (dev->vendor[i] >= 0x20) + *(buffer+(len++)) = dev->vendor[i]; + *(buffer+(len++)) = ' '; + for (i=0; i<16; i++) + if (dev->model[i] >= 0x20) + *(buffer+(len++)) = dev->model[i]; + *(buffer+(len++)) = ' '; + for (i=0; i<4; i++) + if (dev->rev[i] >= 0x20) + *(buffer+(len++)) = dev->rev[i]; + *(buffer+(len++)) = ' '; + + PRINTP("\n%10ld kb read in %5ld secs" ANDP br/1024 ANDP tr); + if (tr) + PRINTP(" @ %5ld bps" ANDP br / tr); + + PRINTP("\n%10ld kb written in %5ld secs" ANDP bw/1024 ANDP tw); + if (tw) + PRINTP(" @ %5ld bps" ANDP bw / tw); + PRINTP("\n"); + } + } +#endif + + status = NCR5380_read(STATUS_REG); + if (!(status & SR_REQ)) + PRINTP("REQ not asserted, phase unknown.\n"); + else { + for (i = 0; (phases[i].value != PHASE_UNKNOWN) && + (phases[i].value != (status & PHASE_MASK)); ++i) + ; + PRINTP("Phase %s\n" ANDP phases[i].name); + } + + if (!hostdata->connected) { + PRINTP("No currently connected command\n"); + } else { + len += sprint_Scsi_Cmnd (buffer, len, (Scsi_Cmnd *) hostdata->connected); + } + + PRINTP("issue_queue\n"); + + for (ptr = (Scsi_Cmnd *) hostdata->issue_queue; ptr; + ptr = (Scsi_Cmnd *) ptr->host_scribble) + len += sprint_Scsi_Cmnd (buffer, len, ptr); + + PRINTP("disconnected_queue\n"); + + for (ptr = (Scsi_Cmnd *) hostdata->disconnected_queue; ptr; + ptr = (Scsi_Cmnd *) ptr->host_scribble) + len += sprint_Scsi_Cmnd (buffer, len, ptr); + + *start = buffer + offset; + len -= offset; + if (len > length) + len = length; + sti(); + return len; +} + +#undef PRINTP +#undef ANDP + +#ifdef MODULE +/* Eventually this will go into an include file, but this will be later */ +Scsi_Host_Template driver_template = GENERIC_NCR5380; + +#include +#include "scsi_module.c" +#endif -- cgit v1.2.1