mirror of
https://github.com/captain-amygdala/pistorm.git
synced 2026-04-14 07:49:25 +00:00
added, not yet functioning, IDE emulation
This commit is contained in:
191
Gayle.c
Normal file
191
Gayle.c
Normal file
@@ -0,0 +1,191 @@
|
||||
//
|
||||
// Gayle.c
|
||||
// Omega
|
||||
//
|
||||
// Created by Matt Parsons on 06/03/2019.
|
||||
// Copyright © 2019 Matt Parsons. All rights reserved.
|
||||
//
|
||||
|
||||
//Write Byte to Gayle Space 0xda9000 (0x0000c3)
|
||||
//Read Byte From Gayle Space 0xda9000
|
||||
//Read Byte From Gayle Space 0xdaa000
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include "Gayle.h"
|
||||
#include "ide.h"
|
||||
|
||||
#define CLOCKBASE 0xDC0000
|
||||
|
||||
#define GSTATUS 0xda201c
|
||||
#define GCLOW 0xda2010
|
||||
#define GDH 0xda2018
|
||||
|
||||
|
||||
|
||||
//Write Byte to Gayle Space 0xda2018 (0x000000)
|
||||
//Read Byte From Gayle Space 0xda2010
|
||||
//Read Byte From Gayle Space 0xda201c
|
||||
//Write Byte to Gayle Space 0xdaa000 (0x00002c)
|
||||
//Write Byte to Gayle Space 0xda8000 (0x000000)
|
||||
//Write Byte to Gayle Space 0xda2018 (0x000000)
|
||||
//Write Byte to Gayle Space 0xda2010 (0x000012)
|
||||
|
||||
|
||||
|
||||
/*
|
||||
Write Byte to Gayle Space 0xda3018 (0x000000)
|
||||
|
||||
Read Byte from Gayle Ident 0xde1000 (0x000004)
|
||||
Write ide_dev_head: 0x0000a0
|
||||
Write Byte to Gayle Space 0xda2018 (0x0000a0)
|
||||
Write ide_cyl_low: 0x000012
|
||||
Write Byte to Gayle Space 0xda2010 (0x000012)
|
||||
Write ide_cyl_low: 0x000034
|
||||
Write Byte to Gayle Space 0xda2010 (0x000034)
|
||||
Write Byte to Gayle Space 0xda3018 (0x000000)
|
||||
Write ide_status_r: 0x000010
|
||||
Write Byte to Gayle Space 0xda201c (0x000010)
|
||||
*/
|
||||
|
||||
|
||||
int counter;
|
||||
static struct ide_controller *ide0;
|
||||
int fd;
|
||||
|
||||
void InitGayle(void){
|
||||
ide0 = ide_allocate("cf");
|
||||
fd = open("hd0.img", O_RDWR);
|
||||
if (fd == -1){
|
||||
printf("HDD Image hd0.image failed open\n");
|
||||
}else{
|
||||
ide_attach(ide0, 0, fd);
|
||||
ide_reset_begin(ide0);
|
||||
printf("HDD Image hd0.image attached\n");
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t CheckIrq(void){
|
||||
uint8_t irq;
|
||||
|
||||
irq = ide0->drive->intrq;
|
||||
// if (irq==0)
|
||||
// printf("IDE IRQ: 0\n");
|
||||
|
||||
return irq;
|
||||
}
|
||||
|
||||
void writeGayleB(unsigned int address, unsigned int value){
|
||||
|
||||
|
||||
if (address == GSTATUS) {
|
||||
ide_write8(ide0, ide_status_r, value);
|
||||
// printf("Write ide_status_r: 0x%06x IRQ:0x%06x\n",value, ide0->drive->intrq);
|
||||
return;
|
||||
}
|
||||
|
||||
if (address == GDH) {
|
||||
ide_write8(ide0, ide_dev_head, value);
|
||||
// printf("Write ide_dev_head: 0x%06x\n",value);
|
||||
return;
|
||||
}
|
||||
|
||||
if (address == GCLOW) {
|
||||
ide_write8(ide0, ide_cyl_low, value);
|
||||
// printf("Write ide_cyl_low: 0x%06x\n",value);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (address == 0xDE1000){
|
||||
counter = 0;
|
||||
// printf("Write Byte to Gayle Ident 0x%06x (0x%06x)\n",address,value);
|
||||
return;
|
||||
}
|
||||
|
||||
if (address == 0xda9000){
|
||||
return;
|
||||
}
|
||||
|
||||
if (address == 0xda8000){
|
||||
return;
|
||||
}
|
||||
|
||||
if (address == 0xdaa000){
|
||||
return;
|
||||
}
|
||||
|
||||
if (address == 0xdab000){
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
printf("Write Byte to Gayle Space 0x%06x (0x%06x)\n",address,value);
|
||||
}
|
||||
|
||||
void writeGayle(unsigned int address, unsigned int value){
|
||||
// printf("Write to Gayle Space 0x%06x (0x%06x)\n",address,value);
|
||||
}
|
||||
|
||||
void writeGayleL(unsigned int address, unsigned int value){
|
||||
// printf("Write Long to Gayle Space 0x%06x (0x%06x)\n",address,value);
|
||||
}
|
||||
|
||||
uint8_t readGayleB(unsigned int address){
|
||||
|
||||
if (address == GSTATUS) {
|
||||
// printf("Read ide_status_r\n");
|
||||
return ide_read8(ide0, ide_status_r);
|
||||
}
|
||||
|
||||
if (address == GCLOW) {
|
||||
// printf("Read ide_cyl_low\n");
|
||||
return ide_read8(ide0, ide_cyl_low);
|
||||
}
|
||||
|
||||
if (address == GDH) {
|
||||
// printf("Read ide_dev_head\n");
|
||||
return ide_read8(ide0, ide_dev_head);
|
||||
}
|
||||
|
||||
|
||||
if (address == 0xDE1000){
|
||||
counter++;
|
||||
// printf("Read Byte from Gayle Ident 0x%06x (0x%06x)\n",address,counter);
|
||||
|
||||
if (counter == 3){
|
||||
// printf("Gayle Ident cycle\n");
|
||||
return 0xFF;//7F; to enable gayle
|
||||
}else{
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (address == 0xda9000){
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (address == 0xdaa000){
|
||||
return 0;
|
||||
}
|
||||
|
||||
printf("Read Byte From Gayle Space 0x%06x\n",address);
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
uint16_t readGayle(unsigned int address){
|
||||
printf("Read From Gayle Space 0x%06x\n",address);
|
||||
return 0x8000;
|
||||
}
|
||||
|
||||
uint32_t readGayleL(unsigned int address){
|
||||
printf("Read Long From Gayle Space 0x%06x\n",address);
|
||||
return 0x8000;
|
||||
}
|
||||
26
Gayle.h
Normal file
26
Gayle.h
Normal file
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// Gayle.h
|
||||
// Omega
|
||||
//
|
||||
// Created by Matt Parsons on 06/03/2019.
|
||||
// Copyright © 2019 Matt Parsons. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Gayle_h
|
||||
#define Gayle_h
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
|
||||
|
||||
uint8_t CheckIrq(void);
|
||||
void InitGayle(void);
|
||||
void writeGayleB(unsigned int address, unsigned value);
|
||||
void writeGayle(unsigned int address, unsigned value);
|
||||
void writeGayleL(unsigned int address, unsigned value);
|
||||
uint8_t readGayleB(unsigned int address);
|
||||
uint16_t readGayle(unsigned int address);
|
||||
uint32_t readGayleL(unsigned int address);
|
||||
#endif /* Gayle_h */
|
||||
934
ide.c
Normal file
934
ide.c
Normal file
@@ -0,0 +1,934 @@
|
||||
/*
|
||||
* IDE Emulation Layer for retro-style PIO interfaces
|
||||
*
|
||||
* (c) Copyright Alan Cox, 2015-2019
|
||||
*
|
||||
* IDE-emu is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* IDE-emu is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with IDE-emu. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <time.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include "ide.h"
|
||||
|
||||
#define IDE_IDLE 0
|
||||
#define IDE_CMD 1
|
||||
#define IDE_DATA_IN 2
|
||||
#define IDE_DATA_OUT 3
|
||||
|
||||
#define DCR_NIEN 2
|
||||
#define DCR_SRST 4
|
||||
|
||||
#define DEVH_HEAD 15
|
||||
#define DEVH_DEV 16
|
||||
#define DEVH_LBA 64
|
||||
|
||||
#define ERR_AMNF 1
|
||||
#define ERR_TKNONF 2
|
||||
#define ERR_ABRT 4
|
||||
#define ERR_MCR 8
|
||||
#define ERR_IDNF 16
|
||||
#define ERR_MC 32
|
||||
#define ERR_UNC 64
|
||||
|
||||
#define ST_ERR 1
|
||||
#define ST_IDX 2
|
||||
#define ST_CORR 4
|
||||
#define ST_DRQ 8
|
||||
#define ST_DSC 16
|
||||
#define ST_DF 32
|
||||
#define ST_DRDY 64
|
||||
#define ST_BSY 128
|
||||
|
||||
#define DCL_SRST 4
|
||||
#define DCL_NIEN 2
|
||||
|
||||
#define IDE_CMD_CALIB 0x10
|
||||
#define IDE_CMD_READ 0x20
|
||||
#define IDE_CMD_READ_NR 0x21
|
||||
#define IDE_CMD_WRITE 0x30
|
||||
#define IDE_CMD_WRITE_NR 0x31
|
||||
#define IDE_CMD_VERIFY 0x40
|
||||
#define IDE_CMD_VERIFY_NR 0x41
|
||||
#define IDE_CMD_SEEK 0x70
|
||||
#define IDE_CMD_EDD 0x90
|
||||
#define IDE_CMD_INTPARAMS 0x91
|
||||
#define IDE_CMD_IDENTIFY 0xEC
|
||||
#define IDE_CMD_SETFEATURES 0xEF
|
||||
|
||||
const uint8_t ide_magic[8] = {
|
||||
'1','D','E','D','1','5','C','0'
|
||||
};
|
||||
|
||||
static char *charmap(uint8_t v)
|
||||
{
|
||||
static char cbuf[3];
|
||||
if (v < 32)
|
||||
sprintf(cbuf, "^%c", '@'+v);
|
||||
else if (v < 127)
|
||||
sprintf(cbuf, " %c", v);
|
||||
else if (v == 127)
|
||||
sprintf(cbuf, "DL");
|
||||
else if (v < 160)
|
||||
sprintf(cbuf, ":%c", '@' + v - 128);
|
||||
else if (v < 255)
|
||||
sprintf(cbuf, "~%c", v - 128);
|
||||
else
|
||||
sprintf(cbuf, "!D");
|
||||
return cbuf;
|
||||
}
|
||||
|
||||
static void hexdump(uint8_t *bp)
|
||||
{
|
||||
int i,j;
|
||||
for (i = 0; i < 512; i+= 16) {
|
||||
for(j = 0; j < 16; j++)
|
||||
fprintf(stderr, "%02X ", bp[i+j]);
|
||||
fprintf(stderr, "|");
|
||||
for(j = 0; j < 16; j++)
|
||||
fprintf(stderr, "%2s", charmap(bp[i+j]));
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
}
|
||||
|
||||
/* FIXME: use proper endian convertors! */
|
||||
static uint16_t le16(uint16_t v)
|
||||
{
|
||||
uint8_t *p = (uint8_t *)&v;
|
||||
return p[0] | (p[1] << 8);
|
||||
}
|
||||
|
||||
static void ide_xlate_errno(struct ide_taskfile *t, int len)
|
||||
{
|
||||
t->status |= ST_ERR;
|
||||
if (len == -1) {
|
||||
if (errno == EIO)
|
||||
t->error = ERR_UNC;
|
||||
else
|
||||
t->error = ERR_AMNF;
|
||||
} else
|
||||
t->error = ERR_AMNF;
|
||||
}
|
||||
|
||||
static void ide_fault(struct ide_drive *d, const char *p)
|
||||
{
|
||||
fprintf(stderr, "ide: %s: %d: %s\n", d->controller->name,
|
||||
(int)(d - d->controller->drive), p);
|
||||
}
|
||||
|
||||
/* Disk translation */
|
||||
static off_t xlate_block(struct ide_taskfile *t)
|
||||
{
|
||||
struct ide_drive *d = t->drive;
|
||||
uint16_t cyl;
|
||||
|
||||
if (t->lba4 & DEVH_LBA) {
|
||||
/* fprintf(stderr, "XLATE LBA %02X:%02X:%02X:%02X\n",
|
||||
t->lba4, t->lba3, t->lba2, t->lba1);*/
|
||||
if (d->lba)
|
||||
return 2 + (((t->lba4 & DEVH_HEAD) << 24) | (t->lba3 << 16) | (t->lba2 << 8) | t->lba1);
|
||||
ide_fault(d, "LBA on non LBA drive");
|
||||
}
|
||||
|
||||
/* Some well known software asks for 0/0/0 when it means 0/0/1. Drives appear
|
||||
to interpret sector 0 as sector 1 */
|
||||
if (t->lba1 == 0) {
|
||||
fprintf(stderr, "[Bug: request for sector offset 0].\n");
|
||||
t->lba1 = 1;
|
||||
}
|
||||
cyl = (t->lba3 << 8) | t->lba2;
|
||||
/* fprintf(stderr, "(H %d C %d S %d)\n", t->lba4 & DEVH_HEAD, cyl, t->lba1); */
|
||||
if (t->lba1 == 0 || t->lba1 > d->sectors || t->lba4 >= d->heads || cyl >= d->cylinders) {
|
||||
return -1;
|
||||
}
|
||||
/* Sector 1 is first */
|
||||
/* Images generally go cylinder/head/sector. This also matters if we ever
|
||||
implement more advanced geometry setting */
|
||||
return 1 + ((cyl * d->heads) + (t->lba4 & DEVH_HEAD)) * d->sectors + t->lba1;
|
||||
}
|
||||
|
||||
/* Indicate the drive is ready */
|
||||
static void ready(struct ide_taskfile *tf)
|
||||
{
|
||||
tf->status &= ~(ST_BSY|ST_DRQ);
|
||||
tf->status |= ST_DRDY;
|
||||
tf->drive->state = IDE_IDLE;
|
||||
}
|
||||
|
||||
/* Return to idle state, completing a command */
|
||||
static void completed(struct ide_taskfile *tf)
|
||||
{
|
||||
ready(tf);
|
||||
tf->drive->intrq = 1;
|
||||
}
|
||||
|
||||
static void drive_failed(struct ide_taskfile *tf)
|
||||
{
|
||||
tf->status |= ST_ERR;
|
||||
tf->error = ERR_IDNF;
|
||||
ready(tf);
|
||||
}
|
||||
|
||||
static void data_in_state(struct ide_taskfile *tf)
|
||||
{
|
||||
struct ide_drive *d = tf->drive;
|
||||
d->state = IDE_DATA_IN;
|
||||
d->dptr = d->data + 512;
|
||||
/* We don't clear DRDY here, drives may well accept a command at this
|
||||
point and at least one firmware for RC2014 assumes this */
|
||||
tf->status &= ~ST_BSY;
|
||||
tf->status |= ST_DRQ;
|
||||
d->intrq = 1; /* Double check */
|
||||
}
|
||||
|
||||
static void data_out_state(struct ide_taskfile *tf)
|
||||
{
|
||||
struct ide_drive *d = tf->drive;
|
||||
d->state = IDE_DATA_OUT;
|
||||
d->dptr = d->data;
|
||||
tf->status &= ~ (ST_BSY|ST_DRDY);
|
||||
tf->status |= ST_DRQ;
|
||||
d->intrq = 1; /* Double check */
|
||||
}
|
||||
|
||||
static void edd_setup(struct ide_taskfile *tf)
|
||||
{
|
||||
tf->error = 0x01; /* All good */
|
||||
tf->lba1 = 0x01; /* EDD always updates drive 0 */
|
||||
tf->lba2 = 0x00;
|
||||
tf->lba3 = 0x00;
|
||||
tf->lba4 = 0x00;
|
||||
tf->count = 0x01;
|
||||
ready(tf);
|
||||
}
|
||||
|
||||
void ide_reset(struct ide_controller *c)
|
||||
{
|
||||
if (c->drive[0].present) {
|
||||
edd_setup(&c->drive[0].taskfile);
|
||||
/* A drive could clear busy then set DRDY up to 2 minutes later if its
|
||||
mindnumbingly slow to start up ! We don't emulate any of that */
|
||||
c->drive[0].taskfile.status = ST_DRDY;
|
||||
c->drive[0].eightbit = 0;
|
||||
}
|
||||
if (c->drive[1].present) {
|
||||
edd_setup(&c->drive[1].taskfile);
|
||||
c->drive[1].taskfile.status = ST_DRDY;
|
||||
c->drive[1].eightbit = 0;
|
||||
}
|
||||
c->selected = 0;
|
||||
}
|
||||
|
||||
void ide_reset_begin(struct ide_controller *c)
|
||||
{
|
||||
if (c->drive[0].present)
|
||||
c->drive[0].taskfile.status |= ST_BSY;
|
||||
if (c->drive[1].present)
|
||||
c->drive[1].taskfile.status |= ST_BSY;
|
||||
/* Ought to be a time delay relative to reset or power on */
|
||||
ide_reset(c);
|
||||
}
|
||||
|
||||
static void ide_srst_begin(struct ide_controller *c)
|
||||
{
|
||||
ide_reset(c);
|
||||
if (c->drive[0].present)
|
||||
c->drive[0].taskfile.status |= ST_BSY;
|
||||
if (c->drive[1].present)
|
||||
c->drive[1].taskfile.status |= ST_BSY;
|
||||
}
|
||||
|
||||
static void ide_srst_end(struct ide_controller *c)
|
||||
{
|
||||
/* Could be time delays here */
|
||||
ready(&c->drive[0].taskfile);
|
||||
ready(&c->drive[1].taskfile);
|
||||
}
|
||||
|
||||
static void cmd_edd_complete(struct ide_taskfile *tf)
|
||||
{
|
||||
struct ide_controller *c = tf->drive->controller;
|
||||
if (c->drive[0].present)
|
||||
edd_setup(&c->drive[0].taskfile);
|
||||
if (c->drive[1].present)
|
||||
edd_setup(&c->drive[1].taskfile);
|
||||
c->selected = 0;
|
||||
}
|
||||
|
||||
static void cmd_identify_complete(struct ide_taskfile *tf)
|
||||
{
|
||||
struct ide_drive *d = tf->drive;
|
||||
memcpy(d->data, d->identify, 512);
|
||||
data_in_state(tf);
|
||||
/* Arrange to copy just the identify buffer */
|
||||
d->dptr = d->data;
|
||||
d->length = 1;
|
||||
}
|
||||
|
||||
static void cmd_initparam_complete(struct ide_taskfile *tf)
|
||||
{
|
||||
struct ide_drive *d = tf->drive;
|
||||
/* We only support the current mapping */
|
||||
if (tf->count != d->sectors || (tf->lba4 & DEVH_HEAD) + 1 != d->heads) {
|
||||
tf->status |= ST_ERR;
|
||||
tf->error |= ERR_ABRT;
|
||||
tf->drive->failed = 1; /* Report ID NF until fixed */
|
||||
/* fprintf(stderr, "geo is %d %d, asked for %d %d\n",
|
||||
d->sectors, d->heads, tf->count, (tf->lba4 & DEVH_HEAD) + 1); */
|
||||
ide_fault(d, "invalid geometry");
|
||||
} else if (tf->drive->failed == 1)
|
||||
tf->drive->failed = 0; /* Valid translation */
|
||||
completed(tf);
|
||||
}
|
||||
|
||||
static void cmd_readsectors_complete(struct ide_taskfile *tf)
|
||||
{
|
||||
struct ide_drive *d = tf->drive;
|
||||
/* Move to data xfer */
|
||||
if (d->failed) {
|
||||
drive_failed(tf);
|
||||
return;
|
||||
}
|
||||
d->offset = xlate_block(tf);
|
||||
/* DRDY is not guaranteed here but at least one buggy RC2014 firmware
|
||||
expects it */
|
||||
tf->status |= ST_DRQ | ST_DSC | ST_DRDY;
|
||||
tf->status &= ~ST_BSY;
|
||||
/* 0 = 256 sectors */
|
||||
d->length = tf->count ? tf->count : 256;
|
||||
/* fprintf(stderr, "READ %d SECTORS @ %ld\n", d->length, d->offset); */
|
||||
if (d->offset == -1 || lseek(d->fd, 512 * d->offset, SEEK_SET) == -1) {
|
||||
tf->status |= ST_ERR;
|
||||
tf->status &= ~ST_DSC;
|
||||
tf->error |= ERR_IDNF;
|
||||
/* return null data */
|
||||
completed(tf);
|
||||
return;
|
||||
}
|
||||
/* do the xfer */
|
||||
data_in_state(tf);
|
||||
}
|
||||
|
||||
static void cmd_verifysectors_complete(struct ide_taskfile *tf)
|
||||
{
|
||||
struct ide_drive *d = tf->drive;
|
||||
/* Move to data xfer */
|
||||
if (d->failed) {
|
||||
drive_failed(tf);
|
||||
return;
|
||||
}
|
||||
d->offset = xlate_block(tf);
|
||||
/* 0 = 256 sectors */
|
||||
d->length = tf->count ? tf->count : 256;
|
||||
if (d->offset == -1 || lseek(d->fd, 512 * (d->offset + d->length - 1), SEEK_SET) == -1) {
|
||||
tf->status &= ~ST_DSC;
|
||||
tf->status |= ST_ERR;
|
||||
tf->error |= ERR_IDNF;
|
||||
}
|
||||
tf->status |= ST_DSC;
|
||||
completed(tf);
|
||||
}
|
||||
|
||||
static void cmd_recalibrate_complete(struct ide_taskfile *tf)
|
||||
{
|
||||
struct ide_drive *d = tf->drive;
|
||||
if (d->failed)
|
||||
drive_failed(tf);
|
||||
if (d->offset == -1 || xlate_block(tf) != 0L) {
|
||||
tf->status &= ~ST_DSC;
|
||||
tf->status |= ST_ERR;
|
||||
tf->error |= ERR_ABRT;
|
||||
}
|
||||
tf->status |= ST_DSC;
|
||||
completed(tf);
|
||||
}
|
||||
|
||||
static void cmd_seek_complete(struct ide_taskfile *tf)
|
||||
{
|
||||
struct ide_drive *d = tf->drive;
|
||||
if (d->failed)
|
||||
drive_failed(tf);
|
||||
d->offset = xlate_block(tf);
|
||||
if (d->offset == -1 || lseek(d->fd, 512 * d->offset, SEEK_SET) == -1) {
|
||||
tf->status &= ~ST_DSC;
|
||||
tf->status |= ST_ERR;
|
||||
tf->error |= ERR_IDNF;
|
||||
}
|
||||
tf->status |= ST_DSC;
|
||||
completed(tf);
|
||||
}
|
||||
|
||||
static void cmd_setfeatures_complete(struct ide_taskfile *tf)
|
||||
{
|
||||
struct ide_drive *d = tf->drive;
|
||||
switch(tf->feature) {
|
||||
case 0x01:
|
||||
d->eightbit = 1;
|
||||
break;
|
||||
case 0x03:
|
||||
if ((tf->count & 0xF0) >= 0x20) {
|
||||
tf->status |= ST_ERR;
|
||||
tf->error |= ERR_ABRT;
|
||||
}
|
||||
/* Silently accept PIO mode settings */
|
||||
break;
|
||||
case 0x81:
|
||||
d->eightbit = 0;
|
||||
break;
|
||||
default:
|
||||
tf->status |= ST_ERR;
|
||||
tf->error |= ERR_ABRT;
|
||||
}
|
||||
completed(tf);
|
||||
}
|
||||
|
||||
static void cmd_writesectors_complete(struct ide_taskfile *tf)
|
||||
{
|
||||
struct ide_drive *d = tf->drive;
|
||||
/* Move to data xfer */
|
||||
if (d->failed) {
|
||||
drive_failed(tf);
|
||||
return;
|
||||
}
|
||||
d->offset = xlate_block(tf);
|
||||
tf->status |= ST_DRQ;
|
||||
/* 0 = 256 sectors */
|
||||
d->length = tf->count ? tf->count : 256;
|
||||
/* fprintf(stderr, "WRITE %d SECTORS @ %ld\n", d->length, d->offset); */
|
||||
if (d->offset == -1 || lseek(d->fd, 512 * d->offset, SEEK_SET) == -1) {
|
||||
tf->status |= ST_ERR;
|
||||
tf->error |= ERR_IDNF;
|
||||
tf->status &= ~ST_DSC;
|
||||
/* return null data */
|
||||
completed(tf);
|
||||
return;
|
||||
}
|
||||
/* do the xfer */
|
||||
data_out_state(tf);
|
||||
}
|
||||
|
||||
static void ide_set_error(struct ide_drive *d)
|
||||
{
|
||||
d->taskfile.lba4 &= ~DEVH_HEAD;
|
||||
|
||||
if (d->taskfile.lba4 & DEVH_LBA) {
|
||||
d->taskfile.lba1 = d->offset & 0xFF;
|
||||
d->taskfile.lba2 = (d->offset >> 8) & 0xFF;
|
||||
d->taskfile.lba3 = (d->offset >> 16) & 0xFF;
|
||||
d->taskfile.lba4 |= (d->offset >> 24) & DEVH_HEAD;
|
||||
} else {
|
||||
d->taskfile.lba1 = d->offset % d->sectors + 1;
|
||||
d->offset /= d->sectors;
|
||||
d->taskfile.lba4 |= d->offset / (d->cylinders * d->sectors);
|
||||
d->offset %= (d->cylinders * d->sectors);
|
||||
d->taskfile.lba2 = d->offset & 0xFF;
|
||||
d->taskfile.lba3 = (d->offset >> 8) & 0xFF;
|
||||
}
|
||||
d->taskfile.count = d->length;
|
||||
d->taskfile.status |= ST_ERR;
|
||||
d->state = IDE_IDLE;
|
||||
completed(&d->taskfile);
|
||||
}
|
||||
|
||||
static int ide_read_sector(struct ide_drive *d)
|
||||
{
|
||||
int len;
|
||||
|
||||
d->dptr = d->data;
|
||||
if ((len = read(d->fd, d->data, 512)) != 512) {
|
||||
perror("ide_read_sector");
|
||||
d->taskfile.status |= ST_ERR;
|
||||
d->taskfile.status &= ~ST_DSC;
|
||||
ide_xlate_errno(&d->taskfile, len);
|
||||
return -1;
|
||||
}
|
||||
// hexdump(d->data);
|
||||
d->offset += 512;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ide_write_sector(struct ide_drive *d)
|
||||
{
|
||||
int len;
|
||||
|
||||
d->dptr = d->data;
|
||||
if ((len = write(d->fd, d->data, 512)) != 512) {
|
||||
d->taskfile.status |= ST_ERR;
|
||||
d->taskfile.status &= ~ST_DSC;
|
||||
ide_xlate_errno(&d->taskfile, len);
|
||||
return -1;
|
||||
}
|
||||
// hexdump(d->data);
|
||||
d->offset += 512;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint16_t ide_data_in(struct ide_drive *d, int len)
|
||||
{
|
||||
uint16_t v;
|
||||
if (d->state == IDE_DATA_IN) {
|
||||
if (d->dptr == d->data + 512) {
|
||||
if (ide_read_sector(d) < 0) {
|
||||
ide_set_error(d); /* Set the LBA or CHS etc */
|
||||
return 0xFFFF; /* and error bits set by read_sector */
|
||||
}
|
||||
}
|
||||
v = *d->dptr;
|
||||
if (!d->eightbit) {
|
||||
if (len == 2)
|
||||
v |= (d->dptr[1] << 8);
|
||||
d->dptr+=2;
|
||||
} else
|
||||
d->dptr++;
|
||||
d->taskfile.data = v;
|
||||
if (d->dptr == d->data + 512) {
|
||||
d->length--;
|
||||
d->intrq = 1; /* we don't yet emulate multimode */
|
||||
if (d->length == 0) {
|
||||
d->state = IDE_IDLE;
|
||||
completed(&d->taskfile);
|
||||
}
|
||||
}
|
||||
} else
|
||||
ide_fault(d, "bad data read");
|
||||
|
||||
if (len == 1)
|
||||
return d->taskfile.data & 0xFF;
|
||||
return d->taskfile.data;
|
||||
}
|
||||
|
||||
static void ide_data_out(struct ide_drive *d, uint16_t v, int len)
|
||||
{
|
||||
if (d->state != IDE_DATA_OUT) {
|
||||
ide_fault(d, "bad data write");
|
||||
d->taskfile.data = v;
|
||||
} else {
|
||||
if (d->eightbit)
|
||||
v &= 0xFF;
|
||||
*d->dptr++ = v;
|
||||
d->taskfile.data = v;
|
||||
if (!d->eightbit) {
|
||||
*d->dptr++ = v >> 8;
|
||||
d->taskfile.data = v >> 8;
|
||||
}
|
||||
if (d->dptr == d->data + 512) {
|
||||
if (ide_write_sector(d) < 0) {
|
||||
ide_set_error(d);
|
||||
return;
|
||||
}
|
||||
d->length--;
|
||||
d->intrq = 1;
|
||||
if (d->length == 0) {
|
||||
d->state = IDE_IDLE;
|
||||
d->taskfile.status |= ST_DSC;
|
||||
completed(&d->taskfile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ide_issue_command(struct ide_taskfile *t)
|
||||
{
|
||||
t->status &= ~(ST_ERR|ST_DRDY);
|
||||
t->status |= ST_BSY;
|
||||
t->error = 0;
|
||||
t->drive->state = IDE_CMD;
|
||||
|
||||
/* We could complete with delays but don't do so yet */
|
||||
switch(t->command) {
|
||||
case IDE_CMD_EDD: /* 0x90 */
|
||||
cmd_edd_complete(t);
|
||||
break;
|
||||
case IDE_CMD_IDENTIFY: /* 0xEC */
|
||||
cmd_identify_complete(t);
|
||||
break;
|
||||
case IDE_CMD_INTPARAMS: /* 0x91 */
|
||||
cmd_initparam_complete(t);
|
||||
break;
|
||||
case IDE_CMD_READ: /* 0x20 */
|
||||
case IDE_CMD_READ_NR: /* 0x21 */
|
||||
cmd_readsectors_complete(t);
|
||||
break;
|
||||
case IDE_CMD_SETFEATURES: /* 0xEF */
|
||||
cmd_setfeatures_complete(t);
|
||||
break;
|
||||
case IDE_CMD_VERIFY: /* 0x40 */
|
||||
case IDE_CMD_VERIFY_NR: /* 0x41 */
|
||||
cmd_verifysectors_complete(t);
|
||||
break;
|
||||
case IDE_CMD_WRITE: /* 0x30 */
|
||||
case IDE_CMD_WRITE_NR: /* 0x31 */
|
||||
cmd_writesectors_complete(t);
|
||||
break;
|
||||
default:
|
||||
if ((t->command & 0xF0) == IDE_CMD_CALIB) /* 1x */
|
||||
cmd_recalibrate_complete(t);
|
||||
else if ((t->command & 0xF0) == IDE_CMD_SEEK) /* 7x */
|
||||
cmd_seek_complete(t);
|
||||
else {
|
||||
/* Unknown */
|
||||
t->status |= ST_ERR;
|
||||
t->error |= ERR_ABRT;
|
||||
completed(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 8bit IDE controller emulation
|
||||
*/
|
||||
|
||||
uint8_t ide_read8(struct ide_controller *c, uint8_t r)
|
||||
{
|
||||
struct ide_drive *d = &c->drive[c->selected];
|
||||
struct ide_taskfile *t = &d->taskfile;
|
||||
switch(r) {
|
||||
case ide_data:
|
||||
return ide_data_in(d, 1);
|
||||
case ide_error_r:
|
||||
return t->error;
|
||||
case ide_sec_count:
|
||||
return t->count;
|
||||
case ide_lba_low:
|
||||
return t->lba1;
|
||||
case ide_lba_mid:
|
||||
return t->lba2;
|
||||
case ide_lba_hi:
|
||||
return t->lba3;
|
||||
case ide_lba_top:
|
||||
return t->lba4;
|
||||
case ide_status_r:
|
||||
d->intrq = 0; /* Acked */
|
||||
case ide_altst_r:
|
||||
return t->status;
|
||||
default:
|
||||
ide_fault(d, "bogus register");
|
||||
return 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
void ide_write8(struct ide_controller *c, uint8_t r, uint8_t v)
|
||||
{
|
||||
struct ide_drive *d = &c->drive[c->selected];
|
||||
struct ide_taskfile *t = &d->taskfile;
|
||||
|
||||
if (r != ide_devctrl_w) {
|
||||
if (t->status & ST_BSY) {
|
||||
ide_fault(d, "command written while busy");
|
||||
return;
|
||||
}
|
||||
/* Not clear this is the right emulation */
|
||||
if (d->present == 0 && r != ide_lba_top) {
|
||||
ide_fault(d, "not present");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
switch(r) {
|
||||
case ide_data:
|
||||
ide_data_out(d, v, 1);
|
||||
break;
|
||||
case ide_feature_w:
|
||||
t->feature = v;
|
||||
break;
|
||||
case ide_sec_count:
|
||||
t->count = v;
|
||||
break;
|
||||
case ide_lba_low:
|
||||
t->lba1 = v;
|
||||
break;
|
||||
case ide_lba_mid:
|
||||
t->lba2 = v;
|
||||
break;
|
||||
case ide_lba_hi:
|
||||
t->lba3 = v;
|
||||
break;
|
||||
case ide_lba_top:
|
||||
c->selected = (v & DEVH_DEV) ? 1 : 0;
|
||||
c->drive[c->selected].taskfile.lba4 = v & (DEVH_HEAD|DEVH_DEV|DEVH_LBA);
|
||||
break;
|
||||
case ide_command_w:
|
||||
t->command = v;
|
||||
ide_issue_command(t);
|
||||
break;
|
||||
case ide_devctrl_w:
|
||||
/* ATA: "When the Device Control register is written, both devices
|
||||
respond to the write regardless of which device is selected" */
|
||||
if ((v ^ t->devctrl) & DCL_SRST) {
|
||||
if (v & DCL_SRST)
|
||||
ide_srst_begin(c);
|
||||
else
|
||||
ide_srst_end(c);
|
||||
}
|
||||
c->drive[0].taskfile.devctrl = v; /* Check versus real h/w does this end up cleared */
|
||||
c->drive[1].taskfile.devctrl = v;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 16bit IDE controller emulation
|
||||
*/
|
||||
|
||||
uint16_t ide_read16(struct ide_controller *c, uint8_t r)
|
||||
{
|
||||
struct ide_drive *d = &c->drive[c->selected];
|
||||
if (r == ide_data)
|
||||
return htons(ide_data_in(d,2));
|
||||
return ide_read8(c, r);
|
||||
}
|
||||
|
||||
void ide_write16(struct ide_controller *c, uint8_t r, uint16_t v)
|
||||
{
|
||||
struct ide_drive *d = &c->drive[c->selected];
|
||||
struct ide_taskfile *t = &d->taskfile;
|
||||
|
||||
if (r != ide_devctrl_w && (t->status & ST_BSY)) {
|
||||
ide_fault(d, "command written while busy");
|
||||
return;
|
||||
}
|
||||
if (r == ide_data)
|
||||
ide_data_out(d, ntohs(v), 2);
|
||||
else
|
||||
ide_write8(c, r, v);
|
||||
}
|
||||
|
||||
/*
|
||||
* Allocate a new IDE controller emulation
|
||||
*/
|
||||
struct ide_controller *ide_allocate(const char *name)
|
||||
{
|
||||
struct ide_controller *c = calloc(1, sizeof(*c));
|
||||
if (c == NULL)
|
||||
return NULL;
|
||||
c->name = strdup(name);
|
||||
if (c->name == NULL) {
|
||||
free(c);
|
||||
return NULL;
|
||||
}
|
||||
c->drive[0].controller = c;
|
||||
c->drive[1].controller = c;
|
||||
c->drive[0].taskfile.drive = &c->drive[0];
|
||||
c->drive[1].taskfile.drive = &c->drive[1];
|
||||
return c;
|
||||
}
|
||||
|
||||
/*
|
||||
* Attach a file to a device on the controller
|
||||
*/
|
||||
int ide_attach(struct ide_controller *c, int drive, int fd)
|
||||
{
|
||||
struct ide_drive *d = &c->drive[drive];
|
||||
if (d->present) {
|
||||
ide_fault(d, "double attach");
|
||||
return -1;
|
||||
}
|
||||
d->fd = fd;
|
||||
if (read(d->fd, d->data, 512) != 512 ||
|
||||
read(d->fd, d->identify, 512) != 512) {
|
||||
ide_fault(d, "i/o error on attach");
|
||||
return -1;
|
||||
}
|
||||
if (memcmp(d->data, ide_magic, 8)) {
|
||||
ide_fault(d, "bad magic");
|
||||
return -1;
|
||||
}
|
||||
d->fd = fd;
|
||||
d->present = 1;
|
||||
d->heads = d->identify[3];
|
||||
d->sectors = d->identify[6];
|
||||
d->cylinders = le16(d->identify[1]);
|
||||
if (d->identify[49] & le16(1 << 9))
|
||||
d->lba = 1;
|
||||
else
|
||||
d->lba = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Detach an IDE device from the interface (not hot pluggable)
|
||||
*/
|
||||
void ide_detach(struct ide_drive *d)
|
||||
{
|
||||
close(d->fd);
|
||||
d->fd = -1;
|
||||
d->present = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Free up and release and IDE controller
|
||||
*/
|
||||
void ide_free(struct ide_controller *c)
|
||||
{
|
||||
if (c->drive[0].present)
|
||||
ide_detach(&c->drive[0]);
|
||||
if (c->drive[1].present)
|
||||
ide_detach(&c->drive[1]);
|
||||
free((void *)c->name);
|
||||
free(c);
|
||||
}
|
||||
|
||||
/*
|
||||
* Emulation interface for an 8bit controller using latches on the
|
||||
* data register
|
||||
*/
|
||||
uint8_t ide_read_latched(struct ide_controller *c, uint8_t reg)
|
||||
{
|
||||
uint16_t v;
|
||||
if (reg == ide_data_latch)
|
||||
return c->data_latch;
|
||||
v = ide_read16(c, reg);
|
||||
if (reg == ide_data) {
|
||||
c->data_latch = v >> 8;
|
||||
v &= 0xFF;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
void ide_write_latched(struct ide_controller *c, uint8_t reg, uint8_t v)
|
||||
{
|
||||
uint16_t d = v;
|
||||
|
||||
if (reg == ide_data_latch) {
|
||||
c->data_latch = v;
|
||||
return;
|
||||
}
|
||||
if (reg == ide_data)
|
||||
d |= (c->data_latch << 8);
|
||||
ide_write16(c, reg, d);
|
||||
}
|
||||
|
||||
static void make_ascii(uint16_t *p, const char *t, int len)
|
||||
{
|
||||
int i;
|
||||
char *d = (char *)p;
|
||||
strncpy(d, t, len);
|
||||
|
||||
for (i = 0; i < len; i += 2) {
|
||||
char c = *d;
|
||||
*d = d[1];
|
||||
d[1] = c;
|
||||
d += 2;
|
||||
}
|
||||
}
|
||||
|
||||
static void make_serial(uint16_t *p)
|
||||
{
|
||||
char buf[21];
|
||||
srand(getpid()^time(NULL));
|
||||
snprintf(buf, 21, "%08d%08d%04d", rand(), rand(), rand());
|
||||
make_ascii(p, buf, 20);
|
||||
}
|
||||
|
||||
int ide_make_drive(uint8_t type, int fd)
|
||||
{
|
||||
uint8_t s, h;
|
||||
uint16_t c;
|
||||
uint32_t sectors;
|
||||
uint16_t ident[256];
|
||||
|
||||
if (type < 1 || type > MAX_DRIVE_TYPE)
|
||||
return -2;
|
||||
|
||||
memset(ident, 0, 512);
|
||||
memcpy(ident, ide_magic, 8);
|
||||
if (write(fd, ident, 512) != 512)
|
||||
return -1;
|
||||
|
||||
memset(ident, 0, 8);
|
||||
ident[0] = le16((1 << 15) | (1 << 6)); /* Non removable */
|
||||
make_serial(ident + 10);
|
||||
ident[47] = 0; /* no read multi for now */
|
||||
ident[51] = le16(240 /* PIO2 */ << 8); /* PIO cycle time */
|
||||
ident[53] = le16(1); /* Geometry words are valid */
|
||||
|
||||
switch(type) {
|
||||
case ACME_ROADRUNNER:
|
||||
/* 504MB drive with LBA support */
|
||||
c = 1024;
|
||||
h = 16;
|
||||
s = 63;
|
||||
make_ascii(ident + 23, "A001.001", 8);
|
||||
make_ascii(ident + 27, "ACME ROADRUNNER v0.1", 40);
|
||||
ident[49] = le16(1 << 9); /* LBA */
|
||||
break;
|
||||
case ACME_ULTRASONICUS:
|
||||
/* 40MB drive with LBA support */
|
||||
c = 977;
|
||||
h = 5;
|
||||
s = 16;
|
||||
ident[49] = le16(1 << 9); /* LBA */
|
||||
make_ascii(ident + 23, "A001.001", 8);
|
||||
make_ascii(ident + 27, "ACME ULTRASONICUS AD INFINITUM v0.1", 40);
|
||||
break;
|
||||
case ACME_NEMESIS:
|
||||
/* 20MB drive with LBA support */
|
||||
c = 615;
|
||||
h = 4;
|
||||
s = 16;
|
||||
ident[49] = le16(1 << 9); /* LBA */
|
||||
make_ascii(ident + 23, "A001.001", 8);
|
||||
make_ascii(ident + 27, "ACME NEMESIS RIDICULII v0.1", 40);
|
||||
break;
|
||||
case ACME_COYOTE:
|
||||
/* 20MB drive without LBA support */
|
||||
c = 615;
|
||||
h = 4;
|
||||
s = 16;
|
||||
make_ascii(ident + 23, "A001.001", 8);
|
||||
make_ascii(ident + 27, "ACME COYOTE v0.1", 40);
|
||||
break;
|
||||
case ACME_ACCELLERATTI:
|
||||
c = 1024;
|
||||
h = 16;
|
||||
s = 16;
|
||||
ident[49] = le16(1 << 9); /* LBA */
|
||||
make_ascii(ident + 23, "A001.001", 8);
|
||||
make_ascii(ident + 27, "ACME ACCELLERATTI INCREDIBILUS v0.1", 40);
|
||||
break;
|
||||
case ACME_ZIPPIBUS:
|
||||
c = 1024;
|
||||
h = 16;
|
||||
s = 32;
|
||||
ident[49] = le16(1 << 9); /* LBA */
|
||||
make_ascii(ident + 23, "A001.001", 8);
|
||||
make_ascii(ident + 27, "ACME ZIPPIBUS v0.1", 40);
|
||||
break;
|
||||
}
|
||||
ident[1] = le16(c);
|
||||
ident[3] = le16(h);
|
||||
ident[6] = le16(s);
|
||||
ident[54] = ident[1];
|
||||
ident[55] = ident[3];
|
||||
ident[56] = ident[6];
|
||||
sectors = c * h * s;
|
||||
ident[57] = le16(sectors & 0xFFFF);
|
||||
ident[58] = le16(sectors >> 16);
|
||||
ident[60] = ident[57];
|
||||
ident[61] = ident[58];
|
||||
if (write(fd, ident, 512) != 512)
|
||||
return -1;
|
||||
|
||||
memset(ident, 0xE5, 512);
|
||||
while(sectors--)
|
||||
if (write(fd, ident, 512) != 512)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
83
ide.h
Normal file
83
ide.h
Normal file
@@ -0,0 +1,83 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#define ACME_ROADRUNNER 1 /* 504MB classic IDE drive */
|
||||
#define ACME_COYOTE 2 /* 20MB early IDE drive */
|
||||
#define ACME_NEMESIS 3 /* 20MB LBA capable drive */
|
||||
#define ACME_ULTRASONICUS 4 /* 40MB LBA capable drive */
|
||||
#define ACME_ACCELLERATTI 5 /* 128MB LBA capable drive */
|
||||
#define ACME_ZIPPIBUS 6 /* 256MB LBA capable drive */
|
||||
|
||||
#define MAX_DRIVE_TYPE 6
|
||||
|
||||
#define ide_data 0
|
||||
#define ide_error_r 1
|
||||
#define ide_feature_w 1
|
||||
#define ide_sec_count 2
|
||||
#define ide_sec_num 3
|
||||
#define ide_lba_low 3
|
||||
#define ide_cyl_low 4
|
||||
#define ide_lba_mid 4
|
||||
#define ide_cyl_hi 5
|
||||
#define ide_lba_hi 5
|
||||
#define ide_dev_head 6
|
||||
#define ide_lba_top 6
|
||||
#define ide_status_r 7
|
||||
#define ide_command_w 7
|
||||
#define ide_altst_r 8
|
||||
#define ide_devctrl_w 8
|
||||
#define ide_data_latch 9
|
||||
|
||||
struct ide_taskfile {
|
||||
uint16_t data;
|
||||
uint8_t error;
|
||||
uint8_t feature;
|
||||
uint8_t count;
|
||||
uint8_t lba1;
|
||||
uint8_t lba2;
|
||||
uint8_t lba3;
|
||||
uint8_t lba4;
|
||||
uint8_t status;
|
||||
uint8_t command;
|
||||
uint8_t devctrl;
|
||||
struct ide_drive *drive;
|
||||
};
|
||||
|
||||
struct ide_drive {
|
||||
struct ide_controller *controller;
|
||||
struct ide_taskfile taskfile;
|
||||
unsigned int present:1, intrq:1, failed:1, lba:1, eightbit:1;
|
||||
uint16_t cylinders;
|
||||
uint8_t heads, sectors;
|
||||
uint8_t data[512];
|
||||
uint16_t identify[256];
|
||||
uint8_t *dptr;
|
||||
int state;
|
||||
int fd;
|
||||
off_t offset;
|
||||
int length;
|
||||
};
|
||||
|
||||
struct ide_controller {
|
||||
struct ide_drive drive[2];
|
||||
int selected;
|
||||
const char *name;
|
||||
uint16_t data_latch;
|
||||
};
|
||||
|
||||
//extern ide_controller idectrl;
|
||||
extern const uint8_t ide_magic[8];
|
||||
|
||||
void ide_reset_begin(struct ide_controller *c);
|
||||
uint8_t ide_read8(struct ide_controller *c, uint8_t r);
|
||||
void ide_write8(struct ide_controller *c, uint8_t r, uint8_t v);
|
||||
uint16_t ide_read16(struct ide_controller *c, uint8_t r);
|
||||
void ide_write16(struct ide_controller *c, uint8_t r, uint16_t v);
|
||||
uint8_t ide_read_latched(struct ide_controller *c, uint8_t r);
|
||||
void ide_write_latched(struct ide_controller *c, uint8_t r, uint8_t v);
|
||||
|
||||
struct ide_controller *ide_allocate(const char *name);
|
||||
int ide_attach(struct ide_controller *c, int drive, int fd);
|
||||
void ide_detach(struct ide_drive *d);
|
||||
void ide_free(struct ide_controller *c);
|
||||
|
||||
int ide_make_drive(uint8_t type, int fd);
|
||||
BIN
ide/.hd0.img.swp
Normal file
BIN
ide/.hd0.img.swp
Normal file
Binary file not shown.
934
ide/ide.c
Normal file
934
ide/ide.c
Normal file
@@ -0,0 +1,934 @@
|
||||
/*
|
||||
* IDE Emulation Layer for retro-style PIO interfaces
|
||||
*
|
||||
* (c) Copyright Alan Cox, 2015-2019
|
||||
*
|
||||
* IDE-emu is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* IDE-emu is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with IDE-emu. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <time.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include "ide.h"
|
||||
|
||||
#define IDE_IDLE 0
|
||||
#define IDE_CMD 1
|
||||
#define IDE_DATA_IN 2
|
||||
#define IDE_DATA_OUT 3
|
||||
|
||||
#define DCR_NIEN 2
|
||||
#define DCR_SRST 4
|
||||
|
||||
#define DEVH_HEAD 15
|
||||
#define DEVH_DEV 16
|
||||
#define DEVH_LBA 64
|
||||
|
||||
#define ERR_AMNF 1
|
||||
#define ERR_TKNONF 2
|
||||
#define ERR_ABRT 4
|
||||
#define ERR_MCR 8
|
||||
#define ERR_IDNF 16
|
||||
#define ERR_MC 32
|
||||
#define ERR_UNC 64
|
||||
|
||||
#define ST_ERR 1
|
||||
#define ST_IDX 2
|
||||
#define ST_CORR 4
|
||||
#define ST_DRQ 8
|
||||
#define ST_DSC 16
|
||||
#define ST_DF 32
|
||||
#define ST_DRDY 64
|
||||
#define ST_BSY 128
|
||||
|
||||
#define DCL_SRST 4
|
||||
#define DCL_NIEN 2
|
||||
|
||||
#define IDE_CMD_CALIB 0x10
|
||||
#define IDE_CMD_READ 0x20
|
||||
#define IDE_CMD_READ_NR 0x21
|
||||
#define IDE_CMD_WRITE 0x30
|
||||
#define IDE_CMD_WRITE_NR 0x31
|
||||
#define IDE_CMD_VERIFY 0x40
|
||||
#define IDE_CMD_VERIFY_NR 0x41
|
||||
#define IDE_CMD_SEEK 0x70
|
||||
#define IDE_CMD_EDD 0x90
|
||||
#define IDE_CMD_INTPARAMS 0x91
|
||||
#define IDE_CMD_IDENTIFY 0xEC
|
||||
#define IDE_CMD_SETFEATURES 0xEF
|
||||
|
||||
const uint8_t ide_magic[8] = {
|
||||
'1','D','E','D','1','5','C','0'
|
||||
};
|
||||
|
||||
static char *charmap(uint8_t v)
|
||||
{
|
||||
static char cbuf[3];
|
||||
if (v < 32)
|
||||
sprintf(cbuf, "^%c", '@'+v);
|
||||
else if (v < 127)
|
||||
sprintf(cbuf, " %c", v);
|
||||
else if (v == 127)
|
||||
sprintf(cbuf, "DL");
|
||||
else if (v < 160)
|
||||
sprintf(cbuf, ":%c", '@' + v - 128);
|
||||
else if (v < 255)
|
||||
sprintf(cbuf, "~%c", v - 128);
|
||||
else
|
||||
sprintf(cbuf, "!D");
|
||||
return cbuf;
|
||||
}
|
||||
|
||||
static void hexdump(uint8_t *bp)
|
||||
{
|
||||
int i,j;
|
||||
for (i = 0; i < 512; i+= 16) {
|
||||
for(j = 0; j < 16; j++)
|
||||
fprintf(stderr, "%02X ", bp[i+j]);
|
||||
fprintf(stderr, "|");
|
||||
for(j = 0; j < 16; j++)
|
||||
fprintf(stderr, "%2s", charmap(bp[i+j]));
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
}
|
||||
|
||||
/* FIXME: use proper endian convertors! */
|
||||
static uint16_t le16(uint16_t v)
|
||||
{
|
||||
uint8_t *p = (uint8_t *)&v;
|
||||
return p[0] | (p[1] << 8);
|
||||
}
|
||||
|
||||
static void ide_xlate_errno(struct ide_taskfile *t, int len)
|
||||
{
|
||||
t->status |= ST_ERR;
|
||||
if (len == -1) {
|
||||
if (errno == EIO)
|
||||
t->error = ERR_UNC;
|
||||
else
|
||||
t->error = ERR_AMNF;
|
||||
} else
|
||||
t->error = ERR_AMNF;
|
||||
}
|
||||
|
||||
static void ide_fault(struct ide_drive *d, const char *p)
|
||||
{
|
||||
fprintf(stderr, "ide: %s: %d: %s\n", d->controller->name,
|
||||
(int)(d - d->controller->drive), p);
|
||||
}
|
||||
|
||||
/* Disk translation */
|
||||
static off_t xlate_block(struct ide_taskfile *t)
|
||||
{
|
||||
struct ide_drive *d = t->drive;
|
||||
uint16_t cyl;
|
||||
|
||||
if (t->lba4 & DEVH_LBA) {
|
||||
/* fprintf(stderr, "XLATE LBA %02X:%02X:%02X:%02X\n",
|
||||
t->lba4, t->lba3, t->lba2, t->lba1);*/
|
||||
if (d->lba)
|
||||
return 2 + (((t->lba4 & DEVH_HEAD) << 24) | (t->lba3 << 16) | (t->lba2 << 8) | t->lba1);
|
||||
ide_fault(d, "LBA on non LBA drive");
|
||||
}
|
||||
|
||||
/* Some well known software asks for 0/0/0 when it means 0/0/1. Drives appear
|
||||
to interpret sector 0 as sector 1 */
|
||||
if (t->lba1 == 0) {
|
||||
fprintf(stderr, "[Bug: request for sector offset 0].\n");
|
||||
t->lba1 = 1;
|
||||
}
|
||||
cyl = (t->lba3 << 8) | t->lba2;
|
||||
/* fprintf(stderr, "(H %d C %d S %d)\n", t->lba4 & DEVH_HEAD, cyl, t->lba1); */
|
||||
if (t->lba1 == 0 || t->lba1 > d->sectors || t->lba4 >= d->heads || cyl >= d->cylinders) {
|
||||
return -1;
|
||||
}
|
||||
/* Sector 1 is first */
|
||||
/* Images generally go cylinder/head/sector. This also matters if we ever
|
||||
implement more advanced geometry setting */
|
||||
return 1 + ((cyl * d->heads) + (t->lba4 & DEVH_HEAD)) * d->sectors + t->lba1;
|
||||
}
|
||||
|
||||
/* Indicate the drive is ready */
|
||||
static void ready(struct ide_taskfile *tf)
|
||||
{
|
||||
tf->status &= ~(ST_BSY|ST_DRQ);
|
||||
tf->status |= ST_DRDY;
|
||||
tf->drive->state = IDE_IDLE;
|
||||
}
|
||||
|
||||
/* Return to idle state, completing a command */
|
||||
static void completed(struct ide_taskfile *tf)
|
||||
{
|
||||
ready(tf);
|
||||
tf->drive->intrq = 1;
|
||||
}
|
||||
|
||||
static void drive_failed(struct ide_taskfile *tf)
|
||||
{
|
||||
tf->status |= ST_ERR;
|
||||
tf->error = ERR_IDNF;
|
||||
ready(tf);
|
||||
}
|
||||
|
||||
static void data_in_state(struct ide_taskfile *tf)
|
||||
{
|
||||
struct ide_drive *d = tf->drive;
|
||||
d->state = IDE_DATA_IN;
|
||||
d->dptr = d->data + 512;
|
||||
/* We don't clear DRDY here, drives may well accept a command at this
|
||||
point and at least one firmware for RC2014 assumes this */
|
||||
tf->status &= ~ST_BSY;
|
||||
tf->status |= ST_DRQ;
|
||||
d->intrq = 1; /* Double check */
|
||||
}
|
||||
|
||||
static void data_out_state(struct ide_taskfile *tf)
|
||||
{
|
||||
struct ide_drive *d = tf->drive;
|
||||
d->state = IDE_DATA_OUT;
|
||||
d->dptr = d->data;
|
||||
tf->status &= ~ (ST_BSY|ST_DRDY);
|
||||
tf->status |= ST_DRQ;
|
||||
d->intrq = 1; /* Double check */
|
||||
}
|
||||
|
||||
static void edd_setup(struct ide_taskfile *tf)
|
||||
{
|
||||
tf->error = 0x01; /* All good */
|
||||
tf->lba1 = 0x01; /* EDD always updates drive 0 */
|
||||
tf->lba2 = 0x00;
|
||||
tf->lba3 = 0x00;
|
||||
tf->lba4 = 0x00;
|
||||
tf->count = 0x01;
|
||||
ready(tf);
|
||||
}
|
||||
|
||||
void ide_reset(struct ide_controller *c)
|
||||
{
|
||||
if (c->drive[0].present) {
|
||||
edd_setup(&c->drive[0].taskfile);
|
||||
/* A drive could clear busy then set DRDY up to 2 minutes later if its
|
||||
mindnumbingly slow to start up ! We don't emulate any of that */
|
||||
c->drive[0].taskfile.status = ST_DRDY;
|
||||
c->drive[0].eightbit = 0;
|
||||
}
|
||||
if (c->drive[1].present) {
|
||||
edd_setup(&c->drive[1].taskfile);
|
||||
c->drive[1].taskfile.status = ST_DRDY;
|
||||
c->drive[1].eightbit = 0;
|
||||
}
|
||||
c->selected = 0;
|
||||
}
|
||||
|
||||
void ide_reset_begin(struct ide_controller *c)
|
||||
{
|
||||
if (c->drive[0].present)
|
||||
c->drive[0].taskfile.status |= ST_BSY;
|
||||
if (c->drive[1].present)
|
||||
c->drive[1].taskfile.status |= ST_BSY;
|
||||
/* Ought to be a time delay relative to reset or power on */
|
||||
ide_reset(c);
|
||||
}
|
||||
|
||||
static void ide_srst_begin(struct ide_controller *c)
|
||||
{
|
||||
ide_reset(c);
|
||||
if (c->drive[0].present)
|
||||
c->drive[0].taskfile.status |= ST_BSY;
|
||||
if (c->drive[1].present)
|
||||
c->drive[1].taskfile.status |= ST_BSY;
|
||||
}
|
||||
|
||||
static void ide_srst_end(struct ide_controller *c)
|
||||
{
|
||||
/* Could be time delays here */
|
||||
ready(&c->drive[0].taskfile);
|
||||
ready(&c->drive[1].taskfile);
|
||||
}
|
||||
|
||||
static void cmd_edd_complete(struct ide_taskfile *tf)
|
||||
{
|
||||
struct ide_controller *c = tf->drive->controller;
|
||||
if (c->drive[0].present)
|
||||
edd_setup(&c->drive[0].taskfile);
|
||||
if (c->drive[1].present)
|
||||
edd_setup(&c->drive[1].taskfile);
|
||||
c->selected = 0;
|
||||
}
|
||||
|
||||
static void cmd_identify_complete(struct ide_taskfile *tf)
|
||||
{
|
||||
struct ide_drive *d = tf->drive;
|
||||
memcpy(d->data, d->identify, 512);
|
||||
data_in_state(tf);
|
||||
/* Arrange to copy just the identify buffer */
|
||||
d->dptr = d->data;
|
||||
d->length = 1;
|
||||
}
|
||||
|
||||
static void cmd_initparam_complete(struct ide_taskfile *tf)
|
||||
{
|
||||
struct ide_drive *d = tf->drive;
|
||||
/* We only support the current mapping */
|
||||
if (tf->count != d->sectors || (tf->lba4 & DEVH_HEAD) + 1 != d->heads) {
|
||||
tf->status |= ST_ERR;
|
||||
tf->error |= ERR_ABRT;
|
||||
tf->drive->failed = 1; /* Report ID NF until fixed */
|
||||
/* fprintf(stderr, "geo is %d %d, asked for %d %d\n",
|
||||
d->sectors, d->heads, tf->count, (tf->lba4 & DEVH_HEAD) + 1); */
|
||||
ide_fault(d, "invalid geometry");
|
||||
} else if (tf->drive->failed == 1)
|
||||
tf->drive->failed = 0; /* Valid translation */
|
||||
completed(tf);
|
||||
}
|
||||
|
||||
static void cmd_readsectors_complete(struct ide_taskfile *tf)
|
||||
{
|
||||
struct ide_drive *d = tf->drive;
|
||||
/* Move to data xfer */
|
||||
if (d->failed) {
|
||||
drive_failed(tf);
|
||||
return;
|
||||
}
|
||||
d->offset = xlate_block(tf);
|
||||
/* DRDY is not guaranteed here but at least one buggy RC2014 firmware
|
||||
expects it */
|
||||
tf->status |= ST_DRQ | ST_DSC | ST_DRDY;
|
||||
tf->status &= ~ST_BSY;
|
||||
/* 0 = 256 sectors */
|
||||
d->length = tf->count ? tf->count : 256;
|
||||
/* fprintf(stderr, "READ %d SECTORS @ %ld\n", d->length, d->offset); */
|
||||
if (d->offset == -1 || lseek(d->fd, 512 * d->offset, SEEK_SET) == -1) {
|
||||
tf->status |= ST_ERR;
|
||||
tf->status &= ~ST_DSC;
|
||||
tf->error |= ERR_IDNF;
|
||||
/* return null data */
|
||||
completed(tf);
|
||||
return;
|
||||
}
|
||||
/* do the xfer */
|
||||
data_in_state(tf);
|
||||
}
|
||||
|
||||
static void cmd_verifysectors_complete(struct ide_taskfile *tf)
|
||||
{
|
||||
struct ide_drive *d = tf->drive;
|
||||
/* Move to data xfer */
|
||||
if (d->failed) {
|
||||
drive_failed(tf);
|
||||
return;
|
||||
}
|
||||
d->offset = xlate_block(tf);
|
||||
/* 0 = 256 sectors */
|
||||
d->length = tf->count ? tf->count : 256;
|
||||
if (d->offset == -1 || lseek(d->fd, 512 * (d->offset + d->length - 1), SEEK_SET) == -1) {
|
||||
tf->status &= ~ST_DSC;
|
||||
tf->status |= ST_ERR;
|
||||
tf->error |= ERR_IDNF;
|
||||
}
|
||||
tf->status |= ST_DSC;
|
||||
completed(tf);
|
||||
}
|
||||
|
||||
static void cmd_recalibrate_complete(struct ide_taskfile *tf)
|
||||
{
|
||||
struct ide_drive *d = tf->drive;
|
||||
if (d->failed)
|
||||
drive_failed(tf);
|
||||
if (d->offset == -1 || xlate_block(tf) != 0L) {
|
||||
tf->status &= ~ST_DSC;
|
||||
tf->status |= ST_ERR;
|
||||
tf->error |= ERR_ABRT;
|
||||
}
|
||||
tf->status |= ST_DSC;
|
||||
completed(tf);
|
||||
}
|
||||
|
||||
static void cmd_seek_complete(struct ide_taskfile *tf)
|
||||
{
|
||||
struct ide_drive *d = tf->drive;
|
||||
if (d->failed)
|
||||
drive_failed(tf);
|
||||
d->offset = xlate_block(tf);
|
||||
if (d->offset == -1 || lseek(d->fd, 512 * d->offset, SEEK_SET) == -1) {
|
||||
tf->status &= ~ST_DSC;
|
||||
tf->status |= ST_ERR;
|
||||
tf->error |= ERR_IDNF;
|
||||
}
|
||||
tf->status |= ST_DSC;
|
||||
completed(tf);
|
||||
}
|
||||
|
||||
static void cmd_setfeatures_complete(struct ide_taskfile *tf)
|
||||
{
|
||||
struct ide_drive *d = tf->drive;
|
||||
switch(tf->feature) {
|
||||
case 0x01:
|
||||
d->eightbit = 1;
|
||||
break;
|
||||
case 0x03:
|
||||
if ((tf->count & 0xF0) >= 0x20) {
|
||||
tf->status |= ST_ERR;
|
||||
tf->error |= ERR_ABRT;
|
||||
}
|
||||
/* Silently accept PIO mode settings */
|
||||
break;
|
||||
case 0x81:
|
||||
d->eightbit = 0;
|
||||
break;
|
||||
default:
|
||||
tf->status |= ST_ERR;
|
||||
tf->error |= ERR_ABRT;
|
||||
}
|
||||
completed(tf);
|
||||
}
|
||||
|
||||
static void cmd_writesectors_complete(struct ide_taskfile *tf)
|
||||
{
|
||||
struct ide_drive *d = tf->drive;
|
||||
/* Move to data xfer */
|
||||
if (d->failed) {
|
||||
drive_failed(tf);
|
||||
return;
|
||||
}
|
||||
d->offset = xlate_block(tf);
|
||||
tf->status |= ST_DRQ;
|
||||
/* 0 = 256 sectors */
|
||||
d->length = tf->count ? tf->count : 256;
|
||||
/* fprintf(stderr, "WRITE %d SECTORS @ %ld\n", d->length, d->offset); */
|
||||
if (d->offset == -1 || lseek(d->fd, 512 * d->offset, SEEK_SET) == -1) {
|
||||
tf->status |= ST_ERR;
|
||||
tf->error |= ERR_IDNF;
|
||||
tf->status &= ~ST_DSC;
|
||||
/* return null data */
|
||||
completed(tf);
|
||||
return;
|
||||
}
|
||||
/* do the xfer */
|
||||
data_out_state(tf);
|
||||
}
|
||||
|
||||
static void ide_set_error(struct ide_drive *d)
|
||||
{
|
||||
d->taskfile.lba4 &= ~DEVH_HEAD;
|
||||
|
||||
if (d->taskfile.lba4 & DEVH_LBA) {
|
||||
d->taskfile.lba1 = d->offset & 0xFF;
|
||||
d->taskfile.lba2 = (d->offset >> 8) & 0xFF;
|
||||
d->taskfile.lba3 = (d->offset >> 16) & 0xFF;
|
||||
d->taskfile.lba4 |= (d->offset >> 24) & DEVH_HEAD;
|
||||
} else {
|
||||
d->taskfile.lba1 = d->offset % d->sectors + 1;
|
||||
d->offset /= d->sectors;
|
||||
d->taskfile.lba4 |= d->offset / (d->cylinders * d->sectors);
|
||||
d->offset %= (d->cylinders * d->sectors);
|
||||
d->taskfile.lba2 = d->offset & 0xFF;
|
||||
d->taskfile.lba3 = (d->offset >> 8) & 0xFF;
|
||||
}
|
||||
d->taskfile.count = d->length;
|
||||
d->taskfile.status |= ST_ERR;
|
||||
d->state = IDE_IDLE;
|
||||
completed(&d->taskfile);
|
||||
}
|
||||
|
||||
static int ide_read_sector(struct ide_drive *d)
|
||||
{
|
||||
int len;
|
||||
|
||||
d->dptr = d->data;
|
||||
if ((len = read(d->fd, d->data, 512)) != 512) {
|
||||
perror("ide_read_sector");
|
||||
d->taskfile.status |= ST_ERR;
|
||||
d->taskfile.status &= ~ST_DSC;
|
||||
ide_xlate_errno(&d->taskfile, len);
|
||||
return -1;
|
||||
}
|
||||
// hexdump(d->data);
|
||||
d->offset += 512;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ide_write_sector(struct ide_drive *d)
|
||||
{
|
||||
int len;
|
||||
|
||||
d->dptr = d->data;
|
||||
if ((len = write(d->fd, d->data, 512)) != 512) {
|
||||
d->taskfile.status |= ST_ERR;
|
||||
d->taskfile.status &= ~ST_DSC;
|
||||
ide_xlate_errno(&d->taskfile, len);
|
||||
return -1;
|
||||
}
|
||||
// hexdump(d->data);
|
||||
d->offset += 512;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint16_t ide_data_in(struct ide_drive *d, int len)
|
||||
{
|
||||
uint16_t v;
|
||||
if (d->state == IDE_DATA_IN) {
|
||||
if (d->dptr == d->data + 512) {
|
||||
if (ide_read_sector(d) < 0) {
|
||||
ide_set_error(d); /* Set the LBA or CHS etc */
|
||||
return 0xFFFF; /* and error bits set by read_sector */
|
||||
}
|
||||
}
|
||||
v = *d->dptr;
|
||||
if (!d->eightbit) {
|
||||
if (len == 2)
|
||||
v |= (d->dptr[1] << 8);
|
||||
d->dptr+=2;
|
||||
} else
|
||||
d->dptr++;
|
||||
d->taskfile.data = v;
|
||||
if (d->dptr == d->data + 512) {
|
||||
d->length--;
|
||||
d->intrq = 1; /* we don't yet emulate multimode */
|
||||
if (d->length == 0) {
|
||||
d->state = IDE_IDLE;
|
||||
completed(&d->taskfile);
|
||||
}
|
||||
}
|
||||
} else
|
||||
ide_fault(d, "bad data read");
|
||||
|
||||
if (len == 1)
|
||||
return d->taskfile.data & 0xFF;
|
||||
return d->taskfile.data;
|
||||
}
|
||||
|
||||
static void ide_data_out(struct ide_drive *d, uint16_t v, int len)
|
||||
{
|
||||
if (d->state != IDE_DATA_OUT) {
|
||||
ide_fault(d, "bad data write");
|
||||
d->taskfile.data = v;
|
||||
} else {
|
||||
if (d->eightbit)
|
||||
v &= 0xFF;
|
||||
*d->dptr++ = v;
|
||||
d->taskfile.data = v;
|
||||
if (!d->eightbit) {
|
||||
*d->dptr++ = v >> 8;
|
||||
d->taskfile.data = v >> 8;
|
||||
}
|
||||
if (d->dptr == d->data + 512) {
|
||||
if (ide_write_sector(d) < 0) {
|
||||
ide_set_error(d);
|
||||
return;
|
||||
}
|
||||
d->length--;
|
||||
d->intrq = 1;
|
||||
if (d->length == 0) {
|
||||
d->state = IDE_IDLE;
|
||||
d->taskfile.status |= ST_DSC;
|
||||
completed(&d->taskfile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ide_issue_command(struct ide_taskfile *t)
|
||||
{
|
||||
t->status &= ~(ST_ERR|ST_DRDY);
|
||||
t->status |= ST_BSY;
|
||||
t->error = 0;
|
||||
t->drive->state = IDE_CMD;
|
||||
|
||||
/* We could complete with delays but don't do so yet */
|
||||
switch(t->command) {
|
||||
case IDE_CMD_EDD: /* 0x90 */
|
||||
cmd_edd_complete(t);
|
||||
break;
|
||||
case IDE_CMD_IDENTIFY: /* 0xEC */
|
||||
cmd_identify_complete(t);
|
||||
break;
|
||||
case IDE_CMD_INTPARAMS: /* 0x91 */
|
||||
cmd_initparam_complete(t);
|
||||
break;
|
||||
case IDE_CMD_READ: /* 0x20 */
|
||||
case IDE_CMD_READ_NR: /* 0x21 */
|
||||
cmd_readsectors_complete(t);
|
||||
break;
|
||||
case IDE_CMD_SETFEATURES: /* 0xEF */
|
||||
cmd_setfeatures_complete(t);
|
||||
break;
|
||||
case IDE_CMD_VERIFY: /* 0x40 */
|
||||
case IDE_CMD_VERIFY_NR: /* 0x41 */
|
||||
cmd_verifysectors_complete(t);
|
||||
break;
|
||||
case IDE_CMD_WRITE: /* 0x30 */
|
||||
case IDE_CMD_WRITE_NR: /* 0x31 */
|
||||
cmd_writesectors_complete(t);
|
||||
break;
|
||||
default:
|
||||
if ((t->command & 0xF0) == IDE_CMD_CALIB) /* 1x */
|
||||
cmd_recalibrate_complete(t);
|
||||
else if ((t->command & 0xF0) == IDE_CMD_SEEK) /* 7x */
|
||||
cmd_seek_complete(t);
|
||||
else {
|
||||
/* Unknown */
|
||||
t->status |= ST_ERR;
|
||||
t->error |= ERR_ABRT;
|
||||
completed(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 8bit IDE controller emulation
|
||||
*/
|
||||
|
||||
uint8_t ide_read8(struct ide_controller *c, uint8_t r)
|
||||
{
|
||||
struct ide_drive *d = &c->drive[c->selected];
|
||||
struct ide_taskfile *t = &d->taskfile;
|
||||
switch(r) {
|
||||
case ide_data:
|
||||
return ide_data_in(d, 1);
|
||||
case ide_error_r:
|
||||
return t->error;
|
||||
case ide_sec_count:
|
||||
return t->count;
|
||||
case ide_lba_low:
|
||||
return t->lba1;
|
||||
case ide_lba_mid:
|
||||
return t->lba2;
|
||||
case ide_lba_hi:
|
||||
return t->lba3;
|
||||
case ide_lba_top:
|
||||
return t->lba4;
|
||||
case ide_status_r:
|
||||
d->intrq = 0; /* Acked */
|
||||
case ide_altst_r:
|
||||
return t->status;
|
||||
default:
|
||||
ide_fault(d, "bogus register");
|
||||
return 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
void ide_write8(struct ide_controller *c, uint8_t r, uint8_t v)
|
||||
{
|
||||
struct ide_drive *d = &c->drive[c->selected];
|
||||
struct ide_taskfile *t = &d->taskfile;
|
||||
|
||||
if (r != ide_devctrl_w) {
|
||||
if (t->status & ST_BSY) {
|
||||
ide_fault(d, "command written while busy");
|
||||
return;
|
||||
}
|
||||
/* Not clear this is the right emulation */
|
||||
if (d->present == 0 && r != ide_lba_top) {
|
||||
ide_fault(d, "not present");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
switch(r) {
|
||||
case ide_data:
|
||||
ide_data_out(d, v, 1);
|
||||
break;
|
||||
case ide_feature_w:
|
||||
t->feature = v;
|
||||
break;
|
||||
case ide_sec_count:
|
||||
t->count = v;
|
||||
break;
|
||||
case ide_lba_low:
|
||||
t->lba1 = v;
|
||||
break;
|
||||
case ide_lba_mid:
|
||||
t->lba2 = v;
|
||||
break;
|
||||
case ide_lba_hi:
|
||||
t->lba3 = v;
|
||||
break;
|
||||
case ide_lba_top:
|
||||
c->selected = (v & DEVH_DEV) ? 1 : 0;
|
||||
c->drive[c->selected].taskfile.lba4 = v & (DEVH_HEAD|DEVH_DEV|DEVH_LBA);
|
||||
break;
|
||||
case ide_command_w:
|
||||
t->command = v;
|
||||
ide_issue_command(t);
|
||||
break;
|
||||
case ide_devctrl_w:
|
||||
/* ATA: "When the Device Control register is written, both devices
|
||||
respond to the write regardless of which device is selected" */
|
||||
if ((v ^ t->devctrl) & DCL_SRST) {
|
||||
if (v & DCL_SRST)
|
||||
ide_srst_begin(c);
|
||||
else
|
||||
ide_srst_end(c);
|
||||
}
|
||||
c->drive[0].taskfile.devctrl = v; /* Check versus real h/w does this end up cleared */
|
||||
c->drive[1].taskfile.devctrl = v;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 16bit IDE controller emulation
|
||||
*/
|
||||
|
||||
uint16_t ide_read16(struct ide_controller *c, uint8_t r)
|
||||
{
|
||||
struct ide_drive *d = &c->drive[c->selected];
|
||||
if (r == ide_data)
|
||||
return htons(ide_data_in(d,2));
|
||||
return ide_read8(c, r);
|
||||
}
|
||||
|
||||
void ide_write16(struct ide_controller *c, uint8_t r, uint16_t v)
|
||||
{
|
||||
struct ide_drive *d = &c->drive[c->selected];
|
||||
struct ide_taskfile *t = &d->taskfile;
|
||||
|
||||
if (r != ide_devctrl_w && (t->status & ST_BSY)) {
|
||||
ide_fault(d, "command written while busy");
|
||||
return;
|
||||
}
|
||||
if (r == ide_data)
|
||||
ide_data_out(d, ntohs(v), 2);
|
||||
else
|
||||
ide_write8(c, r, v);
|
||||
}
|
||||
|
||||
/*
|
||||
* Allocate a new IDE controller emulation
|
||||
*/
|
||||
struct ide_controller *ide_allocate(const char *name)
|
||||
{
|
||||
struct ide_controller *c = calloc(1, sizeof(*c));
|
||||
if (c == NULL)
|
||||
return NULL;
|
||||
c->name = strdup(name);
|
||||
if (c->name == NULL) {
|
||||
free(c);
|
||||
return NULL;
|
||||
}
|
||||
c->drive[0].controller = c;
|
||||
c->drive[1].controller = c;
|
||||
c->drive[0].taskfile.drive = &c->drive[0];
|
||||
c->drive[1].taskfile.drive = &c->drive[1];
|
||||
return c;
|
||||
}
|
||||
|
||||
/*
|
||||
* Attach a file to a device on the controller
|
||||
*/
|
||||
int ide_attach(struct ide_controller *c, int drive, int fd)
|
||||
{
|
||||
struct ide_drive *d = &c->drive[drive];
|
||||
if (d->present) {
|
||||
ide_fault(d, "double attach");
|
||||
return -1;
|
||||
}
|
||||
d->fd = fd;
|
||||
if (read(d->fd, d->data, 512) != 512 ||
|
||||
read(d->fd, d->identify, 512) != 512) {
|
||||
ide_fault(d, "i/o error on attach");
|
||||
return -1;
|
||||
}
|
||||
if (memcmp(d->data, ide_magic, 8)) {
|
||||
ide_fault(d, "bad magic");
|
||||
return -1;
|
||||
}
|
||||
d->fd = fd;
|
||||
d->present = 1;
|
||||
d->heads = d->identify[3];
|
||||
d->sectors = d->identify[6];
|
||||
d->cylinders = le16(d->identify[1]);
|
||||
if (d->identify[49] & le16(1 << 9))
|
||||
d->lba = 1;
|
||||
else
|
||||
d->lba = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Detach an IDE device from the interface (not hot pluggable)
|
||||
*/
|
||||
void ide_detach(struct ide_drive *d)
|
||||
{
|
||||
close(d->fd);
|
||||
d->fd = -1;
|
||||
d->present = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Free up and release and IDE controller
|
||||
*/
|
||||
void ide_free(struct ide_controller *c)
|
||||
{
|
||||
if (c->drive[0].present)
|
||||
ide_detach(&c->drive[0]);
|
||||
if (c->drive[1].present)
|
||||
ide_detach(&c->drive[1]);
|
||||
free((void *)c->name);
|
||||
free(c);
|
||||
}
|
||||
|
||||
/*
|
||||
* Emulation interface for an 8bit controller using latches on the
|
||||
* data register
|
||||
*/
|
||||
uint8_t ide_read_latched(struct ide_controller *c, uint8_t reg)
|
||||
{
|
||||
uint16_t v;
|
||||
if (reg == ide_data_latch)
|
||||
return c->data_latch;
|
||||
v = ide_read16(c, reg);
|
||||
if (reg == ide_data) {
|
||||
c->data_latch = v >> 8;
|
||||
v &= 0xFF;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
void ide_write_latched(struct ide_controller *c, uint8_t reg, uint8_t v)
|
||||
{
|
||||
uint16_t d = v;
|
||||
|
||||
if (reg == ide_data_latch) {
|
||||
c->data_latch = v;
|
||||
return;
|
||||
}
|
||||
if (reg == ide_data)
|
||||
d |= (c->data_latch << 8);
|
||||
ide_write16(c, reg, d);
|
||||
}
|
||||
|
||||
static void make_ascii(uint16_t *p, const char *t, int len)
|
||||
{
|
||||
int i;
|
||||
char *d = (char *)p;
|
||||
strncpy(d, t, len);
|
||||
|
||||
for (i = 0; i < len; i += 2) {
|
||||
char c = *d;
|
||||
*d = d[1];
|
||||
d[1] = c;
|
||||
d += 2;
|
||||
}
|
||||
}
|
||||
|
||||
static void make_serial(uint16_t *p)
|
||||
{
|
||||
char buf[21];
|
||||
srand(getpid()^time(NULL));
|
||||
snprintf(buf, 21, "%08d%08d%04d", rand(), rand(), rand());
|
||||
make_ascii(p, buf, 20);
|
||||
}
|
||||
|
||||
int ide_make_drive(uint8_t type, int fd)
|
||||
{
|
||||
uint8_t s, h;
|
||||
uint16_t c;
|
||||
uint32_t sectors;
|
||||
uint16_t ident[256];
|
||||
|
||||
if (type < 1 || type > MAX_DRIVE_TYPE)
|
||||
return -2;
|
||||
|
||||
memset(ident, 0, 512);
|
||||
memcpy(ident, ide_magic, 8);
|
||||
if (write(fd, ident, 512) != 512)
|
||||
return -1;
|
||||
|
||||
memset(ident, 0, 8);
|
||||
ident[0] = le16((1 << 15) | (1 << 6)); /* Non removable */
|
||||
make_serial(ident + 10);
|
||||
ident[47] = 0; /* no read multi for now */
|
||||
ident[51] = le16(240 /* PIO2 */ << 8); /* PIO cycle time */
|
||||
ident[53] = le16(1); /* Geometry words are valid */
|
||||
|
||||
switch(type) {
|
||||
case ACME_ROADRUNNER:
|
||||
/* 504MB drive with LBA support */
|
||||
c = 1024;
|
||||
h = 16;
|
||||
s = 63;
|
||||
make_ascii(ident + 23, "A001.001", 8);
|
||||
make_ascii(ident + 27, "ACME ROADRUNNER v0.1", 40);
|
||||
ident[49] = le16(1 << 9); /* LBA */
|
||||
break;
|
||||
case ACME_ULTRASONICUS:
|
||||
/* 40MB drive with LBA support */
|
||||
c = 977;
|
||||
h = 5;
|
||||
s = 16;
|
||||
ident[49] = le16(1 << 9); /* LBA */
|
||||
make_ascii(ident + 23, "A001.001", 8);
|
||||
make_ascii(ident + 27, "ACME ULTRASONICUS AD INFINITUM v0.1", 40);
|
||||
break;
|
||||
case ACME_NEMESIS:
|
||||
/* 20MB drive with LBA support */
|
||||
c = 615;
|
||||
h = 4;
|
||||
s = 16;
|
||||
ident[49] = le16(1 << 9); /* LBA */
|
||||
make_ascii(ident + 23, "A001.001", 8);
|
||||
make_ascii(ident + 27, "ACME NEMESIS RIDICULII v0.1", 40);
|
||||
break;
|
||||
case ACME_COYOTE:
|
||||
/* 20MB drive without LBA support */
|
||||
c = 615;
|
||||
h = 4;
|
||||
s = 16;
|
||||
make_ascii(ident + 23, "A001.001", 8);
|
||||
make_ascii(ident + 27, "ACME COYOTE v0.1", 40);
|
||||
break;
|
||||
case ACME_ACCELLERATTI:
|
||||
c = 1024;
|
||||
h = 16;
|
||||
s = 16;
|
||||
ident[49] = le16(1 << 9); /* LBA */
|
||||
make_ascii(ident + 23, "A001.001", 8);
|
||||
make_ascii(ident + 27, "ACME ACCELLERATTI INCREDIBILUS v0.1", 40);
|
||||
break;
|
||||
case ACME_ZIPPIBUS:
|
||||
c = 1024;
|
||||
h = 16;
|
||||
s = 32;
|
||||
ident[49] = le16(1 << 9); /* LBA */
|
||||
make_ascii(ident + 23, "A001.001", 8);
|
||||
make_ascii(ident + 27, "ACME ZIPPIBUS v0.1", 40);
|
||||
break;
|
||||
}
|
||||
ident[1] = le16(c);
|
||||
ident[3] = le16(h);
|
||||
ident[6] = le16(s);
|
||||
ident[54] = ident[1];
|
||||
ident[55] = ident[3];
|
||||
ident[56] = ident[6];
|
||||
sectors = c * h * s;
|
||||
ident[57] = le16(sectors & 0xFFFF);
|
||||
ident[58] = le16(sectors >> 16);
|
||||
ident[60] = ident[57];
|
||||
ident[61] = ident[58];
|
||||
if (write(fd, ident, 512) != 512)
|
||||
return -1;
|
||||
|
||||
memset(ident, 0xE5, 512);
|
||||
while(sectors--)
|
||||
if (write(fd, ident, 512) != 512)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
82
ide/ide.h
Normal file
82
ide/ide.h
Normal file
@@ -0,0 +1,82 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#define ACME_ROADRUNNER 1 /* 504MB classic IDE drive */
|
||||
#define ACME_COYOTE 2 /* 20MB early IDE drive */
|
||||
#define ACME_NEMESIS 3 /* 20MB LBA capable drive */
|
||||
#define ACME_ULTRASONICUS 4 /* 40MB LBA capable drive */
|
||||
#define ACME_ACCELLERATTI 5 /* 128MB LBA capable drive */
|
||||
#define ACME_ZIPPIBUS 6 /* 256MB LBA capable drive */
|
||||
|
||||
#define MAX_DRIVE_TYPE 6
|
||||
|
||||
#define ide_data 0
|
||||
#define ide_error_r 1
|
||||
#define ide_feature_w 1
|
||||
#define ide_sec_count 2
|
||||
#define ide_sec_num 3
|
||||
#define ide_lba_low 3
|
||||
#define ide_cyl_low 4
|
||||
#define ide_lba_mid 4
|
||||
#define ide_cyl_hi 5
|
||||
#define ide_lba_hi 5
|
||||
#define ide_dev_head 6
|
||||
#define ide_lba_top 6
|
||||
#define ide_status_r 7
|
||||
#define ide_command_w 7
|
||||
#define ide_altst_r 8
|
||||
#define ide_devctrl_w 8
|
||||
#define ide_data_latch 9
|
||||
|
||||
struct ide_taskfile {
|
||||
uint16_t data;
|
||||
uint8_t error;
|
||||
uint8_t feature;
|
||||
uint8_t count;
|
||||
uint8_t lba1;
|
||||
uint8_t lba2;
|
||||
uint8_t lba3;
|
||||
uint8_t lba4;
|
||||
uint8_t status;
|
||||
uint8_t command;
|
||||
uint8_t devctrl;
|
||||
struct ide_drive *drive;
|
||||
};
|
||||
|
||||
struct ide_drive {
|
||||
struct ide_controller *controller;
|
||||
struct ide_taskfile taskfile;
|
||||
unsigned int present:1, intrq:1, failed:1, lba:1, eightbit:1;
|
||||
uint16_t cylinders;
|
||||
uint8_t heads, sectors;
|
||||
uint8_t data[512];
|
||||
uint16_t identify[256];
|
||||
uint8_t *dptr;
|
||||
int state;
|
||||
int fd;
|
||||
off_t offset;
|
||||
int length;
|
||||
};
|
||||
|
||||
struct ide_controller {
|
||||
struct ide_drive drive[2];
|
||||
int selected;
|
||||
const char *name;
|
||||
uint16_t data_latch;
|
||||
};
|
||||
|
||||
extern const uint8_t ide_magic[8];
|
||||
|
||||
void ide_reset_begin(struct ide_controller *c);
|
||||
uint8_t ide_read8(struct ide_controller *c, uint8_t r);
|
||||
void ide_write8(struct ide_controller *c, uint8_t r, uint8_t v);
|
||||
uint16_t ide_read16(struct ide_controller *c, uint8_t r);
|
||||
void ide_write16(struct ide_controller *c, uint8_t r, uint16_t v);
|
||||
uint8_t ide_read_latched(struct ide_controller *c, uint8_t r);
|
||||
void ide_write_latched(struct ide_controller *c, uint8_t r, uint8_t v);
|
||||
|
||||
struct ide_controller *ide_allocate(const char *name);
|
||||
int ide_attach(struct ide_controller *c, int drive, int fd);
|
||||
void ide_detach(struct ide_drive *d);
|
||||
void ide_free(struct ide_controller *c);
|
||||
|
||||
int ide_make_drive(uint8_t type, int fd);
|
||||
BIN
ide/makedisk
Executable file
BIN
ide/makedisk
Executable file
Binary file not shown.
29
ide/makedisk.c
Normal file
29
ide/makedisk.c
Normal file
@@ -0,0 +1,29 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include "ide.h"
|
||||
|
||||
int main(int argc, const char *argv[])
|
||||
{
|
||||
int t, fd;
|
||||
if (argc != 3) {
|
||||
fprintf(stderr, "%s [type] [path]\n", argv[0]);
|
||||
exit(1);
|
||||
}
|
||||
t = atoi(argv[1]);
|
||||
if (t < 1 || t > MAX_DRIVE_TYPE) {
|
||||
fprintf(stderr, "%s: unknown drive type.\n", argv[0]);
|
||||
exit(1);
|
||||
}
|
||||
fd = open(argv[2], O_WRONLY|O_TRUNC|O_CREAT|O_EXCL, 0666);
|
||||
if (fd == -1) {
|
||||
perror(argv[2]);
|
||||
exit(1);
|
||||
}
|
||||
if (ide_make_drive(t, fd) < 0) {
|
||||
perror(argv[2]);
|
||||
exit(1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user