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:
@@ -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.
|
||||
|
||||
36
build.log
36
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.
|
||||
|
||||
@@ -27,6 +27,7 @@ add_executable(kernel
|
||||
floppy.c
|
||||
ne2000.c
|
||||
e3c509.c
|
||||
ethernet.c
|
||||
env.c
|
||||
keyboard.c
|
||||
interrupts.S
|
||||
|
||||
30
src/e3c509.c
30
src/e3c509.c
@@ -16,6 +16,7 @@
|
||||
#include "port_io.h"
|
||||
#include "pic.h"
|
||||
#include "devicefs.h"
|
||||
#include "ethernet.h"
|
||||
#include "driver.h"
|
||||
#include <string.h>
|
||||
|
||||
@@ -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);
|
||||
|
||||
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;
|
||||
}
|
||||
208
src/ethernet.h
Normal file
208
src/ethernet.h
Normal file
@@ -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 <stdint.h>
|
||||
|
||||
/** 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 */
|
||||
@@ -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");
|
||||
|
||||
39
src/ne2000.c
39
src/ne2000.c
@@ -20,6 +20,7 @@
|
||||
#include "port_io.h"
|
||||
#include "pic.h"
|
||||
#include "devicefs.h"
|
||||
#include "ethernet.h"
|
||||
#include "driver.h"
|
||||
#include <string.h>
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user