dev: add first draft of the SCSI layer
authorAlan Cox <alan@linux.intel.com>
Sat, 10 Oct 2015 19:49:28 +0000 (20:49 +0100)
committerAlan Cox <alan@linux.intel.com>
Sat, 10 Oct 2015 19:49:28 +0000 (20:49 +0100)
Still working on an xroar emulation of the tc3 to debug test this

Kernel/dev/devscsi.c [new file with mode: 0644]
Kernel/dev/devscsi.h [new file with mode: 0644]
Kernel/dev/devscsi_discard.c [new file with mode: 0644]

diff --git a/Kernel/dev/devscsi.c b/Kernel/dev/devscsi.c
new file mode 100644 (file)
index 0000000..0d394a6
--- /dev/null
@@ -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 <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;
+}
diff --git a/Kernel/dev/devscsi.h b/Kernel/dev/devscsi.h
new file mode 100644 (file)
index 0000000..66571b4
--- /dev/null
@@ -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 (file)
index 0000000..35aae29
--- /dev/null
@@ -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 <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);
+}