/* * JR-IDE Project * - (c) 2017 Alan Hightower * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "netpi.h" #include #include #include #include #include #include #include #include "machxo2_api.h" #define DRIVE_FW_VERSION "20161102" int verbose = 0; int serial_no = 0; int fw_ver_major = 0, fw_ver_minor = 0; ide_regs_t ide; #define IFIFO_SIZE 1024 // words #define OFIFO_SIZE 1024 // words static drive_t *master = NULL; static drive_t *slave = NULL; // First byte of the SPI payload is always the command action #define SPI_CMD_NOP 0 // No operation #define SPI_CMD_FIFO_READ 4 // Read IDE register file #define SPI_CMD_FIFO_WRITE 5 // Update IDE regsiter file #define SPI_CMD_REG_READ 6 // Read IDE payload data from host #define SPI_CMD_REG_WRITE 7 // Write IDE payload data to host /* * While there are two general purpose pins routed to the CPLD * only one is in use at the moment. It is level sensitive and * active low. It is set by the CPLD every time the IDE command * register is written and cleared by this program and pushed * via ide_update. These functions setup the PI GPIO subsystem * to configure it as an input, read it status, and potentially * sleep until it's value changes (idle function of main loop). */ static int gpioa_fd = -1; static void gpio_term (void) { if (gpioa_fd >= 0) close (gpioa_fd); gpioa_fd = -1; return; } static int gpio_init (void) { if ((gpioa_fd = open ("/sys/class/gpio/gpio24/value", O_RDWR)) < 0) { LOGERR("Unable to open BCM24 GPIO - %s", strerror (errno)); gpio_term (); return -1; } return 0; } static int irq_pending = 0; static int gpio_update (void) { char ch = 0; if ((lseek (gpioa_fd, 0L, SEEK_SET) == 0L) && (read (gpioa_fd, &ch, 1) == 1)) irq_pending = (ch == '0'); else return -1; return 0; } static int gpio_sleep (void) { struct timeval tv; gettimeofday (&tv, NULL); struct pollfd fds; fds.fd = gpioa_fd; fds.events = POLLPRI; fds.revents = 0; poll (&fds, 1, 1000 - (tv.tv_usec / 1000)); return gpio_update (); } /* * Utility function to dump transfer buffers if verbose > 2 */ void hexdump (uint32_t offset, void *data, int len) { int i, addr = 0; uint8_t *ptr = (uint8_t *) data; while (len > 0) { printf (" 0x%08x:", (unsigned int) (addr + offset)); for (i = 0; i < 16; i++) { if (i < len) { printf (" %02x", ptr[i]); } else puts (" "); if (i == 7) putc (' ', stdout); } putc (' ', stdout); for (i = 0; i < 16; i++) { if (i < len) { printf ("%c", isalnum(ptr[i]) ? ptr[i] : '.'); } if (i == 7) putc (' ', stdout); } len -= 16; ptr += 16; addr += 16; putc ('\n', stdout); } return; } /* * Central to this design is the ability to upgrade the CPLD code * without using an external programming device. I chose the MachXO2 * due to intimate familiarity and pre-developed legacy support tools; * like this firmware update API. * * The MachXO2 allows update of on-board firmware over I2C - even if * the device is blank from the factory. The firmware image is always * embedded in this executable image so the firmware and this code * will always be in lock-step as long as the -u option is used. * * In the future, I plan on switching to an ice40HX series as there * is a fully open synthesis toolchain that could run directly on the * Raspberry PI. */ static int i2c_fd = -1; static int _i2c_exchange (void *ctx, uint8_t *out, uint8_t out_len, uint8_t *in, uint8_t in_len) { struct i2c_rdwr_ioctl_data list; struct i2c_msg msg[2]; list.msgs = msg; list.nmsgs = 0; if (out_len) { msg[list.nmsgs].addr = I2C_EFB_ADDR; msg[list.nmsgs].flags = 0; msg[list.nmsgs].len = out_len; msg[list.nmsgs].buf = out; list.nmsgs++; } if (in_len) { msg[list.nmsgs].addr = I2C_EFB_ADDR; msg[list.nmsgs].flags = I2C_M_RD | I2C_M_NOSTART; msg[list.nmsgs].len = in_len; msg[list.nmsgs].buf = in; list.nmsgs++; } int rc = ioctl (i2c_fd, I2C_RDWR, &list); return (rc == (int) list.nmsgs) ? 0 : -1; } extern const uint8_t _binary_firmware_jpf_start[]; static int _cpld_version_up (void) { int sts = -1; xo2_handle_t *xo2 = NULL; do { if ((i2c_fd = open (I2C_EFB_DEVICE, O_RDWR)) < 0) { LOGERR("Unable to open '%s' - %s", I2C_EFB_DEVICE, strerror (errno)); break; } if (!(xo2 = xo2_open (_i2c_exchange, NULL))) { LOGERR("Unable to initialize XO2:EFB"); break; } uint32_t dev_usercode = 0xdeadbeaf, img_usercode = 0xdeadbeaf; uint32_t dev_deviceid = 0xdeadbeaf, img_deviceid = 0xdeadbeaf; xo2_file_info (_binary_firmware_jpf_start, &img_usercode, &img_deviceid); if (xo2_read_device_id (xo2, &dev_deviceid)) { LOGERR("Unable to read device ID"); break; } if (dev_deviceid != img_deviceid) { LOGERR("Incompatible device ID (dev=%08xx/img=%08x)", dev_deviceid, img_deviceid); break; } if (xo2_read_user_code (xo2, &dev_usercode)) { LOGERR("Unable to read user code"); break; } if ((dev_usercode ^ img_usercode) & 0x0000ffff) { LOGDBG("Programming CPLD version %d.%02d\n", (img_usercode >> 8) & 0xff, img_usercode & 0xff); uint32_t mode = XO2_MODE_ERASE_CONFIG | XO2_MODE_ERASE_UFM | XO2_MODE_ERASE_FEATROW | XO2_MODE_PROGRAM_CONFIG | XO2_MODE_PROGRAM_UFM | XO2_MODE_PROGRAM_FEATROW | XO2_MODE_VERIFY_CONFIG | XO2_MODE_VERIFY_UFM | XO2_MODE_VERIFY_FEATROW | XO2_MODE_OFFLINE; if (xo2_program (xo2, _binary_firmware_jpf_start, mode)) LOGERR("Unable to program PLD image"); } sts = 0; } while (0); if (xo2) xo2_close (xo2); if (i2c_fd >= 0) { close (i2c_fd); i2c_fd = -1; } return sts; } // Update local copy of IDE register file from CPLD int ide_refresh (void) { LOGDB3 ("IDE Refresh Registers\n"); ide.spi_cmd = SPI_CMD_REG_READ; ide.spi_arg0 = 0; ide.spi_arg1 = 0; ide.spi_arg2 = 0; if (spi_xfer (&ide, sizeof (ide_regs_t)) != sizeof (ide_regs_t)) return -1; if ((fw_ver_major == 0) && ide.spi_arg0) { fw_ver_major = ide.spi_arg0; fw_ver_minor = ide.spi_arg1; LOGDBG("CPLD Firmware version %d.%02d\n", fw_ver_major, fw_ver_minor); } return 0; } // Push local copy of IDE register file to CPLD int ide_update (int iadj, int oadj) { LOGDB2 ("IDE Update Registers\n"); ide.ififo_cnt = iadj; ide.ofifo_cnt = oadj; ide.spi_cmd = SPI_CMD_REG_WRITE; ide.spi_arg0 = 0; ide.spi_arg1 = 0; ide.spi_arg2 = 0; ide.IRQPIn = 1; // disable pending IRQ to Pi if (spi_xfer (&ide, sizeof (ide_regs_t)) != sizeof (ide_regs_t)) return -1; ide.ififo_cnt += iadj; ide.ofifo_cnt += oadj; return 0; } /* * _fixup_LBA is a general helper function that a) translates supplied * CHS geometry to LBA if the LBA flag isn't set, and b) translates * the supplied or translated LBA number to starting byte offset * and ending location based on supplied transfer size. */ static void _fixup_LBA (uint64_t *offset, uint32_t *words, uint32_t *lastsec) { drive_t *drive = ide.SLAVE ? slave : master; if (!ide.LBAM) { uint32_t sector = ide.lba & 0xff; uint32_t head = (ide.lba >> 24) & 0x0f; uint32_t cyl = (ide.lba >> 8) & 0xffff; ide.lba = (cyl * drive->heads + head) * drive->spt + (sector - 1); } uint32_t scount = ide.secz ? ide.secz : 256; *offset = ((uint64_t) ide.lba << 9); *words = scount << 8; *lastsec = ide.lba + scount - 1; return; } static void _reset_signature (void) { ide.SRST = 0; ide.IENn = 0; ide.secz = 1; ide.lba = 1; return; } /* * IDE transfers are fundamentally half-duplex - that is each * command code has an implicit direction and common IDE data * status flags are used to indicate buffer fullness. * * svc_buffer is a common (in & out) holding area for data * gathered from the CPLD or queued to the CPLD. The following * functions handle keeping the FIFO full or drained based on * the pending counts in svc_iwords/svc_owords (only one can be * non-zero. * * It may seem odd, but there are two separate FIFOs for IN/OUT * in the CPLD. This is only because a) it simplifys multiplexing * the end points of the FIFO, and b) there is enough FIFO space * to not have to work about resource constraints. * * Each FIFO (direction) has 1024 words = 2048 bytes; enough to * hold 4 sectors. <256 word transfer sizes are not currently * used (all padded to 256), but the code shouldn't be too far * off from complete (testing mostly). * * The 128 KB max size below comes from a max sector transfer * count of 256. (256 * 512 = 128KB) * * FIFO availability is entirely maintained on the CPLD side. * We push adjustment values during ide_update in theses functions * to adjust the value maintained by the CPLD during IDE update. */ // Write data to the IDE PI->CPLD FIFO int ide_write (void *data, int words) { int len = words << 1; drive_t *drive = ide.SLAVE ? slave : master; uint8_t *block = (uint8_t *) alloca (len + 5); block[0] = SPI_CMD_FIFO_WRITE; block[1] = 0; // arg:0 block[2] = (words >> 8) & 0xff; block[3] = (words ) & 0xff; memcpy (&block[4], data, len); block[len + 4] = 0; // dummy byte if (spi_xfer (block, len + 5) != (len + 5)) return -1; drive->read_diff += words; drive->read_total += words; return words; } // Read data from the IDE CPLD->PI FIFO int ide_read (void *data, int words) { int len = words << 1; drive_t *drive = ide.SLAVE ? slave : master; uint8_t *block = (uint8_t *) alloca (len + 5); block[0] = SPI_CMD_FIFO_READ; block[1] = 0; // arg:0 block[2] = (words >> 8) & 0xff; block[3] = (words ) & 0xff; if (spi_xfer (block, len + 5) != (len + 5)) return -1; memcpy (data, &block[5], len); drive->write_diff += words; drive->write_total += words; return words; } // Holding area and status for high level data transfers static uint8_t svc_buffer[128 * 1024], svc_cmd = 0, svc_sub = 0; static int svc_iwords = 0, svc_owords = 0, svc_offset = 0; static drive_t *svc_drive = NULL; static void _proc_buffer_complete (void); void data_in_setup (uint8_t cmd, uint8_t sub, int words) { LOGDB2("Out-bound FIFO: Expecting %d words\n", words); svc_offset = 0; svc_owords = words; svc_cmd = cmd; svc_sub = sub; ide.DRQ = DRQ_MODE_WRITESEC; return; } void *data_out_setup (int words) { (void) words; return svc_buffer; } void data_out_commit (int words) { LOGDB2("In-bound FIFO: Queued %d words\n", words); svc_offset = 0; svc_iwords = words; ide.DRQ = DRQ_MODE_READSEC; return; } // Service CPLD->PI data fifo (read from persepective of this prog) static void service_datain (void) { while (svc_owords) { /* * OFIFO count is the number of words of free available * space in the CPLD->PI FIFO. Like the IFIFO check * above, the XT-IDE logic will look at DRQ only before * blasting 256 words into the fifo with rep movsw. So * any partial sector fragment may leave the FIFO without * enough space to accept a full sector. So only pull * data out if there is a full sector to transfer. And * only atomically adjust by multiples of 256 words. */ if ((OFIFO_SIZE - ide.ofifo_cnt) < 256) { LOGDB2("Out-bound FIFO: empty - pausing\n"); break; } int words = (svc_owords > 256) ? 256 : svc_owords; // Future proof for < 256 transfer sizes if (words > (OFIFO_SIZE - ide.ofifo_cnt)) words = OFIFO_SIZE - ide.ofifo_cnt; LOGDB2("Out-bound FIFO: Reading %d words\n", words); ide_read (&svc_buffer[svc_offset << 1], words); if (verbose > 2) hexdump (svc_offset, &svc_buffer[svc_offset << 1], words << 1); svc_offset += words; svc_owords -= words; if (svc_owords == 0) ide.DRDY = 1; ide_update (0, words); } if (svc_owords == 0) _proc_buffer_complete (); return; } // Service PI->CPLD data fifo (write from persepective of this prog) static void service_dataout (void) { while (svc_iwords) { /* * Don't write anything until there is enough space * hold an entire sector. The way the XT-IDE BIOS * specifically works is as long as the DRQ flag is * set, it will rep movsw 256 times. If there is a * partial sector, it will under-flow. Writing any- * thing less than a full sector here creates a * timing opportunity if we cannot keep up to have * only a sector fragment in the IFIFO. This is * bad. * * Data availability will not be reflected until we * update IDE regs below with a FIFO adjustment * value. */ if (ide.ififo_cnt >= (IFIFO_SIZE - 255)) { LOGDB2("In-bound FIFO: full - pausing\n"); break; } int words = (svc_iwords > 256) ? 256 : svc_iwords; if (words > (IFIFO_SIZE - ide.ififo_cnt)) words = IFIFO_SIZE - ide.ififo_cnt; LOGDB2("In-bound FIFO: Writing %d words\n", words); ide_write (&svc_buffer[svc_offset << 1], words); svc_offset += words; svc_iwords -= words; ide_update (words, 0); } return; } static struct timeval stat_tv = { 0, 0 }; // Print thoughput numbers every seconds as long as xfers running static void update_stats (void) { struct timeval now; gettimeofday (&now, NULL); time_t delta = now.tv_sec - stat_tv.tv_sec; if (delta) { if (delta == 1) { if (master && (master->read_diff || master->write_diff)) { LOGDBG ("STAT [MASTER]: Write=%d B/s - Read=%d B/s]\n", master->write_diff << 1, master->read_diff << 1); } if (slave && (slave->read_diff || slave->write_diff)) { LOGDBG ("STAT [SLAVE]: Write=%d B/s - Read=%d B/s]\n", slave->write_diff << 1, slave->read_diff << 1); } } if (master) { master->read_diff = 0; master->write_diff = 0; } if (slave) { slave->read_diff = 0; slave->write_diff = 0; } memcpy (&stat_tv, &now, sizeof (struct timeval)); } return; } /* * Currently both hardware and software support limit ATA spec * support to ATAPI-4 / PIO mode only. This function creates * an ID sector based on the supplied drive profile. * * Some of the limitations is lack of software support for LBA48, * lack of hardware implementation for interrupt generation (hw * supports it) and lack of hardware signal support for DMA/UDMA. */ static void _flip_words (uint16_t *word, int count) { while (count) { uint16_t new = ((*word & 0xff) << 8) | ((*word >> 8) & 0xff); *word = new; count--; word++; } return; } static uint8_t ide_multiple = 1; static void _create_idsector (uint16_t *sector, drive_t *drive) { memset (sector, 0, 512); sector[0] = 0x044a; // General config: ATA, non-removable sector[1] = drive->cyls; // Number of logical cylinders sector[3] = drive->heads; // Number of logical heads sector[6] = drive->spt; // Number of logical sectors per track sprintf ((char *) §or[10], "%-20d", serial_no); sprintf ((char *) §or[23], "%-8s", DRIVE_FW_VERSION); sprintf ((char *) §or[27], "%-40s", drive->label); _flip_words (§or[10], 10); _flip_words (§or[23], 4); _flip_words (§or[27], 20); sector[47] = 0x8004; // Maximum # sectors per R/W multiple sector[49] = 0x0700; // Capabilities - LBA / no IORDY sector[50] = 0x4000; // Capabilities - sector[51] = 0x0200; // PIO-2 data transfer mode sector[53] = 0x0007; // 54-58, 64-70, 88 words valid sector[54] = drive->cyls; // Current number of cylinders sector[55] = drive->heads; // Current number of heads sector[56] = drive->spt; // Current number of sectors per track sector[57] = drive->sectors & 0xffff; // capacity in sectors sector[58] = (drive->sectors >> 16) & 0xffff; sector[59] = 0x0100 | ide_multiple; // Mutiple sector valid sector[60] = sector[57]; // number of user addressible sectors low sector[61] = sector[58]; // number of user addressible sectors high sector[64] = 0x0000; // Does not support PIO3&4 sector[65] = 120; // (ns) Minimum multiword DMA time sector[66] = 120; // (ns) Manufacturers suggested DMA time sector[67] = 120; // (ns) Minimum PIO time w/o IORDY sector[68] = 120; // (ns) Minimum PIO time w/ IORDY sector[80] = 0x0004; // Supports ATA/ATAPI-4 sector[81] = 0x0017; // ATAPI-4 Version T13 1153D revision 17 sector[82] = 0x7000; // Sp CMDs: NOP/READ/WRITE sector[83] = 0x4000; // Sp CMDs: sector[84] = 0x4000; // Sp CMDs: sector[85] = 0x7000; // En CMDs: NOP/READ/WRITE sector[86] = 0x4000; // En CMDs: sector[87] = 0x4000; // En CMDs: return; } static void _proc_buffer_complete (void) { LOGDB2("Proc buffer complete\n"); switch (svc_cmd) { case IDE_CMD_WRITE_SEC: case IDE_CMD_WRITE_SEC_NORETRY: case IDE_CMD_WRITE_MULTIPLE: if (svc_drive) { write (svc_drive->fd, svc_buffer, svc_offset << 1); fdatasync (svc_drive->fd); // error handling } break; case IDE_CMD_REDIRECTOR: _handle_redirect_complete (svc_sub, svc_buffer, svc_offset << 1); break; default: LOGDBG("Buffer received (%d) - unknown IDE command %02Xh\n", svc_offset, (int) svc_cmd); break; } svc_cmd = 0; svc_sub = 0; svc_drive = NULL; return; } /* * This is where all the magic happens. It is the handler for * when the IDE command register goes from 0 -> CMD. The IDE * command is processes here and dispatched for other functions * to cleanup. */ static void process_ide_command (void) { uint32_t words = 0; uint64_t offset = 0; uint32_t lastsec = 0; drive_t *drive = ide.SLAVE ? slave : master; /* * Default good status for all handled commands * unless overridden by the default switch handler below */ ide.BSY = 0; // Command complete - not busy ide.DSC = 1; // Seek complete ide.DRDY = 1; // Drive ready ide.ABRT = 0; // Command not-aborted ide.DF = 0; // Drive not faulted ide.IDNF = 0; // Sector found ide.IRQACT = 0; ide.DRQ = DRQ_MODE_OFF; /* * Abort commands issued to non-existent drives even * though this doesn't work this way in the real world * as a missing but selected drive would time-out. */ if (ide.cmd && ((ide.cmd & 0xf0) != 0x80)) // exclude vendor { if ((!ide.SLAVE && (master == NULL)) || ( ide.SLAVE && (slave == NULL))) { LOGDBG("Aborting CMD %02Xh to %s - does not exist\n", ide.cmd, ide.SLAVE ? "SLAVE" : "MASTER"); ide.DRDY = 0; // Drive not ready ide.ABRT = 1; // Command aborted ide.cmd = 0; ide_update (0, 0); return; } } LOGDBG("IDE-CMD: %s ", ide.SLAVE ? "[SLAVE ]" : "[MASTER]"); switch (ide.cmd) { // ******** COMMAND ONLY MODE (no data transfer) ******** case IDE_CMD_NULL: LOGDBG("NOP\n"); break; case IDE_CMD_VERIFY_SEC: case IDE_CMD_VERIFY_SEC_NORETRY: { _fixup_LBA (&offset, &words, &lastsec); LOGDBG("READ VERIFY %s(LBA 0x%x / count %d)\n", (ide.cmd == IDE_CMD_VERIFY_SEC_NORETRY) ? "NO-RETRY " : "", ide.lba, ide.secz ? ide.secz : 256); if (lastsec > drive->sectors) { LOGDBG(" - Last sector past end of device\n"); ide.DSC = 0; // Seek not complete ide.IDNF = 1; // Sector not found break; } break; } case IDE_CMD_SEEK ... (IDE_CMD_SEEK + 0x0f): { ide.secz = 1; // always assume 1 block for sanity check _fixup_LBA (&offset, &words, &lastsec); LOGDBG("SEEK step=%d (LBA 0x%x)\n", ide.cmd & 0x0f, ide.lba); if (lastsec > drive->sectors) { LOGDBG(" - Last sector past end of device\n"); ide.DSC = 0; // Seek not complete ide.IDNF = 1; // Sector not found break; } break; } case IDE_CMD_EXEC_DIAG: { LOGDBG("EXECUTE DIAGNOSTICS (%sIRQ)\n", ide.IENn ? "NOn-" : ""); /* * Special in that the error register should contain a * code after BSY completion rather than the normal bits. * This is handled by using the EMUX bit to control how * the error register is presented to the host. * * 0 = Normal error return bits * 1 = Present the feature reg instead (set here) * * Diagnostic return codes: * * 01h = no error detected * 03h = sector buffer error */ ide.feat = 0x01; ide.EMUX = 1; // Cleared on next command write ide.IRQACT = 1; _reset_signature (); break; } case IDE_CMD_SET_DRIVE_PARM: { int spt = ide.lba & 0xff; int heads = (ide.lba >> 24) & 0x0f; // drive->spt = spt; // drive->heads = heads; drive->cyls = drive->sectors / (spt * heads); LOGDBG("INITIALIZE DEVICE PARAMETERS (SPT=%d/H=%d) = %d\n", spt, heads, drive->cyls); break; } case IDE_CMD_FLUSH_CACHE: LOGDBG("FLUSH CACHE\n"); fdatasync (drive->fd); break; case IDE_CMD_SET_FEATURES: LOGDBG("SET FEATURES: "); switch (ide.feat) { case 0x02: LOGDBG("ENABLE WRITE CACHE\n"); // Always enabled break; case 0x82: LOGDBG("DISABLE WRITE CACHE\n"); // Ignored break; case 0x05: LOGDBG("ENABLE ADVANCED POWER MANAGEMENT\n"); // Always handled internally break; case 0xAA: LOGDBG("ENABLE READ LOOK-AHEAD\n"); // Always enabled break; case 0x03: LOGDBG("SET TRANSFER MODE: %02Xh\n", ide.secz); if (ide.secz >= 0x20) // no DMA support { ide.DRDY = 0; // Drive not ready ide.ABRT = 1; // Command aborted } break; default: LOGDBG("Unhandled %02Xh\n", ide.feat); ide.DRDY = 0; // Drive not ready ide.ABRT = 1; // Command aborted break; } break; case IDE_CMD_GET_MAX_ADDRESS: LOGDBG("READ NATIVE MAX ADDRESS\n"); if (ide.LBAM) { ide.lba = drive->sectors; } else { uint32_t max_sec = (drive->spt < 0x100) ? drive->spt - 1 : 0xff; uint32_t max_cyl = (drive->cyls < 0x10000) ? drive->cyls - 1 : 0xffff; uint32_t max_head = (drive->heads < 0x10) ? drive->heads - 1 : 0xf; ide.lba = (max_head << 24) | (max_cyl << 8) | max_sec; } break; case IDE_CMD_SET_MAX_ADDRESS: LOGDBG("SET MAX ADDRESS"); if (ide.secz & 1) { LOGDBG("\n***** WARNING: Persistent bit set and unsupported *****\n") } if (ide.LBAM) { if (ide.lba >= drive->sectors) { ide.DRDY = 0; // Drive not ready ide.ABRT = 1; // Command aborted LOGDBG(" - exceeds capacity (%u)\n", ide.lba); break; } drive->sectors = ide.lba; LOGDBG(" - set to %u sectors\n", drive->sectors); } else { uint32_t spt = ( ide.lba & 0xff) + 1; uint32_t heads = ((ide.lba >> 24) & 0x0f) + 1; uint32_t cyls = ((ide.lba >> 8) & 0xffff) + 1; ide.lba = spt * heads * cyls; if (ide.lba >= drive->sectors) { ide.DRDY = 0; // Drive not ready ide.ABRT = 1; // Command aborted LOGDBG(" - exceeds capacity (%u)\n", ide.lba); break; } drive->spt = spt; drive->heads = heads; drive->cyls = cyls; LOGDBG(" - set to CHS %u/%d/%d (%u sectors)\n", cyls, heads, spt, drive->sectors); } break; case IDE_CMD_SET_MULTIPLE_MODE: LOGDBG("SET MULTIPLE MODE (%d sectors / block)\n", ide.secz); ide_multiple = ide.secz; break; // ******** PIO-IN MODE ******** case IDE_CMD_READ_SEC: case IDE_CMD_READ_SEC_NORETRY: { _fixup_LBA (&offset, &words, &lastsec); LOGDBG("READ %s(LBA 0x%x / count %d) [PIO-IN]\n", (ide.cmd == IDE_CMD_READ_SEC_NORETRY) ? "NO-RETRY " : "", ide.lba, ide.secz ? ide.secz : 256); if (lastsec > drive->sectors) { LOGDBG(" - Last sector past end of device\n"); ide.DSC = 0; // Seek not complete ide.IDNF = 1; // Sector not found break; } // fix me - error handling lseek (drive->fd, offset, SEEK_SET); read (drive->fd, svc_buffer, words << 1); data_out_commit (words); break; } case IDE_CMD_READ_MULTIPLE: { _fixup_LBA (&offset, &words, &lastsec); LOGDBG("READ MULTIPLE (LBA 0x%x / count %d (%d)) [PIO-IN]\n", ide.lba, ide.secz ? ide.secz : 256, ide_multiple); if (lastsec > drive->sectors) { LOGDBG(" - Last sector past end of device\n"); ide.DSC = 0; // Seek not complete ide.IDNF = 1; // Sector not found break; } // fix me - error handling lseek (drive->fd, offset, SEEK_SET); read (drive->fd, svc_buffer, words << 1); data_out_commit (words); break; } case IDE_CMD_PULL_REQUEST: { char *path = NULL; switch (ide.feat) { case 0: path = "bin/optbios.bin"; break; default: case 1: path = "bin/pimount.com"; break; case 2: path = "bin/picon.com"; break; } LOGDBG("PULL REQUEST - %s [PIO-IN]\n", path); int fd = open (path, O_RDONLY); if (fd < 0) { LOGDBG(" - Unable to open %s\n", path); ide.DSC = 0; // Seek not complete ide.IDNF = 1; // Sector not found break; } ide.lba = (read (fd, svc_buffer, sizeof (svc_buffer)) + 1) / 2; close (fd); data_out_commit (ide.lba); break; } case IDE_CMD_IDENTIFY: { LOGDBG("IDENTIFY DEVICE [PIO-IN]\n"); _create_idsector ((uint16_t *) svc_buffer, drive); LOGDB2("In-bound FIFO: Queued 256 words\n"); svc_offset = 0; svc_iwords = 256; ide.DRQ = DRQ_MODE_READSEC; break; } // ******** PIO-OUT MODE ******** case IDE_CMD_WRITE_SEC: case IDE_CMD_WRITE_SEC_NORETRY: { _fixup_LBA (&offset, &words, &lastsec); LOGDBG("WRITE %s(LBA 0x%x / count %d) [PIO-OUT]\n", (ide.cmd == IDE_CMD_WRITE_SEC_NORETRY) ? "NO-RETRY" : "", ide.lba, ide.secz ? ide.secz : 256); if (lastsec > drive->sectors) { LOGDBG(" - Last sector past end of device\n"); ide.DSC = 0; // Seek not complete ide.IDNF = 1; // Sector not found break; } if (drive->readonly) { LOGDBG(" - Drive is read-only\n"); ide.DF = 1; // Drive faulted break; } // fix me - error handling lseek (drive->fd, offset, SEEK_SET); svc_drive = ide.SLAVE ? slave : master; data_in_setup (ide.cmd, 0, words); break; } case IDE_CMD_WRITE_MULTIPLE: { _fixup_LBA (&offset, &words, &lastsec); LOGDBG("WRITE MULTIPLE (LBA 0x%x / count %d (%d)) [PIO-OUT]\n", ide.lba, ide.secz ? ide.secz : 256, ide_multiple); if (lastsec > drive->sectors) { LOGDBG(" - Last sector past end of device\n"); ide.DSC = 0; // Seek not complete ide.IDNF = 1; // Sector not found break; } if (drive->readonly) { LOGDBG(" - Drive is read-only\n"); ide.DF = 1; // Drive faulted break; } // fix me - error handling lseek (drive->fd, offset, SEEK_SET); svc_drive = ide.SLAVE ? slave : master; data_in_setup (ide.cmd, 0, words); break; } // ******** DOS REDIRECTOR SERVICE ******** case IDE_CMD_REDIRECTOR: _handle_redirect_command (); break; // ******** ABORT MODE ******** default: LOGDBG("Unhandled IDE CMD %02Xh\n", ide.cmd); ide.DRDY = 0; // Drive not ready ide.ABRT = 1; // Command aborted break; } ide.cmd = 0; // Clear active command ide_update (0, 0); return; } static void _program_usage (const char *name) { fprintf (stderr, "\nUsage: %s \n\n" "Options:\n\n" "\t<-v|--verbose> Increase logging verbosity\n" "\t<-u|--upgrade> Perform CPLD version/upgrade check\n" "\t<-c|--config> Specify path to config file\n" "\n", name); return; } static int done = 0; static void _signal_handler (int signo) { done = 1; return; } int main (int argc, char *argv[]) { const char *progname = basename (argv[0]); char *config_path = "./netpi.cfg"; int upgrade_cpld = 0; int sts = -1; argc--; argv++; while (argc) { if ((strcmp (argv[0], "-v") == 0) || (strcmp (argv[0], "--verbose") == 0)) { verbose++; } else if ((strcmp (argv[0], "-u") == 0) || (strcmp (argv[0], "--upgrade") == 0)) { upgrade_cpld = 1; } else if (((strcmp (argv[0], "-c") == 0) || (strcmp (argv[0], "--config") == 0)) && (argc > 1)) { argc--; argv++; config_path = argv[0]; } else { _program_usage (progname); return -1; } argc--; argv++; } serial_no = 666; if (config_load (config_path)) return -1; master = config_drive_init (1); slave = config_drive_init (0); init_redirector (); do { if (upgrade_cpld && _cpld_version_up()) break; if (spi_init (1, SPI_BITRATE)) break; if (gpio_init ()) break; // register graceful exit signals struct sigaction sigvector; memset (&sigvector, 0, sizeof (sigvector)); sigvector.sa_handler = _signal_handler; sigvector.sa_flags = 0; sigemptyset (&sigvector.sa_mask); sigaction (SIGINT, &sigvector, NULL); sigaction (SIGTERM, &sigvector, NULL); sigaction (SIGQUIT, &sigvector, NULL); // Main loop while (!done) { ide_refresh (); update_stats (); // Always process reset requests first if (ide.SRSTP) { LOGDBG("Processing %s reset request\n", ide.SRST ? "SW" : "HW"); memset (&ide, 0, sizeof (ide_regs_t)); ide.DRDY = 1; ide.IRQPIn = 1; ide.lba = 1; // sector 1 non-LBA svc_offset = 0; svc_iwords = 0; svc_owords = 0; ide_update (0, 0); } else if (svc_iwords) service_dataout (); else if (svc_owords) service_datain (); else if (ide.cmd) process_ide_command (); else gpio_sleep (); } sts = 0; } while (0); LOGDBG("Shutting down\n"); if (master) config_drive_term (master); if (slave) config_drive_term (slave); gpio_term (); spi_term (); config_free (); return sts; }