From: Alan Cox Date: Sat, 10 Oct 2015 19:49:28 +0000 (+0100) Subject: dev: add first draft of the SCSI layer X-Git-Url: https://git.ndcode.org/public/gitweb.cgi?a=commitdiff_plain;h=5ae4ca4256792c6d25b8b3f10340f0bbd31f76a0;p=FUZIX.git dev: add first draft of the SCSI layer Still working on an xroar emulation of the tc3 to debug test this --- diff --git a/Kernel/dev/devscsi.c b/Kernel/dev/devscsi.c new file mode 100644 index 00000000..0d394a62 --- /dev/null +++ b/Kernel/dev/devscsi.c @@ -0,0 +1,189 @@ +/* + * Fuzix SCSI/SASI inteface logic + * + * Based upon the code from OMU68K by the late Dr Steve Hosgood & + * Terry Barnaby, licensed under the GPLv2. + */ + +#define _SCSI_PRIVATE + +#include +#include +#include +#include +#include +#include +#include + +/* The live command block */ +struct Sidcmd si_dcb; +uint8_t si_user; + +/* If we get more then union this lot */ +static struct Sierr error; +static uint8_t status[2]; + +/* + * Si_cmdend() Wait till end of scsi comand and return errors. + */ +static uint8_t si_cmdend(void) { + int err; + + status[0] = -1; + si_dcb.length = 2; + si_user = 0; + /* Gets status and message byte */ + if((err = si_read(status)) != 0) + return err; + else return status[0]; +} + +/* + * Si_err() SCSI error recovery + */ +static int si_err(const char *str, int err) +{ + kprintf("scsi%d: %s dev %d cmd %x err %d\n", + si_dcb.bus, str, si_dcb.device, si_dcb.cb.cb0.opcode, err); + + /* Get error info from drive */ + si_dcb.cb.cb0.opcode = SIREQSENSE; + si_dcb.cb.cb0.hiblock = 0; + si_dcb.cb.cb0.miblock = 0; + si_dcb.cb.cb0.loblock = 0; + si_dcb.cb.cb0.noblocks = 18; + si_dcb.cb.cb0.control = 0; + si_dcb.length = 18; + si_dcb.direction = SIDIR_READ; + si_user = 0; + + if (si_docmd((uint8_t *)&error)) { + kprintf("sd: no sense\n", si_dcb.device); + return -1; + } + kprintf("sd: sense %x err %x\n",error.sensekey, + error.errcode); + return 0; +} + +/* + * Si_docmd() Do si cmd + */ + +int si_docmd(uint8_t *data) +{ + int err; + + /* Select the drive */ + if((err = si_select()) != 0) + return err; + + /* Send the command block */ + if(SICLASS(si_dcb.cb.cb0.opcode) == 1) + err = si_writecmd(sizeof(struct Sidcb1)); + else + err = si_writecmd(sizeof(struct Sidcb0)); + + if (err) + return err; + + /* Do command actions */ + if (si_dcb.length) { + if (si_dcb.direction == SIDIR_READ) /* data in */ + err = si_read(data); + else + err = si_write(data); + if (err) + return err; + } + return si_cmdend(); +} + +/* + * Si_cmd() Execute SCSI command with given data + * Perform error recoverey (retry command) + */ + +uint8_t si_cmd(void) +{ + int count, err; + + /* FIXME: we need some kind of mapping helper from the platform */ + si_dcb.device = blk_op.blkdev->driver_data & DRIVE_NR_MASK; + si_dcb.lun = 0; + si_dcb.bus = 0; + + /* Very big disks might need READ10/WRITE10 etc, however if we add + them we need to use them only on big devices as they are not in + early devices or SASI */ + + /* Retry command */ + for(count = 0; count < 10; count++) { + /* Sets up command device control block */ + si_dcb.direction = blk_op.is_read ? SIDIR_READ : SIDIR_WRITE; + si_dcb.cb.cb0.opcode = blk_op.is_read ? SIREAD: SIWRITE; + si_dcb.cb.cb0.hiblock = (blk_op.lba >> 16) & 0x1F; + si_dcb.cb.cb0.miblock = (blk_op.lba >> 8) & 0xFF; + si_dcb.cb.cb0.loblock = blk_op.lba & 0xFF; + /* FIXME: if we do non 512 byte blocks we need to be smarter here */ + si_dcb.cb.cb0.noblocks = blk_op.nblock; + si_dcb.cb.cb0.control = 0; + si_dcb.length = 512 * blk_op.nblock; + + /* User or kernel target ? */ + si_user = blk_op.is_user; + + if(!(err = si_docmd(blk_op.addr))) + break; + si_clear(); /* Clears any data on scsi bus */ + /* si_restore(); Needed ?? - eg for SASI ? */ + } + + /* If any errors print error message */ + if(count) { + /* Caution: si_err trashes si_dcb */ + /* If fatal error */ + if(count == 10){ + si_err("Fatal error\007 10 retries", err); + si_reset(); + return 0; + } + /* If recoverable error */ + else { + si_err("Recovered", err); + } + } + return blk_op.nblock; +} + +/* This needs plumbing into a /dev/sg device off devsys */ + +int si_ioctl(uint8_t dev, uarg_t req, char *data) +{ + struct Siioctl sip; + + /* Upper layer already checked for root */ + if (req != HDIO_RAWCMD) + return -1; + + /* By a cunning co-incidence the kernel and user ioctl block happen + to look identical */ + if (uget(data, &sip, sizeof(sip))) + return -1; + memcpy(&si_dcb, &sip.si_dcb, sizeof(si_dcb)); + + /* Make sure the address given for the user mode I/O is valid */ + if (!valaddr((void *)sip.si_data, sip.si_dcb.length)) + return -1; + /* Set it up as a user mode read/write */ + blk_op.addr = sip.si_data; + blk_op.is_user = 1; + /* Issue the command block */ + return si_docmd(sip.si_data); +} + +/* TODO: issue a cache flush */ +int si_flush(void) +{ + return 0; +} diff --git a/Kernel/dev/devscsi.h b/Kernel/dev/devscsi.h new file mode 100644 index 00000000..66571b4e --- /dev/null +++ b/Kernel/dev/devscsi.h @@ -0,0 +1,161 @@ +/* + * Fuzix SCSI/SASI inteface logic + * + * Based upon the code from OMU68K by the late Dr Steve Hosgood & + * Terry Barnaby, licensed under the GPLv2. + */ + +/* + * Scsi data control block Class 0 comands + */ +struct Sidcb0 { + uint8_t opcode; + uint8_t hiblock; /* Top bits are LUN in SASI & early SCSI */ + uint8_t miblock; + uint8_t loblock; + uint8_t noblocks; + uint8_t control; +}; + +/* + * Scsi data control block Class 1 comands + */ +struct Sidcb1 { + uint8_t opcode; + uint8_t none_1; + uint8_t none_2; + uint8_t hiblock; + uint8_t miblock; + uint8_t loblock; + uint8_t none1; + uint8_t hnoblocks; + uint8_t lnoblocks; + uint8_t control; +}; + +/* Largest block we should ever need to send */ +struct Sidcbmax { + uint8_t buf[16]; +}; + +struct Sidcmd { + /* These must come first */ + union { + struct Sidcb0 cb0; + struct Sidcb1 cb1; + struct Sidcbmax bytes; + } cb; + /* Length must follow the dcb - some asm code assumes this on + certain platforms. If we add a bigger dcb we'll need to deal + with the effect */ + uint16_t length; + uint8_t direction; +#define SIDIR_NONE 0 +#define SIDIR_READ 1 +#define SIDIR_WRITE 2 + /* We need to think a bit more about what this means and have a + LUN map for the blkdev entries ! */ + uint8_t bus; + uint8_t device; + uint8_t lun; +}; + +/* + * Scsi commands + */ +# define SICLASS(a) (((a) >> 5) & 0x3) /* Scsi opcode class */ +# define SITEST_RDY 0 /* Test unit ready */ +# define SIREZERO 1 /* Rezero unit */ +# define SIREQSENSE 3 /* Request sense */ +# define SIFORMAT 4 +# define SIREASSIGNBLKS 7 +# define SIREAD 8 +# define SIWRITE 0x0A +# define SISEEK 0x0B +# define SIREADUSAGE 0x11 +# define SIINQUIRY 0x12 +# define SIMODE_SELECT 0x15 +# define SIRESERVE 0x16 +# define SIRELEASE 0x17 +# define SIMODE_SENSE 0x1A +# define SISTART_STOP 0x1B +# define SIREC_DIAG 0x1C +# define SISEND_DIAG 0x1D +# define SIREAD_CAP 0x25 +# define SIREADBIG 0x28 +# define SIWRITEBIG 0x2A +# define SIREAD_DEFECT 0x37 +# define SICERTIFY 0xE2 + +/* SCSI Sense errors */ +struct Sierr { + uint8_t logadd; + uint8_t none0; + uint8_t sensekey; + uint8_t hiblk; + uint8_t mhiblk; + uint8_t mlblk; + uint8_t loblk; + uint8_t none1[5]; + uint8_t errcode; + uint8_t none2[5]; +}; + +/* IOCTL command structure */ +struct Siioctl { + struct Sidcmd si_dcb; /* Command to scsi */ + uint8_t *si_data; /* Pointer to data area */ +}; + +struct Sicap { + uint32_t lblock; + uint32_t blocklen; +}; + +/* SCSI errors: FIXME */ +# define SCSI_SELTIMEOUT 250000 /* Scsi SEL timeout 0.25 secs Approx */ +# define SCSI_HSTIMEOUT 50000 /* Scsi Hand shake timeout */ +# define SIERR_BUSY 1 /* SCSI BUSY timeout */ +# define SIERR_NODEV 2 /* No device with the given ID */ +# define SIERR_NOREQTX 3 /* No request on tx timeout */ +# define SIERR_NOREQRX 4 /* No request on rx timeout */ + + +#define DRIVE_NR_MASK 0x1F + +#ifndef NSCSI +#define NSCSI 7 +#endif + +/* SCSI layer provides */ + +extern struct Sidcmd si_dcb; +extern uint8_t si_device; +extern uint8_t si_user; + +/* Blkdev interface for SCSI disk I/O */ +extern uint8_t si_cmd(void); +/* Ioctl */ +extern int si_ioctl(uint8_t dev, uarg_t req, char *data); +/* Cache flush */ +extern int si_flush(void); +/* Low level command issue (internal use) */ +extern int si_docmd(uint8_t *data); + +/* Discardables (we don't do hot plug) */ +extern void scsi_init(void); + +/* Driver provided */ + +/* Read dcb.length bytes into the given pointer: honour user/kernel flags */ +extern uint8_t si_read(uint8_t *ptr); +/* Write dcb.length bytes from the given pointer: honour user/kernel flags */ +extern uint8_t si_write(uint8_t *ptr); +/* Write the dcb command block for length bytes to the device */ +extern uint8_t si_writecmd(uint8_t len); +/* Select the device sidev. Must immediately fail if sidev is the controller id */ +extern uint8_t si_select(void); +/* Clear anything stuck on the bus, or wrong directions */ +extern void si_clear(void); +/* Reset the bus if possible */ +extern void si_reset(void); diff --git a/Kernel/dev/devscsi_discard.c b/Kernel/dev/devscsi_discard.c new file mode 100644 index 00000000..35aae29d --- /dev/null +++ b/Kernel/dev/devscsi_discard.c @@ -0,0 +1,113 @@ +/* + * Limits of our SCSI code + * - No multi-lun + * - No disconnect + * - Simple 512 byte/sector disks only (easily fixed for 512, more is hard) + * - No removable media (not too hard to fix - but removable disk is rare) + * - No useful scsi ioctl API in scsi generic style (needs fixing) + * - No CDROM (hard on 8bit) + * - No tape etc + */ + +#define _SCSI_PRIVATE + +#include +#include +#include +#include +#include +#include +#include + +static uint8_t nscsi; +static uint8_t identify[36]; +static uint8_t cap[8]; + +void scsi_dev_init(uint8_t drive) +{ + blkdev_t *blk; + uint8_t *p; + uint16_t secsize; + + /* FIXME: need to sort controller mapping policy here too */ + si_dcb.device = drive; + si_dcb.lun = 0; + si_dcb.bus = 0; + if (si_select()) /* Can't select it - probably not present */ + return; + /* FIXME: check if this would be better as a memcpy of fixed struct */ + si_dcb.cb.cb0.opcode = SIINQUIRY; + si_dcb.cb.cb0.hiblock = 0; + si_dcb.cb.cb0.miblock = 0; + si_dcb.cb.cb0.loblock = 0; + si_dcb.cb.cb0.noblocks = 36; + si_dcb.cb.cb0.control = 0; + si_dcb.length = 36; + si_dcb.direction = SIDIR_READ; + si_user = 0; + if (si_docmd(identify)) + return; + + p = identify + 8; + while (p < identify + 27) + kputchar(*p++); + kputchar('\n'); + + /* Ok the device exists, but we may not be able to drive it */ + switch(identify[0] & 0x1F) { + case 0x00: /* Hard Disk */ + case 0x07: /* Optical */ + case 0x0C: /* RAID */ + case 0x0E: /* RBC */ + break; + default: + return; + } + /* We should spin the device up I guess. We don't currently + support removable media - that would need us to defer much of this to + open and add an open hook to the blkdev layer */ + + /* FIXME: use Test Unit ready - but note that for SASI at least TUR + is optional */ + + /* Read capacity tells us the disk size */ + memset(&si_dcb.cb.cb0, 0, sizeof(si_dcb.cb.cb0)); + si_dcb.cb.cb0.opcode = SIREAD_CAP; + si_dcb.length = 8; + si_dcb.direction = SIDIR_READ; + si_user = 0; + if (si_docmd(cap)) + return; + + if (cap[4] || cap[5]) /* Block size over 64K */ + return; + secsize = (cap[6] << 8) | cap[7]; + if (secsize != 512) { + kprintf("scsi: unsupported sector size %d\n", secsize); + return; + } + + /* Ok we pass. Allocate a disk device */ + blk = blkdev_alloc(); + if (!blk) + return; + blk->transfer = si_cmd; + blk->flush = si_flush; + blk->driver_data = drive; + /* Very big disks report FFFFFFFF if they overrun this. We don't care we + currently only speak READ6 anyway ! */ + blk->drive_lba_count = ((uint32_t)cap[0] << 24) | ((uint32_t)cap[1] << 16) | ((uint16_t)cap[2] << 8) | cap[3]; + blkdev_scan(blk, SWAPSCAN); +} + +void scsi_init(void) +{ + uint8_t i; + /* This is a bit crude - we need to do proper scans of each controller + according to devs/luns etc */ + for (i = 0; i < NSCSI; i++) { + kprintf("\rSCSI drive %d: ", i); + scsi_dev_init(i); + } + kprintf("\r%d SCSI device(s) detected.\n", nscsi); +}