mirror of
https://github.com/LIV2/lide.device.git
synced 2025-12-06 00:32:45 +00:00
972 lines
29 KiB
C
972 lines
29 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/* This file is part of lide.device
|
|
* Copyright (C) 2023 Matthew Harlum <matt@harlum.net>
|
|
*/
|
|
#include <devices/scsidisk.h>
|
|
#include <devices/timer.h>
|
|
#include <devices/trackdisk.h>
|
|
#include <exec/errors.h>
|
|
#include <exec/execbase.h>
|
|
#include <exec/resident.h>
|
|
#include <proto/exec.h>
|
|
#include <proto/expansion.h>
|
|
#include <resources/filesysres.h>
|
|
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
|
|
#include "ata.h"
|
|
#include "atapi.h"
|
|
#include "device.h"
|
|
#include "idetask.h"
|
|
#include "newstyle.h"
|
|
#include "td64.h"
|
|
#include "mounter.h"
|
|
#include "debug.h"
|
|
#include "lide_alib.h"
|
|
|
|
#ifdef NO_AUTOCONFIG
|
|
extern UBYTE bootblock, bootblock_end;
|
|
#endif
|
|
|
|
extern const char device_name[];
|
|
extern const char device_id_string[];
|
|
extern int endskip;
|
|
static struct Library * init(BPTR seg_list asm("a0"));
|
|
|
|
/*-----------------------------------------------------------
|
|
A library or device with a romtag should start with moveq #-1,d0 (to
|
|
safely return an error if a user tries to execute the file), followed by a
|
|
Resident structure.
|
|
------------------------------------------------------------*/
|
|
int __attribute__((no_reorder)) _start()
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
|
|
__attribute__((used,no_reorder))
|
|
static const struct Resident romTag = {
|
|
.rt_MatchWord = RTC_MATCHWORD,
|
|
.rt_MatchTag = (APTR)&romTag,
|
|
.rt_EndSkip = (APTR)&endskip,
|
|
.rt_Flags = RTF_COLDSTART,
|
|
.rt_Version = DEVICE_VERSION,
|
|
.rt_Type = NT_DEVICE,
|
|
.rt_Pri = DEVICE_PRIORITY,
|
|
.rt_Name = (APTR)&device_name,
|
|
.rt_IdString = (APTR)&device_id_string,
|
|
.rt_Init = (APTR)init
|
|
};
|
|
|
|
const char device_name[] = DEVICE_NAME;
|
|
const char device_id_string[] = DEVICE_ID_STRING;
|
|
/**
|
|
* set_dev_name
|
|
*
|
|
* Try to set a unique drive name
|
|
* will prepend 2nd/3rd/4th. etc to the beginning of device_name
|
|
*/
|
|
char * set_dev_name(struct DeviceBase *dev) {
|
|
struct ExecBase *SysBase = dev->SysBase;
|
|
|
|
const ULONG device_prefix[] = {' nd.', ' rd.', ' th.'};
|
|
char * devName = (char *)device_name;
|
|
|
|
/* Prefix the device name if a device with the same name already exists */
|
|
for (int i=0; i<8; i++) {
|
|
if (FindName(&SysBase->DeviceList,devName)) {
|
|
if (i == 0) {
|
|
devName = AllocMem(sizeof(device_name)+4,MEMF_ANY|MEMF_CLEAR);
|
|
if (devName == NULL) return NULL;
|
|
strcpy(devName + 4,device_name);
|
|
}
|
|
|
|
switch (i) {
|
|
case 0:
|
|
*(ULONG *)devName = device_prefix[0];
|
|
break;
|
|
case 1:
|
|
*(ULONG *)devName = device_prefix[1];
|
|
break;
|
|
default:
|
|
*(ULONG *)devName = device_prefix[2];
|
|
break;
|
|
}
|
|
*(char *)devName = '2' + i;
|
|
} else {
|
|
Info("Device name: %s\n",devName);
|
|
return devName;
|
|
}
|
|
}
|
|
|
|
Info("Couldn't set device name.\n");
|
|
// if devName doesn't point to the const device_name then we need to free up that memory
|
|
if (devName != device_name) FreeMem(devName,sizeof(device_name)+4);
|
|
return NULL;
|
|
}
|
|
|
|
#ifdef NO_AUTOCONFIG
|
|
/**
|
|
* CreateFakeConfigDev
|
|
* Create fake ConfigDev and DiagArea to support autoboot without requiring real autoconfig device.
|
|
* Adapted from mounter.c by Toni Wilen
|
|
*
|
|
* @param SysBase Pointer to SysBase
|
|
* @param ExpansionBase Pointer to ExpansionBase
|
|
* @returns Pointer to a ConfigDev struct
|
|
*/
|
|
struct ConfigDev *CreateFakeConfigDev(struct ExecBase *SysBase, struct Library *ExpansionBase)
|
|
{
|
|
struct ConfigDev *cd;
|
|
|
|
cd = AllocConfigDev();
|
|
if (cd) {
|
|
cd->cd_BoardAddr = NULL;
|
|
cd->cd_BoardSize = 0;
|
|
cd->cd_Rom.er_Type = ERTF_DIAGVALID;
|
|
ULONG bbSize = &bootblock_end - &bootblock;
|
|
ULONG daSize = sizeof(struct DiagArea) + bbSize;
|
|
struct DiagArea *diagArea = AllocMem(daSize, MEMF_CLEAR | MEMF_PUBLIC);
|
|
if (diagArea) {
|
|
diagArea->da_Config = DAC_CONFIGTIME;
|
|
diagArea->da_BootPoint = sizeof(struct DiagArea);
|
|
diagArea->da_Size = (UWORD)daSize;
|
|
CopyMem(&bootblock, diagArea+1, bbSize);
|
|
// cd_Rom.er_Reserved0c is used as a pointer to diagArea by strap
|
|
ULONG *da_Pointer = (ULONG *)&cd->cd_Rom.er_Reserved0c;
|
|
*da_Pointer = (ULONG)diagArea;
|
|
}
|
|
} else {
|
|
FreeConfigDev(cd);
|
|
cd = NULL;
|
|
}
|
|
return cd;
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* sleep
|
|
*
|
|
* @param seconds Seconds to wait
|
|
* @param microseconds Microseconds to wait
|
|
*/
|
|
static void sleep(ULONG seconds, ULONG microseconds) {
|
|
struct ExecBase *SysBase = *(struct ExecBase**)4UL;
|
|
|
|
struct timerequest *tr = NULL;
|
|
struct MsgPort *iomp = NULL;
|
|
|
|
|
|
if ((iomp = L_CreatePort(NULL,0))) {
|
|
if ((tr = (struct timerequest *)L_CreateExtIO(iomp, sizeof(struct timerequest)))) {
|
|
if ((OpenDevice("timer.device",UNIT_VBLANK,(struct IORequest *)tr,0)) == 0) {
|
|
tr->tr_node.io_Command = TR_ADDREQUEST;
|
|
tr->tr_time.tv_sec = seconds;
|
|
tr->tr_time.tv_micro = microseconds;
|
|
DoIO((struct IORequest *)tr);
|
|
CloseDevice((struct IORequest *)tr);
|
|
}
|
|
L_DeleteExtIO((struct IORequest *)tr);
|
|
}
|
|
L_DeletePort(iomp);
|
|
}
|
|
}
|
|
|
|
#if CDBOOT
|
|
/**
|
|
* FindCDFS
|
|
*
|
|
* Look for a CD Filesystem in FileSystem.resource
|
|
*
|
|
* @return BOOL True if CDFS found
|
|
*/
|
|
static BOOL FindCDFS() {
|
|
struct ExecBase *SysBase = *(struct ExecBase **)4UL;
|
|
struct FileSysResource *fsr = OpenResource(FSRNAME);
|
|
struct FileSysEntry *fse;
|
|
|
|
if (fsr == NULL) return false;
|
|
|
|
for (fse = (struct FileSysEntry *)fsr->fsr_FileSysEntries.lh_Head; fse->fse_Node.ln_Succ != NULL; fse = (struct FileSysEntry *)fse->fse_Node.ln_Succ) {
|
|
if (fse->fse_DosType == 'CD01') return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
static bool ioreq_is_valid(struct DeviceBase *dev, struct IORequest *ior) {
|
|
struct ExecBase *SysBase = dev->SysBase;
|
|
bool found = false;
|
|
|
|
struct IDEUnit *unit;
|
|
|
|
if (SysBase->LibNode.lib_Version >= 36) {
|
|
ObtainSemaphoreShared(&dev->ulSem);
|
|
} else {
|
|
ObtainSemaphore(&dev->ulSem);
|
|
}
|
|
|
|
for (unit = (struct IDEUnit *)dev->units.mlh_Head;
|
|
unit->mn_Node.mln_Succ != NULL;
|
|
unit = (struct IDEUnit *)unit->mn_Node.mln_Succ) {
|
|
if (unit == (struct IDEUnit *)ior->io_Unit) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
ReleaseSemaphore(&dev->ulSem);
|
|
|
|
if (!found) return false;
|
|
|
|
if ((struct Device *)dev != ior->io_Device) return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Cleanup
|
|
*
|
|
* Free used resources back to the system
|
|
*/
|
|
static void Cleanup(struct DeviceBase *dev) {
|
|
Info("Cleaning up...\n");
|
|
struct ExecBase *SysBase = *(struct ExecBase **)4UL;
|
|
char *devName = dev->lib.lib_Node.ln_Name;
|
|
struct IDEUnit *unit;
|
|
|
|
if (SysBase->LibNode.lib_Version >= 36) {
|
|
ObtainSemaphoreShared(&dev->ulSem);
|
|
} else {
|
|
ObtainSemaphore(&dev->ulSem);
|
|
}
|
|
|
|
for (unit = (struct IDEUnit *)dev->units.mlh_Head;
|
|
unit->mn_Node.mln_Succ != NULL;
|
|
unit = (struct IDEUnit *)unit->mn_Node.mln_Succ)
|
|
{
|
|
unit->cd->cd_Flags |= CDF_CONFIGME;
|
|
}
|
|
|
|
ReleaseSemaphore(&dev->ulSem);
|
|
|
|
if (dev->ExpansionBase) CloseLibrary((struct Library *)dev->ExpansionBase);
|
|
|
|
struct IDETask *itask;
|
|
|
|
for (itask = (struct IDETask *)dev->ideTasks.mlh_Head;
|
|
itask->mn_Node.mln_Succ != NULL;
|
|
itask = (struct IDETask *)itask->mn_Node.mln_Succ)
|
|
{
|
|
FreeMem(itask,sizeof(struct IDETask));
|
|
}
|
|
|
|
// if devName doesn't point to the const device_name then we need to free up that memory
|
|
if (devName != device_name) {
|
|
FreeMem(devName,sizeof(device_name)+4);
|
|
devName = NULL;
|
|
}
|
|
|
|
FreeMem((char *)dev - dev->lib.lib_NegSize, dev->lib.lib_NegSize + dev->lib.lib_PosSize);
|
|
}
|
|
|
|
/**
|
|
* detectChannels
|
|
*
|
|
* Detect how many IDE Channels this board has
|
|
* @param cd Pointer to the ConfigDev struct for this board
|
|
* @returns number of channels
|
|
*/
|
|
static BYTE detectChannels(struct ConfigDev *cd) {
|
|
if ((cd->cd_Rom.er_Manufacturer == OAHR_MANUF_ID) && (cd->cd_Rom.er_Product == RIPPLE_PROD_ID))
|
|
return 2;
|
|
|
|
UBYTE *drvsel = cd->cd_BoardAddr + CHANNEL_0 + ata_reg_devHead;
|
|
|
|
*drvsel = 0xE0; // Select the primary drive + poke IDE to turn off ROM
|
|
|
|
if (cd->cd_Rom.er_Manufacturer == BSC_MANUF_ID || cd->cd_Rom.er_Manufacturer == A1K_MANUF_ID) {
|
|
// On the AT-Bus 2008 (Clone) the ROM is selected on the lower byte when IDE_CS1 is asserted
|
|
// Not a problem in single channel mode - the drive registers there only use the upper byte
|
|
// If Status == Alt Status or it's an AT-Bus card then only one channel is supported.
|
|
//
|
|
// Check for the ROM Footer
|
|
// On the AT-Bus 2008 clone it will still be there
|
|
// On a Matze TK the ROM goes away and the board can do 2 channels
|
|
ULONG signature = 0;
|
|
char *romFooter = (char *)cd->cd_BoardAddr + 0xFFF8;
|
|
|
|
if (cd->cd_Rom.er_InitDiagVec & 1 || // If the board has an odd offset then add it
|
|
cd->cd_Rom.er_InitDiagVec == 0x100) { // WinUAE AT-Bus 2008 has DiagVec @ 0x100 but Driver is on odd offset
|
|
romFooter++;
|
|
}
|
|
|
|
for (int i=0; i<4; i++) {
|
|
signature <<= 8;
|
|
signature |= *romFooter;
|
|
romFooter += 2;
|
|
}
|
|
|
|
if (signature == 'LIDE') {
|
|
Info("Channel detection: Saw ROM footer - assuming AT-Bus single channel mode\n");
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
// Detect if there are 1 or 2 IDE channels on this board
|
|
// 2 channel boards use the CS2 decode for the second channel
|
|
volatile UBYTE *status = cd->cd_BoardAddr + CHANNEL_0 + ata_reg_status;
|
|
volatile UBYTE *alt_status = cd->cd_BoardAddr + CHANNEL_1 + ata_reg_altStatus;
|
|
|
|
// Try a couple of times with a small delay
|
|
// Some drives have been seen to be quite slow at resetting, and interfere with the channel detection unless there's a delay
|
|
for (int i=0; i<4; i++) {
|
|
sleep(0,250000); // 250ms
|
|
if (*status != *alt_status) {
|
|
return 2;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
/**
|
|
* init_device
|
|
*
|
|
* Scan for drives and initialize the driver if any are found
|
|
*/
|
|
struct Library __attribute__((used, saveds)) * init_device(struct ExecBase *SysBase asm("a6"), BPTR seg_list asm("a0"), struct DeviceBase *dev asm("d0"))
|
|
{
|
|
dev->SysBase = SysBase;
|
|
Trace("Init dev, base: %08lx\n",dev);
|
|
struct Library *ExpansionBase = NULL;
|
|
|
|
char *devName;
|
|
|
|
UBYTE numBoards = 0;
|
|
|
|
if (!(devName = set_dev_name(dev))) return NULL;
|
|
/* save pointer to our loaded code (the SegList) */
|
|
dev->saved_seg_list = seg_list;
|
|
dev->lib.lib_Node.ln_Type = NT_DEVICE;
|
|
dev->lib.lib_Node.ln_Name = devName;
|
|
dev->lib.lib_Flags = LIBF_SUMUSED | LIBF_CHANGED;
|
|
dev->lib.lib_Version = DEVICE_VERSION;
|
|
dev->lib.lib_Revision = DEVICE_REVISION;
|
|
dev->lib.lib_IdString = (APTR)device_id_string;
|
|
|
|
dev->isOpen = FALSE;
|
|
dev->numUnits = 0;
|
|
dev->numTasks = 0;
|
|
dev->hasRemovables = false;
|
|
|
|
L_NewList((struct List *)&dev->units);
|
|
InitSemaphore(&dev->ulSem);
|
|
|
|
L_NewList((struct List *)&dev->ideTasks);
|
|
|
|
if (!(ExpansionBase = (struct Library *)OpenLibrary("expansion.library",0))) {
|
|
Cleanup(dev);
|
|
return NULL;
|
|
} else {
|
|
dev->ExpansionBase = ExpansionBase;
|
|
}
|
|
|
|
struct IDETask *itask;
|
|
struct ConfigDev *cd;
|
|
struct Task *self = FindTask(NULL);
|
|
|
|
#ifndef NO_AUTOCONFIG
|
|
|
|
struct CurrentBinding cb;
|
|
|
|
if (GetCurrentBinding(&cb,sizeof(struct CurrentBinding)) == 0) {
|
|
Cleanup(dev);
|
|
return NULL;
|
|
}
|
|
|
|
cd = cb.cb_ConfigDev;
|
|
|
|
// Add an IDE Task for each board
|
|
// When loaded from Autoconfig ROM this will still only attach to one board.
|
|
// If the driver is loaded by BindDrivers though then this should attach to multiple boards.
|
|
for (cd = cb.cb_ConfigDev; cd != NULL; cd = cd->cd_NextCD) {
|
|
|
|
if (!cd) {
|
|
break;
|
|
}
|
|
|
|
if (!(cd->cd_Flags & CDF_CONFIGME)) {
|
|
continue;
|
|
}
|
|
|
|
numBoards++;
|
|
#else
|
|
/**
|
|
* A ConfigDev is needed for Autoboot
|
|
* If we are booting a non-autoconfig device we need to create a ConfigDev struct
|
|
* This could also be done in mounter.c but that would create a new ConfigDev for each unit
|
|
*/
|
|
if ((cd = CreateFakeConfigDev(SysBase,ExpansionBase)) == NULL) {
|
|
Info("Failed to create fake configdev\n");
|
|
Cleanup(dev);
|
|
return NULL;
|
|
}
|
|
cd->cd_BoardAddr = (APTR)BOARD_BASE;
|
|
cd->cd_BoardSize = 0x1000;
|
|
numBoards = 1;
|
|
#endif
|
|
UBYTE channels = detectChannels(cd);
|
|
|
|
for (int c=0; c < channels; c++) {
|
|
|
|
Trace("Starting IDE Task %ld\n",numBoards);
|
|
|
|
itask = AllocMem(sizeof(struct IDETask), MEMF_ANY|MEMF_CLEAR);
|
|
|
|
if (itask == NULL) {
|
|
Info("Couldn't allocate memory\n");
|
|
break;
|
|
}
|
|
|
|
itask->dev = dev;
|
|
itask->cd = cd;
|
|
itask->channel = c;
|
|
itask->taskNum = dev->numTasks;
|
|
itask->parent = self;
|
|
itask->boardNum = (numBoards - 1);
|
|
|
|
SetSignal(0,SIGF_SINGLE);
|
|
|
|
// Start the IDE Task
|
|
itask->task = L_CreateTask(ATA_TASK_NAME,TASK_PRIORITY,ide_task,TASK_STACK_SIZE,itask);
|
|
if (itask->task == NULL) {
|
|
Info("IDE Task %ld failed\n",itask->taskNum);
|
|
FreeMem(itask,sizeof(struct IDETask));
|
|
continue;
|
|
} else {
|
|
Trace("IDE Task %ld created!, waiting for init\n",itask->taskNum);
|
|
}
|
|
|
|
// Wait for task to init
|
|
Wait(SIGF_SINGLE);
|
|
|
|
// If itask->active has been set to false it means the task exited
|
|
if (itask->active == false) {
|
|
Info("IDE Task %ld exited.\n",itask->taskNum);
|
|
FreeMem(itask,sizeof(struct IDETask));
|
|
continue;
|
|
}
|
|
|
|
// Add the task to the list
|
|
AddTail((struct List *)&dev->ideTasks,(struct Node *)&itask->mn_Node);
|
|
dev->numTasks++;
|
|
|
|
Trace("Claiming board %08lx\n",(ULONG)cd->cd_BoardAddr);
|
|
cd->cd_Flags &= ~(CDF_CONFIGME);
|
|
cd->cd_Driver = dev;
|
|
}
|
|
#ifndef NO_AUTOCONFIG
|
|
}
|
|
#endif
|
|
Info("Detected %ld drives, %ld boards\n",((volatile struct DeviceBase *)dev)->numUnits, numBoards);
|
|
|
|
if (dev->numTasks == 0) {
|
|
Cleanup(dev);
|
|
return NULL;
|
|
}
|
|
|
|
if (dev->hasRemovables) dev->ChangeTask = L_CreateTask(CHANGE_TASK_NAME,0,diskchange_task,TASK_STACK_SIZE,dev);
|
|
|
|
Info("Startup finished.\n");
|
|
return (struct Library *)dev;
|
|
|
|
}
|
|
|
|
/*
|
|
* device dependent expunge function
|
|
* !!! CAUTION: This function runs in a forbidden state !!!
|
|
* This call is guaranteed to be single-threaded; only one task
|
|
* will execute your Expunge at a time.
|
|
*
|
|
* IMPORTANT: because Expunge is called from the memory allocator,
|
|
* it may NEVER Wait() or otherwise take long time to complete.
|
|
*/
|
|
static BPTR __attribute__((used, saveds)) expunge(struct DeviceBase *dev asm("a6"))
|
|
{
|
|
Trace((CONST_STRPTR) "running expunge()\n");
|
|
|
|
/**
|
|
* Don't expunge
|
|
*
|
|
* If expunged the driver would be gone until reboot
|
|
*/
|
|
|
|
dev->lib.lib_Flags |= LIBF_DELEXP;
|
|
return 0;
|
|
|
|
}
|
|
|
|
/*
|
|
* device dependent open function
|
|
* !!! CAUTION: This function runs in a forbidden state !!!
|
|
* This call is guaranteed to be single-threaded; only one task
|
|
* will execute your Open at a time.
|
|
*/
|
|
static void __attribute__((used, saveds)) open(struct DeviceBase *dev asm("a6"), struct IORequest *ioreq asm("a1"), ULONG unitnum asm("d0"), ULONG flags asm("d1"))
|
|
{
|
|
struct ExecBase *SysBase = dev->SysBase;
|
|
struct IDEUnit *unit = NULL;
|
|
BYTE error = 0;
|
|
bool found = false;
|
|
|
|
dev->lib.lib_OpenCnt++; // Make sure we're not Expunged during open()
|
|
|
|
Trace((CONST_STRPTR) "running open() for unitnum %ld\n",unitnum);
|
|
|
|
/* IMPORTANT: Must return TDERR_BadUnitNum when lun > 0
|
|
* SCSI Unit encoding places the LUN in the 10s column of the unit number
|
|
* HDToolbox scans each LUN of a unit and stops searching if it sees an error other than TDERR_BadUnitNum
|
|
* So if this is not returned, only one drive will ever be detected
|
|
*/
|
|
UBYTE lun = unitnum / 10;
|
|
unitnum = (unitnum % 10);
|
|
|
|
if (lun != 0) {
|
|
// No LUNs for IDE drives
|
|
error = TDERR_BadUnitNum;
|
|
goto exit;
|
|
}
|
|
|
|
if (unitnum > dev->highestUnit) {
|
|
error = IOERR_OPENFAIL;
|
|
goto exit;
|
|
}
|
|
|
|
if (SysBase->LibNode.lib_Version >= 36) {
|
|
ObtainSemaphoreShared(&dev->ulSem);
|
|
} else {
|
|
ObtainSemaphore(&dev->ulSem);
|
|
}
|
|
|
|
for (unit = (struct IDEUnit *)dev->units.mlh_Head;
|
|
unit->mn_Node.mln_Succ != NULL;
|
|
unit = (struct IDEUnit *)unit->mn_Node.mln_Succ)
|
|
{
|
|
if (unit->unitNum == unitnum) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
ReleaseSemaphore(&dev->ulSem);
|
|
|
|
if (found == false || unit->present == false) {
|
|
error = TDERR_BadUnitNum;
|
|
goto exit;
|
|
}
|
|
|
|
if (unit->itask->task == NULL || unit->itask->active == false) {
|
|
error = IOERR_OPENFAIL;
|
|
goto exit;
|
|
}
|
|
|
|
ioreq->io_Unit = (struct Unit *)unit;
|
|
|
|
/* IMPORTANT: Mark IORequest as "complete" or otherwise CheckIO() may
|
|
* consider it as "in use" in spite of never having been
|
|
* used at all. This also avoids that WaitIO() will hang
|
|
* on an IORequest which has never been used.
|
|
*
|
|
* Source: Olaf Barthel's Trackfile.device
|
|
* https://github.com/obarthel/trackfile-device
|
|
*/
|
|
ioreq->io_Message.mn_Node.ln_Type = NT_REPLYMSG;
|
|
|
|
// Send a TD_CHANGESTATE ioreq for the unit if it is ATAPI and not already open
|
|
// This will update the media presence & geometry
|
|
if (unit->atapi && unit->openCount == 0) direct_changestate(unit,dev);
|
|
|
|
unit->openCount++;
|
|
dev->lib.lib_OpenCnt++;
|
|
|
|
dev->lib.lib_Flags &= ~LIBF_DELEXP;
|
|
|
|
if (!dev->isOpen)
|
|
{
|
|
dev->isOpen = TRUE;
|
|
}
|
|
exit:
|
|
if (error != 0) {
|
|
/* IMPORTANT: Invalidate io_Device and io_Unit on open failure. */
|
|
ioreq->io_Unit = NULL;
|
|
ioreq->io_Device = NULL;
|
|
}
|
|
|
|
ioreq->io_Error = error;
|
|
|
|
dev->lib.lib_OpenCnt--;
|
|
}
|
|
|
|
|
|
static void td_get_geometry(struct IOStdReq *ioreq) {
|
|
struct DriveGeometry *geometry = (struct DriveGeometry *)ioreq->io_Data;
|
|
struct IDEUnit *unit = (struct IDEUnit *)ioreq->io_Unit;
|
|
|
|
// Clear the geometry struct beforehand to make sure reserved / unused parts are zero
|
|
memset(geometry,0,sizeof(struct DriveGeometry));
|
|
|
|
geometry->dg_SectorSize = unit->blockSize;
|
|
geometry->dg_TotalSectors = unit->logicalSectors;
|
|
geometry->dg_Cylinders = unit->cylinders;
|
|
geometry->dg_CylSectors = (unit->sectorsPerTrack * unit->heads);
|
|
geometry->dg_Heads = unit->heads;
|
|
geometry->dg_TrackSectors = unit->sectorsPerTrack;
|
|
geometry->dg_BufMemType = MEMF_PUBLIC;
|
|
geometry->dg_DeviceType = unit->deviceType;
|
|
geometry->dg_Flags = (unit->atapi) ? DGF_REMOVABLE : 0;
|
|
|
|
ioreq->io_Actual = sizeof(struct DriveGeometry);
|
|
}
|
|
|
|
|
|
/*
|
|
* device dependent close function
|
|
* !!! CAUTION: This function runs in a forbidden state !!!
|
|
* This call is guaranteed to be single-threaded; only one task
|
|
* will execute your Close at a time.
|
|
*/
|
|
static BPTR __attribute__((used, saveds)) close(struct DeviceBase *dev asm("a6"), struct IORequest *ioreq asm("a1"))
|
|
{
|
|
if (ioreq_is_valid(dev,ioreq)) {
|
|
struct IDEUnit *unit = (struct IDEUnit *)ioreq->io_Unit;
|
|
Trace((CONST_STRPTR) "running close()\n");
|
|
|
|
if (dev->lib.lib_OpenCnt) dev->lib.lib_OpenCnt--;
|
|
|
|
if (unit->openCount > 0) unit->openCount--;
|
|
|
|
if (dev->lib.lib_OpenCnt == 0 && (dev->lib.lib_Flags & LIBF_DELEXP))
|
|
return expunge(dev);
|
|
|
|
}
|
|
|
|
ioreq->io_Unit = NULL;
|
|
ioreq->io_Device = NULL;
|
|
return 0;
|
|
}
|
|
|
|
const UWORD supported_commands[] =
|
|
{
|
|
CMD_CLEAR,
|
|
CMD_UPDATE,
|
|
CMD_READ,
|
|
CMD_WRITE,
|
|
TD_ADDCHANGEINT,
|
|
TD_REMCHANGEINT,
|
|
TD_REMOVE,
|
|
TD_PROTSTATUS,
|
|
TD_CHANGENUM,
|
|
TD_CHANGESTATE,
|
|
TD_EJECT,
|
|
TD_GETDRIVETYPE,
|
|
TD_GETGEOMETRY,
|
|
TD_MOTOR,
|
|
TD_PROTSTATUS,
|
|
TD_READ64,
|
|
TD_WRITE64,
|
|
TD_FORMAT64,
|
|
ETD_READ,
|
|
ETD_WRITE,
|
|
ETD_FORMAT,
|
|
NSCMD_ETD_READ64,
|
|
NSCMD_ETD_WRITE64,
|
|
NSCMD_ETD_FORMAT64,
|
|
NSCMD_DEVICEQUERY,
|
|
NSCMD_TD_READ64,
|
|
NSCMD_TD_WRITE64,
|
|
NSCMD_TD_FORMAT64,
|
|
HD_SCSICMD,
|
|
0
|
|
};
|
|
|
|
/**
|
|
* begin_io
|
|
*
|
|
* Handle immediate requests and send any others to ide_task
|
|
*/
|
|
static void __attribute__((used, saveds)) begin_io(struct DeviceBase *dev asm("a6"), struct IOStdReq *ioreq asm("a1"))
|
|
{
|
|
struct ExecBase *SysBase = dev->SysBase;
|
|
|
|
BYTE error = TDERR_NotSpecified;
|
|
|
|
// Check that the IOReq has a sane Device / Unit pointer first
|
|
if (ioreq_is_valid(dev,(struct IORequest *)ioreq)) {
|
|
/* This makes sure that WaitIO() is guaranteed to work and
|
|
* will not hang.
|
|
*
|
|
* Source: Olaf Barthel's Trackfile.device
|
|
* https://github.com/obarthel/trackfile-device
|
|
*/
|
|
ioreq->io_Message.mn_Node.ln_Type = NT_MESSAGE;
|
|
|
|
struct IDEUnit *unit = (struct IDEUnit *)ioreq->io_Unit;
|
|
|
|
Trace((CONST_STRPTR) "running begin_io()\n");
|
|
if (ioreq == NULL || ioreq->io_Unit == 0) return;
|
|
|
|
if (unit->itask == NULL || unit->itask->active == false) {
|
|
|
|
// If the IDE task is dead then we can only throw an error and reply now.
|
|
ioreq->io_Error = IOERR_OPENFAIL;
|
|
|
|
if (!(ioreq->io_Flags & IOF_QUICK)) {
|
|
ReplyMsg(&ioreq->io_Message);
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
Trace("Command %lx\n",ioreq->io_Command);
|
|
switch (ioreq->io_Command) {
|
|
case TD_MOTOR:
|
|
case CMD_CLEAR:
|
|
case CMD_UPDATE:
|
|
ioreq->io_Actual = 0;
|
|
error = 0;
|
|
break;
|
|
|
|
case TD_CHANGENUM:
|
|
ioreq->io_Actual = unit->changeCount;
|
|
error = 0;
|
|
break;
|
|
|
|
case TD_GETDRIVETYPE:
|
|
ioreq->io_Actual = unit->deviceType;
|
|
error = 0;
|
|
break;
|
|
|
|
case TD_GETGEOMETRY:
|
|
td_get_geometry(ioreq);
|
|
error = 0;
|
|
break;
|
|
|
|
case TD_REMOVE:
|
|
unit->changeInt = ioreq->io_Data;
|
|
error = 0;
|
|
break;
|
|
|
|
|
|
case TD_ADDCHANGEINT:
|
|
Info("Addchangeint\n");
|
|
|
|
ioreq->io_Flags |= IOF_QUICK; // Must not Reply to this request
|
|
error = 0;
|
|
|
|
Disable();
|
|
AddHead((struct List *)&unit->changeInts,(struct Node *)&ioreq->io_Message.mn_Node);
|
|
Enable();
|
|
break;
|
|
|
|
case TD_REMCHANGEINT:
|
|
error = 0;
|
|
struct MinNode *changeint;
|
|
// Must Disable() rather than Forbid()!
|
|
Disable();
|
|
for (changeint = unit->changeInts.mlh_Head; changeint->mln_Succ != NULL; changeint = changeint->mln_Succ) {
|
|
if (ioreq == (struct IOStdReq *)changeint) {
|
|
Remove(&ioreq->io_Message.mn_Node);
|
|
break;
|
|
}
|
|
}
|
|
Enable();
|
|
break;
|
|
|
|
|
|
case CMD_READ:
|
|
case ETD_READ:
|
|
case CMD_WRITE:
|
|
case ETD_WRITE:
|
|
ioreq->io_Actual = 0; // Clear high offset for 32-bit commands
|
|
case TD_CHANGESTATE:
|
|
case TD_PROTSTATUS:
|
|
case TD_EJECT:
|
|
case TD_FORMAT:
|
|
case TD_READ64:
|
|
case TD_WRITE64:
|
|
case TD_FORMAT64:
|
|
case NSCMD_TD_READ64:
|
|
case NSCMD_TD_WRITE64:
|
|
case NSCMD_TD_FORMAT64:
|
|
case NSCMD_ETD_READ64:
|
|
case NSCMD_ETD_WRITE64:
|
|
case NSCMD_ETD_FORMAT64:
|
|
case CMD_XFER:
|
|
case CMD_PIO:
|
|
case HD_SCSICMD:
|
|
// Send all of these to ide_task
|
|
ioreq->io_Flags &= ~IOF_QUICK;
|
|
PutMsg(unit->itask->iomp,&ioreq->io_Message);
|
|
Trace((CONST_STRPTR) "IO queued\n");
|
|
return;
|
|
|
|
case NSCMD_DEVICEQUERY:
|
|
if (ioreq->io_Length >= sizeof(struct NSDeviceQueryResult))
|
|
{
|
|
struct NSDeviceQueryResult *result = ioreq->io_Data;
|
|
|
|
result->DevQueryFormat = 0;
|
|
result->SizeAvailable = sizeof(struct NSDeviceQueryResult);
|
|
result->DeviceType = NSDEVTYPE_TRACKDISK;
|
|
result->DeviceSubType = 0;
|
|
result->SupportedCommands = (UWORD *)supported_commands;
|
|
|
|
ioreq->io_Actual = sizeof(struct NSDeviceQueryResult);
|
|
error = 0;
|
|
}
|
|
else {
|
|
error = IOERR_BADLENGTH;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
Warn("Unknown command %d\n", ioreq->io_Command);
|
|
error = IOERR_NOCMD;
|
|
}
|
|
}
|
|
|
|
#if DEBUG & DBG_CMD
|
|
traceCommand(ioreq);
|
|
#endif
|
|
ioreq->io_Error = error;
|
|
|
|
if (ioreq && !(ioreq->io_Flags & IOF_QUICK)) {
|
|
ReplyMsg(&ioreq->io_Message);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* abort_io
|
|
*
|
|
* Abort io request
|
|
*/
|
|
static ULONG __attribute__((used, saveds)) abort_io(struct DeviceBase *dev asm("a6"), struct IOStdReq *ioreq asm("a1"))
|
|
{
|
|
struct ExecBase *SysBase = dev->SysBase;
|
|
|
|
Trace((CONST_STRPTR) "running abort_io()\n");
|
|
|
|
struct IORequest *io;
|
|
|
|
BYTE error = 0; // 0 indicates that the IO was *NOT* aborted
|
|
|
|
/* If the IO is still in queue then we can remove it
|
|
* MUST be done inside a Disable()!
|
|
*
|
|
* Copied from Olaf Barthels Trackfile.device
|
|
* https://github.com/obarthel/trackfile-device
|
|
*/
|
|
if (ioreq_is_valid(dev,(struct IORequest *)ioreq)) {
|
|
struct IDEUnit *unit = (struct IDEUnit *)ioreq->io_Unit;
|
|
Disable();
|
|
for (io = (struct IORequest *)unit->itask->iomp->mp_MsgList.lh_Head;
|
|
io->io_Message.mn_Node.ln_Succ != NULL;
|
|
io = (struct IORequest *)io->io_Message.mn_Node.ln_Succ)
|
|
{
|
|
if (io == (struct IORequest *)ioreq) {
|
|
Remove(&io->io_Message.mn_Node);
|
|
error = io->io_Error = IOERR_ABORTED;
|
|
ReplyMsg(&io->io_Message);
|
|
break;
|
|
}
|
|
}
|
|
Enable();
|
|
}
|
|
return error;
|
|
}
|
|
|
|
|
|
static const ULONG device_vectors[] =
|
|
{
|
|
(ULONG)open,
|
|
(ULONG)close,
|
|
(ULONG)expunge,
|
|
0, //extFunc not used here
|
|
(ULONG)begin_io,
|
|
(ULONG)abort_io,
|
|
-1 //function table end marker
|
|
};
|
|
|
|
/**
|
|
* init
|
|
*
|
|
* Create the device and add it to the system if init_device succeeds
|
|
*/
|
|
static struct Library __attribute__((used)) * init(BPTR seg_list asm("a0"))
|
|
{
|
|
struct ExecBase *SysBase = *(struct ExecBase **)4UL;
|
|
Info("Init driver.\n");
|
|
struct MountStruct *ms = NULL;
|
|
struct DeviceBase *mydev = (struct DeviceBase *)MakeLibrary((ULONG *)&device_vectors, // Vectors
|
|
NULL, // InitStruct data
|
|
(APTR)init_device, // Init function
|
|
sizeof(struct DeviceBase), // Library data size
|
|
seg_list); // Segment list
|
|
|
|
if (mydev != NULL) {
|
|
ULONG ms_size = (sizeof(struct MountStruct) + (MAX_UNITS * sizeof(struct UnitStruct)));
|
|
Info("Add Device.\n");
|
|
AddDevice((struct Device *)mydev);
|
|
|
|
if ((ms = AllocMem(ms_size,MEMF_ANY|MEMF_PUBLIC)) == NULL) goto done;
|
|
|
|
ms->deviceName = mydev->lib.lib_Node.ln_Name;
|
|
ms->creatorName = NULL;
|
|
ms->numUnits = 0;
|
|
ms->SysBase = SysBase;
|
|
|
|
UWORD index = 0;
|
|
#if CDBOOT
|
|
BOOL CDBoot = FindCDFS();
|
|
#endif
|
|
struct IDEUnit *unit;
|
|
|
|
if (SysBase->LibNode.lib_Version >= 36) {
|
|
ObtainSemaphoreShared(&mydev->ulSem);
|
|
} else {
|
|
ObtainSemaphore(&mydev->ulSem);
|
|
}
|
|
|
|
for (unit = (struct IDEUnit *)mydev->units.mlh_Head;
|
|
unit->mn_Node.mln_Succ != NULL;
|
|
unit = (struct IDEUnit *)unit->mn_Node.mln_Succ)
|
|
{
|
|
if (unit->present == true) {
|
|
#if CDBOOT
|
|
// If CDFS not resident don't bother adding the CDROM to the mountlist
|
|
if (unit->deviceType == DG_CDROM && !CDBoot) continue;
|
|
#endif
|
|
ms->Units[index].unitNum = unit->unitNum;
|
|
ms->Units[index].configDev = unit->cd;
|
|
index++;
|
|
}
|
|
}
|
|
|
|
ms->numUnits = index;
|
|
|
|
ReleaseSemaphore(&mydev->ulSem);
|
|
if (ms->numUnits > 0) {
|
|
MountDrive(ms);
|
|
}
|
|
|
|
FreeMem(ms,ms_size);
|
|
}
|
|
done:
|
|
return (struct Library *)mydev;
|
|
}
|