From ca37758018206ba764d867dd44e95258a24454c2 Mon Sep 17 00:00:00 2001 From: Dmitry Borisov Date: Sun, 25 Jan 2026 08:40:56 +0600 Subject: [PATCH] nmc93cxx: Rewrite the nmc93cxx emulation This patch rewrites the nmc93cxx emulation code. The primary aim of the rewrite is to fix the Qlogic 1080 BIOS v1.11 flashing feature (the card utilizes a 93C56 chip in 128x16 mode). This work is derived from the MAME serial EEPROM emulation code written by Aaron Giles and published under BSD-3-Clause license. https://github.com/mamedev/mame/blob/master/src/devices/machine/eepromser.cpp The code is modelled on the MAME code with the following differences: - Removed support for the ER5911 and MSM16911 EEPROM devices. - Removed support for the X24C44 NOVRAM device. - Removed support for the Seiko S-29X90 EEPROM devices. The 86Box changes: - The nmc93cxx code now also supports EEPROM devices in 8-bit mode. - Make the default_content parameter optional. - Make the nmc93cxx_eeprom_data function to return a const pointer. --- src/include/86box/nmc93cxx.h | 79 +++- src/mem/nmc93cxx.c | 765 +++++++++++++++++++++++++++-------- src/network/net_ne2000.c | 17 +- src/network/net_rtl8139.c | 8 +- src/network/net_tulip.c | 8 +- src/scsi/scsi_pcscsi.c | 15 +- src/video/vid_s3.c | 6 +- 7 files changed, 669 insertions(+), 229 deletions(-) diff --git a/src/include/86box/nmc93cxx.h b/src/include/86box/nmc93cxx.h index dd02b2d06..b8c9d61b6 100644 --- a/src/include/86box/nmc93cxx.h +++ b/src/include/86box/nmc93cxx.h @@ -1,25 +1,72 @@ -#include <86box/vid_ati_eeprom.h> +/* + * 86Box A hypervisor and IBM PC system emulator that specializes in + * running old operating systems and software designed for IBM + * PC systems and compatibles from 1981 through fairly recent + * system designs based on the PCI bus. + * + * This file is part of the 86Box distribution. + * + * Header of the emulation of the National Semiconductors NMC93Cxx EEPROMs + * (16 bits or 8 bits). + * + * Authors: Cacodemon345 + * + * Copyright 2023 Cacodemon345 + */ +#pragma once -typedef struct nmc93cxx_eeprom_t { - ati_eeprom_t dev; - uint8_t addrbits; - uint16_t size; - char filename[1024]; -} nmc93cxx_eeprom_t; +/* Forward declaration to hide internal device state from users. */ +typedef struct nmc93cxx_eeprom_t nmc93cxx_eeprom_t; +/* EEPROM device type used to specify the size of data array. */ +typedef enum nmc93cxx_eeprom_type { + /* + * Standard 93CX6 class of 16-bit EEPROMs. + * + * Type / Bits per cell / Number of cells + */ + NMC_93C06_x16_16, + NMC_93C46_x16_64, + NMC_93C56_x16_128, + NMC_93C57_x16_128, + NMC_93C66_x16_256, + NMC_93C76_x16_512, + NMC_93C86_x16_1024, + + /* + * Some manufacturers use pin 6 as an "ORG" pin which, + * when pulled low, configures memory for 8-bit accesses. + * + * Type / Bits per cell / Number of cells + */ + NMC_93C46_x8_128, + NMC_93C56_x8_256, + NMC_93C57_x8_256, + NMC_93C66_x8_512, + NMC_93C76_x8_1024, + NMC_93C86_x8_2048, +} nmc93cxx_eeprom_type; + +/* EEPROM device parameters. */ typedef struct nmc93cxx_eeprom_params_t { - uint16_t nwords; - char *filename; - uint16_t *default_content; + /* Device type */ + nmc93cxx_eeprom_type type; + /* Name of EEPROM image file */ + const char *filename; + /* + * Optional pointer to the default data buffer. + * The buffer size should match the size of EEPROM data array specified by nmc93cxx_eeprom_type. + */ + const void *default_content; } nmc93cxx_eeprom_params_t; -/* Read from the EEPROM. */ -uint16_t nmc93cxx_eeprom_read(nmc93cxx_eeprom_t *eeprom); +/* Read the state of the data output (DO) line. */ +bool nmc93cxx_eeprom_read(nmc93cxx_eeprom_t *dev); -/* Write to the EEPROM. */ -void nmc93cxx_eeprom_write(nmc93cxx_eeprom_t *eeprom, int eecs, int eesk, int eedi); +/* Set the state of the input lines. */ +void nmc93cxx_eeprom_write(nmc93cxx_eeprom_t *dev, bool eecs, bool eesk, bool eedi); -/* Get EEPROM data array. */ -uint16_t *nmc93cxx_eeprom_data(nmc93cxx_eeprom_t *eeprom); +/* Returns pointer to the current EEPROM data array. */ +const uint16_t *nmc93cxx_eeprom_data(nmc93cxx_eeprom_t *dev); extern const device_t nmc93cxx_device; diff --git a/src/mem/nmc93cxx.c b/src/mem/nmc93cxx.c index dada96454..c998fb825 100644 --- a/src/mem/nmc93cxx.c +++ b/src/mem/nmc93cxx.c @@ -6,267 +6,676 @@ * * This file is part of the 86Box distribution. * - * Emulation of National Semiconductors NMC93Cxx EEPROMs. + * Emulation of National Semiconductors NMC93Cxx EEPROMs (16 bits or 8 bits). * * Authors: Cacodemon345 * * Copyright 2023 Cacodemon345 */ -/* Ported over from QEMU */ +/* Ported over from the MAME eepromser.cpp implementation. Copyright 2013 Aaron Giles */ + +#ifdef ENABLE_NMC93CXX_EEPROM_LOG +#include +#endif #include #include +#include #include #include -#include +#include #include #include -#define HAVE_STDARG_H +#include + #include <86box/86box.h> #include <86box/device.h> #include <86box/timer.h> #include <86box/nvr.h> +#include <86box/log.h> #include <86box/nmc93cxx.h> #include <86box/plat_unused.h> +#define WRITE_TIME_US 1750 +#define WRITE_ALL_TIME_US 8000 +#define ERASE_TIME_US 1000 +#define ERASE_ALL_TIME_US 8000 + +typedef enum EepromCommand { + COMMAND_INVALID, + COMMAND_READ, + COMMAND_WRITE, + COMMAND_ERASE, + COMMAND_LOCK, + COMMAND_UNLOCK, + COMMAND_WRITEALL, + COMMAND_ERASEALL, + COMMAND_COPY_EEPROM_TO_RAM, + COMMAND_COPY_RAM_TO_EEPROM +} EepromCommand; + +typedef enum EepromState { + STATE_IN_RESET, + STATE_WAIT_FOR_START_BIT, + STATE_WAIT_FOR_COMMAND, + STATE_READING_DATA, + STATE_WAIT_FOR_DATA, + STATE_WAIT_FOR_COMPLETION +} EepromState; + +typedef enum EepromEvent { + EVENT_CS_RISING_EDGE = 1 << 0, + EVENT_CS_FALLING_EDGE = 1 << 1, + EVENT_CLK_RISING_EDGE = 1 << 2, + EVENT_CLK_FALLING_EDGE = 1 << 3 +} EepromEvent; + +struct nmc93cxx_eeprom_t { + /* Command completion timer */ + pc_timer_t cmd_complete_timer; + /* Write tick */ + uint32_t write_tick; + /* State of the CS line */ + bool cs_state; + /* State of the CLK line */ + bool clk_state; + /* State of the DI line */ + bool di_state; + /* READY/BUSY status during a programming operation */ + bool is_busy; + /* Internal device state */ + EepromState state; + /* Accumulator of command+address bits */ + uint32_t command_address_accum; + /* Current address extracted from command */ + uint32_t address; + /* Holds data coming in/going out */ + uint32_t shift_register; + /* Number of bits accumulated */ + uint32_t bits_accum; + /* Current command */ + EepromCommand command; + /* Number of memory cells */ + uint16_t cells; + /* Number of bits per cell */ + uint16_t data_bits; + /* Number of address bits in a command */ + uint8_t command_address_bits; + /* Number of address bits in an address */ + uint8_t address_bits; + /* Are we locked against writes? */ + bool is_locked; + /* Tick of the last CS rising edge */ + uint32_t last_cs_rising_edge_tick; + /* Device logging */ + void *log; + /* Name of EEPROM image file */ + char filename[1024]; + + /* EEPROM image words, must be the last structure member */ + uint16_t array_data[]; +}; + #ifdef ENABLE_NMC93CXX_EEPROM_LOG int nmc93cxx_eeprom_do_log = ENABLE_NMC93CXX_EEPROM_LOG; static void -nmc93cxx_eeprom_log(int lvl, const char *fmt, ...) +nmc93cxx_eeprom_log(nmc93cxx_eeprom_t *dev, int lvl, const char *fmt, ...) { va_list ap; if (nmc93cxx_eeprom_do_log >= lvl) { va_start(ap, fmt); - pclog_ex(fmt, ap); + log_out(dev->log, fmt, ap); va_end(ap); } } #else -# define nmc93cxx_eeprom_log(lvl, fmt, ...) +# define nmc93cxx_eeprom_log(dev, lvl, fmt, ...) #endif +#define MAKE_CASE(x) case x: return #x; +static char* +nmc93cxx_eeprom_state_to_name(EepromState state) +{ + switch (state) { + MAKE_CASE(STATE_IN_RESET); + MAKE_CASE(STATE_WAIT_FOR_START_BIT); + MAKE_CASE(STATE_WAIT_FOR_COMMAND); + MAKE_CASE(STATE_READING_DATA); + MAKE_CASE(STATE_WAIT_FOR_DATA); + MAKE_CASE(STATE_WAIT_FOR_COMPLETION); + default: return ""; + } +} + +static char* +nmc93cxx_eeprom_cmd_to_name(EepromCommand command) +{ + switch (command) { + MAKE_CASE(COMMAND_INVALID); + MAKE_CASE(COMMAND_READ); + MAKE_CASE(COMMAND_WRITE); + MAKE_CASE(COMMAND_ERASE); + MAKE_CASE(COMMAND_LOCK); + MAKE_CASE(COMMAND_UNLOCK); + MAKE_CASE(COMMAND_WRITEALL); + MAKE_CASE(COMMAND_ERASEALL); + MAKE_CASE(COMMAND_COPY_EEPROM_TO_RAM); + MAKE_CASE(COMMAND_COPY_RAM_TO_EEPROM); + default: return ""; + } +} +#undef MAKE_CASE + +static void +nmc93cxx_eeprom_set_state(nmc93cxx_eeprom_t *dev, EepromState state) +{ + if (dev->state != state) { + nmc93cxx_eeprom_log(dev, 2, "EEPROM: New state %s\n", nmc93cxx_eeprom_state_to_name(state)); + } + dev->state = state; +} + +static void +nmc93cxx_eeprom_cmd_complete_timer_callback(void *priv) +{ + nmc93cxx_eeprom_t *dev = priv; + + dev->is_busy = false; +} + +static void +nmc93cxx_eeprom_start_cmd_timer(nmc93cxx_eeprom_t *dev, double period) +{ + dev->is_busy = true; + timer_on_auto(&dev->cmd_complete_timer, period); +} + static void * nmc93cxx_eeprom_init(const device_t *info) { - uint16_t nwords = 64; - uint8_t addrbits = 6; - uint8_t filldefault = 1; nmc93cxx_eeprom_params_t *params_details = (nmc93cxx_eeprom_params_t *) info->local; - nmc93cxx_eeprom_t *eeprom = NULL; - if (info->local == 0) - return NULL; + nmc93cxx_eeprom_t *dev; + bool fill_default = true; + uint16_t cells, nwords, data_bits; + uint8_t addrbits; - nwords = params_details->nwords; + /* Check for mandatory parameters */ + assert(params_details != NULL); + assert(params_details->filename != NULL); - switch (nwords) { - case 16: - case 64: + switch (params_details->type) { + /* 16-bit EEPROMs */ + case NMC_93C06_x16_16: + cells = 16; addrbits = 6; + data_bits = 16; break; - case 128: - case 256: + case NMC_93C46_x16_64: + cells = 64; + addrbits = 6; + data_bits = 16; + break; + case NMC_93C56_x16_128: + cells = 128; addrbits = 8; + data_bits = 16; break; + case NMC_93C57_x16_128: + cells = 128; + addrbits = 7; + data_bits = 16; + break; + case NMC_93C66_x16_256: + cells = 256; + addrbits = 8; + data_bits = 16; + break; + case NMC_93C76_x16_512: + cells = 512; + addrbits = 10; + data_bits = 16; + break; + case NMC_93C86_x16_1024: + cells = 1024; + addrbits = 10; + data_bits = 16; + break; + + /* 8-bit EEPROMs */ + case NMC_93C46_x8_128: + cells = 128; + addrbits = 7; + data_bits = 8; + break; + case NMC_93C56_x8_256: + cells = 256; + addrbits = 9; + data_bits = 8; + break; + case NMC_93C57_x8_256: + cells = 256; + addrbits = 8; + data_bits = 8; + break; + case NMC_93C66_x8_512: + cells = 512; + addrbits = 9; + data_bits = 8; + break; + case NMC_93C76_x8_1024: + cells = 1024; + addrbits = 11; + data_bits = 8; + break; + case NMC_93C86_x8_2048: + cells = 1024; + addrbits = 11; + data_bits = 8; + break; + default: - nwords = 64; - addrbits = 6; + /* Invalid parameter passed to the device */ + assert(false); break; } - eeprom = calloc(1, sizeof(nmc93cxx_eeprom_t) + ((nwords + 1) * 2)); - if (!eeprom) + + /* The "ORG" pin can select the x8 or x16 memory organization */ + if (data_bits == 16) { + nwords = cells; + } else { + nwords = cells / 2; + + assert(data_bits == 8); + } + + dev = calloc(1, offsetof(nmc93cxx_eeprom_t, array_data[nwords])); + if (!dev) return NULL; - eeprom->size = nwords; - eeprom->addrbits = addrbits; - /* Output DO is tristate, read results in 1. */ - eeprom->dev.out = 1; + dev->cells = cells; + dev->command_address_bits = addrbits; + dev->data_bits = data_bits; + dev->is_locked = true; + dev->command = COMMAND_INVALID; + dev->log = log_open("nmc93cxx"); + nmc93cxx_eeprom_set_state(dev, STATE_IN_RESET); - if (params_details->filename) { - FILE *fp = nvr_fopen(params_details->filename, "rb"); - strncpy(eeprom->filename, params_details->filename, sizeof(eeprom->filename) - 1); - if (fp) { - filldefault = !fread(eeprom->dev.data, sizeof(uint16_t), nwords, fp); - fclose(fp); - } + /* Load the EEPROM image */ + snprintf(dev->filename, sizeof(dev->filename), "%s", params_details->filename); + FILE *fp = nvr_fopen(dev->filename, "rb"); + if (fp) { + fill_default = !fread(dev->array_data, dev->data_bits / 8, dev->cells, fp); + fclose(fp); + } + if (fill_default && params_details->default_content) { + memcpy(dev->array_data, params_details->default_content, dev->cells * (dev->data_bits / 8)); } - if (filldefault) { - memcpy(eeprom->dev.data, params_details->default_content, nwords * sizeof(uint16_t)); + /* Compute address bits */ + uint32_t count = dev->cells - 1; + dev->address_bits = 0; + while (count != 0) { + count >>= 1; + dev->address_bits++; } - return eeprom; + timer_add(&dev->cmd_complete_timer, nmc93cxx_eeprom_cmd_complete_timer_callback, dev, 0); + + return dev; +} + +static uint32_t +nmc93cxx_eeprom_cell_read(nmc93cxx_eeprom_t *dev, uint32_t address) +{ + if (dev->data_bits == 16) { + return dev->array_data[address]; + } else { + return *((uint8_t *)dev->array_data + address); + } } static void -nmc93cxx_eeprom_save(nmc93cxx_eeprom_t *eeprom) +nmc93cxx_eeprom_cell_write(nmc93cxx_eeprom_t *dev, uint32_t address, uint32_t data) { - FILE *fp = nvr_fopen(eeprom->filename, "wb"); - if (!fp) - return; - fwrite(eeprom->dev.data, 2, eeprom->size, fp); - fclose(fp); + if (dev->data_bits == 16) { + dev->array_data[address] = data; + } else { + *((uint8_t *)dev->array_data + address) = data; + } +} + +static void +nmc93cxx_eeprom_handle_cmd_write(nmc93cxx_eeprom_t *dev, uint32_t address, uint32_t data) +{ + nmc93cxx_eeprom_log(dev, 1, "EEPROM: WR %08lX <-- %X\n", address, data); + + nmc93cxx_eeprom_cell_write(dev, address, data); + nmc93cxx_eeprom_start_cmd_timer(dev, WRITE_TIME_US); +} + +static void +nmc93cxx_eeprom_handle_cmd_write_all(nmc93cxx_eeprom_t *dev, uint32_t data) +{ + nmc93cxx_eeprom_log(dev, 1, "EEPROM: Write all operation %X\n", data); + + for (uint32_t address = 0; address < (1 << dev->address_bits); ++address) { + nmc93cxx_eeprom_cell_write(dev, address, nmc93cxx_eeprom_cell_read(dev, address) & data); + } + + nmc93cxx_eeprom_start_cmd_timer(dev, WRITE_ALL_TIME_US); +} + +static void +nmc93cxx_eeprom_handle_cmd_erase(nmc93cxx_eeprom_t *dev, uint32_t address) +{ + nmc93cxx_eeprom_log(dev, 1, "EEPROM: Erase data at %08lx\n", address); + + nmc93cxx_eeprom_cell_write(dev, address, ~0); + nmc93cxx_eeprom_start_cmd_timer(dev, ERASE_TIME_US); +} + +static void +nmc93cxx_eeprom_handle_cmd_erase_all(nmc93cxx_eeprom_t *dev) +{ + nmc93cxx_eeprom_log(dev, 1, "EEPROM: Erase all operation\n"); + + for (uint32_t address = 0; address < (1 << dev->address_bits); ++address) { + nmc93cxx_eeprom_cell_write(dev, address, ~0); + } + + nmc93cxx_eeprom_start_cmd_timer(dev, ERASE_ALL_TIME_US); +} + +static void +nmc93cxx_eeprom_parse_command_and_address(nmc93cxx_eeprom_t *dev) +{ + dev->address = dev->command_address_accum & ((1 << dev->command_address_bits) - 1); + + /* Extract the command portion and handle it */ + switch (dev->command_address_accum >> dev->command_address_bits) { + /* Opcode 0 needs two more bits to decode the operation */ + case 0: + switch (dev->address >> (dev->command_address_bits - 2)) { + case 0: dev->command = COMMAND_LOCK; break; + case 1: dev->command = COMMAND_WRITEALL; break; + case 2: dev->command = COMMAND_ERASEALL; break; + case 3: dev->command = COMMAND_UNLOCK; break; + } + dev->address = 0; + break; + + case 1: dev->command = COMMAND_WRITE; break; + case 2: dev->command = COMMAND_READ; break; + case 3: dev->command = COMMAND_ERASE; break; + + default: + dev->command = COMMAND_INVALID; + break; + } + + if (dev->address >= (1 << dev->address_bits)) { + nmc93cxx_eeprom_log(dev, 1, "EEPROM: out-of-range address 0x%X provided (maximum should be 0x%X)\n", dev->address, (1 << dev->address_bits) - 1); + } +} + +static void +nmc93cxx_eeprom_execute_command(nmc93cxx_eeprom_t *dev) +{ + /* Parse into a generic command and reset the accumulator count */ + nmc93cxx_eeprom_parse_command_and_address(dev); + dev->bits_accum = 0; + + nmc93cxx_eeprom_log(dev, 1, "EEPROM: Execute command %s\n", nmc93cxx_eeprom_cmd_to_name(dev->command)); + switch (dev->command) { + /* + * Advance to the READING_DATA state; data is fetched after first CLK + * reset the shift register to 0 to simulate the dummy 0 bit that happens prior + * to the first clock + */ + case COMMAND_READ: + dev->shift_register = 0; + nmc93cxx_eeprom_set_state(dev, STATE_READING_DATA); + break; + + /* Reset the shift register and wait for enough data to be clocked through */ + case COMMAND_WRITE: + case COMMAND_WRITEALL: + dev->shift_register = 0; + nmc93cxx_eeprom_set_state(dev, STATE_WAIT_FOR_DATA); + break; + + /* Erase the parsed address (unless locked) and wait for it to complete */ + case COMMAND_ERASE: + if (dev->is_locked) { + nmc93cxx_eeprom_log(dev, 1, "EEPROM: Attempt to erase while locked\n"); + nmc93cxx_eeprom_set_state(dev, STATE_IN_RESET); + break; + } + nmc93cxx_eeprom_handle_cmd_erase(dev, dev->address); + nmc93cxx_eeprom_set_state(dev, STATE_WAIT_FOR_COMPLETION); + break; + + /* Lock the chip; return to IN_RESET state */ + case COMMAND_LOCK: + dev->is_locked = true; + nmc93cxx_eeprom_set_state(dev, STATE_IN_RESET); + break; + + /* Unlock the chip; return to IN_RESET state */ + case COMMAND_UNLOCK: + dev->is_locked = false; + nmc93cxx_eeprom_set_state(dev, STATE_IN_RESET); + break; + + /* Erase the entire chip (unless locked) and wait for it to complete */ + case COMMAND_ERASEALL: + if (dev->is_locked) { + nmc93cxx_eeprom_log(dev, 1, "EEPROM: Attempt to erase all while locked\n"); + nmc93cxx_eeprom_set_state(dev, STATE_IN_RESET); + break; + } + nmc93cxx_eeprom_handle_cmd_erase_all(dev); + nmc93cxx_eeprom_set_state(dev, STATE_WAIT_FOR_COMPLETION); + break; + + default: + nmc93cxx_eeprom_log(dev, 1, "execute_command called with invalid command %d\n", dev->command); + assert(false); + break; + } +} + +static void +nmc93cxx_eeprom_execute_write_command(nmc93cxx_eeprom_t *dev) +{ + nmc93cxx_eeprom_log(dev, 1, "EEPROM: Execute write command %s\n", nmc93cxx_eeprom_cmd_to_name(dev->command)); + + switch (dev->command) { + /* Reset the shift register and wait for enough data to be clocked through */ + case COMMAND_WRITE: + if (dev->is_locked) { + nmc93cxx_eeprom_log(dev, 1, "EEPROM: Attempt to write to address 0x%X while locked\n", dev->address); + nmc93cxx_eeprom_set_state(dev, STATE_IN_RESET); + break; + } + nmc93cxx_eeprom_handle_cmd_write(dev, dev->address, dev->shift_register); + nmc93cxx_eeprom_set_state(dev, STATE_WAIT_FOR_COMPLETION); + break; + + /* + * Write the entire EEPROM with the same data; ERASEALL is required before so we + * AND against the already-present data + */ + case COMMAND_WRITEALL: + if (dev->is_locked) { + nmc93cxx_eeprom_log(dev, 1, "EEPROM: Attempt to write all while locked\n"); + nmc93cxx_eeprom_set_state(dev, STATE_IN_RESET); + break; + } + nmc93cxx_eeprom_handle_cmd_write_all(dev, dev->shift_register); + nmc93cxx_eeprom_set_state(dev, STATE_WAIT_FOR_COMPLETION); + break; + + default: + nmc93cxx_eeprom_log(dev, 1, "execute_command called with invalid command %d\n", dev->command); + assert(false); + break; + } +} + +static void +nmc93cxx_eeprom_handle_event(nmc93cxx_eeprom_t *dev, EepromEvent event) +{ + switch (dev->state) { + /* CS is not asserted; wait for a rising CS to move us forward, ignoring all clocks */ + case STATE_IN_RESET: + if (event == EVENT_CS_RISING_EDGE) + nmc93cxx_eeprom_set_state(dev, STATE_WAIT_FOR_START_BIT); + break; + + /* + * CS is asserted; wait for rising clock with a 1 start bit; falling CS will reset us + * note that because each bit is written independently, it is possible for us to receive + * a false rising CLK edge at the exact same time as a rising CS edge; it appears we + * should ignore these edges (makes sense really) + */ + case STATE_WAIT_FOR_START_BIT: + if ((event == EVENT_CLK_RISING_EDGE) && dev->di_state && !dev->is_busy && (dev->write_tick != dev->last_cs_rising_edge_tick)) { + dev->command_address_accum = 0; + dev->bits_accum = 0; + nmc93cxx_eeprom_set_state(dev, STATE_WAIT_FOR_COMMAND); + } else if (event == EVENT_CS_FALLING_EDGE) + nmc93cxx_eeprom_set_state(dev, STATE_IN_RESET); + break; + + /* CS is asserted; wait for a command to come through; falling CS will reset us */ + case STATE_WAIT_FOR_COMMAND: + if (event == EVENT_CLK_RISING_EDGE) { + /* If we have enough bits for a command + address, check it out */ + dev->command_address_accum = (dev->command_address_accum << 1) | dev->di_state; + if (++dev->bits_accum == 2 + dev->command_address_bits) + nmc93cxx_eeprom_execute_command(dev); + } else if (event == EVENT_CS_FALLING_EDGE) + nmc93cxx_eeprom_set_state(dev, STATE_IN_RESET); + break; + + /* CS is asserted; reading data, clock the shift register; falling CS will reset us */ + case STATE_READING_DATA: + if (event == EVENT_CLK_RISING_EDGE) { + uint32_t bit_index = dev->bits_accum++; + + /* Wrapping the address on multi-read */ + if (((bit_index % dev->data_bits) == 0) && (bit_index == 0)) + { + uint32_t addr = (dev->address + dev->bits_accum / dev->data_bits) & ((1 << dev->address_bits) - 1); + uint32_t data = nmc93cxx_eeprom_cell_read(dev, addr); + + nmc93cxx_eeprom_log(dev, 1, "EEPROM: RD %08lX --> %X\n", addr, data); + + dev->shift_register = data << (32 - dev->data_bits); + } else { + dev->shift_register = (dev->shift_register << 1) | 1; + } + } else if (event == EVENT_CS_FALLING_EDGE) { + nmc93cxx_eeprom_set_state(dev, STATE_IN_RESET); + + if (dev->bits_accum > (dev->data_bits + 1)) + nmc93cxx_eeprom_log(dev, 1, "EEPROM: Overclocked read by %d bits\n", dev->bits_accum - dev->data_bits); + else if (dev->bits_accum < dev->data_bits) + nmc93cxx_eeprom_log(dev, 1, "EEPROM: CS deasserted in READING_DATA after %d bits\n", dev->bits_accum); + } + break; + + /* CS is asserted; waiting for data; clock data through until we accumulate enough; falling CS will reset us */ + case STATE_WAIT_FOR_DATA: + if (event == EVENT_CLK_RISING_EDGE) { + dev->shift_register = (dev->shift_register << 1) | dev->di_state; + if (++dev->bits_accum == dev->data_bits) + nmc93cxx_eeprom_execute_write_command(dev); + } else if (event == EVENT_CS_FALLING_EDGE) { + nmc93cxx_eeprom_set_state(dev, STATE_IN_RESET); + nmc93cxx_eeprom_log(dev, 1, "EEPROM: CS deasserted in STATE_WAIT_FOR_DATA after %d bits\n", dev->bits_accum); + } + break; + + /* CS is asserted; waiting for completion; watch for CS falling */ + case STATE_WAIT_FOR_COMPLETION: + if (event == EVENT_CS_FALLING_EDGE) + nmc93cxx_eeprom_set_state(dev, STATE_IN_RESET); + break; + + default: + break; + } } void -nmc93cxx_eeprom_write(nmc93cxx_eeprom_t *eeprom, int eecs, int eesk, int eedi) +nmc93cxx_eeprom_write(nmc93cxx_eeprom_t *dev, bool eecs, bool eesk, bool eedi) { - uint8_t tick = eeprom->dev.count; - uint8_t eedo = eeprom->dev.out; - uint16_t address = eeprom->dev.address; - uint8_t command = eeprom->dev.opcode; + assert(dev != NULL); - nmc93cxx_eeprom_log(1, "CS=%u SK=%u DI=%u DO=%u, tick = %u\n", - eecs, eesk, eedi, eedo, tick); + dev->write_tick++; - if (!eeprom->dev.oldena && eecs) { - /* Start chip select cycle. */ - nmc93cxx_eeprom_log(1, "Cycle start, waiting for 1st start bit (0)\n"); - tick = 0; - command = 0x0; - address = 0x0; - } else if (eeprom->dev.oldena && !eecs) { - /* End chip select cycle. This triggers write / erase. */ - if (!eeprom->dev.wp) { - uint8_t subcommand = address >> (eeprom->addrbits - 2); - if (command == 0 && subcommand == 2) { - /* Erase all. */ - for (address = 0; address < eeprom->size; address++) - eeprom->dev.data[address] = 0xffff; + nmc93cxx_eeprom_log(dev, 3, "EEPROM: CS=%u SK=%u DI=%u DO=%u, tick = %u\n", + eecs, eesk, eedi, nmc93cxx_eeprom_read(dev), dev->write_tick); - nmc93cxx_eeprom_save(eeprom); - } else if (command == 3) { - /* Erase word. */ - eeprom->dev.data[address] = 0xffff; - nmc93cxx_eeprom_save(eeprom); - } else if (tick >= (2 + 2 + eeprom->addrbits + 16)) { - if (command == 1) { - /* Write word. */ - eeprom->dev.data[address] &= eeprom->dev.dat; - nmc93cxx_eeprom_save(eeprom); - } else if (command == 0 && subcommand == 1) { - /* Write all. */ - for (address = 0; address < eeprom->size; address++) - eeprom->dev.data[address] &= eeprom->dev.dat; + if (dev->cs_state != eecs) { + dev->cs_state = eecs; - nmc93cxx_eeprom_save(eeprom); - } - } + /* Remember the rising edge tick so we don't process CLK signals at the same time */ + if (eecs) { + dev->last_cs_rising_edge_tick = dev->write_tick; } - /* Output DO is tristate, read results in 1. */ - eedo = 1; - } else if (eecs && !eeprom->dev.oldclk && eesk) { - /* Raising edge of clock shifts data in. */ - if (tick == 0) { - /* Wait for 1st start bit. */ - if (eedi == 0) { - nmc93cxx_eeprom_log(1, "Got correct 1st start bit, waiting for 2nd start bit (1)\n"); - tick++; - } else { - nmc93cxx_eeprom_log(1, "wrong 1st start bit (is 1, should be 0)\n"); - tick = 2; -#if 0 - ~ assert(!"wrong start bit"); -#endif - } - } else if (tick == 1) { - /* Wait for 2nd start bit. */ - if (eedi != 0) { - nmc93cxx_eeprom_log(1, "Got correct 2nd start bit, getting command + address\n"); - tick++; - } else { - nmc93cxx_eeprom_log(1, "1st start bit is longer than needed\n"); - } - } else if (tick < 2 + 2) { - /* Got 2 start bits, transfer 2 opcode bits. */ - tick++; - command <<= 1; - if (eedi) { - command += 1; - } - } else if (tick < 2 + 2 + eeprom->addrbits) { - /* Got 2 start bits and 2 opcode bits, transfer all address bits. */ - tick++; - address = ((address << 1) | eedi); - if (tick == 2 + 2 + eeprom->addrbits) { - nmc93cxx_eeprom_log(1, "Address = 0x%02x (value 0x%04x)\n", - address, eeprom->dev.data[address]); - if (command == 2) { - eedo = 0; - } - address = address % eeprom->size; - if (command == 0) { - /* Command code in upper 2 bits of address. */ - switch (address >> (eeprom->addrbits - 2)) { - case 0: - nmc93cxx_eeprom_log(1, "write disable command\n"); - eeprom->dev.wp = 1; - break; - case 1: - nmc93cxx_eeprom_log(1, "write all command\n"); - break; - case 2: - nmc93cxx_eeprom_log(1, "erase all command\n"); - break; - case 3: - nmc93cxx_eeprom_log(1, "write enable command\n"); - eeprom->dev.wp = 0; - break; - default: - break; - } - } else { - /* Read, write or erase word. */ - eeprom->dev.dat = eeprom->dev.data[address]; - } - } - } else if (tick < 2 + 2 + eeprom->addrbits + 16) { - /* Transfer 16 data bits. */ - tick++; - if (command == 2) { - /* Read word. */ - eedo = ((eeprom->dev.dat & 0x8000) != 0); - } - eeprom->dev.dat <<= 1; - eeprom->dev.dat += eedi; - } else { - nmc93cxx_eeprom_log(1, "additional unneeded tick, not processed\n"); - } + nmc93cxx_eeprom_handle_event(dev, eecs ? EVENT_CS_RISING_EDGE : EVENT_CS_FALLING_EDGE); + } + + dev->di_state = eedi; + + if (dev->clk_state != eesk) { + dev->clk_state = eesk; + nmc93cxx_eeprom_handle_event(dev, eesk ? EVENT_CLK_RISING_EDGE : EVENT_CLK_FALLING_EDGE); } - /* Save status of EEPROM. */ - eeprom->dev.count = tick; - eeprom->dev.oldena = eecs; - eeprom->dev.oldclk = eesk; - eeprom->dev.out = eedo; - eeprom->dev.address = address; - eeprom->dev.opcode = command; } -uint16_t -nmc93cxx_eeprom_read(nmc93cxx_eeprom_t *eeprom) +bool +nmc93cxx_eeprom_read(nmc93cxx_eeprom_t *dev) { - /* Return status of pin DO (0 or 1). */ - return eeprom->dev.out; + assert(dev != NULL); + + if (dev->state == STATE_WAIT_FOR_START_BIT) { + /* Read the state of the READY/BUSY line */ + return !dev->is_busy; + } else if (dev->state == STATE_READING_DATA) { + /* Read the current output bit */ + return ((dev->shift_register & 0x80000000) != 0); + } + + /* The DO pin is tristated */ + return true; } static void nmc93cxx_eeprom_close(void *priv) { - nmc93cxx_eeprom_t *eeprom = (nmc93cxx_eeprom_t *) priv; - FILE *fp = nvr_fopen(eeprom->filename, "wb"); + nmc93cxx_eeprom_t *dev = priv; + FILE *fp = nvr_fopen(dev->filename, "wb"); if (fp) { - fwrite(eeprom->dev.data, 2, eeprom->size, fp); + fwrite(dev->array_data, dev->data_bits / 8, dev->cells, fp); fclose(fp); } - free(priv); + log_close(dev->log); + free(dev); } -uint16_t * -nmc93cxx_eeprom_data(nmc93cxx_eeprom_t *eeprom) +const uint16_t * +nmc93cxx_eeprom_data(nmc93cxx_eeprom_t *dev) { + assert(dev != NULL); + /* Get EEPROM data array. */ - return &eeprom->dev.data[0]; + return &dev->array_data[0]; } const device_t nmc93cxx_device = { diff --git a/src/network/net_ne2000.c b/src/network/net_ne2000.c index decb23d02..979905385 100644 --- a/src/network/net_ne2000.c +++ b/src/network/net_ne2000.c @@ -182,7 +182,7 @@ nic_config_reset(void *priv) { nic_t *dev = (nic_t *) priv; - uint8_t *data = (uint8_t *) nmc93cxx_eeprom_data(dev->eeprom); + const uint8_t *data = (const uint8_t *) nmc93cxx_eeprom_data(dev->eeprom); dev->config1 = (data[0x00] & 0x7f) | 0x80; dev->config2 = (data[0x01] & 0xdf); @@ -425,7 +425,7 @@ page3_read(nic_t *dev, uint32_t off, UNUSED(unsigned int len)) case 0xd: /* CONFIG4 */ if (dev->board == NE2K_RTL8019AS_PNP) { - uint8_t *data = (uint8_t *) nmc93cxx_eeprom_data(dev->eeprom); + const uint8_t *data = (const uint8_t *) nmc93cxx_eeprom_data(dev->eeprom); ret = data[0x03]; } break; @@ -464,7 +464,7 @@ page3_write(nic_t *dev, uint32_t off, uint32_t val, UNUSED(unsigned len)) if ((val & 0xc0) == 0x80) nmc93cxx_eeprom_write(dev->eeprom, !!(val & 0x08), !!(val & 0x04), !!(val & 0x02)); else if ((val & 0xc0) == 0x40) { - uint8_t *data = (uint8_t *) nmc93cxx_eeprom_data(dev->eeprom); + const uint8_t *data = (const uint8_t *) nmc93cxx_eeprom_data(dev->eeprom); dev->config1 = (data[0x00] & 0x7f) | 0x80; dev->config2 = (data[0x01] & 0xdf); @@ -1391,18 +1391,15 @@ nic_init(const device_t *info) nmc93cxx_eeprom_params_t params; char filename[1024] = { 0 }; - params.nwords = 64; - params.default_content = (uint16_t *) dev->eeprom_data; + params.type = NMC_93C46_x16_64; + params.default_content = dev->eeprom_data; params.filename = filename; int inst = device_get_instance(); snprintf(filename, sizeof(filename), "nmc93cxx_eeprom_%s_%d.nvr", info->internal_name, inst); dev->eeprom = device_add_inst_params(&nmc93cxx_device, inst, ¶ms); - if (dev->eeprom == NULL) { - free(dev); - return NULL; - } + if (info->local == NE2K_RTL8019AS_PNP) { - uint8_t *data = (uint8_t *) nmc93cxx_eeprom_data(dev->eeprom); + const uint8_t *data = (const uint8_t *) nmc93cxx_eeprom_data(dev->eeprom); dev->config1 = (data[0x00] & 0x7f) | 0x80; dev->config2 = (data[0x01] & 0xdf); diff --git a/src/network/net_rtl8139.c b/src/network/net_rtl8139.c index 5c50f088c..0daf25a6a 100644 --- a/src/network/net_rtl8139.c +++ b/src/network/net_rtl8139.c @@ -3286,15 +3286,11 @@ nic_init(const device_t *info) for (uint32_t i = 0; i < 6; i++) s->phys[MAC0 + i] = mac_bytes[i]; - params.nwords = 64; - params.default_content = (uint16_t *) s->eeprom_data; + params.type = NMC_93C46_x16_64; + params.default_content = s->eeprom_data; params.filename = filename; snprintf(filename, sizeof(filename), "nmc93cxx_eeprom_%s_%d.nvr", info->internal_name, s->inst); s->eeprom = device_add_inst_params(&nmc93cxx_device, s->inst, ¶ms); - if (s->eeprom == NULL) { - free(s); - return NULL; - } s->nic = network_attach(s, (uint8_t *) &s->phys[MAC0], rtl8139_do_receive, rtl8139_set_link_status); timer_add(&s->timer, rtl8139_timer, s, 0); diff --git a/src/network/net_tulip.c b/src/network/net_tulip.c index e95810cfa..b5219cf23 100644 --- a/src/network/net_tulip.c +++ b/src/network/net_tulip.c @@ -1642,16 +1642,12 @@ nic_init(const device_t *info) } if (info->local != 3) { - params.nwords = 64; - params.default_content = (uint16_t *) s->eeprom_data; + params.type = NMC_93C46_x16_64; + params.default_content = s->eeprom_data; params.filename = filename; int inst = device_get_instance(); snprintf(filename, sizeof(filename), "nmc93cxx_eeprom_%s_%d.nvr", info->internal_name, inst); s->eeprom = device_add_inst_params(&nmc93cxx_device, inst, ¶ms); - if (s->eeprom == NULL) { - free(s); - return NULL; - } } s->tulip_pci_bar[0].addr_regs[0] = 1; diff --git a/src/scsi/scsi_pcscsi.c b/src/scsi/scsi_pcscsi.c index 51aea3f46..327d35cbb 100644 --- a/src/scsi/scsi_pcscsi.c +++ b/src/scsi/scsi_pcscsi.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -2091,19 +2092,17 @@ esp_pci_write(UNUSED(int func), int addr, UNUSED(int len), uint8_t val, void *pr { esp_t *dev = (esp_t *) priv; uint8_t valxor; - int eesk; - int eedi; esp_log("%04X:%08X: ESP PCI: Write value %02X to register %02X\n", CS, cpu_state.pc, val, addr); if (!dev->local) { if ((addr >= 0x80) && (addr <= 0xFF)) { if (addr == 0x80) { - eesk = val & 0x80 ? 1 : 0; - eedi = val & 0x40 ? 1 : 0; - nmc93cxx_eeprom_write(dev->eeprom, 1, eesk, eedi); + bool eesk = !!(val & 0x80); + bool eedi = !!(val & 0x40); + nmc93cxx_eeprom_write(dev->eeprom, true, eesk, eedi); } else if (addr == 0xc0) - nmc93cxx_eeprom_write(dev->eeprom, 0, 0, 0); + nmc93cxx_eeprom_write(dev->eeprom, false, false, false); // esp_log("ESP PCI: Write value %02X to register %02X\n", val, addr); return; } @@ -2300,8 +2299,8 @@ dc390_init(const device_t *info) dev->eeprom_data[EE_CHKSUM1] = checksum & 0xff; dev->eeprom_data[EE_CHKSUM2] = checksum >> 8; - params.nwords = 64; - params.default_content = (uint16_t *) dev->eeprom_data; + params.type = NMC_93C46_x16_64; + params.default_content = dev->eeprom_data; params.filename = filename; snprintf(filename, sizeof(filename), "nmc93cxx_eeprom_%s_%d.nvr", info->internal_name, dev->eeprom_inst); dev->eeprom = device_add_inst_params(&nmc93cxx_device, dev->eeprom_inst, ¶ms); diff --git a/src/video/vid_s3.c b/src/video/vid_s3.c index 3965391cb..37ade5ad1 100644 --- a/src/video/vid_s3.c +++ b/src/video/vid_s3.c @@ -12237,15 +12237,11 @@ s3_init(const device_t *info) checksum = s3_calc_crc16(64, s3->eeprom_data); s3->eeprom_data[0x00] = checksum; - params.nwords = 64; + params.type = NMC_93C46_x16_64; params.default_content = s3->eeprom_data; params.filename = filename; snprintf(filename, sizeof(filename), "nmc93cxx_eeprom_%s_%d.nvr", info->internal_name, s3->eeprom_inst); s3->eeprom = device_add_inst_params(&nmc93cxx_device, s3->eeprom_inst, ¶ms); - if (s3->eeprom == NULL) { - free(s3); - return NULL; - } } s3->accel.multifunc[0xd] = 0xd000;