Skip to content

Instantly share code, notes, and snippets.

@darkstar
Last active April 25, 2019 17:57
Show Gist options
  • Save darkstar/e8534377d97b716e1d0a28006f7412dd to your computer and use it in GitHub Desktop.
Save darkstar/e8534377d97b716e1d0a28006f7412dd to your computer and use it in GitHub Desktop.
VARCem SCSI Modepage rework

First, we have a device personality that can be "attached" to any (SCSI-) device. If the user does not select/attach a personality, some standard or 'default' personality would be used (probably the one with the name default)

struct device_personality {
  char *name;                         /* the name for the personality, to display in the UI */
  /* other required properties, like is this for a SCSI HDD, or SCSI CD ROM? or ATA HDD/CDROM? etc.  */
  struct device_personality *parent;  /* a simple inheritance model, device personalities can inherit
                                         from others and only override those modepages that they care
                                         about.
                                         TODO:
                                         - What to override? Only complete pages, or also fields inside
                                           existing pages?
                                         - add new modepages to an existing personality? (we probably
                                           want that) */
  struct modepage_template **modepages; /* a list of modepage_templates, zero-terminated */
  struct vpd_template **vpd_pages;      /* a list of VPD pages, zero-terminated */
  /* possibly other data for a personality... things like default c/h/s geometry, etc.? */
}

This personality references a list of "templates" that will be used to build the "raw" modepage buffers. This way, the template can be "built" (in the source code) easier than just specifying raw byte values.

When instantiating a device with a personality (i.e. the user creates/attaches a SCSI disk with a personality), the code goes through the personality template once, and builds the "real" (i.e. "raw") mode page buffers from that data. That way the template stays read-only, and the "build" step can later include things like fetching the "saved" values from a file or anywhere else.

The rough process goes like this:

  • Find the topmost "parent" personality (that one which has a parent pointer of 0)
  • "build" the mode pages from that personality
  • for each child up to the user-selected personality:
    • Add any "new" mode pages that this child template provides
    • "patch" (i.e. override) all existing modepages with the (changed) fields from the child
    • repeat until all templates have been parsed/built
  • Each "build" or "patch" step updates the "default" mode page buffer, the "changeable" buffer, and copies the "default" to the "saved" and "current" buffers
    • that way, default, saved and current end up the same the first time the device is initialized
  • after this, optionally restore "saved" values from some kind of backing store (suitable HDD image, cfg file, nvram file, whatever)
  • also, the "current" values could be saved on exit and restored on the next load.
    • I'm currently not sure if the "current" values are supposed to be the ones that get set up after power up of a device, or if the device always resets to the "saved" values...
  • another thing to take care of: after instantiating the device, the geometry/lba/chs settings in the corresponding mode-pages should be updated with the real numbers from the backing virtual harddisk file
    • For new harddisks, selecting a non-default personality could restrict the chs values to those values that were known to exist in these particular harddisks
    • optionally let the user override the geometry if he wants
    • on "attaching" a personality to a harddisk that is not of the correct geometry, a warning could be shown that this particular type of hdd never existed in the first place

So how is each modepage template created?

First, we have something like this:

struct modepage_template {
  uint8_t page;      /* the number of the mode page */
  uint8_t subpage;   /* some mode pages have sub pages. 
                        If not, then this field is zero (or 0xff?) */
  uint16_t length;   /* the length, in bytes, of the mode page. If subpage == 0,
                        then only the lower 8 bits are used, mode pages with sub
                        pages seem to always use 16 bit as the length? */
  struct modepage_field *fields;  /* these are the various fields that make up this
                                     mode page. They are described next */
}

The fields are a list of "declarations" that make up each mode page. They are designed to be created by preprocessor macros which makes them easily human-readable (in the source code). There are multiple types of fields, for the different types that exist in the SCSI modepage specs.

The types are:

enum {
  mp_field_bitfield,   /* the field is a bitfield with 1..7 bits */
  mp_field_u8,         /* the field is an 8-bit byte, aligned at a byte boundary */
  mp_field_u16,        /* the field is an 8-bit byte, aligned at a byte boundary */
  mp_field_u24,        /* the field is an 16-bit value, aligned at a byte boundary */
  mp_field_u32,        /* the field is an 24-bit value, aligned at a byte boundary */
  mp_field_string,     /* the field is an ASCII string with a specific length */
  mp_field_raw         /* the field is a raw byte array */
}

The fields themselves look something like this

union modepage_field_data {      /* the values, depending on the type */
  uint8_t u8;                    /* for bitfields and byte */
  uint16_t u16;                  /* for u16 */
  uint32_t u24_32;               /* for u24 and u32 */
  unsigned char raw_data[100];   /* for ASCII and raw */
} /* TODO: maybe we should simply have only raw_data and convert each element 
     to that directly, that would make sure there are no endian issues... */

struct modepage_field {
  uint16_t startoffset;     /* the byte-offset where the field starts in the mode page buffer */
  uint16_t mask;            /* the mask that defines the valid bits. only for bitfields */
  uint16_t shift;           /* the left-shift that is applied after the mask. only for bit-fields */
  int changeable;           /* 1 if the field is changeable, 0 if not */
  int type;                 /* the type of the field, from the enum above */
  int length;               /* only for ASCII/raw types, the length of the data, in bytes */
  union modepage_field_data default_values;
}

Then, to "build" the modepage data, we would define some macros that build the "modepage_field" structs. Like so

struct modepage_template quantum_page_0x01 = {
  .page = 0x01,
  .subpage = 0x00,
  .length = 0x0a, /* remember, the buffer is 2 bytes bigger than this value. 
                     or 3 (4?) for pages with sub-pages */
  BEGIN_MODEPAGE_FIELDS() /* this expands to something like: ".fields = {" */
  MODEPAGE_BITFIELD_CHANGEABLE("AWRE"/*spec name*/, 0x02/*byteoffset*/, 0x01/*mask*/, 7/*shift*/, 1/*value*/),
  MODEPAGE_BITFIELD_CHANGEABLE("ARRE", 0x02, 0x01, 6, 1),
  MODEPAGE_BITFIELD_CHANGEABLE("TB", 0x02, 0x01, 5, 0),
  MODEPAGE_BITFIELD_CHANGEABLE("RC", 0x02, 0x01, 4, 0),
  MODEPAGE_BITFIELD_NONCHANGEABLE("EER", 0x02, 0x01, 3, 0),
  ....
  MODEPAGE_U16_CHANGEABLE("RTL", 0x0a, 0),
  END_MODEPAGE_FIELDS()   /* this expands to something like: "0 }"
}

other field macros can be used similarly:

MODEPAGE_U24_CHANGEABLE("name", startoffset, value)
MODEPAGE_U32_CHANGEABLE("name", startoffset, value)
MODEPAGE_STRING_CHANGEABLE("name", startoffset, "value")  /* uses "strlen()" as the implied length */
MODEPAGE_RAW_CHANGEABLE("name", startoffset, {0x00, 0x01, 0x02, 0x03, 0x04}, length)
...etc

We could also have some more "advanced" macros that do stuff like generate random serial numbers and stuff: MODEPAGE_SERIALNUMBER_CHANGEABLE("name", startoffset, "pattern") where pattern is something like AAA000000??, which would create a serial number with three capital letters, six numbers, and two numbers or letters at the end. This would probably be only useful if we store/retrieve the current/saved values every time that particular machine/device starts.

Those macros do all the magic of converting their parameters into a correct struct modepage_field_data.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment