ldls: a network, in a process
docs/plan/ldls.md L3a -- the class bridge, and the first of the three pieces
L3 turned out to be.
/dev/netdev is /dev/pci with the arrows reversed. That hands a device to a
process; this lets a process be one. The kernel side is netdev_ops and nothing
else -- transmit, receive, mac, link_up, the same four entries e1000
publishes -- so a process claims the device, supplies a MAC, and from then on
read() gives it frames the stack wants sent and write() hands it frames that
arrived.
cmd/netecho proves it and is deliberately not a driver: no hardware, none
claimed, every frame answered by the program itself. DHCP so the interface
gets an address at all, ARP so the stack can find it, ICMP so it can be
pinged. All built by hand from RFCs 826, 791, 792 and 2131.
# netecho &
netuser: ldls0 is a process now, 2:4c:44:4c:53:1
netecho: I am 10.9.0.1 and will offer 10.9.0.2
netecho: answered DHCP, offering an address
# cat /proc/netif
interface ldls0 driver netuser address 10.9.0.2 gateway 10.9.0.1
# ping 10.9.0.1 3
netecho: answered an ARP
netecho: answered a ping
3 packets transmitted, 3 received, 0% packet loss
The interface got its address by DHCP from a process and then answered ping
from one. lwIP never learns that the thing behind netdev_ops is not a driver,
which is the whole property the bridge exists to have.
It dies the way it should, too. kill the process and link_up() answers false
immediately, so the stack refuses to send -- "ping: sendto: -5" -- rather than
swallowing packets into a driver that is not there. Start another and the
*same* interface comes back, keeping its address. That came free:
netuser_release_all() sits beside pciuser_release_all() in exit() for exactly
the same reason, and the reason is that a process holding a device is one that
may crash.
One thing the kernel needed: netinit() starts DHCP on the devices that exist
when it runs, so an interface created later -- which is how an LDLS-hosted
driver arrives -- would have come up with no address and no way to get one.
net_attach_late() fixes that, and lives in kernel/net/net.c because that is
the only file allowed to include lwIP's headers.
Recorded as not done, because it is not: this proves the bridge, not a driver.
Nothing in that test loads a module. That is why it was done first -- if ping
had not worked here the bridge would be wrong, and now a failure with a real
driver can only be the driver's side. L3b is the SchOS-side host, L3c is the
fifteen symbols for real, and L3 is the two joined.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ7Qx8E1q1M1bbCq8QLHS2
This commit is contained in:
parent
2d9a0ec6e3
commit
2a441af3c6
14 changed files with 953 additions and 5 deletions
|
|
@ -371,6 +371,14 @@ void watch_init(void);
|
|||
void watch_sync(void);
|
||||
int watch_trap(struct trapframe *tf);
|
||||
|
||||
// netuser.c -- /dev/netdev, an interface backed by a process. See netuser.h.
|
||||
void netuser_init(void);
|
||||
int netuser_read(struct inode*, uint64, char*, int);
|
||||
int netuser_write(struct inode*, uint64, char*, int);
|
||||
int netuser_ready(struct inode*);
|
||||
int netuser_ioctl(uint64 request, uint64 argp);
|
||||
void netuser_release_all(struct proc*);
|
||||
|
||||
// dbg.c -- /dev/dbg, the introspection registry. See include/dbg.h.
|
||||
int dbg_read(struct inode*, uint64, char*, int);
|
||||
void dbg_request_dump(void);
|
||||
|
|
@ -617,6 +625,7 @@ void lwip_sys_init(void);
|
|||
void lwip_sys_lock(void);
|
||||
void lwip_sys_unlock(void);
|
||||
int net_eth_attach(struct device *);
|
||||
int net_attach_late(struct device *);
|
||||
int net_eth_poll(void);
|
||||
int net_eth_count(void);
|
||||
void net_eth_counts(int i, uint64 *rx, uint64 *tx);
|
||||
|
|
|
|||
64
include/netuser.h
Normal file
64
include/netuser.h
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
#pragma once
|
||||
|
||||
// /dev/netdev -- being a network interface, from a process.
|
||||
//
|
||||
// docs/plan/ldls.md calls this "the class bridge", and it is the one new
|
||||
// kernel interface that plan needs. A driver hosted by LDLS produces packets
|
||||
// in a process; the network stack consumes them through `struct netdev_ops` in
|
||||
// the kernel. This is what joins the two.
|
||||
//
|
||||
// It is the exact analogue of what `display_ops` is for the GPU, and
|
||||
// include/netdev.h's own opening comment already anticipated the shape:
|
||||
//
|
||||
// "the stack should ask for the network interfaces and get whatever is
|
||||
// bound -- today an e1000, tomorrow a virtio-net or an rtl8139 -- exactly
|
||||
// as a compositor asks for the display and does not care which driver
|
||||
// answers."
|
||||
//
|
||||
// The interface is four calls wide because `netdev_ops` is, and it is
|
||||
// deliberately not a superset of it. Nothing here knows what a driver is:
|
||||
//
|
||||
// ioctl(NETUSERIOC_CREATE) become an interface, with this MAC
|
||||
// read() a frame the stack wants sent
|
||||
// write() a frame that arrived on the wire
|
||||
// ioctl(NETUSERIOC_RELEASE) stop being one; exiting does this too
|
||||
//
|
||||
// **A process is the wire.** Whether there is real hardware on the other side
|
||||
// of it is the process's business, which is what makes this usable by an
|
||||
// LDLS-hosted Linux driver and equally by a test program with no hardware at
|
||||
// all -- and the second one is how the first is debugged.
|
||||
|
||||
#include "types.h"
|
||||
|
||||
#define NETUSER_MAJOR 11
|
||||
|
||||
// One Ethernet frame, with room for the 1500-byte payload, the header and a
|
||||
// VLAN tag. Frames larger than this are refused rather than truncated: a short
|
||||
// frame handed to a protocol stack is a corrupt packet, which is worse than a
|
||||
// dropped one.
|
||||
#define NETUSER_MTU 1536
|
||||
|
||||
// Frames buffered in each direction. Small on purpose -- this is a queue for
|
||||
// smoothing a scheduling gap, not for absorbing a burst, and a deep queue on a
|
||||
// network path adds latency instead of throughput.
|
||||
#define NETUSER_QLEN 16
|
||||
|
||||
struct netuser_create {
|
||||
uint8 mac[6];
|
||||
uint8 pad[2];
|
||||
// The interface's base name; the device model appends an index, so "ldls"
|
||||
// becomes "ldls0". Empty means "eth".
|
||||
char name[16];
|
||||
};
|
||||
|
||||
// Counters, for `schdbg net` and for answering "is anything moving".
|
||||
struct netuser_stat {
|
||||
uint64 tx_frames, tx_bytes, tx_dropped;
|
||||
uint64 rx_frames, rx_bytes, rx_dropped;
|
||||
int attached; // a process holds it
|
||||
int pad;
|
||||
};
|
||||
|
||||
#define NETUSERIOC_CREATE 0x4e01
|
||||
#define NETUSERIOC_RELEASE 0x4e02
|
||||
#define NETUSERIOC_STAT 0x4e03
|
||||
|
|
@ -29,7 +29,7 @@
|
|||
#define NOFILE 128 // open files per process
|
||||
#define NFILE 512 // open files per system
|
||||
#define NINODE 200 // maximum number of active i-nodes
|
||||
#define NDEV 11 // maximum major device number
|
||||
#define NDEV 12 // maximum major device number
|
||||
#define RAMDISK_DEV 2 // memory-backed block device (kernel/dev/ramdisk.c)
|
||||
#define USBDISK_DEV 3 // USB mass storage (kernel/drivers/usb_storage.c)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue