mii_emu/src/drivers/mii_smartport.c

336 lines
8.8 KiB
C

/*
* mii_smartport.c
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*
* This is shamelessly inspired from:
* https:github.com/ct6502/apple2ts/blob/main/src/emulator/harddrivedata.ts
* http://www.1000bit.it/support/manuali/apple/technotes/smpt/tn.smpt.1.html
*/
#define _GNU_SOURCE // for asprintf
#include <errno.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include "mii.h"
#include "mii_bank.h"
#include "mii_dd.h"
#include "mii_slot.h"
#define INCBIN_STYLE INCBIN_STYLE_SNAKE
#define INCBIN_PREFIX mii_
#include "incbin.h"
INCBIN(smartport_rom, "test/asm/mii_smartport_driver.bin");
extern const unsigned char mii_smartport_rom_data[];
#define MII_SM_DRIVE_COUNT 2
typedef struct mii_card_sm_t {
// struct mii_card_sm_t *next;
mii_dd_t drive[MII_SM_DRIVE_COUNT];
struct mii_slot_t *slot;
} mii_card_sm_t;
static void
_mii_hd_callback(
mii_t *mii,
uint8_t trap)
{
int sid = ((mii->cpu.PC >> 8) & 0xf) - 1;
mii_card_sm_t *c = mii->slot[sid].drv_priv;
uint8_t command = mii_read_one(mii, 0x42);
uint8_t unit = mii_read_one(mii, 0x43);
uint16_t buffer = mii_read_word(mii, 0x44);
uint16_t blk = mii_read_word(mii, 0x46);
unit >>= 7; // last bit is the one we want, drive 0/1
switch (command) {
case 0: { // get status
if (!c->drive[unit].file) {
mii->cpu.X = mii->cpu.Y = 0;
mii->cpu.P.C = 1;
} else {
int nblocks = (c->drive[unit].file->size + 511) / 512;
mii->cpu.X = nblocks & 0xff;
mii->cpu.Y = nblocks >> 8;
mii->cpu.P.C = 0;
}
} break;
case 1: {// read block
if (!c->drive[unit].file) {
mii->cpu.P.C = 1;
break;
}
if (blk >= c->drive[unit].file->size / 512) {
mii->cpu.P.C = 1;
break;
}
mii_bank_t * bank = &mii->bank[mii->mem[buffer >> 8].write];
mii->cpu.P.C = mii_dd_read(
&c->drive[unit], bank, buffer, blk, 1) != 0;
} break;
case 2: {// write block
if (!c->drive[unit].file) {
mii->cpu.P.C = 1;
break;
}
if (blk >= c->drive[unit].file->size / 512) {
mii->cpu.P.C = 1;
break;
}
mii_bank_t * bank = &mii->bank[mii->mem[buffer >> 8].read];
mii->cpu.P.C = mii_dd_write(
&c->drive[unit], bank, buffer, blk, 1) != 0;
} break;
default: {
printf("%s cmd %02x unit %02x buffer %04x blk %04x\n", __func__,
command, unit, buffer, blk);
printf("*** %s: unhandled command %02x\n", __func__, command);
mii->cpu.P.C = 1;
}
}
}
static void
_mii_sm_callback(
mii_t *mii,
uint8_t trap)
{
printf("%s\n", __func__);
int sid = ((mii->cpu.PC >> 8) & 0xf) - 1;
mii_card_sm_t *c = mii->slot[sid].drv_priv;
uint16_t sp = 0x100 + mii->cpu.S + 1;
uint16_t call_addr = mii_read_word(mii, sp);
uint8_t spCommand = mii_read_one(mii, call_addr + 1);
uint16_t spParams = mii_read_word(mii, call_addr + 2);
call_addr += 3;
mii_write_word(mii, sp, call_addr);
uint8_t spPCount = mii_read_one(mii, spParams + 0);
uint8_t spUnit = mii_read_one(mii, spParams + 1);
uint16_t spBuffer = mii_read_word(mii, spParams + 2);
printf("%s cmd %02x params %04x pcount %d unit %02x buffer %04x\n", __func__,
spCommand, spParams, spPCount, spUnit, spBuffer);
switch (spCommand) {
case 0: { // get status
if (spPCount != 3) {
mii->cpu.P.C = 1;
break;
}
uint8_t status = mii_read_one(mii, spParams + 4);
printf("%s: unit %d status %02x \n", __func__, spUnit, status);
uint8_t st = 0x80 | 0x40 | 0x20;
uint32_t bsize = 0;
if (spUnit) spUnit--;
if (spUnit < MII_SM_DRIVE_COUNT && c->drive[spUnit].file) {
st |= 0x10;
bsize = (c->drive[spUnit].file->size + 511) / 512;
}
if (status == 0) {
mii->cpu.P.C = 0;
/* Apple IIc reference says this ought to be a status byte,
* but practice and A2Desktop says it ought to be a drive
* count, so here goes... */
// mii_write_one(mii, spBuffer++, st);
mii_write_one(mii, spBuffer++, MII_SM_DRIVE_COUNT);
mii_write_one(mii, spBuffer++, bsize);
mii_write_one(mii, spBuffer++, bsize >> 8);
mii_write_one(mii, spBuffer++, bsize >> 16);
} else if (status == 3 && spUnit < MII_SM_DRIVE_COUNT) {
mii->cpu.P.C = 0;
mii_write_one(mii, spBuffer++, st);
mii_write_one(mii, spBuffer++, bsize);
mii_write_one(mii, spBuffer++, bsize >> 8);
mii_write_one(mii, spBuffer++, bsize >> 16);
char dname[17] = "\x8MII HD 0 ";
dname[8] = '0' + spUnit;
for (int i = 0; i < 17; i++)
mii_write_one(mii, spBuffer++, dname[i]);
mii_write_one(mii, spBuffer++, 0x02); // Profile
mii_write_one(mii, spBuffer++, 0x00); // Profile
mii_write_one(mii, spBuffer++, 0x01); // Version
mii_write_one(mii, spBuffer++, 0x13);
} else {
printf("%s: unit %d bad status %d\n",
__func__, spUnit, status);
mii->cpu.P.C = 1;
}
} break;
case 1: { // read
if (spPCount != 3) {
printf("%s: unit %d bad pcount %d\n",
__func__, spUnit, spPCount);
mii->cpu.P.C = 1;
break;
}
if (spUnit >= MII_SM_DRIVE_COUNT) {
printf("%s: unit %d out of range\n",
__func__, spUnit);
mii->cpu.P.C = 1;
break;
}
uint32_t blk = mii_read_word(mii, spParams + 3) |
(mii_read_one(mii, spParams + 4) << 16) |
(mii_read_one(mii, spParams + 5) << 24);
printf("%s read block %x\n", __func__, blk);
if (!c->drive[spUnit].file) {
mii->cpu.P.C = 1;
break;
}
if (blk >= c->drive[spUnit].file->size / 512) {
printf("%s: block %d out of range\n",
__func__, blk);
mii->cpu.P.C = 1;
break;
}
mii_bank_t * bank = &mii->bank[mii->mem[spBuffer >> 8].write];
mii->cpu.P.C = mii_dd_read(
&c->drive[spUnit], bank, spBuffer, blk, 1) != 0;
// mii->cpu.P.C = 0;
} break;
case 2: { // write
if (spPCount != 3) {
printf("%s: unit %d bad pcount %d\n",
__func__, spUnit, spPCount);
mii->cpu.P.C = 1;
break;
}
if (spUnit >= MII_SM_DRIVE_COUNT) {
printf("%s: unit %d out of range\n",
__func__, spUnit);
mii->cpu.P.C = 1;
break;
}
uint32_t blk = mii_read_word(mii, spParams + 3) |
(mii_read_one(mii, spParams + 4) << 16) |
(mii_read_one(mii, spParams + 5) << 24);
printf("%s write block %x\n", __func__, blk);
if (!c->drive[spUnit].file) {
mii->cpu.P.C = 1;
break;
}
if (blk >= c->drive[spUnit].file->size / 512) {
printf("%s: block %d out of range\n",
__func__, blk);
mii->cpu.P.C = 1;
break;
}
mii_bank_t * bank = &mii->bank[mii->mem[spBuffer >> 8].read];
mii->cpu.P.C = mii_dd_write(
&c->drive[spUnit], bank, spBuffer, blk, 1) != 0;
} break;
}
}
static int
_mii_sm_init(
mii_t * mii,
struct mii_slot_t *slot )
{
mii_card_sm_t *c = calloc(1, sizeof(*c));
c->slot = slot;
slot->drv_priv = c;
printf("%s loading in slot %d\n", __func__, slot->id + 1);
uint16_t addr = 0xc100 + (slot->id * 0x100);
mii_bank_write(
&mii->bank[MII_BANK_CARD_ROM],
addr, mii_smartport_rom_data, 256);
uint8_t trap_hd = mii_register_trap(mii, _mii_hd_callback);
uint8_t trap_sm = mii_register_trap(mii, _mii_sm_callback);
// printf("%s: traps %02x %02x\n", __func__, trap_hd, trap_sm);
mii_bank_write(
&mii->bank[MII_BANK_CARD_ROM],
addr + 0xd2, &trap_hd, 1);
mii_bank_write(
&mii->bank[MII_BANK_CARD_ROM],
addr + 0xe2, &trap_sm, 1);
for (int i = 0; i < MII_SM_DRIVE_COUNT; i++) {
mii_dd_t *dd = &c->drive[i];
dd->slot_id = slot->id + 1;
dd->drive = i + 1;
dd->slot = slot;
asprintf((char **)&dd->name, "SmartPort S:%d D:%d",
dd->slot_id, dd->drive);
}
mii_dd_register_drives(&mii->dd, c->drive, MII_SM_DRIVE_COUNT);
return 0;
}
static void
_mii_sm_dispose(
mii_t * mii,
struct mii_slot_t *slot )
{
mii_card_sm_t *c = slot->drv_priv;
for (int i = 0; i < MII_SM_DRIVE_COUNT; i++) {
free((char *)c->drive[i].name);
c->drive[i].name = NULL;
}
// files attached to drives are automatically freed.
free(c);
slot->drv_priv = NULL;
}
static int
_mii_sm_command(
mii_t * mii,
struct mii_slot_t *slot,
uint8_t cmd,
void * param)
{
mii_card_sm_t *c = slot->drv_priv;
switch (cmd) {
case MII_SLOT_DRIVE_COUNT:
if (param)
*(int *)param = MII_SM_DRIVE_COUNT;
break;
case MII_SLOT_DRIVE_LOAD ... MII_SLOT_DRIVE_LOAD + MII_SM_DRIVE_COUNT - 1:
int drive = cmd - MII_SLOT_DRIVE_LOAD;
const char *filename = param;
mii_dd_file_t *file = NULL;
if (filename && *filename) {
file = mii_dd_file_load(&mii->dd, filename, 0);
if (!file)
return -1;
}
mii_dd_drive_load(&c->drive[drive], file);
break;
}
return 0;
}
static uint8_t
_mii_sm_access(
mii_t * mii, struct mii_slot_t *slot,
uint16_t addr, uint8_t byte, bool write)
{
return 0;
}
static mii_slot_drv_t _driver = {
.name = "smartport",
.desc = "SmartPort card",
.init = _mii_sm_init,
.dispose = _mii_sm_dispose,
.access = _mii_sm_access,
.command = _mii_sm_command,
};
MI_DRIVER_REGISTER(_driver);