diff options
author | Yang Zhang <yang.z.zhang@intel.com> | 2015-08-28 09:58:54 +0800 |
---|---|---|
committer | Yang Zhang <yang.z.zhang@intel.com> | 2015-09-01 12:44:00 +0800 |
commit | e44e3482bdb4d0ebde2d8b41830ac2cdb07948fb (patch) | |
tree | 66b09f592c55df2878107a468a91d21506104d3f /qemu/roms/openbios/drivers/ide.c | |
parent | 9ca8dbcc65cfc63d6f5ef3312a33184e1d726e00 (diff) |
Add qemu 2.4.0
Change-Id: Ic99cbad4b61f8b127b7dc74d04576c0bcbaaf4f5
Signed-off-by: Yang Zhang <yang.z.zhang@intel.com>
Diffstat (limited to 'qemu/roms/openbios/drivers/ide.c')
-rw-r--r-- | qemu/roms/openbios/drivers/ide.c | 1685 |
1 files changed, 1685 insertions, 0 deletions
diff --git a/qemu/roms/openbios/drivers/ide.c b/qemu/roms/openbios/drivers/ide.c new file mode 100644 index 000000000..327c64a40 --- /dev/null +++ b/qemu/roms/openbios/drivers/ide.c @@ -0,0 +1,1685 @@ +/* + * OpenBIOS polled ide driver + * + * Copyright (C) 2004 Jens Axboe <axboe@suse.de> + * Copyright (C) 2005 Stefan Reinauer <stepan@openbios.org> + * + * Credit goes to Hale Landis for his excellent ata demo software + * OF node handling and some fixes by Stefan Reinauer + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 + * + */ + +#include "config.h" +#include "libopenbios/bindings.h" +#include "kernel/kernel.h" +#include "libc/byteorder.h" +#include "libc/vsprintf.h" + +#include "drivers/drivers.h" +#include "ide.h" +#include "hdreg.h" +#include "timer.h" + +#ifdef CONFIG_DEBUG_IDE +#define IDE_DPRINTF(fmt, args...) \ +do { printk("IDE - %s: " fmt, __func__ , ##args); } while (0) +#else +#define IDE_DPRINTF(fmt, args...) do { } while (0) +#endif + +/* DECLARE data structures for the nodes. */ +DECLARE_UNNAMED_NODE( ob_ide, INSTALL_OPEN, sizeof(struct ide_drive*) ); +DECLARE_UNNAMED_NODE( ob_ide_ctrl, INSTALL_OPEN, sizeof(int)); + +/* + * define to 2 for the standard 2 channels only + */ +#ifndef CONFIG_IDE_NUM_CHANNELS +#define IDE_NUM_CHANNELS 4 +#else +#define IDE_NUM_CHANNELS CONFIG_IDE_NUM_CHANNELS +#endif +#define IDE_MAX_CHANNELS 4 + +#ifndef CONFIG_IDE_FIRST_UNIT +#define FIRST_UNIT 0 +#else +#define FIRST_UNIT CONFIG_IDE_FIRST_UNIT +#endif + +#ifndef CONFIG_IDE_DEV_TYPE +#define DEV_TYPE "ide" +#else +#define DEV_TYPE CONFIG_IDE_DEV_TYPE +#endif + +#ifndef CONFIG_IDE_DEV_NAME +#define DEV_NAME "ide%d" +#else +#define DEV_NAME CONFIG_IDE_DEV_NAME +#endif + +static int current_channel = FIRST_UNIT; + +static struct ide_channel *channels = NULL; + +static inline void ide_add_channel(struct ide_channel *chan) +{ + chan->next = channels; + channels = chan; +} + +static struct ide_channel *ide_seek_channel(const char *name) +{ + struct ide_channel *current; + + current = channels; + while (current) { + if (!strcmp(current->name, name)) + return current; + current = current->next; + } + return NULL; +} + +/* + * don't be pedantic + */ +#undef ATA_PEDANTIC + +static void dump_drive(struct ide_drive *drive) +{ +#ifdef CONFIG_DEBUG_IDE + printk("IDE DRIVE @%lx:\n", (unsigned long)drive); + printk("unit: %d\n",drive->unit); + printk("present: %d\n",drive->present); + printk("type: %d\n",drive->type); + printk("media: %d\n",drive->media); + printk("model: %s\n",drive->model); + printk("nr: %d\n",drive->nr); + printk("cyl: %d\n",drive->cyl); + printk("head: %d\n",drive->head); + printk("sect: %d\n",drive->sect); + printk("bs: %d\n",drive->bs); +#endif +} + +/* + * old style io port operations + */ +static unsigned char +ob_ide_inb(struct ide_channel *chan, unsigned int port) +{ + return inb(chan->io_regs[port]); +} + +static void +ob_ide_outb(struct ide_channel *chan, unsigned char data, unsigned int port) +{ + outb(data, chan->io_regs[port]); +} + +static void +ob_ide_insw(struct ide_channel *chan, + unsigned int port, unsigned char *addr, unsigned int count) +{ + insw(chan->io_regs[port], addr, count); +} + +static void +ob_ide_outsw(struct ide_channel *chan, + unsigned int port, unsigned char *addr, unsigned int count) +{ + outsw(chan->io_regs[port], addr, count); +} + +static inline unsigned char +ob_ide_pio_readb(struct ide_drive *drive, unsigned int offset) +{ + struct ide_channel *chan = drive->channel; + + return chan->obide_inb(chan, offset); +} + +static inline void +ob_ide_pio_writeb(struct ide_drive *drive, unsigned int offset, + unsigned char data) +{ + struct ide_channel *chan = drive->channel; + + chan->obide_outb(chan, data, offset); +} + +static inline void +ob_ide_pio_insw(struct ide_drive *drive, unsigned int offset, + unsigned char *addr, unsigned int len) +{ + struct ide_channel *chan = drive->channel; + + if (len & 1) { + IDE_DPRINTF("%d: command not word aligned\n", drive->nr); + return; + } + + chan->obide_insw(chan, offset, addr, len / 2); +} + +static inline void +ob_ide_pio_outsw(struct ide_drive *drive, unsigned int offset, + unsigned char *addr, unsigned int len) +{ + struct ide_channel *chan = drive->channel; + + if (len & 1) { + IDE_DPRINTF("%d: command not word aligned\n", drive->nr); + return; + } + + chan->obide_outsw(chan, offset, addr, len / 2); +} + +static void +ob_ide_400ns_delay(struct ide_drive *drive) +{ + (void) ob_ide_pio_readb(drive, IDEREG_ASTATUS); + (void) ob_ide_pio_readb(drive, IDEREG_ASTATUS); + (void) ob_ide_pio_readb(drive, IDEREG_ASTATUS); + (void) ob_ide_pio_readb(drive, IDEREG_ASTATUS); + + udelay(1); +} + +static void +ob_ide_error(struct ide_drive *drive, unsigned char stat, const char *msg) +{ +#ifdef CONFIG_DEBUG_IDE + struct ide_channel *chan = drive->channel; + unsigned char err; +#endif + + if (!stat) + stat = ob_ide_pio_readb(drive, IDEREG_STATUS); + + IDE_DPRINTF("ob_ide_error drive<%d>: %s:\n", drive->nr, msg); + IDE_DPRINTF(" cmd=%x, stat=%x", chan->ata_cmd.command, stat); + + if ((stat & (BUSY_STAT | ERR_STAT)) == ERR_STAT) { +#ifdef CONFIG_DEBUG_IDE + err = +#endif + ob_ide_pio_readb(drive, IDEREG_ERROR); + IDE_DPRINTF(", err=%x", err); + } + IDE_DPRINTF("\n"); + +#ifdef CONFIG_DEBUG_IDE + /* + * see if sense is valid and dump that + */ + if (chan->ata_cmd.command == WIN_PACKET) { + struct atapi_command *cmd = &chan->atapi_cmd; + unsigned char old_cdb = cmd->cdb[0]; + + if (cmd->cdb[0] == ATAPI_REQ_SENSE) { + old_cdb = cmd->old_cdb; + + IDE_DPRINTF(" atapi opcode=%02x", old_cdb); + } else { + int i; + + IDE_DPRINTF(" cdb: "); + for (i = 0; i < sizeof(cmd->cdb); i++) + IDE_DPRINTF("%02x ", cmd->cdb[i]); + } + if (cmd->sense_valid) + IDE_DPRINTF(", sense: %02x/%02x/%02x", + cmd->sense.sense_key, cmd->sense.asc, + cmd->sense.ascq); + else + IDE_DPRINTF(", no sense"); + IDE_DPRINTF("\n"); + } +#endif +} + +/* + * wait for 'stat' to be set. returns 1 if failed, 0 if succesful + */ +static int +ob_ide_wait_stat(struct ide_drive *drive, unsigned char ok_stat, + unsigned char bad_stat, unsigned char *ret_stat) +{ + unsigned char stat; + int i; + + ob_ide_400ns_delay(drive); + + for (i = 0; i < 5000; i++) { + stat = ob_ide_pio_readb(drive, IDEREG_STATUS); + if (!(stat & BUSY_STAT)) + break; + + udelay(1000); + } + + if (ret_stat) + *ret_stat = stat; + + if (stat & bad_stat) + return 1; + + if ((stat & ok_stat) || !ok_stat) + return 0; + + return 1; +} + +static int +ob_ide_select_drive(struct ide_drive *drive) +{ + struct ide_channel *chan = drive->channel; + unsigned char control = IDEHEAD_DEV0; + + if (ob_ide_wait_stat(drive, 0, BUSY_STAT, NULL)) { + IDE_DPRINTF("select_drive: timed out\n"); + return 1; + } + + /* + * don't select drive if already active. Note: we always + * wait for BUSY clear + */ + if (drive->unit == chan->selected) + return 0; + + if (drive->unit) + control = IDEHEAD_DEV1; + + ob_ide_pio_writeb(drive, IDEREG_CURRENT, control); + ob_ide_400ns_delay(drive); + + if (ob_ide_wait_stat(drive, 0, BUSY_STAT, NULL)) { + IDE_DPRINTF("select_drive: timed out\n"); + return 1; + } + + chan->selected = drive->unit; + return 0; +} + +static void +ob_ide_write_tasklet(struct ide_drive *drive, struct ata_command *cmd) +{ + ob_ide_pio_writeb(drive, IDEREG_FEATURE, cmd->task[1]); + ob_ide_pio_writeb(drive, IDEREG_NSECTOR, cmd->task[3]); + ob_ide_pio_writeb(drive, IDEREG_SECTOR, cmd->task[7]); + ob_ide_pio_writeb(drive, IDEREG_LCYL, cmd->task[8]); + ob_ide_pio_writeb(drive, IDEREG_HCYL, cmd->task[9]); + + ob_ide_pio_writeb(drive, IDEREG_FEATURE, cmd->task[0]); + ob_ide_pio_writeb(drive, IDEREG_NSECTOR, cmd->task[2]); + ob_ide_pio_writeb(drive, IDEREG_SECTOR, cmd->task[4]); + ob_ide_pio_writeb(drive, IDEREG_LCYL, cmd->task[5]); + ob_ide_pio_writeb(drive, IDEREG_HCYL, cmd->task[6]); + + if (drive->unit) + cmd->device_head |= IDEHEAD_DEV1; + + ob_ide_pio_writeb(drive, IDEREG_CURRENT, cmd->device_head); + + ob_ide_pio_writeb(drive, IDEREG_COMMAND, cmd->command); + ob_ide_400ns_delay(drive); +} + +static void +ob_ide_write_registers(struct ide_drive *drive, struct ata_command *cmd) +{ + /* + * we are _always_ polled + */ + ob_ide_pio_writeb(drive, IDEREG_CONTROL, cmd->control | IDECON_NIEN); + + ob_ide_pio_writeb(drive, IDEREG_FEATURE, cmd->feature); + ob_ide_pio_writeb(drive, IDEREG_NSECTOR, cmd->nsector); + ob_ide_pio_writeb(drive, IDEREG_SECTOR, cmd->sector); + ob_ide_pio_writeb(drive, IDEREG_LCYL, cmd->lcyl); + ob_ide_pio_writeb(drive, IDEREG_HCYL, cmd->hcyl); + + if (drive->unit) + cmd->device_head |= IDEHEAD_DEV1; + + ob_ide_pio_writeb(drive, IDEREG_CURRENT, cmd->device_head); + + ob_ide_pio_writeb(drive, IDEREG_COMMAND, cmd->command); + ob_ide_400ns_delay(drive); +} + +/* + * execute command with "pio non data" protocol + */ +#if 0 +static int +ob_ide_pio_non_data(struct ide_drive *drive, struct ata_command *cmd) +{ + if (ob_ide_select_drive(drive)) + return 1; + + ob_ide_write_registers(drive, cmd); + + if (ob_ide_wait_stat(drive, 0, BUSY_STAT, NULL)) + return 1; + + return 0; +} +#endif + +/* + * execute given command with a pio data-in phase. + */ +static int +ob_ide_pio_data_in(struct ide_drive *drive, struct ata_command *cmd) +{ + unsigned char stat; + unsigned int bytes, timeout; + + if (ob_ide_select_drive(drive)) + return 1; + + /* + * ATA must set ready and seek stat, ATAPI need only clear busy + */ + timeout = 0; + do { + stat = ob_ide_pio_readb(drive, IDEREG_STATUS); + + if (drive->type == ide_type_ata) { + /* + * this is BIOS code, don't be too pedantic + */ +#ifdef ATA_PEDANTIC + if ((stat & (BUSY_STAT | READY_STAT | SEEK_STAT)) == + (READY_STAT | SEEK_STAT)) + break; +#else + if ((stat & (BUSY_STAT | READY_STAT)) == READY_STAT) + break; +#endif + } else { + if (!(stat & BUSY_STAT)) + break; + } + ob_ide_400ns_delay(drive); + } while (timeout++ < 1000); + + if (timeout >= 1000) { + ob_ide_error(drive, stat, "drive timed out"); + cmd->stat = stat; + return 1; + } + + ob_ide_write_registers(drive, cmd); + + /* + * now read the data + */ + bytes = cmd->buflen; + do { + unsigned count = cmd->buflen; + + if (count > drive->bs) + count = drive->bs; + + /* delay 100ms for ATAPI? */ + + /* + * wait for BUSY clear + */ + if (ob_ide_wait_stat(drive, 0, BUSY_STAT | ERR_STAT, &stat)) { + ob_ide_error(drive, stat, "timed out waiting for BUSY clear"); + cmd->stat = stat; + break; + } + + /* + * transfer the data + */ + if ((stat & (BUSY_STAT | DRQ_STAT)) == DRQ_STAT) { + ob_ide_pio_insw(drive, IDEREG_DATA, cmd->buffer, count); + cmd->bytes -= count; + cmd->buffer += count; + bytes -= count; + + ob_ide_400ns_delay(drive); + } + + if (stat & (BUSY_STAT | WRERR_STAT | ERR_STAT)) { + cmd->stat = stat; + break; + } + + if (!(stat & DRQ_STAT)) { + cmd->stat = stat; + break; + } + } while (bytes); + + if (bytes) + IDE_DPRINTF("bytes=%d, stat=%x\n", bytes, stat); + + return bytes ? 1 : 0; +} + +/* + * execute ata command with pio packet protocol + */ +static int +ob_ide_pio_packet(struct ide_drive *drive, struct atapi_command *cmd) +{ + unsigned char stat, reason, lcyl, hcyl; + struct ata_command *acmd = &drive->channel->ata_cmd; + unsigned char *buffer; + unsigned int bytes; + + if (ob_ide_select_drive(drive)) + return 1; + + if (cmd->buflen && cmd->data_direction == atapi_ddir_none) + IDE_DPRINTF("non-zero buflen but no data direction\n"); + + memset(acmd, 0, sizeof(*acmd)); + acmd->lcyl = cmd->buflen & 0xff; + acmd->hcyl = (cmd->buflen >> 8) & 0xff; + acmd->command = WIN_PACKET; + ob_ide_write_registers(drive, acmd); + + /* + * BUSY must be set, _or_ DRQ | ERR + */ + stat = ob_ide_pio_readb(drive, IDEREG_ASTATUS); + if ((stat & BUSY_STAT) == 0) { + if (!(stat & (DRQ_STAT | ERR_STAT))) { + ob_ide_error(drive, stat, "bad stat in atapi cmd"); + cmd->stat = stat; + return 1; + } + } + + if (ob_ide_wait_stat(drive, 0, BUSY_STAT | ERR_STAT, &stat)) { + ob_ide_error(drive, stat, "timeout, ATAPI BUSY clear"); + cmd->stat = stat; + return 1; + } + + if ((stat & (BUSY_STAT | DRQ_STAT | ERR_STAT)) != DRQ_STAT) { + /* + * if command isn't request sense, then we have a problem. if + * we are doing a sense, ERR_STAT == CHECK_CONDITION + */ + if (cmd->cdb[0] != ATAPI_REQ_SENSE) { + IDE_DPRINTF("odd, drive didn't want to transfer %x\n", + stat); + return 1; + } + } + + /* + * transfer cdb + */ + ob_ide_pio_outsw(drive, IDEREG_DATA, cmd->cdb,sizeof(cmd->cdb)); + ob_ide_400ns_delay(drive); + + /* + * ok, cdb was sent to drive, now do data transfer (if any) + */ + bytes = cmd->buflen; + buffer = cmd->buffer; + do { + unsigned int bc; + + if (ob_ide_wait_stat(drive, 0, BUSY_STAT | ERR_STAT, &stat)) { + ob_ide_error(drive, stat, "busy not clear after cdb"); + cmd->stat = stat; + break; + } + + /* + * transfer complete! + */ + if ((stat & (BUSY_STAT | DRQ_STAT)) == 0) + break; + + if ((stat & (BUSY_STAT | DRQ_STAT)) != DRQ_STAT) + break; + + reason = ob_ide_pio_readb(drive, IDEREG_NSECTOR); + lcyl = ob_ide_pio_readb(drive, IDEREG_LCYL); + hcyl = ob_ide_pio_readb(drive, IDEREG_HCYL); + + /* + * check if the drive wants to transfer data in the same + * direction as we do... + */ + if ((reason & IREASON_CD) && cmd->data_direction != atapi_ddir_read) { + ob_ide_error(drive, stat, "atapi, bad transfer ddir"); + break; + } + + bc = (hcyl << 8) | lcyl; + if (!bc) + break; + + if (bc > bytes) + bc = bytes; + + if (cmd->data_direction == atapi_ddir_read) + ob_ide_pio_insw(drive, IDEREG_DATA, buffer, bc); + else + ob_ide_pio_outsw(drive, IDEREG_DATA, buffer, bc); + + bytes -= bc; + buffer += bc; + + ob_ide_400ns_delay(drive); + } while (bytes); + + if (cmd->data_direction != atapi_ddir_none) + (void) ob_ide_wait_stat(drive, 0, BUSY_STAT, &stat); + + if (bytes) + IDE_DPRINTF("cdb failed, bytes=%d, stat=%x\n", bytes, stat); + + return (stat & ERR_STAT) || bytes; +} + +/* + * execute a packet command, with retries if appropriate + */ +static int +ob_ide_atapi_packet(struct ide_drive *drive, struct atapi_command *cmd) +{ + int retries = 5, ret; + + if (drive->type != ide_type_atapi) + return 1; + if (cmd->buflen > 0xffff) + return 1; + + /* + * retry loop + */ + do { + ret = ob_ide_pio_packet(drive, cmd); + if (!ret) + break; + + /* + * request sense failed, bummer + */ + if (cmd->cdb[0] == ATAPI_REQ_SENSE) + break; + + if (ob_ide_atapi_request_sense(drive)) + break; + + /* + * we know sense is valid. retry if the drive isn't ready, + * otherwise don't bother. + */ + if (cmd->sense.sense_key != ATAPI_SENSE_NOT_READY) + break; + /* + * ... except 'medium not present' + */ + if (cmd->sense.asc == 0x3a) + break; + + udelay(1000000); + } while (retries--); + + if (ret) + ob_ide_error(drive, 0, "atapi command"); + + return ret; +} + +static int +ob_ide_atapi_request_sense(struct ide_drive *drive) +{ + struct atapi_command *cmd = &drive->channel->atapi_cmd; + unsigned char old_cdb; + + /* + * save old cdb for debug error + */ + old_cdb = cmd->cdb[0]; + + memset(cmd, 0, sizeof(*cmd)); + cmd->cdb[0] = ATAPI_REQ_SENSE; + cmd->cdb[4] = 18; + cmd->buffer = (unsigned char *) &cmd->sense; + cmd->buflen = 18; + cmd->data_direction = atapi_ddir_read; + cmd->old_cdb = old_cdb; + + if (ob_ide_atapi_packet(drive, cmd)) + return 1; + + cmd->sense_valid = 1; + return 0; +} + +/* + * make sure drive is ready and media loaded + */ +static int +ob_ide_atapi_drive_ready(struct ide_drive *drive) +{ + struct atapi_command *cmd = &drive->channel->atapi_cmd; + struct atapi_capacity cap; + + IDE_DPRINTF("ob_ide_atapi_drive_ready\n"); + + /* + * Test Unit Ready is like a ping + */ + memset(cmd, 0, sizeof(*cmd)); + cmd->cdb[0] = ATAPI_TUR; + + if (ob_ide_atapi_packet(drive, cmd)) { + IDE_DPRINTF("%d: TUR failed\n", drive->nr); + return 1; + } + + /* + * don't force load of tray (bit 2 in byte 4 of cdb), it's + * annoying and we don't want to deal with errors from drives + * that cannot do it + */ + memset(cmd, 0, sizeof(*cmd)); + cmd->cdb[0] = ATAPI_START_STOP_UNIT; + cmd->cdb[4] = 0x01; + + if (ob_ide_atapi_packet(drive, cmd)) { + IDE_DPRINTF("%d: START_STOP unit failed\n", drive->nr); + return 1; + } + + /* + * finally, get capacity and block size + */ + memset(cmd, 0, sizeof(*cmd)); + memset(&cap, 0, sizeof(cap)); + + cmd->cdb[0] = ATAPI_READ_CAPACITY; + cmd->buffer = (unsigned char *) ∩ + cmd->buflen = sizeof(cap); + cmd->data_direction = atapi_ddir_read; + + if (ob_ide_atapi_packet(drive, cmd)) { + drive->sectors = 0x1fffff; + drive->bs = 2048; + return 1; + } + + drive->sectors = __be32_to_cpu(cap.lba) + 1; + drive->bs = __be32_to_cpu(cap.block_size); + return 0; +} + +/* + * read from an atapi device, using READ_10 + */ +static int +ob_ide_read_atapi(struct ide_drive *drive, unsigned long long block, + unsigned char *buf, unsigned int sectors) +{ + struct atapi_command *cmd = &drive->channel->atapi_cmd; + + if (ob_ide_atapi_drive_ready(drive)) + return 1; + + memset(cmd, 0, sizeof(*cmd)); + + /* + * READ_10 should work on generally any atapi device + */ + cmd->cdb[0] = ATAPI_READ_10; + cmd->cdb[2] = (block >> 24) & 0xff; + cmd->cdb[3] = (block >> 16) & 0xff; + cmd->cdb[4] = (block >> 8) & 0xff; + cmd->cdb[5] = block & 0xff; + cmd->cdb[7] = (sectors >> 8) & 0xff; + cmd->cdb[8] = sectors & 0xff; + + cmd->buffer = buf; + cmd->buflen = sectors * 2048; + cmd->data_direction = atapi_ddir_read; + + return ob_ide_atapi_packet(drive, cmd); +} + +static int +ob_ide_read_ata_chs(struct ide_drive *drive, unsigned long long block, + unsigned char *buf, unsigned int sectors) +{ + struct ata_command *cmd = &drive->channel->ata_cmd; + unsigned int track = (block / drive->sect); + unsigned int sect = (block % drive->sect) + 1; + unsigned int head = (track % drive->head); + unsigned int cyl = (track / drive->head); + + /* + * fill in chs command to read from disk at given location + */ + cmd->buffer = buf; + cmd->buflen = sectors * 512; + + cmd->nsector = sectors & 0xff; + cmd->sector = sect; + cmd->lcyl = cyl; + cmd->hcyl = cyl >> 8; + cmd->device_head = head; + + cmd->command = WIN_READ; + + return ob_ide_pio_data_in(drive, cmd); +} + +static int +ob_ide_read_ata_lba28(struct ide_drive *drive, unsigned long long block, + unsigned char *buf, unsigned int sectors) +{ + struct ata_command *cmd = &drive->channel->ata_cmd; + + memset(cmd, 0, sizeof(*cmd)); + + /* + * fill in 28-bit lba command to read from disk at given location + */ + cmd->buffer = buf; + cmd->buflen = sectors * 512; + + cmd->nsector = sectors; + cmd->sector = block; + cmd->lcyl = block >>= 8; + cmd->hcyl = block >>= 8; + cmd->device_head = ((block >> 8) & 0x0f); + cmd->device_head |= (1 << 6); + + cmd->command = WIN_READ; + + return ob_ide_pio_data_in(drive, cmd); +} + +static int +ob_ide_read_ata_lba48(struct ide_drive *drive, unsigned long long block, + unsigned char *buf, unsigned int sectors) +{ + struct ata_command *cmd = &drive->channel->ata_cmd; + + memset(cmd, 0, sizeof(*cmd)); + + cmd->buffer = buf; + cmd->buflen = sectors * 512; + + /* + * we are using tasklet addressing here + */ + cmd->task[2] = sectors; + cmd->task[3] = sectors >> 8; + cmd->task[4] = block; + cmd->task[5] = block >> 8; + cmd->task[6] = block >> 16; + cmd->task[7] = block >> 24; + cmd->task[8] = (u64) block >> 32; + cmd->task[9] = (u64) block >> 40; + + cmd->command = WIN_READ_EXT; + + ob_ide_write_tasklet(drive, cmd); + + return ob_ide_pio_data_in(drive, cmd); +} +/* + * read 'sectors' sectors from ata device + */ +static int +ob_ide_read_ata(struct ide_drive *drive, unsigned long long block, + unsigned char *buf, unsigned int sectors) +{ + unsigned long long end_block = block + sectors; + const int need_lba48 = (end_block > (1ULL << 28)) || (sectors > 255); + + if (end_block > drive->sectors) + return 1; + if (need_lba48 && drive->addressing != ide_lba48) + return 1; + + /* + * use lba48 if we have to, otherwise use the faster lba28 + */ + if (need_lba48) + return ob_ide_read_ata_lba48(drive, block, buf, sectors); + else if (drive->addressing != ide_chs) + return ob_ide_read_ata_lba28(drive, block, buf, sectors); + + return ob_ide_read_ata_chs(drive, block, buf, sectors); +} + +static int +ob_ide_read_sectors(struct ide_drive *drive, unsigned long long block, + unsigned char *buf, unsigned int sectors) +{ + if (!sectors) + return 1; + if (block + sectors > drive->sectors) + return 1; + + IDE_DPRINTF("ob_ide_read_sectors: block=%lu sectors=%u\n", + (unsigned long) block, sectors); + + if (drive->type == ide_type_ata) + return ob_ide_read_ata(drive, block, buf, sectors); + else + return ob_ide_read_atapi(drive, block, buf, sectors); +} + +/* + * byte swap the string if necessay, and strip leading/trailing blanks + */ +static void +ob_ide_fixup_string(unsigned char *s, unsigned int len) +{ + unsigned char *p = s, *end = &s[len & ~1]; + + /* + * if big endian arch, byte swap the string + */ +#ifdef CONFIG_BIG_ENDIAN + for (p = end ; p != s;) { + unsigned short *pp = (unsigned short *) (p -= 2); + *pp = __le16_to_cpu(*pp); + } +#endif + + while (s != end && *s == ' ') + ++s; + while (s != end && *s) + if (*s++ != ' ' || (s != end && *s && *s != ' ')) + *p++ = *(s-1); + while (p != end) + *p++ = '\0'; +} + +/* + * it's big endian, we need to swap (if on little endian) the items we use + */ +static int +ob_ide_fixup_id(struct hd_driveid *id) +{ + ob_ide_fixup_string(id->model, 40); + id->config = __le16_to_cpu(id->config); + id->lba_capacity = __le32_to_cpu(id->lba_capacity); + id->cyls = __le16_to_cpu(id->cyls); + id->heads = __le16_to_cpu(id->heads); + id->sectors = __le16_to_cpu(id->sectors); + id->command_set_2 = __le16_to_cpu(id->command_set_2); + id->cfs_enable_2 = __le16_to_cpu(id->cfs_enable_2); + + return 0; +} + +static int +ob_ide_identify_drive(struct ide_drive *drive) +{ + struct ata_command *cmd = &drive->channel->ata_cmd; + struct hd_driveid id; + + memset(cmd, 0, sizeof(*cmd)); + cmd->buffer = (unsigned char *) &id; + cmd->buflen = 512; + + if (drive->type == ide_type_ata) + cmd->command = WIN_IDENTIFY; + else if (drive->type == ide_type_atapi) + cmd->command = WIN_IDENTIFY_PACKET; + else { + IDE_DPRINTF("%s: called with bad device type %d\n", + __FUNCTION__, drive->type); + return 1; + } + + if (ob_ide_pio_data_in(drive, cmd)) + return 1; + + ob_ide_fixup_id(&id); + + if (drive->type == ide_type_atapi) { + drive->media = (id.config >> 8) & 0x1f; + drive->sectors = 0x7fffffff; + drive->bs = 2048; + drive->max_sectors = 31; + } else { + drive->media = ide_media_disk; + drive->sectors = id.lba_capacity; + drive->bs = 512; + drive->max_sectors = 255; + +#ifdef CONFIG_IDE_LBA48 + if ((id.command_set_2 & 0x0400) && (id.cfs_enable_2 & 0x0400)) { + drive->addressing = ide_lba48; + drive->max_sectors = 65535; + } else +#endif + if (id.capability & 2) + drive->addressing = ide_lba28; + else { + drive->addressing = ide_chs; + } + + /* only set these in chs mode? */ + drive->cyl = id.cyls; + drive->head = id.heads; + drive->sect = id.sectors; + } + + strncpy(drive->model, (char*)id.model, sizeof(id.model)); + drive->model[40] = '\0'; + return 0; +} + +/* + * identify type of devices on channel. must have already been probed. + */ +static void +ob_ide_identify_drives(struct ide_channel *chan) +{ + struct ide_drive *drive; + int i; + + for (i = 0; i < 2; i++) { + drive = &chan->drives[i]; + + if (!drive->present) + continue; + + ob_ide_identify_drive(drive); + } +} + +/* + * software reset (ATA-4, section 8.3) + */ +static void +ob_ide_software_reset(struct ide_drive *drive) +{ + struct ide_channel *chan = drive->channel; + + ob_ide_pio_writeb(drive, IDEREG_CONTROL, IDECON_NIEN | IDECON_SRST); + ob_ide_400ns_delay(drive); + ob_ide_pio_writeb(drive, IDEREG_CONTROL, IDECON_NIEN); + ob_ide_400ns_delay(drive); + + /* + * if master is present, wait for BUSY clear + */ + if (chan->drives[0].present) + ob_ide_wait_stat(drive, 0, BUSY_STAT, NULL); + + /* + * if slave is present, wait until it allows register access + */ + if (chan->drives[1].present) { + unsigned char sectorn, sectorc; + int timeout = 1000; + + do { + /* + * select it + */ + ob_ide_pio_writeb(drive, IDEREG_CURRENT, IDEHEAD_DEV1); + ob_ide_400ns_delay(drive); + + sectorn = ob_ide_pio_readb(drive, IDEREG_SECTOR); + sectorc = ob_ide_pio_readb(drive, IDEREG_NSECTOR); + + if (sectorc == 0x01 && sectorn == 0x01) + break; + + } while (--timeout); + } + + /* + * reset done, reselect original device + */ + drive->channel->selected = -1; + ob_ide_select_drive(drive); +} + +/* + * this serves as both a device check, and also to verify that the drives + * we initially "found" are really there + */ +static void +ob_ide_device_type_check(struct ide_drive *drive) +{ + unsigned char sc, sn, cl, ch, st; + + if (ob_ide_select_drive(drive)) + return; + + sc = ob_ide_pio_readb(drive, IDEREG_NSECTOR); + sn = ob_ide_pio_readb(drive, IDEREG_SECTOR); + + if (sc == 0x01 && sn == 0x01) { + /* + * read device signature + */ + cl = ob_ide_pio_readb(drive, IDEREG_LCYL); + ch = ob_ide_pio_readb(drive, IDEREG_HCYL); + st = ob_ide_pio_readb(drive, IDEREG_STATUS); + if (cl == 0x14 && ch == 0xeb) + drive->type = ide_type_atapi; + else if (cl == 0x00 && ch == 0x00 && st != 0x00) + drive->type = ide_type_ata; + else + drive->present = 0; + } else + drive->present = 0; +} + +/* + * pure magic + */ +static void +ob_ide_device_check(struct ide_drive *drive) +{ + unsigned char sc, sn; + + /* + * non-existing io port should return 0xff, don't probe this + * channel at all then + */ + if (ob_ide_pio_readb(drive, IDEREG_STATUS) == 0xff) { + drive->channel->present = 0; + return; + } + + if (ob_ide_select_drive(drive)) + return; + + ob_ide_pio_writeb(drive, IDEREG_NSECTOR, 0x55); + ob_ide_pio_writeb(drive, IDEREG_SECTOR, 0xaa); + ob_ide_pio_writeb(drive, IDEREG_NSECTOR, 0xaa); + ob_ide_pio_writeb(drive, IDEREG_SECTOR, 0x55); + ob_ide_pio_writeb(drive, IDEREG_NSECTOR, 0x55); + ob_ide_pio_writeb(drive, IDEREG_SECTOR, 0xaa); + + sc = ob_ide_pio_readb(drive, IDEREG_NSECTOR); + sn = ob_ide_pio_readb(drive, IDEREG_SECTOR); + + /* + * we _think_ the device is there, we will make sure later + */ + if (sc == 0x55 && sn == 0xaa) { + drive->present = 1; + drive->type = ide_type_unknown; + } +} + +/* + * probe the legacy ide ports and find attached devices. + */ +static void +ob_ide_probe(struct ide_channel *chan) +{ + struct ide_drive *drive; + int i; + + for (i = 0; i < 2; i++) { + drive = &chan->drives[i]; + + ob_ide_device_check(drive); + + /* + * no point in continuing + */ + if (!chan->present) + break; + + if (!drive->present) + continue; + + /* + * select and reset device + */ + if (ob_ide_select_drive(drive)) + continue; + + ob_ide_software_reset(drive); + + ob_ide_device_type_check(drive); + } +} + +/* + * The following functions are interfacing with OpenBIOS. They + * are device node methods. Thus they have to do proper stack handling. + * + */ + +/* + * 255 sectors for ata lba28, 65535 for lba48, and 31 sectors for atapi + */ +static void +ob_ide_max_transfer(int *idx) +{ + struct ide_drive *drive = *(struct ide_drive **)idx; + + IDE_DPRINTF("max_transfer %x\n", drive->max_sectors * drive->bs); + + PUSH(drive->max_sectors * drive->bs); +} + +static void +ob_ide_read_blocks(int *idx) +{ + cell n = POP(), cnt=n; + ucell blk = POP(); + unsigned char *dest = (unsigned char *)cell2pointer(POP()); + struct ide_drive *drive = *(struct ide_drive **)idx; + + IDE_DPRINTF("ob_ide_read_blocks %lx block=%ld n=%ld\n", + (unsigned long)dest, (unsigned long)blk, (long)n); + + while (n) { + int len = n; + if (len > drive->max_sectors) + len = drive->max_sectors; + + if (ob_ide_read_sectors(drive, blk, dest, len)) { + IDE_DPRINTF("ob_ide_read_blocks: error\n"); + RET(0); + } + + dest += len * drive->bs; + n -= len; + blk += len; + } + + PUSH(cnt); +} + +static void +ob_ide_block_size(int *idx) +{ + struct ide_drive *drive = *(struct ide_drive **)idx; + + IDE_DPRINTF("ob_ide_block_size: block size %x\n", drive->bs); + + PUSH(drive->bs); +} + +static void +ob_ide_initialize(int *idx) +{ + int props[3]; + phandle_t ph=get_cur_dev(); + + push_str("block"); + fword("device-type"); + + // Set dummy reg properties + + set_int_property(ph, "#address-cells", 1); + set_int_property(ph, "#size-cells", 0); + + props[0] = __cpu_to_be32(0); props[1] = __cpu_to_be32(0); props[2] = __cpu_to_be32(0); + set_property(ph, "reg", (char *)&props, 3*sizeof(int)); + + fword("is-deblocker"); +} + +static void +ob_ide_open(int *idx) +{ + int ret=1, len; + phandle_t ph; + struct ide_drive *drive; + struct ide_channel *chan; + char *idename; + int unit; + + fword("my-unit"); + unit = POP(); + + fword("my-parent"); + fword("ihandle>phandle"); + ph=(phandle_t)POP(); + idename=get_property(ph, "name", &len); + + chan = ide_seek_channel(idename); + drive = &chan->drives[unit]; + *(struct ide_drive **)idx = drive; + + IDE_DPRINTF("opening channel %d unit %d\n", idx[1], idx[0]); + dump_drive(drive); + + if (drive->type != ide_type_ata) + ret= !ob_ide_atapi_drive_ready(drive); + + selfword("open-deblocker"); + + /* interpose disk-label */ + ph = find_dev("/packages/disk-label"); + fword("my-args"); + PUSH_ph( ph ); + fword("interpose"); + + RET ( -ret ); +} + +static void +ob_ide_close(struct ide_drive *drive) +{ + selfword("close-deblocker"); +} + +NODE_METHODS(ob_ide) = { + { NULL, ob_ide_initialize }, + { "open", ob_ide_open }, + { "close", ob_ide_close }, + { "read-blocks", ob_ide_read_blocks }, + { "block-size", ob_ide_block_size }, + { "max-transfer", ob_ide_max_transfer }, +}; + +static void +ob_ide_ctrl_initialize(int *idx) +{ + phandle_t ph=get_cur_dev(); + + /* set device type */ + push_str(DEV_TYPE); + fword("device-type"); + + set_int_property(ph, "#address-cells", 1); + set_int_property(ph, "#size-cells", 0); +} + +static void +ob_ide_ctrl_decodeunit(int *idx) +{ + fword("parse-hex"); +} + +NODE_METHODS(ob_ide_ctrl) = { + { NULL, ob_ide_ctrl_initialize }, + { "decode-unit", ob_ide_ctrl_decodeunit }, +}; + +static void set_cd_alias(const char *path) +{ + phandle_t aliases; + + aliases = find_dev("/aliases"); + + if (get_property(aliases, "cd", NULL)) + return; + + set_property(aliases, "cd", path, strlen(path) + 1); + set_property(aliases, "cdrom", path, strlen(path) + 1); +} + +static void set_hd_alias(const char *path) +{ + phandle_t aliases; + + aliases = find_dev("/aliases"); + + if (get_property(aliases, "hd", NULL)) + return; + + set_property(aliases, "hd", path, strlen(path) + 1); + set_property(aliases, "disk", path, strlen(path) + 1); +} + +static void set_ide_alias(const char *path) +{ + phandle_t aliases; + static int ide_counter = 0; + char idestr[8]; + + aliases = find_dev("/aliases"); + + snprintf(idestr, sizeof(idestr), "ide%d", ide_counter++); + set_property(aliases, idestr, path, strlen(path) + 1); +} + +int ob_ide_init(const char *path, uint32_t io_port0, uint32_t ctl_port0, + uint32_t io_port1, uint32_t ctl_port1) +{ + int i, j; + char nodebuff[128]; + phandle_t dnode; + struct ide_channel *chan; + int io_ports[IDE_MAX_CHANNELS]; + int ctl_ports[IDE_MAX_CHANNELS]; + u32 props[6]; + + io_ports[0] = io_port0; + ctl_ports[0] = ctl_port0; + io_ports[1] = io_port1; + ctl_ports[1] = ctl_port1; + + for (i = 0; i < IDE_NUM_CHANNELS; i++, current_channel++) { + + chan = malloc(sizeof(struct ide_channel)); + + snprintf(chan->name, sizeof(chan->name), + DEV_NAME, current_channel); + + chan->mmio = 0; + + for (j = 0; j < 8; j++) + chan->io_regs[j] = io_ports[i] + j; + + chan->io_regs[8] = ctl_ports[i]; + chan->io_regs[9] = ctl_ports[i] + 1; + + chan->obide_inb = ob_ide_inb; + chan->obide_insw = ob_ide_insw; + chan->obide_outb = ob_ide_outb; + chan->obide_outsw = ob_ide_outsw; + + chan->selected = -1; + + /* + * assume it's there, if not io port dead check will clear + */ + chan->present = 1; + + for (j = 0; j < 2; j++) { + chan->drives[j].present = 0; + chan->drives[j].unit = j; + chan->drives[j].channel = chan; + /* init with a decent value */ + chan->drives[j].bs = 512; + + chan->drives[j].nr = i * 2 + j; + } + + ide_add_channel(chan); + + ob_ide_probe(chan); + + if (!chan->present) + continue; + + ob_ide_identify_drives(chan); + + snprintf(nodebuff, sizeof(nodebuff), "%s/" DEV_NAME, path, + current_channel); + REGISTER_NAMED_NODE(ob_ide_ctrl, nodebuff); + + dnode = find_dev(nodebuff); + +#if !defined(CONFIG_PPC) && !defined(CONFIG_SPARC64) + props[0]=14; props[1]=0; + set_property(dnode, "interrupts", + (char *)&props, 2*sizeof(props[0])); +#endif + + props[0] = __cpu_to_be32(chan->io_regs[0]); + props[1] = __cpu_to_be32(1); props[2] = __cpu_to_be32(8); + props[3] = __cpu_to_be32(chan->io_regs[8]); + props[4] = __cpu_to_be32(1); props[5] = __cpu_to_be32(2); + set_property(dnode, "reg", (char *)&props, 6*sizeof(props[0])); + + IDE_DPRINTF(DEV_NAME": [io ports 0x%x-0x%x,0x%x]\n", + current_channel, chan->io_regs[0], + chan->io_regs[0] + 7, chan->io_regs[8]); + + for (j = 0; j < 2; j++) { + struct ide_drive *drive = &chan->drives[j]; + const char *media = "UNKNOWN"; + + if (!drive->present) + continue; + + IDE_DPRINTF(" drive%d [ATA%s ", j, + drive->type == ide_type_atapi ? "PI" : ""); + switch (drive->media) { + case ide_media_floppy: + media = "floppy"; + break; + case ide_media_cdrom: + media = "cdrom"; + break; + case ide_media_optical: + media = "mo"; + break; + case ide_media_disk: + media = "disk"; + break; + } + IDE_DPRINTF("%s]: %s\n", media, drive->model); + snprintf(nodebuff, sizeof(nodebuff), + "%s/" DEV_NAME "/%s", path, current_channel, + media); + REGISTER_NAMED_NODE(ob_ide, nodebuff); + dnode=find_dev(nodebuff); + set_int_property(dnode, "reg", j); + + /* create aliases */ + + set_ide_alias(nodebuff); + if (drive->media == ide_media_cdrom) + set_cd_alias(nodebuff); + if (drive->media == ide_media_disk) + set_hd_alias(nodebuff); + } + } + + return 0; +} + +#if defined(CONFIG_DRIVER_MACIO) +static unsigned char +macio_ide_inb(struct ide_channel *chan, unsigned int port) +{ + return in_8((unsigned char*)(chan->mmio + (port << 4))); +} + +static void +macio_ide_outb(struct ide_channel *chan, unsigned char data, unsigned int port) +{ + out_8((unsigned char*)(chan->mmio + (port << 4)), data); +} + +static void +macio_ide_insw(struct ide_channel *chan, + unsigned int port, unsigned char *addr, unsigned int count) +{ + _insw((uint16_t*)(chan->mmio + (port << 4)), addr, count); +} + +static void +macio_ide_outsw(struct ide_channel *chan, + unsigned int port, unsigned char *addr, unsigned int count) +{ + _outsw((uint16_t*)(chan->mmio + (port << 4)), addr, count); +} + +#define MACIO_IDE_OFFSET 0x00020000 +#define MACIO_IDE_SIZE 0x00001000 + +int macio_ide_init(const char *path, uint32_t addr, int nb_channels) +{ + int i, j; + char nodebuff[128]; + phandle_t dnode; + u32 props[8]; + struct ide_channel *chan; + + /* IDE ports on Macs are numbered from 3. + * Also see comments in macio.c:openpic_init() */ + current_channel = 3; + + for (i = 0; i < nb_channels; i++, current_channel++) { + + chan = malloc(sizeof(struct ide_channel)); + + snprintf(chan->name, sizeof(chan->name), + DEV_NAME, current_channel); + + chan->mmio = addr + MACIO_IDE_OFFSET + i * MACIO_IDE_SIZE; + + chan->obide_inb = macio_ide_inb; + chan->obide_insw = macio_ide_insw; + chan->obide_outb = macio_ide_outb; + chan->obide_outsw = macio_ide_outsw; + + chan->selected = -1; + + /* + * assume it's there, if not io port dead check will clear + */ + chan->present = 1; + + for (j = 0; j < 2; j++) { + chan->drives[j].present = 0; + chan->drives[j].unit = j; + chan->drives[j].channel = chan; + /* init with a decent value */ + chan->drives[j].bs = 512; + + chan->drives[j].nr = i * 2 + j; + } + + ob_ide_probe(chan); + + if (!chan->present) { + free(chan); + continue; + } + + ide_add_channel(chan); + + ob_ide_identify_drives(chan); + + snprintf(nodebuff, sizeof(nodebuff), "%s/" DEV_NAME, path, + current_channel); + REGISTER_NAMED_NODE(ob_ide_ctrl, nodebuff); + + dnode = find_dev(nodebuff); + + set_property(dnode, "compatible", (is_oldworld() ? + "heathrow-ata" : "keylargo-ata"), 13); + + props[0] = 0x00000526; + props[1] = 0x00000085; + props[2] = 0x00000025; + props[3] = 0x00000025; + props[4] = 0x00000025; + props[5] = 0x00000000; + props[6] = 0x00000000; + props[7] = 0x00000000; + OLDWORLD(set_property(dnode, "AAPL,pio-timing", + (char *)&props, 8*sizeof(props[0]))); + + /* The first interrupt entry is the ide interrupt, the second + the dbdma interrupt */ + switch (i) { + case 0: + props[0] = 0x0000000d; + props[2] = 0x00000002; + break; + case 1: + props[0] = 0x0000000e; + props[2] = 0x00000003; + break; + case 2: + props[0] = 0x0000000f; + props[2] = 0x00000004; + break; + default: + props[0] = 0x00000000; + props[2] = 0x00000000; + break; + } + props[1] = 0x00000000; /* XXX level triggered on real hw */ + props[3] = 0x00000000; + NEWWORLD(set_property(dnode, "interrupts", + (char *)&props, 4*sizeof(props[0]))); + NEWWORLD(set_int_property(dnode, "#interrupt-cells", 2)); + + props[1] = props[2]; + OLDWORLD(set_property(dnode, "AAPL,interrupts", + (char *)&props, 2*sizeof(props[0]))); + + props[0] = MACIO_IDE_OFFSET + i * MACIO_IDE_SIZE; + props[1] = MACIO_IDE_SIZE; + props[2] = 0x00008b00 + i * 0x0200; + props[3] = 0x0200; + set_property(dnode, "reg", (char *)&props, 4*sizeof(props[0])); + + props[0] = addr + MACIO_IDE_OFFSET + i * MACIO_IDE_SIZE; + props[1] = addr + 0x00008b00 + i * 0x0200; + OLDWORLD(set_property(dnode, "AAPL,address", + (char *)&props, 2*sizeof(props[0]))); + + props[0] = 0; + OLDWORLD(set_property(dnode, "AAPL,bus-id", (char*)props, + 1 * sizeof(props[0]))); + IDE_DPRINTF(DEV_NAME": [io ports 0x%lx]\n", + current_channel, chan->mmio); + + for (j = 0; j < 2; j++) { + struct ide_drive *drive = &chan->drives[j]; + const char *media = "UNKNOWN"; + + if (!drive->present) + continue; + + IDE_DPRINTF(" drive%d [ATA%s ", j, + drive->type == ide_type_atapi ? "PI" : ""); + switch (drive->media) { + case ide_media_floppy: + media = "floppy"; + break; + case ide_media_cdrom: + media = "cdrom"; + break; + case ide_media_optical: + media = "mo"; + break; + case ide_media_disk: + media = "disk"; + break; + } + IDE_DPRINTF("%s]: %s\n", media, drive->model); + snprintf(nodebuff, sizeof(nodebuff), + "%s/" DEV_NAME "/%s", path, current_channel, + media); + REGISTER_NAMED_NODE(ob_ide, nodebuff); + dnode = find_dev(nodebuff); + set_int_property(dnode, "reg", j); + + /* create aliases */ + + set_ide_alias(nodebuff); + if (drive->media == ide_media_cdrom) + set_cd_alias(nodebuff); + if (drive->media == ide_media_disk) + set_hd_alias(nodebuff); + } + } + + return 0; +} +#endif /* CONFIG_DRIVER_MACIO */ |