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 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 NE2000 NIC.
|
||||||
- [x] Create a network driver for the 3C509B NIC. It should only support RJ45 and 10base-T.
|
- [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 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 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.
|
- [ ] 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
|
[ 2%] Building user-mode applications
|
||||||
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
|
|
||||||
Building app: cat
|
Building app: cat
|
||||||
Built: /workspaces/claude-os/build/apps_bin/cat (310 bytes)
|
Built: /workspaces/claude-os/build/apps_bin/cat (310 bytes)
|
||||||
Building app: diskpart
|
Building app: diskpart
|
||||||
@@ -33,23 +28,9 @@ Building app: sh
|
|||||||
| ^~~~
|
| ^~~~
|
||||||
1 warning generated.
|
1 warning generated.
|
||||||
Built: /workspaces/claude-os/build/apps_bin/sh (3428 bytes)
|
Built: /workspaces/claude-os/build/apps_bin/sh (3428 bytes)
|
||||||
make[2]: warning: Clock skew detected. Your build may be incomplete.
|
[ 2%] Built target apps
|
||||||
[ 3%] Built target apps
|
[ 5%] Built target initrd
|
||||||
make[2]: Warning: File 'CMakeFiles/initrd.dir/progress.make' has modification time 48530 s in the future
|
[ 97%] Built target kernel
|
||||||
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
|
|
||||||
[100%] Generating bootable ISO image
|
[100%] Generating bootable ISO image
|
||||||
xorriso 1.5.6 : RockRidge filesystem manipulator, libburnia project.
|
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 current: stdio file, overwriteable
|
||||||
Media status : is blank
|
Media status : is blank
|
||||||
Media summary: 0 sessions, 0 data blocks, 0 data, 126g free
|
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
|
xorriso : UPDATE : 581 files added in 1 seconds
|
||||||
Added to ISO image: directory '/'='/workspaces/claude-os/build/isodir'
|
Added to ISO image: directory '/'='/workspaces/claude-os/build/isodir'
|
||||||
xorriso : UPDATE : 586 files added in 1 seconds
|
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 : 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.
|
xorriso : UPDATE : Thank you for being patient. Working since 0 seconds.
|
||||||
ISO image produced: 5943 sectors
|
ISO image produced: 5967 sectors
|
||||||
Written to medium : 5943 sectors at LBA 0
|
Written to medium : 5967 sectors at LBA 0
|
||||||
Writing to 'stdio:/workspaces/claude-os/release/claude-os.iso' completed successfully.
|
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
|
[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
|
floppy.c
|
||||||
ne2000.c
|
ne2000.c
|
||||||
e3c509.c
|
e3c509.c
|
||||||
|
ethernet.c
|
||||||
env.c
|
env.c
|
||||||
keyboard.c
|
keyboard.c
|
||||||
interrupts.S
|
interrupts.S
|
||||||
|
|||||||
30
src/e3c509.c
30
src/e3c509.c
@@ -16,6 +16,7 @@
|
|||||||
#include "port_io.h"
|
#include "port_io.h"
|
||||||
#include "pic.h"
|
#include "pic.h"
|
||||||
#include "devicefs.h"
|
#include "devicefs.h"
|
||||||
|
#include "ethernet.h"
|
||||||
#include "driver.h"
|
#include "driver.h"
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
@@ -217,28 +218,6 @@ void e3c509_irq(void) {
|
|||||||
e3c509_cmd(base, CMD_ACK_INTR | STAT_INT_LATCH);
|
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
|
* Initialization
|
||||||
* ================================================================ */
|
* ================================================================ */
|
||||||
@@ -394,8 +373,11 @@ static int e3c509_driver_init(void) {
|
|||||||
}
|
}
|
||||||
offset_print("\n");
|
offset_print("\n");
|
||||||
|
|
||||||
/* Register as character device (shares "eth" class with NE2000) */
|
/* Register with ethernet subsystem (creates /dev/ethN) */
|
||||||
devicefs_register_char("eth", &e3c509_char_ops, &e3c509_dev);
|
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 ");
|
offset_print(" 3C509: 10base-T (RJ45) on I/O ");
|
||||||
print_hex(e3c509_dev.io_base);
|
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 "mbr.h"
|
||||||
#include "fat32.h"
|
#include "fat32.h"
|
||||||
#include "keyboard.h"
|
#include "keyboard.h"
|
||||||
|
#include "ethernet.h"
|
||||||
#include "framebuffer.h"
|
#include "framebuffer.h"
|
||||||
|
|
||||||
/* Global framebuffer info, parsed from multiboot2 tags. */
|
/* 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;
|
fb_info.pitch = 80 * 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ethernet_init();
|
||||||
|
offset_print("Ethernet subsystem initialized\n");
|
||||||
|
|
||||||
init_drivers();
|
init_drivers();
|
||||||
EARLY_PRINT("DRV ");
|
EARLY_PRINT("DRV ");
|
||||||
offset_print("Drivers initialized\n");
|
offset_print("Drivers initialized\n");
|
||||||
|
|||||||
39
src/ne2000.c
39
src/ne2000.c
@@ -20,6 +20,7 @@
|
|||||||
#include "port_io.h"
|
#include "port_io.h"
|
||||||
#include "pic.h"
|
#include "pic.h"
|
||||||
#include "devicefs.h"
|
#include "devicefs.h"
|
||||||
|
#include "ethernet.h"
|
||||||
#include "driver.h"
|
#include "driver.h"
|
||||||
#include <string.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
|
* Initialization
|
||||||
* ================================================================ */
|
* ================================================================ */
|
||||||
@@ -535,8 +505,11 @@ static int ne2k_driver_init(void) {
|
|||||||
}
|
}
|
||||||
offset_print("\n");
|
offset_print("\n");
|
||||||
|
|
||||||
/* Register as character device */
|
/* Register with ethernet subsystem (creates /dev/ethN) */
|
||||||
devicefs_register_char("eth", &ne2k_char_ops, &ne2k_dev);
|
ethernet_register(ne2k_dev.mac,
|
||||||
|
(eth_send_fn)ne2k_send,
|
||||||
|
(eth_recv_fn)ne2k_recv,
|
||||||
|
&ne2k_dev);
|
||||||
|
|
||||||
offset_print(" NE2K: initialized on I/O ");
|
offset_print(" NE2K: initialized on I/O ");
|
||||||
print_hex(ne2k_dev.io_base);
|
print_hex(ne2k_dev.io_base);
|
||||||
|
|||||||
Reference in New Issue
Block a user