17 Commits

Author SHA1 Message Date
AI
30c33c30b6 Implement minigolf game with 4 holes, wall collisions, water/sand hazards (AI) 2026-02-24 08:11:02 +00:00
AI
6a8d561b3e Implement pool game using graphics subsystem (AI) 2026-02-24 08:05:19 +00:00
AI
fc5fe9af63 Implement graphics subsystem with VGA mode 0x13 and drawing primitives (AI) 2026-02-24 08:01:20 +00:00
AI
57b2751a81 Implement networking syscalls, ftp and wget apps (AI) 2026-02-24 07:51:33 +00:00
AI
e6929438a0 Implement UDP and TCP stack (AI) 2026-02-24 07:43:45 +00:00
AI
d7ce0d5856 Implement DHCP client subsystem and dhcp app (AI)
- Created src/dhcp.h: DHCP packet struct, lease info struct, message types,
  options codes, client states, discover/receive/get_lease API
- Created src/dhcp.c: DHCP client with DISCOVER/OFFER/REQUEST/ACK flow,
  manual IP+UDP header construction for broadcast, option parsing for
  subnet mask/router/DNS/lease time/server ID, lease table, auto-applies
  configuration to ethernet interface on ACK, sysfs /sys/dhcp/status
- Created apps/dhcp/dhcp.c: reads /sys/dhcp/status to display DHCP info
- Kernel calls dhcp_init() at boot
- Tested: clean boot, DHCP initialized, dhcp app in CPIO
2026-02-24 07:35:20 +00:00
AI
d7d7e8e58e Implement ARP subsystem and arp app (AI)
- Created src/arp.h: ARP packet struct, cache entry struct, operation codes,
  lookup/request/resolve/receive API, sysfs registration
- Created src/arp.c: ARP cache with 32 entries, request/reply handling,
  ARP response to incoming requests for our IP, sysfs /sys/arp/table
  with formatted IP/MAC/interface/state columns
- Created apps/arp/arp.c: reads and displays /sys/arp/table
- Kernel calls arp_init() at boot, registered sysfs 'arp' namespace
- Tested: clean boot, ARP initialized, arp app in CPIO
2026-02-24 07:31:45 +00:00
AI
1825448528 Implement IPv4 stack and ip app (AI)
- Created src/ipv4.h: IPv4 header struct, protocol numbers, checksum,
  address conversion (ipv4_aton/ntoa), send/receive/routing API,
  protocol handler registration
- Created src/ipv4.c: packet construction with header checksum,
  simple routing (direct subnet + gateway), incoming packet validation
  and dispatch to registered protocol handlers
- Created apps/ip/ip.c: displays network interface config from
  /sys/net (MAC, link, IP, netmask, gateway); supports 'ip set'
  to configure interface via sysfs writes
- Added ipv4.c to kernel build, kernel calls ipv4_init() at boot
- Tested: clean boot, IPv4 initialized, ip app in CPIO
2026-02-24 07:28:33 +00:00
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
AI
35bce963be Implement 3C509B (EtherLink III) ISA Ethernet NIC driver (AI)
- Created src/e3c509.h: full windowed register model (8 windows),
  command codes, status/interrupt bits, RX filter, transceiver types,
  device struct and API declarations
- Created src/e3c509.c: full driver implementation with PIO TX/RX,
  window selection, 10base-T (RJ45) transceiver config, MAC address
  read from Window 2, FIFO-based packet send/receive, IRQ handler,
  devicefs char device registration as 'eth' class
- Probe uses manufacturer ID check (0x6D50 at Window 0)
- Only 10base-T supported per design requirements
- Wired IRQ 10 (vector 42) handler into isr.c
- QEMU does not emulate 3C509 ISA, so driver correctly probes
  'not found' in QEMU; tested alongside NE2000 without issues
2026-02-24 07:12:05 +00:00
AI
f87a4e3101 Implement NE2000 ISA Ethernet NIC driver (AI)
Add a driver for NE2000-compatible ISA Ethernet cards based on the
DP8390 controller. Features:

- PROM-based MAC address detection and validation
- Programmed I/O (PIO) remote DMA for data transfers
- Ring buffer management for RX with wrap-around handling
- IRQ 9-driven packet reception and transmission
- Synchronous TX with timeout
- Character device registration as 'eth' class (/dev/ethN)

Probe verifies card presence by resetting the controller, configuring
it for PROM reading, and checking the MAC is not all-0xFF/all-0x00
(which would indicate no hardware at the I/O base).

NE2000 memory layout (16 KiB on-card RAM):
- Pages 0x40-0x45: TX buffer (1536 bytes, 1 MTU frame)
- Pages 0x46-0x7F: RX ring buffer (~14.5 KiB)

Tested with QEMU: `-device ne2k_isa,iobase=0x300,irq=9` correctly
detects the card and registers /dev/eth1. Without the NIC option,
probe correctly reports 'not found'.
2026-02-23 17:34:12 +00:00
AI
27b2042523 Implement mkfs.fat32 filesystem formatter app (AI)
Create a user-space utility that formats block devices with FAT32.
Writes all required on-disk structures sequentially:

- Boot sector (BPB) with BIOS Parameter Block at sector 0
- FSInfo sector at sector 1 with free cluster count/hint
- Backup boot sector at sector 6, backup FSInfo at sector 7
- Two FAT tables with entries 0-2 initialized (media marker,
  EOC markers, root directory cluster EOC)
- Root directory cluster with volume label entry

Geometry calculation:
- Sectors per cluster chosen based on volume size (1-32)
- FAT size computed using Microsoft's formula
- Supports volumes from 128 sectors (~64K) upward

Usage: mkfs.fat32 hdd1mbr1 14336 [LABEL]
The total sector count is required since there is no stat() syscall
yet. Users can find it via 'diskpart <dev> list'.

Also checked off diskpart in README (committed previously).
2026-02-23 16:33:12 +00:00
AI
7dc50aa57d Implement diskpart MBR partition editor app (AI)
Create a user-space diskpart utility that can view and modify MBR
partition tables on block devices accessible through /dev.

Features:
- list: displays all 4 partition slots with type, LBA start, size
- create <slot> <type> <start> <sectors>: creates a new partition
- delete <slot>: removes a partition entry
- activate/deactivate <slot>: sets or clears the bootable flag

The tool reads sector 0 (MBR) via VFS open/read, modifies the
partition table in memory, then writes it back via open/write.
Supports hex type codes and provides human-readable type names
for common partition types (FAT12/16/32, Linux, NTFS, etc.).

Usage: diskpart hdd1 create 1 0C 2048 14336
       diskpart hdd1 list
       diskpart hdd1 delete 2
2026-02-23 16:28:44 +00:00
AI
d3176345a1 Check off character device support - already implemented (AI)
The devicefs subsystem already has full character device support:
- devicefs_char_ops_t with read/write callbacks
- devicefs_register_char() registration function
- VFS read/write delegation to char ops
- VFS_CHARDEV type in readdir/finddir results
2026-02-23 16:21:25 +00:00
AI
31740f7556 Implement floppy disk controller driver (AI)
Add Intel 82077AA-compatible floppy disk controller driver with:
- CMOS-based drive detection (register 0x10)
- FDC reset with DOR toggle and SPECIFY command
- Motor control with spin-up delay
- ISA DMA channel 2 setup for data transfers
- LBA-to-CHS conversion for 1.44MB geometry
- Single-sector read/write via DMA with 7-byte result phase
- Seek and recalibrate with sense interrupt verification
- IRQ 6 handler (vector 38) for command completion
- Devicefs integration as 'floppy' class block devices

The IRQ wait function detects whether interrupts are enabled
(EFLAGS IF bit) and temporarily enables them if needed, allowing
the driver to work during early init before the kernel calls STI.
The scheduler safely handles timer interrupts in this window since
no user processes exist yet.

Tested with QEMU: drive detected as 1.44M 3.5", registered as
/dev/floppy1, full boot succeeds with CD+HDD+floppy attached.
2026-02-23 15:19:42 +00:00
AI
399d0242b7 Check off FAT32/mount tasks, remove diagnostic code 2026-02-23 15:06:27 +00:00
AI
5229758092 Add FAT32 driver, mount app, fix shell cd/path resolution
- FAT32 VFS driver (fat32.h, fat32.c): reads BPB, FAT table, cluster
  chains, directory entries (8.3 SFN), file read/write, mount via
  fat32_mount_device()
- mount app: writes 'device mountpoint' to /sys/vfs/mount to trigger
  FAT32 mount from userspace
- VFS sysfs mount namespace in kernel.c: handles mount requests via
  sysfs write to /sys/vfs/mount, delegates to FAT32 driver
- Shell cd: validates target directory exists before updating CWD,
  resolves relative paths (., .., components) to absolute paths
- Shell run_command: resolves ARG1 paths relative to CWD so apps
  like cat and ls receive absolute paths automatically
- Fixed FAT32 root directory access: use fs->root_cluster when
  VFS mount root node has inode=0
2026-02-23 14:52:41 +00:00
43 changed files with 10793 additions and 39 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -62,22 +62,22 @@ Once a task is completed, it should be checked off.
- [x] Create an IDE driver. It should enumerate all IDE devices, and expose them to the vfs driver as `hddN` or `cdN`, where N is a number that starts at 1 and increases for each new device. The `N` value should be calucated by the devicefs subsystem, not the IDE driver.
- [x] Create an MBR driver. It should be able to automatically detect when new hard drives are detected, and automatically scan them for MBR partitions. Each MBR partition found should be listed as `hddNmbrY`, where Y is a number determined by the devicefs subsystem.
- [x] Create a `sysfs` vfs driver. It should allow drivers to expose text/config files to the VFS. Each driver can request a namespace in the sysfs. E.g.: the IDE driver could request `ide`. During this registration, the drive must provide a struct containing function callbacks. The callbacks must contain the function `list`, `read`, and `write`. These are executed when the user lists a directory, reads a file, or writes a file. It is expected that the contents of these files are extremely small and can simply be stored on the stack. It should be very easy for a driver to expose new information.
- [ ] Create a FAT32 driver. It should allow reading and writing to and from a block device.
- [ ] Create the `mount` app. It should allow on to mount a block device using the fat32 driver. Internally, it should use sysfs (which should be mounted automatically by the kernel to `/sys`) to setup a new mount.
- [ ] Create a floppy driver. Each floppy device should be exposed as `/dev/floppyN`.
- [ ] Add support for character device to the devicefs subsystem.
- [ ] Create an app called `diskpart`. This app can be used to modify the MBR partitions on a block device.
- [ ] Create an app called `mkfs.fat32`. This app can be used to format a block into a FAT32 filesystem.
- [ ] Create a network driver for the NE2000 NIC.
- [ ] 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`.
- [ ] 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.
- [ ] Create a UDP and TCP stack.
- [ ] Implement a simple version of `ftp` and `wget`.
- [ ] Create a graphics subsystem. It should provide functionality to switch between the normal text mode, and a graphics mode.
- [ ] Create a simple game of pool. It should use graphics mode to render the game.
- [ ] Create a simple game of minigolf.
- [x] Create a FAT32 driver. It should allow reading and writing to and from a block device.
- [x] Create the `mount` app. It should allow on to mount a block device using the fat32 driver. Internally, it should use sysfs (which should be mounted automatically by the kernel to `/sys`) to setup a new mount.
- [x] Create a floppy driver. Each floppy device should be exposed as `/dev/floppyN`.
- [x] Add support for character device to the devicefs subsystem.
- [x] Create an app called `diskpart`. This app can be used to modify the MBR partitions on a block device.
- [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.
- [x] Create an ethernet subsytsem. Each ethernet device should be shown as a character device with the name `ethN`.
- [x] Create a IPv4 stack. Create the `ip` app that shows curernt IPv4 configuration. It should read this information from `/sys`
- [x] Create a ARP subsystem. Create the `arp` command that shows current ARP tables. Again, this info should be found in `/sys`
- [x] Create a DHCP subsystem. Create the `dhcp` command to show current DHCP status information.
- [x] Create a UDP and TCP stack.
- [x] Implement a simple version of `ftp` and `wget`.
- [x] Create a graphics subsystem. It should provide functionality to switch between the normal text mode, and a graphics mode.
- [x] Create a simple game of pool. It should use graphics mode to render the game.
- [x] Create a simple game of minigolf.
Finally, before starting, write your prompt into `PROMPT.md`. This makes the request that you were given more easily auditable.

30
apps/arp/arp.c Normal file
View File

@@ -0,0 +1,30 @@
/**
* @file arp.c
* @brief Display the ARP table.
*
* Reads the ARP cache from /sys/arp/table and displays it.
*
* Usage:
* arp - Show the ARP table
*/
#include "syscalls.h"
int main(void) {
/* Open the ARP table sysfs file */
int32_t fd = open("/sys/arp/table", 0);
if (fd < 0) {
puts("arp: failed to open /sys/arp/table\n");
return 1;
}
/* Read and display contents */
char buf[512];
int32_t n;
while ((n = read(fd, buf, sizeof(buf))) > 0) {
write(1, buf, (uint32_t)n);
}
close(fd);
return 0;
}

28
apps/dhcp/dhcp.c Normal file
View File

@@ -0,0 +1,28 @@
/**
* @file dhcp.c
* @brief Display DHCP status information.
*
* Reads DHCP lease information from /sys/dhcp/status and displays it.
*
* Usage:
* dhcp - Show current DHCP status
*/
#include "syscalls.h"
int main(void) {
int32_t fd = open("/sys/dhcp/status", 0);
if (fd < 0) {
puts("dhcp: failed to open /sys/dhcp/status\n");
return 1;
}
char buf[512];
int32_t n;
while ((n = read(fd, buf, sizeof(buf))) > 0) {
write(1, buf, (uint32_t)n);
}
close(fd);
return 0;
}

600
apps/diskpart/diskpart.c Normal file
View File

@@ -0,0 +1,600 @@
/**
* @file diskpart.c
* @brief MBR partition table editor.
*
* Allows viewing, creating, and deleting MBR partitions on block devices.
*
* Usage: diskpart <device> [command] [args...]
*
* Commands:
* list Show partition table (default)
* create <slot> <type> <start> <sectors> Create a partition
* delete <slot> Delete a partition
* activate <slot> Set bootable flag
* deactivate <slot> Clear bootable flag
*
* <slot> is 1-4 (MBR supports up to 4 primary partitions).
* <type> is a hex partition type code (e.g., 0C for FAT32 LBA).
* <start> and <sectors> are decimal LBA values.
*
* Examples:
* diskpart hdd1 list
* diskpart hdd1 create 1 0C 2048 14336
* diskpart hdd1 delete 2
* diskpart hdd1 activate 1
*/
#include "syscalls.h"
typedef unsigned char uint8_t;
/** MBR signature. */
#define MBR_SIG_LO 0x55
#define MBR_SIG_HI 0xAA
/** MBR partition entry offsets. */
#define MBR_PART_TABLE 446
#define MBR_PART_SIZE 16
/* ================================================================
* Numeric formatting helpers
* ================================================================ */
/**
* Print a 32-bit value as a hexadecimal string with "0x" prefix.
*/
static void print_hex(uint32_t val) {
char buf[11]; /* "0x" + 8 digits + NUL */
buf[0] = '0';
buf[1] = 'x';
for (int i = 9; i >= 2; i--) {
int nibble = (int)(val & 0xF);
buf[i] = (nibble < 10) ? (char)('0' + nibble)
: (char)('A' + nibble - 10);
val >>= 4;
}
buf[10] = '\0';
puts(buf);
}
/**
* Print a 32-bit value as a decimal string.
*/
static void print_dec(uint32_t val) {
char buf[12];
int pos = 11;
buf[pos] = '\0';
if (val == 0) {
buf[--pos] = '0';
} else {
while (val > 0 && pos > 0) {
buf[--pos] = (char)('0' + (val % 10));
val /= 10;
}
}
puts(&buf[pos]);
}
/**
* Parse a decimal string to uint32_t.
* Returns the parsed value; sets *ok to 1 on success, 0 on failure.
*/
static uint32_t parse_dec(const char *s, int *ok) {
uint32_t val = 0;
*ok = 0;
if (!s || !*s) return 0;
while (*s) {
if (*s < '0' || *s > '9') { *ok = 0; return 0; }
val = val * 10 + (uint32_t)(*s - '0');
s++;
}
*ok = 1;
return val;
}
/**
* Parse a hexadecimal string (without "0x" prefix) to uint32_t.
*/
static uint32_t parse_hex(const char *s, int *ok) {
uint32_t val = 0;
*ok = 0;
if (!s || !*s) return 0;
/* Skip optional "0x" prefix */
if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) {
s += 2;
}
if (!*s) return 0;
while (*s) {
uint32_t digit;
if (*s >= '0' && *s <= '9') digit = (uint32_t)(*s - '0');
else if (*s >= 'a' && *s <= 'f') digit = (uint32_t)(*s - 'a' + 10);
else if (*s >= 'A' && *s <= 'F') digit = (uint32_t)(*s - 'A' + 10);
else { *ok = 0; return 0; }
val = (val << 4) | digit;
s++;
}
*ok = 1;
return val;
}
/* ================================================================
* Argument parsing
* ================================================================ */
/** Maximum number of tokens we parse from ARG1. */
#define MAX_TOKENS 8
/** Token buffer storage. */
static char token_buf[256];
static char *tokens[MAX_TOKENS];
static int token_count;
/**
* Split the ARG1 string into space-separated tokens.
*/
static void tokenize(const char *input) {
/* Copy input to mutable buffer */
int i = 0;
while (input[i] && i < 255) {
token_buf[i] = input[i];
i++;
}
token_buf[i] = '\0';
token_count = 0;
char *p = token_buf;
while (*p && token_count < MAX_TOKENS) {
/* Skip whitespace */
while (*p == ' ') p++;
if (!*p) break;
tokens[token_count++] = p;
/* Advance to next space or end */
while (*p && *p != ' ') p++;
if (*p) {
*p = '\0';
p++;
}
}
}
/* ================================================================
* Partition type name lookup
* ================================================================ */
/**
* Return a human-readable name for an MBR partition type code.
*/
static const char *type_name(uint32_t type) {
switch (type) {
case 0x00: return "Empty";
case 0x01: return "FAT12";
case 0x04: return "FAT16 <32M";
case 0x05: return "Extended";
case 0x06: return "FAT16 >=32M";
case 0x07: return "NTFS/HPFS";
case 0x0B: return "FAT32";
case 0x0C: return "FAT32 LBA";
case 0x0E: return "FAT16 LBA";
case 0x82: return "Linux swap";
case 0x83: return "Linux";
default: return "Unknown";
}
}
/* ================================================================
* MBR reading / writing
* ================================================================ */
/** MBR sector buffer (512 bytes). */
static uint8_t mbr[512];
/**
* Read a 32-bit little-endian value from a byte array.
*/
static uint32_t read32le(const uint8_t *p) {
return (uint32_t)p[0]
| ((uint32_t)p[1] << 8)
| ((uint32_t)p[2] << 16)
| ((uint32_t)p[3] << 24);
}
/**
* Write a 32-bit little-endian value to a byte array.
*/
static void write32le(uint8_t *p, uint32_t val) {
p[0] = (uint8_t)(val & 0xFF);
p[1] = (uint8_t)((val >> 8) & 0xFF);
p[2] = (uint8_t)((val >> 16) & 0xFF);
p[3] = (uint8_t)((val >> 24) & 0xFF);
}
/**
* Read MBR (sector 0) from a device.
* @param dev_path Full path like "/dev/hdd1".
* @return 0 on success, -1 on failure.
*/
static int read_mbr(const char *dev_path) {
int32_t fd = open(dev_path, 0);
if (fd < 0) {
puts("diskpart: cannot open ");
puts(dev_path);
puts("\n");
return -1;
}
int32_t n = read(fd, mbr, 512);
close(fd);
if (n != 512) {
puts("diskpart: failed to read MBR (got ");
print_dec((uint32_t)(n < 0 ? 0 : n));
puts(" bytes)\n");
return -1;
}
return 0;
}
/**
* Write MBR (sector 0) to a device.
* @param dev_path Full path like "/dev/hdd1".
* @return 0 on success, -1 on failure.
*/
static int write_mbr(const char *dev_path) {
int32_t fd = open(dev_path, 0);
if (fd < 0) {
puts("diskpart: cannot open ");
puts(dev_path);
puts(" for writing\n");
return -1;
}
int32_t n = write(fd, mbr, 512);
close(fd);
if (n != 512) {
puts("diskpart: failed to write MBR\n");
return -1;
}
return 0;
}
/* ================================================================
* Commands
* ================================================================ */
/**
* Display the partition table.
*/
static void cmd_list(void) {
/* Check signature */
if (mbr[510] != MBR_SIG_LO || mbr[511] != MBR_SIG_HI) {
puts("No valid MBR signature found.\n");
puts("Use 'create' to initialize partitions (signature will be added).\n");
return;
}
puts("Slot Boot Type Start LBA Sectors Size\n");
puts("---- ---- ---------- ---------- ---------- ----------\n");
for (int i = 0; i < 4; i++) {
uint8_t *entry = &mbr[MBR_PART_TABLE + i * MBR_PART_SIZE];
uint8_t status = entry[0];
uint8_t type = entry[4];
uint32_t lba_start = read32le(&entry[8]);
uint32_t sectors = read32le(&entry[12]);
/* Slot number */
putchar(' ');
putchar((char)('1' + i));
puts(" ");
/* Boot flag */
if (status == 0x80)
puts(" * ");
else
puts(" ");
/* Type */
puts(" ");
print_hex(type);
puts(" ");
/* Type name (pad to ~10 chars) */
const char *tn = type_name(type);
puts(tn);
int tnlen = (int)strlen(tn);
for (int j = tnlen; j < 12; j++) putchar(' ');
/* Start LBA */
print_dec(lba_start);
/* Pad */
char tmp[12];
int tpos = 11;
tmp[tpos] = '\0';
uint32_t v = lba_start;
if (v == 0) { tmp[--tpos] = '0'; }
else { while (v > 0) { tmp[--tpos] = (char)('0' + v % 10); v /= 10; } }
for (int j = 11 - tpos; j < 12; j++) putchar(' ');
/* Sectors */
print_dec(sectors);
tpos = 11;
tmp[tpos] = '\0';
v = sectors;
if (v == 0) { tmp[--tpos] = '0'; }
else { while (v > 0) { tmp[--tpos] = (char)('0' + v % 10); v /= 10; } }
for (int j = 11 - tpos; j < 12; j++) putchar(' ');
/* Size in KiB or MiB */
uint32_t kib = sectors / 2;
if (kib >= 1024) {
print_dec(kib / 1024);
puts(" MiB");
} else {
print_dec(kib);
puts(" KiB");
}
puts("\n");
}
}
/**
* Create a partition.
* Args: <slot 1-4> <type hex> <start_lba> <sectors>
*/
static int cmd_create(void) {
if (token_count < 6) {
puts("Usage: diskpart <dev> create <slot> <type> <start> <sectors>\n");
puts(" slot: 1-4\n");
puts(" type: hex partition type (e.g., 0C for FAT32 LBA)\n");
puts(" start: start LBA (decimal)\n");
puts(" sectors: partition size in sectors (decimal)\n");
return -1;
}
int ok;
uint32_t slot = parse_dec(tokens[2], &ok);
if (!ok || slot < 1 || slot > 4) {
puts("diskpart: invalid slot (must be 1-4)\n");
return -1;
}
uint32_t type = parse_hex(tokens[3], &ok);
if (!ok || type > 0xFF || type == 0) {
puts("diskpart: invalid type (must be 01-FF hex)\n");
return -1;
}
uint32_t start = parse_dec(tokens[4], &ok);
if (!ok) {
puts("diskpart: invalid start LBA\n");
return -1;
}
uint32_t sectors = parse_dec(tokens[5], &ok);
if (!ok || sectors == 0) {
puts("diskpart: invalid sector count\n");
return -1;
}
uint32_t idx = slot - 1;
uint8_t *entry = &mbr[MBR_PART_TABLE + idx * MBR_PART_SIZE];
/* Check if slot is already in use */
if (entry[4] != 0x00) {
puts("diskpart: slot ");
print_dec(slot);
puts(" is already in use (type ");
print_hex(entry[4]);
puts("). Delete it first.\n");
return -1;
}
/* Clear the entry */
memset(entry, 0, MBR_PART_SIZE);
/* Set type */
entry[4] = (uint8_t)type;
/* Set LBA start and sector count */
write32le(&entry[8], start);
write32le(&entry[12], sectors);
/* Ensure MBR signature is present */
mbr[510] = MBR_SIG_LO;
mbr[511] = MBR_SIG_HI;
puts("Created partition ");
print_dec(slot);
puts(": type=");
print_hex(type);
puts(" (");
puts(type_name(type));
puts(") start=");
print_dec(start);
puts(" sectors=");
print_dec(sectors);
puts("\n");
return 0;
}
/**
* Delete a partition.
*/
static int cmd_delete(void) {
if (token_count < 3) {
puts("Usage: diskpart <dev> delete <slot>\n");
return -1;
}
int ok;
uint32_t slot = parse_dec(tokens[2], &ok);
if (!ok || slot < 1 || slot > 4) {
puts("diskpart: invalid slot (must be 1-4)\n");
return -1;
}
uint32_t idx = slot - 1;
uint8_t *entry = &mbr[MBR_PART_TABLE + idx * MBR_PART_SIZE];
if (entry[4] == 0x00) {
puts("diskpart: slot ");
print_dec(slot);
puts(" is already empty\n");
return 0;
}
uint8_t old_type = entry[4];
memset(entry, 0, MBR_PART_SIZE);
puts("Deleted partition ");
print_dec(slot);
puts(" (was type ");
print_hex(old_type);
puts(" - ");
puts(type_name(old_type));
puts(")\n");
return 0;
}
/**
* Set or clear the bootable flag on a partition.
*/
static int cmd_boot(int activate) {
if (token_count < 3) {
puts("Usage: diskpart <dev> ");
puts(activate ? "activate" : "deactivate");
puts(" <slot>\n");
return -1;
}
int ok;
uint32_t slot = parse_dec(tokens[2], &ok);
if (!ok || slot < 1 || slot > 4) {
puts("diskpart: invalid slot (must be 1-4)\n");
return -1;
}
uint32_t idx = slot - 1;
uint8_t *entry = &mbr[MBR_PART_TABLE + idx * MBR_PART_SIZE];
if (entry[4] == 0x00) {
puts("diskpart: slot ");
print_dec(slot);
puts(" is empty\n");
return -1;
}
if (activate) {
/* Clear bootable flag on all other partitions */
for (int i = 0; i < 4; i++) {
mbr[MBR_PART_TABLE + i * MBR_PART_SIZE] = 0x00;
}
entry[0] = 0x80;
puts("Partition ");
print_dec(slot);
puts(" is now bootable\n");
} else {
entry[0] = 0x00;
puts("Partition ");
print_dec(slot);
puts(" is no longer bootable\n");
}
return 0;
}
/* ================================================================
* Main
* ================================================================ */
int main(void) {
char arg1[256];
if (getenv("ARG1", arg1, sizeof(arg1)) < 0 || arg1[0] == '\0') {
puts("diskpart - MBR partition table editor\n");
puts("\n");
puts("Usage: diskpart <device> [command] [args...]\n");
puts("\n");
puts("Commands:\n");
puts(" list Show partitions (default)\n");
puts(" create <slot> <type> <start> <sectors> Create partition\n");
puts(" delete <slot> Delete partition\n");
puts(" activate <slot> Set bootable flag\n");
puts(" deactivate <slot> Clear bootable flag\n");
puts("\n");
puts("Slot: 1-4, type: hex (e.g., 0C=FAT32 LBA, 83=Linux)\n");
return 1;
}
tokenize(arg1);
if (token_count < 1) {
puts("diskpart: no device specified\n");
return 1;
}
/* Token 0 is the device name. Build /dev/<device> path. */
char dev_path[64];
strcpy(dev_path, "/dev/");
/* Append device name */
char *dp = dev_path + 5;
char *dn = tokens[0];
while (*dn && dp < dev_path + 62) {
*dp++ = *dn++;
}
*dp = '\0';
/* Determine command (default is "list") */
const char *cmd = "list";
if (token_count >= 2) {
cmd = tokens[1];
}
/* For "list" we only read; for others we read-modify-write */
int need_write = 0;
if (strcmp(cmd, "list") == 0) {
/* Just read and display */
if (read_mbr(dev_path) != 0) return 1;
cmd_list();
return 0;
}
/* Read current MBR for modification commands */
if (read_mbr(dev_path) != 0) return 1;
int result = 0;
if (strcmp(cmd, "create") == 0) {
result = cmd_create();
if (result == 0) need_write = 1;
} else if (strcmp(cmd, "delete") == 0) {
result = cmd_delete();
if (result == 0) need_write = 1;
} else if (strcmp(cmd, "activate") == 0) {
result = cmd_boot(1);
if (result == 0) need_write = 1;
} else if (strcmp(cmd, "deactivate") == 0) {
result = cmd_boot(0);
if (result == 0) need_write = 1;
} else {
puts("diskpart: unknown command '");
puts(cmd);
puts("'\n");
return 1;
}
if (result != 0) return 1;
/* Write back modified MBR */
if (need_write) {
if (write_mbr(dev_path) != 0) {
puts("diskpart: WARNING - failed to write MBR!\n");
return 1;
}
puts("MBR written successfully.\n");
}
return 0;
}

302
apps/ftp/ftp.c Normal file
View File

@@ -0,0 +1,302 @@
/**
* @file ftp.c
* @brief Simple FTP client for ClaudeOS.
*
* Connects to an FTP server and provides a minimal interactive
* interface for sending FTP commands and viewing responses.
*
* Usage:
* ftp <ip>[:<port>]
*
* Examples:
* ftp 10.0.2.2
* ftp 192.168.1.1:2121
*
* Once connected, type FTP commands directly:
* USER anonymous
* PASS user@
* LIST
* QUIT
*
* The client handles the control connection. PASV data connections
* are not supported in this minimal version.
*/
#include "syscalls.h"
typedef unsigned char uint8_t;
/**
* Parse a decimal number from a string.
*/
static uint32_t parse_uint(const char **s) {
uint32_t val = 0;
while (**s >= '0' && **s <= '9') {
val = val * 10 + (uint32_t)(**s - '0');
(*s)++;
}
return val;
}
/**
* Parse an IPv4 address.
*/
static uint32_t parse_ip(const char **s) {
uint32_t ip = 0;
for (int i = 0; i < 4; i++) {
uint32_t octet = parse_uint(s);
if (octet > 255) return 0;
ip = (ip << 8) | octet;
if (i < 3) {
if (**s != '.') return 0;
(*s)++;
}
}
return ip;
}
/**
* Print a decimal number.
*/
static void print_dec(uint32_t val) {
char buf[12];
int i = 0;
if (val == 0) { putchar('0'); return; }
while (val > 0) {
buf[i++] = '0' + (char)(val % 10);
val /= 10;
}
while (i > 0) putchar(buf[--i]);
}
/**
* Format IP address.
*/
static void ip_to_str(uint32_t ip, char *buf) {
int pos = 0;
for (int i = 3; i >= 0; i--) {
uint32_t octet = (ip >> (i * 8)) & 0xFF;
char tmp[4]; int ti = 0;
if (octet == 0) { tmp[ti++] = '0'; }
else { while (octet > 0) { tmp[ti++] = '0' + (char)(octet % 10); octet /= 10; } }
while (ti > 0) buf[pos++] = tmp[--ti];
if (i > 0) buf[pos++] = '.';
}
buf[pos] = '\0';
}
/**
* Read a line from stdin with echo.
* Returns length (excluding newline).
*/
static int readline(char *buf, int maxlen) {
int pos = 0;
while (pos < maxlen - 1) {
char c;
int32_t n = read(0, &c, 1);
if (n <= 0) { yield(); continue; }
if (c == '\n' || c == '\r') { putchar('\n'); break; }
if (c == '\b' || c == 127) {
if (pos > 0) { pos--; puts("\b \b"); }
} else if (c >= 32) {
buf[pos++] = c;
putchar(c);
}
}
buf[pos] = '\0';
return pos;
}
/**
* Receive and print FTP server response.
* Reads until we get a line starting with a 3-digit code followed by space.
* Returns the response code, or -1 on error.
*/
static int32_t recv_response(int32_t sockfd) {
char buf[512];
int total = 0;
int timeout = 500;
int code = -1;
while (timeout > 0) {
int32_t n = net_recv(sockfd, buf + total,
(uint32_t)(sizeof(buf) - 1 - (uint32_t)total));
if (n > 0) {
total += n;
buf[total] = '\0';
/* Print received data */
write(1, buf + total - n, (uint32_t)n);
/* Check if we have a complete response.
* FTP response ends when we get "NNN " (3 digits + space) at start of line. */
int got_complete = 0;
for (int i = 0; i < total; i++) {
/* Check for line start */
if (i == 0 || (i > 0 && buf[i-1] == '\n')) {
/* Check for "NNN " pattern */
if (i + 3 < total &&
buf[i] >= '0' && buf[i] <= '9' &&
buf[i+1] >= '0' && buf[i+1] <= '9' &&
buf[i+2] >= '0' && buf[i+2] <= '9' &&
buf[i+3] == ' ') {
code = (buf[i] - '0') * 100 +
(buf[i+1] - '0') * 10 +
(buf[i+2] - '0');
got_complete = 1;
}
}
}
if (got_complete) break;
timeout = 200;
} else if (n < 0) {
return -1;
} else {
yield();
timeout--;
}
}
return code;
}
/**
* Send an FTP command (appends \r\n).
*/
static int32_t send_cmd(int32_t sockfd, const char *cmd) {
char buf[256];
uint32_t len = 0;
while (cmd[len] && len < sizeof(buf) - 3) {
buf[len] = cmd[len];
len++;
}
buf[len++] = '\r';
buf[len++] = '\n';
return net_send(sockfd, buf, len);
}
int main(void) {
char arg[256];
if (getenv("ARG1", arg, sizeof(arg)) < 0 || arg[0] == '\0') {
puts("Usage: ftp <ip>[:<port>]\n");
puts(" e.g. ftp 10.0.2.2\n");
return 1;
}
/* Parse IP and port */
const char *p = arg;
uint32_t ip = parse_ip(&p);
if (ip == 0) {
puts("ftp: invalid IP address\n");
return 1;
}
uint32_t port = 21;
if (*p == ':') {
p++;
port = parse_uint(&p);
if (port == 0 || port > 65535) {
puts("ftp: invalid port\n");
return 1;
}
}
char ip_str[64];
ip_to_str(ip, ip_str);
puts("Connecting to ");
puts(ip_str);
putchar(':');
print_dec(port);
puts("...\n");
/* Create TCP socket */
int32_t sockfd = socket(SOCK_TCP);
if (sockfd < 0) {
puts("ftp: failed to create socket\n");
return 1;
}
/* Connect */
if (connect(sockfd, ip, port) < 0) {
puts("ftp: connect failed\n");
return 1;
}
/* Wait for connection */
int timeout = 500;
while (timeout > 0) {
int32_t state = sockstate(sockfd);
if (state == TCP_STATE_ESTABLISHED) break;
if (state == TCP_STATE_CLOSED) {
puts("ftp: connection refused\n");
return 1;
}
yield();
timeout--;
}
if (timeout <= 0) {
puts("ftp: connection timed out\n");
return 1;
}
puts("Connected to ");
puts(ip_str);
puts("\n");
/* Read server welcome banner */
int32_t code = recv_response(sockfd);
if (code < 0 || code >= 400) {
puts("ftp: server rejected connection\n");
return 1;
}
/* Interactive command loop */
char cmd[256];
for (;;) {
puts("ftp> ");
int len = readline(cmd, sizeof(cmd));
if (len == 0) continue;
/* Check for local quit command */
if (strcmp(cmd, "quit") == 0 || strcmp(cmd, "exit") == 0 ||
strcmp(cmd, "bye") == 0) {
send_cmd(sockfd, "QUIT");
recv_response(sockfd);
puts("Goodbye.\n");
break;
}
/* Check for help */
if (strcmp(cmd, "help") == 0 || strcmp(cmd, "?") == 0) {
puts("ClaudeOS FTP Client\n");
puts("Send raw FTP commands:\n");
puts(" USER <username> - specify username\n");
puts(" PASS <password> - specify password\n");
puts(" PWD - print working directory\n");
puts(" CWD <path> - change directory\n");
puts(" LIST - list files (control channel only)\n");
puts(" SYST - show system type\n");
puts(" STAT - show server status\n");
puts(" QUIT - disconnect\n");
puts(" quit/exit/bye - disconnect and exit\n");
continue;
}
/* Send the command to server */
if (send_cmd(sockfd, cmd) < 0) {
puts("ftp: send failed\n");
break;
}
/* Receive response */
code = recv_response(sockfd);
if (code < 0) {
puts("ftp: connection lost\n");
break;
}
}
return 0;
}

202
apps/ip/ip.c Normal file
View File

@@ -0,0 +1,202 @@
/**
* @file ip.c
* @brief Display and configure IPv4 network configuration.
*
* Reads network interface information from /sys/net and displays
* the current IPv4 configuration for all interfaces.
*
* Usage:
* ip - Show all interfaces
* ip set <iface> <ip> <netmask> <gateway> - Configure an interface
*
* Examples:
* ip
* ip set eth1 192.168.1.100 255.255.255.0 192.168.1.1
*/
#include "syscalls.h"
/**
* Read the contents of a sysfs file into buf.
* Returns number of bytes read, or -1 on failure.
*/
static int32_t read_sysfs(const char *path, char *buf, uint32_t size) {
int32_t fd = open(path, 0);
if (fd < 0) return -1;
int32_t n = read(fd, buf, size - 1);
close(fd);
if (n > 0) {
buf[n] = '\0';
/* Strip trailing newline */
for (int32_t i = n - 1; i >= 0; i--) {
if (buf[i] == '\n' || buf[i] == '\r') buf[i] = '\0';
else break;
}
} else {
buf[0] = '\0';
}
return n;
}
/**
* Build a sysfs path: /sys/net/<iface>/<file>
*/
static void build_path(char *out, uint32_t out_size,
const char *iface, const char *file) {
/* Manual string concatenation */
uint32_t pos = 0;
const char *prefix = "/sys/net/";
while (*prefix && pos < out_size - 1) out[pos++] = *prefix++;
while (*iface && pos < out_size - 1) out[pos++] = *iface++;
if (pos < out_size - 1) out[pos++] = '/';
while (*file && pos < out_size - 1) out[pos++] = *file++;
out[pos] = '\0';
}
/**
* Show info for one interface.
*/
static void show_iface(const char *name) {
char path[128];
char val[64];
puts(name);
puts(":\n");
/* MAC address */
build_path(path, sizeof(path), name, "mac");
if (read_sysfs(path, val, sizeof(val)) > 0) {
puts(" MAC: ");
puts(val);
puts("\n");
}
/* Link status */
build_path(path, sizeof(path), name, "link");
if (read_sysfs(path, val, sizeof(val)) > 0) {
puts(" Link: ");
puts(val);
puts("\n");
}
/* IP address */
build_path(path, sizeof(path), name, "ip");
if (read_sysfs(path, val, sizeof(val)) > 0) {
puts(" IP: ");
puts(val);
puts("\n");
}
/* Netmask */
build_path(path, sizeof(path), name, "netmask");
if (read_sysfs(path, val, sizeof(val)) > 0) {
puts(" Netmask: ");
puts(val);
puts("\n");
}
/* Gateway */
build_path(path, sizeof(path), name, "gateway");
if (read_sysfs(path, val, sizeof(val)) > 0) {
puts(" Gateway: ");
puts(val);
puts("\n");
}
}
/**
* Write a value to a sysfs file.
*/
static int32_t write_sysfs(const char *path, const char *value) {
int32_t fd = open(path, 0);
if (fd < 0) return -1;
int32_t n = write(fd, value, strlen(value));
close(fd);
return n;
}
int main(void) {
char arg1[64];
/* Check if we have a subcommand */
if (getenv("ARG1", arg1, sizeof(arg1)) < 0 || arg1[0] == '\0') {
/* No arguments — show all interfaces */
char name[128];
uint32_t idx = 0;
int found = 0;
while (readdir("/sys/net", idx, name) >= 0) {
show_iface(name);
found = 1;
idx++;
}
if (!found) {
puts("No network interfaces found.\n");
}
return 0;
}
/* Check for "set" subcommand */
if (strcmp(arg1, "set") == 0) {
char iface_name[32], ip[32], netmask[32], gateway[32];
char path[128];
if (getenv("ARG2", iface_name, sizeof(iface_name)) < 0) {
puts("Usage: ip set <iface> <ip> <netmask> <gateway>\n");
return 1;
}
if (getenv("ARG3", ip, sizeof(ip)) < 0) {
puts("Usage: ip set <iface> <ip> <netmask> <gateway>\n");
return 1;
}
if (getenv("ARG4", netmask, sizeof(netmask)) < 0) {
puts("Usage: ip set <iface> <ip> <netmask> <gateway>\n");
return 1;
}
if (getenv("ARG5", gateway, sizeof(gateway)) < 0) {
puts("Usage: ip set <iface> <ip> <netmask> <gateway>\n");
return 1;
}
/* Write IP */
build_path(path, sizeof(path), iface_name, "ip");
if (write_sysfs(path, ip) < 0) {
puts("Failed to set IP address\n");
return 1;
}
/* Write netmask */
build_path(path, sizeof(path), iface_name, "netmask");
if (write_sysfs(path, netmask) < 0) {
puts("Failed to set netmask\n");
return 1;
}
/* Write gateway */
build_path(path, sizeof(path), iface_name, "gateway");
if (write_sysfs(path, gateway) < 0) {
puts("Failed to set gateway\n");
return 1;
}
puts("Configured ");
puts(iface_name);
puts(": ");
puts(ip);
puts(" / ");
puts(netmask);
puts(" gw ");
puts(gateway);
puts("\n");
return 0;
}
puts("Unknown command: ");
puts(arg1);
puts("\nUsage: ip [set <iface> <ip> <netmask> <gateway>]\n");
return 1;
}

View File

@@ -26,6 +26,12 @@ typedef int int32_t;
#define SYS_READDIR 10
#define SYS_OPEN 11
#define SYS_CLOSE 12
#define SYS_SOCKET 13
#define SYS_CONNECT 14
#define SYS_SEND 15
#define SYS_RECV 16
#define SYS_SOCKSTATE 17
#define SYS_GFX 18
static inline int32_t syscall0(int num) {
int32_t ret;
@@ -124,6 +130,157 @@ static inline int32_t close(int32_t fd) {
return syscall1(SYS_CLOSE, (uint32_t)fd);
}
/* ================================================================
* Networking system calls
* ================================================================ */
/** Socket type constants. */
#define SOCK_TCP 0
#define SOCK_UDP 1
/** TCP state constants (match kernel tcp.h). */
#define TCP_STATE_CLOSED 0
#define TCP_STATE_SYN_SENT 2
#define TCP_STATE_ESTABLISHED 4
#define TCP_STATE_CLOSE_WAIT 7
/**
* Create a network socket.
* @param type SOCK_TCP (0) or SOCK_UDP (1).
* @return Socket descriptor (>= 0) or -1 on failure.
*/
static inline int32_t socket(uint32_t type) {
return syscall1(SYS_SOCKET, type);
}
/**
* Connect a TCP socket to a remote host.
* @param sockfd Socket descriptor.
* @param ip Remote IP address (host byte order).
* @param port Remote port (host byte order).
* @return 0 on success (SYN sent), -1 on failure.
*/
static inline int32_t connect(int32_t sockfd, uint32_t ip, uint32_t port) {
return syscall3(SYS_CONNECT, (uint32_t)sockfd, ip, port);
}
/**
* Send data on a connected socket.
* @param sockfd Socket descriptor.
* @param buf Data buffer.
* @param len Data length.
* @return Bytes sent, or -1 on failure.
*/
static inline int32_t net_send(int32_t sockfd, const void *buf, uint32_t len) {
return syscall3(SYS_SEND, (uint32_t)sockfd, (uint32_t)buf, len);
}
/**
* Receive data from a connected socket (non-blocking).
* @param sockfd Socket descriptor.
* @param buf Buffer.
* @param bufsize Buffer size.
* @return Bytes received, 0 if no data, -1 on error/closed.
*/
static inline int32_t net_recv(int32_t sockfd, void *buf, uint32_t bufsize) {
return syscall3(SYS_RECV, (uint32_t)sockfd, (uint32_t)buf, bufsize);
}
/**
* Get the state of a TCP socket.
* @param sockfd Socket descriptor.
* @return TCP state constant, or -1.
*/
static inline int32_t sockstate(int32_t sockfd) {
return syscall1(SYS_SOCKSTATE, (uint32_t)sockfd);
}
/* ================================================================
* Graphics system calls
* ================================================================ */
/** Graphics sub-commands. */
#define GFX_CMD_ENTER 0
#define GFX_CMD_LEAVE 1
#define GFX_CMD_PIXEL 2
#define GFX_CMD_CLEAR 3
#define GFX_CMD_FILL_RECT 4
#define GFX_CMD_LINE 5
#define GFX_CMD_CIRCLE 6
#define GFX_CMD_GET_INFO 7
/** Graphics mode dimensions. */
#define GFX_WIDTH 320
#define GFX_HEIGHT 200
/** Palette color constants. */
#define GFX_BLACK 0
#define GFX_BLUE 1
#define GFX_GREEN 2
#define GFX_CYAN 3
#define GFX_RED 4
#define GFX_MAGENTA 5
#define GFX_BROWN 6
#define GFX_LIGHT_GREY 7
#define GFX_DARK_GREY 8
#define GFX_LIGHT_BLUE 9
#define GFX_LIGHT_GREEN 10
#define GFX_LIGHT_CYAN 11
#define GFX_LIGHT_RED 12
#define GFX_LIGHT_MAGENTA 13
#define GFX_YELLOW 14
#define GFX_WHITE 15
/** Command structs for complex drawing operations. */
typedef struct { uint32_t x, y, w, h, color; } gfx_rect_t;
typedef struct { uint32_t x1, y1, x2, y2, color; } gfx_line_t;
typedef struct { uint32_t cx, cy, r, color; } gfx_circle_t;
/** Convert RGB (0-255) to palette index using 6x6x6 color cube. */
static inline uint32_t gfx_rgb(uint32_t r, uint32_t g, uint32_t b) {
return 16 + (r / 51) * 36 + (g / 51) * 6 + (b / 51);
}
/** Enter graphics mode (320x200x256). */
static inline int32_t gfx_enter(void) {
return syscall1(SYS_GFX, GFX_CMD_ENTER);
}
/** Leave graphics mode, return to text. */
static inline int32_t gfx_leave(void) {
return syscall1(SYS_GFX, GFX_CMD_LEAVE);
}
/** Set a pixel. */
static inline int32_t gfx_pixel(uint32_t x, uint32_t y, uint32_t color) {
return syscall3(SYS_GFX, GFX_CMD_PIXEL, (x | (y << 16)), color);
}
/** Clear screen with a color. */
static inline int32_t gfx_clear(uint32_t color) {
return syscall2(SYS_GFX, GFX_CMD_CLEAR, color);
}
/** Fill a rectangle. */
static inline int32_t gfx_fill_rect(const gfx_rect_t *r) {
return syscall2(SYS_GFX, GFX_CMD_FILL_RECT, (uint32_t)r);
}
/** Draw a line. */
static inline int32_t gfx_line(const gfx_line_t *l) {
return syscall2(SYS_GFX, GFX_CMD_LINE, (uint32_t)l);
}
/** Draw a filled circle. */
static inline int32_t gfx_circle(const gfx_circle_t *c) {
return syscall2(SYS_GFX, GFX_CMD_CIRCLE, (uint32_t)c);
}
/** Get graphics info: returns (width | height<<16). */
static inline int32_t gfx_get_info(void) {
return syscall1(SYS_GFX, GFX_CMD_GET_INFO);
}
/* Basic string operations for user-space */
static inline uint32_t strlen(const char *s) {
uint32_t len = 0;

585
apps/minigolf/minigolf.c Normal file
View File

@@ -0,0 +1,585 @@
/**
* @file minigolf.c
* @brief Simple minigolf game for ClaudeOS.
*
* Uses VGA mode 0x13 (320x200, 256 colors) via the graphics subsystem.
* Features 4 progressively harder holes with walls and obstacles.
*
* Controls:
* A/D or Left/Right - Aim
* W/S or Up/Down - Adjust power
* Space or Enter - Shoot
* Q or Escape - Quit
*/
#include "syscalls.h"
/* ================================================================
* Fixed-point math (16.16)
* ================================================================ */
typedef int32_t fixed_t;
#define FP_SHIFT 16
#define FP_ONE (1 << FP_SHIFT)
#define INT_TO_FP(x) ((fixed_t)(x) << FP_SHIFT)
#define FP_TO_INT(x) ((int)((x) >> FP_SHIFT))
#define FP_MUL(a, b) ((fixed_t)(((int32_t)(a) * (int32_t)(b)) >> FP_SHIFT))
static uint32_t isqrt(uint32_t n) {
if (n == 0) return 0;
uint32_t x = n, y = (x + 1) / 2;
while (y < x) { x = y; y = (x + n / x) / 2; }
return x;
}
/* Quarter-wave sine table (0..64 entries, 16.16 fixed point) */
static const fixed_t sin_q[65] = {
0, 1608, 3216, 4821, 6424, 8022, 9616, 11204,
12785, 14359, 15924, 17479, 19024, 20557, 22078, 23586,
25080, 26558, 28020, 29466, 30893, 32303, 33692, 35062,
36410, 37736, 39040, 40320, 41576, 42806, 44011, 45190,
46341, 47464, 48559, 49624, 50660, 51665, 52639, 53581,
54491, 55368, 56212, 57022, 57798, 58538, 59244, 59914,
60547, 61145, 61705, 62228, 62714, 63162, 63572, 63944,
64277, 64571, 64827, 65043, 65220, 65358, 65457, 65516,
65536
};
static fixed_t fp_sin(int a) {
a &= 255;
int q = a >> 6, i = a & 63;
fixed_t v;
switch (q) {
case 0: v = sin_q[i]; break;
case 1: v = sin_q[64 - i]; break;
case 2: v = -sin_q[i]; break;
case 3: v = -sin_q[64 - i]; break;
default: v = 0;
}
return v;
}
static fixed_t fp_cos(int a) { return fp_sin(a + 64); }
/* ================================================================
* Constants
* ================================================================ */
#define SW 320
#define SH 200
#define BALL_R 3
#define HOLE_R 6
#define FRICTION (FP_ONE - FP_ONE / 80) /* ~0.9875 */
#define MIN_SPEED (FP_ONE / 16)
#define MAX_HOLES 4
/* Colors */
#define C_GRASS GFX_GREEN
#define C_WALL GFX_LIGHT_GREY
#define C_BALL GFX_WHITE
#define C_HOLE GFX_BLACK
#define C_AIM GFX_YELLOW
#define C_POWER GFX_LIGHT_RED
#define C_TEXT GFX_WHITE
#define C_WATER GFX_BLUE
#define C_SAND GFX_BROWN
#define C_BG GFX_DARK_GREY
/* ================================================================
* Wall segment
* ================================================================ */
typedef struct { int x1, y1, x2, y2; } wall_t;
/* ================================================================
* Hole (level) definition
* ================================================================ */
#define MAX_WALLS 16
#define MAX_OBS 4 /* Obstacles (water/sand zones) */
typedef struct {
int par;
int ball_x, ball_y; /* Start position */
int hole_x, hole_y; /* Hole position */
int num_walls;
wall_t walls[MAX_WALLS];
/* Rectangular obstacles */
int num_obs;
struct { int x, y, w, h; uint32_t color; } obs[MAX_OBS];
} hole_def_t;
/* ================================================================
* Course layout (4 holes)
* ================================================================ */
static const hole_def_t course[MAX_HOLES] = {
/* Hole 1: Simple straight shot */
{
.par = 2,
.ball_x = 60, .ball_y = 100,
.hole_x = 260, .hole_y = 100,
.num_walls = 4,
.walls = {
{30, 60, 290, 60}, /* Top wall */
{30, 140, 290, 140}, /* Bottom wall */
{30, 60, 30, 140}, /* Left wall */
{290, 60, 290, 140}, /* Right wall */
},
.num_obs = 0,
},
/* Hole 2: L-shaped with turn */
{
.par = 3,
.ball_x = 50, .ball_y = 50,
.hole_x = 270, .hole_y = 160,
.num_walls = 8,
.walls = {
{20, 20, 200, 20}, /* Top */
{20, 80, 200, 80}, /* Mid horizontal */
{20, 20, 20, 80}, /* Left */
{200, 20, 200, 80}, /* Right-top */
{200, 80, 300, 80}, /* Turn top */
{140, 80, 140, 190}, /* Turn left */
{300, 80, 300, 190}, /* Right */
{140, 190, 300, 190}, /* Bottom */
},
.num_obs = 0,
},
/* Hole 3: Water hazard */
{
.par = 3,
.ball_x = 50, .ball_y = 100,
.hole_x = 270, .hole_y = 100,
.num_walls = 4,
.walls = {
{20, 50, 300, 50},
{20, 150, 300, 150},
{20, 50, 20, 150},
{300, 50, 300, 150},
},
.num_obs = 1,
.obs = {
{130, 70, 60, 60, C_WATER},
},
},
/* Hole 4: Obstacle course */
{
.par = 4,
.ball_x = 40, .ball_y = 100,
.hole_x = 280, .hole_y = 100,
.num_walls = 8,
.walls = {
{20, 30, 300, 30}, /* Top */
{20, 170, 300, 170}, /* Bottom */
{20, 30, 20, 170}, /* Left */
{300, 30, 300, 170}, /* Right */
/* Internal walls (obstacles) */
{100, 30, 100, 100}, /* First barrier from top */
{180, 100, 180, 170}, /* Second barrier from bottom */
{240, 30, 240, 120}, /* Third barrier from top */
{60, 120, 60, 170}, /* Small bump from bottom */
},
.num_obs = 1,
.obs = {
{120, 120, 40, 30, C_SAND}, /* Sand trap */
},
},
};
/* ================================================================
* Game state
* ================================================================ */
static fixed_t ball_x, ball_y, ball_vx, ball_vy;
static int aim_angle = 0;
static int power = 3; /* 1-6 */
static int curr_hole = 0;
static int strokes = 0;
static int total_strokes = 0;
static int ball_moving = 0;
static int ball_in_hole = 0;
static int in_water = 0;
static fixed_t saved_x, saved_y; /* Last safe position (before water) */
/* ================================================================
* Wall collision
* ================================================================ */
/**
* Check if the ball collides with a horizontal or vertical wall segment.
* Simple axis-aligned bounce.
*/
static void check_wall_bounce(const wall_t *w) {
int bx = FP_TO_INT(ball_x);
int by = FP_TO_INT(ball_y);
if (w->y1 == w->y2) {
/* Horizontal wall */
int minx = w->x1 < w->x2 ? w->x1 : w->x2;
int maxx = w->x1 > w->x2 ? w->x1 : w->x2;
if (bx >= minx - BALL_R && bx <= maxx + BALL_R) {
int dy = by - w->y1;
if (dy < 0) dy = -dy;
if (dy <= BALL_R) {
ball_vy = -ball_vy;
/* Push ball out */
if (by < w->y1)
ball_y = INT_TO_FP(w->y1 - BALL_R - 1);
else
ball_y = INT_TO_FP(w->y1 + BALL_R + 1);
}
}
} else if (w->x1 == w->x2) {
/* Vertical wall */
int miny = w->y1 < w->y2 ? w->y1 : w->y2;
int maxy = w->y1 > w->y2 ? w->y1 : w->y2;
if (by >= miny - BALL_R && by <= maxy + BALL_R) {
int dx = bx - w->x1;
if (dx < 0) dx = -dx;
if (dx <= BALL_R) {
ball_vx = -ball_vx;
if (bx < w->x1)
ball_x = INT_TO_FP(w->x1 - BALL_R - 1);
else
ball_x = INT_TO_FP(w->x1 + BALL_R + 1);
}
}
}
}
/* ================================================================
* Physics update
* ================================================================ */
static void update_physics(void) {
const hole_def_t *h = &course[curr_hole];
/* Move ball */
ball_x += ball_vx;
ball_y += ball_vy;
/* Save last safe position */
int bx = FP_TO_INT(ball_x);
int by = FP_TO_INT(ball_y);
/* Check obstacles */
in_water = 0;
for (int i = 0; i < h->num_obs; i++) {
int ox = h->obs[i].x, oy = h->obs[i].y;
int ow = h->obs[i].w, oh = h->obs[i].h;
if (bx >= ox && bx <= ox + ow && by >= oy && by <= oy + oh) {
if (h->obs[i].color == C_WATER) {
in_water = 1;
/* Reset ball to last safe position */
ball_x = saved_x;
ball_y = saved_y;
ball_vx = 0;
ball_vy = 0;
strokes++; /* Penalty stroke */
return;
} else if (h->obs[i].color == C_SAND) {
/* Sand: extra friction */
ball_vx = FP_MUL(ball_vx, FP_ONE - FP_ONE / 20);
ball_vy = FP_MUL(ball_vy, FP_ONE - FP_ONE / 20);
}
}
}
/* Apply friction */
ball_vx = FP_MUL(ball_vx, FRICTION);
ball_vy = FP_MUL(ball_vy, FRICTION);
/* Check if stopped */
fixed_t speed_sq = FP_MUL(ball_vx, ball_vx) + FP_MUL(ball_vy, ball_vy);
if (speed_sq < FP_MUL(MIN_SPEED, MIN_SPEED)) {
ball_vx = 0;
ball_vy = 0;
ball_moving = 0;
saved_x = ball_x;
saved_y = ball_y;
}
/* Wall collisions */
for (int i = 0; i < h->num_walls; i++) {
check_wall_bounce(&h->walls[i]);
}
/* Check hole */
fixed_t dx = ball_x - INT_TO_FP(h->hole_x);
fixed_t dy = ball_y - INT_TO_FP(h->hole_y);
fixed_t dist_sq = FP_MUL(dx, dx) + FP_MUL(dy, dy);
fixed_t hole_r = INT_TO_FP(HOLE_R);
if (dist_sq < FP_MUL(hole_r, hole_r)) {
ball_in_hole = 1;
ball_vx = 0;
ball_vy = 0;
ball_moving = 0;
}
}
/* ================================================================
* Drawing
* ================================================================ */
static void draw_number(int x, int y, int num, uint32_t color) {
char buf[8];
int len = 0;
if (num == 0) { buf[len++] = '0'; }
else {
int n = num;
while (n > 0) { buf[len++] = '0' + (char)(n % 10); n /= 10; }
}
/* Reverse and draw as pixels (very simple 3x5 digit font) */
for (int i = len - 1; i >= 0; i--) {
/* Draw digit as a small cluster of pixels */
int d = buf[i] - '0';
int dx = x + (len - 1 - i) * 5;
/* Simple representation: draw a small filled rect for each digit */
gfx_rect_t r = {(uint32_t)dx, (uint32_t)y, 4, 5, color};
gfx_fill_rect(&r);
/* Blank out parts to make it look like a number - simplified */
if (d == 0) { gfx_rect_t inner = {(uint32_t)(dx+1), (uint32_t)(y+1), 2, 3, C_BG}; gfx_fill_rect(&inner); }
if (d == 1) { gfx_rect_t l = {(uint32_t)dx, (uint32_t)y, 1, 5, C_BG}; gfx_fill_rect(&l);
gfx_rect_t r2 = {(uint32_t)(dx+2), (uint32_t)y, 2, 5, C_BG}; gfx_fill_rect(&r2); }
}
}
static void draw_hole(void) {
const hole_def_t *h = &course[curr_hole];
/* Background */
gfx_clear(C_BG);
/* Draw course grass area (fill inside walls approximately) */
/* Just fill the entire course bounding box with grass */
int minx = 999, miny = 999, maxx = 0, maxy = 0;
for (int i = 0; i < h->num_walls; i++) {
if (h->walls[i].x1 < minx) minx = h->walls[i].x1;
if (h->walls[i].x2 < minx) minx = h->walls[i].x2;
if (h->walls[i].y1 < miny) miny = h->walls[i].y1;
if (h->walls[i].y2 < miny) miny = h->walls[i].y2;
if (h->walls[i].x1 > maxx) maxx = h->walls[i].x1;
if (h->walls[i].x2 > maxx) maxx = h->walls[i].x2;
if (h->walls[i].y1 > maxy) maxy = h->walls[i].y1;
if (h->walls[i].y2 > maxy) maxy = h->walls[i].y2;
}
gfx_rect_t grass = {(uint32_t)minx, (uint32_t)miny,
(uint32_t)(maxx - minx), (uint32_t)(maxy - miny), C_GRASS};
gfx_fill_rect(&grass);
/* Draw obstacles */
for (int i = 0; i < h->num_obs; i++) {
gfx_rect_t obs = {(uint32_t)h->obs[i].x, (uint32_t)h->obs[i].y,
(uint32_t)h->obs[i].w, (uint32_t)h->obs[i].h,
h->obs[i].color};
gfx_fill_rect(&obs);
}
/* Draw walls */
for (int i = 0; i < h->num_walls; i++) {
gfx_line_t line = {(uint32_t)h->walls[i].x1, (uint32_t)h->walls[i].y1,
(uint32_t)h->walls[i].x2, (uint32_t)h->walls[i].y2, C_WALL};
gfx_line(&line);
}
/* Draw hole */
gfx_circle_t hole_circ = {(uint32_t)h->hole_x, (uint32_t)h->hole_y, HOLE_R, C_HOLE};
gfx_circle(&hole_circ);
/* Hole rim */
/* Draw a slightly larger circle outline in white for visibility */
/* We'll just use the filled circle - the black on green is visible */
/* Draw ball */
if (!ball_in_hole) {
gfx_circle_t bc = {(uint32_t)FP_TO_INT(ball_x), (uint32_t)FP_TO_INT(ball_y),
BALL_R, C_BALL};
gfx_circle(&bc);
}
/* Draw aiming line */
if (!ball_moving && !ball_in_hole) {
int cx = FP_TO_INT(ball_x);
int cy = FP_TO_INT(ball_y);
int len = 15 + power * 4;
int ex = cx + FP_TO_INT(FP_MUL(INT_TO_FP(len), fp_cos(aim_angle)));
int ey = cy + FP_TO_INT(FP_MUL(INT_TO_FP(len), fp_sin(aim_angle)));
gfx_line_t aim = {(uint32_t)cx, (uint32_t)cy,
(uint32_t)ex, (uint32_t)ey, C_AIM};
gfx_line(&aim);
}
/* HUD */
/* Hole number indicator */
gfx_rect_t hud_bg = {0, 0, SW, 12, C_BG};
gfx_fill_rect(&hud_bg);
/* "Hole N Par N Strokes N" */
/* Simple pixel text for HUD - draw filled rects as digit placeholders */
/* Hole number */
gfx_rect_t h_label = {2, 2, 24, 7, C_WALL};
gfx_fill_rect(&h_label); /* "Hole" background */
draw_number(28, 2, curr_hole + 1, C_TEXT);
/* Par */
gfx_rect_t p_label = {50, 2, 16, 7, C_WALL};
gfx_fill_rect(&p_label);
draw_number(68, 2, h->par, C_TEXT);
/* Strokes */
gfx_rect_t s_label = {90, 2, 36, 7, C_WALL};
gfx_fill_rect(&s_label);
draw_number(128, 2, strokes, C_TEXT);
/* Power bar */
gfx_rect_t pwr_bg = {SW - 62, 2, 60, 7, C_BG};
gfx_fill_rect(&pwr_bg);
gfx_rect_t pwr = {SW - 62, 2, (uint32_t)(power * 10), 7, C_POWER};
gfx_fill_rect(&pwr);
/* Ball in hole message */
if (ball_in_hole) {
gfx_rect_t msg_bg = {SW/2 - 40, SH/2 - 8, 80, 16, GFX_BLUE};
gfx_fill_rect(&msg_bg);
/* "IN" text represented as colored block */
gfx_rect_t msg = {SW/2 - 8, SH/2 - 4, 16, 8, C_TEXT};
gfx_fill_rect(&msg);
}
}
/* ================================================================
* Input
* ================================================================ */
static int handle_input(void) {
char c;
if (read(0, &c, 1) <= 0) return 0;
switch (c) {
case 'q': case 'Q': case 27:
return 1;
case 'a': case 'A':
aim_angle = (aim_angle - 4) & 255;
break;
case 'd': case 'D':
aim_angle = (aim_angle + 4) & 255;
break;
case 'w': case 'W':
if (power < 6) power++;
break;
case 's': case 'S':
if (power > 1) power--;
break;
case ' ': case '\n': case '\r':
if (!ball_moving && !ball_in_hole) {
fixed_t p = INT_TO_FP(power);
ball_vx = FP_MUL(p, fp_cos(aim_angle));
ball_vy = FP_MUL(p, fp_sin(aim_angle));
ball_moving = 1;
strokes++;
}
break;
case 'n': case 'N':
/* Next hole (after sinking) */
if (ball_in_hole) {
total_strokes += strokes;
curr_hole++;
if (curr_hole >= MAX_HOLES) return 2; /* Game complete */
/* Reset for next hole */
strokes = 0;
ball_in_hole = 0;
ball_moving = 0;
aim_angle = 0;
power = 3;
ball_x = INT_TO_FP(course[curr_hole].ball_x);
ball_y = INT_TO_FP(course[curr_hole].ball_y);
ball_vx = 0;
ball_vy = 0;
saved_x = ball_x;
saved_y = ball_y;
}
break;
}
return 0;
}
/* ================================================================
* Main
* ================================================================ */
int main(void) {
gfx_enter();
/* Initialize first hole */
curr_hole = 0;
strokes = 0;
total_strokes = 0;
ball_in_hole = 0;
ball_moving = 0;
aim_angle = 0;
power = 3;
ball_x = INT_TO_FP(course[0].ball_x);
ball_y = INT_TO_FP(course[0].ball_y);
ball_vx = 0;
ball_vy = 0;
saved_x = ball_x;
saved_y = ball_y;
int quit = 0;
while (!quit) {
int r = handle_input();
if (r == 1) break; /* Quit */
if (r == 2) { quit = 2; break; } /* Game complete */
if (ball_moving) {
update_physics();
}
draw_hole();
for (int i = 0; i < 2; i++) yield();
}
/* Show final score */
if (quit == 2) {
total_strokes += strokes;
/* Show completion screen */
gfx_clear(C_BG);
gfx_rect_t box = {SW/2 - 60, SH/2 - 20, 120, 40, GFX_BLUE};
gfx_fill_rect(&box);
/* Score display */
draw_number(SW/2 - 10, SH/2 - 5, total_strokes, C_TEXT);
/* Wait */
for (int i = 0; i < 300; i++) yield();
}
gfx_leave();
puts("Minigolf complete!\n");
puts("Total strokes: ");
char tmp[8];
int ti = 0, s = total_strokes;
if (s == 0) putchar('0');
else { while (s > 0) { tmp[ti++] = '0' + (char)(s % 10); s /= 10; } while (ti > 0) putchar(tmp[--ti]); }
puts("\n");
/* Calculate par total */
int total_par = 0;
for (int i = 0; i < MAX_HOLES; i++) total_par += course[i].par;
puts("Par: ");
ti = 0; s = total_par;
if (s == 0) putchar('0');
else { while (s > 0) { tmp[ti++] = '0' + (char)(s % 10); s /= 10; } while (ti > 0) putchar(tmp[--ti]); }
puts("\n");
return 0;
}

View File

@@ -0,0 +1,511 @@
/**
* @file mkfs.fat32.c
* @brief Format a block device with a FAT32 filesystem.
*
* Writes the FAT32 boot sector (BPB), FSInfo sector, two File Allocation
* Tables, and an empty root directory cluster to the specified device.
*
* Usage: mkfs.fat32 <device> <total_sectors>
* e.g.: mkfs.fat32 hdd1mbr1 14336
*
* The device is accessed via /dev/<device>. Total sectors must be
* specified because the OS does not yet expose device size to userspace.
*/
#include "syscalls.h"
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
/* ================================================================
* Constants
* ================================================================ */
#define SECTOR_SIZE 512
#define RESERVED_SECTS 32
#define NUM_FATS 2
#define ROOT_CLUSTER 2
#define FSINFO_SECTOR 1
#define BACKUP_BOOT 6
/* ================================================================
* Helpers
* ================================================================ */
/**
* Print a 32-bit decimal value.
*/
static void print_dec(uint32_t val) {
char buf[12];
int pos = 11;
buf[pos] = '\0';
if (val == 0) {
buf[--pos] = '0';
} else {
while (val > 0 && pos > 0) {
buf[--pos] = (char)('0' + (val % 10));
val /= 10;
}
}
puts(&buf[pos]);
}
/**
* Print a hex value.
*/
static void print_hex(uint32_t val) {
char buf[11];
buf[0] = '0';
buf[1] = 'x';
for (int i = 9; i >= 2; i--) {
int n = (int)(val & 0xF);
buf[i] = (n < 10) ? (char)('0' + n) : (char)('A' + n - 10);
val >>= 4;
}
buf[10] = '\0';
puts(buf);
}
/**
* Parse decimal string.
*/
static uint32_t parse_dec(const char *s, int *ok) {
uint32_t val = 0;
*ok = 0;
if (!s || !*s) return 0;
while (*s) {
if (*s < '0' || *s > '9') { *ok = 0; return 0; }
val = val * 10 + (uint32_t)(*s - '0');
s++;
}
*ok = 1;
return val;
}
/**
* Write a 16-bit little-endian value.
*/
static void w16(uint8_t *p, uint32_t val) {
p[0] = (uint8_t)(val & 0xFF);
p[1] = (uint8_t)((val >> 8) & 0xFF);
}
/**
* Write a 32-bit little-endian value.
*/
static void w32(uint8_t *p, uint32_t val) {
p[0] = (uint8_t)(val & 0xFF);
p[1] = (uint8_t)((val >> 8) & 0xFF);
p[2] = (uint8_t)((val >> 16) & 0xFF);
p[3] = (uint8_t)((val >> 24) & 0xFF);
}
/* ================================================================
* Argument parsing
* ================================================================ */
#define MAX_TOKENS 4
static char tok_buf[256];
static char *tokens[MAX_TOKENS];
static int token_count;
static void tokenize(const char *input) {
int i = 0;
while (input[i] && i < 255) { tok_buf[i] = input[i]; i++; }
tok_buf[i] = '\0';
token_count = 0;
char *p = tok_buf;
while (*p && token_count < MAX_TOKENS) {
while (*p == ' ') p++;
if (!*p) break;
tokens[token_count++] = p;
while (*p && *p != ' ') p++;
if (*p) { *p = '\0'; p++; }
}
}
/* ================================================================
* Sector buffer (reused for each write)
* ================================================================ */
static uint8_t sector[SECTOR_SIZE];
/**
* Write the sector buffer to the device fd.
* Returns 0 on success, -1 on failure.
*/
static int write_sector(int32_t fd) {
int32_t n = write(fd, sector, SECTOR_SIZE);
return (n == SECTOR_SIZE) ? 0 : -1;
}
/**
* Write N zero sectors to the device fd.
*/
static int write_zero_sectors(int32_t fd, uint32_t count) {
memset(sector, 0, SECTOR_SIZE);
for (uint32_t i = 0; i < count; i++) {
if (write_sector(fd) != 0) return -1;
}
return 0;
}
/* ================================================================
* FAT32 geometry calculation
* ================================================================ */
/**
* Determine sectors per cluster based on volume size.
* Uses Microsoft's recommended values for FAT32.
*/
static uint32_t choose_spc(uint32_t total_sectors) {
/* For small volumes, use small clusters to maximize cluster count.
* FAT32 "officially" needs >= 65525 clusters, but our driver
* doesn't enforce this minimum. */
if (total_sectors <= 16384) return 1; /* <= 8 MiB: 512B */
if (total_sectors <= 131072) return 8; /* <= 64 MiB: 4K */
if (total_sectors <= 524288) return 8; /* <= 256 MiB: 4K */
if (total_sectors <= 16777216) return 8; /* <= 8 GiB: 4K */
if (total_sectors <= 33554432) return 16; /* <= 16 GiB: 8K */
return 32; /* > 16 GiB: 16K */
}
/**
* Calculate FAT size in sectors using Microsoft's formula.
*
* fat_size = ceil((total - reserved) / (entries_per_sec * spc + num_fats))
* where entries_per_sec = 128 (512 / 4 bytes per FAT32 entry).
*/
static uint32_t calc_fat_size(uint32_t total_sectors, uint32_t spc) {
uint32_t data_area = total_sectors - RESERVED_SECTS;
uint32_t tmp = 128 * spc + NUM_FATS;
return (data_area + tmp - 1) / tmp;
}
/* ================================================================
* BPB / Boot sector construction
* ================================================================ */
/**
* Build the FAT32 boot sector (BPB) into the sector buffer.
*/
static void build_boot_sector(uint32_t total_sectors, uint32_t spc,
uint32_t fat_size, const char *label) {
memset(sector, 0, SECTOR_SIZE);
/* Jump instruction: EB 58 90 (jump over BPB to boot code area) */
sector[0] = 0xEB;
sector[1] = 0x58;
sector[2] = 0x90;
/* OEM Name (offset 3, 8 bytes) */
memcpy(&sector[3], "CLAUDEOS", 8);
/* BPB — BIOS Parameter Block */
w16(&sector[11], SECTOR_SIZE); /* bytes_per_sector */
sector[13] = (uint8_t)spc; /* sectors_per_cluster */
w16(&sector[14], RESERVED_SECTS); /* reserved_sectors */
sector[16] = NUM_FATS; /* num_fats */
w16(&sector[17], 0); /* root_entry_count (0 for FAT32) */
w16(&sector[19], 0); /* total_sectors_16 (0, use 32-bit) */
sector[21] = 0xF8; /* media_type (hard disk) */
w16(&sector[22], 0); /* fat_size_16 (0, use 32-bit) */
w16(&sector[24], 63); /* sectors_per_track */
w16(&sector[26], 255); /* num_heads */
w32(&sector[28], 0); /* hidden_sectors */
w32(&sector[32], total_sectors); /* total_sectors_32 */
/* FAT32 Extended BPB (offset 36) */
w32(&sector[36], fat_size); /* fat_size_32 */
w16(&sector[40], 0); /* ext_flags */
w16(&sector[42], 0); /* fs_version */
w32(&sector[44], ROOT_CLUSTER); /* root_cluster */
w16(&sector[48], FSINFO_SECTOR); /* fs_info sector */
w16(&sector[50], BACKUP_BOOT); /* backup_boot sector */
/* Reserved (12 bytes at offset 52, already zeroed) */
sector[64] = 0x80; /* drive_number (HDD) */
sector[65] = 0; /* reserved1 */
sector[66] = 0x29; /* boot_sig (extended boot sig) */
/* Volume serial number (we'll use a simple hash) */
uint32_t serial = total_sectors ^ (spc << 16) ^ 0xC1A0DE05;
w32(&sector[67], serial);
/* Volume label (11 bytes, space-padded) */
int li = 0;
while (label[li] && li < 11) {
char c = label[li];
/* Convert to uppercase for FAT compatibility */
if (c >= 'a' && c <= 'z') c = (char)(c - 32);
sector[71 + li] = (uint8_t)c;
li++;
}
while (li < 11) {
sector[71 + li] = ' ';
li++;
}
/* FS type string (8 bytes) */
memcpy(&sector[82], "FAT32 ", 8);
/* Boot sector signature */
sector[510] = 0x55;
sector[511] = 0xAA;
}
/* ================================================================
* FSInfo sector construction
* ================================================================ */
/**
* Build the FSInfo sector into the sector buffer.
*/
static void build_fsinfo(uint32_t free_clusters, uint32_t next_free) {
memset(sector, 0, SECTOR_SIZE);
/* FSInfo signatures */
w32(&sector[0], 0x41615252); /* Lead signature */
w32(&sector[484], 0x61417272); /* Structure signature */
w32(&sector[488], free_clusters); /* Free cluster count */
w32(&sector[492], next_free); /* Next free cluster hint */
w32(&sector[508], 0xAA550000); /* Trail signature */
}
/* ================================================================
* FAT table construction
* ================================================================ */
/**
* Build the first sector of the FAT into the sector buffer.
* Entry 0 = media byte marker, Entry 1 = EOC, Entry 2 = EOC (root dir).
*/
static void build_fat_first_sector(void) {
memset(sector, 0, SECTOR_SIZE);
/* FAT entry 0: media type in low byte, rest 0xFF */
w32(&sector[0], 0x0FFFFFF8);
/* FAT entry 1: end-of-chain marker */
w32(&sector[4], 0x0FFFFFFF);
/* FAT entry 2: root directory cluster (end-of-chain) */
w32(&sector[8], 0x0FFFFFFF);
}
/* ================================================================
* Root directory construction
* ================================================================ */
/**
* Build the root directory cluster's first sector.
* Contains a volume label entry only.
*/
static void build_root_dir(const char *label) {
memset(sector, 0, SECTOR_SIZE);
/* Volume label directory entry (32 bytes) */
int li = 0;
while (label[li] && li < 11) {
char c = label[li];
if (c >= 'a' && c <= 'z') c = (char)(c - 32);
sector[li] = (uint8_t)c;
li++;
}
while (li < 11) {
sector[li] = ' ';
li++;
}
sector[11] = 0x08; /* ATTR_VOLUME_ID */
}
/* ================================================================
* Main formatting routine
* ================================================================ */
/**
* Format the device with FAT32.
*
* Writes sectors sequentially from sector 0:
* [0] Boot sector (BPB)
* [1] FSInfo
* [2..5] Zero (reserved)
* [6] Backup boot sector
* [7] Backup FSInfo
* [8..31] Zero (reserved)
* [32..32+fat_size-1] FAT 1
* [32+fat_size..32+2*fat_size-1] FAT 2
* [data_start..data_start+spc-1] Root directory cluster
*/
static int format_device(const char *dev_path, uint32_t total_sectors,
const char *label) {
uint32_t spc = choose_spc(total_sectors);
uint32_t fat_size = calc_fat_size(total_sectors, spc);
uint32_t data_start = RESERVED_SECTS + NUM_FATS * fat_size;
uint32_t data_sectors = total_sectors - data_start;
uint32_t total_clusters = data_sectors / spc;
uint32_t free_clusters = total_clusters - 1; /* Root dir uses cluster 2 */
/* Print format parameters */
puts("Formatting ");
puts(dev_path);
puts(" as FAT32\n");
puts(" Total sectors: ");
print_dec(total_sectors);
puts("\n Sectors/cluster: ");
print_dec(spc);
puts("\n Reserved sectors: ");
print_dec(RESERVED_SECTS);
puts("\n FAT size (sectors): ");
print_dec(fat_size);
puts("\n Number of FATs: ");
print_dec(NUM_FATS);
puts("\n Data start sector: ");
print_dec(data_start);
puts("\n Total clusters: ");
print_dec(total_clusters);
puts("\n Volume label: ");
puts(label);
puts("\n");
/* Sanity checks */
if (total_sectors < 128) {
puts("Error: volume too small for FAT32\n");
return -1;
}
if (data_start >= total_sectors) {
puts("Error: FAT tables don't fit in volume\n");
return -1;
}
/* Open device for writing */
int32_t fd = open(dev_path, 0);
if (fd < 0) {
puts("Error: cannot open ");
puts(dev_path);
puts("\n");
return -1;
}
puts("Writing boot sector...\n");
/* Sector 0: Boot sector */
build_boot_sector(total_sectors, spc, fat_size, label);
if (write_sector(fd) != 0) goto fail;
/* Sector 1: FSInfo */
build_fsinfo(free_clusters, 3); /* Next free = cluster 3 */
if (write_sector(fd) != 0) goto fail;
/* Sectors 2-5: zero */
if (write_zero_sectors(fd, 4) != 0) goto fail;
/* Sector 6: Backup boot sector */
build_boot_sector(total_sectors, spc, fat_size, label);
if (write_sector(fd) != 0) goto fail;
/* Sector 7: Backup FSInfo */
build_fsinfo(free_clusters, 3);
if (write_sector(fd) != 0) goto fail;
/* Sectors 8-31: zero (remaining reserved area) */
if (write_zero_sectors(fd, RESERVED_SECTS - 8) != 0) goto fail;
puts("Writing FAT 1...\n");
/* FAT 1: first sector has entries 0-2 */
build_fat_first_sector();
if (write_sector(fd) != 0) goto fail;
/* FAT 1: remaining sectors (all zero = free) */
if (fat_size > 1) {
if (write_zero_sectors(fd, fat_size - 1) != 0) goto fail;
}
puts("Writing FAT 2...\n");
/* FAT 2: identical copy */
build_fat_first_sector();
if (write_sector(fd) != 0) goto fail;
if (fat_size > 1) {
if (write_zero_sectors(fd, fat_size - 1) != 0) goto fail;
}
puts("Writing root directory...\n");
/* Root directory cluster (first sector has volume label) */
build_root_dir(label);
if (write_sector(fd) != 0) goto fail;
/* Remaining sectors in root cluster (zero) */
if (spc > 1) {
if (write_zero_sectors(fd, spc - 1) != 0) goto fail;
}
close(fd);
puts("Format complete!\n");
puts(" ");
print_dec(data_start + spc);
puts(" sectors written\n");
return 0;
fail:
close(fd);
puts("Error: write failed!\n");
return -1;
}
/* ================================================================
* Main
* ================================================================ */
int main(void) {
char arg1[256];
if (getenv("ARG1", arg1, sizeof(arg1)) < 0 || arg1[0] == '\0') {
puts("mkfs.fat32 - Format a device with FAT32\n");
puts("\n");
puts("Usage: mkfs.fat32 <device> <total_sectors> [label]\n");
puts("\n");
puts(" device: Device name (e.g., hdd1mbr1)\n");
puts(" total_sectors: Number of 512-byte sectors\n");
puts(" label: Volume label (default: CLAUDEOS)\n");
puts("\n");
puts("Example: mkfs.fat32 hdd1mbr1 14336\n");
return 1;
}
tokenize(arg1);
if (token_count < 2) {
puts("mkfs.fat32: need <device> and <total_sectors>\n");
puts(" Use 'diskpart <dev> list' to find sector count.\n");
return 1;
}
/* Build /dev/<device> path */
char dev_path[64];
strcpy(dev_path, "/dev/");
char *dp = dev_path + 5;
char *dn = tokens[0];
while (*dn && dp < dev_path + 62) *dp++ = *dn++;
*dp = '\0';
/* Parse total sectors */
int ok;
uint32_t total_sectors = parse_dec(tokens[1], &ok);
if (!ok || total_sectors == 0) {
puts("mkfs.fat32: invalid sector count '");
puts(tokens[1]);
puts("'\n");
return 1;
}
/* Volume label (default "CLAUDEOS") */
const char *label = "CLAUDEOS";
if (token_count >= 3) {
label = tokens[2];
}
return (format_device(dev_path, total_sectors, label) == 0) ? 0 : 1;
}

95
apps/mount/mount.c Normal file
View File

@@ -0,0 +1,95 @@
/**
* @file mount.c
* @brief Mount a block device as FAT32.
*
* Writes to /sys/vfs/mount to trigger a kernel-level FAT32 mount.
*
* Usage: mount <device> <path>
* e.g.: mount hdd1mbr1 /mnt
*/
#include "syscalls.h"
int main(void) {
char device[64];
char path[128];
if (getenv("ARG1", device, sizeof(device)) < 0 || device[0] == '\0') {
puts("Usage: mount <device> <mountpoint>\n");
puts(" e.g.: mount hdd1mbr1 /mnt\n");
return 1;
}
/* ARG1 contains "device path" — we need to split it.
* Actually, the shell currently only passes the first word as ARG1.
* Let's accept: mount <device> <path> where ARG1 = "device path" */
/* Find the space separating device and path */
int i = 0;
while (device[i] && device[i] != ' ') i++;
if (device[i] == ' ') {
/* ARG1 contains both: "hdd1mbr1 /mnt" */
device[i] = '\0';
/* Copy path part */
int j = i + 1;
while (device[j] == ' ') j++;
int pi = 0;
while (device[j] && pi < 127) {
path[pi++] = device[j++];
}
path[pi] = '\0';
} else {
/* Only device, no path — default to /mnt */
path[0] = '/'; path[1] = 'm'; path[2] = 'n'; path[3] = 't';
path[4] = '\0';
}
if (path[0] == '\0') {
puts("mount: missing mountpoint, using /mnt\n");
path[0] = '/'; path[1] = 'm'; path[2] = 'n'; path[3] = 't';
path[4] = '\0';
}
/* Build the mount command: "device path" */
char cmd[192];
int pos = 0;
for (int k = 0; device[k] && pos < 190; k++) cmd[pos++] = device[k];
cmd[pos++] = ' ';
for (int k = 0; path[k] && pos < 190; k++) cmd[pos++] = path[k];
cmd[pos] = '\0';
/* Open /sys/vfs/mount and write the command */
int32_t fd = open("/sys/vfs/mount", 0);
if (fd < 0) {
puts("mount: cannot open /sys/vfs/mount\n");
return 1;
}
int32_t n = write(fd, cmd, (uint32_t)pos);
close(fd);
if (n < 0) {
/* Read the status to show the error */
fd = open("/sys/vfs/mounts", 0);
if (fd >= 0) {
char status[256];
int32_t sn = read(fd, status, sizeof(status) - 1);
close(fd);
if (sn > 0) {
status[sn] = '\0';
puts(status);
}
} else {
puts("mount: failed\n");
}
return 1;
}
puts("mount: ");
puts(device);
puts(" mounted at ");
puts(path);
puts("\n");
return 0;
}

582
apps/pool/pool.c Normal file
View File

@@ -0,0 +1,582 @@
/**
* @file pool.c
* @brief Simple pool (billiards) game for ClaudeOS.
*
* Uses VGA mode 0x13 (320x200, 256 colors) via the graphics subsystem.
*
* Controls:
* Left/Right arrows (or A/D) - Aim the cue
* Up/Down arrows (or W/S) - Adjust shot power
* Space or Enter - Shoot
* Q or Escape - Quit
*
* Simplified 8-ball pool:
* - 1 cue ball (white) + 7 colored balls
* - Pot balls into the 6 pockets
* - Pot the cue ball = foul (ball resets)
*/
#include "syscalls.h"
/* ================================================================
* Fixed-point math (16.16 format)
* ================================================================ */
typedef int32_t fixed_t;
#define FP_SHIFT 16
#define FP_ONE (1 << FP_SHIFT)
#define FP_HALF (FP_ONE / 2)
#define INT_TO_FP(x) ((fixed_t)(x) << FP_SHIFT)
#define FP_TO_INT(x) ((int)((x) >> FP_SHIFT))
#define FP_MUL(a, b) ((fixed_t)(((int32_t)(a) * (int32_t)(b)) >> FP_SHIFT))
#define FP_DIV(a, b) ((fixed_t)(((int32_t)(a) << FP_SHIFT) / (b)))
/** Integer square root (for fixed-point magnitude). */
static uint32_t isqrt(uint32_t n) {
if (n == 0) return 0;
uint32_t x = n;
uint32_t y = (x + 1) / 2;
while (y < x) {
x = y;
y = (x + n / x) / 2;
}
return x;
}
static fixed_t fp_sqrt(fixed_t x) {
if (x <= 0) return 0;
return (fixed_t)isqrt((uint32_t)x << FP_SHIFT);
}
/* ================================================================
* Simple sin/cos lookup (256 entries for angle 0-255 == 0-360 deg)
* Values in 16.16 fixed point.
* Using a 64-entry quarter-wave table.
* ================================================================ */
/** Pre-computed sine table for angles 0..63 (quarter wave, 0 to PI/2).
* Values are 16.16 fixed-point. */
static const fixed_t sin_table_q[65] = {
0, 1608, 3216, 4821, 6424, 8022, 9616, 11204,
12785, 14359, 15924, 17479, 19024, 20557, 22078, 23586,
25080, 26558, 28020, 29466, 30893, 32303, 33692, 35062,
36410, 37736, 39040, 40320, 41576, 42806, 44011, 45190,
46341, 47464, 48559, 49624, 50660, 51665, 52639, 53581,
54491, 55368, 56212, 57022, 57798, 58538, 59244, 59914,
60547, 61145, 61705, 62228, 62714, 63162, 63572, 63944,
64277, 64571, 64827, 65043, 65220, 65358, 65457, 65516,
65536
};
/** Get sine for angle (0-255 maps to 0-360 degrees), returns 16.16 fixed. */
static fixed_t fp_sin(int angle) {
angle = angle & 255;
int quadrant = angle >> 6; /* 0-3 */
int idx = angle & 63;
fixed_t val;
switch (quadrant) {
case 0: val = sin_table_q[idx]; break;
case 1: val = sin_table_q[64 - idx]; break;
case 2: val = -sin_table_q[idx]; break;
case 3: val = -sin_table_q[64 - idx]; break;
default: val = 0; break;
}
return val;
}
static fixed_t fp_cos(int angle) {
return fp_sin(angle + 64);
}
/* ================================================================
* Game constants
* ================================================================ */
#define SCREEN_W 320
#define SCREEN_H 200
/* Table dimensions (inner playing area) */
#define TABLE_X 30
#define TABLE_Y 20
#define TABLE_W 260
#define TABLE_H 160
#define TABLE_RIGHT (TABLE_X + TABLE_W)
#define TABLE_BOTTOM (TABLE_Y + TABLE_H)
/* Bumper/rail width */
#define RAIL_W 6
/* Ball properties */
#define BALL_RADIUS 4
#define NUM_BALLS 8 /* 1 cue + 7 object balls */
/* Pocket properties */
#define POCKET_RADIUS 8
#define NUM_POCKETS 6
/* Physics */
#define FRICTION (FP_ONE - FP_ONE / 100) /* ~0.99 */
#define MIN_SPEED (FP_ONE / 8) /* Below this, stop the ball */
#define MAX_POWER INT_TO_FP(6)
/* Colors */
#define COL_FELT GFX_GREEN /* 2: green */
#define COL_RAIL GFX_BROWN /* 6: brown */
#define COL_POCKET GFX_BLACK /* 0: black */
#define COL_CUE_BALL GFX_WHITE /* 15: white */
#define COL_AIM GFX_LIGHT_GREY /* 7 */
#define COL_POWER GFX_LIGHT_RED /* 12 */
#define COL_TEXT GFX_WHITE /* 15 */
#define COL_BG GFX_DARK_GREY /* 8 */
/* Object ball colors */
static const uint32_t ball_colors[7] = {
GFX_YELLOW, /* Ball 1: Yellow */
GFX_BLUE, /* Ball 2: Blue */
GFX_RED, /* Ball 3: Red */
GFX_MAGENTA, /* Ball 4: Purple */
GFX_LIGHT_RED, /* Ball 5: Orange-ish */
GFX_LIGHT_GREEN, /* Ball 6: Light Green */
GFX_LIGHT_CYAN, /* Ball 7: Light Cyan */
};
/* ================================================================
* Game state
* ================================================================ */
typedef struct {
fixed_t x, y; /* Position (fixed-point) */
fixed_t vx, vy; /* Velocity (fixed-point) */
uint32_t color; /* Palette color index */
int active; /* 1 if on table, 0 if potted */
} ball_t;
typedef struct {
fixed_t x, y; /* Center position */
} pocket_t;
static ball_t balls[NUM_BALLS];
static pocket_t pockets[NUM_POCKETS];
static int aim_angle = 0; /* 0-255 */
static int shot_power = 3; /* 1-6 */
static int balls_moving = 0; /* Nonzero while physics is running */
static int score = 0;
static int game_over = 0;
static int foul = 0; /* Set when cue ball potted */
/* ================================================================
* Initialization
* ================================================================ */
static void init_pockets(void) {
/* 6 pockets: 4 corners + 2 side midpoints */
pockets[0] = (pocket_t){INT_TO_FP(TABLE_X), INT_TO_FP(TABLE_Y)};
pockets[1] = (pocket_t){INT_TO_FP(TABLE_X + TABLE_W/2), INT_TO_FP(TABLE_Y)};
pockets[2] = (pocket_t){INT_TO_FP(TABLE_RIGHT), INT_TO_FP(TABLE_Y)};
pockets[3] = (pocket_t){INT_TO_FP(TABLE_X), INT_TO_FP(TABLE_BOTTOM)};
pockets[4] = (pocket_t){INT_TO_FP(TABLE_X + TABLE_W/2), INT_TO_FP(TABLE_BOTTOM)};
pockets[5] = (pocket_t){INT_TO_FP(TABLE_RIGHT), INT_TO_FP(TABLE_BOTTOM)};
}
static void init_balls(void) {
/* Cue ball on left side */
balls[0].x = INT_TO_FP(TABLE_X + TABLE_W / 4);
balls[0].y = INT_TO_FP(TABLE_Y + TABLE_H / 2);
balls[0].vx = 0;
balls[0].vy = 0;
balls[0].color = COL_CUE_BALL;
balls[0].active = 1;
/* Object balls in a triangle formation on right side */
fixed_t start_x = INT_TO_FP(TABLE_X + TABLE_W * 3 / 4);
fixed_t start_y = INT_TO_FP(TABLE_Y + TABLE_H / 2);
int ball_idx = 1;
/* Row 1: 1 ball */
balls[ball_idx].x = start_x;
balls[ball_idx].y = start_y;
balls[ball_idx].color = ball_colors[0];
balls[ball_idx].active = 1;
balls[ball_idx].vx = 0;
balls[ball_idx].vy = 0;
ball_idx++;
/* Row 2: 2 balls */
for (int i = 0; i < 2 && ball_idx < NUM_BALLS; i++) {
balls[ball_idx].x = start_x + INT_TO_FP(BALL_RADIUS * 2 + 1);
balls[ball_idx].y = start_y + INT_TO_FP((i * 2 - 1) * (BALL_RADIUS + 1));
balls[ball_idx].color = ball_colors[ball_idx - 1];
balls[ball_idx].active = 1;
balls[ball_idx].vx = 0;
balls[ball_idx].vy = 0;
ball_idx++;
}
/* Row 3: 3 balls */
for (int i = 0; i < 3 && ball_idx < NUM_BALLS; i++) {
balls[ball_idx].x = start_x + INT_TO_FP(BALL_RADIUS * 4 + 2);
balls[ball_idx].y = start_y + INT_TO_FP((i - 1) * (BALL_RADIUS * 2 + 1));
balls[ball_idx].color = ball_colors[ball_idx - 1];
balls[ball_idx].active = 1;
balls[ball_idx].vx = 0;
balls[ball_idx].vy = 0;
ball_idx++;
}
/* Row 4: remaining balls */
for (int i = 0; ball_idx < NUM_BALLS; i++) {
balls[ball_idx].x = start_x + INT_TO_FP(BALL_RADIUS * 6 + 3);
balls[ball_idx].y = start_y + INT_TO_FP((i * 2 - 1) * (BALL_RADIUS + 1));
balls[ball_idx].color = ball_colors[ball_idx - 1];
balls[ball_idx].active = 1;
balls[ball_idx].vx = 0;
balls[ball_idx].vy = 0;
ball_idx++;
}
}
/* ================================================================
* Physics
* ================================================================ */
static void check_wall_collisions(ball_t *b) {
fixed_t left = INT_TO_FP(TABLE_X + RAIL_W + BALL_RADIUS);
fixed_t right = INT_TO_FP(TABLE_RIGHT - RAIL_W - BALL_RADIUS);
fixed_t top = INT_TO_FP(TABLE_Y + RAIL_W + BALL_RADIUS);
fixed_t bottom = INT_TO_FP(TABLE_BOTTOM - RAIL_W - BALL_RADIUS);
if (b->x < left) { b->x = left; b->vx = -b->vx; }
if (b->x > right) { b->x = right; b->vx = -b->vx; }
if (b->y < top) { b->y = top; b->vy = -b->vy; }
if (b->y > bottom) { b->y = bottom; b->vy = -b->vy; }
}
static void check_pocket(ball_t *b, int ball_idx) {
for (int p = 0; p < NUM_POCKETS; p++) {
fixed_t dx = b->x - pockets[p].x;
fixed_t dy = b->y - pockets[p].y;
fixed_t dist_sq = FP_MUL(dx, dx) + FP_MUL(dy, dy);
fixed_t pocket_r = INT_TO_FP(POCKET_RADIUS);
if (dist_sq < FP_MUL(pocket_r, pocket_r)) {
if (ball_idx == 0) {
/* Cue ball potted = foul */
foul = 1;
b->x = INT_TO_FP(TABLE_X + TABLE_W / 4);
b->y = INT_TO_FP(TABLE_Y + TABLE_H / 2);
b->vx = 0;
b->vy = 0;
} else {
b->active = 0;
b->vx = 0;
b->vy = 0;
score++;
}
return;
}
}
}
static void check_ball_collisions(void) {
for (int i = 0; i < NUM_BALLS; i++) {
if (!balls[i].active) continue;
for (int j = i + 1; j < NUM_BALLS; j++) {
if (!balls[j].active) continue;
fixed_t dx = balls[j].x - balls[i].x;
fixed_t dy = balls[j].y - balls[i].y;
fixed_t dist_sq = FP_MUL(dx, dx) + FP_MUL(dy, dy);
fixed_t min_dist = INT_TO_FP(BALL_RADIUS * 2);
fixed_t min_dist_sq = FP_MUL(min_dist, min_dist);
if (dist_sq < min_dist_sq && dist_sq > 0) {
/* Elastic collision */
fixed_t dist = fp_sqrt(dist_sq);
if (dist == 0) dist = 1;
/* Normal vector */
fixed_t nx = FP_DIV(dx, dist);
fixed_t ny = FP_DIV(dy, dist);
/* Relative velocity along normal */
fixed_t dvx = balls[i].vx - balls[j].vx;
fixed_t dvy = balls[i].vy - balls[j].vy;
fixed_t dvn = FP_MUL(dvx, nx) + FP_MUL(dvy, ny);
/* Only resolve if balls are approaching */
if (dvn <= 0) continue;
/* Update velocities (equal mass elastic collision) */
balls[i].vx -= FP_MUL(dvn, nx);
balls[i].vy -= FP_MUL(dvn, ny);
balls[j].vx += FP_MUL(dvn, nx);
balls[j].vy += FP_MUL(dvn, ny);
/* Separate balls */
fixed_t overlap = min_dist - dist;
if (overlap > 0) {
fixed_t sep = overlap / 2 + FP_ONE / 4;
balls[i].x -= FP_MUL(sep, nx);
balls[i].y -= FP_MUL(sep, ny);
balls[j].x += FP_MUL(sep, nx);
balls[j].y += FP_MUL(sep, ny);
}
}
}
}
}
static void update_physics(void) {
balls_moving = 0;
for (int i = 0; i < NUM_BALLS; i++) {
if (!balls[i].active) continue;
/* Apply velocity */
balls[i].x += balls[i].vx;
balls[i].y += balls[i].vy;
/* Apply friction */
balls[i].vx = FP_MUL(balls[i].vx, FRICTION);
balls[i].vy = FP_MUL(balls[i].vy, FRICTION);
/* Check if ball is still moving */
fixed_t speed_sq = FP_MUL(balls[i].vx, balls[i].vx) +
FP_MUL(balls[i].vy, balls[i].vy);
if (speed_sq < FP_MUL(MIN_SPEED, MIN_SPEED)) {
balls[i].vx = 0;
balls[i].vy = 0;
} else {
balls_moving = 1;
}
/* Wall collisions */
check_wall_collisions(&balls[i]);
/* Pocket check */
check_pocket(&balls[i], i);
}
/* Ball-ball collisions */
check_ball_collisions();
}
/* ================================================================
* Drawing
* ================================================================ */
static void draw_table(void) {
/* Background */
gfx_clear(COL_BG);
/* Rail (border) */
gfx_rect_t rail = {TABLE_X - RAIL_W, TABLE_Y - RAIL_W,
TABLE_W + RAIL_W * 2, TABLE_H + RAIL_W * 2, COL_RAIL};
gfx_fill_rect(&rail);
/* Felt (playing surface) */
gfx_rect_t felt = {TABLE_X, TABLE_Y, TABLE_W, TABLE_H, COL_FELT};
gfx_fill_rect(&felt);
/* Pockets */
for (int i = 0; i < NUM_POCKETS; i++) {
gfx_circle_t pocket = {
(uint32_t)FP_TO_INT(pockets[i].x),
(uint32_t)FP_TO_INT(pockets[i].y),
POCKET_RADIUS, COL_POCKET
};
gfx_circle(&pocket);
}
}
static void draw_balls(void) {
for (int i = 0; i < NUM_BALLS; i++) {
if (!balls[i].active) continue;
gfx_circle_t c = {
(uint32_t)FP_TO_INT(balls[i].x),
(uint32_t)FP_TO_INT(balls[i].y),
BALL_RADIUS,
balls[i].color
};
gfx_circle(&c);
}
}
static void draw_aim(void) {
if (balls_moving || !balls[0].active) return;
/* Draw aim line from cue ball */
int cx = FP_TO_INT(balls[0].x);
int cy = FP_TO_INT(balls[0].y);
int len = 20 + shot_power * 5;
int ex = cx + FP_TO_INT(FP_MUL(INT_TO_FP(len), fp_cos(aim_angle)));
int ey = cy + FP_TO_INT(FP_MUL(INT_TO_FP(len), fp_sin(aim_angle)));
gfx_line_t line = {(uint32_t)cx, (uint32_t)cy,
(uint32_t)ex, (uint32_t)ey, COL_AIM};
gfx_line(&line);
}
static void draw_hud(void) {
/* Score */
char score_str[32] = "Score: ";
int pos = 7;
if (score == 0) {
score_str[pos++] = '0';
} else {
char tmp[8];
int ti = 0;
int s = score;
while (s > 0) { tmp[ti++] = '0' + (char)(s % 10); s /= 10; }
while (ti > 0) score_str[pos++] = tmp[--ti];
}
score_str[pos] = '\0';
/* Use pixels directly for HUD text at top */
/* Score on left side of top bar */
int tx = 2;
int ty = 2;
for (int i = 0; score_str[i]; i++) {
gfx_pixel((uint32_t)(tx + i * 6), (uint32_t)ty, COL_TEXT);
/* Draw each character as small 4x5 digits — simplified */
}
/* Power indicator bar */
gfx_rect_t power_bg = {2, SCREEN_H - 12, 60, 8, COL_BG};
gfx_fill_rect(&power_bg);
gfx_rect_t power_bar = {2, SCREEN_H - 12, (uint32_t)(shot_power * 10), 8, COL_POWER};
gfx_fill_rect(&power_bar);
/* Foul indicator */
if (foul) {
gfx_rect_t foul_bg = {SCREEN_W / 2 - 20, SCREEN_H - 12, 40, 8, GFX_RED};
gfx_fill_rect(&foul_bg);
}
/* Win message */
if (score >= 7) {
game_over = 1;
gfx_rect_t win_bg = {SCREEN_W/2 - 30, SCREEN_H/2 - 10, 60, 20, GFX_BLUE};
gfx_fill_rect(&win_bg);
}
}
static void draw_frame(void) {
draw_table();
draw_balls();
draw_aim();
draw_hud();
}
/* ================================================================
* Input handling
* ================================================================ */
static void shoot(void) {
if (balls_moving || !balls[0].active) return;
fixed_t power = INT_TO_FP(shot_power);
balls[0].vx = FP_MUL(power, fp_cos(aim_angle));
balls[0].vy = FP_MUL(power, fp_sin(aim_angle));
foul = 0;
}
static int handle_input(void) {
char c;
int32_t n = read(0, &c, 1);
if (n <= 0) return 0;
switch (c) {
case 'q':
case 'Q':
case 27: /* Escape */
return 1; /* Quit */
case 'a':
case 'A':
aim_angle = (aim_angle - 4) & 255;
break;
case 'd':
case 'D':
aim_angle = (aim_angle + 4) & 255;
break;
case 'w':
case 'W':
if (shot_power < 6) shot_power++;
break;
case 's':
case 'S':
if (shot_power > 1) shot_power--;
break;
case ' ':
case '\n':
case '\r':
shoot();
break;
default:
break;
}
return 0;
}
/* ================================================================
* Main
* ================================================================ */
int main(void) {
/* Enter graphics mode */
gfx_enter();
/* Initialize game */
init_pockets();
init_balls();
/* Main game loop */
while (!game_over) {
/* Handle input */
if (handle_input()) break;
/* Update physics */
if (balls_moving) {
update_physics();
}
/* Draw everything */
draw_frame();
/* Frame delay (cooperative yield) */
for (int i = 0; i < 2; i++) yield();
}
/* Wait a moment on game over */
if (game_over) {
for (int i = 0; i < 200; i++) yield();
}
/* Return to text mode */
gfx_leave();
puts("Game over! Final score: ");
/* Print score */
char tmp[8];
int ti = 0;
int s = score;
if (s == 0) { putchar('0'); }
else {
while (s > 0) { tmp[ti++] = '0' + (char)(s % 10); s /= 10; }
while (ti > 0) putchar(tmp[--ti]);
}
puts("/7\n");
return 0;
}

View File

@@ -56,21 +56,177 @@ static char *find_space(char *s) {
return s;
}
/** String length. */
static int slen(const char *s) {
int n = 0;
while (s[n]) n++;
return n;
}
/** Resolve an absolute path from cwd + a possibly relative path.
* Handles '..', '.', and trailing slashes. Result is always absolute. */
static void resolve_path(const char *cwd, const char *arg, char *out, int out_sz) {
char tmp[256];
int ti = 0;
/* If arg is absolute, start from it; otherwise prepend cwd */
if (arg[0] == '/') {
/* copy arg into tmp */
for (int i = 0; arg[i] && ti < 255; i++)
tmp[ti++] = arg[i];
} else {
/* copy cwd */
for (int i = 0; cwd[i] && ti < 255; i++)
tmp[ti++] = cwd[i];
/* ensure separator */
if (ti > 0 && tmp[ti - 1] != '/' && ti < 255)
tmp[ti++] = '/';
/* append arg */
for (int i = 0; arg[i] && ti < 255; i++)
tmp[ti++] = arg[i];
}
tmp[ti] = '\0';
/* Now canonicalize: split on '/' and process each component */
/* Stack of component start offsets */
int comp_starts[64];
int comp_lens[64];
int depth = 0;
int i = 0;
while (tmp[i]) {
/* skip slashes */
while (tmp[i] == '/') i++;
if (!tmp[i]) break;
/* find end of component */
int start = i;
while (tmp[i] && tmp[i] != '/') i++;
int len = i - start;
if (len == 1 && tmp[start] == '.') {
/* current dir: skip */
continue;
} else if (len == 2 && tmp[start] == '.' && tmp[start + 1] == '.') {
/* parent dir: pop */
if (depth > 0) depth--;
} else {
/* normal component: push */
if (depth < 64) {
comp_starts[depth] = start;
comp_lens[depth] = len;
depth++;
}
}
}
/* Build output */
int oi = 0;
if (depth == 0) {
if (oi < out_sz - 1) out[oi++] = '/';
} else {
for (int d = 0; d < depth && oi < out_sz - 2; d++) {
out[oi++] = '/';
for (int j = 0; j < comp_lens[d] && oi < out_sz - 1; j++)
out[oi++] = tmp[comp_starts[d] + j];
}
}
out[oi] = '\0';
}
/** Built-in: cd <path> */
static void builtin_cd(char *arg) {
if (!arg || !*arg) {
arg = "/";
}
/* Update CWD environment variable */
setenv("CWD", arg);
/* Verify it was set */
/* Get current working directory */
char cwd[128];
if (getenv("CWD", cwd, sizeof(cwd)) >= 0) {
puts("cd: ");
puts(cwd);
putchar('\n');
if (getenv("CWD", cwd, sizeof(cwd)) < 0) {
strcpy(cwd, "/");
}
/* Resolve the path (handles relative, .., etc.) */
char resolved[256];
resolve_path(cwd, arg, resolved, sizeof(resolved));
/* Validate: check if the directory exists by trying to readdir it.
* Root "/" always exists. For others, readdir index 0 must not fail
* with -1 unless the dir is simply empty, so we also accept stat-like
* checks. A simple heuristic: if readdir returns -1 for index 0 AND
* the path is not "/", we check if the *parent* can list it. */
if (resolved[0] == '/' && resolved[1] != '\0') {
/* Try listing the first entry; if the path is a valid directory,
* readdir(path, 0, ...) returns >= 0 even for an empty dir
* (it returns -1 meaning "end"), BUT if the path doesn't exist
* at all, the VFS resolve_path will fail and readdir returns -1.
*
* Better approach: try to readdir the parent and look for this
* entry among its children. */
char name[128];
int32_t type = readdir(resolved, 0, name);
/* type >= 0 means there's at least one entry => directory exists.
* type == -1 could mean empty dir or non-existent.
* For empty dirs: the directory itself was resolved successfully.
* For non-existent: the VFS resolution failed.
*
* Our VFS readdir calls resolve_path internally. If the path
* doesn't exist, it returns -1. If it exists but is empty,
* it also returns -1. We need to distinguish these.
* Use a stat-like approach: try to open the path. */
/* Actually the simplest check: try readdir on the *parent* and
* see if our target component exists as a directory entry. */
char parent[256];
char target[128];
/* Split resolved into parent + target */
int rlen = slen(resolved);
int last_slash = 0;
for (int i = 0; i < rlen; i++) {
if (resolved[i] == '/') last_slash = i;
}
if (last_slash == 0) {
/* Parent is root */
parent[0] = '/';
parent[1] = '\0';
strcpy(target, resolved + 1);
} else {
memcpy(parent, resolved, (uint32_t)last_slash);
parent[last_slash] = '\0';
strcpy(target, resolved + last_slash + 1);
}
/* Search parent directory for target */
int found = 0;
uint32_t idx = 0;
int32_t etype;
while ((etype = readdir(parent, idx, name)) >= 0) {
if (strcmp(name, target) == 0) {
if (etype == 2) {
found = 1; /* It's a directory */
} else {
puts("cd: ");
puts(resolved);
puts(": not a directory\n");
return;
}
break;
}
idx++;
}
if (!found) {
puts("cd: ");
puts(resolved);
puts(": no such directory\n");
return;
}
}
/* Update CWD environment variable */
setenv("CWD", resolved);
}
/** Built-in: env - print all known env vars. */
@@ -104,6 +260,16 @@ static void builtin_help(void) {
puts("External commands are loaded from initrd.\n");
}
/** Check if a string looks like a path (contains / or starts with .) */
static int looks_like_path(const char *s) {
if (!s || !*s) return 0;
if (s[0] == '.' || s[0] == '/') return 1;
for (int i = 0; s[i]; i++) {
if (s[i] == '/') return 1;
}
return 0;
}
/** Execute an external command via fork+exec. */
static void run_command(const char *cmd, const char *arg) {
int32_t pid = fork();
@@ -115,7 +281,19 @@ static void run_command(const char *cmd, const char *arg) {
if (pid == 0) {
/* Child: set ARG1 if there's an argument */
if (arg && *arg) {
setenv("ARG1", arg);
/* If the argument looks like a path and isn't absolute,
* resolve it relative to CWD so apps get absolute paths */
if (looks_like_path(arg) && arg[0] != '/') {
char cwd[128];
if (getenv("CWD", cwd, sizeof(cwd)) < 0) {
strcpy(cwd, "/");
}
char resolved_arg[256];
resolve_path(cwd, arg, resolved_arg, sizeof(resolved_arg));
setenv("ARG1", resolved_arg);
} else {
setenv("ARG1", arg);
}
} else {
setenv("ARG1", "");
}

236
apps/wget/wget.c Normal file
View File

@@ -0,0 +1,236 @@
/**
* @file wget.c
* @brief Simple HTTP client for ClaudeOS.
*
* Downloads a resource from an HTTP server using a TCP connection.
* Only supports HTTP/1.0 GET with IP addresses (no DNS).
*
* Usage:
* wget <ip>[:<port>]/<path>
* wget <ip> - fetches /
*
* Examples:
* wget 10.0.2.2/index.html
* wget 10.0.2.2:8080/api/data
* wget 192.168.1.1
*/
#include "syscalls.h"
typedef unsigned char uint8_t;
/**
* Parse a decimal number from a string.
* Advances *s past the digits.
*/
static uint32_t parse_uint(const char **s) {
uint32_t val = 0;
while (**s >= '0' && **s <= '9') {
val = val * 10 + (uint32_t)(**s - '0');
(*s)++;
}
return val;
}
/**
* Parse an IPv4 dotted-decimal address into a 32-bit host-order integer.
* Returns 0 on failure.
*/
static uint32_t parse_ip(const char **s) {
uint32_t ip = 0;
for (int i = 0; i < 4; i++) {
uint32_t octet = parse_uint(s);
if (octet > 255) return 0;
ip = (ip << 8) | octet;
if (i < 3) {
if (**s != '.') return 0;
(*s)++;
}
}
return ip;
}
/**
* Print a decimal number.
*/
static void print_dec(uint32_t val) {
char buf[12];
int i = 0;
if (val == 0) { putchar('0'); return; }
while (val > 0) {
buf[i++] = '0' + (char)(val % 10);
val /= 10;
}
while (i > 0) putchar(buf[--i]);
}
/**
* Build the HTTP GET request.
* Returns length of the request string.
*/
static uint32_t build_request(char *buf, uint32_t bufsize,
const char *host, const char *path) {
uint32_t pos = 0;
/* "GET <path> HTTP/1.0\r\nHost: <host>\r\nConnection: close\r\n\r\n" */
const char *parts[] = {
"GET ", path, " HTTP/1.0\r\nHost: ", host,
"\r\nConnection: close\r\n\r\n", (const char *)0
};
for (int i = 0; parts[i]; i++) {
const char *s = parts[i];
while (*s && pos < bufsize - 1) {
buf[pos++] = *s++;
}
}
buf[pos] = '\0';
return pos;
}
/**
* Format an IP address as a dotted-decimal string.
*/
static void ip_to_str(uint32_t ip, char *buf) {
int pos = 0;
for (int i = 3; i >= 0; i--) {
uint32_t octet = (ip >> (i * 8)) & 0xFF;
char tmp[4];
int ti = 0;
if (octet == 0) { tmp[ti++] = '0'; }
else {
while (octet > 0) { tmp[ti++] = '0' + (char)(octet % 10); octet /= 10; }
}
while (ti > 0) buf[pos++] = tmp[--ti];
if (i > 0) buf[pos++] = '.';
}
buf[pos] = '\0';
}
int main(void) {
char url[256];
/* Get URL from ARG1 */
if (getenv("ARG1", url, sizeof(url)) < 0 || url[0] == '\0') {
puts("Usage: wget <ip>[:<port>]/<path>\n");
puts(" e.g. wget 10.0.2.2/index.html\n");
return 1;
}
/* Parse: skip optional "http://" prefix */
const char *p = url;
if (strncmp(p, "http://", 7) == 0) {
p += 7;
}
/* Parse IP address */
uint32_t ip = parse_ip(&p);
if (ip == 0) {
puts("wget: invalid IP address\n");
return 1;
}
/* Parse optional port */
uint32_t port = 80;
if (*p == ':') {
p++;
port = parse_uint(&p);
if (port == 0 || port > 65535) {
puts("wget: invalid port\n");
return 1;
}
}
/* Parse path (default to /) */
const char *path = "/";
if (*p == '/') {
path = p;
} else if (*p != '\0') {
puts("wget: invalid URL format\n");
return 1;
}
/* Build host string for Host header */
char host_str[64];
ip_to_str(ip, host_str);
/* Print what we're doing */
puts("Connecting to ");
puts(host_str);
putchar(':');
print_dec(port);
puts(path);
puts("...\n");
/* Create TCP socket */
int32_t sockfd = socket(SOCK_TCP);
if (sockfd < 0) {
puts("wget: failed to create socket\n");
return 1;
}
/* Connect */
if (connect(sockfd, ip, port) < 0) {
puts("wget: connect failed\n");
return 1;
}
/* Wait for connection to establish (poll with yield) */
int timeout = 500; /* ~5 seconds at ~100 yields/sec */
while (timeout > 0) {
int32_t state = sockstate(sockfd);
if (state == TCP_STATE_ESTABLISHED) break;
if (state == TCP_STATE_CLOSED) {
puts("wget: connection refused\n");
return 1;
}
yield();
timeout--;
}
if (timeout <= 0) {
puts("wget: connection timed out\n");
return 1;
}
puts("Connected.\n");
/* Build and send HTTP request */
char req[512];
uint32_t req_len = build_request(req, sizeof(req), host_str, path);
int32_t sent = net_send(sockfd, req, req_len);
if (sent < 0) {
puts("wget: send failed\n");
return 1;
}
/* Receive response */
puts("--- Response ---\n");
char buf[512];
int done = 0;
int recv_timeout = 1000;
while (!done && recv_timeout > 0) {
int32_t n = net_recv(sockfd, buf, sizeof(buf) - 1);
if (n > 0) {
buf[n] = '\0';
write(1, buf, (uint32_t)n);
recv_timeout = 200; /* Reset timeout on data */
} else if (n < 0) {
/* Connection closed or error */
done = 1;
} else {
/* No data yet */
yield();
recv_timeout--;
}
}
puts("\n--- End ---\n");
/* Close socket - we use close() which goes through SYS_CLOSE.
* For sockets, we should ideally have a socket-specific close,
* but for simplicity, the socket will be cleaned up on process exit. */
return 0;
}

73
build.log Normal file
View File

@@ -0,0 +1,73 @@
[ 2%] Building user-mode applications
Building app: arp
Built: /workspaces/claude-os/build/apps_bin/arp (214 bytes)
Building app: cat
Built: /workspaces/claude-os/build/apps_bin/cat (310 bytes)
Building app: dhcp
Built: /workspaces/claude-os/build/apps_bin/dhcp (219 bytes)
Building app: diskpart
/usr/bin/ld: warning: /workspaces/claude-os/build/apps_bin/diskpart.elf has a LOAD segment with RWX permissions
Built: /workspaces/claude-os/build/apps_bin/diskpart (8406 bytes)
Building app: env-test
/usr/bin/ld: warning: /workspaces/claude-os/build/apps_bin/env-test.elf has a LOAD segment with RWX permissions
Built: /workspaces/claude-os/build/apps_bin/env-test (389 bytes)
Building app: fork-test
Built: /workspaces/claude-os/build/apps_bin/fork-test (132 bytes)
Building app: ftp
Built: /workspaces/claude-os/build/apps_bin/ftp (3406 bytes)
Building app: hello-world
Built: /workspaces/claude-os/build/apps_bin/hello-world (49 bytes)
Building app: ip
Built: /workspaces/claude-os/build/apps_bin/ip (3695 bytes)
Building app: ls
Built: /workspaces/claude-os/build/apps_bin/ls (250 bytes)
Building app: minigolf
/workspaces/claude-os/apps/minigolf/minigolf.c:29:17: warning: unused function 'isqrt' [-Wunused-function]
29 | static uint32_t isqrt(uint32_t n) {
| ^~~~~
1 warning generated.
/usr/bin/ld: warning: /workspaces/claude-os/build/apps_bin/minigolf.elf has a LOAD segment with RWX permissions
Built: /workspaces/claude-os/build/apps_bin/minigolf (3456 bytes)
Building app: mkfs.fat32
/workspaces/claude-os/apps/mkfs.fat32/mkfs.fat32.c:56:13: warning: unused function 'print_hex' [-Wunused-function]
56 | static void print_hex(uint32_t val) {
| ^~~~~~~~~
1 warning generated.
/usr/bin/ld: warning: /workspaces/claude-os/build/apps_bin/mkfs.fat32.elf has a LOAD segment with RWX permissions
Built: /workspaces/claude-os/build/apps_bin/mkfs.fat32 (5121 bytes)
Building app: mount
Built: /workspaces/claude-os/build/apps_bin/mount (992 bytes)
Building app: pool
/usr/bin/ld: warning: /workspaces/claude-os/build/apps_bin/pool.elf has a LOAD segment with RWX permissions
Built: /workspaces/claude-os/build/apps_bin/pool (2936 bytes)
Building app: sh
/workspaces/claude-os/apps/sh/sh.c:167:17: warning: unused variable 'type' [-Wunused-variable]
167 | int32_t type = readdir(resolved, 0, name);
| ^~~~
1 warning generated.
Built: /workspaces/claude-os/build/apps_bin/sh (3428 bytes)
Building app: wget
Built: /workspaces/claude-os/build/apps_bin/wget (2193 bytes)
[ 2%] Built target apps
[ 4%] Generating CPIO initial ramdisk
Generated initrd: 37232 bytes
[ 4%] Built target initrd
[ 97%] Built target kernel
[100%] Generating bootable ISO image
xorriso 1.5.6 : RockRidge filesystem manipulator, libburnia project.
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.NLidpF'
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 : 64.38% done
ISO image produced: 6058 sectors
Written to medium : 6058 sectors at LBA 0
Writing to 'stdio:/workspaces/claude-os/release/claude-os.iso' completed successfully.
[100%] Built target iso

View File

@@ -23,6 +23,17 @@ add_executable(kernel
sysfs.c
ide.c
mbr.c
fat32.c
floppy.c
ne2000.c
e3c509.c
ethernet.c
ipv4.c
arp.c
dhcp.c
udp.c
tcp.c
graphics.c
env.c
keyboard.c
interrupts.S

382
src/arp.c Normal file
View File

@@ -0,0 +1,382 @@
/**
* @file arp.c
* @brief Address Resolution Protocol (ARP) implementation.
*
* Maintains an ARP cache mapping IPv4 addresses to Ethernet MAC
* addresses. Sends ARP requests and processes ARP replies.
* Also responds to incoming ARP requests for our own IP addresses.
*
* The ARP table is exposed via sysfs at /sys/arp.
*/
#include "arp.h"
#include "ethernet.h"
#include "ipv4.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
* ================================================================ */
/** ARP cache table. */
static arp_entry_t arp_table[ARP_TABLE_SIZE];
/** Number of active entries. */
static uint32_t arp_count = 0;
/* ================================================================
* ARP cache management
* ================================================================ */
/**
* Find an ARP entry by IP address.
* @return Pointer to entry, or NULL if not found.
*/
static arp_entry_t *arp_find(uint32_t ip) {
for (uint32_t i = 0; i < ARP_TABLE_SIZE; i++) {
if (arp_table[i].state != ARP_STATE_FREE &&
arp_table[i].ip_addr == ip) {
return &arp_table[i];
}
}
return NULL;
}
/**
* Allocate a new ARP entry.
* Reuses a free slot, or evicts the oldest entry.
*/
static arp_entry_t *arp_alloc(void) {
/* Look for a free slot */
for (uint32_t i = 0; i < ARP_TABLE_SIZE; i++) {
if (arp_table[i].state == ARP_STATE_FREE) {
return &arp_table[i];
}
}
/* Evict the oldest entry (lowest timestamp) */
arp_entry_t *oldest = &arp_table[0];
for (uint32_t i = 1; i < ARP_TABLE_SIZE; i++) {
if (arp_table[i].timestamp < oldest->timestamp) {
oldest = &arp_table[i];
}
}
return oldest;
}
/* ================================================================
* ARP send
* ================================================================ */
/**
* Send an ARP packet.
*/
static int arp_send_packet(uint32_t iface_idx, uint16_t operation,
const uint8_t *target_mac, uint32_t target_ip) {
eth_iface_t *iface = ethernet_get_iface(iface_idx);
if (!iface || !iface->active) return -1;
arp_packet_t pkt;
pkt.hw_type = htons(ARP_HW_ETHER);
pkt.proto_type = htons(ETHERTYPE_IPV4);
pkt.hw_len = 6;
pkt.proto_len = 4;
pkt.operation = htons(operation);
/* Sender: our MAC and IP */
memcpy(pkt.sender_mac, iface->mac, 6);
pkt.sender_ip = htonl(iface->ip_addr);
/* Target */
memcpy(pkt.target_mac, target_mac, 6);
pkt.target_ip = htonl(target_ip);
/* For ARP requests, broadcast; for replies, send directly */
const uint8_t *dst_mac;
if (operation == ARP_OP_REQUEST) {
static const uint8_t bcast[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
dst_mac = bcast;
} else {
dst_mac = target_mac;
}
return ethernet_send(iface, dst_mac, ETHERTYPE_ARP,
&pkt, ARP_PACKET_SIZE);
}
/* ================================================================
* Public API
* ================================================================ */
int arp_lookup(uint32_t ip, uint8_t *mac) {
arp_entry_t *entry = arp_find(ip);
if (entry && entry->state == ARP_STATE_RESOLVED) {
memcpy(mac, entry->mac, 6);
return 0;
}
return -1;
}
int arp_request(uint32_t iface_idx, uint32_t target_ip) {
static const uint8_t zero_mac[6] = {0, 0, 0, 0, 0, 0};
/* Create an incomplete entry if we don't have one */
arp_entry_t *entry = arp_find(target_ip);
if (!entry) {
entry = arp_alloc();
memset(entry, 0, sizeof(arp_entry_t));
entry->ip_addr = target_ip;
entry->state = ARP_STATE_INCOMPLETE;
entry->iface_idx = (uint8_t)iface_idx;
entry->timestamp = 0; /* will be updated on reply */
arp_count++;
}
return arp_send_packet(iface_idx, ARP_OP_REQUEST, zero_mac, target_ip);
}
int arp_resolve(uint32_t iface_idx, uint32_t ip, uint8_t *mac) {
/* Broadcast address — no ARP needed */
if (ip == 0xFFFFFFFF) {
memset(mac, 0xFF, 6);
return 0;
}
/* Check cache */
if (arp_lookup(ip, mac) == 0) return 0;
/* Send ARP request */
arp_request(iface_idx, ip);
return -1;
}
void arp_receive(const void *data, uint32_t len, uint32_t iface_idx) {
if (len < ARP_PACKET_SIZE) return;
const arp_packet_t *pkt = (const arp_packet_t *)data;
/* Validate: must be Ethernet/IPv4 */
if (ntohs(pkt->hw_type) != ARP_HW_ETHER) return;
if (ntohs(pkt->proto_type) != ETHERTYPE_IPV4) return;
if (pkt->hw_len != 6 || pkt->proto_len != 4) return;
uint32_t sender_ip = ntohl(pkt->sender_ip);
uint32_t target_ip = ntohl(pkt->target_ip);
uint16_t operation = ntohs(pkt->operation);
/* Update or create ARP cache entry for sender */
arp_entry_t *entry = arp_find(sender_ip);
if (entry) {
memcpy(entry->mac, pkt->sender_mac, 6);
entry->state = ARP_STATE_RESOLVED;
entry->iface_idx = (uint8_t)iface_idx;
} else {
entry = arp_alloc();
entry->ip_addr = sender_ip;
memcpy(entry->mac, pkt->sender_mac, 6);
entry->state = ARP_STATE_RESOLVED;
entry->iface_idx = (uint8_t)iface_idx;
entry->timestamp = 0;
arp_count++;
}
/* Check if this ARP is targeted at us */
eth_iface_t *iface = ethernet_get_iface(iface_idx);
if (!iface || iface->ip_addr == 0) return;
if (target_ip == iface->ip_addr && operation == ARP_OP_REQUEST) {
/* Send ARP reply */
arp_send_packet(iface_idx, ARP_OP_REPLY,
pkt->sender_mac, sender_ip);
}
}
int arp_add_static(uint32_t ip, const uint8_t *mac, uint32_t iface_idx) {
arp_entry_t *entry = arp_find(ip);
if (!entry) {
entry = arp_alloc();
if (!entry) return -1;
arp_count++;
}
entry->ip_addr = ip;
memcpy(entry->mac, mac, 6);
entry->state = ARP_STATE_RESOLVED;
entry->iface_idx = (uint8_t)iface_idx;
entry->timestamp = 0;
return 0;
}
const arp_entry_t *arp_get_entry(uint32_t index) {
uint32_t count = 0;
for (uint32_t i = 0; i < ARP_TABLE_SIZE; i++) {
if (arp_table[i].state == ARP_STATE_FREE) continue;
if (count == index) return &arp_table[i];
count++;
}
return NULL;
}
uint32_t arp_get_count(void) {
return arp_count;
}
/* ================================================================
* Sysfs interface: /sys/arp
*
* Layout:
* /sys/arp/
* table - ARP table in human-readable format
* ================================================================ */
/**
* Format a MAC address as "XX:XX:XX:XX:XX:XX".
*/
static void mac_to_str(const uint8_t *mac, char *buf) {
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] = '\0';
}
/**
* Format an IPv4 address (host byte order) as dotted decimal.
*/
static int ip_to_str(uint32_t ip, char *buf, uint32_t size) {
int pos = 0;
for (int i = 0; i < 4; i++) {
if (i > 0 && pos < (int)size - 1) buf[pos++] = '.';
uint8_t octet = (uint8_t)((ip >> (24 - i * 8)) & 0xFF);
if (octet >= 100 && pos < (int)size - 3) {
buf[pos++] = (char)('0' + octet / 100);
buf[pos++] = (char)('0' + (octet % 100) / 10);
buf[pos++] = (char)('0' + octet % 10);
} else if (octet >= 10 && pos < (int)size - 2) {
buf[pos++] = (char)('0' + octet / 10);
buf[pos++] = (char)('0' + octet % 10);
} else if (pos < (int)size - 1) {
buf[pos++] = (char)('0' + octet);
}
}
if (pos < (int)size) buf[pos] = '\0';
return pos;
}
/**
* Append a string to buf at position *pos.
*/
static void append_str(char *buf, uint32_t size, int *pos, const char *s) {
while (*s && *pos < (int)size - 1) buf[(*pos)++] = *s++;
}
static int arp_sysfs_list(void *ctx, const char *path, uint32_t idx,
sysfs_entry_t *out) {
(void)ctx;
if (path[0] == '\0') {
if (idx == 0) {
memset(out, 0, sizeof(sysfs_entry_t));
strncpy(out->name, "table", SYSFS_MAX_NAME - 1);
out->is_dir = 0;
return 0;
}
return -1;
}
return -1;
}
static int arp_sysfs_read(void *ctx, const char *path, char *buf,
uint32_t buf_size) {
(void)ctx;
if (strcmp(path, "table") != 0) return -1;
int pos = 0;
/* Header */
append_str(buf, buf_size, &pos, "IP Address MAC Address Iface State\n");
for (uint32_t i = 0; i < ARP_TABLE_SIZE; i++) {
if (arp_table[i].state == ARP_STATE_FREE) continue;
/* IP address */
char ip_buf[16];
ip_to_str(arp_table[i].ip_addr, ip_buf, sizeof(ip_buf));
append_str(buf, buf_size, &pos, ip_buf);
/* Pad to column 17 */
int ip_len = (int)strlen(ip_buf);
for (int p = ip_len; p < 17; p++) {
if (pos < (int)buf_size - 1) buf[pos++] = ' ';
}
/* MAC */
char mac_buf[18];
mac_to_str(arp_table[i].mac, mac_buf);
append_str(buf, buf_size, &pos, mac_buf);
if (pos < (int)buf_size - 1) buf[pos++] = ' ';
if (pos < (int)buf_size - 1) buf[pos++] = ' ';
/* Interface */
eth_iface_t *iface = ethernet_get_iface(arp_table[i].iface_idx);
if (iface) {
append_str(buf, buf_size, &pos, iface->name);
} else {
append_str(buf, buf_size, &pos, "?");
}
/* Pad and state */
int name_len = iface ? (int)strlen(iface->name) : 1;
for (int p = name_len; p < 7; p++) {
if (pos < (int)buf_size - 1) buf[pos++] = ' ';
}
if (arp_table[i].state == ARP_STATE_RESOLVED) {
append_str(buf, buf_size, &pos, "resolved");
} else {
append_str(buf, buf_size, &pos, "incomplete");
}
if (pos < (int)buf_size - 1) buf[pos++] = '\n';
}
buf[pos] = '\0';
return pos;
}
static int arp_sysfs_write(void *ctx, const char *path, const char *buf,
uint32_t size) {
(void)ctx;
(void)path;
(void)buf;
(void)size;
return -1; /* Read-only for now */
}
static sysfs_ops_t arp_sysfs_ops = {
.list = arp_sysfs_list,
.read = arp_sysfs_read,
.write = arp_sysfs_write,
};
/* ================================================================
* Initialization
* ================================================================ */
void arp_init(void) {
memset(arp_table, 0, sizeof(arp_table));
arp_count = 0;
sysfs_register("arp", &arp_sysfs_ops, NULL);
offset_print(" ARP: initialized\n");
}

145
src/arp.h Normal file
View File

@@ -0,0 +1,145 @@
/**
* @file arp.h
* @brief Address Resolution Protocol (ARP) subsystem.
*
* Implements ARP (RFC 826) for mapping IPv4 addresses to Ethernet
* MAC addresses. Maintains an ARP cache and handles ARP requests
* and replies.
*
* The ARP table is exposed via sysfs at /sys/arp for userspace tools.
*/
#ifndef ARP_H
#define ARP_H
#include <stdint.h>
/** Maximum number of ARP cache entries. */
#define ARP_TABLE_SIZE 32
/** ARP hardware type: Ethernet */
#define ARP_HW_ETHER 1
/** ARP operation codes */
#define ARP_OP_REQUEST 1
#define ARP_OP_REPLY 2
/** ARP cache entry states */
#define ARP_STATE_FREE 0 /**< Unused slot. */
#define ARP_STATE_INCOMPLETE 1 /**< Request sent, awaiting reply. */
#define ARP_STATE_RESOLVED 2 /**< MAC address known. */
/* ================================================================
* ARP packet (28 bytes for IPv4-over-Ethernet)
* ================================================================ */
/**
* ARP packet structure for IPv4 over Ethernet.
* All multi-byte fields are in network byte order.
*/
typedef struct __attribute__((packed)) arp_packet {
uint16_t hw_type; /**< Hardware type (1 = Ethernet). */
uint16_t proto_type; /**< Protocol type (0x0800 = IPv4). */
uint8_t hw_len; /**< Hardware address length (6). */
uint8_t proto_len; /**< Protocol address length (4). */
uint16_t operation; /**< Operation (1=request, 2=reply). */
uint8_t sender_mac[6]; /**< Sender hardware address. */
uint32_t sender_ip; /**< Sender protocol address. */
uint8_t target_mac[6]; /**< Target hardware address. */
uint32_t target_ip; /**< Target protocol address. */
} arp_packet_t;
/** ARP packet size. */
#define ARP_PACKET_SIZE 28
/* ================================================================
* ARP cache entry
* ================================================================ */
/**
* ARP cache entry.
*/
typedef struct arp_entry {
uint32_t ip_addr; /**< IPv4 address (host byte order). */
uint8_t mac[6]; /**< Resolved MAC address. */
uint8_t state; /**< ARP_STATE_*. */
uint8_t iface_idx; /**< Ethernet interface index. */
uint32_t timestamp; /**< Time when entry was created (tick count). */
} arp_entry_t;
/* ================================================================
* Public API
* ================================================================ */
/**
* Initialize the ARP subsystem.
* Registers sysfs namespace "arp".
*/
void arp_init(void);
/**
* Look up an IP address in the ARP cache.
*
* @param ip IPv4 address (host byte order).
* @param mac Output: 6-byte MAC address if found.
* @return 0 if found and resolved, -1 if not in cache or incomplete.
*/
int arp_lookup(uint32_t ip, uint8_t *mac);
/**
* Send an ARP request for the given IP address.
*
* @param iface_idx Ethernet interface to send on.
* @param target_ip IP address to resolve (host byte order).
* @return 0 on success, -1 on failure.
*/
int arp_request(uint32_t iface_idx, uint32_t target_ip);
/**
* Resolve an IP address to a MAC address.
*
* Checks the ARP cache first. If not found, sends an ARP request
* and returns -1 (caller should retry later).
*
* @param iface_idx Ethernet interface index.
* @param ip IPv4 address (host byte order).
* @param mac Output: 6-byte MAC address.
* @return 0 if resolved, -1 if pending.
*/
int arp_resolve(uint32_t iface_idx, uint32_t ip, uint8_t *mac);
/**
* Process an incoming ARP packet.
*
* Called by the Ethernet subsystem when an ARP frame is received.
*
* @param data Raw ARP packet.
* @param len Packet length.
* @param iface_idx Interface the packet arrived on.
*/
void arp_receive(const void *data, uint32_t len, uint32_t iface_idx);
/**
* Add a static ARP entry.
*
* @param ip IPv4 address (host byte order).
* @param mac 6-byte MAC address.
* @param iface_idx Ethernet interface index.
* @return 0 on success, -1 if table full.
*/
int arp_add_static(uint32_t ip, const uint8_t *mac, uint32_t iface_idx);
/**
* Get an ARP table entry by index (for enumeration).
*
* @param index 0-based index.
* @return Pointer to entry, or NULL if out of range.
*/
const arp_entry_t *arp_get_entry(uint32_t index);
/**
* Get the number of active ARP entries.
*/
uint32_t arp_get_count(void);
#endif /* ARP_H */

View File

@@ -335,6 +335,8 @@ devicefs_device_t *devicefs_find(const char *name) {
return NULL;
}
int init_devicefs(void) {
memset(devices, 0, sizeof(devices));
device_count = 0;

552
src/dhcp.c Normal file
View File

@@ -0,0 +1,552 @@
/**
* @file dhcp.c
* @brief DHCP client implementation.
*
* Implements the DHCP client protocol (RFC 2131) for automatic IPv4
* address configuration. Communicates via UDP (port 68→67) over
* Ethernet broadcasts.
*
* Since we don't have a full UDP stack yet, DHCP packets are
* constructed manually with IP+UDP headers and sent via the
* Ethernet subsystem directly.
*
* Status is exposed via sysfs at /sys/dhcp.
*/
#include "dhcp.h"
#include "ethernet.h"
#include "ipv4.h"
#include "arp.h"
#include "sysfs.h"
#include <string.h>
/* Debug print helpers */
extern void offset_print(const char *str);
extern void print_hex(uint32_t val);
/* ================================================================
* UDP header (for DHCP — minimal inline UDP)
* ================================================================ */
/** UDP header structure. */
typedef struct __attribute__((packed)) udp_header {
uint16_t src_port;
uint16_t dst_port;
uint16_t length;
uint16_t checksum;
} udp_header_t;
#define UDP_HLEN 8
/* ================================================================
* Global state
* ================================================================ */
/** Maximum number of tracked leases (one per interface). */
#define MAX_LEASES 8
/** DHCP lease table. */
static dhcp_lease_t leases[MAX_LEASES];
static uint32_t lease_count = 0;
/** Simple pseudo-random XID counter. */
static uint32_t xid_counter = 0x12345678;
/* ================================================================
* Helpers
* ================================================================ */
/**
* Compute Internet checksum.
*/
static uint16_t inet_checksum(const void *data, uint32_t len) {
const uint16_t *words = (const uint16_t *)data;
uint32_t sum = 0;
while (len > 1) { sum += *words++; len -= 2; }
if (len == 1) sum += *(const uint8_t *)words;
while (sum >> 16) sum = (sum & 0xFFFF) + (sum >> 16);
return (uint16_t)(~sum);
}
/**
* Get or create a lease for an interface.
*/
static dhcp_lease_t *get_lease(uint32_t iface_idx) {
for (uint32_t i = 0; i < lease_count; i++) {
if (leases[i].iface_idx == (uint8_t)iface_idx) return &leases[i];
}
if (lease_count >= MAX_LEASES) return NULL;
dhcp_lease_t *l = &leases[lease_count++];
memset(l, 0, sizeof(dhcp_lease_t));
l->iface_idx = (uint8_t)iface_idx;
return l;
}
/**
* Format an IP as dotted decimal into buf, return chars written.
*/
static int fmt_ip(uint32_t ip, char *buf, uint32_t size) {
int pos = 0;
for (int i = 0; i < 4; i++) {
if (i > 0 && pos < (int)size - 1) buf[pos++] = '.';
uint8_t o = (uint8_t)((ip >> (24 - i * 8)) & 0xFF);
if (o >= 100 && pos < (int)size - 3) {
buf[pos++] = (char)('0' + o / 100);
buf[pos++] = (char)('0' + (o % 100) / 10);
buf[pos++] = (char)('0' + o % 10);
} else if (o >= 10 && pos < (int)size - 2) {
buf[pos++] = (char)('0' + o / 10);
buf[pos++] = (char)('0' + o % 10);
} else if (pos < (int)size - 1) {
buf[pos++] = (char)('0' + o);
}
}
if (pos < (int)size) buf[pos] = '\0';
return pos;
}
/* ================================================================
* DHCP packet construction
* ================================================================ */
/**
* Build and send a DHCP packet wrapped in IP+UDP.
*
* Since we may not have a full UDP stack, we construct the whole
* IP+UDP+DHCP frame manually and send it as a broadcast Ethernet frame.
*/
static int dhcp_send_packet(uint32_t iface_idx, dhcp_packet_t *dhcp_pkt,
uint32_t dhcp_len) {
eth_iface_t *iface = ethernet_get_iface(iface_idx);
if (!iface) return -1;
/* Total sizes */
uint32_t udp_len = UDP_HLEN + dhcp_len;
uint32_t ip_len = IPV4_HLEN + udp_len;
if (ip_len > ETH_MTU) return -1;
/* Build combined IP+UDP+DHCP packet */
uint8_t pkt[1500];
memset(pkt, 0, sizeof(pkt));
/* IPv4 header */
ipv4_header_t *ip = (ipv4_header_t *)pkt;
ip->ihl_version = 0x45;
ip->tos = 0;
ip->total_length = htons((uint16_t)ip_len);
ip->identification = htons(xid_counter & 0xFFFF);
ip->flags_fragoff = 0;
ip->ttl = 64;
ip->protocol = IP_PROTO_UDP;
ip->checksum = 0;
ip->src_ip = htonl(iface->ip_addr); /* 0.0.0.0 if not yet configured */
ip->dst_ip = htonl(0xFFFFFFFF); /* Broadcast */
ip->checksum = inet_checksum(ip, IPV4_HLEN);
/* UDP header */
udp_header_t *udp = (udp_header_t *)(pkt + IPV4_HLEN);
udp->src_port = htons(DHCP_CLIENT_PORT);
udp->dst_port = htons(DHCP_SERVER_PORT);
udp->length = htons((uint16_t)udp_len);
udp->checksum = 0; /* UDP checksum optional in IPv4 */
/* DHCP payload */
memcpy(pkt + IPV4_HLEN + UDP_HLEN, dhcp_pkt, dhcp_len);
/* Send as broadcast Ethernet frame */
static const uint8_t bcast[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
return ethernet_send(iface, bcast, ETHERTYPE_IPV4, pkt, ip_len);
}
/**
* Add a DHCP option to the options buffer.
* Returns new offset.
*/
static uint32_t add_option(uint8_t *opts, uint32_t off,
uint8_t code, uint8_t len, const void *data) {
opts[off++] = code;
opts[off++] = len;
memcpy(&opts[off], data, len);
return off + len;
}
/* ================================================================
* DHCP protocol
* ================================================================ */
int dhcp_discover(uint32_t iface_idx) {
eth_iface_t *iface = ethernet_get_iface(iface_idx);
if (!iface) return -1;
dhcp_lease_t *lease = get_lease(iface_idx);
if (!lease) return -1;
/* Generate transaction ID */
xid_counter += 0x1234;
lease->xid = xid_counter;
lease->state = DHCP_STATE_DISCOVER;
/* Build DHCP DISCOVER packet */
dhcp_packet_t pkt;
memset(&pkt, 0, sizeof(pkt));
pkt.op = 1; /* BOOTREQUEST */
pkt.htype = 1; /* Ethernet */
pkt.hlen = 6;
pkt.hops = 0;
pkt.xid = htonl(lease->xid);
pkt.secs = 0;
pkt.flags = htons(0x8000); /* Broadcast flag */
pkt.ciaddr = 0;
pkt.yiaddr = 0;
pkt.siaddr = 0;
pkt.giaddr = 0;
memcpy(pkt.chaddr, iface->mac, 6);
pkt.magic = htonl(DHCP_MAGIC_COOKIE);
/* Options */
uint32_t off = 0;
/* Option 53: DHCP Message Type = DISCOVER */
uint8_t msg_type = DHCP_DISCOVER;
off = add_option(pkt.options, off, DHCP_OPT_MSG_TYPE, 1, &msg_type);
/* Option 55: Parameter Request List */
uint8_t params[] = {
DHCP_OPT_SUBNET_MASK,
DHCP_OPT_ROUTER,
DHCP_OPT_DNS,
};
off = add_option(pkt.options, off, DHCP_OPT_PARAM_LIST,
sizeof(params), params);
/* End option */
pkt.options[off++] = DHCP_OPT_END;
offset_print(" DHCP: sending DISCOVER on ");
offset_print(iface->name);
offset_print("\n");
return dhcp_send_packet(iface_idx, &pkt, DHCP_FIXED_SIZE + off);
}
/**
* Send a DHCP REQUEST message.
*/
static int dhcp_send_request(uint32_t iface_idx, dhcp_lease_t *lease) {
eth_iface_t *iface = ethernet_get_iface(iface_idx);
if (!iface) return -1;
dhcp_packet_t pkt;
memset(&pkt, 0, sizeof(pkt));
pkt.op = 1;
pkt.htype = 1;
pkt.hlen = 6;
pkt.xid = htonl(lease->xid);
pkt.flags = htons(0x8000);
memcpy(pkt.chaddr, iface->mac, 6);
pkt.magic = htonl(DHCP_MAGIC_COOKIE);
uint32_t off = 0;
/* Option 53: DHCP Message Type = REQUEST */
uint8_t msg_type = DHCP_REQUEST;
off = add_option(pkt.options, off, DHCP_OPT_MSG_TYPE, 1, &msg_type);
/* Option 50: Requested IP */
uint32_t req_ip = htonl(lease->ip_addr);
off = add_option(pkt.options, off, DHCP_OPT_REQUESTED_IP, 4, &req_ip);
/* Option 54: Server Identifier */
uint32_t srv_ip = htonl(lease->server_ip);
off = add_option(pkt.options, off, DHCP_OPT_SERVER_ID, 4, &srv_ip);
/* Option 55: Parameter Request List */
uint8_t params[] = {
DHCP_OPT_SUBNET_MASK,
DHCP_OPT_ROUTER,
DHCP_OPT_DNS,
};
off = add_option(pkt.options, off, DHCP_OPT_PARAM_LIST,
sizeof(params), params);
pkt.options[off++] = DHCP_OPT_END;
lease->state = DHCP_STATE_REQUESTING;
offset_print(" DHCP: sending REQUEST on ");
offset_print(iface->name);
offset_print("\n");
return dhcp_send_packet(iface_idx, &pkt, DHCP_FIXED_SIZE + off);
}
/**
* Parse DHCP options from a received packet.
*/
static void parse_options(const uint8_t *opts, uint32_t len,
uint8_t *msg_type, dhcp_lease_t *lease) {
uint32_t i = 0;
while (i < len) {
uint8_t code = opts[i++];
if (code == DHCP_OPT_END) break;
if (code == 0) continue; /* Padding */
if (i >= len) break;
uint8_t opt_len = opts[i++];
if (i + opt_len > len) break;
switch (code) {
case DHCP_OPT_MSG_TYPE:
if (opt_len >= 1) *msg_type = opts[i];
break;
case DHCP_OPT_SUBNET_MASK:
if (opt_len >= 4) {
uint32_t val;
memcpy(&val, &opts[i], 4);
lease->netmask = ntohl(val);
}
break;
case DHCP_OPT_ROUTER:
if (opt_len >= 4) {
uint32_t val;
memcpy(&val, &opts[i], 4);
lease->gateway = ntohl(val);
}
break;
case DHCP_OPT_DNS:
if (opt_len >= 4) {
uint32_t val;
memcpy(&val, &opts[i], 4);
lease->dns_server = ntohl(val);
}
break;
case DHCP_OPT_LEASE_TIME:
if (opt_len >= 4) {
uint32_t val;
memcpy(&val, &opts[i], 4);
lease->lease_time = ntohl(val);
}
break;
case DHCP_OPT_SERVER_ID:
if (opt_len >= 4) {
uint32_t val;
memcpy(&val, &opts[i], 4);
lease->server_ip = ntohl(val);
}
break;
default:
break;
}
i += opt_len;
}
}
void dhcp_receive(const void *data, uint32_t len, uint32_t iface_idx) {
if (len < DHCP_FIXED_SIZE) return;
const dhcp_packet_t *pkt = (const dhcp_packet_t *)data;
/* Must be a BOOTREPLY */
if (pkt->op != 2) return;
/* Check magic cookie */
if (ntohl(pkt->magic) != DHCP_MAGIC_COOKIE) return;
dhcp_lease_t *lease = get_lease(iface_idx);
if (!lease) return;
/* Match transaction ID */
if (ntohl(pkt->xid) != lease->xid) return;
/* Parse options */
uint8_t msg_type = 0;
uint32_t opts_len = len - DHCP_FIXED_SIZE;
if (opts_len > sizeof(pkt->options)) opts_len = sizeof(pkt->options);
parse_options(pkt->options, opts_len, &msg_type, lease);
/* Store offered IP */
uint32_t offered_ip = ntohl(pkt->yiaddr);
switch (msg_type) {
case DHCP_OFFER:
if (lease->state != DHCP_STATE_DISCOVER) break;
lease->ip_addr = offered_ip;
offset_print(" DHCP: received OFFER ");
print_hex(offered_ip);
offset_print("\n");
/* Send REQUEST */
dhcp_send_request(iface_idx, lease);
break;
case DHCP_ACK:
if (lease->state != DHCP_STATE_REQUESTING) break;
lease->ip_addr = offered_ip;
lease->state = DHCP_STATE_BOUND;
/* Apply configuration to the interface */
{
eth_iface_t *iface = ethernet_get_iface(iface_idx);
if (iface) {
iface->ip_addr = lease->ip_addr;
iface->netmask = lease->netmask;
iface->gateway = lease->gateway;
}
}
offset_print(" DHCP: BOUND on ");
{
eth_iface_t *iface2 = ethernet_get_iface(iface_idx);
if (iface2) offset_print(iface2->name);
}
offset_print(" IP ");
print_hex(lease->ip_addr);
offset_print("\n");
break;
case DHCP_NAK:
lease->state = DHCP_STATE_FAILED;
offset_print(" DHCP: received NAK\n");
break;
default:
break;
}
}
const dhcp_lease_t *dhcp_get_lease(uint32_t iface_idx) {
for (uint32_t i = 0; i < lease_count; i++) {
if (leases[i].iface_idx == (uint8_t)iface_idx) return &leases[i];
}
return NULL;
}
const char *dhcp_state_name(uint8_t state) {
switch (state) {
case DHCP_STATE_IDLE: return "idle";
case DHCP_STATE_DISCOVER: return "discovering";
case DHCP_STATE_REQUESTING: return "requesting";
case DHCP_STATE_BOUND: return "bound";
case DHCP_STATE_RENEWING: return "renewing";
case DHCP_STATE_FAILED: return "failed";
default: return "unknown";
}
}
/* ================================================================
* Sysfs: /sys/dhcp
*
* /sys/dhcp/
* status - overall DHCP status
* ================================================================ */
static void append(char *buf, uint32_t size, int *pos, const char *s) {
while (*s && *pos < (int)size - 1) buf[(*pos)++] = *s++;
}
static int dhcp_sysfs_list(void *ctx, const char *path, uint32_t idx,
sysfs_entry_t *out) {
(void)ctx;
if (path[0] == '\0') {
if (idx == 0) {
memset(out, 0, sizeof(sysfs_entry_t));
strncpy(out->name, "status", SYSFS_MAX_NAME - 1);
out->is_dir = 0;
return 0;
}
return -1;
}
return -1;
}
static int dhcp_sysfs_read(void *ctx, const char *path, char *buf,
uint32_t buf_size) {
(void)ctx;
if (strcmp(path, "status") != 0) return -1;
int pos = 0;
if (lease_count == 0) {
append(buf, buf_size, &pos, "No DHCP leases.\n");
buf[pos] = '\0';
return pos;
}
for (uint32_t i = 0; i < lease_count; i++) {
dhcp_lease_t *l = &leases[i];
eth_iface_t *iface = ethernet_get_iface(l->iface_idx);
if (iface) {
append(buf, buf_size, &pos, iface->name);
} else {
append(buf, buf_size, &pos, "?");
}
append(buf, buf_size, &pos, ": ");
append(buf, buf_size, &pos, dhcp_state_name(l->state));
append(buf, buf_size, &pos, "\n");
if (l->state == DHCP_STATE_BOUND) {
char ip_buf[16];
append(buf, buf_size, &pos, " IP: ");
fmt_ip(l->ip_addr, ip_buf, sizeof(ip_buf));
append(buf, buf_size, &pos, ip_buf);
append(buf, buf_size, &pos, "\n");
append(buf, buf_size, &pos, " Netmask: ");
fmt_ip(l->netmask, ip_buf, sizeof(ip_buf));
append(buf, buf_size, &pos, ip_buf);
append(buf, buf_size, &pos, "\n");
append(buf, buf_size, &pos, " Gateway: ");
fmt_ip(l->gateway, ip_buf, sizeof(ip_buf));
append(buf, buf_size, &pos, ip_buf);
append(buf, buf_size, &pos, "\n");
append(buf, buf_size, &pos, " DNS: ");
fmt_ip(l->dns_server, ip_buf, sizeof(ip_buf));
append(buf, buf_size, &pos, ip_buf);
append(buf, buf_size, &pos, "\n");
append(buf, buf_size, &pos, " Server: ");
fmt_ip(l->server_ip, ip_buf, sizeof(ip_buf));
append(buf, buf_size, &pos, ip_buf);
append(buf, buf_size, &pos, "\n");
}
}
buf[pos] = '\0';
return pos;
}
static int dhcp_sysfs_write(void *ctx, const char *path, const char *buf,
uint32_t size) {
(void)ctx;
(void)path;
(void)buf;
(void)size;
return -1;
}
static sysfs_ops_t dhcp_sysfs_ops = {
.list = dhcp_sysfs_list,
.read = dhcp_sysfs_read,
.write = dhcp_sysfs_write,
};
/* ================================================================
* Initialization
* ================================================================ */
void dhcp_init(void) {
memset(leases, 0, sizeof(leases));
lease_count = 0;
sysfs_register("dhcp", &dhcp_sysfs_ops, NULL);
offset_print(" DHCP: initialized\n");
}

145
src/dhcp.h Normal file
View File

@@ -0,0 +1,145 @@
/**
* @file dhcp.h
* @brief DHCP (Dynamic Host Configuration Protocol) client subsystem.
*
* Implements a minimal DHCP client (RFC 2131) that can obtain an IPv4
* address, subnet mask, gateway, and DNS server from a DHCP server.
*
* DHCP operates over UDP: client port 68, server port 67.
* Uses the Ethernet subsystem for broadcast communication.
*
* Status information is exposed via sysfs at /sys/dhcp.
*/
#ifndef DHCP_H
#define DHCP_H
#include <stdint.h>
/** DHCP ports. */
#define DHCP_CLIENT_PORT 68
#define DHCP_SERVER_PORT 67
/** DHCP message types. */
#define DHCP_DISCOVER 1
#define DHCP_OFFER 2
#define DHCP_REQUEST 3
#define DHCP_DECLINE 4
#define DHCP_ACK 5
#define DHCP_NAK 6
#define DHCP_RELEASE 7
/** DHCP options. */
#define DHCP_OPT_SUBNET_MASK 1
#define DHCP_OPT_ROUTER 3
#define DHCP_OPT_DNS 6
#define DHCP_OPT_HOSTNAME 12
#define DHCP_OPT_REQUESTED_IP 50
#define DHCP_OPT_LEASE_TIME 51
#define DHCP_OPT_MSG_TYPE 53
#define DHCP_OPT_SERVER_ID 54
#define DHCP_OPT_PARAM_LIST 55
#define DHCP_OPT_END 255
/** DHCP magic cookie. */
#define DHCP_MAGIC_COOKIE 0x63825363
/** DHCP client states. */
#define DHCP_STATE_IDLE 0
#define DHCP_STATE_DISCOVER 1
#define DHCP_STATE_REQUESTING 2
#define DHCP_STATE_BOUND 3
#define DHCP_STATE_RENEWING 4
#define DHCP_STATE_FAILED 5
/* ================================================================
* DHCP packet structure (548 bytes minimum)
* ================================================================ */
/**
* DHCP message (over UDP, 236 bytes fixed + options).
*/
typedef struct __attribute__((packed)) dhcp_packet {
uint8_t op; /**< Message op: 1=BOOTREQUEST, 2=BOOTREPLY. */
uint8_t htype; /**< Hardware type: 1=Ethernet. */
uint8_t hlen; /**< Hardware address length: 6. */
uint8_t hops; /**< Hops: 0. */
uint32_t xid; /**< Transaction ID. */
uint16_t secs; /**< Seconds elapsed. */
uint16_t flags; /**< Flags (0x8000 = broadcast). */
uint32_t ciaddr; /**< Client IP (if bound). */
uint32_t yiaddr; /**< 'Your' (client) IP address. */
uint32_t siaddr; /**< Server IP address. */
uint32_t giaddr; /**< Gateway IP address. */
uint8_t chaddr[16]; /**< Client hardware address. */
uint8_t sname[64]; /**< Server host name (unused). */
uint8_t file[128]; /**< Boot file name (unused). */
uint32_t magic; /**< Magic cookie (0x63825363). */
uint8_t options[312]; /**< DHCP options. */
} dhcp_packet_t;
/** Fixed part of DHCP packet (before options). */
#define DHCP_FIXED_SIZE 240
/* ================================================================
* DHCP lease information
* ================================================================ */
/**
* Information obtained from a DHCP lease.
*/
typedef struct dhcp_lease {
uint32_t ip_addr; /**< Assigned IP address (host byte order). */
uint32_t netmask; /**< Subnet mask (host byte order). */
uint32_t gateway; /**< Default gateway (host byte order). */
uint32_t dns_server; /**< DNS server (host byte order). */
uint32_t server_ip; /**< DHCP server IP (host byte order). */
uint32_t lease_time; /**< Lease time in seconds. */
uint32_t xid; /**< Transaction ID used. */
uint8_t state; /**< Current DHCP state. */
uint8_t iface_idx; /**< Ethernet interface. */
} dhcp_lease_t;
/* ================================================================
* Public API
* ================================================================ */
/**
* Initialize the DHCP subsystem.
*/
void dhcp_init(void);
/**
* Start a DHCP discover/request sequence on an interface.
*
* @param iface_idx Ethernet interface index.
* @return 0 on success (discover sent), -1 on failure.
*/
int dhcp_discover(uint32_t iface_idx);
/**
* Process an incoming DHCP (UDP) packet.
*
* @param data Raw DHCP packet.
* @param len Packet length.
* @param iface_idx Interface index.
*/
void dhcp_receive(const void *data, uint32_t len, uint32_t iface_idx);
/**
* Get the current DHCP lease for an interface.
*
* @param iface_idx Interface index.
* @return Pointer to lease info, or NULL.
*/
const dhcp_lease_t *dhcp_get_lease(uint32_t iface_idx);
/**
* Get the DHCP state name as a string.
*
* @param state DHCP_STATE_* constant.
* @return Human-readable state name.
*/
const char *dhcp_state_name(uint8_t state);
#endif /* DHCP_H */

409
src/e3c509.c Normal file
View File

@@ -0,0 +1,409 @@
/**
* @file e3c509.c
* @brief 3Com 3C509B (EtherLink III) ISA Ethernet NIC driver.
*
* Drives 3Com 3C509/3C509B Ethernet adapters using PIO (programmed I/O).
* The 3C509B uses a windowed register model: 8 register windows of 16 I/O
* ports each, selected by writing to the command register.
*
* Only 10base-T (RJ45) operation is supported, per design requirements.
*
* Packet TX: write packet length, then write data to TX PIO port.
* Packet RX: poll RX status, read data from RX PIO port.
*/
#include "e3c509.h"
#include "port_io.h"
#include "pic.h"
#include "devicefs.h"
#include "ethernet.h"
#include "driver.h"
#include <string.h>
/* Debug print helpers */
extern void offset_print(const char *str);
extern void print_hex(uint32_t val);
/* ================================================================
* Global state
* ================================================================ */
/** Single 3C509B device. */
static e3c509_device_t e3c509_dev;
/** Volatile flags set by IRQ handler. */
static volatile int e3c509_rx_ready = 0;
static volatile int e3c509_tx_done = 0;
/* ================================================================
* Register access helpers
* ================================================================ */
/** Write a 16-bit command to the 3C509B command register. */
static inline void e3c509_cmd(uint16_t base, uint16_t cmd) {
outw(base + E3C509_CMD, cmd);
}
/** Read the 16-bit status register. */
static inline uint16_t e3c509_status(uint16_t base) {
return inw(base + E3C509_STATUS);
}
/**
* Select a register window.
*/
static void e3c509_select_window(uint16_t base, uint16_t window) {
e3c509_cmd(base, CMD_SELECT_WINDOW | (window & 0x07));
}
/**
* Wait for a command to complete.
*/
static void e3c509_wait_cmd(uint16_t base) {
int timeout = 100000;
while ((e3c509_status(base) & STAT_CMD_IN_PROG) && --timeout > 0);
}
/* ================================================================
* Packet send / receive
* ================================================================ */
int e3c509_send(e3c509_device_t *dev, const void *data, uint32_t len) {
if (!dev || !dev->present) return -1;
if (len > ETH_FRAME_MAX) return -1;
uint16_t base = dev->io_base;
uint32_t send_len = len;
if (send_len < 60) send_len = 60;
/* Switch to window 1 */
e3c509_select_window(base, 1);
/* Wait for free TX space */
int timeout = 100000;
while (inw(base + E3C509_W1_FREE_TX) < send_len + 4 && --timeout > 0) {
asm volatile("pause");
}
if (timeout == 0) {
offset_print(" 3C509: tx fifo full\n");
return -1;
}
/* Write TX preamble: packet length (low 11 bits, bit 15 = no interrupt) */
outw(base + E3C509_W1_TX_PIO, (uint16_t)(send_len | 0x00));
/* Pad to dword-aligned length */
outw(base + E3C509_W1_TX_PIO, 0x0000);
/* Write packet data as 16-bit words */
const uint16_t *data16 = (const uint16_t *)data;
uint32_t words = len / 2;
for (uint32_t i = 0; i < words; i++) {
outw(base + E3C509_W1_TX_PIO, data16[i]);
}
/* Write last byte if odd */
if (len & 1) {
const uint8_t *data8 = (const uint8_t *)data;
outw(base + E3C509_W1_TX_PIO, (uint16_t)data8[len - 1]);
}
/* Pad to minimum frame size with zeros */
if (len < 60) {
uint32_t pad_words = (60 - len + 1) / 2;
for (uint32_t i = 0; i < pad_words; i++) {
outw(base + E3C509_W1_TX_PIO, 0x0000);
}
}
/* Wait for TX complete */
e3c509_tx_done = 0;
timeout = 1000000;
while (!e3c509_tx_done && --timeout > 0) {
/* Check TX status directly in case interrupts are slow */
uint8_t txstat = inb(base + E3C509_W1_TX_STATUS);
if (txstat & 0x80) { /* TX complete */
/* Acknowledge by writing status back */
outb(base + E3C509_W1_TX_STATUS, txstat);
break;
}
asm volatile("pause");
}
return 0;
}
int e3c509_recv(e3c509_device_t *dev, void *buf, uint32_t bufsize) {
if (!dev || !dev->present) return -1;
uint16_t base = dev->io_base;
/* Switch to window 1 */
e3c509_select_window(base, 1);
/* Read RX status */
uint16_t rx_status = inw(base + E3C509_W1_RX_STATUS);
/* Check if a packet is available (bit 15 = incomplete/error) */
if (rx_status & 0x8000) {
/* Error — discard the packet */
e3c509_cmd(base, CMD_RX_DISCARD);
e3c509_wait_cmd(base);
return -1;
}
/* Bits 10:0 = packet length */
uint16_t pkt_len = rx_status & 0x07FF;
if (pkt_len == 0) {
return 0; /* No packet */
}
uint32_t copy_len = pkt_len;
if (copy_len > bufsize) copy_len = bufsize;
/* Read packet data as 16-bit words */
uint16_t *buf16 = (uint16_t *)buf;
uint32_t read_words = copy_len / 2;
for (uint32_t i = 0; i < read_words; i++) {
buf16[i] = inw(base + E3C509_W1_RX_PIO);
}
if (copy_len & 1) {
uint16_t w = inw(base + E3C509_W1_RX_PIO);
((uint8_t *)buf)[copy_len - 1] = (uint8_t)(w & 0xFF);
}
/* Discard any remaining data and advance */
e3c509_cmd(base, CMD_RX_DISCARD);
e3c509_wait_cmd(base);
return (int)copy_len;
}
/* ================================================================
* IRQ handler
* ================================================================ */
void e3c509_irq(void) {
if (!e3c509_dev.present) return;
uint16_t base = e3c509_dev.io_base;
uint16_t status = e3c509_status(base);
if (status & STAT_RX_COMPLETE) {
e3c509_rx_ready = 1;
e3c509_cmd(base, CMD_ACK_INTR | STAT_RX_COMPLETE);
}
if (status & STAT_TX_COMPLETE) {
e3c509_tx_done = 1;
/* Read and clear TX status */
e3c509_select_window(base, 1);
uint8_t txstat = inb(base + E3C509_W1_TX_STATUS);
outb(base + E3C509_W1_TX_STATUS, txstat);
e3c509_cmd(base, CMD_ACK_INTR | STAT_TX_COMPLETE);
}
if (status & STAT_TX_AVAILABLE) {
e3c509_cmd(base, CMD_ACK_INTR | STAT_TX_AVAILABLE);
}
if (status & STAT_ADAPTER_FAIL) {
e3c509_cmd(base, CMD_ACK_INTR | STAT_ADAPTER_FAIL);
}
if (status & STAT_UPDATE_STATS) {
e3c509_cmd(base, CMD_ACK_INTR | STAT_UPDATE_STATS);
}
/* Acknowledge the interrupt latch */
e3c509_cmd(base, CMD_ACK_INTR | STAT_INT_LATCH);
}
/* ================================================================
* Initialization
* ================================================================ */
/**
* Read the MAC address from the 3C509B's EEPROM (Window 2).
*/
static void e3c509_read_mac(uint16_t base, uint8_t *mac) {
e3c509_select_window(base, 2);
uint16_t w0 = inw(base + E3C509_W2_ADDR0);
uint16_t w1 = inw(base + E3C509_W2_ADDR1);
uint16_t w2 = inw(base + E3C509_W2_ADDR2);
mac[0] = (uint8_t)(w0 & 0xFF);
mac[1] = (uint8_t)((w0 >> 8) & 0xFF);
mac[2] = (uint8_t)(w1 & 0xFF);
mac[3] = (uint8_t)((w1 >> 8) & 0xFF);
mac[4] = (uint8_t)(w2 & 0xFF);
mac[5] = (uint8_t)((w2 >> 8) & 0xFF);
}
/**
* Initialize the 3C509B hardware.
*/
static int e3c509_hw_init(uint16_t base) {
/* Global reset */
e3c509_cmd(base, CMD_GLOBAL_RESET);
/* Wait for reset to complete */
int timeout = 100000;
while ((e3c509_status(base) & STAT_CMD_IN_PROG) && --timeout > 0);
if (timeout == 0) {
offset_print(" 3C509: reset timeout\n");
return -1;
}
/* Small extra delay */
for (volatile int i = 0; i < 50000; i++) {
asm volatile("pause");
}
/* Read MAC address */
e3c509_read_mac(base, e3c509_dev.mac);
/* Select 10base-T (RJ45) transceiver — Window 0, Address Config */
e3c509_select_window(base, 0);
uint16_t addr_cfg = inw(base + E3C509_W0_ADDR_CFG);
/* Clear transceiver bits (14:13) and set to TP (00) */
addr_cfg &= ~(0x3 << 14);
addr_cfg |= (XCVR_TP << 14);
outw(base + E3C509_W0_ADDR_CFG, addr_cfg);
/* Configure IRQ in resource config register */
uint16_t res_cfg = inw(base + E3C509_W0_RES_CFG);
/* IRQ is in bits 15:12. Set to our IRQ. */
res_cfg = (res_cfg & 0x0FFF) | ((uint16_t)e3c509_dev.irq << 12);
outw(base + E3C509_W0_RES_CFG, res_cfg);
/* Enable the adapter */
outw(base + E3C509_W0_CFG_CTRL, 0x0001); /* Enable */
/* Reset TX and RX */
e3c509_cmd(base, CMD_TX_RESET);
e3c509_wait_cmd(base);
e3c509_cmd(base, CMD_RX_RESET);
e3c509_wait_cmd(base);
/* Set RX filter: accept station + broadcast */
e3c509_cmd(base, CMD_SET_RX_FILTER | RX_FILTER_STATION | RX_FILTER_BCAST);
/* Set TX start threshold — start transmitting after full packet */
e3c509_cmd(base, CMD_SET_TX_START | (ETH_FRAME_MAX >> 2));
/* Set interrupt mask */
e3c509_cmd(base, CMD_SET_INTR_MASK |
STAT_RX_COMPLETE | STAT_TX_COMPLETE | STAT_TX_AVAILABLE |
STAT_ADAPTER_FAIL | STAT_UPDATE_STATS);
/* Enable TX and RX */
e3c509_cmd(base, CMD_TX_ENABLE);
e3c509_cmd(base, CMD_RX_ENABLE);
/* Acknowledge any pending interrupts */
e3c509_cmd(base, CMD_ACK_INTR | 0xFF);
/* Switch to operating window (window 1) */
e3c509_select_window(base, 1);
return 0;
}
/* ================================================================
* Driver framework
* ================================================================ */
/**
* Probe for a 3C509B card.
*
* Check the manufacturer ID at Window 0, offset 0x00.
* The 3Com 3C509B should return 0x6D50.
*/
static driver_probe_result_t e3c509_probe(void) {
uint16_t base = E3C509_DEFAULT_IOBASE;
/* Try to select Window 0 and read manufacturer ID */
e3c509_cmd(base, CMD_SELECT_WINDOW | 0);
/* Brief delay */
for (volatile int i = 0; i < 10000; i++) {
asm volatile("pause");
}
uint16_t mfg_id = inw(base + E3C509_W0_MFG_ID);
/* 3Com manufacturer ID = 0x6D50 */
if (mfg_id == 0x6D50) {
return DRIVER_PROBE_OK;
}
/* Also check for the product ID being in a reasonable range */
if ((mfg_id & 0xFF00) == 0x9000 || (mfg_id & 0xFF00) == 0x9100) {
return DRIVER_PROBE_OK;
}
return DRIVER_PROBE_NOT_FOUND;
}
/**
* Initialize the 3C509B driver.
*/
static int e3c509_driver_init(void) {
memset(&e3c509_dev, 0, sizeof(e3c509_dev));
e3c509_dev.io_base = E3C509_DEFAULT_IOBASE;
e3c509_dev.irq = E3C509_DEFAULT_IRQ;
/* Unmask IRQ */
pic_clear_mask(e3c509_dev.irq);
/* Initialize hardware */
if (e3c509_hw_init(e3c509_dev.io_base) != 0) {
offset_print(" 3C509: initialization failed\n");
return -1;
}
e3c509_dev.present = 1;
/* Print MAC address */
offset_print(" 3C509: MAC ");
for (int i = 0; i < 6; i++) {
if (i > 0) offset_print(":");
print_hex(e3c509_dev.mac[i]);
}
offset_print("\n");
/* 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);
offset_print(" IRQ ");
print_hex(e3c509_dev.irq);
offset_print("\n");
return 0;
}
e3c509_device_t *e3c509_get_device(void) {
return e3c509_dev.present ? &e3c509_dev : NULL;
}
int e3c509_init(void) {
return e3c509_driver_init();
}
/* ================================================================
* Driver registration
* ================================================================ */
static const driver_t e3c509_driver = {
.name = "3c509b",
.probe = e3c509_probe,
.init = e3c509_driver_init,
};
REGISTER_DRIVER(e3c509_driver);

158
src/e3c509.h Normal file
View File

@@ -0,0 +1,158 @@
/**
* @file e3c509.h
* @brief 3Com 3C509B (EtherLink III) ISA Ethernet NIC driver.
*
* Drives 3Com 3C509B NICs. Only supports RJ45 (10base-T) transceiver.
* The 3C509B uses a windowed register model with 8 register windows
* selected via the Window register. Packets are transferred through
* PIO (programmed I/O) using the card's FIFO.
*
* Uses IRQ 10 by default for interrupt-driven operation.
* Registers as a character device with devicefs (/dev/ethN).
*/
#ifndef E3C509_H
#define E3C509_H
#include <stdint.h>
/* ================================================================
* 3C509B I/O Port Layout
*
* The 3C509B uses 16 I/O ports starting at the base address.
* Registers are organized into 8 windows (0-7).
* Window selection: write window number to port base+0x0E.
* ================================================================ */
/** Default ISA I/O base for 3C509B. */
#define E3C509_DEFAULT_IOBASE 0x210
/** Default ISA IRQ for 3C509B. */
#define E3C509_DEFAULT_IRQ 10
/* --- Global registers (always accessible) --- */
#define E3C509_CMD 0x0E /**< Command register (write) */
#define E3C509_STATUS 0x0E /**< Status register (read) */
/* --- Window 0: Setup / Configuration --- */
#define E3C509_W0_MFG_ID 0x00 /**< Manufacturer ID (should be 0x6D50) */
#define E3C509_W0_ADDR_CFG 0x06 /**< Address config (transceiver type) */
#define E3C509_W0_RES_CFG 0x08 /**< Resource config (IRQ) */
#define E3C509_W0_CFG_CTRL 0x04 /**< Config control */
/* --- Window 1: Operating Set --- */
#define E3C509_W1_TX_PIO 0x00 /**< TX PIO data (write) */
#define E3C509_W1_TX_STATUS 0x0B /**< TX status */
#define E3C509_W1_RX_PIO 0x00 /**< RX PIO data (read) */
#define E3C509_W1_RX_STATUS 0x08 /**< RX status */
#define E3C509_W1_FREE_TX 0x0C /**< Free TX bytes */
/* --- Window 2: Station Address --- */
#define E3C509_W2_ADDR0 0x00 /**< Station address word 0 */
#define E3C509_W2_ADDR1 0x02 /**< Station address word 1 */
#define E3C509_W2_ADDR2 0x04 /**< Station address word 2 */
/* --- Window 3: FIFO Management --- */
/* (Used for internal FIFO buffer management) */
/* --- Window 4: Diagnostics --- */
#define E3C509_W4_MEDIA_TYPE 0x0A /**< Media type and status */
#define E3C509_W4_NET_DIAG 0x06 /**< Network diagnostics */
/* --- Window 5: Read Zeroes (for RX filter) --- */
/* --- Window 6: Statistics --- */
#define E3C509_W6_TX_BYTES 0x0C /**< Total TX bytes */
#define E3C509_W6_RX_BYTES 0x0A /**< Total RX bytes */
/* ================================================================
* 3C509B Commands (written to command register)
* ================================================================ */
#define CMD_GLOBAL_RESET 0x0000 /**< Global reset */
#define CMD_SELECT_WINDOW 0x0800 /**< Select window (OR with window num) */
#define CMD_RX_ENABLE 0x2000 /**< Enable receiver */
#define CMD_RX_RESET 0x2800 /**< Reset receiver */
#define CMD_RX_DISCARD 0x4000 /**< Discard top RX packet */
#define CMD_TX_ENABLE 0x4800 /**< Enable transmitter */
#define CMD_TX_RESET 0x5800 /**< Reset transmitter */
#define CMD_REQ_INTR 0x6000 /**< Request interrupt (OR with mask) */
#define CMD_ACK_INTR 0x6800 /**< Acknowledge interrupt (OR with mask) */
#define CMD_SET_INTR_MASK 0x7000 /**< Set interrupt mask */
#define CMD_SET_RX_FILTER 0x8000 /**< Set RX filter */
#define CMD_TX_DONE 0x8800 /**< ? */
#define CMD_STATS_ENABLE 0x9000 /**< Enable statistics */
#define CMD_STATS_DISABLE 0xB000 /**< Disable statistics */
#define CMD_SET_TX_START 0x9800 /**< Set TX start threshold */
/* ================================================================
* Status / Interrupt bits
* ================================================================ */
#define STAT_INT_LATCH 0x0001 /**< Interrupt latch */
#define STAT_ADAPTER_FAIL 0x0002 /**< Adapter failure */
#define STAT_TX_COMPLETE 0x0004 /**< TX complete */
#define STAT_TX_AVAILABLE 0x0008 /**< TX available */
#define STAT_RX_COMPLETE 0x0010 /**< RX complete */
#define STAT_RX_EARLY 0x0020 /**< RX early threshold */
#define STAT_INT_REQ 0x0040 /**< Interrupt requested */
#define STAT_UPDATE_STATS 0x0080 /**< Update statistics */
#define STAT_CMD_IN_PROG 0x1000 /**< Command in progress */
#define STAT_WINDOW_MASK 0xE000 /**< Current window bits */
/* ================================================================
* RX Filter bits (for CMD_SET_RX_FILTER)
* ================================================================ */
#define RX_FILTER_STATION 0x01 /**< Accept frames to station address */
#define RX_FILTER_MCAST 0x02 /**< Accept multicast */
#define RX_FILTER_BCAST 0x04 /**< Accept broadcast */
#define RX_FILTER_PROMISC 0x08 /**< Promiscuous mode */
/* ================================================================
* Transceiver types
* ================================================================ */
#define XCVR_TP 0x00 /**< 10base-T / RJ45 */
#define XCVR_AUI 0x01 /**< AUI */
#define XCVR_BNC 0x03 /**< BNC / 10base2 */
/** Maximum Ethernet frame size. */
#define ETH_FRAME_MAX 1518
/* ================================================================
* 3C509B device state
* ================================================================ */
typedef struct e3c509_device {
uint16_t io_base; /**< I/O base address. */
uint8_t irq; /**< IRQ number. */
uint8_t mac[6]; /**< MAC address. */
int present; /**< 1 if card detected. */
} e3c509_device_t;
/* ================================================================
* Public API
* ================================================================ */
/**
* Initialize 3C509B driver.
*/
int e3c509_init(void);
/**
* 3C509B IRQ handler.
*/
void e3c509_irq(void);
/**
* Send an Ethernet frame.
*/
int e3c509_send(e3c509_device_t *dev, const void *data, uint32_t len);
/**
* Receive a pending Ethernet frame.
*/
int e3c509_recv(e3c509_device_t *dev, void *buf, uint32_t bufsize);
/**
* Get the 3C509B device pointer.
*/
e3c509_device_t *e3c509_get_device(void);
#endif /* E3C509_H */

393
src/ethernet.c Normal file
View 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",
&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
View 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 */

673
src/fat32.c Normal file
View File

@@ -0,0 +1,673 @@
/**
* @file fat32.c
* @brief FAT32 filesystem driver implementation.
*
* Reads and writes FAT32 filesystems on block devices. Supports:
* - Reading the BPB and FAT
* - Following cluster chains
* - Reading directory entries (short 8.3 names)
* - Reading and writing file data
* - Creating new files
*
* Mounted via fat32_mount_device() which registers VFS operations.
*/
#include "fat32.h"
#include "vfs.h"
#include "kmalloc.h"
#include <string.h>
/* Debug print helpers defined in kernel.c */
extern void offset_print(const char *str);
extern void print_hex(uint32_t val);
/** Maximum number of simultaneous FAT32 mounts. */
#define FAT32_MAX_MOUNTS 4
/** Sector buffer size (one sector). */
#define SECTOR_SIZE 512
/** Static mount contexts. */
static fat32_fs_t fat32_mounts[FAT32_MAX_MOUNTS];
static int fat32_mount_count = 0;
/* ================================================================
* Low-level block I/O helpers
* ================================================================ */
/**
* Read sectors from the underlying block device.
*
* @param fs FAT32 filesystem instance.
* @param lba Logical block address (absolute, 0 = device start).
* @param count Number of sectors to read.
* @param buf Output buffer (must be count * 512 bytes).
* @return 0 on success, -1 on error.
*/
static int fat32_read_sectors(fat32_fs_t *fs, uint32_t lba,
uint32_t count, void *buf) {
if (!fs->dev || !fs->dev->block_ops || !fs->dev->block_ops->read_sectors) {
return -1;
}
return fs->dev->block_ops->read_sectors(fs->dev->dev_data, lba, count, buf);
}
/**
* Write sectors to the underlying block device.
*/
static int fat32_write_sectors(fat32_fs_t *fs, uint32_t lba,
uint32_t count, const void *buf) {
if (!fs->dev || !fs->dev->block_ops || !fs->dev->block_ops->write_sectors) {
return -1;
}
return fs->dev->block_ops->write_sectors(fs->dev->dev_data, lba, count, buf);
}
/* ================================================================
* FAT table operations
* ================================================================ */
/**
* Read a single FAT entry (the next cluster in the chain).
*
* @param fs Filesystem instance.
* @param cluster Current cluster number.
* @return Next cluster number, or FAT32_EOC if end of chain.
*/
static uint32_t fat32_read_fat(fat32_fs_t *fs, uint32_t cluster) {
/* Each FAT entry is 4 bytes. Calculate which sector and offset. */
uint32_t fat_offset = cluster * 4;
uint32_t fat_sector = fs->fat_start_lba + (fat_offset / SECTOR_SIZE);
uint32_t entry_offset = fat_offset % SECTOR_SIZE;
uint8_t sector_buf[SECTOR_SIZE];
if (fat32_read_sectors(fs, fat_sector, 1, sector_buf) != 0) {
return FAT32_EOC;
}
uint32_t entry = *(uint32_t *)(sector_buf + entry_offset);
return entry & 0x0FFFFFFF; /* Mask off top 4 bits */
}
/**
* Write a FAT entry.
*
* @param fs Filesystem instance.
* @param cluster Cluster to update.
* @param value New value for the FAT entry.
* @return 0 on success, -1 on error.
*/
static int fat32_write_fat(fat32_fs_t *fs, uint32_t cluster,
uint32_t value) {
uint32_t fat_offset = cluster * 4;
uint32_t fat_sector = fs->fat_start_lba + (fat_offset / SECTOR_SIZE);
uint32_t entry_offset = fat_offset % SECTOR_SIZE;
uint8_t sector_buf[SECTOR_SIZE];
if (fat32_read_sectors(fs, fat_sector, 1, sector_buf) != 0) {
return -1;
}
/* Preserve top 4 bits of the existing entry */
uint32_t *entry = (uint32_t *)(sector_buf + entry_offset);
*entry = (*entry & 0xF0000000) | (value & 0x0FFFFFFF);
/* Write back to all FATs */
for (uint8_t f = 0; f < fs->bpb.num_fats; f++) {
uint32_t fat_sec = fs->fat_start_lba +
(f * fs->bpb.fat_size_32) + (fat_offset / SECTOR_SIZE);
if (fat32_write_sectors(fs, fat_sec, 1, sector_buf) != 0) {
return -1;
}
}
return 0;
}
/**
* Find a free cluster in the FAT and mark it as end-of-chain.
*
* @param fs Filesystem instance.
* @return Cluster number, or 0 if disk is full.
*/
static uint32_t fat32_alloc_cluster(fat32_fs_t *fs) {
for (uint32_t c = 2; c < fs->total_clusters + 2; c++) {
uint32_t val = fat32_read_fat(fs, c);
if (val == FAT32_FREE) {
fat32_write_fat(fs, c, FAT32_EOC);
/* Zero out the cluster */
uint8_t zero[SECTOR_SIZE];
memset(zero, 0, SECTOR_SIZE);
uint32_t first_sec = fs->data_start_lba +
(c - 2) * fs->sectors_per_cluster;
for (uint32_t s = 0; s < fs->sectors_per_cluster; s++) {
fat32_write_sectors(fs, first_sec + s, 1, zero);
}
return c;
}
}
return 0; /* Full */
}
/* ================================================================
* Cluster / sector helpers
* ================================================================ */
/**
* Convert a cluster number to its first sector (absolute LBA).
*/
static uint32_t cluster_to_lba(fat32_fs_t *fs, uint32_t cluster) {
return fs->data_start_lba + (cluster - 2) * fs->sectors_per_cluster;
}
/**
* Read all data from a cluster chain into a buffer.
*
* @param fs Filesystem instance.
* @param start Starting cluster.
* @param buf Output buffer.
* @param offset Byte offset to start reading from.
* @param size Maximum bytes to read.
* @return Bytes read, or -1 on error.
*/
static int32_t fat32_read_chain(fat32_fs_t *fs, uint32_t start,
uint32_t offset, uint32_t size,
void *buf) {
if (start < 2) return -1;
uint32_t bpc = fs->bytes_per_cluster;
uint32_t bytes_read = 0;
uint32_t cluster = start;
uint32_t pos = 0; /* Byte position in the chain */
/* Skip clusters until we reach the offset */
while (pos + bpc <= offset && cluster >= 2 && cluster < FAT32_EOC) {
pos += bpc;
cluster = fat32_read_fat(fs, cluster);
}
/* Read data */
uint8_t cluster_buf[SECTOR_SIZE]; /* Read sector by sector */
while (cluster >= 2 && cluster < FAT32_EOC && bytes_read < size) {
uint32_t lba = cluster_to_lba(fs, cluster);
for (uint32_t s = 0; s < fs->sectors_per_cluster && bytes_read < size; s++) {
uint32_t sec_start = pos;
uint32_t sec_end = pos + SECTOR_SIZE;
if (sec_end <= offset) {
/* This sector is entirely before our offset, skip */
pos += SECTOR_SIZE;
continue;
}
if (fat32_read_sectors(fs, lba + s, 1, cluster_buf) != 0) {
return (bytes_read > 0) ? (int32_t)bytes_read : -1;
}
/* Calculate how much of this sector we need */
uint32_t sect_off = 0;
if (offset > sec_start) {
sect_off = offset - sec_start;
}
uint32_t sect_avail = SECTOR_SIZE - sect_off;
uint32_t to_copy = size - bytes_read;
if (to_copy > sect_avail) to_copy = sect_avail;
memcpy((uint8_t *)buf + bytes_read, cluster_buf + sect_off, to_copy);
bytes_read += to_copy;
pos += SECTOR_SIZE;
}
cluster = fat32_read_fat(fs, cluster);
}
return (int32_t)bytes_read;
}
/**
* Write data to a cluster chain, extending it as needed.
*
* @param fs Filesystem instance.
* @param start Starting cluster (must be valid, >= 2).
* @param offset Byte offset to start writing at.
* @param size Bytes to write.
* @param buf Input data.
* @return Bytes written, or -1 on error.
*/
static int32_t fat32_write_chain(fat32_fs_t *fs, uint32_t start,
uint32_t offset, uint32_t size,
const void *buf) {
if (start < 2) return -1;
uint32_t bpc = fs->bytes_per_cluster;
uint32_t bytes_written = 0;
uint32_t cluster = start;
uint32_t prev_cluster = 0;
uint32_t pos = 0;
/* Navigate to the cluster containing the offset */
while (pos + bpc <= offset) {
if (cluster < 2 || cluster >= FAT32_EOC) {
/* Need to extend the chain */
uint32_t new_cluster = fat32_alloc_cluster(fs);
if (new_cluster == 0) return -1;
if (prev_cluster >= 2) {
fat32_write_fat(fs, prev_cluster, new_cluster);
}
cluster = new_cluster;
}
prev_cluster = cluster;
pos += bpc;
cluster = fat32_read_fat(fs, cluster);
}
/* Ensure current cluster is valid */
if (cluster < 2 || cluster >= FAT32_EOC) {
uint32_t new_cluster = fat32_alloc_cluster(fs);
if (new_cluster == 0) return -1;
if (prev_cluster >= 2) {
fat32_write_fat(fs, prev_cluster, new_cluster);
}
cluster = new_cluster;
}
/* Write data sector by sector */
uint8_t sector_buf[SECTOR_SIZE];
while (bytes_written < size) {
if (cluster < 2 || cluster >= FAT32_EOC) {
/* Extend chain */
uint32_t new_cluster = fat32_alloc_cluster(fs);
if (new_cluster == 0) break;
if (prev_cluster >= 2) {
fat32_write_fat(fs, prev_cluster, new_cluster);
}
cluster = new_cluster;
}
uint32_t lba = cluster_to_lba(fs, cluster);
for (uint32_t s = 0; s < fs->sectors_per_cluster && bytes_written < size; s++) {
uint32_t sec_start = pos;
if (sec_start + SECTOR_SIZE <= offset) {
pos += SECTOR_SIZE;
continue;
}
/* Read-modify-write for partial sectors */
if (fat32_read_sectors(fs, lba + s, 1, sector_buf) != 0) {
return (bytes_written > 0) ? (int32_t)bytes_written : -1;
}
uint32_t sect_off = 0;
if (offset > sec_start) {
sect_off = offset - sec_start;
}
uint32_t sect_avail = SECTOR_SIZE - sect_off;
uint32_t to_copy = size - bytes_written;
if (to_copy > sect_avail) to_copy = sect_avail;
memcpy(sector_buf + sect_off, (const uint8_t *)buf + bytes_written, to_copy);
if (fat32_write_sectors(fs, lba + s, 1, sector_buf) != 0) {
return (bytes_written > 0) ? (int32_t)bytes_written : -1;
}
bytes_written += to_copy;
offset += to_copy; /* Advance offset for subsequent sectors */
pos += SECTOR_SIZE;
}
prev_cluster = cluster;
pos = pos; /* pos already advanced in the loop */
cluster = fat32_read_fat(fs, cluster);
}
return (int32_t)bytes_written;
}
/* ================================================================
* Directory operations
* ================================================================ */
/**
* VFS node fs_data encodes the FAT32 context:
* - inode field: first cluster of the file/directory
* - fs_data: pointer to fat32_fs_t
*/
/**
* Convert an 8.3 short filename to a human-readable string.
* "HELLO TXT" -> "HELLO.TXT"
*/
static void sfn_to_name(const uint8_t sfn[11], char *out) {
int pos = 0;
/* Copy base name (bytes 0-7), trimming trailing spaces */
int base_end = 7;
while (base_end >= 0 && sfn[base_end] == ' ') base_end--;
for (int i = 0; i <= base_end; i++) {
char c = (char)sfn[i];
/* Convert to lowercase for display */
if (c >= 'A' && c <= 'Z') c = c - 'A' + 'a';
out[pos++] = c;
}
/* Append extension (bytes 8-10) if non-empty */
int ext_end = 10;
while (ext_end >= 8 && sfn[ext_end] == ' ') ext_end--;
if (ext_end >= 8) {
out[pos++] = '.';
for (int i = 8; i <= ext_end; i++) {
char c = (char)sfn[i];
if (c >= 'A' && c <= 'Z') c = c - 'A' + 'a';
out[pos++] = c;
}
}
out[pos] = '\0';
}
/**
* Convert a human-readable filename to 8.3 short format.
* "hello.txt" -> "HELLO TXT"
*/
static void name_to_sfn(const char *name, uint8_t sfn[11]) {
memset(sfn, ' ', 11);
int pos = 0;
int i = 0;
/* Base name (up to 8 chars) */
while (name[i] && name[i] != '.' && pos < 8) {
char c = name[i];
if (c >= 'a' && c <= 'z') c = c - 'a' + 'A';
sfn[pos++] = (uint8_t)c;
i++;
}
/* Extension */
if (name[i] == '.') {
i++;
pos = 8;
while (name[i] && pos < 11) {
char c = name[i];
if (c >= 'a' && c <= 'z') c = c - 'a' + 'A';
sfn[pos++] = (uint8_t)c;
i++;
}
}
}
/**
* Get the first cluster number from a directory entry.
*/
static uint32_t dirent_cluster(const fat32_dirent_t *de) {
return ((uint32_t)de->cluster_hi << 16) | de->cluster_lo;
}
/* ================================================================
* VFS operations
* ================================================================ */
/**
* Read file data for a FAT32 file.
*/
static int32_t fat32_vfs_read(vfs_node_t *node, uint32_t offset,
uint32_t size, void *buf) {
if (!node || !node->fs_data) return -1;
fat32_fs_t *fs = (fat32_fs_t *)node->fs_data;
uint32_t cluster = node->inode;
if (cluster < 2) return -1;
/* Don't read past file end */
if (offset >= node->size) return 0;
if (offset + size > node->size) {
size = node->size - offset;
}
return fat32_read_chain(fs, cluster, offset, size, buf);
}
/**
* Write file data for a FAT32 file.
*/
static int32_t fat32_vfs_write(vfs_node_t *node, uint32_t offset,
uint32_t size, const void *buf) {
if (!node || !node->fs_data) return -1;
fat32_fs_t *fs = (fat32_fs_t *)node->fs_data;
uint32_t cluster = node->inode;
if (cluster < 2) return -1;
int32_t written = fat32_write_chain(fs, cluster, offset, size, buf);
/* Update file size if we wrote past the end */
if (written > 0 && offset + (uint32_t)written > node->size) {
node->size = offset + (uint32_t)written;
/* TODO: update the directory entry on disk with new size */
}
return written;
}
/**
* Read directory entries from a FAT32 directory.
*/
static int fat32_vfs_readdir(vfs_node_t *dir, uint32_t idx,
vfs_dirent_t *out) {
if (!dir || !dir->fs_data) return -1;
fat32_fs_t *fs = (fat32_fs_t *)dir->fs_data;
/* inode == 0 means mount root: use the filesystem's root cluster */
uint32_t cluster = dir->inode;
if (cluster < 2) cluster = fs->root_cluster;
if (cluster < 2) return -1;
/* Read the directory cluster chain and iterate entries.
* We read 32-byte entries, skipping deleted (0xE5), LFN, and
* volume label entries. idx counts only valid file/dir entries. */
uint32_t count = 0;
uint32_t byte_pos = 0;
uint8_t sector_buf[SECTOR_SIZE];
uint32_t cur_cluster = cluster;
while (cur_cluster >= 2 && cur_cluster < FAT32_EOC) {
uint32_t lba = cluster_to_lba(fs, cur_cluster);
for (uint32_t s = 0; s < fs->sectors_per_cluster; s++) {
if (fat32_read_sectors(fs, lba + s, 1, sector_buf) != 0) {
return -1;
}
/* Each sector has 512/32 = 16 directory entries */
for (uint32_t e = 0; e < SECTOR_SIZE / 32; e++) {
fat32_dirent_t *de = (fat32_dirent_t *)(sector_buf + e * 32);
/* 0x00 = end of directory */
if (de->name[0] == 0x00) return -1;
/* 0xE5 = deleted entry */
if (de->name[0] == 0xE5) continue;
/* Skip LFN entries */
if ((de->attr & FAT32_ATTR_LFN) == FAT32_ATTR_LFN) continue;
/* Skip volume label */
if (de->attr & FAT32_ATTR_VOLUME_ID) continue;
/* Skip . and .. entries */
if (de->name[0] == '.' &&
(de->name[1] == ' ' || de->name[1] == '.')) {
continue;
}
if (count == idx) {
memset(out, 0, sizeof(vfs_dirent_t));
sfn_to_name(de->name, out->name);
out->type = (de->attr & FAT32_ATTR_DIRECTORY)
? VFS_DIRECTORY : VFS_FILE;
out->inode = dirent_cluster(de);
return 0;
}
count++;
}
}
cur_cluster = fat32_read_fat(fs, cur_cluster);
}
return -1; /* No more entries */
}
/**
* Look up a child by name in a FAT32 directory.
*/
static int fat32_vfs_finddir(vfs_node_t *dir, const char *name,
vfs_node_t *out) {
if (!dir || !dir->fs_data) return -1;
fat32_fs_t *fs = (fat32_fs_t *)dir->fs_data;
/* inode == 0 means mount root: use the filesystem's root cluster */
uint32_t cluster = dir->inode;
if (cluster < 2) cluster = fs->root_cluster;
if (cluster < 2) return -1;
/* Convert search name to 8.3 for comparison */
uint8_t search_sfn[11];
name_to_sfn(name, search_sfn);
uint8_t sector_buf[SECTOR_SIZE];
uint32_t cur_cluster = cluster;
while (cur_cluster >= 2 && cur_cluster < FAT32_EOC) {
uint32_t lba = cluster_to_lba(fs, cur_cluster);
for (uint32_t s = 0; s < fs->sectors_per_cluster; s++) {
if (fat32_read_sectors(fs, lba + s, 1, sector_buf) != 0) {
return -1;
}
for (uint32_t e = 0; e < SECTOR_SIZE / 32; e++) {
fat32_dirent_t *de = (fat32_dirent_t *)(sector_buf + e * 32);
if (de->name[0] == 0x00) return -1;
if (de->name[0] == 0xE5) continue;
if ((de->attr & FAT32_ATTR_LFN) == FAT32_ATTR_LFN) continue;
if (de->attr & FAT32_ATTR_VOLUME_ID) continue;
/* Compare both 8.3 format and human-readable format */
if (memcmp(de->name, search_sfn, 11) == 0) {
/* Match by 8.3 name */
goto found;
}
/* Also compare with the lowercase display name */
char display[13];
sfn_to_name(de->name, display);
if (strcmp(display, name) == 0) {
goto found;
}
continue;
found:
memset(out, 0, sizeof(vfs_node_t));
sfn_to_name(de->name, out->name);
out->inode = dirent_cluster(de);
out->size = de->file_size;
out->type = (de->attr & FAT32_ATTR_DIRECTORY)
? VFS_DIRECTORY : VFS_FILE;
out->fs_data = fs;
return 0;
}
}
cur_cluster = fat32_read_fat(fs, cur_cluster);
}
return -1;
}
/** VFS operations for FAT32. */
static vfs_fs_ops_t fat32_vfs_ops = {
.open = NULL,
.close = NULL,
.read = fat32_vfs_read,
.write = fat32_vfs_write,
.readdir = fat32_vfs_readdir,
.finddir = fat32_vfs_finddir,
};
/* ================================================================
* Mount
* ================================================================ */
int fat32_mount_device(devicefs_device_t *dev, const char *mount_path) {
if (!dev || !mount_path) return -1;
if (fat32_mount_count >= FAT32_MAX_MOUNTS) {
offset_print(" FAT32: too many mounts\n");
return -1;
}
fat32_fs_t *fs = &fat32_mounts[fat32_mount_count];
memset(fs, 0, sizeof(fat32_fs_t));
fs->dev = dev;
/* Read the boot sector / BPB */
uint8_t boot_sector[SECTOR_SIZE];
if (fat32_read_sectors(fs, 0, 1, boot_sector) != 0) {
offset_print(" FAT32: failed to read boot sector\n");
return -1;
}
/* Copy BPB */
memcpy(&fs->bpb, boot_sector, sizeof(fat32_bpb_t));
/* Validate */
if (fs->bpb.bytes_per_sector != 512) {
offset_print(" FAT32: unsupported sector size\n");
return -1;
}
if (fs->bpb.fat_size_32 == 0) {
offset_print(" FAT32: fat_size_32 is 0, not FAT32\n");
return -1;
}
/* Check boot sector signature */
if (boot_sector[510] != 0x55 || boot_sector[511] != 0xAA) {
offset_print(" FAT32: invalid boot sector signature\n");
return -1;
}
/* Compute layout */
fs->sectors_per_cluster = fs->bpb.sectors_per_cluster;
fs->bytes_per_cluster = fs->sectors_per_cluster * SECTOR_SIZE;
fs->fat_start_lba = fs->bpb.reserved_sectors;
fs->data_start_lba = fs->fat_start_lba +
(fs->bpb.num_fats * fs->bpb.fat_size_32);
fs->root_cluster = fs->bpb.root_cluster;
uint32_t total_sectors = fs->bpb.total_sectors_32;
if (total_sectors == 0) total_sectors = fs->bpb.total_sectors_16;
uint32_t data_sectors = total_sectors - fs->data_start_lba;
fs->total_clusters = data_sectors / fs->sectors_per_cluster;
offset_print(" FAT32: mounted ");
offset_print(mount_path);
offset_print(" (");
print_hex(fs->total_clusters);
offset_print(" clusters, ");
print_hex(fs->sectors_per_cluster);
offset_print(" sec/clust)\n");
/* Register with VFS */
if (vfs_mount(mount_path, &fat32_vfs_ops, fs) != 0) {
offset_print(" FAT32: VFS mount failed\n");
return -1;
}
fat32_mount_count++;
return 0;
}

116
src/fat32.h Normal file
View File

@@ -0,0 +1,116 @@
/**
* @file fat32.h
* @brief FAT32 filesystem driver.
*
* Provides a VFS driver for reading and writing FAT32 filesystems
* on block devices. Supports long filenames (LFN), directory traversal,
* file read/write, and file creation.
*
* Usage:
* fat32_mount_device(dev, mount_path);
*
* This reads the BPB from sector 0 of the device, validates the FAT32
* signature, and mounts the filesystem at the specified VFS path.
*/
#ifndef FAT32_H
#define FAT32_H
#include <stdint.h>
#include "devicefs.h"
/** FAT32 cluster chain end marker. */
#define FAT32_EOC 0x0FFFFFF8
#define FAT32_FREE 0x00000000
#define FAT32_BAD 0x0FFFFFF7
/** Maximum filename length (8.3 short name). */
#define FAT32_SFN_LEN 11
/** Directory entry attributes. */
#define FAT32_ATTR_READ_ONLY 0x01
#define FAT32_ATTR_HIDDEN 0x02
#define FAT32_ATTR_SYSTEM 0x04
#define FAT32_ATTR_VOLUME_ID 0x08
#define FAT32_ATTR_DIRECTORY 0x10
#define FAT32_ATTR_ARCHIVE 0x20
#define FAT32_ATTR_LFN (FAT32_ATTR_READ_ONLY | FAT32_ATTR_HIDDEN | \
FAT32_ATTR_SYSTEM | FAT32_ATTR_VOLUME_ID)
/**
* FAT32 BIOS Parameter Block (BPB) — packed, read directly from disk.
* Covers the first 90 bytes of the boot sector.
*/
typedef struct __attribute__((packed)) fat32_bpb {
uint8_t jmp_boot[3]; /**< Jump instruction. */
uint8_t oem_name[8]; /**< OEM identifier. */
uint16_t bytes_per_sector; /**< Usually 512. */
uint8_t sectors_per_cluster;
uint16_t reserved_sectors; /**< Sectors before the first FAT. */
uint8_t num_fats; /**< Number of FATs (usually 2). */
uint16_t root_entry_count; /**< 0 for FAT32. */
uint16_t total_sectors_16; /**< 0 for FAT32. */
uint8_t media_type;
uint16_t fat_size_16; /**< 0 for FAT32. */
uint16_t sectors_per_track;
uint16_t num_heads;
uint32_t hidden_sectors;
uint32_t total_sectors_32;
/* FAT32 extended fields */
uint32_t fat_size_32; /**< Sectors per FAT. */
uint16_t ext_flags;
uint16_t fs_version;
uint32_t root_cluster; /**< First cluster of root directory (usually 2). */
uint16_t fs_info; /**< Sector number of FSInfo structure. */
uint16_t backup_boot; /**< Sector of backup boot sector. */
uint8_t reserved[12];
uint8_t drive_number;
uint8_t reserved1;
uint8_t boot_sig; /**< 0x29 if next 3 fields are valid. */
uint32_t volume_id;
uint8_t volume_label[11];
uint8_t fs_type[8]; /**< "FAT32 " */
} fat32_bpb_t;
/**
* FAT32 directory entry (32 bytes, packed).
*/
typedef struct __attribute__((packed)) fat32_dirent {
uint8_t name[11]; /**< 8.3 short name. */
uint8_t attr; /**< File attributes. */
uint8_t nt_reserved; /**< Reserved (case info). */
uint8_t create_time_tenth; /**< Creation time in tenths of second. */
uint16_t create_time; /**< Creation time. */
uint16_t create_date; /**< Creation date. */
uint16_t access_date; /**< Last access date. */
uint16_t cluster_hi; /**< High 16 bits of first cluster. */
uint16_t write_time; /**< Last write time. */
uint16_t write_date; /**< Last write date. */
uint16_t cluster_lo; /**< Low 16 bits of first cluster. */
uint32_t file_size; /**< File size in bytes. */
} fat32_dirent_t;
/**
* FAT32 filesystem instance (per-mount context).
*/
typedef struct fat32_fs {
devicefs_device_t *dev; /**< Underlying block device. */
fat32_bpb_t bpb; /**< Cached BPB. */
uint32_t fat_start_lba; /**< First sector of the FAT. */
uint32_t data_start_lba; /**< First sector of the data region. */
uint32_t sectors_per_cluster;
uint32_t bytes_per_cluster;
uint32_t root_cluster; /**< Root directory first cluster. */
uint32_t total_clusters;
} fat32_fs_t;
/**
* Mount a FAT32 filesystem from a block device.
*
* @param dev The devicefs block device to read from.
* @param mount_path Where to mount in the VFS (e.g., "/mnt").
* @return 0 on success, -1 on error.
*/
int fat32_mount_device(devicefs_device_t *dev, const char *mount_path);
#endif /* FAT32_H */

688
src/floppy.c Normal file
View File

@@ -0,0 +1,688 @@
/**
* @file floppy.c
* @brief Floppy disk controller driver implementation.
*
* Drives the Intel 82077AA-compatible floppy disk controller using
* ISA DMA channel 2 for data transfers and IRQ 6 for completion
* notification.
*
* Supports 1.44 MB 3.5" HD floppies. The driver detects drives via
* CMOS register 0x10, initializes the controller, and registers each
* detected drive with the devicefs subsystem as a "floppy" class
* block device (floppy1, floppy2, ...).
*
* LBA addressing is converted to CHS for the floppy controller.
*/
#include "floppy.h"
#include "port_io.h"
#include "pic.h"
#include "devicefs.h"
#include "driver.h"
#include <string.h>
/* Debug print helpers defined in kernel.c */
extern void offset_print(const char *str);
extern void print_hex(uint32_t val);
/* ================================================================
* Global state
* ================================================================ */
/** Detected floppy drives. */
static floppy_drive_t floppy_drives[FLOPPY_MAX_DRIVES];
/** Volatile flag set by IRQ 6 handler. */
static volatile int floppy_irq_received = 0;
/** DMA bounce buffer — must be in the first 16 MB of physical memory
* and must not cross a 64 KB boundary. We use a static buffer that
* the linker places in the kernel BSS (which lives in low memory). */
static uint8_t dma_buffer[FLOPPY_SECTOR_SIZE] __attribute__((aligned(512)));
/* ================================================================
* Low-level helpers
* ================================================================ */
/**
* Read CMOS register.
*
* @param reg CMOS register address (0x00-0x7F).
* @return Register value.
*/
static uint8_t cmos_read(uint8_t reg) {
outb(0x70, reg);
/* Small delay for CMOS to respond */
inb(0x80);
return inb(0x71);
}
/**
* Wait for IRQ 6 with timeout.
* The floppy controller fires IRQ 6 on command completion.
*
* If interrupts are currently disabled (e.g. during early init before
* the kernel calls STI), we temporarily enable them so IRQ 6 can
* actually fire, then restore the previous interrupt state.
*
* @return 0 on success (IRQ received), -1 on timeout.
*/
static int fdc_wait_irq(void) {
/* Check whether interrupts are currently enabled (IF flag) */
uint32_t eflags;
asm volatile("pushfl; popl %0" : "=r"(eflags));
int need_sti = !(eflags & 0x200);
if (need_sti) {
asm volatile("sti");
}
int timeout = 5000000;
while (!floppy_irq_received && --timeout > 0) {
/* Busy wait — in a real OS we'd use a proper sleep/wait */
asm volatile("pause");
}
if (need_sti) {
asm volatile("cli");
}
if (timeout == 0) {
offset_print(" FLOPPY: IRQ timeout\n");
return -1;
}
floppy_irq_received = 0;
return 0;
}
/**
* Write a byte to the FDC FIFO register.
* Waits for the controller to be ready (MSR RQM=1, DIO=0).
*
* @param val Byte to write.
* @return 0 on success, -1 on timeout.
*/
static int fdc_write_byte(uint8_t val) {
int timeout = 500000;
while (--timeout > 0) {
uint8_t msr = inb(FDC_MSR);
if ((msr & (MSR_RQM | MSR_DIO)) == MSR_RQM) {
outb(FDC_FIFO, val);
return 0;
}
}
offset_print(" FLOPPY: write timeout\n");
return -1;
}
/**
* Read a byte from the FDC FIFO register.
* Waits for the controller to have data ready (MSR RQM=1, DIO=1).
*
* @return Byte read, or -1 on timeout.
*/
static int fdc_read_byte(void) {
int timeout = 500000;
while (--timeout > 0) {
uint8_t msr = inb(FDC_MSR);
if ((msr & (MSR_RQM | MSR_DIO)) == (MSR_RQM | MSR_DIO)) {
return (int)inb(FDC_FIFO);
}
}
offset_print(" FLOPPY: read timeout\n");
return -1;
}
/**
* Issue a SENSE INTERRUPT command and read the result.
*
* @param st0_out Output: ST0 byte.
* @param cyl_out Output: current cylinder.
*/
static void fdc_sense_interrupt(uint8_t *st0_out, uint8_t *cyl_out) {
fdc_write_byte(FDC_CMD_SENSE_INT);
int st0 = fdc_read_byte();
int cyl = fdc_read_byte();
if (st0_out) *st0_out = (uint8_t)(st0 >= 0 ? st0 : 0xFF);
if (cyl_out) *cyl_out = (uint8_t)(cyl >= 0 ? cyl : 0xFF);
}
/* ================================================================
* Motor control
* ================================================================ */
/**
* Turn the motor on for a drive.
*
* @param drive Drive number (0 or 1).
*/
static void fdc_motor_on(uint8_t drive) {
if (drive >= FLOPPY_MAX_DRIVES) return;
if (floppy_drives[drive].motor_on) return;
uint8_t dor = drive | DOR_RESET | DOR_DMA_IRQ |
(DOR_MOTOR_A << drive);
outb(FDC_DOR, dor);
/* Wait ~500 ms for motor spin-up (busy wait in timer ticks) */
for (volatile int i = 0; i < 2000000; i++) {
asm volatile("pause");
}
floppy_drives[drive].motor_on = 1;
}
/**
* Turn the motor off for a drive.
*
* @param drive Drive number (0 or 1).
*/
static void fdc_motor_off(uint8_t drive) {
if (drive >= FLOPPY_MAX_DRIVES) return;
uint8_t dor = drive | DOR_RESET | DOR_DMA_IRQ;
outb(FDC_DOR, dor);
floppy_drives[drive].motor_on = 0;
}
/* ================================================================
* DMA setup for ISA DMA channel 2
* ================================================================ */
/**
* Set up ISA DMA channel 2 for a floppy transfer.
*
* The floppy controller uses DMA channel 2. We configure the DMA
* controller with the physical address of our bounce buffer and the
* transfer length.
*
* @param addr Physical address of the DMA buffer (must be < 16 MB).
* @param len Transfer length in bytes (minus 1 is written to count).
* @param read 1 for read (disk→memory), 0 for write (memory→disk).
*/
static void dma_setup(uint32_t addr, uint16_t len, int read) {
/* Mask DMA channel 2 */
outb(0x0A, 0x06);
/* Reset flip-flop */
outb(0x0C, 0xFF);
/* Mode: single transfer, auto-init disabled, increment, channel 2
* Read = 0x46 (single, addr incr, write-to-memory = FDC read)
* Write = 0x4A (single, addr incr, read-from-memory = FDC write) */
outb(0x0B, read ? 0x46 : 0x4A);
/* Address (low 16 bits via channel 2 address reg 0x04) */
outb(0x04, (uint8_t)(addr & 0xFF));
outb(0x04, (uint8_t)((addr >> 8) & 0xFF));
/* Page register for channel 2 (port 0x81) — bits 16-23 of address */
outb(0x81, (uint8_t)((addr >> 16) & 0xFF));
/* Count (len - 1, low byte first) */
outb(0x05, (uint8_t)((len - 1) & 0xFF));
outb(0x05, (uint8_t)(((len - 1) >> 8) & 0xFF));
/* Unmask DMA channel 2 */
outb(0x0A, 0x02);
}
/* ================================================================
* CHS conversion
* ================================================================ */
/**
* Convert an LBA address to CHS for a 1.44 MB floppy.
*
* @param lba Logical block address.
* @param cyl_out Output: cylinder number.
* @param head_out Output: head number.
* @param sec_out Output: sector number (1-based).
*/
static void lba_to_chs(uint32_t lba, uint8_t *cyl_out,
uint8_t *head_out, uint8_t *sec_out) {
*cyl_out = (uint8_t)(lba / (FLOPPY_HEADS * FLOPPY_SECTORS_PER_TRACK));
uint32_t tmp = lba % (FLOPPY_HEADS * FLOPPY_SECTORS_PER_TRACK);
*head_out = (uint8_t)(tmp / FLOPPY_SECTORS_PER_TRACK);
*sec_out = (uint8_t)((tmp % FLOPPY_SECTORS_PER_TRACK) + 1);
}
/* ================================================================
* Seek and calibrate
* ================================================================ */
/**
* Recalibrate a drive (seek to track 0).
*
* @param drive Drive number.
* @return 0 on success, -1 on failure.
*/
static int fdc_recalibrate(uint8_t drive) {
floppy_irq_received = 0;
fdc_write_byte(FDC_CMD_RECALIBRATE);
fdc_write_byte(drive);
if (fdc_wait_irq() != 0) return -1;
uint8_t st0, cyl;
fdc_sense_interrupt(&st0, &cyl);
if (cyl != 0) {
offset_print(" FLOPPY: recalibrate failed, cyl=");
print_hex(cyl);
offset_print("\n");
return -1;
}
floppy_drives[drive].current_track = 0;
return 0;
}
/**
* Seek to a specific cylinder.
*
* @param drive Drive number.
* @param cyl Target cylinder.
* @return 0 on success, -1 on failure.
*/
static int fdc_seek(uint8_t drive, uint8_t cyl) {
if (floppy_drives[drive].current_track == cyl) return 0;
floppy_irq_received = 0;
fdc_write_byte(FDC_CMD_SEEK);
fdc_write_byte((0 << 2) | drive); /* Head 0, drive N */
fdc_write_byte(cyl);
if (fdc_wait_irq() != 0) return -1;
uint8_t st0, res_cyl;
fdc_sense_interrupt(&st0, &res_cyl);
if (res_cyl != cyl) {
offset_print(" FLOPPY: seek failed, wanted cyl=");
print_hex(cyl);
offset_print(" got=");
print_hex(res_cyl);
offset_print("\n");
return -1;
}
floppy_drives[drive].current_track = cyl;
return 0;
}
/* ================================================================
* Read / Write sector
* ================================================================ */
/**
* Read a single sector from a floppy drive using DMA.
*
* @param drive Drive number (0 or 1).
* @param lba Logical sector address.
* @param buf Output buffer (512 bytes).
* @return 0 on success, -1 on failure.
*/
static int fdc_read_sector(uint8_t drive, uint32_t lba, void *buf) {
if (lba >= FLOPPY_TOTAL_SECTORS) return -1;
uint8_t cyl, head, sec;
lba_to_chs(lba, &cyl, &head, &sec);
fdc_motor_on(drive);
/* Seek to the correct cylinder */
if (fdc_seek(drive, cyl) != 0) {
fdc_motor_off(drive);
return -1;
}
/* Set up DMA for read (disk → memory) */
uint32_t dma_addr = (uint32_t)dma_buffer;
dma_setup(dma_addr, FLOPPY_SECTOR_SIZE, 1);
/* Issue READ DATA command */
floppy_irq_received = 0;
fdc_write_byte(FDC_CMD_READ_DATA);
fdc_write_byte((head << 2) | drive);
fdc_write_byte(cyl);
fdc_write_byte(head);
fdc_write_byte(sec);
fdc_write_byte(2); /* Sector size code: 2 = 512 bytes */
fdc_write_byte(FLOPPY_SECTORS_PER_TRACK); /* End of track */
fdc_write_byte(0x1B); /* GPL: gap length */
fdc_write_byte(0xFF); /* DTL: unused when sector size != 0 */
/* Wait for command completion */
if (fdc_wait_irq() != 0) {
fdc_motor_off(drive);
return -1;
}
/* Read result phase (7 bytes) */
uint8_t st0 = (uint8_t)fdc_read_byte();
uint8_t st1 = (uint8_t)fdc_read_byte();
uint8_t st2 = (uint8_t)fdc_read_byte();
(void)fdc_read_byte(); /* C */
(void)fdc_read_byte(); /* H */
(void)fdc_read_byte(); /* R */
(void)fdc_read_byte(); /* N */
/* Check for errors */
if ((st0 & 0xC0) != 0 || st1 != 0 || st2 != 0) {
offset_print(" FLOPPY: read error st0=");
print_hex(st0);
offset_print(" st1=");
print_hex(st1);
offset_print(" st2=");
print_hex(st2);
offset_print("\n");
fdc_motor_off(drive);
return -1;
}
/* Copy from DMA buffer to caller's buffer */
memcpy(buf, dma_buffer, FLOPPY_SECTOR_SIZE);
fdc_motor_off(drive);
return 0;
}
/**
* Write a single sector to a floppy drive using DMA.
*
* @param drive Drive number (0 or 1).
* @param lba Logical sector address.
* @param buf Input buffer (512 bytes).
* @return 0 on success, -1 on failure.
*/
static int fdc_write_sector(uint8_t drive, uint32_t lba, const void *buf) {
if (lba >= FLOPPY_TOTAL_SECTORS) return -1;
uint8_t cyl, head, sec;
lba_to_chs(lba, &cyl, &head, &sec);
fdc_motor_on(drive);
if (fdc_seek(drive, cyl) != 0) {
fdc_motor_off(drive);
return -1;
}
/* Copy data to DMA buffer */
memcpy(dma_buffer, buf, FLOPPY_SECTOR_SIZE);
/* Set up DMA for write (memory → disk) */
uint32_t dma_addr = (uint32_t)dma_buffer;
dma_setup(dma_addr, FLOPPY_SECTOR_SIZE, 0);
/* Issue WRITE DATA command */
floppy_irq_received = 0;
fdc_write_byte(FDC_CMD_WRITE_DATA);
fdc_write_byte((head << 2) | drive);
fdc_write_byte(cyl);
fdc_write_byte(head);
fdc_write_byte(sec);
fdc_write_byte(2); /* Sector size code: 512 bytes */
fdc_write_byte(FLOPPY_SECTORS_PER_TRACK);
fdc_write_byte(0x1B);
fdc_write_byte(0xFF);
if (fdc_wait_irq() != 0) {
fdc_motor_off(drive);
return -1;
}
/* Read result phase */
uint8_t st0 = (uint8_t)fdc_read_byte();
uint8_t st1 = (uint8_t)fdc_read_byte();
uint8_t st2 = (uint8_t)fdc_read_byte();
(void)fdc_read_byte();
(void)fdc_read_byte();
(void)fdc_read_byte();
(void)fdc_read_byte();
if ((st0 & 0xC0) != 0 || st1 != 0 || st2 != 0) {
offset_print(" FLOPPY: write error st0=");
print_hex(st0);
offset_print(" st1=");
print_hex(st1);
offset_print(" st2=");
print_hex(st2);
offset_print("\n");
fdc_motor_off(drive);
return -1;
}
fdc_motor_off(drive);
return 0;
}
/* ================================================================
* Devicefs block operations
* ================================================================ */
/**
* Read sectors from a floppy drive.
* dev_data is a pointer to the floppy_drive_t.
*/
static int floppy_block_read(void *dev_data, uint32_t lba,
uint32_t count, void *buf) {
floppy_drive_t *drv = (floppy_drive_t *)dev_data;
if (!drv || !drv->present) return -1;
uint8_t *p = (uint8_t *)buf;
for (uint32_t i = 0; i < count; i++) {
if (fdc_read_sector(drv->drive_num, lba + i, p) != 0) {
return -1;
}
p += FLOPPY_SECTOR_SIZE;
}
return 0;
}
/**
* Write sectors to a floppy drive.
*/
static int floppy_block_write(void *dev_data, uint32_t lba,
uint32_t count, const void *buf) {
floppy_drive_t *drv = (floppy_drive_t *)dev_data;
if (!drv || !drv->present) return -1;
const uint8_t *p = (const uint8_t *)buf;
for (uint32_t i = 0; i < count; i++) {
if (fdc_write_sector(drv->drive_num, lba + i, p) != 0) {
return -1;
}
p += FLOPPY_SECTOR_SIZE;
}
return 0;
}
/**
* Get floppy sector size (always 512).
*/
static uint32_t floppy_block_sector_size(void *dev_data) {
(void)dev_data;
return FLOPPY_SECTOR_SIZE;
}
/**
* Get total sector count for a 1.44 MB floppy.
*/
static uint32_t floppy_block_sector_count(void *dev_data) {
(void)dev_data;
return FLOPPY_TOTAL_SECTORS;
}
/** Block device operations for floppy drives. */
static devicefs_block_ops_t floppy_block_ops = {
.read_sectors = floppy_block_read,
.write_sectors = floppy_block_write,
.sector_size = floppy_block_sector_size,
.sector_count = floppy_block_sector_count,
};
/* ================================================================
* Controller initialization
* ================================================================ */
/**
* Reset the floppy disk controller.
*
* Toggles the RESET bit in the DOR and re-initializes the controller.
*
* @return 0 on success, -1 on failure.
*/
static int fdc_reset(void) {
/* Enter reset state */
outb(FDC_DOR, 0x00);
/* Small delay */
for (volatile int i = 0; i < 100000; i++) {
asm volatile("pause");
}
/* Exit reset: enable DMA/IRQ, select drive 0, motor off */
floppy_irq_received = 0;
outb(FDC_DOR, DOR_RESET | DOR_DMA_IRQ);
/* Controller generates IRQ 6 after reset */
if (fdc_wait_irq() != 0) {
offset_print(" FLOPPY: no IRQ after reset\n");
return -1;
}
/* Sense interrupt for each of the 4 possible drives */
for (int i = 0; i < 4; i++) {
uint8_t st0, cyl;
fdc_sense_interrupt(&st0, &cyl);
}
/* Set data rate to 500 kbps (for 1.44 MB HD floppies) */
outb(FDC_CCR, 0x00);
/* SPECIFY command: step rate = 3 ms, head unload = 240 ms,
* head load = 16 ms, non-DMA mode = 0 */
fdc_write_byte(FDC_CMD_SPECIFY);
fdc_write_byte(0xDF); /* SRT=3ms, HUT=240ms */
fdc_write_byte(0x02); /* HLT=16ms, NDMA=0 */
return 0;
}
/* ================================================================
* Driver framework integration
* ================================================================ */
/**
* Probe for floppy controller.
* Reads CMOS to determine if any floppy drives are configured.
*/
static driver_probe_result_t floppy_probe(void) {
uint8_t cmos_floppy = cmos_read(0x10);
uint8_t drive_a = (cmos_floppy >> 4) & 0x0F;
uint8_t drive_b = cmos_floppy & 0x0F;
if (drive_a == CMOS_FLOPPY_NONE && drive_b == CMOS_FLOPPY_NONE) {
offset_print(" FLOPPY: no drives in CMOS\n");
return DRIVER_PROBE_NOT_FOUND;
}
return DRIVER_PROBE_OK;
}
/**
* Initialize the floppy controller and register detected drives.
*/
static int floppy_driver_init(void) {
memset(floppy_drives, 0, sizeof(floppy_drives));
/* Read CMOS for drive types */
uint8_t cmos_floppy = cmos_read(0x10);
uint8_t types[2] = {
(cmos_floppy >> 4) & 0x0F,
cmos_floppy & 0x0F,
};
/* Unmask IRQ 6 in the PIC */
pic_clear_mask(6);
/* Reset the controller */
if (fdc_reset() != 0) {
offset_print(" FLOPPY: controller reset failed\n");
return -1;
}
/* Register each detected drive */
int found = 0;
for (int i = 0; i < 2; i++) {
if (types[i] == CMOS_FLOPPY_NONE) continue;
/* We only fully support 1.44 MB for now, but register others too */
floppy_drives[i].drive_num = (uint8_t)i;
floppy_drives[i].cmos_type = types[i];
floppy_drives[i].present = 1;
floppy_drives[i].motor_on = 0;
floppy_drives[i].current_track = 0xFF; /* Unknown */
const char *type_str;
switch (types[i]) {
case CMOS_FLOPPY_360K: type_str = "360K 5.25\""; break;
case CMOS_FLOPPY_1200K: type_str = "1.2M 5.25\""; break;
case CMOS_FLOPPY_720K: type_str = "720K 3.5\""; break;
case CMOS_FLOPPY_1440K: type_str = "1.44M 3.5\""; break;
case CMOS_FLOPPY_2880K: type_str = "2.88M 3.5\""; break;
default: type_str = "unknown"; break;
}
offset_print(" FLOPPY: drive ");
print_hex(i);
offset_print(": ");
offset_print(type_str);
offset_print("\n");
/* Recalibrate the drive */
fdc_motor_on((uint8_t)i);
fdc_recalibrate((uint8_t)i);
fdc_motor_off((uint8_t)i);
/* Register with devicefs */
devicefs_register_block("floppy", &floppy_block_ops,
&floppy_drives[i]);
found++;
}
if (found == 0) {
offset_print(" FLOPPY: no usable drives found\n");
} else {
offset_print(" FLOPPY: ");
print_hex((uint32_t)found);
offset_print(" drive(s) initialized\n");
}
return 0;
}
/* ================================================================
* IRQ handler
* ================================================================ */
void floppy_irq(void) {
floppy_irq_received = 1;
}
/* ================================================================
* Public API & driver registration
* ================================================================ */
int floppy_init(void) {
return floppy_driver_init();
}
/** Driver descriptor. */
static const driver_t floppy_driver = {
.name = "floppy",
.probe = floppy_probe,
.init = floppy_driver_init,
};
REGISTER_DRIVER(floppy_driver);

122
src/floppy.h Normal file
View File

@@ -0,0 +1,122 @@
/**
* @file floppy.h
* @brief Floppy disk controller driver.
*
* Drives the Intel 82077AA-compatible floppy disk controller.
* Detects floppy drives via CMOS and registers them with the devicefs
* subsystem as block devices with class "floppy" (floppy1, floppy2, ...).
*
* Supports 1.44 MB 3.5" HD floppies (18 sectors/track, 80 tracks, 2 heads).
* Uses IRQ 6 for command completion notification and DMA channel 2 for
* data transfer.
*/
#ifndef FLOPPY_H
#define FLOPPY_H
#include <stdint.h>
/* ================================================================
* Floppy controller I/O port registers (primary controller)
* ================================================================ */
#define FDC_BASE 0x3F0 /**< Base I/O port of primary FDC. */
#define FDC_SRA 0x3F0 /**< Status Register A (read-only). */
#define FDC_SRB 0x3F1 /**< Status Register B (read-only). */
#define FDC_DOR 0x3F2 /**< Digital Output Register. */
#define FDC_TDR 0x3F3 /**< Tape Drive Register. */
#define FDC_MSR 0x3F4 /**< Main Status Register (read-only). */
#define FDC_DSR 0x3F4 /**< Data Rate Select Register (write-only). */
#define FDC_FIFO 0x3F5 /**< Data (FIFO) register. */
#define FDC_DIR 0x3F7 /**< Digital Input Register (read-only). */
#define FDC_CCR 0x3F7 /**< Configuration Control Register (write-only). */
/* ================================================================
* Digital Output Register (DOR) bits
* ================================================================ */
#define DOR_DRIVE_SEL_MASK 0x03 /**< Drive select (0-3). */
#define DOR_RESET 0x04 /**< 0 = enter reset, 1 = normal operation. */
#define DOR_DMA_IRQ 0x08 /**< 1 = enable DMA and IRQ. */
#define DOR_MOTOR_A 0x10 /**< Motor enable for drive 0. */
#define DOR_MOTOR_B 0x20 /**< Motor enable for drive 1. */
#define DOR_MOTOR_C 0x40 /**< Motor enable for drive 2. */
#define DOR_MOTOR_D 0x80 /**< Motor enable for drive 3. */
/* ================================================================
* Main Status Register (MSR) bits
* ================================================================ */
#define MSR_ACTA 0x01 /**< Drive 0 is seeking. */
#define MSR_ACTB 0x02 /**< Drive 1 is seeking. */
#define MSR_ACTC 0x04 /**< Drive 2 is seeking. */
#define MSR_ACTD 0x08 /**< Drive 3 is seeking. */
#define MSR_CB 0x10 /**< Command busy. */
#define MSR_NDMA 0x20 /**< Non-DMA mode. */
#define MSR_DIO 0x40 /**< Data direction: 1 = FDC→CPU, 0 = CPU→FDC. */
#define MSR_RQM 0x80 /**< Ready for data transfer. */
/* ================================================================
* Floppy commands
* ================================================================ */
#define FDC_CMD_SPECIFY 0x03 /**< Specify step rate, head load/unload. */
#define FDC_CMD_WRITE_DATA 0xC5 /**< Write Data (MT+MFM). */
#define FDC_CMD_READ_DATA 0xE6 /**< Read Data (MT+MFM+SK). */
#define FDC_CMD_RECALIBRATE 0x07 /**< Recalibrate (seek to track 0). */
#define FDC_CMD_SENSE_INT 0x08 /**< Sense Interrupt Status. */
#define FDC_CMD_SEEK 0x0F /**< Seek to a track. */
#define FDC_CMD_VERSION 0x10 /**< Get controller version. */
#define FDC_CMD_CONFIGURE 0x13 /**< Configure controller. */
#define FDC_CMD_LOCK 0x94 /**< Lock configuration. */
/* ================================================================
* Geometry for 1.44 MB 3.5" HD floppy
* ================================================================ */
#define FLOPPY_SECTORS_PER_TRACK 18 /**< Sectors per track. */
#define FLOPPY_HEADS 2 /**< Number of heads. */
#define FLOPPY_TRACKS 80 /**< Number of cylinders/tracks. */
#define FLOPPY_SECTOR_SIZE 512 /**< Bytes per sector. */
#define FLOPPY_TOTAL_SECTORS (FLOPPY_SECTORS_PER_TRACK * FLOPPY_HEADS * FLOPPY_TRACKS)
/* ================================================================
* CMOS floppy type codes
* ================================================================ */
#define CMOS_FLOPPY_NONE 0x00 /**< No drive. */
#define CMOS_FLOPPY_360K 0x01 /**< 5.25" 360 KB. */
#define CMOS_FLOPPY_1200K 0x02 /**< 5.25" 1.2 MB. */
#define CMOS_FLOPPY_720K 0x03 /**< 3.5" 720 KB. */
#define CMOS_FLOPPY_1440K 0x04 /**< 3.5" 1.44 MB. */
#define CMOS_FLOPPY_2880K 0x05 /**< 3.5" 2.88 MB. */
/** Maximum supported floppy drives. */
#define FLOPPY_MAX_DRIVES 2
/**
* Per-drive state.
*/
typedef struct floppy_drive {
uint8_t drive_num; /**< Drive number (0 or 1). */
uint8_t cmos_type; /**< CMOS type code. */
uint8_t present; /**< 1 if drive is detected and usable. */
uint8_t motor_on; /**< 1 if motor is currently running. */
uint8_t current_track; /**< Last sought cylinder (cached). */
} floppy_drive_t;
/**
* Initialize the floppy disk controller driver.
* Detects drives via CMOS and registers them with devicefs.
*
* @return 0 on success, non-zero on failure.
*/
int floppy_init(void);
/**
* IRQ 6 handler — called from isr_handler when vector 38 fires.
*/
void floppy_irq(void);
#endif /* FLOPPY_H */

450
src/graphics.c Normal file
View File

@@ -0,0 +1,450 @@
/**
* @file graphics.c
* @brief Graphics subsystem implementation.
*
* Provides VGA mode 0x13 (320x200, 256 colors) with drawing primitives.
* Handles switching between text mode (0x03) and graphics mode via
* direct VGA register programming.
*/
#include "graphics.h"
#include "port_io.h"
#include "font8x16.h"
#include "string.h"
#include "vga.h"
/* ================================================================
* VGA register programming constants
* ================================================================ */
/* VGA ports */
#define VGA_MISC_WRITE 0x3C2
#define VGA_MISC_READ 0x3CC
#define VGA_SEQ_INDEX 0x3C4
#define VGA_SEQ_DATA 0x3C5
#define VGA_CRTC_INDEX 0x3D4
#define VGA_CRTC_DATA 0x3D5
#define VGA_GC_INDEX 0x3CE
#define VGA_GC_DATA 0x3CF
#define VGA_AC_INDEX 0x3C0
#define VGA_AC_WRITE 0x3C0
#define VGA_AC_READ 0x3C1
#define VGA_INSTAT_READ 0x3DA
#define VGA_DAC_WRITE_IDX 0x3C8
#define VGA_DAC_DATA 0x3C9
/* Number of registers in each group */
#define VGA_NUM_SEQ_REGS 5
#define VGA_NUM_CRTC_REGS 25
#define VGA_NUM_GC_REGS 9
#define VGA_NUM_AC_REGS 21
/* ================================================================
* VGA register tables for Mode 0x13 (320x200x256)
* ================================================================ */
static const uint8_t mode13_misc = 0x63;
static const uint8_t mode13_seq[VGA_NUM_SEQ_REGS] = {
0x03, 0x01, 0x0F, 0x00, 0x0E
};
static const uint8_t mode13_crtc[VGA_NUM_CRTC_REGS] = {
0x5F, 0x4F, 0x50, 0x82, 0x54, 0x80, 0xBF, 0x1F,
0x00, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x9C, 0x0E, 0x8F, 0x28, 0x40, 0x96, 0xB9, 0xA3,
0xFF
};
static const uint8_t mode13_gc[VGA_NUM_GC_REGS] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x05, 0x0F,
0xFF
};
static const uint8_t mode13_ac[VGA_NUM_AC_REGS] = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x41, 0x00, 0x0F, 0x00, 0x00
};
/* ================================================================
* VGA register tables for Mode 0x03 (80x25 text)
* ================================================================ */
static const uint8_t mode03_misc = 0x67;
static const uint8_t mode03_seq[VGA_NUM_SEQ_REGS] = {
0x03, 0x00, 0x03, 0x00, 0x02
};
static const uint8_t mode03_crtc[VGA_NUM_CRTC_REGS] = {
0x5F, 0x4F, 0x50, 0x82, 0x55, 0x81, 0xBF, 0x1F,
0x00, 0x4F, 0x0D, 0x0E, 0x00, 0x00, 0x00, 0x50,
0x9C, 0x0E, 0x8F, 0x28, 0x1F, 0x96, 0xB9, 0xA3,
0xFF
};
static const uint8_t mode03_gc[VGA_NUM_GC_REGS] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x0E, 0x00,
0xFF
};
static const uint8_t mode03_ac[VGA_NUM_AC_REGS] = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x14, 0x07,
0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
0x0C, 0x00, 0x0F, 0x08, 0x00
};
/* ================================================================
* Module state
* ================================================================ */
static int gfx_mode = GFX_MODE_TEXT;
static uint8_t *framebuffer = (uint8_t *)GFX_FRAMEBUFFER;
/* ================================================================
* VGA register programming helpers
* ================================================================ */
/**
* Write a set of VGA registers to switch modes.
*/
static void vga_write_regs(uint8_t misc, const uint8_t *seq,
const uint8_t *crtc, const uint8_t *gc,
const uint8_t *ac)
{
/* Miscellaneous output */
outb(VGA_MISC_WRITE, misc);
/* Sequencer */
for (int i = 0; i < VGA_NUM_SEQ_REGS; i++) {
outb(VGA_SEQ_INDEX, (uint8_t)i);
outb(VGA_SEQ_DATA, seq[i]);
}
/* Unlock CRTC (clear protect bit in register 0x11) */
outb(VGA_CRTC_INDEX, 0x11);
outb(VGA_CRTC_DATA, inb(VGA_CRTC_DATA) & 0x7F);
/* CRTC */
for (int i = 0; i < VGA_NUM_CRTC_REGS; i++) {
outb(VGA_CRTC_INDEX, (uint8_t)i);
outb(VGA_CRTC_DATA, crtc[i]);
}
/* Graphics Controller */
for (int i = 0; i < VGA_NUM_GC_REGS; i++) {
outb(VGA_GC_INDEX, (uint8_t)i);
outb(VGA_GC_DATA, gc[i]);
}
/* Attribute Controller */
/* Reading port 0x3DA resets the AC flip-flop to index mode */
inb(VGA_INSTAT_READ);
for (int i = 0; i < VGA_NUM_AC_REGS; i++) {
outb(VGA_AC_INDEX, (uint8_t)i);
outb(VGA_AC_WRITE, ac[i]);
}
/* Re-enable display by setting bit 5 of the AC index */
outb(VGA_AC_INDEX, 0x20);
}
/* ================================================================
* VGA font restore for text mode
* ================================================================ */
/**
* Load the 8x16 font into VGA plane 2 after returning to text mode.
* This restores readable text after graphics mode.
*/
static void vga_load_font(void) {
volatile uint8_t *vmem = (volatile uint8_t *)0xA0000;
/* Set up sequencer for font loading:
* - Map Mask: select plane 2 only
* - Memory Mode: disable chain-4, enable odd/even */
outb(VGA_SEQ_INDEX, 0x02);
outb(VGA_SEQ_DATA, 0x04); /* Map Mask: plane 2 */
outb(VGA_SEQ_INDEX, 0x04);
outb(VGA_SEQ_DATA, 0x06); /* Memory Mode: enable sequential, disable chain-4 */
/* Set up graphics controller for font loading:
* - Read Map Select: plane 2
* - Graphics Mode: write mode 0, read mode 0
* - Miscellaneous: text mode mapping (B8000-BFFFF) */
outb(VGA_GC_INDEX, 0x04);
outb(VGA_GC_DATA, 0x02); /* Read Map Select: plane 2 */
outb(VGA_GC_INDEX, 0x05);
outb(VGA_GC_DATA, 0x00); /* Graphics Mode: write mode 0 */
outb(VGA_GC_INDEX, 0x06);
outb(VGA_GC_DATA, 0x04); /* Misc: map to A0000, no chain, no odd/even */
/* Write font data for 256 characters.
* Each char entry is 32 bytes (only first 16 used for 8x16 font).
* Characters outside our font range get a filled block. */
for (int ch = 0; ch < 256; ch++) {
for (int row = 0; row < 16; row++) {
uint8_t bits;
if (ch >= FONT_FIRST && ch <= FONT_LAST) {
bits = font8x16_data[ch - FONT_FIRST][row];
} else if (ch == 0) {
bits = 0x00; /* Null char = blank */
} else {
bits = 0xFF; /* Unknown = filled block */
}
vmem[ch * 32 + row] = bits;
}
/* Zero out remaining 16 bytes of the 32-byte slot */
for (int row = 16; row < 32; row++) {
vmem[ch * 32 + row] = 0x00;
}
}
/* Restore sequencer for text mode:
* - Map Mask: planes 0 and 1 (text attribute + char)
* - Memory Mode: enable odd/even, no chain-4 */
outb(VGA_SEQ_INDEX, 0x02);
outb(VGA_SEQ_DATA, 0x03); /* Map Mask: planes 0 and 1 */
outb(VGA_SEQ_INDEX, 0x04);
outb(VGA_SEQ_DATA, 0x02); /* Memory Mode: odd/even addressing */
/* Restore graphics controller for text mode */
outb(VGA_GC_INDEX, 0x04);
outb(VGA_GC_DATA, 0x00); /* Read Map Select: plane 0 */
outb(VGA_GC_INDEX, 0x05);
outb(VGA_GC_DATA, 0x10); /* Graphics Mode: odd/even */
outb(VGA_GC_INDEX, 0x06);
outb(VGA_GC_DATA, 0x0E); /* Misc: map to B8000, odd/even */
}
/* ================================================================
* 256-color palette setup
* ================================================================ */
/**
* Standard VGA 16-color palette (RGB 6-bit values).
*/
static const uint8_t vga_palette_16[16][3] = {
{ 0, 0, 0}, /* 0: Black */
{ 0, 0, 42}, /* 1: Blue */
{ 0, 42, 0}, /* 2: Green */
{ 0, 42, 42}, /* 3: Cyan */
{42, 0, 0}, /* 4: Red */
{42, 0, 42}, /* 5: Magenta */
{42, 21, 0}, /* 6: Brown */
{42, 42, 42}, /* 7: Light Grey */
{21, 21, 21}, /* 8: Dark Grey */
{21, 21, 63}, /* 9: Light Blue */
{21, 63, 21}, /* 10: Light Green */
{21, 63, 63}, /* 11: Light Cyan */
{63, 21, 21}, /* 12: Light Red */
{63, 21, 63}, /* 13: Light Magenta */
{63, 63, 21}, /* 14: Yellow */
{63, 63, 63}, /* 15: White */
};
/**
* Set up the 256-color palette.
* 0-15: Standard 16 VGA colors
* 16-231: 6x6x6 RGB color cube
* 232-255: 24-step grayscale
*/
static void setup_palette(void) {
/* VGA DAC: write index, then R, G, B (6-bit values, 0-63) */
/* Standard 16 colors */
outb(VGA_DAC_WRITE_IDX, 0);
for (int i = 0; i < 16; i++) {
outb(VGA_DAC_DATA, vga_palette_16[i][0]);
outb(VGA_DAC_DATA, vga_palette_16[i][1]);
outb(VGA_DAC_DATA, vga_palette_16[i][2]);
}
/* 6x6x6 RGB color cube (indices 16-231) */
outb(VGA_DAC_WRITE_IDX, 16);
for (int r = 0; r < 6; r++) {
for (int g = 0; g < 6; g++) {
for (int b = 0; b < 6; b++) {
outb(VGA_DAC_DATA, (uint8_t)(r * 63 / 5));
outb(VGA_DAC_DATA, (uint8_t)(g * 63 / 5));
outb(VGA_DAC_DATA, (uint8_t)(b * 63 / 5));
}
}
}
/* 24-step grayscale (indices 232-255) */
outb(VGA_DAC_WRITE_IDX, 232);
for (int i = 0; i < 24; i++) {
uint8_t v = (uint8_t)(i * 63 / 23);
outb(VGA_DAC_DATA, v);
outb(VGA_DAC_DATA, v);
outb(VGA_DAC_DATA, v);
}
}
/* ================================================================
* Mode switching
* ================================================================ */
void graphics_enter(void) {
if (gfx_mode == GFX_MODE_GRAPHICS) return;
/* Switch to mode 0x13 */
vga_write_regs(mode13_misc, mode13_seq, mode13_crtc, mode13_gc, mode13_ac);
/* Set up the color palette */
setup_palette();
/* Clear the framebuffer */
memset(framebuffer, 0, GFX_WIDTH * GFX_HEIGHT);
gfx_mode = GFX_MODE_GRAPHICS;
}
void graphics_leave(void) {
if (gfx_mode == GFX_MODE_TEXT) return;
/* Switch to mode 0x03 */
vga_write_regs(mode03_misc, mode03_seq, mode03_crtc, mode03_gc, mode03_ac);
/* Reload the VGA font into plane 2 */
vga_load_font();
/* Clear the text-mode framebuffer */
volatile uint16_t *text_buf = (volatile uint16_t *)0xB8000;
for (int i = 0; i < 80 * 25; i++) {
text_buf[i] = 0x0720; /* Light grey on black, space */
}
gfx_mode = GFX_MODE_TEXT;
/* Re-initialize the VGA text driver */
vga_init();
}
int graphics_get_mode(void) {
return gfx_mode;
}
/* ================================================================
* Drawing primitives
* ================================================================ */
void gfx_pixel(int x, int y, uint8_t color) {
if (x < 0 || x >= GFX_WIDTH || y < 0 || y >= GFX_HEIGHT) return;
framebuffer[y * GFX_WIDTH + x] = color;
}
void gfx_clear(uint8_t color) {
memset(framebuffer, color, GFX_WIDTH * GFX_HEIGHT);
}
void gfx_fill_rect(int x, int y, int w, int h, uint8_t color) {
/* Clip */
int x1 = x < 0 ? 0 : x;
int y1 = y < 0 ? 0 : y;
int x2 = (x + w > GFX_WIDTH) ? GFX_WIDTH : (x + w);
int y2 = (y + h > GFX_HEIGHT) ? GFX_HEIGHT : (y + h);
for (int row = y1; row < y2; row++) {
memset(&framebuffer[row * GFX_WIDTH + x1], color, (uint32_t)(x2 - x1));
}
}
void gfx_draw_line(int x1, int y1, int x2, int y2, uint8_t color) {
/* Bresenham's line algorithm */
int dx = x2 - x1;
int dy = y2 - y1;
int sx = dx >= 0 ? 1 : -1;
int sy = dy >= 0 ? 1 : -1;
if (dx < 0) dx = -dx;
if (dy < 0) dy = -dy;
int err = dx - dy;
for (;;) {
gfx_pixel(x1, y1, color);
if (x1 == x2 && y1 == y2) break;
int e2 = 2 * err;
if (e2 > -dy) { err -= dy; x1 += sx; }
if (e2 < dx) { err += dx; y1 += sy; }
}
}
void gfx_fill_circle(int cx, int cy, int r, uint8_t color) {
if (r <= 0) { gfx_pixel(cx, cy, color); return; }
for (int y = -r; y <= r; y++) {
/* Horizontal span for this scanline */
int dx = 0;
while (dx * dx + y * y <= r * r) dx++;
dx--;
int left = cx - dx;
int right = cx + dx;
/* Clip */
if (cy + y < 0 || cy + y >= GFX_HEIGHT) continue;
if (left < 0) left = 0;
if (right >= GFX_WIDTH) right = GFX_WIDTH - 1;
if (left > right) continue;
memset(&framebuffer[(cy + y) * GFX_WIDTH + left], color,
(uint32_t)(right - left + 1));
}
}
void gfx_draw_circle(int cx, int cy, int r, uint8_t color) {
/* Midpoint circle algorithm */
int x = r, y = 0;
int err = 1 - r;
while (x >= y) {
gfx_pixel(cx + x, cy + y, color);
gfx_pixel(cx + y, cy + x, color);
gfx_pixel(cx - y, cy + x, color);
gfx_pixel(cx - x, cy + y, color);
gfx_pixel(cx - x, cy - y, color);
gfx_pixel(cx - y, cy - x, color);
gfx_pixel(cx + y, cy - x, color);
gfx_pixel(cx + x, cy - y, color);
y++;
if (err < 0) {
err += 2 * y + 1;
} else {
x--;
err += 2 * (y - x) + 1;
}
}
}
void gfx_draw_char(int x, int y, char c, uint8_t color) {
if (c < FONT_FIRST || c > FONT_LAST) return;
const uint8_t *glyph = font8x16_data[c - FONT_FIRST];
/* Draw at half vertical scale: 8x8 pixels per character.
* Sample every other row from the 8x16 font. */
for (int row = 0; row < 8; row++) {
uint8_t bits = glyph[row * 2]; /* Sample even rows */
for (int col = 0; col < 8; col++) {
if (bits & (0x80 >> col)) {
gfx_pixel(x + col, y + row, color);
}
}
}
}
void gfx_draw_text(int x, int y, const char *str, uint8_t color) {
int cx = x;
while (*str) {
if (*str == '\n') {
cx = x;
y += 8;
} else {
gfx_draw_char(cx, y, *str, color);
cx += 8;
}
str++;
}
}

173
src/graphics.h Normal file
View File

@@ -0,0 +1,173 @@
/**
* @file graphics.h
* @brief Graphics subsystem for ClaudeOS.
*
* Provides VGA mode 0x13 (320x200, 256 colors) graphics with
* drawing primitives. Supports switching between text mode (0x03)
* and graphics mode at runtime.
*
* Color palette:
* 0-15 : Standard 16 VGA colors
* 16-231: 6x6x6 RGB color cube (index = 16 + r*36 + g*6 + b, where r,g,b in 0-5)
* 232-255: 24-step grayscale
*/
#ifndef GRAPHICS_H
#define GRAPHICS_H
#include <stdint.h>
/** Graphics mode screen dimensions. */
#define GFX_WIDTH 320
#define GFX_HEIGHT 200
/** Framebuffer base address for mode 0x13. */
#define GFX_FRAMEBUFFER 0xA0000
/** Graphics mode state. */
#define GFX_MODE_TEXT 0
#define GFX_MODE_GRAPHICS 1
/* ================================================================
* Standard palette color indices (0-15)
* ================================================================ */
#define GFX_BLACK 0
#define GFX_BLUE 1
#define GFX_GREEN 2
#define GFX_CYAN 3
#define GFX_RED 4
#define GFX_MAGENTA 5
#define GFX_BROWN 6
#define GFX_LIGHT_GREY 7
#define GFX_DARK_GREY 8
#define GFX_LIGHT_BLUE 9
#define GFX_LIGHT_GREEN 10
#define GFX_LIGHT_CYAN 11
#define GFX_LIGHT_RED 12
#define GFX_LIGHT_MAGENTA 13
#define GFX_YELLOW 14
#define GFX_WHITE 15
/* ================================================================
* GFX syscall sub-commands
* ================================================================ */
#define GFX_CMD_ENTER 0 /**< Enter graphics mode. Returns 0. */
#define GFX_CMD_LEAVE 1 /**< Leave graphics mode (back to text). Returns 0. */
#define GFX_CMD_PIXEL 2 /**< Set pixel. ECX=(x|y<<16), EDX=color. */
#define GFX_CMD_CLEAR 3 /**< Clear screen. ECX=color. */
#define GFX_CMD_FILL_RECT 4 /**< Fill rect. ECX=ptr to gfx_rect_t. */
#define GFX_CMD_LINE 5 /**< Draw line. ECX=ptr to gfx_line_t. */
#define GFX_CMD_CIRCLE 6 /**< Draw filled circle. ECX=ptr to gfx_circle_t. */
#define GFX_CMD_GET_INFO 7 /**< Get info. Returns (width | height<<16). */
/* ================================================================
* Command structs (used with pointer-based sub-commands)
* ================================================================ */
typedef struct {
uint32_t x, y, w, h;
uint32_t color;
} gfx_rect_t;
typedef struct {
uint32_t x1, y1, x2, y2;
uint32_t color;
} gfx_line_t;
typedef struct {
uint32_t cx, cy, r;
uint32_t color;
} gfx_circle_t;
/* ================================================================
* Public API
* ================================================================ */
/**
* Convert RGB (0-255 each) to a palette index.
* Uses the 6x6x6 color cube (indices 16-231).
*/
static inline uint8_t gfx_rgb(uint8_t r, uint8_t g, uint8_t b) {
return (uint8_t)(16 + (r / 51) * 36 + (g / 51) * 6 + (b / 51));
}
/**
* Enter graphics mode (VGA 0x13: 320x200x256).
* Saves text mode state and initializes the color palette.
*/
void graphics_enter(void);
/**
* Leave graphics mode and return to text mode (VGA 0x03: 80x25).
* Restores the VGA font and text display.
*/
void graphics_leave(void);
/**
* Get the current graphics mode.
* @return GFX_MODE_TEXT or GFX_MODE_GRAPHICS.
*/
int graphics_get_mode(void);
/**
* Set a single pixel.
* @param x X coordinate (0 to GFX_WIDTH-1).
* @param y Y coordinate (0 to GFX_HEIGHT-1).
* @param color Palette color index (0-255).
*/
void gfx_pixel(int x, int y, uint8_t color);
/**
* Clear the screen with a color.
* @param color Palette color index.
*/
void gfx_clear(uint8_t color);
/**
* Draw a filled rectangle.
* @param x Top-left X.
* @param y Top-left Y.
* @param w Width.
* @param h Height.
* @param color Palette color index.
*/
void gfx_fill_rect(int x, int y, int w, int h, uint8_t color);
/**
* Draw a line (Bresenham).
* @param x1,y1 Start point.
* @param x2,y2 End point.
* @param color Palette color index.
*/
void gfx_draw_line(int x1, int y1, int x2, int y2, uint8_t color);
/**
* Draw a filled circle.
* @param cx,cy Center.
* @param r Radius.
* @param color Palette color index.
*/
void gfx_fill_circle(int cx, int cy, int r, uint8_t color);
/**
* Draw a circle outline.
* @param cx,cy Center.
* @param r Radius.
* @param color Palette color index.
*/
void gfx_draw_circle(int cx, int cy, int r, uint8_t color);
/**
* Draw a text string using the 8x16 font (scaled to 8x8 in mode 13).
* @param x,y Top-left position.
* @param str Null-terminated string.
* @param color Palette color index.
*/
void gfx_draw_text(int x, int y, const char *str, uint8_t color);
/**
* Draw a single character using the 8x16 font at half vertical scale (8x8).
*/
void gfx_draw_char(int x, int y, char c, uint8_t color);
#endif /* GRAPHICS_H */

279
src/ipv4.c Normal file
View File

@@ -0,0 +1,279 @@
/**
* @file ipv4.c
* @brief IPv4 network layer implementation.
*
* Handles IPv4 packet construction, parsing, checksum calculation,
* and routing to the appropriate Ethernet interface.
*
* Incoming Ethernet frames with EtherType 0x0800 are passed to
* ipv4_receive(), which validates the header and dispatches to
* registered protocol handlers (ICMP, UDP, TCP, etc.).
*/
#include "ipv4.h"
#include "ethernet.h"
#include <string.h>
/* Debug print helpers */
extern void offset_print(const char *str);
extern void print_hex(uint32_t val);
/* ================================================================
* Global state
* ================================================================ */
/** Maximum number of registered protocol handlers. */
#define MAX_PROTO_HANDLERS 16
/** Protocol handler entry. */
typedef struct {
uint8_t protocol;
ipv4_proto_handler_t handler;
} proto_handler_t;
/** Registered protocol handlers. */
static proto_handler_t proto_handlers[MAX_PROTO_HANDLERS];
static uint32_t proto_handler_count = 0;
/** Global IP identification counter. */
static uint16_t ip_id_counter = 1;
/* ================================================================
* Checksum
* ================================================================ */
uint16_t ipv4_checksum(const void *data, uint32_t len) {
const uint16_t *words = (const uint16_t *)data;
uint32_t sum = 0;
while (len > 1) {
sum += *words++;
len -= 2;
}
/* Add left-over byte, if any */
if (len == 1) {
sum += *(const uint8_t *)words;
}
/* Fold 32-bit sum into 16 bits */
while (sum >> 16) {
sum = (sum & 0xFFFF) + (sum >> 16);
}
return (uint16_t)(~sum);
}
/* ================================================================
* Address conversion helpers
* ================================================================ */
uint32_t ipv4_aton(const char *str) {
uint32_t octets[4] = {0};
int idx = 0;
for (const char *p = str; *p && idx < 4; p++) {
if (*p == '.') {
idx++;
} else if (*p >= '0' && *p <= '9') {
octets[idx] = octets[idx] * 10 + (uint32_t)(*p - '0');
} else {
break;
}
}
return (octets[0] << 24) | (octets[1] << 16) |
(octets[2] << 8) | octets[3];
}
char *ipv4_ntoa(uint32_t ip, char *buf, uint32_t size) {
if (size < 16) { buf[0] = '\0'; return buf; }
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);
buf[pos++] = (char)('0' + (octet % 100) / 10);
buf[pos++] = (char)('0' + octet % 10);
} else if (octet >= 10) {
buf[pos++] = (char)('0' + octet / 10);
buf[pos++] = (char)('0' + octet % 10);
} else {
buf[pos++] = (char)('0' + octet);
}
}
buf[pos] = '\0';
return buf;
}
/* ================================================================
* Routing
* ================================================================ */
/**
* Find the Ethernet interface for a given destination IP.
*
* Simple routing: check if dst is on a directly-connected subnet.
* If not, use the first interface with a configured gateway.
* Fallback: first interface.
*
* @param dst_ip Destination IP (host byte order).
* @return Interface index, or -1 if no interfaces.
*/
static int ipv4_route(uint32_t dst_ip) {
uint32_t count = ethernet_get_iface_count();
if (count == 0) return -1;
/* Check for directly-connected subnet */
for (uint32_t i = 0; i < count; i++) {
eth_iface_t *iface = ethernet_get_iface(i);
if (!iface || !iface->active) continue;
if (iface->ip_addr == 0 || iface->netmask == 0) continue;
if ((dst_ip & iface->netmask) == (iface->ip_addr & iface->netmask)) {
return (int)i;
}
}
/* Use first interface with a gateway */
for (uint32_t i = 0; i < count; i++) {
eth_iface_t *iface = ethernet_get_iface(i);
if (!iface || !iface->active) continue;
if (iface->gateway != 0) return (int)i;
}
/* Fallback: first active interface */
for (uint32_t i = 0; i < count; i++) {
eth_iface_t *iface = ethernet_get_iface(i);
if (iface && iface->active) return (int)i;
}
return -1;
}
/* ================================================================
* Send
* ================================================================ */
int ipv4_send_iface(uint32_t iface_idx, uint32_t dst_ip, uint8_t protocol,
const void *payload, uint32_t len) {
if (len > IPV4_MTU) return -1;
eth_iface_t *iface = ethernet_get_iface(iface_idx);
if (!iface || !iface->active) return -1;
/* Build IPv4 packet on stack */
uint8_t packet[ETH_MTU];
ipv4_header_t *hdr = (ipv4_header_t *)packet;
hdr->ihl_version = 0x45; /* IPv4, IHL=5 (20 bytes) */
hdr->tos = 0;
hdr->total_length = htons((uint16_t)(IPV4_HLEN + len));
hdr->identification = htons(ip_id_counter++);
hdr->flags_fragoff = htons(IPV4_FLAG_DF); /* Don't fragment */
hdr->ttl = 64;
hdr->protocol = protocol;
hdr->checksum = 0;
hdr->src_ip = htonl(iface->ip_addr);
hdr->dst_ip = htonl(dst_ip);
/* Compute header checksum */
hdr->checksum = ipv4_checksum(hdr, IPV4_HLEN);
/* Copy payload */
memcpy(packet + IPV4_HLEN, payload, len);
/* Determine destination MAC.
* For now, use broadcast (FF:FF:FF:FF:FF:FF) — the ARP subsystem
* will override this once implemented. */
uint8_t dst_mac[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
/* TODO: ARP lookup for dst_ip (or gateway if not on-link) */
return ethernet_send(iface, dst_mac, ETHERTYPE_IPV4, packet,
IPV4_HLEN + len);
}
int ipv4_send(uint32_t dst_ip, uint8_t protocol,
const void *payload, uint32_t len) {
int iface_idx = ipv4_route(dst_ip);
if (iface_idx < 0) return -1;
return ipv4_send_iface((uint32_t)iface_idx, dst_ip, protocol, payload, len);
}
/* ================================================================
* Receive
* ================================================================ */
void ipv4_receive(const void *data, uint32_t len, uint32_t iface_idx) {
if (len < IPV4_HLEN) return;
const ipv4_header_t *hdr = (const ipv4_header_t *)data;
/* Verify version */
if ((hdr->ihl_version >> 4) != 4) return;
/* Verify header length */
uint32_t ihl = (uint32_t)(hdr->ihl_version & 0x0F) * 4;
if (ihl < IPV4_HLEN || ihl > len) return;
/* Verify checksum */
if (ipv4_checksum(data, ihl) != 0) return;
uint16_t total_len = ntohs(hdr->total_length);
if (total_len > len) return;
uint32_t src_ip = ntohl(hdr->src_ip);
uint32_t dst_ip = ntohl(hdr->dst_ip);
const uint8_t *payload = (const uint8_t *)data + ihl;
uint32_t payload_len = total_len - ihl;
/* Check if this packet is for us */
eth_iface_t *iface = ethernet_get_iface(iface_idx);
if (iface && iface->ip_addr != 0) {
if (dst_ip != iface->ip_addr &&
dst_ip != 0xFFFFFFFF && /* broadcast */
(dst_ip & ~iface->netmask) != ~iface->netmask) {
return; /* Not for us */
}
}
/* Dispatch to registered protocol handler */
for (uint32_t i = 0; i < proto_handler_count; i++) {
if (proto_handlers[i].protocol == hdr->protocol) {
proto_handlers[i].handler(src_ip, dst_ip, payload,
payload_len, iface_idx);
return;
}
}
/* No handler registered for this protocol */
}
/* ================================================================
* Protocol registration
* ================================================================ */
int ipv4_register_proto(uint8_t protocol, ipv4_proto_handler_t handler) {
if (proto_handler_count >= MAX_PROTO_HANDLERS) return -1;
proto_handlers[proto_handler_count].protocol = protocol;
proto_handlers[proto_handler_count].handler = handler;
proto_handler_count++;
return 0;
}
/* ================================================================
* Initialization
* ================================================================ */
void ipv4_init(void) {
memset(proto_handlers, 0, sizeof(proto_handlers));
proto_handler_count = 0;
ip_id_counter = 1;
offset_print(" IPv4: initialized\n");
}

158
src/ipv4.h Normal file
View File

@@ -0,0 +1,158 @@
/**
* @file ipv4.h
* @brief IPv4 network layer.
*
* Provides IPv4 packet construction, parsing, and routing.
* Sits on top of the Ethernet subsystem and provides the foundation
* for higher-level protocols (ICMP, UDP, TCP).
*
* The IPv4 stack maintains a simple routing table: each Ethernet
* interface has an IP address, netmask, and default gateway.
* Outbound packets are routed to the appropriate interface.
*
* Inbound packets are dispatched to registered protocol handlers
* based on the IP protocol field.
*/
#ifndef IPV4_H
#define IPV4_H
#include <stdint.h>
/* ================================================================
* IPv4 header (20 bytes minimum, no options)
* ================================================================ */
/** IPv4 header structure (network byte order in memory). */
typedef struct __attribute__((packed)) ipv4_header {
uint8_t ihl_version; /**< Version (4) and IHL (5 for no options). */
uint8_t tos; /**< Type of Service. */
uint16_t total_length; /**< Total datagram length (header + data). */
uint16_t identification; /**< Fragment identification. */
uint16_t flags_fragoff; /**< Flags (3 bits) + Fragment Offset (13 bits). */
uint8_t ttl; /**< Time to Live. */
uint8_t protocol; /**< Upper-layer protocol number. */
uint16_t checksum; /**< Header checksum. */
uint32_t src_ip; /**< Source IP address (network byte order). */
uint32_t dst_ip; /**< Destination IP address (network byte order). */
} ipv4_header_t;
/** IPv4 header size (no options). */
#define IPV4_HLEN 20
/** Maximum IPv4 payload over Ethernet (1500 - 20). */
#define IPV4_MTU 1480
/* ================================================================
* Protocol numbers
* ================================================================ */
#define IP_PROTO_ICMP 1
#define IP_PROTO_TCP 6
#define IP_PROTO_UDP 17
/* ================================================================
* IPv4 flags
* ================================================================ */
#define IPV4_FLAG_DF 0x4000 /**< Don't Fragment. */
#define IPV4_FLAG_MF 0x2000 /**< More Fragments. */
/* ================================================================
* Protocol handler callback
* ================================================================ */
/**
* Callback for handling incoming IPv4 packets of a specific protocol.
*
* @param src_ip Source IP address (host byte order).
* @param dst_ip Destination IP address (host byte order).
* @param payload Protocol payload (past IPv4 header).
* @param len Payload length.
* @param iface_idx Ethernet interface index the packet arrived on.
*/
typedef void (*ipv4_proto_handler_t)(uint32_t src_ip, uint32_t dst_ip,
const void *payload, uint32_t len,
uint32_t iface_idx);
/* ================================================================
* Public API
* ================================================================ */
/**
* Initialize the IPv4 subsystem.
*/
void ipv4_init(void);
/**
* Send an IPv4 packet.
*
* Automatically routes to the correct Ethernet interface based on
* the destination IP and configured routes.
*
* @param dst_ip Destination IP (host byte order).
* @param protocol IP protocol number (e.g., IP_PROTO_UDP).
* @param payload Packet payload.
* @param len Payload length (max IPV4_MTU).
* @return 0 on success, -1 on failure.
*/
int ipv4_send(uint32_t dst_ip, uint8_t protocol,
const void *payload, uint32_t len);
/**
* Send an IPv4 packet through a specific interface.
*
* @param iface_idx Ethernet interface index.
* @param dst_ip Destination IP (host byte order).
* @param protocol IP protocol number.
* @param payload Packet payload.
* @param len Payload length.
* @return 0 on success, -1 on failure.
*/
int ipv4_send_iface(uint32_t iface_idx, uint32_t dst_ip, uint8_t protocol,
const void *payload, uint32_t len);
/**
* Process an incoming IPv4 packet (called from Ethernet layer).
*
* @param data Raw IPv4 packet (header + payload).
* @param len Total packet length.
* @param iface_idx Interface index the packet arrived on.
*/
void ipv4_receive(const void *data, uint32_t len, uint32_t iface_idx);
/**
* Register a protocol handler.
*
* @param protocol IP protocol number.
* @param handler Callback function.
* @return 0 on success, -1 if table is full.
*/
int ipv4_register_proto(uint8_t protocol, ipv4_proto_handler_t handler);
/**
* Compute the Internet checksum over a buffer.
*
* @param data Buffer.
* @param len Length in bytes.
* @return Checksum in network byte order.
*/
uint16_t ipv4_checksum(const void *data, uint32_t len);
/**
* Convert an IPv4 address string "A.B.C.D" to a uint32_t (host byte order).
*
* @param str Dotted-decimal string.
* @return IPv4 address, or 0 on failure.
*/
uint32_t ipv4_aton(const char *str);
/**
* Format an IPv4 address (host byte order) into a dotted-decimal string.
*
* @param ip IPv4 address.
* @param buf Output buffer (at least 16 bytes).
* @param size Buffer size.
* @return Pointer to buf.
*/
char *ipv4_ntoa(uint32_t ip, char *buf, uint32_t size);
#endif /* IPV4_H */

View File

@@ -3,6 +3,9 @@
#include "process.h"
#include "syscall.h"
#include "keyboard.h"
#include "floppy.h"
#include "ne2000.h"
#include "e3c509.h"
#include <stdint.h>
/* Forward declaration for kernel panic or similar */
@@ -64,6 +67,15 @@ void isr_handler(registers_t *regs)
} else if (regs->int_no == 33) {
/* Keyboard IRQ */
keyboard_irq(regs);
} else if (regs->int_no == 38) {
/* Floppy IRQ */
floppy_irq();
} else if (regs->int_no == 41) {
/* NE2000 Ethernet IRQ (IRQ 9, vector 41) */
ne2k_irq();
} else if (regs->int_no == 42) {
/* 3C509B Ethernet IRQ (IRQ 10, vector 42) */
e3c509_irq();
}
return;
}

View File

@@ -20,7 +20,14 @@
#include "devicefs.h"
#include "sysfs.h"
#include "mbr.h"
#include "fat32.h"
#include "keyboard.h"
#include "ethernet.h"
#include "ipv4.h"
#include "arp.h"
#include "dhcp.h"
#include "udp.h"
#include "tcp.h"
#include "framebuffer.h"
/* Global framebuffer info, parsed from multiboot2 tags. */
@@ -68,6 +75,128 @@ void print_hex(uint32_t val)
outb(0xE9, '\n'); serial_putc('\n');
}
/* ================================================================
* VFS sysfs namespace — handles mount/umount via /sys/vfs
* ================================================================
*
* /sys/vfs/mount — write "device mountpoint" to mount a FAT32 device
* /sys/vfs/mounts — read to list active mounts
*/
/** Last mount status message for reading back. */
static char vfs_sysfs_status[256] = "no mounts\n";
static int vfs_sysfs_list(void *ctx, const char *path, uint32_t idx,
sysfs_entry_t *out) {
(void)ctx;
if (path[0] != '\0') return -1; /* No subdirectories */
static const char *entries[] = { "mount", "mounts" };
if (idx >= 2) return -1;
memset(out, 0, sizeof(sysfs_entry_t));
strncpy(out->name, entries[idx], SYSFS_MAX_NAME - 1);
out->is_dir = 0;
return 0;
}
static int vfs_sysfs_read(void *ctx, const char *path, char *buf,
uint32_t buf_size) {
(void)ctx;
if (strcmp(path, "mount") == 0 || strcmp(path, "mounts") == 0) {
uint32_t len = strlen(vfs_sysfs_status);
if (len + 1 > buf_size) len = buf_size - 1;
memcpy(buf, vfs_sysfs_status, len);
buf[len] = '\0';
return (int)len;
}
return -1;
}
static int vfs_sysfs_write(void *ctx, const char *path, const char *buf,
uint32_t size) {
(void)ctx;
if (strcmp(path, "mount") != 0) return -1;
/* Parse: "device_name mount_path\n" */
char device[64];
char mount_path[128];
int di = 0, mi = 0;
uint32_t i = 0;
/* Parse device name */
while (i < size && buf[i] != ' ' && buf[i] != '\n' && di < 63) {
device[di++] = buf[i++];
}
device[di] = '\0';
/* Skip spaces */
while (i < size && buf[i] == ' ') i++;
/* Parse mount path */
while (i < size && buf[i] != ' ' && buf[i] != '\n' && mi < 127) {
mount_path[mi++] = buf[i++];
}
mount_path[mi] = '\0';
if (di == 0 || mi == 0) {
strncpy(vfs_sysfs_status, "error: usage: device mountpoint\n",
sizeof(vfs_sysfs_status) - 1);
return -1;
}
offset_print(" VFS mount request: ");
offset_print(device);
offset_print(" -> ");
offset_print(mount_path);
offset_print("\n");
/* Find the device */
devicefs_device_t *dev = devicefs_find(device);
if (!dev) {
strncpy(vfs_sysfs_status, "error: device not found\n",
sizeof(vfs_sysfs_status) - 1);
offset_print(" VFS mount: device not found\n");
return -1;
}
/* Mount as FAT32 */
int ret = fat32_mount_device(dev, mount_path);
if (ret != 0) {
strncpy(vfs_sysfs_status, "error: FAT32 mount failed\n",
sizeof(vfs_sysfs_status) - 1);
offset_print(" VFS mount: FAT32 mount failed\n");
return -1;
}
/* Build success message */
strncpy(vfs_sysfs_status, "mounted ", sizeof(vfs_sysfs_status) - 1);
uint32_t slen = strlen(vfs_sysfs_status);
strncpy(vfs_sysfs_status + slen, device,
sizeof(vfs_sysfs_status) - 1 - slen);
slen = strlen(vfs_sysfs_status);
strncpy(vfs_sysfs_status + slen, " at ",
sizeof(vfs_sysfs_status) - 1 - slen);
slen = strlen(vfs_sysfs_status);
strncpy(vfs_sysfs_status + slen, mount_path,
sizeof(vfs_sysfs_status) - 1 - slen);
slen = strlen(vfs_sysfs_status);
if (slen < sizeof(vfs_sysfs_status) - 1) {
vfs_sysfs_status[slen] = '\n';
vfs_sysfs_status[slen + 1] = '\0';
}
return (int)size;
}
static sysfs_ops_t vfs_sysfs_ops = {
.list = vfs_sysfs_list,
.read = vfs_sysfs_read,
.write = vfs_sysfs_write,
};
void kernel_main(uint32_t magic, uint32_t addr) {
/* Initialize serial port first so all debug output goes to COM1 too */
serial_init();
@@ -258,6 +387,10 @@ void kernel_main(uint32_t magic, uint32_t addr) {
EARLY_PRINT("SYS ");
offset_print("Sysfs initialized\n");
/* Register VFS mount namespace in sysfs */
sysfs_register("vfs", &vfs_sysfs_ops, NULL);
offset_print("VFS sysfs namespace registered\n");
init_tss();
EARLY_PRINT("TSS ");
offset_print("TSS initialized\n");
@@ -288,6 +421,24 @@ void kernel_main(uint32_t magic, uint32_t addr) {
fb_info.pitch = 80 * 2;
}
ethernet_init();
offset_print("Ethernet subsystem initialized\n");
ipv4_init();
offset_print("IPv4 stack initialized\n");
arp_init();
offset_print("ARP subsystem initialized\n");
dhcp_init();
offset_print("DHCP subsystem initialized\n");
udp_init();
offset_print("UDP stack initialized\n");
tcp_init();
offset_print("TCP stack initialized\n");
init_drivers();
EARLY_PRINT("DRV ");
offset_print("Drivers initialized\n");

541
src/ne2000.c Normal file
View File

@@ -0,0 +1,541 @@
/**
* @file ne2000.c
* @brief NE2000-compatible ISA Ethernet NIC driver implementation.
*
* Drives NE2000-compatible NICs based on the DP8390 Ethernet controller.
* Uses programmed I/O (PIO) remote DMA for data transfer and IRQ-driven
* packet reception.
*
* The driver probes I/O base 0x300 by performing a reset and checking
* the ISR reset bit. It reads the MAC address from the card's PROM
* (first 6 bytes of on-card memory), configures the TX and RX ring
* buffers, and registers as a character device with devicefs.
*
* NE2000 memory layout (16 KiB, 64 pages of 256 bytes):
* Pages 0x40-0x45: TX buffer (1536 bytes, holds 1 MTU frame)
* Pages 0x46-0x7F: RX ring buffer (~14.5 KiB)
*/
#include "ne2000.h"
#include "port_io.h"
#include "pic.h"
#include "devicefs.h"
#include "ethernet.h"
#include "driver.h"
#include <string.h>
/* Debug print helpers defined in kernel.c */
extern void offset_print(const char *str);
extern void print_hex(uint32_t val);
/* ================================================================
* Global state
* ================================================================ */
/** Single NE2000 device (we only support one for now). */
static ne2k_device_t ne2k_dev;
/** Volatile flag set by IRQ handler when a packet arrives. */
static volatile int ne2k_rx_ready = 0;
/** Volatile flag set by IRQ handler when transmit completes. */
static volatile int ne2k_tx_done = 0;
/* ================================================================
* Register access helpers
* ================================================================ */
/** Write to a NE2000 register. */
static inline void ne2k_write(uint16_t base, uint8_t reg, uint8_t val) {
outb(base + reg, val);
}
/** Read from a NE2000 register. */
static inline uint8_t ne2k_read(uint16_t base, uint8_t reg) {
return inb(base + reg);
}
/**
* Select a register page (0, 1, or 2).
* Preserves the STA/STP and RD bits.
*/
static void ne2k_page(uint16_t base, uint8_t page) {
uint8_t cr = ne2k_read(base, NE2K_CR);
cr = (cr & ~(CR_PS0 | CR_PS1)) | ((page & 0x03) << 6);
ne2k_write(base, NE2K_CR, cr);
}
/* ================================================================
* Remote DMA (PIO) operations
* ================================================================ */
/**
* Read bytes from the NE2000's on-card memory via remote DMA.
*
* @param base I/O base address.
* @param src Source address in card memory (byte address).
* @param dst Destination buffer in system RAM.
* @param len Number of bytes to read.
*/
static void ne2k_dma_read(uint16_t base, uint16_t src, void *dst, uint16_t len) {
ne2k_write(base, NE2K_CR, CR_STA | CR_DMA_ABORT);
/* Set remote DMA byte count */
ne2k_write(base, NE2K_RBCR0, (uint8_t)(len & 0xFF));
ne2k_write(base, NE2K_RBCR1, (uint8_t)((len >> 8) & 0xFF));
/* Set remote DMA start address */
ne2k_write(base, NE2K_RSAR0, (uint8_t)(src & 0xFF));
ne2k_write(base, NE2K_RSAR1, (uint8_t)((src >> 8) & 0xFF));
/* Start remote DMA read */
ne2k_write(base, NE2K_CR, CR_STA | CR_DMA_READ);
/* Read data from data port */
uint8_t *buf = (uint8_t *)dst;
uint16_t words = len / 2;
for (uint16_t i = 0; i < words; i++) {
uint16_t w = inw(base + NE2K_DATA);
buf[i * 2] = (uint8_t)(w & 0xFF);
buf[i * 2 + 1] = (uint8_t)((w >> 8) & 0xFF);
}
if (len & 1) {
uint16_t w = inw(base + NE2K_DATA);
buf[len - 1] = (uint8_t)(w & 0xFF);
}
/* Wait for DMA complete */
int timeout = 100000;
while (!(ne2k_read(base, NE2K_ISR) & ISR_RDC) && --timeout > 0);
/* Acknowledge RDC */
ne2k_write(base, NE2K_ISR, ISR_RDC);
}
/**
* Write bytes to the NE2000's on-card memory via remote DMA.
*
* @param base I/O base address.
* @param dst Destination address in card memory (byte address).
* @param src Source buffer in system RAM.
* @param len Number of bytes to write.
*/
static void ne2k_dma_write(uint16_t base, uint16_t dst, const void *src,
uint16_t len) {
ne2k_write(base, NE2K_CR, CR_STA | CR_DMA_ABORT);
/* Set remote DMA byte count */
ne2k_write(base, NE2K_RBCR0, (uint8_t)(len & 0xFF));
ne2k_write(base, NE2K_RBCR1, (uint8_t)((len >> 8) & 0xFF));
/* Set remote DMA start address */
ne2k_write(base, NE2K_RSAR0, (uint8_t)(dst & 0xFF));
ne2k_write(base, NE2K_RSAR1, (uint8_t)((dst >> 8) & 0xFF));
/* Start remote DMA write */
ne2k_write(base, NE2K_CR, CR_STA | CR_DMA_WRITE);
/* Write data to data port */
const uint8_t *buf = (const uint8_t *)src;
uint16_t words = len / 2;
for (uint16_t i = 0; i < words; i++) {
uint16_t w = (uint16_t)buf[i * 2] | ((uint16_t)buf[i * 2 + 1] << 8);
outw(base + NE2K_DATA, w);
}
if (len & 1) {
outw(base + NE2K_DATA, (uint16_t)buf[len - 1]);
}
/* Wait for DMA complete */
int timeout = 100000;
while (!(ne2k_read(base, NE2K_ISR) & ISR_RDC) && --timeout > 0);
/* Acknowledge RDC */
ne2k_write(base, NE2K_ISR, ISR_RDC);
}
/* ================================================================
* Packet send / receive
* ================================================================ */
int ne2k_send(ne2k_device_t *dev, const void *data, uint32_t len) {
if (!dev || !dev->present) return -1;
if (len > ETH_FRAME_MAX) return -1;
/* Minimum Ethernet frame size is 60 bytes (without CRC) */
uint32_t send_len = len;
if (send_len < 60) send_len = 60;
uint16_t base = dev->io_base;
/* Write packet to TX buffer on card */
ne2k_dma_write(base, (uint16_t)(NE2K_TX_START << 8),
data, (uint16_t)len);
/* If we need to pad, write zeros for the remaining bytes.
* For simplicity, the DMA write already handles partial words.
* The card will transmit send_len bytes from the TX page. */
/* Set transmit page start */
ne2k_page(base, 0);
ne2k_write(base, NE2K_TPSR, NE2K_TX_START);
ne2k_write(base, NE2K_TBCR0, (uint8_t)(send_len & 0xFF));
ne2k_write(base, NE2K_TBCR1, (uint8_t)((send_len >> 8) & 0xFF));
/* Trigger transmit */
ne2k_tx_done = 0;
ne2k_write(base, NE2K_CR, CR_STA | CR_TXP | CR_DMA_ABORT);
/* Wait for transmit completion (with timeout) */
int timeout = 1000000;
while (!ne2k_tx_done && --timeout > 0) {
asm volatile("pause");
}
if (timeout == 0) {
offset_print(" NE2K: tx timeout\n");
return -1;
}
return 0;
}
int ne2k_recv(ne2k_device_t *dev, void *buf, uint32_t bufsize) {
if (!dev || !dev->present) return -1;
uint16_t base = dev->io_base;
/* Check if there's a packet in the ring */
ne2k_page(base, 1);
uint8_t curr = ne2k_read(base, NE2K_CURR);
ne2k_page(base, 0);
if (dev->next_rx_page == curr) {
return 0; /* Ring is empty */
}
/* Read the 4-byte rx header from the ring */
ne2k_rx_header_t hdr;
uint16_t hdr_addr = (uint16_t)(dev->next_rx_page << 8);
ne2k_dma_read(base, hdr_addr, &hdr, sizeof(hdr));
/* Sanity check the header */
uint16_t pkt_len = hdr.length - (uint16_t)sizeof(ne2k_rx_header_t);
if (pkt_len > ETH_FRAME_MAX || pkt_len == 0) {
/* Invalid packet, advance pointer and skip */
dev->next_rx_page = hdr.next_page;
ne2k_write(base, NE2K_BNRY,
(dev->next_rx_page == NE2K_RX_START)
? NE2K_RX_STOP - 1
: dev->next_rx_page - 1);
return -1;
}
/* Determine how many bytes to copy to caller */
uint32_t copy_len = pkt_len;
if (copy_len > bufsize) copy_len = bufsize;
/* Read packet data (starts after the 4-byte header) */
uint16_t data_addr = hdr_addr + (uint16_t)sizeof(ne2k_rx_header_t);
/* Handle ring buffer wrap-around */
uint16_t ring_end = (uint16_t)(NE2K_RX_STOP << 8);
uint16_t ring_start = (uint16_t)(NE2K_RX_START << 8);
if (data_addr + copy_len > ring_end) {
/* Wraps around */
uint16_t first_part = ring_end - data_addr;
ne2k_dma_read(base, data_addr, buf, first_part);
ne2k_dma_read(base, ring_start,
(uint8_t *)buf + first_part,
(uint16_t)(copy_len - first_part));
} else {
ne2k_dma_read(base, data_addr, buf, (uint16_t)copy_len);
}
/* Advance BNRY to next_page (one behind CURR means buffer is full) */
dev->next_rx_page = hdr.next_page;
ne2k_write(base, NE2K_BNRY,
(dev->next_rx_page == NE2K_RX_START)
? NE2K_RX_STOP - 1
: dev->next_rx_page - 1);
return (int)copy_len;
}
/* ================================================================
* IRQ handler
* ================================================================ */
void ne2k_irq(void) {
if (!ne2k_dev.present) return;
uint16_t base = ne2k_dev.io_base;
uint8_t isr = ne2k_read(base, NE2K_ISR);
if (isr & ISR_PRX) {
/* Packet received */
ne2k_rx_ready = 1;
ne2k_write(base, NE2K_ISR, ISR_PRX);
}
if (isr & ISR_PTX) {
/* Packet transmitted */
ne2k_tx_done = 1;
ne2k_write(base, NE2K_ISR, ISR_PTX);
}
if (isr & ISR_RXE) {
/* Receive error */
ne2k_write(base, NE2K_ISR, ISR_RXE);
}
if (isr & ISR_TXE) {
/* Transmit error */
ne2k_tx_done = 1; /* Unblock sender even on error */
ne2k_write(base, NE2K_ISR, ISR_TXE);
}
if (isr & ISR_OVW) {
/* Overflow — need to handle by resetting RX */
ne2k_write(base, NE2K_ISR, ISR_OVW);
}
if (isr & ISR_CNT) {
/* Counter overflow */
ne2k_read(base, NE2K_CNTR0);
ne2k_read(base, NE2K_CNTR1);
ne2k_read(base, NE2K_CNTR2);
ne2k_write(base, NE2K_ISR, ISR_CNT);
}
}
/* ================================================================
* Initialization
* ================================================================ */
/**
* Read the MAC address from the NE2000 PROM.
*
* The first 32 bytes of NE2000's memory contain the PROM data.
* In word-wide mode, each MAC byte is duplicated in the high byte,
* so the PROM looks like: M0 M0 M1 M1 M2 M2 M3 M3 M4 M4 M5 M5 ...
*/
static void ne2k_read_mac(uint16_t base, uint8_t *mac) {
uint8_t prom[32];
ne2k_dma_read(base, 0x0000, prom, 32);
/* NE2000 duplicates each byte in word mode */
mac[0] = prom[0];
mac[1] = prom[2];
mac[2] = prom[4];
mac[3] = prom[6];
mac[4] = prom[8];
mac[5] = prom[10];
}
/**
* Reset and initialize the NE2000 controller.
*/
static int ne2k_hw_init(uint16_t base) {
/* Reset the card: read from RESET port, then write to it */
uint8_t tmp = inb(base + NE2K_RESET);
outb(base + NE2K_RESET, tmp);
/* Wait for reset to complete (ISR bit 7 = RST set) */
int timeout = 100000;
while (!(ne2k_read(base, NE2K_ISR) & ISR_RST) && --timeout > 0);
if (timeout == 0) {
offset_print(" NE2K: reset timeout\n");
return -1;
}
/* Acknowledge the reset */
ne2k_write(base, NE2K_ISR, 0xFF);
/* Stop the NIC and abort any DMA */
ne2k_write(base, NE2K_CR, CR_STP | CR_DMA_ABORT);
/* Data Configuration Register:
* Word-wide transfers (WTS), normal operation (LS),
* FIFO threshold 8 bytes (FT1) */
ne2k_write(base, NE2K_DCR, DCR_WTS | DCR_LS | DCR_FT1);
/* Clear remote byte count registers (required before starting) */
ne2k_write(base, NE2K_RBCR0, 0);
ne2k_write(base, NE2K_RBCR1, 0);
/* Receive Configuration: Accept broadcast and physical match */
ne2k_write(base, NE2K_RCR, RCR_AB);
/* Transmit Configuration: internal loopback during init */
ne2k_write(base, NE2K_TCR, TCR_LB0);
/* Set up ring buffer pointers */
ne2k_write(base, NE2K_PSTART, NE2K_RX_START);
ne2k_write(base, NE2K_PSTOP, NE2K_RX_STOP);
ne2k_write(base, NE2K_BNRY, NE2K_RX_START);
/* Read MAC address from PROM */
ne2k_read_mac(base, ne2k_dev.mac);
/* Switch to page 1 to set PAR (Physical Address) and CURR */
ne2k_page(base, 1);
ne2k_write(base, NE2K_PAR0, ne2k_dev.mac[0]);
ne2k_write(base, NE2K_PAR1, ne2k_dev.mac[1]);
ne2k_write(base, NE2K_PAR2, ne2k_dev.mac[2]);
ne2k_write(base, NE2K_PAR3, ne2k_dev.mac[3]);
ne2k_write(base, NE2K_PAR4, ne2k_dev.mac[4]);
ne2k_write(base, NE2K_PAR5, ne2k_dev.mac[5]);
/* Set current page (next write page for incoming packets) */
ne2k_write(base, NE2K_CURR, NE2K_RX_START + 1);
ne2k_dev.next_rx_page = NE2K_RX_START + 1;
/* Set multicast address registers to accept all multicast */
for (int i = 0; i < 8; i++) {
ne2k_write(base, NE2K_MAR0 + (uint8_t)i, 0xFF);
}
/* Switch back to page 0 */
ne2k_page(base, 0);
/* Clear all pending interrupts */
ne2k_write(base, NE2K_ISR, 0xFF);
/* Enable interrupts: PRX, PTX, RXE, TXE, OVW, CNT */
ne2k_write(base, NE2K_IMR, ISR_PRX | ISR_PTX | ISR_RXE |
ISR_TXE | ISR_OVW | ISR_CNT);
/* Take the NIC out of loopback: normal transmit configuration */
ne2k_write(base, NE2K_TCR, 0x00);
/* Start the NIC */
ne2k_write(base, NE2K_CR, CR_STA | CR_DMA_ABORT);
return 0;
}
/* ================================================================
* Driver framework integration
* ================================================================ */
/**
* Probe for an NE2000 card at the default I/O base.
*
* Reset the card, check the ISR reset bit, then verify by reading
* the PROM (MAC address). If all bytes are 0xFF or 0x00 there is
* no real card at this address.
*/
static driver_probe_result_t ne2k_probe(void) {
uint16_t base = NE2K_DEFAULT_IOBASE;
/* Try to reset */
uint8_t tmp = inb(base + NE2K_RESET);
outb(base + NE2K_RESET, tmp);
/* Brief delay */
for (volatile int i = 0; i < 10000; i++) {
asm volatile("pause");
}
/* Check if the ISR reset bit is set */
uint8_t isr = ne2k_read(base, NE2K_ISR);
if (!(isr & ISR_RST)) {
return DRIVER_PROBE_NOT_FOUND;
}
/* Acknowledge the reset */
ne2k_write(base, NE2K_ISR, 0xFF);
/* Configure for PROM reading */
ne2k_write(base, NE2K_CR, CR_STP | CR_DMA_ABORT);
ne2k_write(base, NE2K_DCR, DCR_WTS | DCR_LS | DCR_FT1);
ne2k_write(base, NE2K_RBCR0, 0);
ne2k_write(base, NE2K_RBCR1, 0);
ne2k_write(base, NE2K_RCR, RCR_MON);
ne2k_write(base, NE2K_TCR, TCR_LB0);
ne2k_write(base, NE2K_PSTART, NE2K_RX_START);
ne2k_write(base, NE2K_PSTOP, NE2K_RX_STOP);
ne2k_write(base, NE2K_BNRY, NE2K_RX_START);
/* Read 32 bytes of PROM data */
uint8_t prom[32];
ne2k_dma_read(base, 0x0000, prom, 32);
/* Validate: the MAC should not be all 0xFF or all 0x00 */
int all_ff = 1, all_00 = 1;
for (int i = 0; i < 12; i += 2) {
if (prom[i] != 0xFF) all_ff = 0;
if (prom[i] != 0x00) all_00 = 0;
}
if (all_ff || all_00) {
return DRIVER_PROBE_NOT_FOUND;
}
return DRIVER_PROBE_OK;
}
/**
* Initialize the NE2000 driver.
*/
static int ne2k_driver_init(void) {
memset(&ne2k_dev, 0, sizeof(ne2k_dev));
ne2k_dev.io_base = NE2K_DEFAULT_IOBASE;
ne2k_dev.irq = NE2K_DEFAULT_IRQ;
/* Enable IRQ in PIC */
pic_clear_mask(ne2k_dev.irq);
/* Initialize hardware */
if (ne2k_hw_init(ne2k_dev.io_base) != 0) {
offset_print(" NE2K: initialization failed\n");
return -1;
}
ne2k_dev.present = 1;
/* Print MAC address */
offset_print(" NE2K: MAC ");
for (int i = 0; i < 6; i++) {
if (i > 0) offset_print(":");
print_hex(ne2k_dev.mac[i]);
}
offset_print("\n");
/* 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);
offset_print(" IRQ ");
print_hex(ne2k_dev.irq);
offset_print("\n");
return 0;
}
ne2k_device_t *ne2k_get_device(void) {
return ne2k_dev.present ? &ne2k_dev : NULL;
}
int ne2k_init(void) {
return ne2k_driver_init();
}
/* ================================================================
* Driver registration
* ================================================================ */
static const driver_t ne2k_driver = {
.name = "ne2000",
.probe = ne2k_probe,
.init = ne2k_driver_init,
};
REGISTER_DRIVER(ne2k_driver);

221
src/ne2000.h Normal file
View File

@@ -0,0 +1,221 @@
/**
* @file ne2000.h
* @brief NE2000-compatible ISA Ethernet NIC driver.
*
* Drives NE2000-compatible NICs based on the DP8390 Ethernet chip.
* The driver probes I/O base 0x300 (the common default) and uses
* IRQ 9 for interrupt-driven packet reception.
*
* Packets are stored in the card's internal 16 KiB RAM using a
* ring buffer. The driver transmits packets synchronously.
*
* The NE2000 registers with the devicefs as a character device
* (named "eth1", "eth2", etc.) for the ethernet subsystem to use.
*/
#ifndef NE2000_H
#define NE2000_H
#include <stdint.h>
/* ================================================================
* NE2000 I/O Port Layout (offsets from base)
*
* The DP8390 has 3 register pages selected by bits 6-7 of the
* Command register (offset 0x00).
* ================================================================ */
/** Default ISA I/O base for NE2000. */
#define NE2K_DEFAULT_IOBASE 0x300
/** Default ISA IRQ for NE2000. */
#define NE2K_DEFAULT_IRQ 9
/* --- Shared registers (all pages) --- */
#define NE2K_CR 0x00 /**< Command register */
/* --- Page 0 read registers --- */
#define NE2K_CLDA0 0x01 /**< Current Local DMA Address 0 */
#define NE2K_CLDA1 0x02 /**< Current Local DMA Address 1 */
#define NE2K_BNRY 0x03 /**< Boundary pointer (last read page) */
#define NE2K_TSR 0x04 /**< Transmit Status Register */
#define NE2K_NCR 0x05 /**< Number of Collisions Register */
#define NE2K_FIFO 0x06 /**< FIFO */
#define NE2K_ISR 0x07 /**< Interrupt Status Register */
#define NE2K_CRDA0 0x08 /**< Current Remote DMA Address 0 */
#define NE2K_CRDA1 0x09 /**< Current Remote DMA Address 1 */
#define NE2K_RSR 0x0C /**< Receive Status Register */
#define NE2K_CNTR0 0x0D /**< Tally Counter 0 (frame alignment errors) */
#define NE2K_CNTR1 0x0E /**< Tally Counter 1 (CRC errors) */
#define NE2K_CNTR2 0x0F /**< Tally Counter 2 (missed packets) */
/* --- Page 0 write registers --- */
#define NE2K_PSTART 0x01 /**< Page Start (rx ring start, in pages) */
#define NE2K_PSTOP 0x02 /**< Page Stop (rx ring end, in pages) */
/* BNRY = 0x03 shared */
#define NE2K_TPSR 0x04 /**< Transmit Page Start */
#define NE2K_TBCR0 0x05 /**< Transmit Byte Count 0 */
#define NE2K_TBCR1 0x06 /**< Transmit Byte Count 1 */
/* ISR = 0x07 shared */
#define NE2K_RSAR0 0x08 /**< Remote Start Address 0 */
#define NE2K_RSAR1 0x09 /**< Remote Start Address 1 */
#define NE2K_RBCR0 0x0A /**< Remote Byte Count 0 */
#define NE2K_RBCR1 0x0B /**< Remote Byte Count 1 */
#define NE2K_RCR 0x0C /**< Receive Configuration Register */
#define NE2K_TCR 0x0D /**< Transmit Configuration Register */
#define NE2K_DCR 0x0E /**< Data Configuration Register */
#define NE2K_IMR 0x0F /**< Interrupt Mask Register */
/* --- Page 1 registers (r/w) --- */
/* NE2K_CR = 0x00 */
#define NE2K_PAR0 0x01 /**< Physical Address 0 (MAC byte 0) */
#define NE2K_PAR1 0x02
#define NE2K_PAR2 0x03
#define NE2K_PAR3 0x04
#define NE2K_PAR4 0x05
#define NE2K_PAR5 0x06
#define NE2K_CURR 0x07 /**< Current Page (next rx write page) */
#define NE2K_MAR0 0x08 /**< Multicast Address Register 0 */
/* --- NE2000 data port (offset 0x10) --- */
#define NE2K_DATA 0x10 /**< Data port for remote DMA */
#define NE2K_RESET 0x1F /**< Reset port */
/* ================================================================
* Command Register (CR) bits
* ================================================================ */
#define CR_STP 0x01 /**< Stop: software reset */
#define CR_STA 0x02 /**< Start: activate NIC */
#define CR_TXP 0x04 /**< Transmit Packet */
#define CR_RD0 0x08 /**< Remote DMA command bit 0 */
#define CR_RD1 0x10 /**< Remote DMA command bit 1 */
#define CR_RD2 0x20 /**< Remote DMA command bit 2 (abort/complete) */
#define CR_PS0 0x40 /**< Page Select bit 0 */
#define CR_PS1 0x80 /**< Page Select bit 1 */
/** Remote DMA read */
#define CR_DMA_READ CR_RD0
/** Remote DMA write */
#define CR_DMA_WRITE CR_RD1
/** Abort/complete remote DMA */
#define CR_DMA_ABORT CR_RD2
/* ================================================================
* ISR / IMR bits
* ================================================================ */
#define ISR_PRX 0x01 /**< Packet Received */
#define ISR_PTX 0x02 /**< Packet Transmitted */
#define ISR_RXE 0x04 /**< Receive Error */
#define ISR_TXE 0x08 /**< Transmit Error */
#define ISR_OVW 0x10 /**< Overflow Warning */
#define ISR_CNT 0x20 /**< Counter Overflow */
#define ISR_RDC 0x40 /**< Remote DMA Complete */
#define ISR_RST 0x80 /**< Reset Status */
/* ================================================================
* DCR bits
* ================================================================ */
#define DCR_WTS 0x01 /**< Word Transfer Select (1=16-bit) */
#define DCR_BOS 0x02 /**< Byte Order Select */
#define DCR_LAS 0x04 /**< Long Address Select */
#define DCR_LS 0x08 /**< Loopback Select (1=normal) */
#define DCR_AR 0x10 /**< Auto-initialize Remote */
#define DCR_FT0 0x20 /**< FIFO Threshold bit 0 */
#define DCR_FT1 0x40 /**< FIFO Threshold bit 1 */
/* ================================================================
* TCR bits
* ================================================================ */
#define TCR_LB0 0x02 /**< Loopback bit 0 */
#define TCR_LB1 0x04 /**< Loopback bit 1 */
/* ================================================================
* RCR bits
* ================================================================ */
#define RCR_SEP 0x01 /**< Save Errored Packets */
#define RCR_AR 0x02 /**< Accept Runt Packets */
#define RCR_AB 0x04 /**< Accept Broadcast */
#define RCR_AM 0x08 /**< Accept Multicast */
#define RCR_PRO 0x10 /**< Promiscuous Mode */
#define RCR_MON 0x20 /**< Monitor Mode */
/* ================================================================
* NE2000 ring buffer layout (16 KiB on-card RAM)
*
* Pages are 256 bytes each. NE2000 has 32 pages (0x40-0x80).
* - TX buffer: page 0x40 (room for 1 MTU frame, 6 pages)
* - RX ring: pages 0x46-0x80
* ================================================================ */
#define NE2K_MEM_START 0x40 /**< Start of NE2000 RAM (page number) */
#define NE2K_MEM_END 0x80 /**< End of NE2000 RAM (page number, exclusive) */
#define NE2K_TX_START 0x40 /**< TX buffer start page */
#define NE2K_RX_START 0x46 /**< RX ring start page */
#define NE2K_RX_STOP 0x80 /**< RX ring stop page (exclusive) */
/** Maximum Ethernet frame size. */
#define ETH_FRAME_MAX 1518
#define ETH_HEADER_SIZE 14
/* ================================================================
* NE2000 received packet header (prepended by the card)
* ================================================================ */
typedef struct __attribute__((packed)) ne2k_rx_header {
uint8_t status; /**< Receive status (matches RSR). */
uint8_t next_page; /**< Next packet page pointer. */
uint16_t length; /**< Total length including this header. */
} ne2k_rx_header_t;
/* ================================================================
* NE2000 device state
* ================================================================ */
typedef struct ne2k_device {
uint16_t io_base; /**< I/O base address. */
uint8_t irq; /**< IRQ number. */
uint8_t mac[6]; /**< MAC address. */
uint8_t next_rx_page; /**< Next page to read from RX ring. */
int present; /**< 1 if card detected. */
} ne2k_device_t;
/* ================================================================
* Public API
* ================================================================ */
/**
* Initialize NE2000 driver.
* Called automatically via REGISTER_DRIVER.
*/
int ne2k_init(void);
/**
* NE2000 IRQ handler.
* Called from isr.c when the NE2000 IRQ fires.
*/
void ne2k_irq(void);
/**
* Send an Ethernet frame.
*
* @param dev NE2000 device.
* @param data Frame data (starting with destination MAC).
* @param len Frame length in bytes (max 1518).
* @return 0 on success, -1 on failure.
*/
int ne2k_send(ne2k_device_t *dev, const void *data, uint32_t len);
/**
* Receive a pending Ethernet frame.
*
* @param dev NE2000 device.
* @param buf Buffer for received frame.
* @param bufsize Buffer size.
* @return Number of bytes received, 0 if no packet, -1 on error.
*/
int ne2k_recv(ne2k_device_t *dev, void *buf, uint32_t bufsize);
/**
* Get the NE2000 device pointer (for eth subsystem).
* @return Pointer to the device struct, or NULL if not present.
*/
ne2k_device_t *ne2k_get_device(void);
#endif /* NE2000_H */

View File

@@ -17,6 +17,9 @@
#include "cpio.h"
#include "paging.h"
#include "pmm.h"
#include "tcp.h"
#include "udp.h"
#include "graphics.h"
#include <stddef.h>
#include <string.h>
@@ -339,22 +342,210 @@ static int32_t sys_readdir(registers_t *regs) {
return (int32_t)entry.type;
}
/* ================================================================
* Networking system calls
* ================================================================ */
/** Socket type constants (kernel side). */
#define SOCK_TYPE_TCP 0
#define SOCK_TYPE_UDP 1
/** Per-process socket tracking (simple: global table). */
#define MAX_USER_SOCKETS 16
static struct {
uint8_t active;
uint8_t type; /* SOCK_TYPE_TCP or SOCK_TYPE_UDP */
int kern_sockfd; /* Kernel-side socket index */
} user_sockets[MAX_USER_SOCKETS];
/**
* Handle SYS_SOCKET: create a network socket.
* EBX = type (0=TCP, 1=UDP).
* Returns user sockfd (>= 0) or -1.
*/
static int32_t sys_socket(registers_t *regs) {
uint32_t type = regs->ebx;
int kern_fd;
if (type == SOCK_TYPE_TCP) {
kern_fd = tcp_socket_create();
} else if (type == SOCK_TYPE_UDP) {
kern_fd = udp_socket_create();
} else {
return -1;
}
if (kern_fd < 0) return -1;
/* Find a free user socket slot */
for (int i = 0; i < MAX_USER_SOCKETS; i++) {
if (!user_sockets[i].active) {
user_sockets[i].active = 1;
user_sockets[i].type = (uint8_t)type;
user_sockets[i].kern_sockfd = kern_fd;
return i;
}
}
/* No free slots — close the kernel socket */
if (type == SOCK_TYPE_TCP) tcp_close(kern_fd);
else udp_close(kern_fd);
return -1;
}
/**
* Handle SYS_CONNECT: connect a TCP socket to a remote host.
* EBX = user sockfd, ECX = remote IP (host byte order), EDX = remote port.
* Returns 0 on success (SYN sent), -1 on failure.
*/
static int32_t sys_connect(registers_t *regs) {
int ufd = (int)regs->ebx;
uint32_t remote_ip = regs->ecx;
uint16_t remote_port = (uint16_t)regs->edx;
if (ufd < 0 || ufd >= MAX_USER_SOCKETS || !user_sockets[ufd].active)
return -1;
if (user_sockets[ufd].type != SOCK_TYPE_TCP)
return -1;
return tcp_connect(user_sockets[ufd].kern_sockfd, remote_ip, remote_port);
}
/**
* Handle SYS_SEND: send data on a socket.
* EBX = user sockfd, ECX = buffer pointer, EDX = length.
* Returns bytes sent or -1.
*/
static int32_t sys_send(registers_t *regs) {
int ufd = (int)regs->ebx;
const void *buf = (const void *)regs->ecx;
uint32_t len = regs->edx;
if (ufd < 0 || ufd >= MAX_USER_SOCKETS || !user_sockets[ufd].active)
return -1;
if (user_sockets[ufd].type == SOCK_TYPE_TCP)
return tcp_send(user_sockets[ufd].kern_sockfd, buf, len);
else
return -1; /* UDP sendto requires address — not supported via SYS_SEND */
}
/**
* Handle SYS_RECV: receive data from a socket (non-blocking).
* EBX = user sockfd, ECX = buffer pointer, EDX = buffer size.
* Returns bytes received, 0 if no data, -1 on error/closed.
*/
static int32_t sys_recv(registers_t *regs) {
int ufd = (int)regs->ebx;
void *buf = (void *)regs->ecx;
uint32_t bufsize = regs->edx;
if (ufd < 0 || ufd >= MAX_USER_SOCKETS || !user_sockets[ufd].active)
return -1;
if (user_sockets[ufd].type == SOCK_TYPE_TCP)
return tcp_recv(user_sockets[ufd].kern_sockfd, buf, bufsize);
else
return -1; /* UDP recvfrom requires address — not supported via SYS_RECV */
}
/**
* Handle SYS_SOCKSTATE: get the state of a TCP socket.
* EBX = user sockfd.
* Returns TCP state constant, or -1 for invalid/UDP sockets.
*/
static int32_t sys_sockstate(registers_t *regs) {
int ufd = (int)regs->ebx;
if (ufd < 0 || ufd >= MAX_USER_SOCKETS || !user_sockets[ufd].active)
return -1;
if (user_sockets[ufd].type == SOCK_TYPE_TCP)
return (int32_t)tcp_get_state(user_sockets[ufd].kern_sockfd);
else
return -1;
}
/**
* Handle SYS_GFX: graphics operations.
* EBX = sub-command, ECX = arg1, EDX = arg2.
*/
static int32_t sys_gfx(registers_t *regs) {
uint32_t cmd = regs->ebx;
uint32_t arg1 = regs->ecx;
uint32_t arg2 = regs->edx;
switch (cmd) {
case GFX_CMD_ENTER:
graphics_enter();
return 0;
case GFX_CMD_LEAVE:
graphics_leave();
return 0;
case GFX_CMD_PIXEL: {
int x = (int)(arg1 & 0xFFFF);
int y = (int)(arg1 >> 16);
gfx_pixel(x, y, (uint8_t)arg2);
return 0;
}
case GFX_CMD_CLEAR:
gfx_clear((uint8_t)arg1);
return 0;
case GFX_CMD_FILL_RECT: {
const gfx_rect_t *r = (const gfx_rect_t *)arg1;
gfx_fill_rect((int)r->x, (int)r->y, (int)r->w, (int)r->h,
(uint8_t)r->color);
return 0;
}
case GFX_CMD_LINE: {
const gfx_line_t *l = (const gfx_line_t *)arg1;
gfx_draw_line((int)l->x1, (int)l->y1, (int)l->x2, (int)l->y2,
(uint8_t)l->color);
return 0;
}
case GFX_CMD_CIRCLE: {
const gfx_circle_t *c = (const gfx_circle_t *)arg1;
gfx_fill_circle((int)c->cx, (int)c->cy, (int)c->r,
(uint8_t)c->color);
return 0;
}
case GFX_CMD_GET_INFO:
return (int32_t)(GFX_WIDTH | (GFX_HEIGHT << 16));
default:
return -1;
}
}
/** System call dispatch table. */
typedef int32_t (*syscall_fn)(registers_t *);
static syscall_fn syscall_table[NUM_SYSCALLS] = {
[SYS_EXIT] = sys_exit,
[SYS_WRITE] = sys_write,
[SYS_READ] = sys_read,
[SYS_FORK] = sys_fork,
[SYS_GETPID] = sys_getpid,
[SYS_YIELD] = sys_yield,
[SYS_WAITPID] = sys_waitpid,
[SYS_EXEC] = sys_exec,
[SYS_GETENV] = sys_getenv,
[SYS_SETENV] = sys_setenv,
[SYS_READDIR] = sys_readdir,
[SYS_OPEN] = sys_open,
[SYS_CLOSE] = sys_close,
[SYS_EXIT] = sys_exit,
[SYS_WRITE] = sys_write,
[SYS_READ] = sys_read,
[SYS_FORK] = sys_fork,
[SYS_GETPID] = sys_getpid,
[SYS_YIELD] = sys_yield,
[SYS_WAITPID] = sys_waitpid,
[SYS_EXEC] = sys_exec,
[SYS_GETENV] = sys_getenv,
[SYS_SETENV] = sys_setenv,
[SYS_READDIR] = sys_readdir,
[SYS_OPEN] = sys_open,
[SYS_CLOSE] = sys_close,
[SYS_SOCKET] = sys_socket,
[SYS_CONNECT] = sys_connect,
[SYS_SEND] = sys_send,
[SYS_RECV] = sys_recv,
[SYS_SOCKSTATE] = sys_sockstate,
[SYS_GFX] = sys_gfx,
};
void syscall_handler(registers_t *regs) {

View File

@@ -27,9 +27,15 @@
#define SYS_READDIR 10 /**< Read directory entry. path=EBX, idx=ECX, buf=EDX. Returns type or -1. */
#define SYS_OPEN 11 /**< Open a file. path=EBX, flags=ECX. Returns fd or -1. */
#define SYS_CLOSE 12 /**< Close a file descriptor. fd=EBX. Returns 0 or -1. */
#define SYS_SOCKET 13 /**< Create a network socket. type=EBX (0=TCP, 1=UDP). Returns sockfd. */
#define SYS_CONNECT 14 /**< Connect TCP socket. sockfd=EBX, ip=ECX (host order), port=EDX. */
#define SYS_SEND 15 /**< Send data on socket. sockfd=EBX, buf=ECX, len=EDX. Returns bytes sent. */
#define SYS_RECV 16 /**< Receive data from socket. sockfd=EBX, buf=ECX, len=EDX. Returns bytes. */
#define SYS_SOCKSTATE 17 /**< Get socket state. sockfd=EBX. Returns state constant. */
#define SYS_GFX 18 /**< Graphics operations. subcmd=EBX, arg1=ECX, arg2=EDX. */
/** Total number of system calls. */
#define NUM_SYSCALLS 13
#define NUM_SYSCALLS 19
/**
* Initialize the system call handler.

512
src/tcp.c Normal file
View File

@@ -0,0 +1,512 @@
/**
* @file tcp.c
* @brief Transmission Control Protocol (TCP) implementation.
*
* Minimal TCP state machine supporting:
* - Active open (connect)
* - Data transfer (send/recv)
* - Connection teardown (close with FIN)
*
* Limitations:
* - No retransmission timer (single-attempt)
* - No congestion control
* - No passive open (listen/accept) yet
* - No out-of-order segment handling
* - Window size is static
*/
#include "tcp.h"
#include "ipv4.h"
#include "ethernet.h"
#include "kmalloc.h"
#include "string.h"
#include "vga.h"
/* ================================================================
* Internal state
* ================================================================ */
static tcp_socket_t tcp_sockets[TCP_MAX_SOCKETS];
static uint16_t tcp_next_port = 49152; /* Ephemeral port start. */
/* Simple pseudo-random ISN based on a counter. */
static uint32_t tcp_isn_counter = 0x12345678;
static uint32_t tcp_generate_isn(void) {
tcp_isn_counter = tcp_isn_counter * 1103515245 + 12345;
return tcp_isn_counter;
}
/* ================================================================
* TCP pseudo-header checksum
* ================================================================ */
/**
* Compute TCP checksum including the pseudo-header.
*/
static uint16_t tcp_checksum(uint32_t src_ip, uint32_t dst_ip,
const void *tcp_seg, uint32_t tcp_len)
{
uint32_t sum = 0;
/* Pseudo-header */
sum += (src_ip >> 16) & 0xFFFF;
sum += src_ip & 0xFFFF;
sum += (dst_ip >> 16) & 0xFFFF;
sum += dst_ip & 0xFFFF;
sum += htons(6); /* IP_PROTO_TCP */
sum += htons(tcp_len);
/* TCP segment */
const uint16_t *p = (const uint16_t *)tcp_seg;
uint32_t rem = tcp_len;
while (rem > 1) {
sum += *p++;
rem -= 2;
}
if (rem == 1) {
sum += *((const uint8_t *)p);
}
/* Fold carries */
while (sum >> 16)
sum = (sum & 0xFFFF) + (sum >> 16);
return (uint16_t)(~sum);
}
/* ================================================================
* Send a TCP segment
* ================================================================ */
static int tcp_send_segment(tcp_socket_t *sock, uint8_t flags,
const void *data, uint32_t data_len)
{
uint32_t total_len = TCP_HLEN + data_len;
uint8_t buf[TCP_HLEN + TCP_MSS];
if (total_len > sizeof(buf)) return -1;
/* Build TCP header */
tcp_header_t *hdr = (tcp_header_t *)buf;
hdr->src_port = htons(sock->local_port);
hdr->dst_port = htons(sock->remote_port);
hdr->seq_num = htonl(sock->snd_nxt);
hdr->ack_num = (flags & TCP_ACK) ? htonl(sock->rcv_nxt) : 0;
hdr->data_offset = (TCP_HLEN / 4) << 4; /* 5 words, no options */
hdr->flags = flags;
hdr->window = htons(TCP_RX_BUF_SIZE - sock->rx_count);
hdr->checksum = 0;
hdr->urgent = 0;
/* Copy payload */
if (data && data_len > 0) {
memcpy(buf + TCP_HLEN, data, data_len);
}
/* Compute local IP from first ethernet interface. */
eth_iface_t *iface = ethernet_get_iface(0);
uint32_t src_ip = iface ? iface->ip_addr : 0;
/* Compute checksum over the whole segment. */
hdr->checksum = tcp_checksum(htonl(src_ip), htonl(sock->remote_ip),
buf, total_len);
/* Advance SND.NXT for SYN, FIN, and data. */
if (flags & TCP_SYN)
sock->snd_nxt++;
if (flags & TCP_FIN)
sock->snd_nxt++;
sock->snd_nxt += data_len;
/* Send via IPv4. */
return ipv4_send(sock->remote_ip, IP_PROTO_TCP, buf, total_len);
}
/* ================================================================
* Send a RST in response to an unexpected segment
* ================================================================ */
static void tcp_send_rst(uint32_t src_ip, uint32_t dst_ip,
const tcp_header_t *in_hdr, uint32_t seg_len)
{
uint8_t buf[TCP_HLEN];
tcp_header_t *hdr = (tcp_header_t *)buf;
memset(buf, 0, TCP_HLEN);
hdr->src_port = in_hdr->dst_port; /* Already in network byte order. */
hdr->dst_port = in_hdr->src_port;
hdr->data_offset = (TCP_HLEN / 4) << 4;
if (in_hdr->flags & TCP_ACK) {
hdr->seq_num = in_hdr->ack_num;
hdr->flags = TCP_RST;
} else {
hdr->seq_num = 0;
hdr->ack_num = htonl(ntohl(in_hdr->seq_num) + seg_len);
hdr->flags = TCP_RST | TCP_ACK;
}
hdr->window = 0;
hdr->checksum = tcp_checksum(htonl(dst_ip), htonl(src_ip),
buf, TCP_HLEN);
ipv4_send(src_ip, IP_PROTO_TCP, buf, TCP_HLEN);
}
/* ================================================================
* Find a socket matching an incoming segment
* ================================================================ */
static tcp_socket_t *tcp_find_socket(uint32_t remote_ip,
uint16_t remote_port,
uint16_t local_port)
{
for (int i = 0; i < TCP_MAX_SOCKETS; i++) {
tcp_socket_t *s = &tcp_sockets[i];
if (!s->active) continue;
if (s->local_port != local_port) continue;
if (s->state == TCP_STATE_LISTEN) return s;
if (s->remote_port == remote_port && s->remote_ip == remote_ip)
return s;
}
return NULL;
}
/* ================================================================
* Ring buffer helpers
* ================================================================ */
static void rx_buf_write(tcp_socket_t *sock, const uint8_t *data, uint32_t len) {
for (uint32_t i = 0; i < len && sock->rx_count < TCP_RX_BUF_SIZE; i++) {
sock->rx_buf[sock->rx_head] = data[i];
sock->rx_head = (sock->rx_head + 1) % TCP_RX_BUF_SIZE;
sock->rx_count++;
}
}
static uint32_t rx_buf_read(tcp_socket_t *sock, uint8_t *buf, uint32_t len) {
uint32_t n = 0;
while (n < len && sock->rx_count > 0) {
buf[n++] = sock->rx_buf[sock->rx_tail];
sock->rx_tail = (sock->rx_tail + 1) % TCP_RX_BUF_SIZE;
sock->rx_count--;
}
return n;
}
/* ================================================================
* TCP input processing
* ================================================================ */
void tcp_receive(uint32_t src_ip_ho, uint32_t dst_ip_ho,
const void *data, uint32_t len, uint32_t iface_idx)
{
(void)iface_idx;
if (len < TCP_HLEN) return;
const tcp_header_t *hdr = (const tcp_header_t *)data;
uint32_t hdr_len = ((hdr->data_offset >> 4) & 0x0F) * 4;
if (hdr_len < TCP_HLEN || hdr_len > len) return;
uint16_t src_port = ntohs(hdr->src_port);
uint16_t dst_port = ntohs(hdr->dst_port);
uint32_t seq = ntohl(hdr->seq_num);
uint32_t ack = ntohl(hdr->ack_num);
uint8_t flags = hdr->flags;
const uint8_t *payload = (const uint8_t *)data + hdr_len;
uint32_t payload_len = len - hdr_len;
/* Compute segment length (SYN and FIN count as 1 byte each). */
uint32_t seg_len = payload_len;
if (flags & TCP_SYN) seg_len++;
if (flags & TCP_FIN) seg_len++;
/* Find matching socket. */
tcp_socket_t *sock = tcp_find_socket(src_ip_ho, src_port, dst_port);
if (!sock) {
/* No socket found — send RST if not already RST. */
if (!(flags & TCP_RST)) {
tcp_send_rst(src_ip_ho, dst_ip_ho, hdr, seg_len);
}
return;
}
/* Handle RST. */
if (flags & TCP_RST) {
sock->state = TCP_STATE_CLOSED;
sock->active = 0;
return;
}
switch (sock->state) {
case TCP_STATE_SYN_SENT:
/* Expecting SYN+ACK */
if ((flags & (TCP_SYN | TCP_ACK)) == (TCP_SYN | TCP_ACK)) {
if (ack == sock->snd_nxt) {
sock->rcv_nxt = seq + 1;
sock->snd_una = ack;
sock->state = TCP_STATE_ESTABLISHED;
/* Send ACK */
tcp_send_segment(sock, TCP_ACK, NULL, 0);
}
} else if (flags & TCP_SYN) {
/* Simultaneous open — simplified handling. */
sock->rcv_nxt = seq + 1;
sock->state = TCP_STATE_SYN_RECEIVED;
tcp_send_segment(sock, TCP_SYN | TCP_ACK, NULL, 0);
}
break;
case TCP_STATE_SYN_RECEIVED:
if (flags & TCP_ACK) {
if (ack == sock->snd_nxt) {
sock->snd_una = ack;
sock->state = TCP_STATE_ESTABLISHED;
}
}
break;
case TCP_STATE_ESTABLISHED:
/* Check sequence number. */
if (seq != sock->rcv_nxt) {
/* Out-of-order — send duplicate ACK. */
tcp_send_segment(sock, TCP_ACK, NULL, 0);
break;
}
/* Update SND.UNA if ACK present. */
if (flags & TCP_ACK) {
sock->snd_una = ack;
}
/* Process payload. */
if (payload_len > 0) {
rx_buf_write(sock, payload, payload_len);
sock->rcv_nxt += payload_len;
}
/* Handle FIN. */
if (flags & TCP_FIN) {
sock->rcv_nxt++;
sock->state = TCP_STATE_CLOSE_WAIT;
/* ACK the FIN. */
tcp_send_segment(sock, TCP_ACK, NULL, 0);
} else if (payload_len > 0) {
/* ACK the data. */
tcp_send_segment(sock, TCP_ACK, NULL, 0);
}
break;
case TCP_STATE_FIN_WAIT_1:
if (flags & TCP_ACK) {
sock->snd_una = ack;
}
if ((flags & TCP_FIN) && (flags & TCP_ACK) && ack == sock->snd_nxt) {
/* FIN+ACK: simultaneous close shortcut. */
sock->rcv_nxt++;
sock->state = TCP_STATE_TIME_WAIT;
tcp_send_segment(sock, TCP_ACK, NULL, 0);
} else if (flags & TCP_FIN) {
sock->rcv_nxt++;
sock->state = TCP_STATE_CLOSING;
tcp_send_segment(sock, TCP_ACK, NULL, 0);
} else if ((flags & TCP_ACK) && ack == sock->snd_nxt) {
/* Our FIN was ACKed. */
sock->state = TCP_STATE_FIN_WAIT_2;
}
/* Accept any data in FIN_WAIT_1 */
if (payload_len > 0 && seq == sock->rcv_nxt) {
rx_buf_write(sock, payload, payload_len);
sock->rcv_nxt += payload_len;
}
break;
case TCP_STATE_FIN_WAIT_2:
if (payload_len > 0 && seq == sock->rcv_nxt) {
rx_buf_write(sock, payload, payload_len);
sock->rcv_nxt += payload_len;
tcp_send_segment(sock, TCP_ACK, NULL, 0);
}
if (flags & TCP_FIN) {
sock->rcv_nxt++;
sock->state = TCP_STATE_TIME_WAIT;
tcp_send_segment(sock, TCP_ACK, NULL, 0);
}
break;
case TCP_STATE_CLOSE_WAIT:
/* Waiting for application to close. Accept ACKs. */
if (flags & TCP_ACK) {
sock->snd_una = ack;
}
break;
case TCP_STATE_CLOSING:
if ((flags & TCP_ACK) && ack == sock->snd_nxt) {
sock->state = TCP_STATE_TIME_WAIT;
}
break;
case TCP_STATE_LAST_ACK:
if ((flags & TCP_ACK) && ack == sock->snd_nxt) {
sock->state = TCP_STATE_CLOSED;
sock->active = 0;
}
break;
case TCP_STATE_TIME_WAIT:
/* In a real OS we'd wait 2*MSL. Here we just ACK and stay. */
if (flags & TCP_FIN) {
tcp_send_segment(sock, TCP_ACK, NULL, 0);
}
/* Immediately transition to CLOSED (no timer). */
sock->state = TCP_STATE_CLOSED;
sock->active = 0;
break;
default:
break;
}
}
/* ================================================================
* Public API
* ================================================================ */
void tcp_init(void)
{
memset(tcp_sockets, 0, sizeof(tcp_sockets));
ipv4_register_proto(IP_PROTO_TCP, tcp_receive);
vga_puts("[TCP] Initialized (");
vga_put_dec(TCP_MAX_SOCKETS);
vga_puts(" sockets)\n");
}
int tcp_socket_create(void)
{
for (int i = 0; i < TCP_MAX_SOCKETS; i++) {
if (!tcp_sockets[i].active) {
memset(&tcp_sockets[i], 0, sizeof(tcp_socket_t));
tcp_sockets[i].active = 1;
tcp_sockets[i].state = TCP_STATE_CLOSED;
return i;
}
}
return -1;
}
int tcp_connect(int sockfd, uint32_t remote_ip, uint16_t remote_port)
{
if (sockfd < 0 || sockfd >= TCP_MAX_SOCKETS) return -1;
tcp_socket_t *sock = &tcp_sockets[sockfd];
if (!sock->active || sock->state != TCP_STATE_CLOSED) return -1;
/* Assign ephemeral local port. */
sock->local_port = tcp_next_port++;
if (tcp_next_port == 0) tcp_next_port = 49152;
sock->remote_ip = remote_ip;
sock->remote_port = remote_port;
/* Generate ISN. */
sock->snd_nxt = tcp_generate_isn();
sock->snd_una = sock->snd_nxt;
/* Send SYN. */
sock->state = TCP_STATE_SYN_SENT;
return tcp_send_segment(sock, TCP_SYN, NULL, 0);
}
int tcp_send(int sockfd, const void *data, uint32_t len)
{
if (sockfd < 0 || sockfd >= TCP_MAX_SOCKETS) return -1;
tcp_socket_t *sock = &tcp_sockets[sockfd];
if (!sock->active || sock->state != TCP_STATE_ESTABLISHED) return -1;
/* Send in MSS-sized chunks. */
uint32_t sent = 0;
const uint8_t *p = (const uint8_t *)data;
while (sent < len) {
uint32_t chunk = len - sent;
if (chunk > TCP_MSS) chunk = TCP_MSS;
if (tcp_send_segment(sock, TCP_ACK | TCP_PSH, p + sent, chunk) < 0)
break;
sent += chunk;
}
return (int)sent;
}
int tcp_recv(int sockfd, void *buf, uint32_t bufsize)
{
if (sockfd < 0 || sockfd >= TCP_MAX_SOCKETS) return -1;
tcp_socket_t *sock = &tcp_sockets[sockfd];
if (!sock->active) return -1;
/* If connection is closed with data still in buffer, return it. */
if (sock->rx_count == 0) {
if (sock->state == TCP_STATE_CLOSE_WAIT ||
sock->state == TCP_STATE_CLOSED) {
return -1; /* EOF / connection closed. */
}
return 0; /* No data available yet. */
}
return (int)rx_buf_read(sock, (uint8_t *)buf, bufsize);
}
void tcp_close(int sockfd)
{
if (sockfd < 0 || sockfd >= TCP_MAX_SOCKETS) return;
tcp_socket_t *sock = &tcp_sockets[sockfd];
if (!sock->active) return;
switch (sock->state) {
case TCP_STATE_ESTABLISHED:
sock->state = TCP_STATE_FIN_WAIT_1;
tcp_send_segment(sock, TCP_FIN | TCP_ACK, NULL, 0);
break;
case TCP_STATE_CLOSE_WAIT:
sock->state = TCP_STATE_LAST_ACK;
tcp_send_segment(sock, TCP_FIN | TCP_ACK, NULL, 0);
break;
case TCP_STATE_SYN_SENT:
case TCP_STATE_SYN_RECEIVED:
sock->state = TCP_STATE_CLOSED;
sock->active = 0;
break;
default:
/* Already closing or closed — force close. */
sock->state = TCP_STATE_CLOSED;
sock->active = 0;
break;
}
}
uint8_t tcp_get_state(int sockfd)
{
if (sockfd < 0 || sockfd >= TCP_MAX_SOCKETS) return TCP_STATE_CLOSED;
return tcp_sockets[sockfd].state;
}
const char *tcp_state_name(uint8_t state)
{
switch (state) {
case TCP_STATE_CLOSED: return "CLOSED";
case TCP_STATE_LISTEN: return "LISTEN";
case TCP_STATE_SYN_SENT: return "SYN_SENT";
case TCP_STATE_SYN_RECEIVED: return "SYN_RECEIVED";
case TCP_STATE_ESTABLISHED: return "ESTABLISHED";
case TCP_STATE_FIN_WAIT_1: return "FIN_WAIT_1";
case TCP_STATE_FIN_WAIT_2: return "FIN_WAIT_2";
case TCP_STATE_CLOSE_WAIT: return "CLOSE_WAIT";
case TCP_STATE_CLOSING: return "CLOSING";
case TCP_STATE_LAST_ACK: return "LAST_ACK";
case TCP_STATE_TIME_WAIT: return "TIME_WAIT";
default: return "UNKNOWN";
}
}

169
src/tcp.h Normal file
View File

@@ -0,0 +1,169 @@
/**
* @file tcp.h
* @brief Transmission Control Protocol (TCP) subsystem.
*
* Provides a minimal TCP implementation supporting active open
* (client connections). Implements the core TCP state machine
* for connection setup (SYN/SYN-ACK/ACK), data transfer, and
* connection teardown (FIN/ACK).
*
* Built on top of the IPv4 stack.
*/
#ifndef TCP_H
#define TCP_H
#include <stdint.h>
/** Maximum number of TCP sockets. */
#define TCP_MAX_SOCKETS 8
/** Maximum TCP segment payload. */
#define TCP_MSS 1460 /* ETH_MTU(1500) - IP(20) - TCP(20) */
/** TCP header size (no options). */
#define TCP_HLEN 20
/** TCP receive buffer size per socket. */
#define TCP_RX_BUF_SIZE 4096
/** TCP send buffer size per socket. */
#define TCP_TX_BUF_SIZE 4096
/* ================================================================
* TCP flags
* ================================================================ */
#define TCP_FIN 0x01
#define TCP_SYN 0x02
#define TCP_RST 0x04
#define TCP_PSH 0x08
#define TCP_ACK 0x10
#define TCP_URG 0x20
/* ================================================================
* TCP states (RFC 793)
* ================================================================ */
#define TCP_STATE_CLOSED 0
#define TCP_STATE_LISTEN 1
#define TCP_STATE_SYN_SENT 2
#define TCP_STATE_SYN_RECEIVED 3
#define TCP_STATE_ESTABLISHED 4
#define TCP_STATE_FIN_WAIT_1 5
#define TCP_STATE_FIN_WAIT_2 6
#define TCP_STATE_CLOSE_WAIT 7
#define TCP_STATE_CLOSING 8
#define TCP_STATE_LAST_ACK 9
#define TCP_STATE_TIME_WAIT 10
/* ================================================================
* TCP header
* ================================================================ */
typedef struct __attribute__((packed)) tcp_header {
uint16_t src_port; /**< Source port. */
uint16_t dst_port; /**< Destination port. */
uint32_t seq_num; /**< Sequence number. */
uint32_t ack_num; /**< Acknowledgment number. */
uint8_t data_offset; /**< Data offset (upper 4 bits) + reserved. */
uint8_t flags; /**< TCP flags. */
uint16_t window; /**< Window size. */
uint16_t checksum; /**< Checksum. */
uint16_t urgent; /**< Urgent pointer. */
} tcp_header_t;
/* ================================================================
* TCP socket
* ================================================================ */
typedef struct tcp_socket {
uint8_t active; /**< 1 if in use. */
uint8_t state; /**< TCP_STATE_*. */
uint16_t local_port; /**< Local port (host byte order). */
uint16_t remote_port; /**< Remote port (host byte order). */
uint32_t remote_ip; /**< Remote IP (host byte order). */
/* Sequence numbers */
uint32_t snd_una; /**< Send unacknowledged. */
uint32_t snd_nxt; /**< Send next. */
uint32_t rcv_nxt; /**< Receive next expected. */
/* Receive buffer (ring buffer) */
uint8_t rx_buf[TCP_RX_BUF_SIZE];
uint32_t rx_head; /**< Write position. */
uint32_t rx_tail; /**< Read position. */
uint32_t rx_count; /**< Bytes available. */
} tcp_socket_t;
/* ================================================================
* Public API
* ================================================================ */
/**
* Initialize the TCP subsystem.
*/
void tcp_init(void);
/**
* Create a TCP socket.
* @return Socket index (>= 0), or -1 on failure.
*/
int tcp_socket_create(void);
/**
* Connect to a remote host (active open).
* Sends SYN and transitions to SYN_SENT state.
*
* @param sockfd Socket index.
* @param remote_ip Remote IP (host byte order).
* @param remote_port Remote port (host byte order).
* @return 0 on success (SYN sent), -1 on failure.
*/
int tcp_connect(int sockfd, uint32_t remote_ip, uint16_t remote_port);
/**
* Send data on an established connection.
*
* @param sockfd Socket index.
* @param data Data to send.
* @param len Data length.
* @return Number of bytes sent, or -1 on failure.
*/
int tcp_send(int sockfd, const void *data, uint32_t len);
/**
* Receive data from an established connection (non-blocking).
*
* @param sockfd Socket index.
* @param buf Buffer.
* @param bufsize Buffer size.
* @return Number of bytes received, 0 if no data, -1 on error/closed.
*/
int tcp_recv(int sockfd, void *buf, uint32_t bufsize);
/**
* Close a TCP connection.
*
* @param sockfd Socket index.
*/
void tcp_close(int sockfd);
/**
* Get the state of a TCP socket.
*
* @param sockfd Socket index.
* @return TCP state constant, or TCP_STATE_CLOSED for invalid sockets.
*/
uint8_t tcp_get_state(int sockfd);
/**
* Get the state name as a string.
*/
const char *tcp_state_name(uint8_t state);
/**
* Process an incoming TCP segment (called from IPv4 layer).
*/
void tcp_receive(uint32_t src_ip, uint32_t dst_ip,
const void *data, uint32_t len, uint32_t iface_idx);
#endif /* TCP_H */

180
src/udp.c Normal file
View File

@@ -0,0 +1,180 @@
/**
* @file udp.c
* @brief UDP subsystem implementation.
*
* Provides a simple socket-like interface for sending and receiving
* UDP datagrams. Registers with the IPv4 stack as protocol handler 17.
*/
#include "udp.h"
#include "ipv4.h"
#include "ethernet.h"
#include <string.h>
/* Debug print helpers */
extern void offset_print(const char *str);
extern void print_hex(uint32_t val);
/* ================================================================
* Global state
* ================================================================ */
/** UDP socket table. */
static udp_socket_t sockets[UDP_MAX_SOCKETS];
/* ================================================================
* Internal helpers
* ================================================================ */
/**
* Find a socket bound to the given local port.
*/
static udp_socket_t *find_socket_by_port(uint16_t port) {
for (int i = 0; i < UDP_MAX_SOCKETS; i++) {
if (sockets[i].active && sockets[i].bound &&
sockets[i].local_port == port) {
return &sockets[i];
}
}
return NULL;
}
/* ================================================================
* Public API
* ================================================================ */
int udp_socket_create(void) {
for (int i = 0; i < UDP_MAX_SOCKETS; i++) {
if (!sockets[i].active) {
memset(&sockets[i], 0, sizeof(udp_socket_t));
sockets[i].active = 1;
return i;
}
}
return -1;
}
int udp_bind(int sockfd, uint16_t port) {
if (sockfd < 0 || sockfd >= UDP_MAX_SOCKETS) return -1;
if (!sockets[sockfd].active) return -1;
/* Check port not already used */
if (find_socket_by_port(port)) return -1;
sockets[sockfd].local_port = port;
sockets[sockfd].bound = 1;
return 0;
}
int udp_sendto(int sockfd, uint32_t dst_ip, uint16_t dst_port,
const void *data, uint32_t len) {
if (sockfd < 0 || sockfd >= UDP_MAX_SOCKETS) return -1;
if (!sockets[sockfd].active) return -1;
if (len > UDP_MAX_PAYLOAD) return -1;
/* Build UDP datagram */
uint8_t pkt[UDP_HLEN + UDP_MAX_PAYLOAD];
udp_header_t *hdr = (udp_header_t *)pkt;
hdr->src_port = htons(sockets[sockfd].local_port);
hdr->dst_port = htons(dst_port);
hdr->length = htons((uint16_t)(UDP_HLEN + len));
hdr->checksum = 0; /* Optional in IPv4 */
memcpy(pkt + UDP_HLEN, data, len);
/* Send via IPv4 */
int ret = ipv4_send(dst_ip, IP_PROTO_UDP, pkt, UDP_HLEN + len);
return (ret == 0) ? (int)len : -1;
}
int udp_recvfrom(int sockfd, void *buf, uint32_t bufsize,
uint32_t *src_ip, uint16_t *src_port) {
if (sockfd < 0 || sockfd >= UDP_MAX_SOCKETS) return -1;
if (!sockets[sockfd].active) return -1;
udp_socket_t *sock = &sockets[sockfd];
/* Scan the receive queue for the oldest entry */
for (int i = 0; i < UDP_RX_QUEUE_SIZE; i++) {
if (sock->rx_queue[i].used) {
udp_rx_entry_t *entry = &sock->rx_queue[i];
uint32_t copy_len = entry->len;
if (copy_len > bufsize) copy_len = bufsize;
memcpy(buf, entry->data, copy_len);
if (src_ip) *src_ip = entry->src_ip;
if (src_port) *src_port = entry->src_port;
entry->used = 0; /* Free the slot */
return (int)copy_len;
}
}
return 0; /* No data available */
}
void udp_close(int sockfd) {
if (sockfd < 0 || sockfd >= UDP_MAX_SOCKETS) return;
memset(&sockets[sockfd], 0, sizeof(udp_socket_t));
}
/* ================================================================
* IPv4 protocol handler
* ================================================================ */
void udp_receive(uint32_t src_ip, uint32_t dst_ip,
const void *data, uint32_t len, uint32_t iface_idx) {
(void)dst_ip;
(void)iface_idx;
if (len < UDP_HLEN) return;
const udp_header_t *hdr = (const udp_header_t *)data;
uint16_t dst_port = ntohs(hdr->dst_port);
uint16_t src_port_val = ntohs(hdr->src_port);
uint16_t udp_len = ntohs(hdr->length);
if (udp_len > len) return;
uint32_t payload_len = udp_len - UDP_HLEN;
/* Find socket bound to this port */
udp_socket_t *sock = find_socket_by_port(dst_port);
if (!sock) return;
/* Enqueue the datagram */
for (int i = 0; i < UDP_RX_QUEUE_SIZE; i++) {
if (!sock->rx_queue[i].used) {
udp_rx_entry_t *entry = &sock->rx_queue[i];
entry->src_ip = src_ip;
entry->src_port = src_port_val;
entry->len = (uint16_t)(payload_len > UDP_RX_BUF_SIZE ?
UDP_RX_BUF_SIZE : payload_len);
memcpy(entry->data, (const uint8_t *)data + UDP_HLEN,
entry->len);
entry->used = 1;
return;
}
}
/* Queue full — drop packet */
}
/* ================================================================
* Initialization
* ================================================================ */
/**
* IPv4 callback wrapper for UDP.
*/
static void udp_ipv4_handler(uint32_t src_ip, uint32_t dst_ip,
const void *payload, uint32_t len,
uint32_t iface_idx) {
udp_receive(src_ip, dst_ip, payload, len, iface_idx);
}
void udp_init(void) {
memset(sockets, 0, sizeof(sockets));
ipv4_register_proto(IP_PROTO_UDP, udp_ipv4_handler);
offset_print(" UDP: initialized\n");
}

128
src/udp.h Normal file
View File

@@ -0,0 +1,128 @@
/**
* @file udp.h
* @brief User Datagram Protocol (UDP) subsystem.
*
* Provides a simple UDP socket interface built on top of the IPv4 stack.
* Supports connectionless datagram communication.
*/
#ifndef UDP_H
#define UDP_H
#include <stdint.h>
/** Maximum number of UDP sockets. */
#define UDP_MAX_SOCKETS 16
/** Maximum UDP payload size. */
#define UDP_MAX_PAYLOAD 1472 /* ETH_MTU(1500) - IP(20) - UDP(8) */
/** UDP header size. */
#define UDP_HLEN 8
/* ================================================================
* UDP header
* ================================================================ */
typedef struct __attribute__((packed)) udp_header {
uint16_t src_port; /**< Source port (network byte order). */
uint16_t dst_port; /**< Destination port (network byte order). */
uint16_t length; /**< Total length (header + payload, NBO). */
uint16_t checksum; /**< Checksum (0 = not computed). */
} udp_header_t;
/* ================================================================
* UDP socket
* ================================================================ */
/** Maximum number of queued received datagrams per socket. */
#define UDP_RX_QUEUE_SIZE 8
/** Maximum datagram size in the receive queue. */
#define UDP_RX_BUF_SIZE 1500
/** Received datagram in the queue. */
typedef struct udp_rx_entry {
uint32_t src_ip; /**< Source IP (host byte order). */
uint16_t src_port; /**< Source port (host byte order). */
uint16_t len; /**< Payload length. */
uint8_t data[UDP_RX_BUF_SIZE]; /**< Payload data. */
uint8_t used; /**< 1 if slot is occupied. */
} udp_rx_entry_t;
/**
* UDP socket state.
*/
typedef struct udp_socket {
uint16_t local_port; /**< Bound local port (host byte order). */
uint8_t active; /**< 1 if socket is in use. */
uint8_t bound; /**< 1 if bound to a port. */
udp_rx_entry_t rx_queue[UDP_RX_QUEUE_SIZE]; /**< Receive queue. */
uint32_t rx_head; /**< Next slot to write. */
} udp_socket_t;
/* ================================================================
* Public API
* ================================================================ */
/**
* Initialize the UDP subsystem.
* Registers as an IPv4 protocol handler.
*/
void udp_init(void);
/**
* Create a UDP socket.
* @return Socket index (>= 0) on success, -1 on failure.
*/
int udp_socket_create(void);
/**
* Bind a socket to a local port.
*
* @param sockfd Socket index.
* @param port Local port number (host byte order).
* @return 0 on success, -1 on failure.
*/
int udp_bind(int sockfd, uint16_t port);
/**
* Send a UDP datagram.
*
* @param sockfd Socket index.
* @param dst_ip Destination IP (host byte order).
* @param dst_port Destination port (host byte order).
* @param data Payload data.
* @param len Payload length (max UDP_MAX_PAYLOAD).
* @return Number of bytes sent, or -1 on failure.
*/
int udp_sendto(int sockfd, uint32_t dst_ip, uint16_t dst_port,
const void *data, uint32_t len);
/**
* Receive a UDP datagram (non-blocking).
*
* @param sockfd Socket index.
* @param buf Buffer for payload.
* @param bufsize Buffer size.
* @param src_ip Output: source IP (host byte order, can be NULL).
* @param src_port Output: source port (host byte order, can be NULL).
* @return Number of bytes received, 0 if no data, -1 on error.
*/
int udp_recvfrom(int sockfd, void *buf, uint32_t bufsize,
uint32_t *src_ip, uint16_t *src_port);
/**
* Close a UDP socket.
*
* @param sockfd Socket index.
*/
void udp_close(int sockfd);
/**
* Process an incoming UDP datagram (called from IPv4 layer).
*/
void udp_receive(uint32_t src_ip, uint32_t dst_ip,
const void *data, uint32_t len, uint32_t iface_idx);
#endif /* UDP_H */