diff --git a/README.md b/README.md index 4051606..81b2c08 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ Once a task is completed, it should be checked off. - [x] Create an app called `mkfs.fat32`. This app can be used to format a block into a FAT32 filesystem. - [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`. +- [x] 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` - [ ] Create a DHCP subsystem. Create the `dhcp` command to show current DHCP status information. diff --git a/build.log b/build.log index fadc68d..5373539 100644 --- a/build.log +++ b/build.log @@ -1,9 +1,4 @@ -make: Warning: File 'Makefile' has modification time 48547 s in the future -make[1]: Warning: File 'CMakeFiles/Makefile2' has modification time 48547 s in the future -make[2]: Warning: File 'CMakeFiles/apps.dir/progress.make' has modification time 48547 s in the future -make[2]: warning: Clock skew detected. Your build may be incomplete. -make[2]: Warning: File 'CMakeFiles/apps.dir/progress.make' has modification time 48547 s in the future -[ 3%] Building user-mode applications +[ 2%] Building user-mode applications Building app: cat Built: /workspaces/claude-os/build/apps_bin/cat (310 bytes) Building app: diskpart @@ -33,23 +28,9 @@ Building app: sh | ^~~~ 1 warning generated. Built: /workspaces/claude-os/build/apps_bin/sh (3428 bytes) -make[2]: warning: Clock skew detected. Your build may be incomplete. -[ 3%] Built target apps -make[2]: Warning: File 'CMakeFiles/initrd.dir/progress.make' has modification time 48530 s in the future -make[2]: warning: Clock skew detected. Your build may be incomplete. -make[2]: Warning: File 'CMakeFiles/initrd.dir/progress.make' has modification time 48530 s in the future -make[2]: warning: Clock skew detected. Your build may be incomplete. -[ 6%] Built target initrd -make[2]: Warning: File 'src/CMakeFiles/kernel.dir/build.make' has modification time 48529 s in the future -make[2]: warning: Clock skew detected. Your build may be incomplete. -make[2]: Warning: File 'src/CMakeFiles/kernel.dir/build.make' has modification time 48529 s in the future -[ 9%] Building C object src/CMakeFiles/kernel.dir/ne2000.c.o -[ 12%] Linking C executable ../bin/kernel -make[2]: warning: Clock skew detected. Your build may be incomplete. -[ 96%] Built target kernel -make[2]: Warning: File 'CMakeFiles/iso.dir/progress.make' has modification time 48527 s in the future -make[2]: warning: Clock skew detected. Your build may be incomplete. -make[2]: Warning: File 'CMakeFiles/iso.dir/progress.make' has modification time 48527 s in the future +[ 2%] Built target apps +[ 5%] Built target initrd +[ 97%] Built target kernel [100%] Generating bootable ISO image xorriso 1.5.6 : RockRidge filesystem manipulator, libburnia project. @@ -57,17 +38,14 @@ Drive current: -outdev 'stdio:/workspaces/claude-os/release/claude-os.iso' Media current: stdio file, overwriteable Media status : is blank Media summary: 0 sessions, 0 data blocks, 0 data, 126g free -Added to ISO image: directory '/'='/tmp/grub.jahFGc' +Added to ISO image: directory '/'='/tmp/grub.KhfgHk' xorriso : UPDATE : 581 files added in 1 seconds Added to ISO image: directory '/'='/workspaces/claude-os/build/isodir' xorriso : UPDATE : 586 files added in 1 seconds xorriso : NOTE : Copying to System Area: 512 bytes from file '/usr/lib/grub/i386-pc/boot_hybrid.img' xorriso : UPDATE : Thank you for being patient. Working since 0 seconds. -ISO image produced: 5943 sectors -Written to medium : 5943 sectors at LBA 0 +ISO image produced: 5967 sectors +Written to medium : 5967 sectors at LBA 0 Writing to 'stdio:/workspaces/claude-os/release/claude-os.iso' completed successfully. -make[2]: warning: Clock skew detected. Your build may be incomplete. [100%] Built target iso -make[1]: warning: Clock skew detected. Your build may be incomplete. -make: warning: Clock skew detected. Your build may be incomplete. diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6c74901..48f3dd0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -27,6 +27,7 @@ add_executable(kernel floppy.c ne2000.c e3c509.c + ethernet.c env.c keyboard.c interrupts.S diff --git a/src/e3c509.c b/src/e3c509.c index 489cd33..4967592 100644 --- a/src/e3c509.c +++ b/src/e3c509.c @@ -16,6 +16,7 @@ #include "port_io.h" #include "pic.h" #include "devicefs.h" +#include "ethernet.h" #include "driver.h" #include @@ -217,28 +218,6 @@ void e3c509_irq(void) { 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 * ================================================================ */ @@ -394,8 +373,11 @@ static int e3c509_driver_init(void) { } offset_print("\n"); - /* Register as character device (shares "eth" class with NE2000) */ - devicefs_register_char("eth", &e3c509_char_ops, &e3c509_dev); + /* Register with ethernet subsystem (creates /dev/ethN) */ + ethernet_register(e3c509_dev.mac, + (eth_send_fn)e3c509_send, + (eth_recv_fn)e3c509_recv, + &e3c509_dev); offset_print(" 3C509: 10base-T (RJ45) on I/O "); print_hex(e3c509_dev.io_base); diff --git a/src/ethernet.c b/src/ethernet.c new file mode 100644 index 0000000..72cf7eb --- /dev/null +++ b/src/ethernet.c @@ -0,0 +1,393 @@ +/** + * @file ethernet.c + * @brief Ethernet subsystem implementation. + * + * Provides a unified layer over individual Ethernet NIC drivers. + * NIC drivers register through ethernet_register(), which creates the + * `/dev/ethN` char device and adds the interface to an internal table. + * + * The subsystem also exposes network interface info via sysfs at + * `/sys/net`, allowing userspace tools to query interface status. + */ + +#include "ethernet.h" +#include "devicefs.h" +#include "sysfs.h" +#include + +/* Debug print helpers */ +extern void offset_print(const char *str); +extern void print_hex(uint32_t val); + +/* ================================================================ + * Global state + * ================================================================ */ + +/** Interface table. */ +static eth_iface_t ifaces[ETH_MAX_IFACES]; + +/** Number of registered interfaces. */ +static uint32_t iface_count = 0; + +/* ================================================================ + * Character device operations + * + * Maps devicefs char read/write to the NIC driver send/recv. + * ================================================================ */ + +/** + * Read from `/dev/ethN`: receive a raw Ethernet frame. + */ +static int32_t eth_char_read(void *dev_data, uint32_t size, void *buf) { + eth_iface_t *iface = (eth_iface_t *)dev_data; + if (!iface || !iface->active || !iface->recv) return -1; + return (int32_t)iface->recv(iface->dev_data, buf, size); +} + +/** + * Write to `/dev/ethN`: send a raw Ethernet frame. + */ +static int32_t eth_char_write(void *dev_data, uint32_t size, const void *buf) { + eth_iface_t *iface = (eth_iface_t *)dev_data; + if (!iface || !iface->active || !iface->send) return -1; + int ret = iface->send(iface->dev_data, buf, size); + return (ret == 0) ? (int32_t)size : -1; +} + +/** Devicefs char ops shared by all ethernet interfaces. */ +static devicefs_char_ops_t eth_char_ops = { + .read = eth_char_read, + .write = eth_char_write, +}; + +/* ================================================================ + * Sysfs operations for /sys/net + * + * Directory layout: + * /sys/net/ + * eth1/ + * mac - MAC address as hex string + * ip - IPv4 address + * netmask - subnet mask + * gateway - default gateway + * link - "up" or "down" + * eth2/ + * ... + * ================================================================ */ + +/** + * Format a MAC address as "XX:XX:XX:XX:XX:XX" into buf. + */ +static int format_mac(const uint8_t *mac, char *buf, uint32_t buf_size) { + if (buf_size < 18) return -1; + static const char hex[] = "0123456789ABCDEF"; + int pos = 0; + for (int i = 0; i < 6; i++) { + if (i > 0) buf[pos++] = ':'; + buf[pos++] = hex[(mac[i] >> 4) & 0xF]; + buf[pos++] = hex[mac[i] & 0xF]; + } + buf[pos++] = '\n'; + buf[pos] = '\0'; + return pos; +} + +/** + * Format an IPv4 address as "A.B.C.D\n" into buf. + */ +static int format_ipv4(uint32_t ip, char *buf, uint32_t buf_size) { + if (buf_size < 16) return -1; + int pos = 0; + for (int i = 0; i < 4; i++) { + if (i > 0) buf[pos++] = '.'; + uint8_t octet = (uint8_t)((ip >> (24 - i * 8)) & 0xFF); + if (octet >= 100) { buf[pos++] = (char)('0' + octet / 100); octet %= 100; buf[pos++] = (char)('0' + octet / 10); octet %= 10; } + else if (octet >= 10) { buf[pos++] = (char)('0' + octet / 10); octet %= 10; } + buf[pos++] = (char)('0' + octet); + } + buf[pos++] = '\n'; + buf[pos] = '\0'; + return pos; +} + +/** + * Parse interface name from the path prefix. + * E.g., path "eth1/mac" → iface "eth1", subpath "mac". + * Returns the iface, or NULL if not found. + */ +static eth_iface_t *parse_iface_path(const char *path, const char **subpath) { + /* Find the first '/' */ + const char *slash = NULL; + for (const char *p = path; *p; p++) { + if (*p == '/') { slash = p; break; } + } + + char iface_name[16]; + if (slash) { + uint32_t len = (uint32_t)(slash - path); + if (len >= sizeof(iface_name)) return NULL; + memcpy(iface_name, path, len); + iface_name[len] = '\0'; + *subpath = slash + 1; + } else { + /* No slash — path is just the interface name */ + uint32_t len = strlen(path); + if (len >= sizeof(iface_name)) return NULL; + memcpy(iface_name, path, len + 1); + *subpath = ""; + } + + return ethernet_find_iface(iface_name); +} + +/** + * Sysfs list callback for /sys/net. + */ +static int net_sysfs_list(void *ctx, const char *path, uint32_t idx, + sysfs_entry_t *out) { + (void)ctx; + + if (path[0] == '\0') { + /* Root of /sys/net — list interfaces */ + uint32_t count = 0; + for (uint32_t i = 0; i < ETH_MAX_IFACES; i++) { + if (!ifaces[i].active) continue; + if (count == idx) { + memset(out, 0, sizeof(sysfs_entry_t)); + strncpy(out->name, ifaces[i].name, SYSFS_MAX_NAME - 1); + out->is_dir = 1; + return 0; + } + count++; + } + return -1; + } + + /* Listing inside an interface directory */ + const char *subpath; + eth_iface_t *iface = parse_iface_path(path, &subpath); + if (!iface) return -1; + + /* Files: mac, ip, netmask, gateway, link */ + static const char *files[] = { "mac", "ip", "netmask", "gateway", "link" }; + if (idx >= 5) return -1; + + memset(out, 0, sizeof(sysfs_entry_t)); + strncpy(out->name, files[idx], SYSFS_MAX_NAME - 1); + out->is_dir = 0; + return 0; +} + +/** + * Sysfs read callback for /sys/net. + */ +static int net_sysfs_read(void *ctx, const char *path, char *buf, + uint32_t buf_size) { + (void)ctx; + + const char *subpath; + eth_iface_t *iface = parse_iface_path(path, &subpath); + if (!iface) return -1; + + if (strcmp(subpath, "mac") == 0) { + return format_mac(iface->mac, buf, buf_size); + } + if (strcmp(subpath, "ip") == 0) { + return format_ipv4(iface->ip_addr, buf, buf_size); + } + if (strcmp(subpath, "netmask") == 0) { + return format_ipv4(iface->netmask, buf, buf_size); + } + if (strcmp(subpath, "gateway") == 0) { + return format_ipv4(iface->gateway, buf, buf_size); + } + if (strcmp(subpath, "link") == 0) { + const char *s = iface->link_up ? "up\n" : "down\n"; + strncpy(buf, s, buf_size); + return (int)strlen(s); + } + + return -1; +} + +/** + * Sysfs write callback for /sys/net. + * Allows setting IP address, netmask, gateway. + */ +static int net_sysfs_write(void *ctx, const char *path, const char *buf, + uint32_t size) { + (void)ctx; + (void)size; + + const char *subpath; + eth_iface_t *iface = parse_iface_path(path, &subpath); + if (!iface) return -1; + + /* Parse dotted-decimal IP from buf */ + if (strcmp(subpath, "ip") == 0 || strcmp(subpath, "netmask") == 0 || + strcmp(subpath, "gateway") == 0) { + uint32_t octets[4] = {0}; + int octet_idx = 0; + for (uint32_t i = 0; i < size && octet_idx < 4; i++) { + if (buf[i] == '.' || buf[i] == '\n' || buf[i] == '\0') { + octet_idx++; + } else if (buf[i] >= '0' && buf[i] <= '9') { + octets[octet_idx] = octets[octet_idx] * 10 + (uint32_t)(buf[i] - '0'); + } + } + uint32_t addr = (octets[0] << 24) | (octets[1] << 16) | + (octets[2] << 8) | octets[3]; + + if (strcmp(subpath, "ip") == 0) iface->ip_addr = addr; + if (strcmp(subpath, "netmask") == 0) iface->netmask = addr; + if (strcmp(subpath, "gateway") == 0) iface->gateway = addr; + + return (int)size; + } + + return -1; +} + +/** Sysfs operations for /sys/net. */ +static sysfs_ops_t net_sysfs_ops = { + .list = net_sysfs_list, + .read = net_sysfs_read, + .write = net_sysfs_write, +}; + +/* ================================================================ + * Public API + * ================================================================ */ + +void ethernet_init(void) { + memset(ifaces, 0, sizeof(ifaces)); + iface_count = 0; + + sysfs_register("net", &net_sysfs_ops, NULL); + offset_print(" ETHERNET: subsystem initialized\n"); +} + +eth_iface_t *ethernet_register(const uint8_t *mac, + eth_send_fn send, + eth_recv_fn recv, + void *dev_data) { + if (iface_count >= ETH_MAX_IFACES) { + offset_print(" ETHERNET: too many interfaces\n"); + return NULL; + } + + /* Find free slot */ + eth_iface_t *iface = NULL; + for (uint32_t i = 0; i < ETH_MAX_IFACES; i++) { + if (!ifaces[i].active) { + iface = &ifaces[i]; + break; + } + } + if (!iface) return NULL; + + memset(iface, 0, sizeof(eth_iface_t)); + + /* Copy MAC */ + memcpy(iface->mac, mac, ETH_ALEN); + iface->send = send; + iface->recv = recv; + iface->dev_data = dev_data; + iface->link_up = 1; /* Assume link up after init */ + iface->active = 1; + + /* Register as /dev/ethN — devicefs assigns the number */ + devicefs_device_t *devfs_dev = devicefs_register_char("eth", + ð_char_ops, + iface); + if (devfs_dev) { + /* Copy the assigned name back */ + strncpy(iface->name, devfs_dev->name, sizeof(iface->name) - 1); + } else { + /* Fallback name */ + iface->name[0] = 'e'; iface->name[1] = 't'; iface->name[2] = 'h'; + iface->name[3] = (char)('0' + iface_count + 1); + iface->name[4] = '\0'; + } + + iface_count++; + + offset_print(" ETHERNET: registered "); + offset_print(iface->name); + offset_print(" MAC "); + for (int i = 0; i < 6; i++) { + if (i > 0) offset_print(":"); + print_hex(mac[i]); + } + offset_print("\n"); + + return iface; +} + +int ethernet_send(eth_iface_t *iface, const uint8_t *dst_mac, + uint16_t ethertype, const void *payload, uint32_t len) { + if (!iface || !iface->active || !iface->send) return -1; + if (len > ETH_MTU) return -1; + + /* Build complete Ethernet frame on stack */ + uint8_t frame[ETH_FRAME_LEN]; + eth_header_t *hdr = (eth_header_t *)frame; + + memcpy(hdr->dst, dst_mac, ETH_ALEN); + memcpy(hdr->src, iface->mac, ETH_ALEN); + hdr->ethertype = htons(ethertype); + + memcpy(frame + ETH_HLEN, payload, len); + + uint32_t frame_len = ETH_HLEN + len; + if (frame_len < 60) frame_len = 60; /* Minimum Ethernet frame */ + + return iface->send(iface->dev_data, frame, frame_len); +} + +int ethernet_recv(eth_iface_t *iface, void *buf, uint32_t bufsize, + uint8_t *src_mac, uint16_t *ethertype) { + if (!iface || !iface->active || !iface->recv) return -1; + + /* Receive into temporary buffer */ + uint8_t frame[ETH_FRAME_LEN]; + int ret = iface->recv(iface->dev_data, frame, sizeof(frame)); + if (ret <= 0) return ret; + + if ((uint32_t)ret < ETH_HLEN) return -1; /* Runt frame */ + + eth_header_t *hdr = (eth_header_t *)frame; + + /* Copy out src MAC and ethertype if requested */ + if (src_mac) memcpy(src_mac, hdr->src, ETH_ALEN); + if (ethertype) *ethertype = ntohs(hdr->ethertype); + + /* Copy payload */ + uint32_t payload_len = (uint32_t)ret - ETH_HLEN; + if (payload_len > bufsize) payload_len = bufsize; + memcpy(buf, frame + ETH_HLEN, payload_len); + + return (int)payload_len; +} + +eth_iface_t *ethernet_get_iface(uint32_t index) { + uint32_t count = 0; + for (uint32_t i = 0; i < ETH_MAX_IFACES; i++) { + if (!ifaces[i].active) continue; + if (count == index) return &ifaces[i]; + count++; + } + return NULL; +} + +uint32_t ethernet_get_iface_count(void) { + return iface_count; +} + +eth_iface_t *ethernet_find_iface(const char *name) { + for (uint32_t i = 0; i < ETH_MAX_IFACES; i++) { + if (!ifaces[i].active) continue; + if (strcmp(ifaces[i].name, name) == 0) return &ifaces[i]; + } + return NULL; +} diff --git a/src/ethernet.h b/src/ethernet.h new file mode 100644 index 0000000..2a7dac4 --- /dev/null +++ b/src/ethernet.h @@ -0,0 +1,208 @@ +/** + * @file ethernet.h + * @brief Ethernet subsystem. + * + * Provides a unified abstraction over individual Ethernet NIC drivers. + * Each NIC driver registers itself with the ethernet subsystem, which + * then creates the `/dev/ethN` character device and exposes interface + * information via `/sys/net`. + * + * Higher-level protocols (IPv4, ARP, etc.) use this subsystem to send + * and receive Ethernet frames without knowing which NIC driver is in use. + */ + +#ifndef ETHERNET_H +#define ETHERNET_H + +#include + +/** Maximum number of Ethernet interfaces. */ +#define ETH_MAX_IFACES 8 + +/** Ethernet MAC address length. */ +#define ETH_ALEN 6 + +/** Ethernet header size (dst + src + ethertype). */ +#define ETH_HLEN 14 + +/** Maximum Ethernet payload size. */ +#define ETH_MTU 1500 + +/** Maximum Ethernet frame size (header + payload). */ +#define ETH_FRAME_LEN (ETH_HLEN + ETH_MTU) + +/* ================================================================ + * EtherType constants + * ================================================================ */ +#define ETHERTYPE_IPV4 0x0800 /**< IPv4 */ +#define ETHERTYPE_ARP 0x0806 /**< ARP */ + +/* ================================================================ + * Ethernet header (14 bytes) + * ================================================================ */ + +/** + * Ethernet frame header. + */ +typedef struct __attribute__((packed)) eth_header { + uint8_t dst[ETH_ALEN]; /**< Destination MAC address. */ + uint8_t src[ETH_ALEN]; /**< Source MAC address. */ + uint16_t ethertype; /**< EtherType (big-endian). */ +} eth_header_t; + +/* ================================================================ + * NIC driver callbacks + * ================================================================ */ + +/** + * Callback to send a raw Ethernet frame (complete with header). + * + * @param dev_data Driver-specific device pointer. + * @param frame Complete Ethernet frame (header + payload). + * @param len Frame length in bytes. + * @return 0 on success, -1 on failure. + */ +typedef int (*eth_send_fn)(void *dev_data, const void *frame, uint32_t len); + +/** + * Callback to receive a raw Ethernet frame (complete with header). + * + * @param dev_data Driver-specific device pointer. + * @param buf Buffer for received frame. + * @param bufsize Buffer size. + * @return Number of bytes received, 0 if none, -1 on error. + */ +typedef int (*eth_recv_fn)(void *dev_data, void *buf, uint32_t bufsize); + +/* ================================================================ + * Ethernet interface + * ================================================================ */ + +/** + * Represents one Ethernet network interface. + */ +typedef struct eth_iface { + char name[16]; /**< Interface name (e.g., "eth1"). */ + uint8_t mac[ETH_ALEN]; /**< MAC address. */ + uint8_t active; /**< 1 if registered, 0 if free. */ + uint8_t link_up; /**< 1 if link is up. */ + uint32_t ip_addr; /**< IPv4 address (0 if not configured). */ + uint32_t netmask; /**< Subnet mask (0 if not configured). */ + uint32_t gateway; /**< Default gateway (0 if not configured). */ + + eth_send_fn send; /**< NIC driver send callback. */ + eth_recv_fn recv; /**< NIC driver receive callback. */ + void *dev_data; /**< Opaque pointer to NIC driver state. */ +} eth_iface_t; + +/* ================================================================ + * Public API + * ================================================================ */ + +/** + * Initialize the ethernet subsystem. + * Registers sysfs namespace "net" and prepares the interface table. + */ +void ethernet_init(void); + +/** + * Register a NIC with the ethernet subsystem. + * + * Creates a `/dev/ethN` character device and adds the interface to the + * internal table. Called by NIC drivers during initialization. + * + * @param mac 6-byte MAC address. + * @param send Send callback function. + * @param recv Receive callback function. + * @param dev_data Opaque NIC device pointer (passed to callbacks). + * @return Pointer to the registered interface, or NULL on failure. + */ +eth_iface_t *ethernet_register(const uint8_t *mac, + eth_send_fn send, + eth_recv_fn recv, + void *dev_data); + +/** + * Send an Ethernet frame through an interface. + * + * Constructs the Ethernet header (src MAC from interface, dst from caller) + * and dispatches through the NIC driver. + * + * @param iface Interface to send through. + * @param dst_mac Destination MAC address (6 bytes). + * @param ethertype EtherType (host byte order, will be converted to big-endian). + * @param payload Frame payload. + * @param len Payload length (max ETH_MTU). + * @return 0 on success, -1 on failure. + */ +int ethernet_send(eth_iface_t *iface, const uint8_t *dst_mac, + uint16_t ethertype, const void *payload, uint32_t len); + +/** + * Receive an Ethernet frame from an interface. + * + * @param iface Interface to receive from. + * @param buf Buffer for frame payload (Ethernet header is stripped). + * @param bufsize Buffer size. + * @param src_mac Output: source MAC of received frame (6 bytes, can be NULL). + * @param ethertype Output: EtherType of received frame (host byte order, can be NULL). + * @return Payload length in bytes, 0 if no packet, -1 on error. + */ +int ethernet_recv(eth_iface_t *iface, void *buf, uint32_t bufsize, + uint8_t *src_mac, uint16_t *ethertype); + +/** + * Get an interface by index. + * + * @param index 0-based index. + * @return Pointer to interface, or NULL if index out of range. + */ +eth_iface_t *ethernet_get_iface(uint32_t index); + +/** + * Get the number of registered interfaces. + * + * @return Number of active Ethernet interfaces. + */ +uint32_t ethernet_get_iface_count(void); + +/** + * Find an interface by name (e.g., "eth1"). + * + * @param name Interface name. + * @return Pointer to interface, or NULL if not found. + */ +eth_iface_t *ethernet_find_iface(const char *name); + +/** + * Convert a 16-bit value from host to network byte order (big-endian). + */ +static inline uint16_t htons(uint16_t h) { + return (uint16_t)((h >> 8) | (h << 8)); +} + +/** + * Convert a 16-bit value from network to host byte order. + */ +static inline uint16_t ntohs(uint16_t n) { + return htons(n); +} + +/** + * Convert a 32-bit value from host to network byte order (big-endian). + */ +static inline uint32_t htonl(uint32_t h) { + return ((h >> 24) & 0x000000FF) | + ((h >> 8) & 0x0000FF00) | + ((h << 8) & 0x00FF0000) | + ((h << 24) & 0xFF000000); +} + +/** + * Convert a 32-bit value from network to host byte order. + */ +static inline uint32_t ntohl(uint32_t n) { + return htonl(n); +} + +#endif /* ETHERNET_H */ diff --git a/src/kernel.c b/src/kernel.c index 54695e4..288953e 100644 --- a/src/kernel.c +++ b/src/kernel.c @@ -22,6 +22,7 @@ #include "mbr.h" #include "fat32.h" #include "keyboard.h" +#include "ethernet.h" #include "framebuffer.h" /* Global framebuffer info, parsed from multiboot2 tags. */ @@ -415,6 +416,9 @@ void kernel_main(uint32_t magic, uint32_t addr) { fb_info.pitch = 80 * 2; } + ethernet_init(); + offset_print("Ethernet subsystem initialized\n"); + init_drivers(); EARLY_PRINT("DRV "); offset_print("Drivers initialized\n"); diff --git a/src/ne2000.c b/src/ne2000.c index 37ce302..c7a8d5b 100644 --- a/src/ne2000.c +++ b/src/ne2000.c @@ -20,6 +20,7 @@ #include "port_io.h" #include "pic.h" #include "devicefs.h" +#include "ethernet.h" #include "driver.h" #include @@ -309,37 +310,6 @@ void ne2k_irq(void) { } } -/* ================================================================ - * Devicefs character device operations - * ================================================================ */ - -/** - * Read from the NE2000 character device. - * Returns one received Ethernet frame. - */ -static int32_t ne2k_char_read(void *dev_data, uint32_t size, void *buf) { - ne2k_device_t *dev = (ne2k_device_t *)dev_data; - return (int32_t)ne2k_recv(dev, buf, size); -} - -/** - * Write to the NE2000 character device. - * Sends an Ethernet frame. - */ -static int32_t ne2k_char_write(void *dev_data, uint32_t size, const void *buf) { - ne2k_device_t *dev = (ne2k_device_t *)dev_data; - if (ne2k_send(dev, buf, size) == 0) { - return (int32_t)size; - } - return -1; -} - -/** Character device operations for NE2000. */ -static devicefs_char_ops_t ne2k_char_ops = { - .read = ne2k_char_read, - .write = ne2k_char_write, -}; - /* ================================================================ * Initialization * ================================================================ */ @@ -535,8 +505,11 @@ static int ne2k_driver_init(void) { } offset_print("\n"); - /* Register as character device */ - devicefs_register_char("eth", &ne2k_char_ops, &ne2k_dev); + /* Register with ethernet subsystem (creates /dev/ethN) */ + ethernet_register(ne2k_dev.mac, + (eth_send_fn)ne2k_send, + (eth_recv_fn)ne2k_recv, + &ne2k_dev); offset_print(" NE2K: initialized on I/O "); print_hex(ne2k_dev.io_base);