historical/m0-applesillicon.git/xnu-qemu-arm64-5.1.0/roms/openbios/drivers/ide.c

1740 lines
38 KiB
C
Raw Normal View History

2024-01-16 11:20:27 -06:00
/*
* OpenBIOS polled ide driver
*
* Copyright (C) 2004 Jens Axboe <axboe@suse.de>
* Copyright (C) 2005 Stefan Reinauer
*
* 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, 0, sizeof(struct ide_drive*) );
DECLARE_UNNAMED_NODE( ob_ide_ctrl, 0, 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"
#else
#define DEV_NAME CONFIG_IDE_DEV_NAME
#endif
static int current_channel = FIRST_UNIT;
/*
* 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 *) &cap;
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(drive->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_open(int *idx)
{
int ret=1;
phandle_t ph;
struct ide_drive *drive;
PUSH(find_ih_method("drive", my_self()));
fword("execute");
drive = cell2pointer(POP());
*(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");
}
static void
ob_ide_dma_alloc(int *idx)
{
call_parent_method("dma-alloc");
}
static void
ob_ide_dma_free(int *idx)
{
call_parent_method("dma-free");
}
static void
ob_ide_dma_map_in(int *idx)
{
call_parent_method("dma-map-in");
}
static void
ob_ide_dma_map_out(int *idx)
{
call_parent_method("dma-map-out");
}
static void
ob_ide_dma_sync(int *idx)
{
call_parent_method("dma-sync");
}
NODE_METHODS(ob_ide) = {
{ "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 },
{ "dma-alloc", ob_ide_dma_alloc },
{ "dma-free", ob_ide_dma_free },
{ "dma-map-in", ob_ide_dma_map_in },
{ "dma-map-out", ob_ide_dma_map_out },
{ "dma-sync", ob_ide_dma_sync },
};
static void
ob_ide_ctrl_decodeunit(int *idx)
{
fword("parse-hex");
}
static void
ob_ide_ctrl_open(int *idx)
{
RET(-1);
}
static void
ob_ide_ctrl_close(int *idx)
{
}
NODE_METHODS(ob_ide_ctrl) = {
{ "open", ob_ide_ctrl_open },
{ "close", ob_ide_ctrl_close },
{ "decode-unit", ob_ide_ctrl_decodeunit },
{ "dma-alloc", ob_ide_dma_alloc },
{ "dma-free", ob_ide_dma_free },
{ "dma-map-in", ob_ide_dma_map_in },
{ "dma-map-out", ob_ide_dma_map_out },
{ "dma-sync", ob_ide_dma_sync },
};
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));
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;
}
ob_ide_probe(chan);
if (!chan->present)
continue;
ob_ide_identify_drives(chan);
fword("new-device");
dnode = get_cur_dev();
PUSH(pointer2cell(chan));
feval("value chan");
BIND_NODE_METHODS(get_cur_dev(), ob_ide_ctrl);
#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(current_channel);
props[1] = __cpu_to_be32(0);
props[2] = 0;
set_property(dnode, "reg", (char *)&props, 3*sizeof(props[0]));
set_int_property(dnode, "#address-cells", 1);
set_int_property(dnode, "#size-cells", 0);
push_str(DEV_NAME);
fword("device-name");
push_str(DEV_TYPE);
fword("device-type");
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);
fword("new-device");
dnode = get_cur_dev();
set_int_property(dnode, "reg", j);
push_str(media);
fword("device-name");
push_str("block");
fword("device-type");
PUSH(pointer2cell(drive));
feval("value drive");
BIND_NODE_METHODS(dnode, ob_ide);
fword("is-deblocker");
fword("finish-device");
/* create aliases */
snprintf(nodebuff, sizeof(nodebuff), "%s",
get_path_from_ph(dnode));
set_ide_alias(nodebuff);
if (drive->media == ide_media_cdrom)
set_cd_alias(nodebuff);
if (drive->media == ide_media_disk)
set_hd_alias(nodebuff);
}
fword("finish-device");
}
return 0;
}
void ob_ide_quiesce(void)
{
phandle_t ph = 0, xt;
struct ide_drive *drive;
while ((ph = dt_iterate_type(ph, "block"))) {
xt = find_package_method("drive", ph);
if (xt) {
PUSH(xt);
fword("execute");
drive = cell2pointer(POP());
ob_ide_select_drive(drive);
ob_ide_software_reset(drive);
ob_ide_device_type_check(drive);
}
}
}
#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 pci.c:ob_pci_host_set_interrupt_map() */
current_channel = 3;
for (i = 0; i < nb_channels; i++) {
chan = malloc(sizeof(struct ide_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;
}
ob_ide_identify_drives(chan);
fword("new-device");
dnode = get_cur_dev();
PUSH(pointer2cell(chan));
feval("value chan");
snprintf(nodebuff, sizeof(nodebuff), DEV_NAME "-%d", current_channel);
push_str(nodebuff);
fword("device-name");
push_str(DEV_TYPE);
fword("device-type");
set_int_property(dnode, "#address-cells", 1);
set_int_property(dnode, "#size-cells", 0);
set_property(dnode, "compatible", (is_oldworld() ?
"heathrow-ata" : "keylargo-ata"), 13);
set_property(dnode, "model", ((current_channel == 3) ?
"ata-3" : "ata-4"), strlen("ata-*") + 1);
set_property(dnode, "AAPL,connector", "ata",
strlen("ata") + 1);
props[0] = 0x00000526;
props[1] = 0x00000085;
props[2] = 0x00000025;
props[3] = 0x00000025;
props[4] = 0x00000025;
props[5] = 0x00000000;
props[6] = 0x00000000;
props[7] = 0x00000000;
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] = 0x00000001;
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] = i;
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);
BIND_NODE_METHODS(dnode, ob_ide_ctrl);
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);
fword("new-device");
dnode = get_cur_dev();
set_int_property(dnode, "reg", j);
push_str(media);
fword("device-name");
push_str("block");
fword("device-type");
PUSH(pointer2cell(drive));
feval("value drive");
BIND_NODE_METHODS(dnode, ob_ide);
fword("is-deblocker");
fword("finish-device");
/* create aliases */
snprintf(nodebuff, sizeof(nodebuff), "%s",
get_path_from_ph(dnode));
set_ide_alias(nodebuff);
if (drive->media == ide_media_cdrom)
set_cd_alias(nodebuff);
if (drive->media == ide_media_disk)
set_hd_alias(nodebuff);
}
fword("finish-device");
}
return 0;
}
#endif /* CONFIG_DRIVER_MACIO */