Compare commits
11 Commits
27b2042523
...
attempt-2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
30c33c30b6 | ||
|
|
6a8d561b3e | ||
|
|
fc5fe9af63 | ||
|
|
57b2751a81 | ||
|
|
e6929438a0 | ||
|
|
d7ce0d5856 | ||
|
|
d7d7e8e58e | ||
|
|
1825448528 | ||
|
|
35bafdcad9 | ||
|
|
35bce963be | ||
|
|
f87a4e3101 |
22
README.md
22
README.md
@@ -68,16 +68,16 @@ Once a task is completed, it should be checked off.
|
|||||||
- [x] Add support for character device to the devicefs subsystem.
|
- [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 `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 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.
|
- [x] Create a network driver for the NE2000 NIC.
|
||||||
- [ ] Create a network driver for the 3C509B NIC. It should only support RJ45 and 10base-T.
|
- [x] Create a network driver for the 3C509B NIC. It should only support RJ45 and 10base-T.
|
||||||
- [ ] Create an ethernet subsytsem. Each ethernet device should be shown as a character device with the name `ethN`.
|
- [x] Create an ethernet subsytsem. Each ethernet device should be shown as a character device with the name `ethN`.
|
||||||
- [ ] Create a IPv4 stack. Create the `ip` app that shows curernt IPv4 configuration. It should read this information from `/sys`
|
- [x] 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`
|
- [x] 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.
|
- [x] Create a DHCP subsystem. Create the `dhcp` command to show current DHCP status information.
|
||||||
- [ ] Create a UDP and TCP stack.
|
- [x] Create a UDP and TCP stack.
|
||||||
- [ ] Implement a simple version of `ftp` and `wget`.
|
- [x] 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.
|
- [x] 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.
|
- [x] Create a simple game of pool. It should use graphics mode to render the game.
|
||||||
- [ ] Create a simple game of minigolf.
|
- [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.
|
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
30
apps/arp/arp.c
Normal 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
28
apps/dhcp/dhcp.c
Normal 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;
|
||||||
|
}
|
||||||
302
apps/ftp/ftp.c
Normal file
302
apps/ftp/ftp.c
Normal 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
202
apps/ip/ip.c
Normal 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;
|
||||||
|
}
|
||||||
@@ -26,6 +26,12 @@ typedef int int32_t;
|
|||||||
#define SYS_READDIR 10
|
#define SYS_READDIR 10
|
||||||
#define SYS_OPEN 11
|
#define SYS_OPEN 11
|
||||||
#define SYS_CLOSE 12
|
#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) {
|
static inline int32_t syscall0(int num) {
|
||||||
int32_t ret;
|
int32_t ret;
|
||||||
@@ -124,6 +130,157 @@ static inline int32_t close(int32_t fd) {
|
|||||||
return syscall1(SYS_CLOSE, (uint32_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 */
|
/* Basic string operations for user-space */
|
||||||
static inline uint32_t strlen(const char *s) {
|
static inline uint32_t strlen(const char *s) {
|
||||||
uint32_t len = 0;
|
uint32_t len = 0;
|
||||||
|
|||||||
585
apps/minigolf/minigolf.c
Normal file
585
apps/minigolf/minigolf.c
Normal 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;
|
||||||
|
}
|
||||||
582
apps/pool/pool.c
Normal file
582
apps/pool/pool.c
Normal 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;
|
||||||
|
}
|
||||||
236
apps/wget/wget.c
Normal file
236
apps/wget/wget.c
Normal 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;
|
||||||
|
}
|
||||||
40
build.log
40
build.log
@@ -1,6 +1,10 @@
|
|||||||
[ 3%] Building user-mode applications
|
[ 2%] Building user-mode applications
|
||||||
|
Building app: arp
|
||||||
|
Built: /workspaces/claude-os/build/apps_bin/arp (214 bytes)
|
||||||
Building app: cat
|
Building app: cat
|
||||||
Built: /workspaces/claude-os/build/apps_bin/cat (310 bytes)
|
Built: /workspaces/claude-os/build/apps_bin/cat (310 bytes)
|
||||||
|
Building app: dhcp
|
||||||
|
Built: /workspaces/claude-os/build/apps_bin/dhcp (219 bytes)
|
||||||
Building app: diskpart
|
Building app: diskpart
|
||||||
/usr/bin/ld: warning: /workspaces/claude-os/build/apps_bin/diskpart.elf has a LOAD segment with RWX permissions
|
/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)
|
Built: /workspaces/claude-os/build/apps_bin/diskpart (8406 bytes)
|
||||||
@@ -9,10 +13,21 @@ Building app: env-test
|
|||||||
Built: /workspaces/claude-os/build/apps_bin/env-test (389 bytes)
|
Built: /workspaces/claude-os/build/apps_bin/env-test (389 bytes)
|
||||||
Building app: fork-test
|
Building app: fork-test
|
||||||
Built: /workspaces/claude-os/build/apps_bin/fork-test (132 bytes)
|
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
|
Building app: hello-world
|
||||||
Built: /workspaces/claude-os/build/apps_bin/hello-world (49 bytes)
|
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
|
Building app: ls
|
||||||
Built: /workspaces/claude-os/build/apps_bin/ls (250 bytes)
|
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
|
Building app: mkfs.fat32
|
||||||
/workspaces/claude-os/apps/mkfs.fat32/mkfs.fat32.c:56:13: warning: unused function 'print_hex' [-Wunused-function]
|
/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) {
|
56 | static void print_hex(uint32_t val) {
|
||||||
@@ -22,17 +37,22 @@ Building app: mkfs.fat32
|
|||||||
Built: /workspaces/claude-os/build/apps_bin/mkfs.fat32 (5121 bytes)
|
Built: /workspaces/claude-os/build/apps_bin/mkfs.fat32 (5121 bytes)
|
||||||
Building app: mount
|
Building app: mount
|
||||||
Built: /workspaces/claude-os/build/apps_bin/mount (992 bytes)
|
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
|
Building app: sh
|
||||||
/workspaces/claude-os/apps/sh/sh.c:167:17: warning: unused variable 'type' [-Wunused-variable]
|
/workspaces/claude-os/apps/sh/sh.c:167:17: warning: unused variable 'type' [-Wunused-variable]
|
||||||
167 | int32_t type = readdir(resolved, 0, name);
|
167 | int32_t type = readdir(resolved, 0, name);
|
||||||
| ^~~~
|
| ^~~~
|
||||||
1 warning generated.
|
1 warning generated.
|
||||||
Built: /workspaces/claude-os/build/apps_bin/sh (3428 bytes)
|
Built: /workspaces/claude-os/build/apps_bin/sh (3428 bytes)
|
||||||
[ 3%] Built target apps
|
Building app: wget
|
||||||
[ 6%] Generating CPIO initial ramdisk
|
Built: /workspaces/claude-os/build/apps_bin/wget (2193 bytes)
|
||||||
Generated initrd: 20288 bytes
|
[ 2%] Built target apps
|
||||||
[ 6%] Built target initrd
|
[ 4%] Generating CPIO initial ramdisk
|
||||||
[ 96%] Built target kernel
|
Generated initrd: 37232 bytes
|
||||||
|
[ 4%] Built target initrd
|
||||||
|
[ 97%] Built target kernel
|
||||||
[100%] Generating bootable ISO image
|
[100%] Generating bootable ISO image
|
||||||
xorriso 1.5.6 : RockRidge filesystem manipulator, libburnia project.
|
xorriso 1.5.6 : RockRidge filesystem manipulator, libburnia project.
|
||||||
|
|
||||||
@@ -40,14 +60,14 @@ Drive current: -outdev 'stdio:/workspaces/claude-os/release/claude-os.iso'
|
|||||||
Media current: stdio file, overwriteable
|
Media current: stdio file, overwriteable
|
||||||
Media status : is blank
|
Media status : is blank
|
||||||
Media summary: 0 sessions, 0 data blocks, 0 data, 126g free
|
Media summary: 0 sessions, 0 data blocks, 0 data, 126g free
|
||||||
Added to ISO image: directory '/'='/tmp/grub.bEiDnH'
|
Added to ISO image: directory '/'='/tmp/grub.NLidpF'
|
||||||
xorriso : UPDATE : 581 files added in 1 seconds
|
xorriso : UPDATE : 581 files added in 1 seconds
|
||||||
Added to ISO image: directory '/'='/workspaces/claude-os/build/isodir'
|
Added to ISO image: directory '/'='/workspaces/claude-os/build/isodir'
|
||||||
xorriso : UPDATE : 586 files added in 1 seconds
|
xorriso : UPDATE : 586 files added in 1 seconds
|
||||||
xorriso : NOTE : Copying to System Area: 512 bytes from file '/usr/lib/grub/i386-pc/boot_hybrid.img'
|
xorriso : NOTE : Copying to System Area: 512 bytes from file '/usr/lib/grub/i386-pc/boot_hybrid.img'
|
||||||
xorriso : UPDATE : Thank you for being patient. Working since 0 seconds.
|
xorriso : UPDATE : 64.38% done
|
||||||
ISO image produced: 5924 sectors
|
ISO image produced: 6058 sectors
|
||||||
Written to medium : 5924 sectors at LBA 0
|
Written to medium : 6058 sectors at LBA 0
|
||||||
Writing to 'stdio:/workspaces/claude-os/release/claude-os.iso' completed successfully.
|
Writing to 'stdio:/workspaces/claude-os/release/claude-os.iso' completed successfully.
|
||||||
|
|
||||||
[100%] Built target iso
|
[100%] Built target iso
|
||||||
|
|||||||
@@ -25,6 +25,15 @@ add_executable(kernel
|
|||||||
mbr.c
|
mbr.c
|
||||||
fat32.c
|
fat32.c
|
||||||
floppy.c
|
floppy.c
|
||||||
|
ne2000.c
|
||||||
|
e3c509.c
|
||||||
|
ethernet.c
|
||||||
|
ipv4.c
|
||||||
|
arp.c
|
||||||
|
dhcp.c
|
||||||
|
udp.c
|
||||||
|
tcp.c
|
||||||
|
graphics.c
|
||||||
env.c
|
env.c
|
||||||
keyboard.c
|
keyboard.c
|
||||||
interrupts.S
|
interrupts.S
|
||||||
|
|||||||
382
src/arp.c
Normal file
382
src/arp.c
Normal 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
145
src/arp.h
Normal 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 */
|
||||||
552
src/dhcp.c
Normal file
552
src/dhcp.c
Normal 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
145
src/dhcp.h
Normal 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
409
src/e3c509.c
Normal 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
158
src/e3c509.h
Normal 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
393
src/ethernet.c
Normal file
@@ -0,0 +1,393 @@
|
|||||||
|
/**
|
||||||
|
* @file ethernet.c
|
||||||
|
* @brief Ethernet subsystem implementation.
|
||||||
|
*
|
||||||
|
* Provides a unified layer over individual Ethernet NIC drivers.
|
||||||
|
* NIC drivers register through ethernet_register(), which creates the
|
||||||
|
* `/dev/ethN` char device and adds the interface to an internal table.
|
||||||
|
*
|
||||||
|
* The subsystem also exposes network interface info via sysfs at
|
||||||
|
* `/sys/net`, allowing userspace tools to query interface status.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ethernet.h"
|
||||||
|
#include "devicefs.h"
|
||||||
|
#include "sysfs.h"
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/* Debug print helpers */
|
||||||
|
extern void offset_print(const char *str);
|
||||||
|
extern void print_hex(uint32_t val);
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* Global state
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
/** Interface table. */
|
||||||
|
static eth_iface_t ifaces[ETH_MAX_IFACES];
|
||||||
|
|
||||||
|
/** Number of registered interfaces. */
|
||||||
|
static uint32_t iface_count = 0;
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* Character device operations
|
||||||
|
*
|
||||||
|
* Maps devicefs char read/write to the NIC driver send/recv.
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read from `/dev/ethN`: receive a raw Ethernet frame.
|
||||||
|
*/
|
||||||
|
static int32_t eth_char_read(void *dev_data, uint32_t size, void *buf) {
|
||||||
|
eth_iface_t *iface = (eth_iface_t *)dev_data;
|
||||||
|
if (!iface || !iface->active || !iface->recv) return -1;
|
||||||
|
return (int32_t)iface->recv(iface->dev_data, buf, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write to `/dev/ethN`: send a raw Ethernet frame.
|
||||||
|
*/
|
||||||
|
static int32_t eth_char_write(void *dev_data, uint32_t size, const void *buf) {
|
||||||
|
eth_iface_t *iface = (eth_iface_t *)dev_data;
|
||||||
|
if (!iface || !iface->active || !iface->send) return -1;
|
||||||
|
int ret = iface->send(iface->dev_data, buf, size);
|
||||||
|
return (ret == 0) ? (int32_t)size : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Devicefs char ops shared by all ethernet interfaces. */
|
||||||
|
static devicefs_char_ops_t eth_char_ops = {
|
||||||
|
.read = eth_char_read,
|
||||||
|
.write = eth_char_write,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* Sysfs operations for /sys/net
|
||||||
|
*
|
||||||
|
* Directory layout:
|
||||||
|
* /sys/net/
|
||||||
|
* eth1/
|
||||||
|
* mac - MAC address as hex string
|
||||||
|
* ip - IPv4 address
|
||||||
|
* netmask - subnet mask
|
||||||
|
* gateway - default gateway
|
||||||
|
* link - "up" or "down"
|
||||||
|
* eth2/
|
||||||
|
* ...
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format a MAC address as "XX:XX:XX:XX:XX:XX" into buf.
|
||||||
|
*/
|
||||||
|
static int format_mac(const uint8_t *mac, char *buf, uint32_t buf_size) {
|
||||||
|
if (buf_size < 18) return -1;
|
||||||
|
static const char hex[] = "0123456789ABCDEF";
|
||||||
|
int pos = 0;
|
||||||
|
for (int i = 0; i < 6; i++) {
|
||||||
|
if (i > 0) buf[pos++] = ':';
|
||||||
|
buf[pos++] = hex[(mac[i] >> 4) & 0xF];
|
||||||
|
buf[pos++] = hex[mac[i] & 0xF];
|
||||||
|
}
|
||||||
|
buf[pos++] = '\n';
|
||||||
|
buf[pos] = '\0';
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format an IPv4 address as "A.B.C.D\n" into buf.
|
||||||
|
*/
|
||||||
|
static int format_ipv4(uint32_t ip, char *buf, uint32_t buf_size) {
|
||||||
|
if (buf_size < 16) return -1;
|
||||||
|
int pos = 0;
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
if (i > 0) buf[pos++] = '.';
|
||||||
|
uint8_t octet = (uint8_t)((ip >> (24 - i * 8)) & 0xFF);
|
||||||
|
if (octet >= 100) { buf[pos++] = (char)('0' + octet / 100); octet %= 100; buf[pos++] = (char)('0' + octet / 10); octet %= 10; }
|
||||||
|
else if (octet >= 10) { buf[pos++] = (char)('0' + octet / 10); octet %= 10; }
|
||||||
|
buf[pos++] = (char)('0' + octet);
|
||||||
|
}
|
||||||
|
buf[pos++] = '\n';
|
||||||
|
buf[pos] = '\0';
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse interface name from the path prefix.
|
||||||
|
* E.g., path "eth1/mac" → iface "eth1", subpath "mac".
|
||||||
|
* Returns the iface, or NULL if not found.
|
||||||
|
*/
|
||||||
|
static eth_iface_t *parse_iface_path(const char *path, const char **subpath) {
|
||||||
|
/* Find the first '/' */
|
||||||
|
const char *slash = NULL;
|
||||||
|
for (const char *p = path; *p; p++) {
|
||||||
|
if (*p == '/') { slash = p; break; }
|
||||||
|
}
|
||||||
|
|
||||||
|
char iface_name[16];
|
||||||
|
if (slash) {
|
||||||
|
uint32_t len = (uint32_t)(slash - path);
|
||||||
|
if (len >= sizeof(iface_name)) return NULL;
|
||||||
|
memcpy(iface_name, path, len);
|
||||||
|
iface_name[len] = '\0';
|
||||||
|
*subpath = slash + 1;
|
||||||
|
} else {
|
||||||
|
/* No slash — path is just the interface name */
|
||||||
|
uint32_t len = strlen(path);
|
||||||
|
if (len >= sizeof(iface_name)) return NULL;
|
||||||
|
memcpy(iface_name, path, len + 1);
|
||||||
|
*subpath = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return ethernet_find_iface(iface_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sysfs list callback for /sys/net.
|
||||||
|
*/
|
||||||
|
static int net_sysfs_list(void *ctx, const char *path, uint32_t idx,
|
||||||
|
sysfs_entry_t *out) {
|
||||||
|
(void)ctx;
|
||||||
|
|
||||||
|
if (path[0] == '\0') {
|
||||||
|
/* Root of /sys/net — list interfaces */
|
||||||
|
uint32_t count = 0;
|
||||||
|
for (uint32_t i = 0; i < ETH_MAX_IFACES; i++) {
|
||||||
|
if (!ifaces[i].active) continue;
|
||||||
|
if (count == idx) {
|
||||||
|
memset(out, 0, sizeof(sysfs_entry_t));
|
||||||
|
strncpy(out->name, ifaces[i].name, SYSFS_MAX_NAME - 1);
|
||||||
|
out->is_dir = 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Listing inside an interface directory */
|
||||||
|
const char *subpath;
|
||||||
|
eth_iface_t *iface = parse_iface_path(path, &subpath);
|
||||||
|
if (!iface) return -1;
|
||||||
|
|
||||||
|
/* Files: mac, ip, netmask, gateway, link */
|
||||||
|
static const char *files[] = { "mac", "ip", "netmask", "gateway", "link" };
|
||||||
|
if (idx >= 5) return -1;
|
||||||
|
|
||||||
|
memset(out, 0, sizeof(sysfs_entry_t));
|
||||||
|
strncpy(out->name, files[idx], SYSFS_MAX_NAME - 1);
|
||||||
|
out->is_dir = 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sysfs read callback for /sys/net.
|
||||||
|
*/
|
||||||
|
static int net_sysfs_read(void *ctx, const char *path, char *buf,
|
||||||
|
uint32_t buf_size) {
|
||||||
|
(void)ctx;
|
||||||
|
|
||||||
|
const char *subpath;
|
||||||
|
eth_iface_t *iface = parse_iface_path(path, &subpath);
|
||||||
|
if (!iface) return -1;
|
||||||
|
|
||||||
|
if (strcmp(subpath, "mac") == 0) {
|
||||||
|
return format_mac(iface->mac, buf, buf_size);
|
||||||
|
}
|
||||||
|
if (strcmp(subpath, "ip") == 0) {
|
||||||
|
return format_ipv4(iface->ip_addr, buf, buf_size);
|
||||||
|
}
|
||||||
|
if (strcmp(subpath, "netmask") == 0) {
|
||||||
|
return format_ipv4(iface->netmask, buf, buf_size);
|
||||||
|
}
|
||||||
|
if (strcmp(subpath, "gateway") == 0) {
|
||||||
|
return format_ipv4(iface->gateway, buf, buf_size);
|
||||||
|
}
|
||||||
|
if (strcmp(subpath, "link") == 0) {
|
||||||
|
const char *s = iface->link_up ? "up\n" : "down\n";
|
||||||
|
strncpy(buf, s, buf_size);
|
||||||
|
return (int)strlen(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sysfs write callback for /sys/net.
|
||||||
|
* Allows setting IP address, netmask, gateway.
|
||||||
|
*/
|
||||||
|
static int net_sysfs_write(void *ctx, const char *path, const char *buf,
|
||||||
|
uint32_t size) {
|
||||||
|
(void)ctx;
|
||||||
|
(void)size;
|
||||||
|
|
||||||
|
const char *subpath;
|
||||||
|
eth_iface_t *iface = parse_iface_path(path, &subpath);
|
||||||
|
if (!iface) return -1;
|
||||||
|
|
||||||
|
/* Parse dotted-decimal IP from buf */
|
||||||
|
if (strcmp(subpath, "ip") == 0 || strcmp(subpath, "netmask") == 0 ||
|
||||||
|
strcmp(subpath, "gateway") == 0) {
|
||||||
|
uint32_t octets[4] = {0};
|
||||||
|
int octet_idx = 0;
|
||||||
|
for (uint32_t i = 0; i < size && octet_idx < 4; i++) {
|
||||||
|
if (buf[i] == '.' || buf[i] == '\n' || buf[i] == '\0') {
|
||||||
|
octet_idx++;
|
||||||
|
} else if (buf[i] >= '0' && buf[i] <= '9') {
|
||||||
|
octets[octet_idx] = octets[octet_idx] * 10 + (uint32_t)(buf[i] - '0');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
uint32_t addr = (octets[0] << 24) | (octets[1] << 16) |
|
||||||
|
(octets[2] << 8) | octets[3];
|
||||||
|
|
||||||
|
if (strcmp(subpath, "ip") == 0) iface->ip_addr = addr;
|
||||||
|
if (strcmp(subpath, "netmask") == 0) iface->netmask = addr;
|
||||||
|
if (strcmp(subpath, "gateway") == 0) iface->gateway = addr;
|
||||||
|
|
||||||
|
return (int)size;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sysfs operations for /sys/net. */
|
||||||
|
static sysfs_ops_t net_sysfs_ops = {
|
||||||
|
.list = net_sysfs_list,
|
||||||
|
.read = net_sysfs_read,
|
||||||
|
.write = net_sysfs_write,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* Public API
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
void ethernet_init(void) {
|
||||||
|
memset(ifaces, 0, sizeof(ifaces));
|
||||||
|
iface_count = 0;
|
||||||
|
|
||||||
|
sysfs_register("net", &net_sysfs_ops, NULL);
|
||||||
|
offset_print(" ETHERNET: subsystem initialized\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
eth_iface_t *ethernet_register(const uint8_t *mac,
|
||||||
|
eth_send_fn send,
|
||||||
|
eth_recv_fn recv,
|
||||||
|
void *dev_data) {
|
||||||
|
if (iface_count >= ETH_MAX_IFACES) {
|
||||||
|
offset_print(" ETHERNET: too many interfaces\n");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Find free slot */
|
||||||
|
eth_iface_t *iface = NULL;
|
||||||
|
for (uint32_t i = 0; i < ETH_MAX_IFACES; i++) {
|
||||||
|
if (!ifaces[i].active) {
|
||||||
|
iface = &ifaces[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!iface) return NULL;
|
||||||
|
|
||||||
|
memset(iface, 0, sizeof(eth_iface_t));
|
||||||
|
|
||||||
|
/* Copy MAC */
|
||||||
|
memcpy(iface->mac, mac, ETH_ALEN);
|
||||||
|
iface->send = send;
|
||||||
|
iface->recv = recv;
|
||||||
|
iface->dev_data = dev_data;
|
||||||
|
iface->link_up = 1; /* Assume link up after init */
|
||||||
|
iface->active = 1;
|
||||||
|
|
||||||
|
/* Register as /dev/ethN — devicefs assigns the number */
|
||||||
|
devicefs_device_t *devfs_dev = devicefs_register_char("eth",
|
||||||
|
ð_char_ops,
|
||||||
|
iface);
|
||||||
|
if (devfs_dev) {
|
||||||
|
/* Copy the assigned name back */
|
||||||
|
strncpy(iface->name, devfs_dev->name, sizeof(iface->name) - 1);
|
||||||
|
} else {
|
||||||
|
/* Fallback name */
|
||||||
|
iface->name[0] = 'e'; iface->name[1] = 't'; iface->name[2] = 'h';
|
||||||
|
iface->name[3] = (char)('0' + iface_count + 1);
|
||||||
|
iface->name[4] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
iface_count++;
|
||||||
|
|
||||||
|
offset_print(" ETHERNET: registered ");
|
||||||
|
offset_print(iface->name);
|
||||||
|
offset_print(" MAC ");
|
||||||
|
for (int i = 0; i < 6; i++) {
|
||||||
|
if (i > 0) offset_print(":");
|
||||||
|
print_hex(mac[i]);
|
||||||
|
}
|
||||||
|
offset_print("\n");
|
||||||
|
|
||||||
|
return iface;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ethernet_send(eth_iface_t *iface, const uint8_t *dst_mac,
|
||||||
|
uint16_t ethertype, const void *payload, uint32_t len) {
|
||||||
|
if (!iface || !iface->active || !iface->send) return -1;
|
||||||
|
if (len > ETH_MTU) return -1;
|
||||||
|
|
||||||
|
/* Build complete Ethernet frame on stack */
|
||||||
|
uint8_t frame[ETH_FRAME_LEN];
|
||||||
|
eth_header_t *hdr = (eth_header_t *)frame;
|
||||||
|
|
||||||
|
memcpy(hdr->dst, dst_mac, ETH_ALEN);
|
||||||
|
memcpy(hdr->src, iface->mac, ETH_ALEN);
|
||||||
|
hdr->ethertype = htons(ethertype);
|
||||||
|
|
||||||
|
memcpy(frame + ETH_HLEN, payload, len);
|
||||||
|
|
||||||
|
uint32_t frame_len = ETH_HLEN + len;
|
||||||
|
if (frame_len < 60) frame_len = 60; /* Minimum Ethernet frame */
|
||||||
|
|
||||||
|
return iface->send(iface->dev_data, frame, frame_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
int ethernet_recv(eth_iface_t *iface, void *buf, uint32_t bufsize,
|
||||||
|
uint8_t *src_mac, uint16_t *ethertype) {
|
||||||
|
if (!iface || !iface->active || !iface->recv) return -1;
|
||||||
|
|
||||||
|
/* Receive into temporary buffer */
|
||||||
|
uint8_t frame[ETH_FRAME_LEN];
|
||||||
|
int ret = iface->recv(iface->dev_data, frame, sizeof(frame));
|
||||||
|
if (ret <= 0) return ret;
|
||||||
|
|
||||||
|
if ((uint32_t)ret < ETH_HLEN) return -1; /* Runt frame */
|
||||||
|
|
||||||
|
eth_header_t *hdr = (eth_header_t *)frame;
|
||||||
|
|
||||||
|
/* Copy out src MAC and ethertype if requested */
|
||||||
|
if (src_mac) memcpy(src_mac, hdr->src, ETH_ALEN);
|
||||||
|
if (ethertype) *ethertype = ntohs(hdr->ethertype);
|
||||||
|
|
||||||
|
/* Copy payload */
|
||||||
|
uint32_t payload_len = (uint32_t)ret - ETH_HLEN;
|
||||||
|
if (payload_len > bufsize) payload_len = bufsize;
|
||||||
|
memcpy(buf, frame + ETH_HLEN, payload_len);
|
||||||
|
|
||||||
|
return (int)payload_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
eth_iface_t *ethernet_get_iface(uint32_t index) {
|
||||||
|
uint32_t count = 0;
|
||||||
|
for (uint32_t i = 0; i < ETH_MAX_IFACES; i++) {
|
||||||
|
if (!ifaces[i].active) continue;
|
||||||
|
if (count == index) return &ifaces[i];
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t ethernet_get_iface_count(void) {
|
||||||
|
return iface_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
eth_iface_t *ethernet_find_iface(const char *name) {
|
||||||
|
for (uint32_t i = 0; i < ETH_MAX_IFACES; i++) {
|
||||||
|
if (!ifaces[i].active) continue;
|
||||||
|
if (strcmp(ifaces[i].name, name) == 0) return &ifaces[i];
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
208
src/ethernet.h
Normal file
208
src/ethernet.h
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
/**
|
||||||
|
* @file ethernet.h
|
||||||
|
* @brief Ethernet subsystem.
|
||||||
|
*
|
||||||
|
* Provides a unified abstraction over individual Ethernet NIC drivers.
|
||||||
|
* Each NIC driver registers itself with the ethernet subsystem, which
|
||||||
|
* then creates the `/dev/ethN` character device and exposes interface
|
||||||
|
* information via `/sys/net`.
|
||||||
|
*
|
||||||
|
* Higher-level protocols (IPv4, ARP, etc.) use this subsystem to send
|
||||||
|
* and receive Ethernet frames without knowing which NIC driver is in use.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef ETHERNET_H
|
||||||
|
#define ETHERNET_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/** Maximum number of Ethernet interfaces. */
|
||||||
|
#define ETH_MAX_IFACES 8
|
||||||
|
|
||||||
|
/** Ethernet MAC address length. */
|
||||||
|
#define ETH_ALEN 6
|
||||||
|
|
||||||
|
/** Ethernet header size (dst + src + ethertype). */
|
||||||
|
#define ETH_HLEN 14
|
||||||
|
|
||||||
|
/** Maximum Ethernet payload size. */
|
||||||
|
#define ETH_MTU 1500
|
||||||
|
|
||||||
|
/** Maximum Ethernet frame size (header + payload). */
|
||||||
|
#define ETH_FRAME_LEN (ETH_HLEN + ETH_MTU)
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* EtherType constants
|
||||||
|
* ================================================================ */
|
||||||
|
#define ETHERTYPE_IPV4 0x0800 /**< IPv4 */
|
||||||
|
#define ETHERTYPE_ARP 0x0806 /**< ARP */
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* Ethernet header (14 bytes)
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ethernet frame header.
|
||||||
|
*/
|
||||||
|
typedef struct __attribute__((packed)) eth_header {
|
||||||
|
uint8_t dst[ETH_ALEN]; /**< Destination MAC address. */
|
||||||
|
uint8_t src[ETH_ALEN]; /**< Source MAC address. */
|
||||||
|
uint16_t ethertype; /**< EtherType (big-endian). */
|
||||||
|
} eth_header_t;
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* NIC driver callbacks
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback to send a raw Ethernet frame (complete with header).
|
||||||
|
*
|
||||||
|
* @param dev_data Driver-specific device pointer.
|
||||||
|
* @param frame Complete Ethernet frame (header + payload).
|
||||||
|
* @param len Frame length in bytes.
|
||||||
|
* @return 0 on success, -1 on failure.
|
||||||
|
*/
|
||||||
|
typedef int (*eth_send_fn)(void *dev_data, const void *frame, uint32_t len);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback to receive a raw Ethernet frame (complete with header).
|
||||||
|
*
|
||||||
|
* @param dev_data Driver-specific device pointer.
|
||||||
|
* @param buf Buffer for received frame.
|
||||||
|
* @param bufsize Buffer size.
|
||||||
|
* @return Number of bytes received, 0 if none, -1 on error.
|
||||||
|
*/
|
||||||
|
typedef int (*eth_recv_fn)(void *dev_data, void *buf, uint32_t bufsize);
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* Ethernet interface
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents one Ethernet network interface.
|
||||||
|
*/
|
||||||
|
typedef struct eth_iface {
|
||||||
|
char name[16]; /**< Interface name (e.g., "eth1"). */
|
||||||
|
uint8_t mac[ETH_ALEN]; /**< MAC address. */
|
||||||
|
uint8_t active; /**< 1 if registered, 0 if free. */
|
||||||
|
uint8_t link_up; /**< 1 if link is up. */
|
||||||
|
uint32_t ip_addr; /**< IPv4 address (0 if not configured). */
|
||||||
|
uint32_t netmask; /**< Subnet mask (0 if not configured). */
|
||||||
|
uint32_t gateway; /**< Default gateway (0 if not configured). */
|
||||||
|
|
||||||
|
eth_send_fn send; /**< NIC driver send callback. */
|
||||||
|
eth_recv_fn recv; /**< NIC driver receive callback. */
|
||||||
|
void *dev_data; /**< Opaque pointer to NIC driver state. */
|
||||||
|
} eth_iface_t;
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* Public API
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the ethernet subsystem.
|
||||||
|
* Registers sysfs namespace "net" and prepares the interface table.
|
||||||
|
*/
|
||||||
|
void ethernet_init(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a NIC with the ethernet subsystem.
|
||||||
|
*
|
||||||
|
* Creates a `/dev/ethN` character device and adds the interface to the
|
||||||
|
* internal table. Called by NIC drivers during initialization.
|
||||||
|
*
|
||||||
|
* @param mac 6-byte MAC address.
|
||||||
|
* @param send Send callback function.
|
||||||
|
* @param recv Receive callback function.
|
||||||
|
* @param dev_data Opaque NIC device pointer (passed to callbacks).
|
||||||
|
* @return Pointer to the registered interface, or NULL on failure.
|
||||||
|
*/
|
||||||
|
eth_iface_t *ethernet_register(const uint8_t *mac,
|
||||||
|
eth_send_fn send,
|
||||||
|
eth_recv_fn recv,
|
||||||
|
void *dev_data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send an Ethernet frame through an interface.
|
||||||
|
*
|
||||||
|
* Constructs the Ethernet header (src MAC from interface, dst from caller)
|
||||||
|
* and dispatches through the NIC driver.
|
||||||
|
*
|
||||||
|
* @param iface Interface to send through.
|
||||||
|
* @param dst_mac Destination MAC address (6 bytes).
|
||||||
|
* @param ethertype EtherType (host byte order, will be converted to big-endian).
|
||||||
|
* @param payload Frame payload.
|
||||||
|
* @param len Payload length (max ETH_MTU).
|
||||||
|
* @return 0 on success, -1 on failure.
|
||||||
|
*/
|
||||||
|
int ethernet_send(eth_iface_t *iface, const uint8_t *dst_mac,
|
||||||
|
uint16_t ethertype, const void *payload, uint32_t len);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receive an Ethernet frame from an interface.
|
||||||
|
*
|
||||||
|
* @param iface Interface to receive from.
|
||||||
|
* @param buf Buffer for frame payload (Ethernet header is stripped).
|
||||||
|
* @param bufsize Buffer size.
|
||||||
|
* @param src_mac Output: source MAC of received frame (6 bytes, can be NULL).
|
||||||
|
* @param ethertype Output: EtherType of received frame (host byte order, can be NULL).
|
||||||
|
* @return Payload length in bytes, 0 if no packet, -1 on error.
|
||||||
|
*/
|
||||||
|
int ethernet_recv(eth_iface_t *iface, void *buf, uint32_t bufsize,
|
||||||
|
uint8_t *src_mac, uint16_t *ethertype);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an interface by index.
|
||||||
|
*
|
||||||
|
* @param index 0-based index.
|
||||||
|
* @return Pointer to interface, or NULL if index out of range.
|
||||||
|
*/
|
||||||
|
eth_iface_t *ethernet_get_iface(uint32_t index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the number of registered interfaces.
|
||||||
|
*
|
||||||
|
* @return Number of active Ethernet interfaces.
|
||||||
|
*/
|
||||||
|
uint32_t ethernet_get_iface_count(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find an interface by name (e.g., "eth1").
|
||||||
|
*
|
||||||
|
* @param name Interface name.
|
||||||
|
* @return Pointer to interface, or NULL if not found.
|
||||||
|
*/
|
||||||
|
eth_iface_t *ethernet_find_iface(const char *name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a 16-bit value from host to network byte order (big-endian).
|
||||||
|
*/
|
||||||
|
static inline uint16_t htons(uint16_t h) {
|
||||||
|
return (uint16_t)((h >> 8) | (h << 8));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a 16-bit value from network to host byte order.
|
||||||
|
*/
|
||||||
|
static inline uint16_t ntohs(uint16_t n) {
|
||||||
|
return htons(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a 32-bit value from host to network byte order (big-endian).
|
||||||
|
*/
|
||||||
|
static inline uint32_t htonl(uint32_t h) {
|
||||||
|
return ((h >> 24) & 0x000000FF) |
|
||||||
|
((h >> 8) & 0x0000FF00) |
|
||||||
|
((h << 8) & 0x00FF0000) |
|
||||||
|
((h << 24) & 0xFF000000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a 32-bit value from network to host byte order.
|
||||||
|
*/
|
||||||
|
static inline uint32_t ntohl(uint32_t n) {
|
||||||
|
return htonl(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* ETHERNET_H */
|
||||||
450
src/graphics.c
Normal file
450
src/graphics.c
Normal 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
173
src/graphics.h
Normal 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
279
src/ipv4.c
Normal 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
158
src/ipv4.h
Normal 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 */
|
||||||
@@ -4,6 +4,8 @@
|
|||||||
#include "syscall.h"
|
#include "syscall.h"
|
||||||
#include "keyboard.h"
|
#include "keyboard.h"
|
||||||
#include "floppy.h"
|
#include "floppy.h"
|
||||||
|
#include "ne2000.h"
|
||||||
|
#include "e3c509.h"
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
/* Forward declaration for kernel panic or similar */
|
/* Forward declaration for kernel panic or similar */
|
||||||
@@ -68,6 +70,12 @@ void isr_handler(registers_t *regs)
|
|||||||
} else if (regs->int_no == 38) {
|
} else if (regs->int_no == 38) {
|
||||||
/* Floppy IRQ */
|
/* Floppy IRQ */
|
||||||
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
24
src/kernel.c
24
src/kernel.c
@@ -22,6 +22,12 @@
|
|||||||
#include "mbr.h"
|
#include "mbr.h"
|
||||||
#include "fat32.h"
|
#include "fat32.h"
|
||||||
#include "keyboard.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"
|
#include "framebuffer.h"
|
||||||
|
|
||||||
/* Global framebuffer info, parsed from multiboot2 tags. */
|
/* Global framebuffer info, parsed from multiboot2 tags. */
|
||||||
@@ -415,6 +421,24 @@ void kernel_main(uint32_t magic, uint32_t addr) {
|
|||||||
fb_info.pitch = 80 * 2;
|
fb_info.pitch = 80 * 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ethernet_init();
|
||||||
|
offset_print("Ethernet subsystem initialized\n");
|
||||||
|
|
||||||
|
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();
|
init_drivers();
|
||||||
EARLY_PRINT("DRV ");
|
EARLY_PRINT("DRV ");
|
||||||
offset_print("Drivers initialized\n");
|
offset_print("Drivers initialized\n");
|
||||||
|
|||||||
541
src/ne2000.c
Normal file
541
src/ne2000.c
Normal 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
221
src/ne2000.h
Normal 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 */
|
||||||
217
src/syscall.c
217
src/syscall.c
@@ -17,6 +17,9 @@
|
|||||||
#include "cpio.h"
|
#include "cpio.h"
|
||||||
#include "paging.h"
|
#include "paging.h"
|
||||||
#include "pmm.h"
|
#include "pmm.h"
|
||||||
|
#include "tcp.h"
|
||||||
|
#include "udp.h"
|
||||||
|
#include "graphics.h"
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
@@ -339,22 +342,210 @@ static int32_t sys_readdir(registers_t *regs) {
|
|||||||
return (int32_t)entry.type;
|
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. */
|
/** System call dispatch table. */
|
||||||
typedef int32_t (*syscall_fn)(registers_t *);
|
typedef int32_t (*syscall_fn)(registers_t *);
|
||||||
static syscall_fn syscall_table[NUM_SYSCALLS] = {
|
static syscall_fn syscall_table[NUM_SYSCALLS] = {
|
||||||
[SYS_EXIT] = sys_exit,
|
[SYS_EXIT] = sys_exit,
|
||||||
[SYS_WRITE] = sys_write,
|
[SYS_WRITE] = sys_write,
|
||||||
[SYS_READ] = sys_read,
|
[SYS_READ] = sys_read,
|
||||||
[SYS_FORK] = sys_fork,
|
[SYS_FORK] = sys_fork,
|
||||||
[SYS_GETPID] = sys_getpid,
|
[SYS_GETPID] = sys_getpid,
|
||||||
[SYS_YIELD] = sys_yield,
|
[SYS_YIELD] = sys_yield,
|
||||||
[SYS_WAITPID] = sys_waitpid,
|
[SYS_WAITPID] = sys_waitpid,
|
||||||
[SYS_EXEC] = sys_exec,
|
[SYS_EXEC] = sys_exec,
|
||||||
[SYS_GETENV] = sys_getenv,
|
[SYS_GETENV] = sys_getenv,
|
||||||
[SYS_SETENV] = sys_setenv,
|
[SYS_SETENV] = sys_setenv,
|
||||||
[SYS_READDIR] = sys_readdir,
|
[SYS_READDIR] = sys_readdir,
|
||||||
[SYS_OPEN] = sys_open,
|
[SYS_OPEN] = sys_open,
|
||||||
[SYS_CLOSE] = sys_close,
|
[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) {
|
void syscall_handler(registers_t *regs) {
|
||||||
|
|||||||
@@ -27,9 +27,15 @@
|
|||||||
#define SYS_READDIR 10 /**< Read directory entry. path=EBX, idx=ECX, buf=EDX. Returns type or -1. */
|
#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_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_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. */
|
/** Total number of system calls. */
|
||||||
#define NUM_SYSCALLS 13
|
#define NUM_SYSCALLS 19
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize the system call handler.
|
* Initialize the system call handler.
|
||||||
|
|||||||
512
src/tcp.c
Normal file
512
src/tcp.c
Normal 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
169
src/tcp.h
Normal 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
180
src/udp.c
Normal 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
128
src/udp.h
Normal 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 */
|
||||||
Reference in New Issue
Block a user