--- /dev/null
+/*
+ * 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 <kernel.h>
+#include <kdata.h>
+#include <printf.h>
+#include <stdbool.h>
+#include <timer.h>
+#include <devscsi.h>
+#include <blkdev.h>
+
+/* 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;
+}
--- /dev/null
+/*
+ * 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);
--- /dev/null
+/*
+ * 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 <kernel.h>
+#include <kdata.h>
+#include <printf.h>
+#include <stdbool.h>
+#include <timer.h>
+#include <devscsi.h>
+#include <blkdev.h>
+
+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);
+}