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
This commit is contained in:
393
src/ethernet.c
Normal file
393
src/ethernet.c
Normal file
@@ -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 <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",
|
||||
ð_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;
|
||||
}
|
||||
Reference in New Issue
Block a user