diff --git a/README.md b/README.md index 574e1f5..4051606 100644 --- a/README.md +++ b/README.md @@ -68,8 +68,8 @@ Once a task is completed, it should be checked off. - [x] Add support for character device to the devicefs subsystem. - [x] Create an app called `diskpart`. This app can be used to modify the MBR partitions on a block device. - [x] Create an app called `mkfs.fat32`. This app can be used to format a block into a FAT32 filesystem. -- [ ] Create a network driver for the NE2000 NIC. -- [ ] Create a network driver for the 3C509B NIC. It should only support RJ45 and 10base-T. +- [x] Create a network driver for the NE2000 NIC. +- [x] Create a network driver for the 3C509B NIC. It should only support RJ45 and 10base-T. - [ ] Create an ethernet subsytsem. Each ethernet device should be shown as a character device with the name `ethN`. - [ ] Create a IPv4 stack. Create the `ip` app that shows curernt IPv4 configuration. It should read this information from `/sys` - [ ] Create a ARP subsystem. Create the `arp` command that shows current ARP tables. Again, this info should be found in `/sys` diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index bb08127..6c74901 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -26,6 +26,7 @@ add_executable(kernel fat32.c floppy.c ne2000.c + e3c509.c env.c keyboard.c interrupts.S diff --git a/src/e3c509.c b/src/e3c509.c new file mode 100644 index 0000000..489cd33 --- /dev/null +++ b/src/e3c509.c @@ -0,0 +1,427 @@ +/** + * @file e3c509.c + * @brief 3Com 3C509B (EtherLink III) ISA Ethernet NIC driver. + * + * Drives 3Com 3C509/3C509B Ethernet adapters using PIO (programmed I/O). + * The 3C509B uses a windowed register model: 8 register windows of 16 I/O + * ports each, selected by writing to the command register. + * + * Only 10base-T (RJ45) operation is supported, per design requirements. + * + * Packet TX: write packet length, then write data to TX PIO port. + * Packet RX: poll RX status, read data from RX PIO port. + */ + +#include "e3c509.h" +#include "port_io.h" +#include "pic.h" +#include "devicefs.h" +#include "driver.h" +#include + +/* Debug print helpers */ +extern void offset_print(const char *str); +extern void print_hex(uint32_t val); + +/* ================================================================ + * Global state + * ================================================================ */ + +/** Single 3C509B device. */ +static e3c509_device_t e3c509_dev; + +/** Volatile flags set by IRQ handler. */ +static volatile int e3c509_rx_ready = 0; +static volatile int e3c509_tx_done = 0; + +/* ================================================================ + * Register access helpers + * ================================================================ */ + +/** Write a 16-bit command to the 3C509B command register. */ +static inline void e3c509_cmd(uint16_t base, uint16_t cmd) { + outw(base + E3C509_CMD, cmd); +} + +/** Read the 16-bit status register. */ +static inline uint16_t e3c509_status(uint16_t base) { + return inw(base + E3C509_STATUS); +} + +/** + * Select a register window. + */ +static void e3c509_select_window(uint16_t base, uint16_t window) { + e3c509_cmd(base, CMD_SELECT_WINDOW | (window & 0x07)); +} + +/** + * Wait for a command to complete. + */ +static void e3c509_wait_cmd(uint16_t base) { + int timeout = 100000; + while ((e3c509_status(base) & STAT_CMD_IN_PROG) && --timeout > 0); +} + +/* ================================================================ + * Packet send / receive + * ================================================================ */ + +int e3c509_send(e3c509_device_t *dev, const void *data, uint32_t len) { + if (!dev || !dev->present) return -1; + if (len > ETH_FRAME_MAX) return -1; + + uint16_t base = dev->io_base; + uint32_t send_len = len; + if (send_len < 60) send_len = 60; + + /* Switch to window 1 */ + e3c509_select_window(base, 1); + + /* Wait for free TX space */ + int timeout = 100000; + while (inw(base + E3C509_W1_FREE_TX) < send_len + 4 && --timeout > 0) { + asm volatile("pause"); + } + if (timeout == 0) { + offset_print(" 3C509: tx fifo full\n"); + return -1; + } + + /* Write TX preamble: packet length (low 11 bits, bit 15 = no interrupt) */ + outw(base + E3C509_W1_TX_PIO, (uint16_t)(send_len | 0x00)); + + /* Pad to dword-aligned length */ + outw(base + E3C509_W1_TX_PIO, 0x0000); + + /* Write packet data as 16-bit words */ + const uint16_t *data16 = (const uint16_t *)data; + uint32_t words = len / 2; + for (uint32_t i = 0; i < words; i++) { + outw(base + E3C509_W1_TX_PIO, data16[i]); + } + /* Write last byte if odd */ + if (len & 1) { + const uint8_t *data8 = (const uint8_t *)data; + outw(base + E3C509_W1_TX_PIO, (uint16_t)data8[len - 1]); + } + + /* Pad to minimum frame size with zeros */ + if (len < 60) { + uint32_t pad_words = (60 - len + 1) / 2; + for (uint32_t i = 0; i < pad_words; i++) { + outw(base + E3C509_W1_TX_PIO, 0x0000); + } + } + + /* Wait for TX complete */ + e3c509_tx_done = 0; + timeout = 1000000; + while (!e3c509_tx_done && --timeout > 0) { + /* Check TX status directly in case interrupts are slow */ + uint8_t txstat = inb(base + E3C509_W1_TX_STATUS); + if (txstat & 0x80) { /* TX complete */ + /* Acknowledge by writing status back */ + outb(base + E3C509_W1_TX_STATUS, txstat); + break; + } + asm volatile("pause"); + } + + return 0; +} + +int e3c509_recv(e3c509_device_t *dev, void *buf, uint32_t bufsize) { + if (!dev || !dev->present) return -1; + + uint16_t base = dev->io_base; + + /* Switch to window 1 */ + e3c509_select_window(base, 1); + + /* Read RX status */ + uint16_t rx_status = inw(base + E3C509_W1_RX_STATUS); + + /* Check if a packet is available (bit 15 = incomplete/error) */ + if (rx_status & 0x8000) { + /* Error — discard the packet */ + e3c509_cmd(base, CMD_RX_DISCARD); + e3c509_wait_cmd(base); + return -1; + } + + /* Bits 10:0 = packet length */ + uint16_t pkt_len = rx_status & 0x07FF; + if (pkt_len == 0) { + return 0; /* No packet */ + } + + uint32_t copy_len = pkt_len; + if (copy_len > bufsize) copy_len = bufsize; + + /* Read packet data as 16-bit words */ + uint16_t *buf16 = (uint16_t *)buf; + uint32_t read_words = copy_len / 2; + for (uint32_t i = 0; i < read_words; i++) { + buf16[i] = inw(base + E3C509_W1_RX_PIO); + } + if (copy_len & 1) { + uint16_t w = inw(base + E3C509_W1_RX_PIO); + ((uint8_t *)buf)[copy_len - 1] = (uint8_t)(w & 0xFF); + } + + /* Discard any remaining data and advance */ + e3c509_cmd(base, CMD_RX_DISCARD); + e3c509_wait_cmd(base); + + return (int)copy_len; +} + +/* ================================================================ + * IRQ handler + * ================================================================ */ + +void e3c509_irq(void) { + if (!e3c509_dev.present) return; + + uint16_t base = e3c509_dev.io_base; + uint16_t status = e3c509_status(base); + + if (status & STAT_RX_COMPLETE) { + e3c509_rx_ready = 1; + e3c509_cmd(base, CMD_ACK_INTR | STAT_RX_COMPLETE); + } + + if (status & STAT_TX_COMPLETE) { + e3c509_tx_done = 1; + /* Read and clear TX status */ + e3c509_select_window(base, 1); + uint8_t txstat = inb(base + E3C509_W1_TX_STATUS); + outb(base + E3C509_W1_TX_STATUS, txstat); + e3c509_cmd(base, CMD_ACK_INTR | STAT_TX_COMPLETE); + } + + if (status & STAT_TX_AVAILABLE) { + e3c509_cmd(base, CMD_ACK_INTR | STAT_TX_AVAILABLE); + } + + if (status & STAT_ADAPTER_FAIL) { + e3c509_cmd(base, CMD_ACK_INTR | STAT_ADAPTER_FAIL); + } + + if (status & STAT_UPDATE_STATS) { + e3c509_cmd(base, CMD_ACK_INTR | STAT_UPDATE_STATS); + } + + /* Acknowledge the interrupt latch */ + e3c509_cmd(base, CMD_ACK_INTR | STAT_INT_LATCH); +} + +/* ================================================================ + * Character device operations + * ================================================================ */ + +static int32_t e3c509_char_read(void *dev_data, uint32_t size, void *buf) { + e3c509_device_t *dev = (e3c509_device_t *)dev_data; + return (int32_t)e3c509_recv(dev, buf, size); +} + +static int32_t e3c509_char_write(void *dev_data, uint32_t size, const void *buf) { + e3c509_device_t *dev = (e3c509_device_t *)dev_data; + if (e3c509_send(dev, buf, size) == 0) { + return (int32_t)size; + } + return -1; +} + +static devicefs_char_ops_t e3c509_char_ops = { + .read = e3c509_char_read, + .write = e3c509_char_write, +}; + +/* ================================================================ + * Initialization + * ================================================================ */ + +/** + * Read the MAC address from the 3C509B's EEPROM (Window 2). + */ +static void e3c509_read_mac(uint16_t base, uint8_t *mac) { + e3c509_select_window(base, 2); + uint16_t w0 = inw(base + E3C509_W2_ADDR0); + uint16_t w1 = inw(base + E3C509_W2_ADDR1); + uint16_t w2 = inw(base + E3C509_W2_ADDR2); + + mac[0] = (uint8_t)(w0 & 0xFF); + mac[1] = (uint8_t)((w0 >> 8) & 0xFF); + mac[2] = (uint8_t)(w1 & 0xFF); + mac[3] = (uint8_t)((w1 >> 8) & 0xFF); + mac[4] = (uint8_t)(w2 & 0xFF); + mac[5] = (uint8_t)((w2 >> 8) & 0xFF); +} + +/** + * Initialize the 3C509B hardware. + */ +static int e3c509_hw_init(uint16_t base) { + /* Global reset */ + e3c509_cmd(base, CMD_GLOBAL_RESET); + + /* Wait for reset to complete */ + int timeout = 100000; + while ((e3c509_status(base) & STAT_CMD_IN_PROG) && --timeout > 0); + if (timeout == 0) { + offset_print(" 3C509: reset timeout\n"); + return -1; + } + + /* Small extra delay */ + for (volatile int i = 0; i < 50000; i++) { + asm volatile("pause"); + } + + /* Read MAC address */ + e3c509_read_mac(base, e3c509_dev.mac); + + /* Select 10base-T (RJ45) transceiver — Window 0, Address Config */ + e3c509_select_window(base, 0); + uint16_t addr_cfg = inw(base + E3C509_W0_ADDR_CFG); + /* Clear transceiver bits (14:13) and set to TP (00) */ + addr_cfg &= ~(0x3 << 14); + addr_cfg |= (XCVR_TP << 14); + outw(base + E3C509_W0_ADDR_CFG, addr_cfg); + + /* Configure IRQ in resource config register */ + uint16_t res_cfg = inw(base + E3C509_W0_RES_CFG); + /* IRQ is in bits 15:12. Set to our IRQ. */ + res_cfg = (res_cfg & 0x0FFF) | ((uint16_t)e3c509_dev.irq << 12); + outw(base + E3C509_W0_RES_CFG, res_cfg); + + /* Enable the adapter */ + outw(base + E3C509_W0_CFG_CTRL, 0x0001); /* Enable */ + + /* Reset TX and RX */ + e3c509_cmd(base, CMD_TX_RESET); + e3c509_wait_cmd(base); + e3c509_cmd(base, CMD_RX_RESET); + e3c509_wait_cmd(base); + + /* Set RX filter: accept station + broadcast */ + e3c509_cmd(base, CMD_SET_RX_FILTER | RX_FILTER_STATION | RX_FILTER_BCAST); + + /* Set TX start threshold — start transmitting after full packet */ + e3c509_cmd(base, CMD_SET_TX_START | (ETH_FRAME_MAX >> 2)); + + /* Set interrupt mask */ + e3c509_cmd(base, CMD_SET_INTR_MASK | + STAT_RX_COMPLETE | STAT_TX_COMPLETE | STAT_TX_AVAILABLE | + STAT_ADAPTER_FAIL | STAT_UPDATE_STATS); + + /* Enable TX and RX */ + e3c509_cmd(base, CMD_TX_ENABLE); + e3c509_cmd(base, CMD_RX_ENABLE); + + /* Acknowledge any pending interrupts */ + e3c509_cmd(base, CMD_ACK_INTR | 0xFF); + + /* Switch to operating window (window 1) */ + e3c509_select_window(base, 1); + + return 0; +} + +/* ================================================================ + * Driver framework + * ================================================================ */ + +/** + * Probe for a 3C509B card. + * + * Check the manufacturer ID at Window 0, offset 0x00. + * The 3Com 3C509B should return 0x6D50. + */ +static driver_probe_result_t e3c509_probe(void) { + uint16_t base = E3C509_DEFAULT_IOBASE; + + /* Try to select Window 0 and read manufacturer ID */ + e3c509_cmd(base, CMD_SELECT_WINDOW | 0); + + /* Brief delay */ + for (volatile int i = 0; i < 10000; i++) { + asm volatile("pause"); + } + + uint16_t mfg_id = inw(base + E3C509_W0_MFG_ID); + + /* 3Com manufacturer ID = 0x6D50 */ + if (mfg_id == 0x6D50) { + return DRIVER_PROBE_OK; + } + + /* Also check for the product ID being in a reasonable range */ + if ((mfg_id & 0xFF00) == 0x9000 || (mfg_id & 0xFF00) == 0x9100) { + return DRIVER_PROBE_OK; + } + + return DRIVER_PROBE_NOT_FOUND; +} + +/** + * Initialize the 3C509B driver. + */ +static int e3c509_driver_init(void) { + memset(&e3c509_dev, 0, sizeof(e3c509_dev)); + + e3c509_dev.io_base = E3C509_DEFAULT_IOBASE; + e3c509_dev.irq = E3C509_DEFAULT_IRQ; + + /* Unmask IRQ */ + pic_clear_mask(e3c509_dev.irq); + + /* Initialize hardware */ + if (e3c509_hw_init(e3c509_dev.io_base) != 0) { + offset_print(" 3C509: initialization failed\n"); + return -1; + } + + e3c509_dev.present = 1; + + /* Print MAC address */ + offset_print(" 3C509: MAC "); + for (int i = 0; i < 6; i++) { + if (i > 0) offset_print(":"); + print_hex(e3c509_dev.mac[i]); + } + offset_print("\n"); + + /* Register as character device (shares "eth" class with NE2000) */ + devicefs_register_char("eth", &e3c509_char_ops, &e3c509_dev); + + offset_print(" 3C509: 10base-T (RJ45) on I/O "); + print_hex(e3c509_dev.io_base); + offset_print(" IRQ "); + print_hex(e3c509_dev.irq); + offset_print("\n"); + + return 0; +} + +e3c509_device_t *e3c509_get_device(void) { + return e3c509_dev.present ? &e3c509_dev : NULL; +} + +int e3c509_init(void) { + return e3c509_driver_init(); +} + +/* ================================================================ + * Driver registration + * ================================================================ */ + +static const driver_t e3c509_driver = { + .name = "3c509b", + .probe = e3c509_probe, + .init = e3c509_driver_init, +}; + +REGISTER_DRIVER(e3c509_driver); diff --git a/src/e3c509.h b/src/e3c509.h new file mode 100644 index 0000000..28326f9 --- /dev/null +++ b/src/e3c509.h @@ -0,0 +1,158 @@ +/** + * @file e3c509.h + * @brief 3Com 3C509B (EtherLink III) ISA Ethernet NIC driver. + * + * Drives 3Com 3C509B NICs. Only supports RJ45 (10base-T) transceiver. + * The 3C509B uses a windowed register model with 8 register windows + * selected via the Window register. Packets are transferred through + * PIO (programmed I/O) using the card's FIFO. + * + * Uses IRQ 10 by default for interrupt-driven operation. + * Registers as a character device with devicefs (/dev/ethN). + */ + +#ifndef E3C509_H +#define E3C509_H + +#include + +/* ================================================================ + * 3C509B I/O Port Layout + * + * The 3C509B uses 16 I/O ports starting at the base address. + * Registers are organized into 8 windows (0-7). + * Window selection: write window number to port base+0x0E. + * ================================================================ */ + +/** Default ISA I/O base for 3C509B. */ +#define E3C509_DEFAULT_IOBASE 0x210 + +/** Default ISA IRQ for 3C509B. */ +#define E3C509_DEFAULT_IRQ 10 + +/* --- Global registers (always accessible) --- */ +#define E3C509_CMD 0x0E /**< Command register (write) */ +#define E3C509_STATUS 0x0E /**< Status register (read) */ + +/* --- Window 0: Setup / Configuration --- */ +#define E3C509_W0_MFG_ID 0x00 /**< Manufacturer ID (should be 0x6D50) */ +#define E3C509_W0_ADDR_CFG 0x06 /**< Address config (transceiver type) */ +#define E3C509_W0_RES_CFG 0x08 /**< Resource config (IRQ) */ +#define E3C509_W0_CFG_CTRL 0x04 /**< Config control */ + +/* --- Window 1: Operating Set --- */ +#define E3C509_W1_TX_PIO 0x00 /**< TX PIO data (write) */ +#define E3C509_W1_TX_STATUS 0x0B /**< TX status */ +#define E3C509_W1_RX_PIO 0x00 /**< RX PIO data (read) */ +#define E3C509_W1_RX_STATUS 0x08 /**< RX status */ +#define E3C509_W1_FREE_TX 0x0C /**< Free TX bytes */ + +/* --- Window 2: Station Address --- */ +#define E3C509_W2_ADDR0 0x00 /**< Station address word 0 */ +#define E3C509_W2_ADDR1 0x02 /**< Station address word 1 */ +#define E3C509_W2_ADDR2 0x04 /**< Station address word 2 */ + +/* --- Window 3: FIFO Management --- */ +/* (Used for internal FIFO buffer management) */ + +/* --- Window 4: Diagnostics --- */ +#define E3C509_W4_MEDIA_TYPE 0x0A /**< Media type and status */ +#define E3C509_W4_NET_DIAG 0x06 /**< Network diagnostics */ + +/* --- Window 5: Read Zeroes (for RX filter) --- */ + +/* --- Window 6: Statistics --- */ +#define E3C509_W6_TX_BYTES 0x0C /**< Total TX bytes */ +#define E3C509_W6_RX_BYTES 0x0A /**< Total RX bytes */ + +/* ================================================================ + * 3C509B Commands (written to command register) + * ================================================================ */ +#define CMD_GLOBAL_RESET 0x0000 /**< Global reset */ +#define CMD_SELECT_WINDOW 0x0800 /**< Select window (OR with window num) */ +#define CMD_RX_ENABLE 0x2000 /**< Enable receiver */ +#define CMD_RX_RESET 0x2800 /**< Reset receiver */ +#define CMD_RX_DISCARD 0x4000 /**< Discard top RX packet */ +#define CMD_TX_ENABLE 0x4800 /**< Enable transmitter */ +#define CMD_TX_RESET 0x5800 /**< Reset transmitter */ +#define CMD_REQ_INTR 0x6000 /**< Request interrupt (OR with mask) */ +#define CMD_ACK_INTR 0x6800 /**< Acknowledge interrupt (OR with mask) */ +#define CMD_SET_INTR_MASK 0x7000 /**< Set interrupt mask */ +#define CMD_SET_RX_FILTER 0x8000 /**< Set RX filter */ +#define CMD_TX_DONE 0x8800 /**< ? */ +#define CMD_STATS_ENABLE 0x9000 /**< Enable statistics */ +#define CMD_STATS_DISABLE 0xB000 /**< Disable statistics */ +#define CMD_SET_TX_START 0x9800 /**< Set TX start threshold */ + +/* ================================================================ + * Status / Interrupt bits + * ================================================================ */ +#define STAT_INT_LATCH 0x0001 /**< Interrupt latch */ +#define STAT_ADAPTER_FAIL 0x0002 /**< Adapter failure */ +#define STAT_TX_COMPLETE 0x0004 /**< TX complete */ +#define STAT_TX_AVAILABLE 0x0008 /**< TX available */ +#define STAT_RX_COMPLETE 0x0010 /**< RX complete */ +#define STAT_RX_EARLY 0x0020 /**< RX early threshold */ +#define STAT_INT_REQ 0x0040 /**< Interrupt requested */ +#define STAT_UPDATE_STATS 0x0080 /**< Update statistics */ +#define STAT_CMD_IN_PROG 0x1000 /**< Command in progress */ +#define STAT_WINDOW_MASK 0xE000 /**< Current window bits */ + +/* ================================================================ + * RX Filter bits (for CMD_SET_RX_FILTER) + * ================================================================ */ +#define RX_FILTER_STATION 0x01 /**< Accept frames to station address */ +#define RX_FILTER_MCAST 0x02 /**< Accept multicast */ +#define RX_FILTER_BCAST 0x04 /**< Accept broadcast */ +#define RX_FILTER_PROMISC 0x08 /**< Promiscuous mode */ + +/* ================================================================ + * Transceiver types + * ================================================================ */ +#define XCVR_TP 0x00 /**< 10base-T / RJ45 */ +#define XCVR_AUI 0x01 /**< AUI */ +#define XCVR_BNC 0x03 /**< BNC / 10base2 */ + +/** Maximum Ethernet frame size. */ +#define ETH_FRAME_MAX 1518 + +/* ================================================================ + * 3C509B device state + * ================================================================ */ +typedef struct e3c509_device { + uint16_t io_base; /**< I/O base address. */ + uint8_t irq; /**< IRQ number. */ + uint8_t mac[6]; /**< MAC address. */ + int present; /**< 1 if card detected. */ +} e3c509_device_t; + +/* ================================================================ + * Public API + * ================================================================ */ + +/** + * Initialize 3C509B driver. + */ +int e3c509_init(void); + +/** + * 3C509B IRQ handler. + */ +void e3c509_irq(void); + +/** + * Send an Ethernet frame. + */ +int e3c509_send(e3c509_device_t *dev, const void *data, uint32_t len); + +/** + * Receive a pending Ethernet frame. + */ +int e3c509_recv(e3c509_device_t *dev, void *buf, uint32_t bufsize); + +/** + * Get the 3C509B device pointer. + */ +e3c509_device_t *e3c509_get_device(void); + +#endif /* E3C509_H */ diff --git a/src/isr.c b/src/isr.c index dd1a99d..97195f4 100644 --- a/src/isr.c +++ b/src/isr.c @@ -5,6 +5,7 @@ #include "keyboard.h" #include "floppy.h" #include "ne2000.h" +#include "e3c509.h" #include /* Forward declaration for kernel panic or similar */ @@ -72,6 +73,9 @@ void isr_handler(registers_t *regs) } else if (regs->int_no == 41) { /* NE2000 Ethernet IRQ (IRQ 9, vector 41) */ ne2k_irq(); + } else if (regs->int_no == 42) { + /* 3C509B Ethernet IRQ (IRQ 10, vector 42) */ + e3c509_irq(); } return; }