/** * @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; }