====== Host Control Protocol ====== The device can be controlled over the ATAPI interface. This is achieved by using commands in the "vendor specific" space of the SCSI command space from 0xC0 to 0xFF. Note that all commands are subject to change in firmware updates. All structs are expected to be packed with something like #pragma pack(1). The example code here assumes you have a function "SendPACKET" with the declaration: bool SendPACKET(const uint8_t* cmd, int cmdLength, int cmdExtra, uint8_t* response, int respLength); ===== Detecting the device ===== The device can be detected by issuing a standard INQUIRY (0x12) command over ATAPI. An example INQUIRY packet in code might appear as: const uint8_t SCSI_INQUIRY = 0x12; uint8_t response[36]; const uint8_t cmd[12] = {SCSI_INQUIRY, 0, 0, 0, sizeof(response), 0}; bool found = SendPACKET(cmd, sizeof(cmd), 0, resp, sizeof(resp)); Sending this via the ATA "PACKET" command will generate a response of 36 bytes, containing amongst other things the manufacturer information. Confirm the string "MICE" appears here: found &= memcmp(resp + 8, "MICE ", 8) == 0; If any subsequent commands fail, it can be assumed that the firmware is too old to support the host commands, and the user can be prompted to install an update. ===== Getting device state ===== #define TATTIEBOGLE_SYSTEM_STATE 0xC0 ^ Request packet format ^^^^^^^^^^^^ ^0 ^1 ^2 ^3 ^4 ^5 ^6 ^7 ^8 ^9 ^10 ^11 ^ |TATTIEBOGLE_SYSTEM_STATE |response length high |response length low |0 |0 |0 |0 |0 |0 |0 |0 |0 | This command retrieves the "device state". This provides basic information about the device version, serial number and firmware. The structure returned is as follows: typedef struct { char name[50]; char version[50]; char date[12]; char notes[50]; } FIRMWARE_DATA; typedef struct { uint32_t magic; #define TB_MAGIC ('TTBG') uint32_t model; #define TB_MODEL_V1 ('IDEs') #define TB_MODEL_V2 ('IDE2') uint16_t hardware_revision; uint8_t serialNo[16]; uint16_t flags; #define TB_STATE_FLAG_SD_PRESENT (1 << 0) #define TB_STATE_FLAG_BOOTABLE_BANK0 (1 << 1) #define TB_STATE_FLAG_BOOTABLE_BANK1 (1 << 2) #define TB_STATE_FLAG_SELECTED_BANK0 (1 << 3) #define TB_STATE_FLAG_SELECTED_BANK1 (1 << 4) #define TB_STATE_FLAG_ACTIVE_BANK0 (1 << 5) #define TB_STATE_FLAG_ACTIVE_BANK1 (1 << 6) #define TB_STATE_FLAG_VALID_BANK0 (1 << 7) #define TB_STATE_FLAG_VALID_BANK1 (1 << 8) uint16_t usb[2]; #define TB_STATE_USB_MODE_MASK (0x03) #define TB_STATE_USB_MODE_NONE (0x00) #define TB_STATE_USB_MODE_HOST (0x01) #define TB_STATE_USB_MODE_DEVICE (0x02) #define TB_STATE_USB_SPEED_MASK (0x03 << 2) #define TB_STATE_USB_SPEED_LOW (0x01 << 2) #define TB_STATE_USB_SPEED_FULL (0x02 << 2) #define TB_STATE_USB_SPEED_HIGH (0x03 << 2) #define TB_STATE_USB_DRIVER_SHIFT (4) #define TB_STATE_USB_GET_DRIVER(x) ((int16_t)( \ ((x) >> TB_STATE_USB_DRIVER_SHIFT) | \ (((x) & 0x8000) ? (0xF << 12) : 0) \ )) FIRMWARE_DATA firmwares[2]; } TATTIEBOGLE_SYSTEM_STATE_RESPONSE; On devices which don't support flash banks, the output of the firmware A/B fields may instead represent the bootloader/application state. Similarly, devices which don't include all USB drivers may not return those values. Retrieving this struct is much the same as the INQUIRY: int sim_get_status(TATTIEBOGLE_SYSTEM_STATE_RESPONSE* state) { uint16_t len = sizeof(*state); uint8_t cmd[12] = {TATTIEBOGLE_SYSTEM_STATE, len >> 8, len & 0xFF, 0, 0, 0, 0, 0, 0, 0, 0, 0}; return SendPACKET(cmd, sizeof(cmd), 0, state, sizeof(*state)); } ===== Setting and listing a directory ===== #define TATTIEBOGLE_DIR_LISTING 0xC1 This command can both set the current directory, and list the current directory's contents. ^ Request packet format ^^^^^^^^^^^^ ^0 ^1 ^2 ^3 ^4 ^5 ^6 ^7 ^8 ^9 ^10 ^11 ^ |TATTIEBOGLE_DIR_LISTING |response length high |response length low |request length high |request length low |request port |0 |0 |0 |0 |0 |0 | ==== Setting directory ==== The directory can be set by providing the name of the directory as a data part of the command. This involves setting the "request length" fields and "request port". The ports are as follows: #define DEVICE_SD (1) #define DEVICE_USB0 (2) #define DEVICE_USB1 (3) Note that specific devices may only support certain ports. The system state request should indicate which ports are active, if any. The request is followed by the actual data for the packet, which is the requested path. Note that since IDE is 16-bit, the requested path must be even, so if it's an odd number of characters, padding must be added. Example: bool sim_set_directory(unsigned char port, const char *dir) { uint16_t outlen = (strlen(dir) + 1) & ~1; // must be even uint8_t cmd[12 + outlen]; memset(cmd, 0, sizeof(cmd)); cmd[0] = TATTIEBOGLE_DIR_LISTING; cmd[3] = outlen >> 8; cmd[4] = outlen & 0xFF; cmd[5] = port; memcpy(cmd + 12, dir, outlen); return SendPACKET(cmd, 12, outlen, NULL, 0); } ==== Reading the directory ==== The directory can be read by setting the "request" length fields. The usual length of such a request is 2048 bytes as this is the common request size for CD/DVD-ROM drivers. Example: bool sim_read_directory(unsigned char *response, int len) { char cmd[12] = {TATTIEBOGLE_DIR_LISTING, len >> 8, len & 0xFF, 0, 0, 0, 0, 0, 0, 0, 0, 0}; return SendPACKET(cmd, sizeof(cmd), 0, response, len); } This request can be repeated to continue reading contents of a directory exceeding the size of a single 2Kb request. Example: static char data[2048]; while (sim_read_directory(data, sizeof(data))) { for (int i = 0; i < (sizeof(data) - sizeof(TATTIEBOGLE_DIR_LISTING_RESPONSE));) { TATTIEBOGLE_DIR_LISTING_RESPONSE *entry = (TATTIEBOGLE_DIR_LISTING_RESPONSE*)(data + i); if (entry->type == FTYPE_INVALID) break; // Use 'entry' here i += sizeof(*entry) + entry->namelen; } } ===== Selecting a new image ===== #define TATTIEBOGLE_SELECT_IMAGE 0xC2 ^ Request packet format ^^^^^^^^^^^^ ^0 ^1 ^2 ^3 ^4 ^5 ^6 ^7 ^8 ^9 ^10 ^11 ^ |TATTIEBOGLE_SELECT_IMAGE |0 |0 |request length high |request length low |request port |0 |0 |0 |0 |0 |0 | This command works the same as the "set directory" command, but will cause the device to load the new disk image as the current "disk" in the "drive". The "port" constants are also the same. Example: bool sim_set_image(unsigned char port, const char* dir) { unsigned short outlen = (strlen(dir) + 1) & ~1; // must be even uint8_t cmd[12 + outlen]; memset(cmd, 0, sizeof(cmd)); cmd[0] = TATTIEBOGLE_SELECT_IMAGE; cmd[3] = outlen >> 8; cmd[4] = outlen & 0xFF; cmd[5] = port; memcpy(cmd + 12, dir, outlen); return SendPACKET(cmd, 12, outlen, NULL, 0); }