Files
claude-os/src/ethernet.c
AI 35bafdcad9 Implement Ethernet subsystem with unified NIC abstraction (AI)
- Created src/ethernet.h: eth_iface_t interface struct, Ethernet header
  type, htons/ntohs/htonl/ntohl byte-order helpers, EtherType constants,
  send/recv with automatic header construction/stripping
- Created src/ethernet.c: interface table, char device ops routed
  through ethernet layer, sysfs /sys/net namespace exposing per-iface
  mac/ip/netmask/gateway/link files, IPv4 address parse/format
- NE2000 and 3C509B drivers now register through ethernet_register()
  instead of directly via devicefs_register_char(); removed redundant
  char device ops from both drivers
- Kernel calls ethernet_init() before init_drivers() so the subsystem
  is ready when NIC drivers probe
- Tested: NE2000 detected with NIC, 'eth1' registered via ethernet
  subsystem; clean boot without NIC
2026-02-24 07:24:56 +00:00

394 lines
12 KiB
C

/**
* @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 <string.h>
/* 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",
&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;
}