Attempt 2 #2

Open
seeseemelk wants to merge 56 commits from attempt-2 into master
5 changed files with 592 additions and 2 deletions
Showing only changes of commit 35bce963be - Show all commits

View File

@@ -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] 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 `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. - [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. - [x] 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 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 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 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` - [ ] Create a ARP subsystem. Create the `arp` command that shows current ARP tables. Again, this info should be found in `/sys`

View File

@@ -26,6 +26,7 @@ add_executable(kernel
fat32.c fat32.c
floppy.c floppy.c
ne2000.c ne2000.c
e3c509.c
env.c env.c
keyboard.c keyboard.c
interrupts.S interrupts.S

427
src/e3c509.c Normal file
View File

@@ -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 <string.h>
/* 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);

158
src/e3c509.h Normal file
View File

@@ -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 <stdint.h>
/* ================================================================
* 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 */

View File

@@ -5,6 +5,7 @@
#include "keyboard.h" #include "keyboard.h"
#include "floppy.h" #include "floppy.h"
#include "ne2000.h" #include "ne2000.h"
#include "e3c509.h"
#include <stdint.h> #include <stdint.h>
/* Forward declaration for kernel panic or similar */ /* Forward declaration for kernel panic or similar */
@@ -72,6 +73,9 @@ void isr_handler(registers_t *regs)
} else if (regs->int_no == 41) { } else if (regs->int_no == 41) {
/* NE2000 Ethernet IRQ (IRQ 9, vector 41) */ /* NE2000 Ethernet IRQ (IRQ 9, vector 41) */
ne2k_irq(); ne2k_irq();
} else if (regs->int_no == 42) {
/* 3C509B Ethernet IRQ (IRQ 10, vector 42) */
e3c509_irq();
} }
return; return;
} }