#ifndef _HW_IDE_C_
#define _HW_IDE_C_
#include "device_table.h"
typedef enum _io_direction {
is_read,
is_write,
} io_direction;
enum {
nr_ide_controllers = 2,
nr_ide_drives_per_controller = 2,
nr_fifo_entries = 8192,
};
enum {
ide_data_reg,
ide_error_reg,
ide_sector_count_reg,
ide_sector_number_reg,
ide_cylinder_reg0,
ide_cylinder_reg1,
ide_drive_head_reg,
ide_status_reg,
ide_feature_reg,
ide_command_reg,
ide_alternate_status_reg,
ide_control_reg,
ide_dma_command_reg,
ide_dma_unused_1_reg,
ide_dma_status_reg,
ide_dma_unused_3_reg,
ide_dma_prd_table_address_reg0,
ide_dma_prd_table_address_reg1,
ide_dma_prd_table_address_reg2,
ide_dma_prd_table_address_reg3,
nr_ide_registers,
};
typedef enum _ide_states {
idle_state,
busy_loaded_state,
busy_drained_state,
busy_dma_state,
busy_command_state,
loading_state,
draining_state,
} ide_states;
static const char *
ide_state_name(ide_states state)
{
switch (state) {
case idle_state: return "idle";
case busy_loaded_state: return "busy_loaded_state";
case busy_drained_state: return "busy_drained_state";
case busy_dma_state: return "busy_dma_state";
case busy_command_state: return "busy_command_state";
case loading_state: return "loading_state";
case draining_state: return "draining_state";
default: return "illegal-state";
}
}
typedef struct _ide_geometry {
int head;
int sector;
int byte;
} ide_geometry;
typedef struct _ide_drive {
int nr;
device *device;
ide_geometry geometry;
ide_geometry default_geometry;
} ide_drive;
typedef struct _ide_controller {
int nr;
ide_states state;
unsigned8 reg[nr_ide_registers];
unsigned8 fifo[nr_fifo_entries];
int fifo_pos;
int fifo_size;
ide_drive *current_drive;
int current_byte;
int current_transfer;
ide_drive drive[nr_ide_drives_per_controller];
device *me;
event_entry_tag event_tag;
int is_interrupting;
signed64 ready_delay;
} ide_controller;
static void
set_interrupt(device *me,
ide_controller *controller)
{
if ((controller->reg[ide_control_reg] & 0x2) == 0) {
DTRACE(ide, ("controller %d - interrupt set\n", controller->nr));
device_interrupt_event(me, controller->nr, 1, NULL, 0);
controller->is_interrupting = 1;
}
}
static void
clear_interrupt(device *me,
ide_controller *controller)
{
if (controller->is_interrupting) {
DTRACE(ide, ("controller %d - interrupt clear\n", controller->nr));
device_interrupt_event(me, controller->nr, 0, NULL, 0);
controller->is_interrupting = 0;
}
}
static void
do_event(void *data)
{
ide_controller *controller = data;
device *me = controller->me;
controller->event_tag = 0;
switch (controller->state) {
case busy_loaded_state:
case busy_drained_state:
if (controller->current_transfer > 0) {
controller->state = (controller->state == busy_loaded_state
? loading_state : draining_state);
}
else {
controller->state = idle_state;
}
set_interrupt(me, controller);
break;
default:
device_error(me, "controller %d - unexpected event", controller->nr);
break;
}
}
static void
schedule_ready_event(device *me,
ide_controller *controller)
{
if (controller->event_tag != 0)
device_error(me, "controller %d - attempting to schedule multiple events",
controller->nr);
controller->event_tag =
device_event_queue_schedule(me, controller->ready_delay,
do_event, controller);
}
static void
do_fifo_read(device *me,
ide_controller *controller,
void *dest,
int nr_bytes)
{
if (controller->state != draining_state)
device_error(me, "controller %d - reading fifo when not ready (%s)",
controller->nr,
ide_state_name(controller->state));
if (controller->fifo_pos + nr_bytes > controller->fifo_size)
device_error(me, "controller %d - fifo underflow", controller->nr);
if (nr_bytes > 0) {
memcpy(dest, &controller->fifo[controller->fifo_pos], nr_bytes);
controller->fifo_pos += nr_bytes;
}
if (controller->fifo_pos == controller->fifo_size) {
controller->current_transfer -= 1;
if (controller->current_transfer > 0
&& controller->current_drive != NULL) {
DTRACE(ide, ("controller %d:%d - reading %d byte block at 0x%x\n",
controller->nr,
controller->current_drive->nr,
controller->fifo_size,
controller->current_byte));
if (device_io_read_buffer(controller->current_drive->device,
controller->fifo,
0, controller->current_byte,
controller->fifo_size,
NULL, 0)
!= controller->fifo_size)
device_error(me, "controller %d - disk %s io read error",
controller->nr,
device_path(controller->current_drive->device));
}
controller->state = busy_drained_state;
controller->fifo_pos = 0;
controller->current_byte += controller->fifo_size;
schedule_ready_event(me, controller);
}
}
static void
do_fifo_write(device *me,
ide_controller *controller,
const void *source,
int nr_bytes)
{
if (controller->state != loading_state)
device_error(me, "controller %d - writing fifo when not ready (%s)",
controller->nr,
ide_state_name(controller->state));
if (controller->fifo_pos + nr_bytes > controller->fifo_size)
device_error(me, "controller %d - fifo overflow", controller->nr);
if (nr_bytes > 0) {
memcpy(&controller->fifo[controller->fifo_pos], source, nr_bytes);
controller->fifo_pos += nr_bytes;
}
if (controller->fifo_pos == controller->fifo_size) {
if (controller->current_transfer > 0
&& controller->current_drive != NULL) {
DTRACE(ide, ("controller %d:%d - writing %d byte block at 0x%x\n",
controller->nr,
controller->current_drive->nr,
controller->fifo_size,
controller->current_byte));
if (device_io_write_buffer(controller->current_drive->device,
controller->fifo,
0, controller->current_byte,
controller->fifo_size,
NULL, 0)
!= controller->fifo_size)
device_error(me, "controller %d - disk %s io write error",
controller->nr,
device_path(controller->current_drive->device));
}
controller->current_transfer -= 1;
controller->fifo_pos = 0;
controller->current_byte += controller->fifo_size;
controller->state = busy_loaded_state;
schedule_ready_event(me, controller);
}
}
static void
setup_fifo(device *me,
ide_controller *controller,
int is_simple,
int is_with_disk,
io_direction direction)
{
if (is_with_disk) {
int drive_nr = (controller->reg[ide_drive_head_reg] & 0x10) != 0;
controller->current_drive = &controller->drive[drive_nr];
}
else {
controller->current_drive = NULL;
}
if (is_simple)
controller->current_transfer = 1;
else {
int sector_count = controller->reg[ide_sector_count_reg];
if (sector_count == 0)
controller->current_transfer = 256;
else
controller->current_transfer = sector_count;
}
if (controller->current_drive == NULL)
controller->fifo_size = 512;
else
controller->fifo_size = controller->current_drive->geometry.byte;
controller->fifo_pos = 0;
if (controller->current_drive == NULL)
controller->current_byte = 0;
else if (controller->reg[ide_drive_head_reg] & 0x40) {
controller->current_byte = controller->fifo_size
* (((controller->reg[ide_drive_head_reg] & 0xf) << 24)
| (controller->reg[ide_cylinder_reg1] << 16)
| (controller->reg[ide_cylinder_reg0] << 8)
| (controller->reg[ide_sector_number_reg]));
}
else if (controller->current_drive->geometry.head != 0
&& controller->current_drive->geometry.sector != 0) {
int head_nr = controller->reg[ide_drive_head_reg] & 0xf;
int cylinder_nr = ((controller->reg[ide_cylinder_reg1] << 8)
| controller->reg[ide_cylinder_reg0]);
int sector_nr = controller->reg[ide_sector_number_reg];
controller->current_byte = controller->fifo_size
* ((cylinder_nr * controller->current_drive->geometry.head + head_nr)
* controller->current_drive->geometry.sector + sector_nr - 1);
}
else
device_error(me, "controller %d:%d - CHS addressing disabled",
controller->nr, controller->current_drive->nr);
DTRACE(ide, ("controller %ld:%ld - transfer (%s) %ld blocks of %ld bytes from 0x%lx\n",
(long)controller->nr,
controller->current_drive == NULL ? -1L : (long)controller->current_drive->nr,
direction == is_read ? "read" : "write",
(long)controller->current_transfer,
(long)controller->fifo_size,
(unsigned long)controller->current_byte));
switch (direction) {
case is_read:
controller->current_transfer += 1;
controller->state = draining_state;
controller->fifo_pos = controller->fifo_size;
do_fifo_read(me, controller, NULL, 0);
break;
case is_write:
controller->state = loading_state;
break;
}
}
static void
do_command(device *me,
ide_controller *controller,
int command)
{
if (controller->state != idle_state)
device_error(me, "controller %d - command when not idle", controller->nr);
switch (command) {
case 0x20: case 0x21:
setup_fifo(me, controller, 0, 1, is_read);
break;
case 0x30: case 0x31:
setup_fifo(me, controller, 0, 1, is_write);
break;
}
}
static unsigned8
get_status(device *me,
ide_controller *controller)
{
switch (controller->state) {
case loading_state:
case draining_state:
return 0x08;
case busy_loaded_state:
case busy_drained_state:
return 0x80;
case idle_state:
return 0x40;
default:
device_error(me, "internal error");
return 0;
}
}
enum {
nr_address_blocks = 6,
};
typedef struct _address_block {
int space;
unsigned_word base_addr;
unsigned_word bound_addr;
int controller;
int base_reg;
} address_block;
typedef struct _address_decoder {
address_block block[nr_address_blocks];
} address_decoder;
static void
decode_address(device *me,
address_decoder *decoder,
int space,
unsigned_word address,
int *controller,
int *reg,
io_direction direction)
{
int i;
for (i = 0; i < nr_address_blocks; i++) {
if (space == decoder->block[i].space
&& address >= decoder->block[i].base_addr
&& address <= decoder->block[i].bound_addr) {
*controller = decoder->block[i].controller;
*reg = (address
- decoder->block[i].base_addr
+ decoder->block[i].base_reg);
if (direction == is_write) {
switch (*reg) {
case ide_error_reg: *reg = ide_feature_reg; break;
case ide_status_reg: *reg = ide_command_reg; break;
case ide_alternate_status_reg: *reg = ide_control_reg; break;
default: break;
}
}
return;
}
}
device_error(me, "address %d:0x%lx invalid",
space, (unsigned long)address);
}
static void
build_address_decoder(device *me,
address_decoder *decoder)
{
int reg;
for (reg = 1; reg < 6; reg++) {
reg_property_spec unit;
int space;
unsigned_word address;
unsigned size;
if (!device_find_reg_array_property(me, "reg", reg, &unit))
device_error(me, "missing or invalid reg entry %d", reg);
device_address_to_attach_address(device_parent(me), &unit.address,
&space, &address, me);
device_size_to_attach_size(device_parent(me), &unit.size, &size, me);
switch (reg) {
case 1:
case 2:
if (size != 8)
device_error(me, "reg entry %d must have a size of 8", reg);
decoder->block[reg-1].space = space;
decoder->block[reg-1].base_addr = address;
decoder->block[reg-1].bound_addr = address + size - 1;
decoder->block[reg-1].controller = (reg + 1) % nr_ide_controllers;
decoder->block[reg-1].base_reg = ide_data_reg;
DTRACE(ide, ("controller %d command register block at %d:0x%lx..0x%lx\n",
decoder->block[reg-1].controller,
decoder->block[reg-1].space,
(unsigned long)decoder->block[reg-1].base_addr,
(unsigned long)decoder->block[reg-1].bound_addr));
break;
case 3:
case 4:
if (size != 1)
device_error(me, "reg entry %d must have a size of 1", reg);
decoder->block[reg-1].space = space;
decoder->block[reg-1].base_addr = address;
decoder->block[reg-1].bound_addr = address + size - 1;
decoder->block[reg-1].controller = (reg + 1) % nr_ide_controllers;
decoder->block[reg-1].base_reg = ide_alternate_status_reg;
DTRACE(ide, ("controller %d control register block at %d:0x%lx..0x%lx\n",
decoder->block[reg-1].controller,
decoder->block[reg-1].space,
(unsigned long)decoder->block[reg-1].base_addr,
(unsigned long)decoder->block[reg-1].bound_addr));
break;
case 5:
if (size != 8)
device_error(me, "reg entry %d must have a size of 8", reg);
decoder->block[reg-1].space = space;
decoder->block[reg-1].base_addr = address;
decoder->block[reg-1].bound_addr = address + 4 - 1;
decoder->block[reg-1].base_reg = ide_dma_command_reg;
decoder->block[reg-1].controller = 0;
DTRACE(ide, ("controller %d dma register block at %d:0x%lx..0x%lx\n",
decoder->block[reg-1].controller,
decoder->block[reg-1].space,
(unsigned long)decoder->block[reg-1].base_addr,
(unsigned long)decoder->block[reg-1].bound_addr));
decoder->block[reg].space = space;
decoder->block[reg].base_addr = address + 4;
decoder->block[reg].bound_addr = address + 8 - 1;
decoder->block[reg].controller = 1;
decoder->block[reg].base_reg = ide_dma_command_reg;
DTRACE(ide, ("controller %d dma register block at %d:0x%lx..0x%lx\n",
decoder->block[reg].controller,
decoder->block[reg-1].space,
(unsigned long)decoder->block[reg].base_addr,
(unsigned long)decoder->block[reg].bound_addr));
break;
default:
device_error(me, "internal error - bad switch");
break;
}
}
}
typedef struct _hw_ide_device {
ide_controller controller[nr_ide_controllers];
address_decoder decoder;
} hw_ide_device;
static void
hw_ide_init_address(device *me)
{
hw_ide_device *ide = device_data(me);
int controller;
int drive;
for (controller = 0; controller < nr_ide_controllers; controller++) {
memset(&ide->controller[controller], 0, sizeof(ide_controller));
for (drive = 0; drive < nr_ide_drives_per_controller; drive++) {
ide->controller[controller].drive[drive].nr = drive;
}
ide->controller[controller].me = me;
if (device_find_property(me, "ready-delay") != NULL)
ide->controller[controller].ready_delay =
device_find_integer_property(me, "ready-delay");
}
generic_device_init_address(me);
build_address_decoder(me, &ide->decoder);
}
static void
hw_ide_attach_address(device *me,
attach_type type,
int space,
unsigned_word addr,
unsigned nr_bytes,
access_type access,
device *client)
{
hw_ide_device *ide = (hw_ide_device*)device_data(me);
int controller_nr = addr / nr_ide_drives_per_controller;
int drive_nr = addr % nr_ide_drives_per_controller;
ide_controller *controller;
ide_drive *drive;
if (controller_nr >= nr_ide_controllers)
device_error(me, "no controller for disk %s",
device_path(client));
controller = &ide->controller[controller_nr];
drive = &controller->drive[drive_nr];
drive->device = client;
if (device_find_property(client, "ide-byte-count") != NULL)
drive->geometry.byte = device_find_integer_property(client, "ide-byte-count");
else
drive->geometry.byte = 512;
if (device_find_property(client, "ide-sector-count") != NULL)
drive->geometry.sector = device_find_integer_property(client, "ide-sector-count");
if (device_find_property(client, "ide-head-count") != NULL)
drive->geometry.head = device_find_integer_property(client, "ide-head-count");
drive->default_geometry = drive->geometry;
DTRACE(ide, ("controller %d:%d %s byte-count %d, sector-count %d, head-count %d\n",
controller_nr,
drive->nr,
device_path(client),
drive->geometry.byte,
drive->geometry.sector,
drive->geometry.head));
}
static unsigned
hw_ide_io_read_buffer(device *me,
void *dest,
int space,
unsigned_word addr,
unsigned nr_bytes,
cpu *processor,
unsigned_word cia)
{
hw_ide_device *ide = (hw_ide_device *)device_data(me);
int control_nr;
int reg;
ide_controller *controller;
decode_address(me, &ide->decoder, space, addr, &control_nr, ®, is_read);
controller = & ide->controller[control_nr];
memset(dest, 0, nr_bytes);
switch (reg) {
case ide_data_reg:
do_fifo_read(me, controller, dest, nr_bytes);
break;
case ide_status_reg:
*(unsigned8*)dest = get_status(me, controller);
clear_interrupt(me, controller);
break;
case ide_alternate_status_reg:
*(unsigned8*)dest = get_status(me, controller);
break;
case ide_error_reg:
case ide_sector_count_reg:
case ide_sector_number_reg:
case ide_cylinder_reg0:
case ide_cylinder_reg1:
case ide_drive_head_reg:
case ide_control_reg:
case ide_dma_command_reg:
case ide_dma_status_reg:
case ide_dma_prd_table_address_reg0:
case ide_dma_prd_table_address_reg1:
case ide_dma_prd_table_address_reg2:
case ide_dma_prd_table_address_reg3:
*(unsigned8*)dest = controller->reg[reg];
break;
default:
device_error(me, "bus-error at address 0x%lx", addr);
break;
}
return nr_bytes;
}
static unsigned
hw_ide_io_write_buffer(device *me,
const void *source,
int space,
unsigned_word addr,
unsigned nr_bytes,
cpu *processor,
unsigned_word cia)
{
hw_ide_device *ide = (hw_ide_device *)device_data(me);
int control_nr;
int reg;
ide_controller *controller;
decode_address(me, &ide->decoder, space, addr, &control_nr, ®, is_write);
controller = &ide->controller[control_nr];
switch (reg) {
case ide_data_reg:
do_fifo_write(me, controller, source, nr_bytes);
break;
case ide_command_reg:
do_command(me, controller, *(unsigned8*)source);
break;
case ide_control_reg:
controller->reg[reg] = *(unsigned8*)source;
if ((controller->reg[reg] & 0x02) == 0x02)
clear_interrupt(me, controller);
break;
case ide_feature_reg:
case ide_sector_count_reg:
case ide_sector_number_reg:
case ide_cylinder_reg0:
case ide_cylinder_reg1:
case ide_drive_head_reg:
case ide_dma_command_reg:
case ide_dma_status_reg:
case ide_dma_prd_table_address_reg0:
case ide_dma_prd_table_address_reg1:
case ide_dma_prd_table_address_reg2:
case ide_dma_prd_table_address_reg3:
controller->reg[reg] = *(unsigned8*)source;
break;
default:
device_error(me, "bus-error at 0x%lx", addr);
break;
}
return nr_bytes;
}
static const device_interrupt_port_descriptor hw_ide_interrupt_ports[] = {
{ "a", 0, 0 },
{ "b", 1, 0 },
{ "c", 2, 0 },
{ "d", 3, 0 },
{ NULL }
};
static device_callbacks const hw_ide_callbacks = {
{ hw_ide_init_address, },
{ hw_ide_attach_address, },
{ hw_ide_io_read_buffer, hw_ide_io_write_buffer, },
{ NULL, },
{ NULL, NULL, hw_ide_interrupt_ports },
{ generic_device_unit_decode,
generic_device_unit_encode,
generic_device_address_to_attach_address,
generic_device_size_to_attach_size },
};
static void *
hw_ide_create(const char *name,
const device_unit *unit_address,
const char *args)
{
hw_ide_device *ide = ZALLOC(hw_ide_device);
return ide;
}
const device_descriptor hw_ide_device_descriptor[] = {
{ "ide", hw_ide_create, &hw_ide_callbacks },
{ NULL, },
};
#endif