From 366234543419c0ac1ae643233ff72ddaee72e6ee Mon Sep 17 00:00:00 2001 From: xyou Date: Fri, 3 Jul 2026 12:59:59 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20WiFi=E9=A9=B1=E5=8A=A8(ath9k)=20+=20TTF?= =?UTF-8?q?=E5=AD=97=E4=BD=93=20+=20shell=E5=91=BD=E4=BB=A4=E8=A1=8C=20+?= =?UTF-8?q?=20FirstInit=20WiFi=E8=B7=B3=E8=BF=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增: ath9k PCIe WiFi驱动(被动扫描/beacon解析), shell命令行(help/clear/echo/version/date/reboot/halt), Consolas 18px TTF字体, render_ascii.py 修改: dkm_net_api扩展scan接口, FirstInit无WiFi设备时跳过, DSK加载shell.elf, 非首次启动直接进shell --- CODE/DKM/ath9k/ath9k.c | 911 ++++++++++ CODE/DKM/e1000/e1000.c | 111 +- CODE/DKM/virtio_net/virtio_net.c | 20 + CODE/UTSM/include/utsm/net.h | 35 + CODE/UTSM/kernel/net.c | 38 +- CODE/UTSM/kernel/net.o | Bin 3384 -> 4072 bytes CODE/dsk/main.c | 94 +- CODE/dsk/main.o | Bin 13032 -> 13704 bytes CODE/firstInit/ascii_bitmaps.c | 1908 +++++++++++++++++++++ CODE/firstInit/main.c | 487 ++++-- CODE/firstInit/main.o | Bin 130640 -> 144896 bytes CODE/firstInit/text_bitmaps.o | Bin 744 -> 0 bytes CODE/font/render_ascii.py | 54 + CODE/netman/main.c | 254 ++- CODE/netman/main.o | Bin 16856 -> 24680 bytes CODE/shell/MAKEFILE | 25 + CODE/shell/linker.ld | 16 + CODE/shell/main.c | 631 +++++++ CODE/shell/main.o | Bin 0 -> 198200 bytes LESSONS_LEARNED.md | 64 + SYSTEM/boot/limine.conf | 2 + SYSTEM/boot/utsm.elf | Bin 17265776 -> 17265952 bytes SYSTEM/driver/manifest.json | 8 + SYSTEM/driver/net/ath9k.drv | Bin 0 -> 14512 bytes SYSTEM/driver/net/e1000.drv | Bin 7944 -> 11488 bytes SYSTEM/driver/net/virtio_net.drv | Bin 12304 -> 12384 bytes SYSTEM/system/deshab64/deshab.elf | Bin 743840 -> 743840 bytes SYSTEM/system/deshab64/network/netman.elf | Bin 166088 -> 170344 bytes SYSTEM/system/deshab64/shell.elf | Bin 0 -> 180160 bytes SYSTEM/system/user/use/FirstInit.elf | Bin 133464 -> 146256 bytes build.ps1 | 19 + 31 files changed, 4437 insertions(+), 240 deletions(-) create mode 100644 CODE/DKM/ath9k/ath9k.c create mode 100644 CODE/firstInit/ascii_bitmaps.c delete mode 100644 CODE/firstInit/text_bitmaps.o create mode 100644 CODE/font/render_ascii.py create mode 100644 CODE/shell/MAKEFILE create mode 100644 CODE/shell/linker.ld create mode 100644 CODE/shell/main.c create mode 100644 CODE/shell/main.o create mode 100644 SYSTEM/driver/net/ath9k.drv create mode 100644 SYSTEM/system/deshab64/shell.elf diff --git a/CODE/DKM/ath9k/ath9k.c b/CODE/DKM/ath9k/ath9k.c new file mode 100644 index 0000000..05fd8e2 --- /dev/null +++ b/CODE/DKM/ath9k/ath9k.c @@ -0,0 +1,911 @@ +/* DKM ath9k Driver — Atheros AR928x 802.11abgn WiFi + * Stage 3, optional, depends on "pci" and "irq", provides "netdev". + * + * 实现范围: + * - PCI 探测(vendor 0x168c,AR9280/9285/9287 等常见 PCIe chip) + * - BAR0 MMIO 映射(HHDM 模式,与 e1000 相同) + * - 软件复位 + RTC 唤醒序列 + * - EEPROM 读取(4KB ATF EEPROM,读 MAC 地址 + 校准数据基址) + * - 简化硬件初始化(ath9k_hw_init 关键寄存器序列) + * - RX 描述符环 + beacon 接收 + * - 信道切换 + 被动扫描状态机 + * - 802.11 beacon IE 解析(SSID/RSSI/DS channel/RSN/RSN capabilities) + * - netdev 注册 + 扫描结果查询回调(通过 kernel_api.net.scan_*) + * + * 注意:QEMU 不支持 WiFi 网卡模拟,本驱动只能在真实硬件测试。 + * 完整 ath9k HAL 寄存器初始化序列非常庞大,本驱动保留核心扫描路径, + * 部分高级特性(ANI/MRR/AGC 调优)留待硬件验证后增量完善。 + */ + +#include + +#define DKM_DRIVER_MAGIC 0x444B4D31u +#define DKM_ABI_VERSION 1u + +typedef unsigned char u8; +typedef unsigned short u16; +typedef unsigned int u32; +typedef unsigned long long u64; +typedef long long i64; +typedef signed char i8; +#define NULL ((void *)0) + +/* ---- DKM ABI ---- */ +struct dkm_log_api { + void (*info)(const char *msg); + void (*warn)(const char *msg); + void (*error)(const char *msg); + void (*panic)(const char *msg); +}; + +struct dkm_net_scan_result { + char ssid[33]; + u8 bssid[6]; + u8 channel; + i8 rssi; + u8 security; +}; + +struct dkm_net_device_desc { + const char *name; + u8 mac[6]; + u32 flags; + void *ctx; + int (*tx)(void *ctx, const void *packet, u32 length); + int (*rx_poll)(void *ctx, void *buffer, u32 capacity, u32 *out_length); + int (*scan_start)(void *ctx); + int (*scan_count)(void *ctx); + int (*scan_result)(void *ctx, u32 n, struct dkm_net_scan_result *out); + int (*is_wireless)(void *ctx); +}; + +struct dkm_net_api { + int (*register_device)(const struct dkm_net_device_desc *desc); + u32 (*device_count)(void); + int (*device_info)(u32 index, void *out); + int (*tx)(u32 index, const void *packet, u32 length); + int (*rx_poll)(u32 index, void *buffer, u32 capacity, u32 *out_length); + int (*scan_start)(u32 index); + int (*scan_count)(u32 index); + int (*scan_result)(u32 index, u32 n, struct dkm_net_scan_result *out); + int (*is_wireless)(u32 index); +}; + +struct dkm_dma_buffer { + void *virt; + u64 phys; + u64 size; +}; + +struct dkm_dma_api { + int (*alloc_pages)(u64 page_count, u64 alignment, u64 max_phys, struct dkm_dma_buffer *out); +}; + +#define DKM_NET_F_LINK_UP (1u << 0) +#define DKM_NET_F_TX_READY (1u << 1) +#define DKM_NET_F_RX_READY (1u << 2) +#define DKM_NET_F_WIRELESS (1u << 3) + +#define DKM_NET_SEC_OPEN 0u +#define DKM_NET_SEC_WEP 1u +#define DKM_NET_SEC_WPA 2u +#define DKM_NET_SEC_WPA2 3u +#define DKM_NET_SEC_WPA3 4u + +struct dkm_kernel_api { + u32 version; u32 size; u64 feature_bits; + const struct dkm_log_api *log; + const void *mem,*utsm,*irq_api,*pci_api; + const struct dkm_dma_api *dma; + const void *vfs_api; + const struct dkm_net_api *net; + const void *timer,*drr; + const void *rsdp_address,*fb_address; + u64 fb_width,fb_height,fb_pitch; u16 fb_bpp; + const void *boot_modules_response; + int (*irq_register)(u8 irq, void *handler); + u64 hhdm_offset; +}; + +struct dkm_driver_handle; +struct dkm_driver_desc { + u32 magic; u16 abi_version; u16 desc_size; + const char *name,*version,*vendor; + u32 driver_class,stage,flags,priority; + const char *const *depends; u32 depends_count; + const char *const *provides; u32 provides_count; + u64 min_kernel_abi,feature_bits,reserved0,reserved1; +}; + +static const char *const g_depends[] = {"pci","irq"}; +static const char *const g_provides[] = {"netdev"}; +__attribute__((visibility("default"), used)) +const struct dkm_driver_desc driver_desc = { + .magic=DKM_DRIVER_MAGIC,.abi_version=DKM_ABI_VERSION, + .desc_size=sizeof(struct dkm_driver_desc), + .name="ath9k",.version="0.1.0",.vendor="Deshab", + .driver_class=9,.stage=3,.flags=0,.priority=0, + .depends=g_depends,.depends_count=2, + .provides=g_provides,.provides_count=1,.min_kernel_abi=1, +}; + +/* ---- PCI config ---- */ +#define PCI_ADDR 0xCF8 +#define PCI_DATA 0xCFC +#define PCI_VENDOR_ID 0x00 +#define PCI_COMMAND 0x04 +#define PCI_BAR0 0x10 +#define PCI_BAR1 0x14 +#define PCI_IRQ_LINE 0x3C + +#define PCI_CMD_IO (1u << 0) +#define PCI_CMD_MEM (1u << 1) +#define PCI_CMD_BUSMST (1u << 2) + +/* ---- ath9k 寄存器(AR9280/9285/9287 PCIe MAC,Linux ath9k reg.h 关键子集) ---- */ +#define AR_RC 0x4000u +#define AR_RC_MAC 0x00000001u +#define AR_RC_BB 0x00000002u +#define AR_RC_RPCU 0x00000004u +#define AR_RC_RDMA 0x00000008u + +#define AR_RTC_RESET 0x0040u +#define AR_RTC_FORCE_WAKE 0x0044u +#define AR_RTC_FORCE_WAKE_ON 0x00000001u +#define AR_RTC_FORCE_WAKE_INT 0x00000002u +#define AR_RTC_STATUS 0x0048u +#define AR_RTC_STATUS_SHUTDOWN 0x00000001u +#define AR_RTC_STATUS_ON 0x00000002u +#define AR_RTC_SLEEP_CLK 0x004Cu + +#define AR_EEPROM_CMD 0x4008u +#define AR_EEPROM_STS 0x400Cu +#define AR_EEPROM_DATA 0x4010u +#define AR_EEPROM_CMD_READ 0x00000001u +#define AR_EEPROM_CMD_RESET 0x00000004u +#define AR_EEPROM_STS_RDBUSY 0x00010000u +#define AR_EEPROM_STS_DATA_M 0x0000FFFFu +#define AR_EEPROM_STS_VALID 0x00000100u + +/* MAC address registers */ +#define AR_STA_ID0 0x0800u +#define AR_STA_ID1 0x0804u +#define AR_BSSMSKL 0x0808u +#define AR_BSSMSKH 0x080Cu + +/* RX DMA */ +#define AR_RXDP 0x0000u +#define AR_RXCFG 0x0004u +#define AR_RXCFG_ZLFDMA 0x08000000u +#define AR_MIBC 0x0044u +#define AR_CR 0x0008u +#define AR_CR_RXE 0x00000004u +#define AR_CR_RXD 0x00000001u +#define AR_RXNOPTR 0x0064u + +/* Interrupts */ +#define AR_IMR 0x0080u +#define AR_ISR 0x0084u +#define AR_ISR_RAC 0x00C0u +#define AR_ISR_RXOK 0x00000001u +#define AR_ISR_RXERR 0x00000002u +#define AR_ISR_RXORN 0x00000020u +#define AR_ISR_RXKINT 0x00000100u +#define AR_IMR_RXOK AR_ISR_RXOK +#define AR_IMR_RXERR AR_ISR_RXERR +#define AR_IMR_RXORN AR_ISR_RXORN +#define AR_IMR_RXKINT AR_ISR_RXKINT + +/* Channel registers (PHY) */ +#define AR_PHY_BASE 0x9800u +#define AR_PHY(_n) (AR_PHY_BASE + ((_n) << 2)) +#define AR_PHY_CCK_DETECT AR_PHY(0x0A14) +#define AR_PHY_SYNTH_CONTROL AR_PHY(0x9874) +#define AR_PHY_RFBUS_REQ AR_PHY(0x987C) +#define AR_PHY_RFBUS_GRANT AR_PHY(0x987C) + +#define AR_PHY_PLL 0x9914u +#define AR_PHY_PLL_CONTROL AR_PHY(0x9914) + +/*---- RX 描述符 (ath9k RX status descriptor, 8 words = 32 bytes) ---- */ +struct ath9k_rx_desc { + u32 ds_link; /* next descriptor phys (32-bit) */ + u32 ds_data; /* data buffer phys (32-bit) */ + u32 ds_ctl0; /* buffer len + control */ + u32 ds_ctl1; /* frame type */ + u32 ds_status0; /* RX status 0 */ + u32 ds_status1; /* RX status 1 */ + u32 ds_status2; /* RX status 2 (RSSI) */ + u32 ds_status3; /* RX status 3 */ +} __attribute__((packed)); + +#define ATH9K_RXCTL_BUF_LEN_S 0 +#define ATH9K_RXCTL_BUF_LEN_M 0x00000FFFu +#define ATH9K_RXCTL_INTREQ 0x00020000u + +#define ATH9K_RXSTATUS_DONE 0x00000001u +#define ATH9K_RXSTATUS_OK 0x00000002u +#define ATH9K_RXSTATUS_CRC 0x00000004u +#define ATH9K_RXSTATUS_DECRYPT_BUSY 0x00000008u +#define ATH9K_RXSTATUS_FRAME_LEN_S 0 +#define ATH9K_RXSTATUS_FRAME_LEN_M 0x0000FFF8u + +#define ATH9K_RX_BUF_SIZE 4096u +#define ATH9K_RX_DESC_COUNT 16u + +/* ---- 802.11 帧解析 ---- */ +/* frame_control: type=2 bits, subtype=4 bits */ +#define IEEE80211_FTYPE_MGMT 0x0000u +#define IEEE80211_STYPE_BEACON 0x0008u +#define IEEE80211_STYPE_PROBE_RESP 0x0005u + +/* Information Element IDs */ +#define WLAN_EID_SSID 0 +#define WLAN_EID_DS_PARAMS 3 +#define WLAN_EID_RSN 48 +#define WLAN_EID_VENDOR 221 + +#define ATH9K_MAX_SCAN_RESULTS 32u +#define ATH9K_SSID_MAX 32u + +struct ath9k_scan_result { + char ssid[ATH9K_SSID_MAX + 1]; + u8 bssid[6]; + u8 channel; + i8 rssi; + u8 security; +}; + +/* 2.4GHz 信道表 (channel -> freq MHz) */ +__attribute__((used)) +static const u16 g_chan2g[] = { + 2412, 2417, 2422, 2427, 2432, 2437, 2442, 2447, + 2452, 2457, 2462, 2467, 2472 +}; +#define ATH9K_CHAN_2G_COUNT (sizeof(g_chan2g) / sizeof(g_chan2g[0])) + +/* ---- 全局状态 ---- */ +static const struct dkm_log_api *g_log; +static volatile u8 *g_mmio; +static u64 g_bar0_phys; +static u8 g_mac[6]; +static u16 g_pci_device_id; +static u8 g_pci_irq; + +static struct dkm_dma_buffer g_rx_desc_dma; +static struct ath9k_rx_desc *g_rx_desc; +static struct dkm_dma_buffer g_rx_buf_dma[ATH9K_RX_DESC_COUNT]; +static u8 *g_rx_buf[ATH9K_RX_DESC_COUNT]; +static u32 g_rx_head; +static int g_rx_ready; + +/* 扫描状态 */ +static struct ath9k_scan_result g_scan_results[ATH9K_MAX_SCAN_RESULTS]; +static u32 g_scan_count; +static int g_scanning; + +/* ---- 串口日志(参考 e1000 raw_log) ---- */ +static __inline__ void outb(u16 p, u8 v) { __asm__ volatile("outb %0,%1"::"a"(v),"Nd"(p):"memory"); } +static __inline__ u8 inb(u16 p) { u8 v; __asm__ volatile("inb %1,%0":"=a"(v):"Nd"(p):"memory"); return v; } +static __inline__ void outl(u16 p, u32 v) { __asm__ volatile("outl %0,%1"::"a"(v),"Nd"(p):"memory"); } +static __inline__ u32 inl(u16 p) { u32 v; __asm__ volatile("inl %1,%0":"=a"(v):"Nd"(p):"memory"); return v; } + +static void raw_log(const char *s) { + while (*s) { + char c = *s++; + if (c == '\n') { + for (u32 i=0;i<100000;i++) if (inb(0x3F8+5)&0x20) break; + outb(0x3F8, '\r'); + } + for (u32 i=0;i<100000;i++) if (inb(0x3F8+5)&0x20) break; + outb(0x3F8, (u8)c); + } +} + +static void raw_hex(const char *prefix, u64 v) { + static const char h[] = "0123456789abcdef"; + raw_log(prefix); + raw_log("0x"); + for (int i=15; i>=0; i--) { + for (u32 wait=0; wait<100000; wait++) if (inb(0x3F8+5)&0x20) break; + outb(0x3F8, (u8)h[(v >> (i * 4)) & 0xf]); + } + raw_log("\n"); +} + +static void delay_us(u32 us) { + /* 粗略延时:每个 iteration ~1us on QEMU x86 */ + for (u32 i = 0; i < us * 10; i++) __asm__ volatile("pause"); +} + +/* ---- PCI 配置空间 ---- */ +static u32 pci_read(u8 bus, u8 dev, u8 func, u8 reg) { + u32 a = (1u<<31)|((u32)bus<<16)|((u32)dev<<11)|((u32)func<<8)|((u32)reg&0xFC); + outl(PCI_ADDR,a); return inl(PCI_DATA); +} + +static void pci_write(u8 bus, u8 dev, u8 func, u8 reg, u32 v) { + u32 a = (1u<<31)|((u32)bus<<16)|((u32)dev<<11)|((u32)func<<8)|((u32)reg&0xFC); + outl(PCI_ADDR,a); outl(PCI_DATA,v); +} + +/* ath9k PCI 设备 ID 表(vendor 0x168c Atheros) */ +static int ath9k_find(u8 *bus, u8 *dev) { + for (u8 d=0; d<32; d++) { + u32 vd = pci_read(0,d,0,PCI_VENDOR_ID); + if ((vd&0xffff)==0xffff) continue; + if ((vd&0xffff)!=0x168c) continue; + u16 did = (u16)(vd>>16); + /* AR9280/9285/9287/9380/9580/9565 等常见 PCIe chip */ + if (did==0x002A || did==0x002B || did==0x002C || did==0x002D || + did==0x002E || did==0x0030 || did==0x0032 || did==0x0033 || + did==0x0034 || did==0x0036 || did==0x0037 || did==0xAB34 || + did==0x0029 || did==0x1A56 || did==0x2A37 || did==0x003C || + did==0x003D || did==0x003E || did==0x0042 || did==0x0043 || + did==0x0044 || did==0x0046 || did==0x0047 || did==0x0051 || + did==0x0052 || did==0x0053 || did==0x0059 || did==0x005A || + did==0x005B || did==0x005C || did==0x0061 || did==0x0063 || + did==0x0065 || did==0x0066 || did==0x0067 || did==0x0068 || + did==0x0069 || did==0x006A || did==0x006B || did==0x006C || + did==0x006E || did==0x006F || did==0x0070 || did==0x0071 || + did==0x0072 || did==0x0073 || did==0x0074 || did==0x0075 || + did==0x0076 || did==0x0077 || did==0x0078 || did==0x0079 || + did==0x007A || did==0x0080 || did==0x0081 || did==0x0082 || + did==0x0083 || did==0x0085 || did==0x0086 || did==0x0087 || + did==0x0088 || did==0x0089 || did==0x008A || did==0x008B || + did==0x008C || did==0x008D || did==0x008E || did==0x008F || + did==0x0090 || did==0x0091 || did==0x0092 || did==0x0093 || + did==0x0094 || did==0x0095 || did==0x0096 || did==0x0097 || + did==0x0098 || did==0x0099 || did==0x009A || did==0x009B || + did==0x009C || did==0x009D || did==0x009E || did==0x009F || + did==0x00A0 || did==0x00A1 || did==0x00A2 || did==0x00A3 || + did==0x00A4 || did==0x00A5 || did==0x00A6 || did==0x00A7 || + did==0x00A8 || did==0x00A9 || did==0x00AA || did==0x00AB || + did==0x00AC || did==0x00AD || did==0x00AE || did==0x00AF) { + *bus=0; *dev=d; return 0; + } + /* 兜底:所有 Atheros 设备都接受(驱动加载后会校验 chip ID) */ + *bus=0; *dev=d; + raw_log("[ath9k.raw] found Atheros device, did=0x"); + static const char hh[] = "0123456789abcdef"; + char hex[5]; + hex[0]=hh[(did>>12)&0xf]; hex[1]=hh[(did>>8)&0xf]; + hex[2]=hh[(did>>4)&0xf]; hex[3]=hh[did&0xf]; hex[4]=0; + raw_log(hex); raw_log("\n"); + return 0; + } + return -1; +} + +/* ---- MMIO 寄存器读写 ---- */ +static u32 ath9k_reg_read(u32 reg) { + if (!g_mmio) return 0; + u32 v = *(volatile u32 *)(g_mmio + reg); + __asm__ volatile("" ::: "memory"); + return v; +} + +static void ath9k_reg_write(u32 reg, u32 v) { + if (!g_mmio) return; + *(volatile u32 *)(g_mmio + reg) = v; + __asm__ volatile("" ::: "memory"); +} + +static void ath9k_reg_rmw(u32 reg, u32 set, u32 clr) { + u32 v = ath9k_reg_read(reg); + v &= ~clr; + v |= set; + ath9k_reg_write(reg, v); +} + +/* ---- EEPROM 读取(AR5416/AR9280 I2C-style EEPROM 接口) ---- */ +static u16 ath9k_eeprom_read(u16 addr) { + /* AR5416_EEPROM_CMD: bit0=READ, bit1=BUSY, bit2=RESET */ + /* 启动 read:写 cmd=READ | addr<<3 */ + ath9k_reg_write(AR_EEPROM_CMD, AR_EEPROM_CMD_RESET); + delay_us(5); + u32 cmd = AR_EEPROM_CMD_READ | ((u32)(addr & 0x3FF) << 3); + ath9k_reg_write(AR_EEPROM_CMD, cmd); + /* 等待 RDBUSY 清除 */ + for (u32 i = 0; i < 100000; i++) { + u32 sts = ath9k_reg_read(AR_EEPROM_STS); + if (!(sts & AR_EEPROM_STS_RDBUSY)) { + if (sts & AR_EEPROM_STS_VALID) { + return (u16)(sts & AR_EEPROM_STS_DATA_M); + } + return 0xffff; + } + delay_us(1); + } + return 0xffff; +} + +/* ---- 软件复位 + RTC 唤醒 ---- */ +static int ath9k_reset(void) { + raw_log("[ath9k.raw] reset sequence\n"); + /* 1. 强制唤醒(Power-On) */ + ath9k_reg_write(AR_RTC_FORCE_WAKE, AR_RTC_FORCE_WAKE_ON | AR_RTC_FORCE_WAKE_INT); + delay_us(50); + /* 2. 等待 RTC 进入 ON 状态 */ + for (u32 i = 0; i < 100000; i++) { + u32 s = ath9k_reg_read(AR_RTC_STATUS); + if ((s & 0x3) == AR_RTC_STATUS_ON) break; + delay_us(1); + } + /* 3. 全 chip 复位 */ + ath9k_reg_write(AR_RC, AR_RC_MAC | AR_RC_BB | AR_RC_RPCU); + delay_us(100); + ath9k_reg_write(AR_RC, 0); + delay_us(100); + /* 4. 重新唤醒 */ + ath9k_reg_write(AR_RTC_FORCE_WAKE, AR_RTC_FORCE_WAKE_ON | AR_RTC_FORCE_WAKE_INT); + delay_us(50); + /* 5. 等待 RTC ON */ + u32 ok = 0; + for (u32 i = 0; i < 100000; i++) { + u32 s = ath9k_reg_read(AR_RTC_STATUS); + if ((s & 0x3) == AR_RTC_STATUS_ON) { ok = 1; break; } + delay_us(1); + } + if (!ok) { raw_log("[ath9k.raw] reset: RTC not ON\n"); return -1; } + raw_log("[ath9k.raw] reset: RTC ON\n"); + return 0; +} + +/* ---- 简化硬件初始化(ath9k_hw_init 关键序列) ---- + * 完整 ath9k HAL 涉及 PLL/ADCDCO/AGC/ANI/Noise Floor 等数百寄存器魔法值, + * 本驱动保留最小可加载序列:基础时钟 + MAC 寄存器清零 + RX 使能准备。 + * 真实扫描路径在硬件验证后增量完善。 + */ +static int ath9k_hw_init(void) { + raw_log("[ath9k.raw] hw_init minimal\n"); + /* 关闭 RX/DMA,清 STA_ID1 */ + ath9k_reg_write(AR_CR, AR_CR_RXD); + delay_us(10); + ath9k_reg_write(AR_STA_ID1, 0); + /* 清中断 */ + ath9k_reg_write(AR_ISR, 0xFFFFFFFFu); + ath9k_reg_read(AR_ISR_RAC); + /* 写 MAC 地址到 STA_ID0/1(驱动本地缓存) */ + u32 mac_lo = (u32)g_mac[0] | ((u32)g_mac[1] << 8) | + ((u32)g_mac[2] << 16) | ((u32)g_mac[3] << 24); + u32 mac_hi = (u32)g_mac[4] | ((u32)g_mac[5] << 8); + ath9k_reg_write(AR_STA_ID0, mac_lo); + ath9k_reg_rmw(AR_STA_ID1, (mac_hi & 0xFFFF), 0xFFFFu); + raw_log("[ath9k.raw] hw_init: MAC written\n"); + return 0; +} + +/* ---- RX 环初始化 ---- */ +static int ath9k_rx_init(const struct dkm_dma_api *dma) { + if (!dma || !dma->alloc_pages || !g_mmio) return -1; + + /* 分配 RX 描述符环(1页 = 4096 字节,足够 16 个 32B 描述符) */ + if (dma->alloc_pages(1, 16, 0x100000000ULL, &g_rx_desc_dma) != 0) { + raw_log("[ath9k.raw] rx_init: desc alloc failed\n"); + return -2; + } + g_rx_desc = (struct ath9k_rx_desc *)g_rx_desc_dma.virt; + /* 清零描述符 */ + for (u32 i = 0; i < sizeof(struct ath9k_rx_desc) * ATH9K_RX_DESC_COUNT; i++) { + ((u8 *)g_rx_desc)[i] = 0; + } + + /* 分配每个 RX 缓冲区 */ + for (u32 i = 0; i < ATH9K_RX_DESC_COUNT; i++) { + if (dma->alloc_pages(1, 16, 0x100000000ULL, &g_rx_buf_dma[i]) != 0) { + raw_log("[ath9k.raw] rx_init: buf alloc failed\n"); + return -3; + } + g_rx_buf[i] = (u8 *)g_rx_buf_dma[i].virt; + /* 填描述符:link 指向下一个,data 指向缓冲区 */ + g_rx_desc[i].ds_link = (u32)(g_rx_desc_dma.phys + + (((i + 1) % ATH9K_RX_DESC_COUNT) * sizeof(struct ath9k_rx_desc))); + g_rx_desc[i].ds_data = (u32)g_rx_buf_dma[i].phys; + g_rx_desc[i].ds_ctl0 = ATH9K_RX_BUF_SIZE & ATH9K_RXCTL_BUF_LEN_M; + g_rx_desc[i].ds_ctl1 = ATH9K_RXCTL_INTREQ; + g_rx_desc[i].ds_status0 = 0; + g_rx_desc[i].ds_status1 = 0; + g_rx_desc[i].ds_status2 = 0; + g_rx_desc[i].ds_status3 = 0; + } + __asm__ volatile("" ::: "memory"); + raw_log("[ath9k.raw] rx_init: rings allocated\n"); + + /* 写 RXDP 指向第一个描述符,启用 RX */ + ath9k_reg_write(AR_RXDP, (u32)g_rx_desc_dma.phys); + ath9k_reg_write(AR_RXCFG, 0); /* 默认 RX 配置 */ + ath9k_reg_write(AR_MIBC, 0); /* 关闭 MIB 计数器,避免中断风暴 */ + + /* 使能 RXOK/RXERR 中断 */ + ath9k_reg_write(AR_IMR, AR_IMR_RXOK | AR_IMR_RXERR | AR_IMR_RXORN); + + /* 启动 RX DMA */ + ath9k_reg_write(AR_CR, AR_CR_RXE); + g_rx_head = 0; + g_rx_ready = 1; + raw_log("[ath9k.raw] rx_init: RX enabled\n"); + return 0; +} + +/* ---- 802.11 beacon IE 解析 ---- + * 从 beacon/probe-response 帧中提取 SSID、信道、加密类型。 + * 返回 0 = 解析成功,-1 = 非 beacon,-2 = 太短 + */ +static int parse_beacon(const u8 *frame, u32 len, struct ath9k_scan_result *out) { + if (len < 24 + 12) return -2; + /* frame_control(2) + duration(2) + DA(6) + SA(6) + BSSID(6) + seq(2) = 24 */ + u16 fctl = (u16)frame[0] | ((u16)frame[1] << 8); + u16 ftype = fctl & 0x000Cu; + u16 fsubtype = fctl & 0x00F0u; + if (ftype != IEEE80211_FTYPE_MGMT) return -1; + if (fsubtype != IEEE80211_STYPE_BEACON && fsubtype != IEEE80211_STYPE_PROBE_RESP) return -1; + + /* BSSID 在 offset 16 */ + for (int i = 0; i < 6; i++) out->bssid[i] = frame[16 + i]; + + /* fixed: timestamp(8) + beacon_interval(2) + capability(2) = 12 bytes */ + u32 ie_off = 24 + 12; + /* 默认值 */ + out->ssid[0] = 0; + out->channel = 0; + out->security = DKM_NET_SEC_OPEN; + + /* 遍历 IE */ + while (ie_off + 2 <= len) { + u8 eid = frame[ie_off]; + u8 elen = frame[ie_off + 1]; + if (ie_off + 2 + elen > len) break; + const u8 *edata = frame + ie_off + 2; + if (eid == WLAN_EID_SSID && elen > 0 && elen <= ATH9K_SSID_MAX) { + for (u8 i = 0; i < elen; i++) out->ssid[i] = (char)edata[i]; + out->ssid[elen] = 0; + } else if (eid == WLAN_EID_SSID && elen == 0) { + out->ssid[0] = 0; /* hidden SSID */ + } else if (eid == WLAN_EID_DS_PARAMS && elen >= 1) { + out->channel = edata[0]; + } else if (eid == WLAN_EID_RSN && elen >= 2) { + /* RSN IE → WPA2 (或 WPA3 由 version 决定) */ + u16 version = (u16)edata[0] | ((u16)edata[1] << 8); + if (version == 1) out->security = DKM_NET_SEC_WPA2; + else if (version == 2) out->security = DKM_NET_SEC_WPA3; + else out->security = DKM_NET_SEC_WPA2; + } else if (eid == WLAN_EID_VENDOR && elen >= 4) { + /* Microsoft WPA IE: OUI 00:50:F2 + type 1 */ + if (edata[0]==0x00 && edata[1]==0x50 && edata[2]==0xF2 && edata[3]==0x01) { + if (out->security < DKM_NET_SEC_WPA) out->security = DKM_NET_SEC_WPA; + } + } + ie_off += 2 + elen; + } + return 0; +} + +/* 查找已扫描 BSSID 是否已记录,返回索引或 -1 */ +static int find_bssid(const u8 bssid[6]) { + for (u32 i = 0; i < g_scan_count; i++) { + int match = 1; + for (int j = 0; j < 6; j++) if (g_scan_results[i].bssid[j] != bssid[j]) { match = 0; break; } + if (match) return (int)i; + } + return -1; +} + +/* 处理一个接收到的 802.11 帧(从 RX 缓冲区解析) */ +static void ath9k_handle_rx_frame(const u8 *buf, u32 len, i8 rssi) { + struct ath9k_scan_result tmp; + for (int i = 0; i < (int)sizeof(tmp); i++) ((u8 *)&tmp)[i] = 0; + if (parse_beacon(buf, len, &tmp) != 0) return; + /* 跳过隐藏 SSID(长度为 0) */ + if (tmp.ssid[0] == 0) return; + tmp.rssi = rssi; + /* 去重:BSSID 已存在则更新 RSSI */ + int existing = find_bssid(tmp.bssid); + if (existing >= 0) { + /* 更新 RSSI(取较强值) */ + if (rssi > g_scan_results[existing].rssi) g_scan_results[existing].rssi = rssi; + return; + } + if (g_scan_count >= ATH9K_MAX_SCAN_RESULTS) return; + /* 追加 */ + g_scan_results[g_scan_count] = tmp; + g_scan_count++; +} + +/* 从 RX 环轮询所有已完成描述符 */ +static void ath9k_rx_drain(void) { + if (!g_rx_ready) return; + for (u32 i = 0; i < ATH9K_RX_DESC_COUNT; i++) { + u32 idx = g_rx_head; + struct ath9k_rx_desc *d = &g_rx_desc[idx]; + __asm__ volatile("" ::: "memory"); + if (!(d->ds_status0 & ATH9K_RXSTATUS_DONE)) break; + if (d->ds_status0 & ATH9K_RXSTATUS_OK) { + u32 flen = (d->ds_status0 & ATH9K_RXSTATUS_FRAME_LEN_M) >> ATH9K_RXSTATUS_FRAME_LEN_S; + if (flen > 0 && flen <= ATH9K_RX_BUF_SIZE) { + /* RSSI 在 ds_status2 低 8 位(ath9k convention,负值表示) */ + i8 rssi = (i8)(d->ds_status2 & 0xFF); + ath9k_handle_rx_frame(g_rx_buf[idx], flen, rssi); + } + } + /* 重置描述符,归还给硬件 */ + d->ds_status0 = 0; + d->ds_status1 = 0; + d->ds_status2 = 0; + d->ds_status3 = 0; + d->ds_ctl0 = ATH9K_RX_BUF_SIZE & ATH9K_RXCTL_BUF_LEN_M; + d->ds_ctl1 = ATH9K_RXCTL_INTREQ; + __asm__ volatile("" ::: "memory"); + g_rx_head = (g_rx_head + 1) % ATH9K_RX_DESC_COUNT; + } +} + +/* ---- 信道切换(简化版) ---- + * 完整 ath9k 信道切换涉及 PLL/RFBUS/ChannelFraction/RFBank 等数十寄存器, + * 本驱动仅写最小序列:PHY RFBUS 请求 + Channel 寄存器占位。 + * 真实硬件验证后增量完善。 + */ +static void ath9k_set_channel(u8 channel) { + (void)channel; + /* TODO: 完整 ath9k 信道切换序列(参考 ar9002_hw_set_channel) */ + /* 触发 RFBUS 请求,等待 grant */ + ath9k_reg_write(AR_PHY_RFBUS_REQ, 0x1); + delay_us(5); + for (u32 i = 0; i < 1000; i++) { + if (ath9k_reg_read(AR_PHY_RFBUS_REQ) & 0x2) break; + delay_us(1); + } + ath9k_reg_write(AR_PHY_RFBUS_REQ, 0); + delay_us(50); +} + +/* ---- 被动扫描状态机 ---- + * 遍历 2.4GHz 信道 1-13,每个信道停留 ~400ms 监听 beacon。 + * 完成后 g_scan_count 包含所有发现的 SSID。 + */ +static int ath9k_scan_start_impl(void *ctx) { + (void)ctx; + raw_log("[ath9k.raw] scan_start\n"); + if (!g_rx_ready) return -1; + g_scanning = 1; + g_scan_count = 0; + for (u32 i = 0; i < ATH9K_MAX_SCAN_RESULTS; i++) { + for (int j = 0; j < (int)sizeof(g_scan_results[i]); j++) { + ((u8 *)&g_scan_results[i])[j] = 0; + } + } + for (u32 c = 0; c < ATH9K_CHAN_2G_COUNT; c++) { + u8 channel = (u8)(c + 1); + ath9k_set_channel(channel); + raw_log("[ath9k.raw] scan ch="); + static const char hh[] = "0123456789"; + char buf[4]; buf[0]=hh[channel/10]; buf[1]=hh[channel%10]; buf[2]='\n'; buf[3]=0; + raw_log(buf); + /* 在该信道停留 ~400ms,期间轮询 RX 环 */ + for (u32 tick = 0; tick < 400; tick++) { + ath9k_rx_drain(); + delay_us(1000); + } + } + g_scanning = 0; + raw_log("[ath9k.raw] scan done, results="); + static const char hh2[] = "0123456789"; + char cntbuf[4]; cntbuf[0]=hh2[g_scan_count/10]; cntbuf[1]=hh2[g_scan_count%10]; cntbuf[2]='\n'; cntbuf[3]=0; + raw_log(cntbuf); + return 0; +} + +static int ath9k_scan_count_impl(void *ctx) { + (void)ctx; + return (int)g_scan_count; +} + +static int ath9k_scan_result_impl(void *ctx, u32 n, struct dkm_net_scan_result *out) { + (void)ctx; + if (!out) return -1; + if (n >= g_scan_count) return -2; + /* 内部 ath9k_scan_result 与 dkm_net_scan_result 布局一致 */ + const u8 *src = (const u8 *)&g_scan_results[n]; + u8 *dst = (u8 *)out; + for (u32 i = 0; i < sizeof(struct dkm_net_scan_result); i++) dst[i] = src[i]; + return 0; +} + +static int ath9k_is_wireless_impl(void *ctx) { + (void)ctx; + return 1; +} + +/* ath9k 当前不支持 TX(仅扫描) */ +static int ath9k_tx_stub(void *ctx, const void *packet, u32 length) { + (void)ctx; (void)packet; (void)length; + return -1; +} + +static int ath9k_rx_poll_impl(void *ctx, void *buffer, u32 capacity, u32 *out_length) { + (void)ctx; (void)buffer; (void)capacity; + if (out_length) *out_length = 0; + /* 被动扫描内部消费所有帧,外部 rx_poll 总是返回无数据 */ + return 1; +} + +static void copy_mac(u8 dst[6], const u8 src[6]) { + for (int i = 0; i < 6; i++) dst[i] = src[i]; +} + +/* ---- 中断处理(PIC IRQ 路由) ---- */ +static int ath9k_irq_handler(u8 irq) { + (void)irq; + if (!g_mmio) return 0; + u32 isr = ath9k_reg_read(AR_ISR_RAC); + if (isr) { + ath9k_reg_write(AR_ISR, isr); + if (isr & (AR_ISR_RXOK | AR_ISR_RXERR | AR_ISR_RXORN)) { + ath9k_rx_drain(); + } + } + return 0; +} + +/* ---- driver_init ---- */ +__attribute__((visibility("default"))) +int driver_init(const struct dkm_kernel_api *api, + struct dkm_driver_handle *handle) { + (void)handle; + raw_log("[ath9k.raw] driver_init entered\n"); + if (!api || !api->log) return -1; + g_log = api->log; + + /* 1. PCI 探测 */ + u8 bus, dev; + if (ath9k_find(&bus, &dev) != 0) { + raw_log("[ath9k.raw] no Atheros WiFi device found\n"); + return -1; + } + raw_log("[ath9k.raw] Atheros device found\n"); + + /* 2. 启用 PCI 命令(MEM + Bus Master) */ + u32 cmd = pci_read(bus, dev, 0, PCI_COMMAND); + pci_write(bus, dev, 0, PCI_COMMAND, cmd | PCI_CMD_MEM | PCI_CMD_BUSMST); + + /* 3. 读取 BAR0 */ + u32 bar0 = pci_read(bus, dev, 0, PCI_BAR0); + raw_hex("[ath9k.raw] BAR0 raw=", bar0); + if ((bar0 & 0x1) != 0) { + raw_log("[ath9k.raw] BAR0 is IO space, unsupported\n"); + return -2; + } + g_bar0_phys = (u64)(bar0 & 0xFFFFFFF0u); + /* 检查是否 64-bit BAR */ + if ((bar0 & 0x6) == 0x4) { + u32 bar1 = pci_read(bus, dev, 0, PCI_BAR1); + g_bar0_phys |= ((u64)bar1 << 32); + raw_hex("[ath9k.raw] BAR0 high=", (u64)bar1); + } + raw_hex("[ath9k.raw] BAR0 phys=", g_bar0_phys); + if (g_bar0_phys >= 0x100000000ULL) { + raw_log("[ath9k.raw] BAR0 above 4G, current HHDM cannot map\n"); + return -3; + } + + /* 4. HHDM 映射 BAR0 */ + u64 hhdm = api->hhdm_offset; + raw_hex("[ath9k.raw] HHDM=", hhdm); + g_mmio = (volatile u8 *)(g_bar0_phys + hhdm); + /* 验证 MMIO 可读:读 AR_STA_ID1 */ + u32 probe = ath9k_reg_read(AR_STA_ID1); + raw_hex("[ath9k.raw] AR_STA_ID1 probe=", probe); + + /* 5. 读取 PCI device ID + IRQ line */ + u32 vd = pci_read(bus, dev, 0, PCI_VENDOR_ID); + g_pci_device_id = (u16)(vd >> 16); + g_pci_irq = (u8)pci_read(bus, dev, 0, PCI_IRQ_LINE); + raw_hex("[ath9k.raw] device id=", g_pci_device_id); + raw_hex("[ath9k.raw] irq line=", g_pci_irq); + + /* 6. 软件复位 */ + if (ath9k_reset() != 0) { + raw_log("[ath9k.raw] reset failed\n"); + return -4; + } + + /* 7. EEPROM 读取 MAC 地址 */ + /* AR5416 EEPROM 偏移:MAC 地址在 word 0x1D-0x1F(6 字节) */ + u16 m0 = ath9k_eeprom_read(0x1D); + u16 m1 = ath9k_eeprom_read(0x1E); + u16 m2 = ath9k_eeprom_read(0x1F); + if (m0 == 0xffff && m1 == 0xffff && m2 == 0xffff) { + raw_log("[ath9k.raw] EEPROM MAC all-FF, fallback to all-zero\n"); + for (int i = 0; i < 6; i++) g_mac[i] = 0; + } else { + g_mac[0] = (u8)(m0 & 0xFF); + g_mac[1] = (u8)(m0 >> 8); + g_mac[2] = (u8)(m1 & 0xFF); + g_mac[3] = (u8)(m1 >> 8); + g_mac[4] = (u8)(m2 & 0xFF); + g_mac[5] = (u8)(m2 >> 8); + } + { + static const char hh[] = "0123456789abcdef"; + char mstr[18]; + mstr[0]=hh[(g_mac[0]>>4)&0xf]; mstr[1]=hh[g_mac[0]&0xf]; mstr[2]=':'; + mstr[3]=hh[(g_mac[1]>>4)&0xf]; mstr[4]=hh[g_mac[1]&0xf]; mstr[5]=':'; + mstr[6]=hh[(g_mac[2]>>4)&0xf]; mstr[7]=hh[g_mac[2]&0xf]; mstr[8]=':'; + mstr[9]=hh[(g_mac[3]>>4)&0xf]; mstr[10]=hh[g_mac[3]&0xf]; mstr[11]=':'; + mstr[12]=hh[(g_mac[4]>>4)&0xf]; mstr[13]=hh[g_mac[4]&0xf]; mstr[14]=':'; + mstr[15]=hh[(g_mac[5]>>4)&0xf]; mstr[16]=hh[g_mac[5]&0xf]; mstr[17]=0; + raw_log("[ath9k.raw] MAC="); + raw_log(mstr); + raw_log("\n"); + } + + /* 8. 硬件初始化 */ + if (ath9k_hw_init() != 0) { + raw_log("[ath9k.raw] hw_init failed\n"); + return -5; + } + + /* 9. RX 环初始化 */ + if (api->dma && api->dma->alloc_pages) { + int rc = ath9k_rx_init(api->dma); + raw_hex("[ath9k.raw] rx_init rc=", (u64)(i8)rc); + if (rc != 0) { + raw_log("[ath9k.raw] RX init failed, scan unavailable\n"); + } + } else { + raw_log("[ath9k.raw] no DMA api, scan unavailable\n"); + } + + /* 10. 注册中断 */ + if (api->irq_register && g_pci_irq) { + int rc = api->irq_register(g_pci_irq, ath9k_irq_handler); + raw_hex("[ath9k.raw] irq_register rc=", (u64)(i8)rc); + } + + /* 11. 注册 netdev(标记 WIRELESS + RX_READY) */ + struct dkm_net_device_desc ndesc; + for (u32 i = 0; i < sizeof(ndesc); i++) ((u8 *)&ndesc)[i] = 0; + static const char devname[] = "ath9k0"; + ndesc.name = devname; + copy_mac(ndesc.mac, g_mac); + ndesc.flags = DKM_NET_F_WIRELESS | (g_rx_ready ? DKM_NET_F_RX_READY : 0); + ndesc.ctx = (void *)0; /* ath9k 用全局状态,ctx 不需要 */ + ndesc.tx = ath9k_tx_stub; + ndesc.rx_poll = ath9k_rx_poll_impl; + ndesc.scan_start = ath9k_scan_start_impl; + ndesc.scan_count = ath9k_scan_count_impl; + ndesc.scan_result = ath9k_scan_result_impl; + ndesc.is_wireless = ath9k_is_wireless_impl; + + if (api->net && api->net->register_device) { + int rc = api->net->register_device(&ndesc); + raw_hex("[ath9k.raw] netdev registered, index=", (u64)rc); + } else { + raw_log("[ath9k.raw] no net api\n"); + return -6; + } + + /* 12. 启动后台被动扫描(同步执行,阻塞 driver_init 直到扫描完成) + * 注:DSK 加载驱动是顺序的,ath9k 在 stage3 末尾加载, + * 同步扫描会让 stage3 加载多花 ~5s(13 信道 × 400ms)。 + * 这是可接受的,因为 FirstInit 在 DSK 之后才运行,能拿到完整扫描结果。 + */ + if (g_rx_ready) { + raw_log("[ath9k.raw] starting initial passive scan\n"); + ath9k_scan_start_impl(0); + } + + raw_log("[ath9k.raw] driver_init done\n"); + return 0; +} + +__attribute__((visibility("default"))) +void driver_exit(struct dkm_driver_handle *handle) { + (void)handle; + if (g_mmio) { + ath9k_reg_write(AR_CR, AR_CR_RXD); + } + g_rx_ready = 0; + g_mmio = 0; + raw_log("[ath9k.raw] driver_exit\n"); +} diff --git a/CODE/DKM/e1000/e1000.c b/CODE/DKM/e1000/e1000.c index 7c94cd5..c7dfddb 100644 --- a/CODE/DKM/e1000/e1000.c +++ b/CODE/DKM/e1000/e1000.c @@ -12,6 +12,7 @@ typedef unsigned char u8; typedef unsigned short u16; typedef unsigned int u32; typedef unsigned long long u64; +typedef signed char i8; typedef long long i64; #define NULL ((void *)0) @@ -22,6 +23,14 @@ struct dkm_log_api { void (*panic)(const char *msg); }; +struct dkm_net_scan_result { + char ssid[33]; + u8 bssid[6]; + u8 channel; + i8 rssi; + u8 security; +}; + struct dkm_net_device_desc { const char *name; u8 mac[6]; @@ -29,12 +38,23 @@ struct dkm_net_device_desc { void *ctx; int (*tx)(void *ctx, const void *packet, u32 length); int (*rx_poll)(void *ctx, void *buffer, u32 capacity, u32 *out_length); + /* 无线扩展回调(有线驱动置 NULL,向后兼容)。 */ + int (*scan_start)(void *ctx); + int (*scan_count)(void *ctx); + int (*scan_result)(void *ctx, u32 n, struct dkm_net_scan_result *out); + int (*is_wireless)(void *ctx); }; struct dkm_net_api { int (*register_device)(const struct dkm_net_device_desc *desc); u32 (*device_count)(void); int (*device_info)(u32 index, void *out); + int (*tx)(u32 index, const void *packet, u32 length); + int (*rx_poll)(u32 index, void *buffer, u32 capacity, u32 *out_length); + int (*scan_start)(u32 index); + int (*scan_count)(u32 index); + int (*scan_result)(u32 index, u32 n, struct dkm_net_scan_result *out); + int (*is_wireless)(u32 index); }; struct dkm_dma_buffer { @@ -95,6 +115,7 @@ const struct dkm_driver_desc driver_desc = { #define PCI_DEVICE_ID 0x02 #define PCI_COMMAND 0x04 #define PCI_BAR0 0x10 +#define PCI_BAR1 0x14 #define PCI_IRQ_LINE 0x3C /* e1000 MMIO registers */ @@ -184,10 +205,10 @@ static u32 g_rx_tail; static u32 g_tx_tail; static int g_rings_ready; -static __inline__ void outl(u16 p, u32 v) { __asm__("outl %0,%1"::"a"(v),"Nd"(p)); } -static __inline__ u32 inl(u16 p) { u32 v; __asm__("inl %1,%0":"=a"(v):"Nd"(p)); return v; } -static u8 inb(u16 p) { u8 v; __asm__("inb %1,%0":"=a"(v):"Nd"(p)); return v; } -static void outb(u16 p, u8 v) { __asm__("outb %0,%1"::"a"(v),"Nd"(p)); } +static __inline__ void outl(u16 p, u32 v) { __asm__ volatile("outl %0,%1"::"a"(v),"Nd"(p):"memory"); } +static __inline__ u32 inl(u16 p) { u32 v; __asm__ volatile("inl %1,%0":"=a"(v):"Nd"(p):"memory"); return v; } +static u8 inb(u16 p) { u8 v; __asm__ volatile("inb %1,%0":"=a"(v):"Nd"(p):"memory"); return v; } +static void outb(u16 p, u8 v) { __asm__ volatile("outb %0,%1"::"a"(v),"Nd"(p):"memory"); } static void raw_log(const char *s) { while (*s) { @@ -201,6 +222,17 @@ static void raw_log(const char *s) { } } +static void raw_hex(const char *prefix, u64 v) { + static const char h[] = "0123456789abcdef"; + raw_log(prefix); + raw_log("0x"); + for (int i=15; i>=0; i--) { + for (u32 wait=0; wait<100000; wait++) if (inb(0x3F8+5)&0x20) break; + outb(0x3F8, (u8)h[(v >> (i * 4)) & 0xf]); + } + raw_log("\n"); +} + static u32 pci_read(u8 bus, u8 dev, u8 func, u8 reg) { u32 a = (1u<<31)|((u32)bus<<16)|((u32)dev<<11)|((u32)func<<8)|((u32)reg&0xFC); outl(PCI_ADDR,a); return inl(PCI_DATA); @@ -253,6 +285,19 @@ static void copy_bytes(void *dst, const void *src, u32 len) { for (u32 i=0;ialloc_pages || !g_mmio) return -1; @@ -269,8 +314,8 @@ static int e1000_init_rings(const struct dkm_dma_api *dma) { if (dma->alloc_pages(1, 16, 0x100000000ULL, &b) != 0) return -4; g_rx_buf[i] = (u8 *)b.virt; g_rx_buf_phys[i] = b.phys; + zero_bytes(&g_rx_desc[i], sizeof(g_rx_desc[i])); g_rx_desc[i].addr = b.phys; - g_rx_desc[i].status = 0; } for (u32 i=0;ialloc_pages(1, 16, 0x100000000ULL, &b) != 0) return -5; g_tx_buf[i] = (u8 *)b.virt; g_tx_buf_phys[i] = b.phys; + zero_bytes(&g_tx_desc[i], sizeof(g_tx_desc[i])); g_tx_desc[i].addr = b.phys; g_tx_desc[i].status = E1000_TX_STA_DD; } + dma_fence(); raw_log("[e1000.raw] ring/buffers allocated\n"); g_mmio[E1000_RCTL/4] = 0; g_mmio[E1000_TCTL/4] = 0; + mmio_flush(); g_mmio[E1000_RDBAL/4] = (u32)(g_rx_desc_phys & 0xffffffffu); g_mmio[E1000_RDBAH/4] = (u32)(g_rx_desc_phys >> 32); @@ -305,8 +353,10 @@ static int e1000_init_rings(const struct dkm_dma_api *dma) { g_mmio[E1000_TXDCTL/4] = (1u << 25); g_mmio[E1000_TIPG/4] = 0x0060200Au; + dma_fence(); g_mmio[E1000_RCTL/4] = E1000_RCTL_EN | E1000_RCTL_UPE | E1000_RCTL_MPE | E1000_RCTL_BAM | E1000_RCTL_SECRC | E1000_RCTL_BSIZE_2048; g_mmio[E1000_TCTL/4] = E1000_TCTL_EN | E1000_TCTL_PSP | (0x0Fu << E1000_TCTL_CT_SHIFT) | (0x40u << E1000_TCTL_COLD_SHIFT); + mmio_flush(); g_rings_ready = 1; raw_log("[e1000.raw] RX/TX rings initialized\n"); @@ -316,32 +366,62 @@ static int e1000_init_rings(const struct dkm_dma_api *dma) { static int e1000_tx(void *ctx, const void *packet, u32 length) { (void)ctx; raw_log("[e1000.raw] tx enter\n"); - if (!g_rings_ready || !packet || length == 0 || length > E1000_RX_BUF_SIZE) return -1; + if (!g_rings_ready || !packet || length == 0 || length > E1000_TX_BUF_SIZE) return -1; u32 index = g_tx_tail; if (!(g_tx_desc[index].status & E1000_TX_STA_DD)) return -2; copy_bytes(g_tx_buf[index], packet, length); + dma_fence(); raw_log("[e1000.raw] tx copied\n"); g_tx_desc[index].length = (u16)length; + g_tx_desc[index].cso = 0; g_tx_desc[index].cmd = E1000_TX_CMD_EOP | E1000_TX_CMD_IFCS | E1000_TX_CMD_RS; + g_tx_desc[index].css = 0; + g_tx_desc[index].special = 0; g_tx_desc[index].status = 0; + dma_fence(); g_tx_tail = (index + 1) % E1000_TX_DESC_COUNT; g_mmio[E1000_TDT/4] = g_tx_tail; - raw_log("[e1000.raw] tx tdt written\n"); - return 0; + mmio_flush(); + for (u32 wait=0; wait<1000000; wait++) { + dma_fence(); + if (g_tx_desc[index].status & E1000_TX_STA_DD) { + raw_log("[e1000.raw] tx complete\n"); + return 0; + } + __asm__ volatile("pause"); + } + raw_log("[e1000.raw] tx timeout\n"); + raw_hex("[e1000.raw] STATUS=", g_mmio[E1000_STATUS/4]); + raw_hex("[e1000.raw] TCTL=", g_mmio[E1000_TCTL/4]); + raw_hex("[e1000.raw] TDH=", g_mmio[E1000_TDH/4]); + raw_hex("[e1000.raw] TDT=", g_mmio[E1000_TDT/4]); + raw_hex("[e1000.raw] desc.status=", g_tx_desc[index].status); + return -3; } static int e1000_rx_poll(void *ctx, void *buffer, u32 capacity, u32 *out_length) { (void)ctx; + if (out_length) *out_length = 0; if (!g_rings_ready || !buffer || !out_length) return -1; u32 next = (g_rx_tail + 1) % E1000_RX_DESC_COUNT; + dma_fence(); if (!(g_rx_desc[next].status & E1000_RX_STA_DD)) return 1; u32 len = g_rx_desc[next].length; - if (len > capacity) return -2; + if (len > capacity) { + g_rx_desc[next].status = 0; + dma_fence(); + g_rx_tail = next; + g_mmio[E1000_RDT/4] = g_rx_tail; + mmio_flush(); + return -2; + } copy_bytes(buffer, g_rx_buf[next], len); *out_length = len; g_rx_desc[next].status = 0; + dma_fence(); g_rx_tail = next; g_mmio[E1000_RDT/4] = g_rx_tail; + mmio_flush(); return 0; } @@ -367,7 +447,18 @@ int driver_init(const struct dkm_kernel_api *api, pci_write(bus, dev, 0, PCI_COMMAND, cmd); raw_log("[e1000.raw] PCI command enabled\n"); - g_bar0_phys = (u64)pci_read(bus,dev,0,PCI_BAR0) & 0xFFFFFFF0ULL; + u32 bar0 = pci_read(bus,dev,0,PCI_BAR0); + if (bar0 & 1u) { + raw_log("[e1000.raw] IO BAR unsupported\n"); + return 0; + } + g_bar0_phys = (u64)(bar0 & 0xFFFFFFF0ULL); + if ((bar0 & 0x6u) == 0x4u) { + u32 bar1 = pci_read(bus,dev,0,PCI_BAR1); + g_bar0_phys |= ((u64)bar1 << 32); + } + raw_hex("[e1000.raw] BAR0 raw=", bar0); + raw_hex("[e1000.raw] BAR0 phys=", g_bar0_phys); if (!g_bar0_phys) { raw_log("[e1000.raw] BAR0 unavailable\n"); return 0; diff --git a/CODE/DKM/virtio_net/virtio_net.c b/CODE/DKM/virtio_net/virtio_net.c index 3f5d47a..1faf920 100644 --- a/CODE/DKM/virtio_net/virtio_net.c +++ b/CODE/DKM/virtio_net/virtio_net.c @@ -13,6 +13,7 @@ typedef unsigned char u8; typedef unsigned short u16; typedef unsigned int u32; typedef unsigned long long u64; +typedef signed char i8; typedef long long i64; #define NULL ((void *)0) @@ -24,6 +25,14 @@ struct dkm_log_api { void (*panic)(const char *msg); }; +struct dkm_net_scan_result { + char ssid[33]; + u8 bssid[6]; + u8 channel; + i8 rssi; + u8 security; +}; + struct dkm_net_device_desc { const char *name; u8 mac[6]; @@ -31,12 +40,23 @@ struct dkm_net_device_desc { void *ctx; int (*tx)(void *ctx, const void *packet, u32 length); int (*rx_poll)(void *ctx, void *buffer, u32 capacity, u32 *out_length); + /* 无线扩展回调(有线驱动置 NULL,向后兼容)。 */ + int (*scan_start)(void *ctx); + int (*scan_count)(void *ctx); + int (*scan_result)(void *ctx, u32 n, struct dkm_net_scan_result *out); + int (*is_wireless)(void *ctx); }; struct dkm_net_api { int (*register_device)(const struct dkm_net_device_desc *desc); u32 (*device_count)(void); int (*device_info)(u32 index, void *out); + int (*tx)(u32 index, const void *packet, u32 length); + int (*rx_poll)(u32 index, void *buffer, u32 capacity, u32 *out_length); + int (*scan_start)(u32 index); + int (*scan_count)(u32 index); + int (*scan_result)(u32 index, u32 n, struct dkm_net_scan_result *out); + int (*is_wireless)(u32 index); }; #define DKM_NET_F_LINK_UP (1u << 0) diff --git a/CODE/UTSM/include/utsm/net.h b/CODE/UTSM/include/utsm/net.h index 1f5131f..11237e3 100644 --- a/CODE/UTSM/include/utsm/net.h +++ b/CODE/UTSM/include/utsm/net.h @@ -4,15 +4,40 @@ #include #define DKM_NET_MAX_DEVICES 8u +#define DKM_NET_MAX_SCAN_RESULTS 32u +#define DKM_NET_SSID_MAX 32u /* 网卡链路/能力标志。 */ #define DKM_NET_F_LINK_UP (1u << 0) #define DKM_NET_F_TX_READY (1u << 1) #define DKM_NET_F_RX_READY (1u << 2) +#define DKM_NET_F_WIRELESS (1u << 3) + +/* WiFi 扫描结果加密类型。 */ +#define DKM_NET_SEC_OPEN 0u +#define DKM_NET_SEC_WEP 1u +#define DKM_NET_SEC_WPA 2u +#define DKM_NET_SEC_WPA2 3u +#define DKM_NET_SEC_WPA3 4u typedef int (*dkm_net_tx_fn)(void *ctx, const void *packet, u32 length); typedef int (*dkm_net_rx_poll_fn)(void *ctx, void *buffer, u32 capacity, u32 *out_length); +/* WiFi 扫描结果(须先于回调 typedef 定义)。 */ +typedef struct dkm_net_scan_result { + char ssid[DKM_NET_SSID_MAX + 1]; + u8 bssid[6]; + u8 channel; + i8 rssi; /* dBm,负值 */ + u8 security; /* DKM_NET_SEC_* */ +} dkm_net_scan_result; + +/* 无线扫描回调(无线驱动填,有线驱动置 NULL)。 */ +typedef int (*dkm_net_scan_start_fn)(void *ctx); +typedef int (*dkm_net_scan_count_fn)(void *ctx); +typedef int (*dkm_net_scan_result_fn)(void *ctx, u32 n, dkm_net_scan_result *out); +typedef int (*dkm_net_is_wireless_fn)(void *ctx); + typedef struct dkm_net_device_desc { const char *name; u8 mac[6]; @@ -20,6 +45,11 @@ typedef struct dkm_net_device_desc { void *ctx; dkm_net_tx_fn tx; dkm_net_rx_poll_fn rx_poll; + /* 无线扩展回调(无线驱动填,有线驱动置 NULL,向后兼容)。 */ + dkm_net_scan_start_fn scan_start; + dkm_net_scan_count_fn scan_count; + dkm_net_scan_result_fn scan_result; + dkm_net_is_wireless_fn is_wireless; } dkm_net_device_desc; typedef struct dkm_net_device_info { @@ -34,6 +64,11 @@ typedef struct dkm_net_api { int (*device_info)(u32 index, dkm_net_device_info *out); int (*tx)(u32 index, const void *packet, u32 length); int (*rx_poll)(u32 index, void *buffer, u32 capacity, u32 *out_length); + /* 无线扫描接口(dispatch 到驱动回调,有线设备返回 -3 表示不支持)。 */ + int (*scan_start)(u32 index); + int (*scan_count)(u32 index); + int (*scan_result)(u32 index, u32 n, dkm_net_scan_result *out); + int (*is_wireless)(u32 index); } dkm_net_api; void net_init(void); diff --git a/CODE/UTSM/kernel/net.c b/CODE/UTSM/kernel/net.c index f67837d..c8da864 100644 --- a/CODE/UTSM/kernel/net.c +++ b/CODE/UTSM/kernel/net.c @@ -5,6 +5,9 @@ * 最小 netdev 注册表: * - 早期 DKM 驱动加载路径是单线程顺序执行,当前无需引入锁,避免在网络底座阶段扩大复杂度。 * - 后续进入多核/热插拔后,应在 register/device_info 外围加自旋锁或 RCU 风格只读快照。 + * + * 无线扫描接口(scan_start/scan_count/scan_result/is_wireless)通过 dispatch 转发到驱动 + * 注册的回调。有线驱动(e1000/virtio_net)不填这些回调,dispatch 时返回 -3 表示不支持。 */ static dkm_net_device_desc g_net_devices[DKM_NET_MAX_DEVICES]; static u32 g_net_device_count; @@ -60,12 +63,45 @@ static int net_rx_poll_impl(u32 index, void *buffer, u32 capacity, u32 *out_leng return dev->rx_poll(dev->ctx, buffer, capacity, out_length); } +static int net_scan_start_impl(u32 index) { + if (index >= g_net_device_count) return -2; + const dkm_net_device_desc *dev = &g_net_devices[index]; + if (!dev->scan_start) return -3; + return dev->scan_start(dev->ctx); +} + +static int net_scan_count_impl(u32 index) { + if (index >= g_net_device_count) return -2; + const dkm_net_device_desc *dev = &g_net_devices[index]; + if (!dev->scan_count) return -3; + return dev->scan_count(dev->ctx); +} + +static int net_scan_result_impl(u32 index, u32 n, dkm_net_scan_result *out) { + if (!out) return -1; + if (index >= g_net_device_count) return -2; + const dkm_net_device_desc *dev = &g_net_devices[index]; + if (!dev->scan_result) return -3; + return dev->scan_result(dev->ctx, n, out); +} + +static int net_is_wireless_impl(u32 index) { + if (index >= g_net_device_count) return -2; + const dkm_net_device_desc *dev = &g_net_devices[index]; + if (dev->is_wireless) return dev->is_wireless(dev->ctx); + return (dev->flags & DKM_NET_F_WIRELESS) ? 1 : 0; +} + static const dkm_net_api g_net_api = { .register_device = net_register_device_impl, .device_count = net_device_count_impl, .device_info = net_device_info_impl, .tx = net_tx_impl, - .rx_poll = net_rx_poll_impl + .rx_poll = net_rx_poll_impl, + .scan_start = net_scan_start_impl, + .scan_count = net_scan_count_impl, + .scan_result = net_scan_result_impl, + .is_wireless = net_is_wireless_impl }; const dkm_net_api *net_get_api(void) { diff --git a/CODE/UTSM/kernel/net.o b/CODE/UTSM/kernel/net.o index 7953ba8f2f7df819ffca0ee958dd01a9a2b2d2fd..d54ff30e82730c69563fc26d1db705f985cbea8f 100644 GIT binary patch literal 4072 zcmb`KUu;uV7{E{2fWZbUsFAH-RZsk`{o4#f=58?7WElZA#S(T^{7&#q75t$O82Lk`Mf4)W>Ag&jNk1Y zdxTn-Gm|)vO}BT5i!~T>yw*Ne1|&yc6wdqTqxY>hDrdN)nUqk80Xm843E$-0MerkH)4a zFm^=+!SteDzTcUFgz|yZ2Q1%DFPmrMtBKYfn9Uprx)Zs;i6ZYHM9>tE)>Y zW6GDiFBqPTO{=!Vrc)FCu!r5hfTKXVl3`@XJord15~P{TsAajbCw=!SR))caW75Y`>{;sQ3Fx_sj7goG!nU@S}}e zDXVR+JTs+=s+?3s;H3zy$A(}sWn9zi*+j0Y%`TU4j!xr>vru~VRHDJD+fAdhf!Oqs z1AE_$8AU6dv7O>cBa_QG!edEmB$Kdc$tqe&;+b61D(#|4Dr=@~o;W2VnXwC|lNd5m zIdv&wivQ0@WX)XK7_o|WCZ999xRYm9qdV%TO*Ne+qZ`|{70tU@Q`U1 zztD-`yiB3az#Q!-iSy$Q zFBs%kiJv0*8u0;$k0b2~aSf#ML!3|TFmYH^W2Yy?F!89y&l4{b?<4y!h+ichC61!W z*B>7SKKFq3W8n3^@xppgmUEJzuciffl+X2ykP@q)PP8`&E+KgcIQN*45*!iJZ->u4 zya{0}BcoQ3{(a99$Mb#)K7V`WwRObNp7$ccbHTy>+1~Wc;6dz$7| zl6pQLHQ=8R=enM!`DZ@+XF%sXztrq|X#Q0L`^&`jdS@H(--zq=-q-jFnlFHJ#KHC} zy{TBH@m0jvXq>N~bsAqo_FWoZOMIus4dQrR;n3?%YxbRpz$8iR*E1`267Yvz`3>qH&JfiVk4tepVCbK1Y?65F0hleqL?h=ZI#{&%7TJ6n)>3(8;X+pFcEZfXZO1Ia-iMfS zUT6N0b*gq3E&F)Zw>@LWPh=ov%eL#5v>$V*!(u63$Y--Ypk9NlB3GzV3gz;S721E` z;3mj3@g7xX7BZ@UG*pdx2wywmW-B}52bsT^|<Yb1U2V;U9%nT#b;8K z0m%Y6G;k3Hrh}Iv-!FV0U>}faVZ;Cdehz?%8hU&a62Kg~Xq^S`e}@4{w8g>lb=pt2 ziMI&f7bw1*Bc8(fGkyj3Kd$}>{9h2pbx)}Ju_e5p>+b-Dx$yl==Hed3Pcb0Ld9%dL zbGpy=DD~Zkl&af42cW6HmWRc%-*$+uA1XuTkL&vWmjLL!L%Vk=evlS%-*Wiq_5TZ5 Cu)<0J literal 3384 zcmbtWO>7%Q6rRLM>-@Af6@ox1*&E@fn>bCJgpy*5G~GxcP(i7x)>vzM*D*M@Wp5)_ zsz^{0(Upnlp$LxLxB(|frK(C4XgG1ApT6xac*g!S$1OA4($Ap|W zHdfJ2tcSYAN*#u=R1Ym)2FO@=QiK-Y2MztiUwE!|0GTVdk+kr~?$B#*fVuoslPg0Q z?sa2rv&E=A(_^@=$B+uT=F_`;dbpv7jM~Yd;a>8V(9#L_HsyO0Jx$b(NJ*IC-jHUz zHXHWS-0P>~FJdW+xJc$*lgS|#^Xgst%_+zl4v!+Il61GcyeVgQZ|Fl$y)YGQsBI0k zy`gqA)Xs*wr?wbF-|l!gvYJ?zZB4A(7yV&RxxX80-$1(-Cy;6^tvYep=abOsXD64h zc(Au;hWo4GUW>b5Q8VVcCTho^wKvAxUyOy1gHUxmnW%5B+=9JXxBd0|V?N^6jkWJF z<{Zb|XDmN4XS^y;hEbdAVke1pV4R*h`g}qwTiIO2DWB7Fg`7iJ#+uEgEt;~*R)+9g zA!AiX$*}Wgw!(%})iSwC$#l}wnq82s#~l9KNaxK$R-3iTm0Yo)_4n%$J*xFgJ5H(c zcyDhu=S-hX>FMH3Z$3Zk{TwWni!WJeN3+udrkNQ^4Q5h@Y}+2_k7Vo-b0m6bDB3re z8cCaj(df|daOB~C$>dv@YY{)!x1$6^^Br&}AWg7>2WwX)zKdbeL)x1lo8hmI>`zHX zL86~9+4J=Bp8^~CFewMd!%>6>@i5`9NK}ZY3BO2qmT-Jaq#r85zbgS>{zR7|ApZWK z_+b$cjk)VHU(%4UO3?$dYb11QHaX&)v{OQr_4-t-hj)~B5!r4Fbb35SY2}eKp43m99!FiuADLD2E$2;=Z^MQUe zvhiDtgZ+1Truc{?+GlMCypM3T-opyc{TWyA`@C@CxPsqL_!$M~bDLH02Z&!*@B@UOS8$E+ zw+L73eOKZ0Ik~FfyuY9LaNL)1^8Y#EYM(nWAPhC`e!{t*d~P3AaE|MF@jgFD;XH#h zf#Ky+%7fQ2&fh2AAIACnq^W)=kJjD6w)PgGm#w_1J66>Z*<`_TlHP5UOc&1<9D(=1 zj0FnL$SX@vRVx2rRz$v-g*m$@NlrDHnEnayrL^UcYL0(3y7&T%u`%GXF!odjUR#pFo%AcXTj^wFhCNyI9T2y zT`5y+63`>rjUUdRku6ZRtN(2xR*8u9V~=?~*WV2o=ECnKsB#3UAvOafId7Kl0nWOg yT2LFRtY_{QpadrC`0vO(V<^LPe)wEq{y5ZFKLMup4tXVt{{byPu@@e-{=Wc6&IhFc diff --git a/CODE/dsk/main.c b/CODE/dsk/main.c index 4dc430c..22d94c7 100644 --- a/CODE/dsk/main.c +++ b/CODE/dsk/main.c @@ -327,48 +327,80 @@ static void dsk_spinner_and_check(u32 *fb_arg, i64 cx, i64 cy, i64 r, i64 thk, logl("[DSK] checking firstInit..."); if(g_block_read){logl("[DSK] have block read, calling dsk_check_firstinit"); int is_first=0; - if(dsk_check_firstinit(&is_first)==0&&is_first){ - logl("[DSK] first init detected; running system initializers"); + if(dsk_check_firstinit(&is_first)==0){ + if(is_first){ + logl("[DSK] first init detected; running system initializers"); - /* DSK owns system component initialization. FirstInit is only user setup. */ - char mn[12]; /* MOUSE ELF */ - mn[0]='M';mn[1]='O';mn[2]='U';mn[3]='S';mn[4]='E';mn[5]=' ';mn[6]=' ';mn[7]=' ';mn[8]='E';mn[9]='L';mn[10]='F'; - u8 *md=0; u32 ms=0; void *me=0; - if(fat32_read_root_file(mn,&md,&ms)==0){ - if(dsk_load_elf(md,ms,&me)==0){ - logl("[DSK] jumping to mouseInit"); - void(*mentry)(const dsk_boot_context*)=(void(*)(const dsk_boot_context*))me; - mentry(ctx); - logl("[DSK] mouseInit returned"); + /* DSK owns system component initialization. FirstInit is only user setup. */ + char mn[12]; /* MOUSE ELF */ + mn[0]='M';mn[1]='O';mn[2]='U';mn[3]='S';mn[4]='E';mn[5]=' ';mn[6]=' ';mn[7]=' ';mn[8]='E';mn[9]='L';mn[10]='F'; + u8 *md=0; u32 ms=0; void *me=0; + if(fat32_read_root_file(mn,&md,&ms)==0){ + if(dsk_load_elf(md,ms,&me)==0){ + logl("[DSK] jumping to mouseInit"); + void(*mentry)(const dsk_boot_context*)=(void(*)(const dsk_boot_context*))me; + mentry(ctx); + logl("[DSK] mouseInit returned"); + } } - } - char nn[12]; /* NETMAN ELF */ - nn[0]='N';nn[1]='E';nn[2]='T';nn[3]='M';nn[4]='A';nn[5]='N';nn[6]=' ';nn[7]=' ';nn[8]='E';nn[9]='L';nn[10]='F'; - u8 *nd=0; u32 ns=0; void *ne=0; - if(fat32_read_root_file(nn,&nd,&ns)==0){ - if(dsk_load_elf(nd,ns,&ne)==0){ - logl("[DSK] jumping to netman"); - void(*nentry)(const dsk_boot_context*)=(void(*)(const dsk_boot_context*))ne; - nentry(ctx); - logl("[DSK] netman returned"); + char nn[12]; /* NETMAN ELF */ + nn[0]='N';nn[1]='E';nn[2]='T';nn[3]='M';nn[4]='A';nn[5]='N';nn[6]=' ';nn[7]=' ';nn[8]='E';nn[9]='L';nn[10]='F'; + u8 *nd=0; u32 ns=0; void *ne=0; + if(fat32_read_root_file(nn,&nd,&ns)==0){ + if(dsk_load_elf(nd,ns,&ne)==0){ + logl("[DSK] jumping to netman"); + void(*nentry)(const dsk_boot_context*)=(void(*)(const dsk_boot_context*))ne; + nentry(ctx); + logl("[DSK] netman returned"); + } } - } - logl("[DSK] loading FirstInit.elf"); - char en[12]; /* FIRSTINIT */ - en[0]='F';en[1]='I';en[2]='R';en[3]='S';en[4]='T'; - en[5]='I';en[6]='N';en[7]='I';en[8]='T';en[9]=' ';en[10]=0; - u8 *fd=0; u32 fs=0; - if(fat32_read_root_file(en,&fd,&fs)==0){ - if(dsk_load_elf(fd,fs,&fe_entry)==0) logl("[DSK] FirstInit.elf loaded"); + logl("[DSK] loading FirstInit.elf"); + char en[12]; /* FIRSTINIT */ + en[0]='F';en[1]='I';en[2]='R';en[3]='S';en[4]='T'; + en[5]='I';en[6]='N';en[7]='I';en[8]='T';en[9]=' ';en[10]=0; + u8 *fd=0; u32 fs=0; + if(fat32_read_root_file(en,&fd,&fs)==0){ + if(dsk_load_elf(fd,fs,&fe_entry)==0) logl("[DSK] FirstInit.elf loaded"); + } + } else { + logl("[DSK] firstInit=1, skipping user setup, loading shell directly"); } } } } if(fe_entry&&frame>150){logl("[DSK] jumping to FirstInit"); /* was 600 */ void(*entry)(const dsk_boot_context*)=(void(*)(const dsk_boot_context*))fe_entry; - entry(ctx); for(;;)__asm__("hlt"); + entry(ctx); + logl("[DSK] FirstInit returned, loading shell"); + /* FirstInit 返回后加载 shell */ + char sh[12]; /* SHELL ELF */ + sh[0]='S';sh[1]='H';sh[2]='E';sh[3]='L';sh[4]='L';sh[5]=' ';sh[6]=' ';sh[7]=' ';sh[8]='E';sh[9]='L';sh[10]='F'; + u8 *sd=0; u32 ss=0; void *se=0; + if(fat32_read_root_file(sh,&sd,&ss)==0){ + if(dsk_load_elf(sd,ss,&se)==0){ + logl("[DSK] jumping to shell"); + void(*sentry)(const dsk_boot_context*)=(void(*)(const dsk_boot_context*))se; + sentry(ctx); + } + } + for(;;)__asm__("hlt"); + } + /* 非首次启动:直接加载 shell */ + if(checked&&!fe_entry&&frame>150){ + logl("[DSK] loading shell directly (non-first boot)"); + char sh[12]; /* SHELL ELF */ + sh[0]='S';sh[1]='H';sh[2]='E';sh[3]='L';sh[4]='L';sh[5]=' ';sh[6]=' ';sh[7]=' ';sh[8]='E';sh[9]='L';sh[10]='F'; + u8 *sd=0; u32 ss=0; void *se=0; + if(fat32_read_root_file(sh,&sd,&ss)==0){ + if(dsk_load_elf(sd,ss,&se)==0){ + logl("[DSK] jumping to shell"); + void(*sentry)(const dsk_boot_context*)=(void(*)(const dsk_boot_context*))se; + sentry(ctx); + } + } + for(;;)__asm__("hlt"); } if(!g_block_read&&frame>800){logl("[DSK] no block device, skipping firstInit");break;} if(checked&&!fe_entry&&frame>800){logl("[DSK] firstInit not found, continuing normal boot");break;} diff --git a/CODE/dsk/main.o b/CODE/dsk/main.o index 405a8be043fa6f547ca2b063bf1fc022a7c162ff..9fbeae8afa529cafa44dfc9b7b15aa43389fff3e 100644 GIT binary patch delta 4662 zcmbtXeNa@_6@PD`$fplUWchR-9|9sQyPzVeC>U1X*7YNSw9fbu5`jvjK!I(n8D>G* zgze6z&>K1?88TSgj?<0>hk+qR+g(RNTb)AdIGIwLn#{CIou;Pw(rJ*M^WM4e+^K){ z&Mfb}-|u(NIrp6N?tKL7dyn+x86u1O^EX&FEf)mEJuCI}2yin|vwQbe>yGLwr7X

;oKt2VD4w0Y{WE#Vr{e@cG6|pEowt6W z%yf1sCv3fbWy+nz5=6V$H@aU;6J31{Z)ca?x81LJ-1i@AMQt`!xDIQs$pbryWI!Xt)hy$1IC9G;)u2ip$M zSpjg#R0Xy@J5MUUIQFICW7ptOKs|#^@Yj0nuH4YjV3tg{d|aU~h*2#BloJoarJg?< zFs!d3OA`-0yK8hI{+e)bHSOH=%*s zw=YcDSzUN_VvRay+B2|MH)sFojqr2gw*3HAd3|W1N*caeDSlEJxD#2~eMb-$1kWvL zcnoJ(c?ZxjD5fjLe^+*YC@i=HX;?KV4ZG{u<%seWH4R=h4G!->1?U~#16`O|mb&a# z9Ol&e1*Xh9*u)+#^NaV`ZzAi&c$OVC@A6?*6<>FQ2uV0GXHX=^RY-w~dYvbg6Auqb zKk`HG3tcnRwlODpRKO3@2XKe zw(AX3wwrrvCfQJQgv*bq=a%Ne@V@i9;&b{H-*Y3uy~_YN^gi*5=P$uYu;!lac!EzE zhbgM_yG78werY_Z>pvE**9xmhRriChgAN!~D!E5+#n|{z`CvyFD~w5V*@iHbDYpVi z!8_WP`tqfFS(NQpMW|*ci%!xiKo1#RFT0Yw>oTSr|Z?qn|)x`jm-X zwwj%lyJQ)%;fHKowK*nTw%=|8mwDuJ%I%;#04@C1*9mzULk)z2f?$RgO1a{_sKH|> z4!#wwMw7$+9>apD(ICyyI<=Y=YARf?<&SO3{!dy(vlzR`| zLA6F8A|xOpM1y^hQ%Ft$M5MuENeD-25v}CD=b!|OuZ_by=t32=m;#-5H-)P%gNi`V z90kcv>_@-S{qV;Sh{OVNpzDFyacBuZ!5f!Ul{~GoQqMGK7rXO~0ElHMkHjwZ%t-DM zY=&-^3Msx_BEa(WCQ)*q{CqK0bBVlTaO;{47UCo3^N&2zR757K6`R8$tA$nD913f@ z1$DLgAp&8_HiQ>=98s@Jtv(cV?ZeLYJ9EJ-6xlfv{8OX~BytbuaVq+JZbaLLIdKY! z|HJhi$b~!4?<`z&U8}iH;~pxOTsMp!jjLY1v7xc8y~Ezz*imjRklT+mwzf7le_M98 z*SE>-^&QUE0=cQ>g*wFA57##}$#sow^@lo|zRxbFW;Sf&ljSzhwl&w+g~)|{Rn}S_ZMNy@Ii9&=h!9>~k;$K&2isMi&9Pz~7 zq?suiEMR}*td9v_MR+ISt03t(R%#dFr-a9bv177%O>3ftE9B-1x%rCZsdW7zn~{yl zhl#a-5gbX{MW|$AhA}yX*n`BT5&J2#W*A);$ngNNTZlbKvtETOj$-X1yhi$)r00>; z68X+ie!gHP!c2r)BZU2g=M%nz^=B9j*#`ED40H51$!bX$`!FLX zvy2`OQdZMx&N;I6N*K$}%qhe2Vo~}@#xsHY9^z*S=lLrn{0j4A=0-)(({1){X6{oT zkjwitCxKkPMR+t-jtVKX6E26dl&oEb>gDXEtgT`hJDX)pE`zxw7v3g2;7llEx3i3( z`8=yg)qDjTAdZh7(UfOzSJ{n(+9@K{!?}w? zV+&V58sR@+cUWQ%gs|(Q^sJ9q9{D?uuoqWjZ2w&S_2k##c;Gglgd46u#VT`*$-maI zm`~1LVc!MzBOQyCFjonk;Uf5+MD4E6$j2S z!H>^jI$YM_f79VuOEM~?vKMlV$+__Divz7?!jD_q#QJlMu0b7}tz*Y^_-Y+)q|FSi z>Gd0Qc#clLRfp&5@Sujf)Ufbn~Q<7 zOSnrY0s*gC&k`Z%OYp7^x9IfOb+}cBN71py7m${q1-dO1H@LJ~J{v;tW@!q+`HCZH zTU~TODJ=`d$?-EfyhImhrHjs^MMDYN-uLoj@VDt~2-RQE;kXg=jCLo(0}Rx!habmp z5bg>!R6<`6q4@5{;4eQ0@6q8KbdJ3`9C|KMNTU_Mqa#Y$#kHv}JH^P4CJYIG+X<(0 zK*bQ>K{ziS*Y74g9yB;OUQIYP(IWmW4Xs|_U^*bej4hLZK{ReV}EdfJ>WMyKlti|-IL}}yV#kX& W=LN}4FZ^GI16@1?KVCfQTj9UwSBcC3 delta 4030 zcmZ{n4Qx}_702ImQg8_2g)nd&lE(RVLLhb^w5$T`5W{o(0&0`7Od|~+fznZJKohC! zi0qVjcpzi0t38r-aFUa zcp_s>rDzxSMf>||a{4rVMvcvE)Z`g;>W->6p6H))+D8^jl2R!BXZF7Pb9Jh# zPd(~85mhHb*(^u)%cnMv=;8_^GCKsRXr4ckVx?G`QXgovoj@0({F0w&J%uR zcwCJvOjGrUlt?6=jHHjL@-^_a!Y7~mk3RRbds?0F1+hkV6oQ}D^w)1fU`>DXHawI< zoEOB_ePe1Qi+y4FSD>|5A3Os7?ssQP_8DVU(kvYLD^`_=sYmamD?J}su&hTgLO8t_ zsz6mc5c{dN!WUF7T+$;4A&_!mQjc`NODvwPT!`wSm*Cw$G4UQeW|u;SWPE>O!q3{% zo0hd3Zddey{es&IBbU?Pk~hBqs39D|APvg!q*wm8H+IA7?Y<#N2}!@I43DCBLwEpl zgT)oE{F%4=6Dbjplwl*FG92n)znix{&*((h=!6y?@PNG*ZtcU)vgPxyW?@g=pJUg; zgGbnd`B6E}eq(Kyt*kWTexNa^M!NUO(1D{f7FE$EVWop=_-kY8(Yt$;SEI0N24dD& z$1+%mnb)CHDZQkf(~u@fzk(W0DJ5UT$C3G3*9kH4d^t%r(CJc`EglNm-u zadYET^t@t$;}3>Q>q;?Zox_pJuD^E$3WDBM`dGo*zF(GWIWK!wdXn+b3D0z136 z5%*+N?Y{dOcquIAhz0M+9a?hd9Y@Up8w9kT$<7w~q(fBO zAJUU-m!nNS1eMw3kJ+Ccg^Ld^GNtdqzQFMg%P4A-kFn>AYBDxK&t++zw2+-CvhBW^ zZCbnnjl;pf163z^=9yS(C0oUrT(ah2<5mstgiWDa|0s_sD!XJ)dKT zifwiesV*oLN3Qvh?CcDvpV0)KB{iSa57<<(jk!v!-VaHsC1r>_28i=)UBu53uOj;r z)?Z??+>_bal2r@hWOU5JJ}D_Levdw$rwJF3Pap9=z_BhZU-u$?{5zTPBDWKlVZY$u zQ(H&e&h*lX44oR_W^b2Pe6@)_Y2=e*x=Pi=pC!JG_#?zS(pX;ER?D6&_N}sdxsJVA zX0z9U4XuH6itNE$s$Tii8P;EE3)~je zQbFydTLWX33H&9xBan;sS&IR{Kwb`id`5PGW6d-LX{E_?%;f|j-(qfpdiHdcEwD4i zEjQg%J5zWCd3`^Hdx)P*;aiCRJcU;gKWFklvaD(nVkr)l#3uz_Da`wtz;WIx*?Z~0 zVxN%nX*bT{frpK1pCh1ydCo##!gno!s4!9!1O>hVbiP2x1@7cL0H^(3L8#^e@Q(z( zQs8zxKroVtbId}wr*i7V?i9{<#u4B!e<2XfICm*hk(+zMpUNrmE`is8AD{8lbT>XM z)Vz0&J>&s%ZE{TE4V#fyn3>5h34D!^pr0;sOyCyyb%7gePw_X|Q{xBB1a1E(P^DkO zj|;q3NPJ4*V&Y#2e6?V2_8$Pw0RO&0mGf^4&c9BauI8luH;MB#<@RmF=@LoW?;%cG zLJDw)=SZMO(%}FB{>8%Wc|i^+8VBbdvd1e62j?BcE2>txr1lpNwC(+N$KmfvBO7bJ zBeOAA0sGkX1S@qHj6CIDDrfu}&jE~|vFB?yS|;u6Z0%##5|g#CncBzTADg1ppsZlK zSC<#R3|VlC;!=piML(oMY3%IkwV6@++2F{@WN*|K ? */ - {0x7c,0xc6,0xde,0xde,0xde,0xc0,0x78,0}, /* @ */ - {0x38,0x6c,0xc6,0xfe,0xc6,0xc6,0xc6,0},{0xfc,0x66,0x66,0x7c,0x66,0x66,0xfc,0}, /* A B */ - {0x3c,0x66,0xc0,0xc0,0xc0,0x66,0x3c,0},{0xf8,0x6c,0x66,0x66,0x66,0x6c,0xf8,0}, /* C D */ - {0xfe,0x62,0x68,0x78,0x68,0x62,0xfe,0},{0xfe,0x62,0x68,0x78,0x68,0x60,0xf0,0}, /* E F */ - {0x3c,0x66,0xc0,0xc0,0xce,0x66,0x3e,0},{0xc6,0xc6,0xc6,0xfe,0xc6,0xc6,0xc6,0}, /* G H */ - {0x3c,0x18,0x18,0x18,0x18,0x18,0x3c,0},{0x1e,0xc,0xc,0xc,0xcc,0xcc,0x78,0}, /* I J */ - {0xe6,0x66,0x6c,0x78,0x6c,0x66,0xe6,0},{0xf0,0x60,0x60,0x60,0x62,0x66,0xfe,0}, /* K L */ - {0xc6,0xee,0xfe,0xfe,0xd6,0xc6,0xc6,0},{0xc6,0xe6,0xf6,0xde,0xce,0xc6,0xc6,0}, /* M N */ - {0x7c,0xc6,0xc6,0xc6,0xc6,0xc6,0x7c,0},{0xfc,0x66,0x66,0x7c,0x60,0x60,0xf0,0}, /* O P */ - {0x7c,0xc6,0xc6,0xc6,0xc6,0xce,0x7c,0xe},{0xfc,0x66,0x66,0x7c,0x6c,0x66,0xe6,0}, /* Q R */ - {0x7c,0xc6,0xe0,0x78,0xe,0xc6,0x7c,0},{0x7e,0x7e,0x5a,0x18,0x18,0x18,0x3c,0}, /* S T */ - {0xc6,0xc6,0xc6,0xc6,0xc6,0xc6,0x7c,0},{0xc6,0xc6,0xc6,0xc6,0xc6,0x6c,0x38,0}, /* U V */ - {0xc6,0xc6,0xc6,0xd6,0xd6,0xfe,0x6c,0},{0xc6,0xc6,0x6c,0x38,0x38,0x6c,0xc6,0}, /* W X */ - {0x66,0x66,0x66,0x3c,0x18,0x18,0x3c,0},{0xfe,0xc6,0x8c,0x18,0x32,0x66,0xfe,0}, /* Y Z */ - {0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0}, /* [ \ */ - {0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0}, /* ] ^ */ - {0x18,0x18,0x18,0x18,0x18,0x18,0x18,0}, /* _ */ - {0,0,0,0,0,0,0,0}, /* ` */ - /* a-z lowercase — full-height (no top blank rows) so they scale like uppercase */ - {0x7c,0xc6,0xc6,0xfe,0xc6,0xc6,0xc6,0}, /* a */ - {0xc6,0xc6,0xc6,0xfc,0xc6,0xc6,0xfc,0}, /* b */ - {0x7c,0xc6,0xc0,0xc0,0xc0,0xc6,0x7c,0}, /* c */ - {0x06,0x06,0x06,0xfc,0xc6,0xc6,0xfc,0}, /* d */ - {0x7c,0xc6,0xfe,0xc0,0xfc,0xc0,0xc0,0}, /* e */ - {0x7c,0xc6,0xc0,0xf8,0xc0,0xc0,0xc0,0}, /* f */ - {0x7c,0xc6,0xc0,0xde,0xc6,0xc6,0x7c,0}, /* g */ - {0xc6,0xc6,0xc6,0xfe,0xc6,0xc6,0xc6,0}, /* h */ - {0x7e,0x18,0x18,0x18,0x18,0x18,0x18,0}, /* i */ - {0x06,0x06,0x06,0xc6,0xc6,0xc6,0x7c,0}, /* j */ - {0xc6,0xcc,0xd8,0xf0,0xd8,0xcc,0xc6,0}, /* k */ - {0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xfe,0}, /* l */ - {0xc6,0xee,0xfe,0xfe,0xc6,0xc6,0xc6,0}, /* m */ - {0xc6,0xe6,0xf6,0xde,0xce,0xc6,0xc6,0}, /* n */ - {0x7c,0xc6,0xc6,0xc6,0xc6,0xc6,0x7c,0}, /* o */ - {0xc6,0xc6,0xc6,0xfc,0xc0,0xc0,0xc0,0}, /* p */ - {0x7c,0xc6,0xc6,0xce,0x7c,0x06,0x06,0}, /* q */ - {0xc6,0xc6,0xcc,0xf8,0xcc,0xc6,0xc6,0}, /* r */ - {0x7c,0xc0,0x7c,0x06,0xc6,0xc6,0x7c,0}, /* s */ - {0x18,0x18,0x7e,0x18,0x18,0x18,0x18,0}, /* t */ - {0xc6,0xc6,0xc6,0xc6,0xc6,0xc6,0x7c,0}, /* u */ - {0xc6,0xc6,0xc6,0xc6,0xc6,0x6c,0x38,0}, /* v */ - {0xc6,0xc6,0xd6,0xfe,0xee,0xc6,0xc6,0}, /* w */ - {0xc6,0xc6,0x6c,0x38,0x6c,0xc6,0xc6,0}, /* x */ - {0xc6,0xc6,0xc6,0x7c,0x06,0x06,0x7c,0}, /* y */ - {0xfe,0x0c,0x18,0x30,0x60,0xc0,0xfe,0}, /* z */ -}; - -/* ASCII text — thin 8x8 → 10x16, preserves case, draws foreground only (no bg fill) */ -#define ASCII_W 10 -#define ASCII_H 16 -#define ASCII_STEP 12 +/* ASCII font — Consolas 18px grayscale, pre-rendered from C:\Windows\Fonts\consola.ttf */ +#include "ascii_bitmaps.c" +/* ASCII text — Consolas 18px grayscale, alpha-blend rendered (skips transparent pixels) */ +#define ASCII_W 11 +#define ASCII_H 18 +#define ASCII_STEP 11 static void fb_char(u32 *fb, u32 ch, i64 x, i64 y, u32 fg, u32 bg) { - u32 idx = ch >= ' ' && ch <= 'z' ? (u32)(ch-' ') : 0; - const u8 *g = font[idx]; - u8 pat[8][8]; - for (i64 r=0; r<8; r++) { - u8 row = g[r]; - for (i64 c=0; c<8; c++) { - pat[r][c] = (row & (0x80>>c)) ? 1 : 0; - } - } - for (i64 sy=0; sy '~') ch = ' '; + u32 idx = (u32)(ch - ' '); + const u8 *g = g_ascii[idx]; + for (i64 r=0; r= fb_h) continue; u32 *line = (u32 *)((u8 *)fb + (u64)yy * fb_p); - i64 src_y = sy * 7 / (ASCII_H - 1); - if (src_y > 7) src_y = 7; - for (i64 sx=0; sx 7) src_x = 7; - int on = pat[src_y][src_x]; - int edge = 0; - if (!on) { - if (src_x > 0 && pat[src_y][src_x-1]) edge = 1; - if (src_x < 7 && pat[src_y][src_x+1]) edge = 1; - if (src_y > 0 && pat[src_y-1][src_x]) edge = 1; - if (src_y < 7 && pat[src_y+1][src_x]) edge = 1; - if (!edge) continue; - } - i64 xx = x + sx; + for (i64 c=0; c= fb_w) continue; - line[(u64)xx] = on ? fg : blend(bg, fg, 72); + line[(u64)xx] = (a == 255) ? fg : blend(bg, fg, a); } } } @@ -352,7 +272,7 @@ static void draw_rounded_input(u32 *fb, i64 x, i64 y, i64 w, i64 h, const char * char out[64]; int i=0; while(value[i] && i<60) { out[i] = mask ? '*' : value[i]; i++; } out[i]=0; - if (out[0]) fb_text(fb, out, x+14, y+(h-16)/2, fg, fill); + if (out[0]) fb_text(fb, out, x+14, y+(h-ASCII_H)/2, fg, fill); if (active) { i64 cx2 = x + 14 + i * ASCII_STEP; fill_rect(fb, cx2, y+10, 2, h-20, fg); @@ -704,43 +624,202 @@ static void read_prefs_page(u32 *fb, i64 card_x, i64 card_y, setup_prefs *prefs, } typedef struct { - int mode; - int device; - int ip; - int dns; + int selected; + int connected; + char ssid[32]; + char password[64]; } setup_net; -static const char *net_value(int row, int val) { - if (row == 0) return val == 0 ? "Disabled" : (val == 1 ? "DHCP" : "Manual"); - if (row == 1) return val == 0 ? "Auto" : (val == 1 ? "e1000" : "virtio-net"); - if (row == 2) return val ? "Manual" : "Auto DHCP"; - if (row == 3) return val ? "Manual DNS" : "Auto DNS"; - return ""; +/* UI 显示行数(固定 4 行布局);真实扫描结果数由 g_wifi_scan_count 决定。 */ +#define WIFI_SCAN_COUNT 4 +#define WIFI_MAX_RESULTS 8 + +/* 真实 WiFi 扫描结果(来自 ath9k 驱动通过 kernel_api.net.scan_* 查询)。 */ +static char g_wifi_ssids[WIFI_MAX_RESULTS][34]; +static char g_wifi_meta[WIFI_MAX_RESULTS][20]; +static int g_wifi_scan_count; +static int g_wifi_scan_tried; /* 是否已尝试查询扫描结果 */ +static int g_no_wireless_device; /* 检测到无无线设备时跳过 WiFi 页面 */ + +/* dkm_net_api 函数指针类型(通过偏移访问 kernel_api.net)。 */ +typedef u32 (*net_device_count_fn)(void); +typedef int (*net_device_info_fn)(u32 index, void *out); +typedef int (*net_scan_start_fn)(u32 index); +typedef int (*net_scan_count_fn)(u32 index); +typedef int (*net_scan_result_fn)(u32 index, u32 n, void *out); +typedef int (*net_is_wireless_fn)(u32 index); + +/* dkm_net_scan_result 布局(须与 UTSM/include/utsm/net.h 一致)。 */ +typedef struct { + char ssid[33]; + u8 bssid[6]; + u8 channel; + i8 rssi; + u8 security; +} net_scan_result; + +#define DKM_NET_SEC_OPEN 0 +#define DKM_NET_SEC_WEP 1 +#define DKM_NET_SEC_WPA 2 +#define DKM_NET_SEC_WPA2 3 +#define DKM_NET_SEC_WPA3 4 + +/* 把加密类型 + RSSI 转为 "WPA2 strong" 这种简短描述。 */ +static void wifi_meta_str(char *out, u32 cap, u8 security, i8 rssi) { + if (!cap) return; + const char *sec; + switch (security) { + case DKM_NET_SEC_WPA2: sec = "WPA2"; break; + case DKM_NET_SEC_WPA: sec = "WPA "; break; + case DKM_NET_SEC_WPA3: sec = "WPA3"; break; + case DKM_NET_SEC_WEP: sec = "WEP "; break; + default: sec = "Open"; break; + } + /* RSSI 分级:>=-50 strong, -51..-65 good, -66..-75 weak, else very weak */ + const char *q; + i8 r = rssi; + if (r >= -50) q = "strong"; + else if (r >= -65) q = "good"; + else if (r >= -75) q = "weak"; + else q = "very weak"; + /* 手动拼接 "SEC quality" */ + u32 i = 0; + while (sec[i] && i + 1 < cap) { out[i] = sec[i]; i++; } + while (i < 5 && i + 1 < cap) { out[i++] = ' '; } + u32 j = 0; + while (q[j] && i + 1 < cap) { out[i++] = q[j++]; } + out[i] = 0; } -static int net_get(const setup_net *n, int row) { - if (row == 0) return n->mode; - if (row == 1) return n->device; - if (row == 2) return n->ip; - if (row == 3) return n->dns; - return 0; +/* 通过 kernel_api 查询无线设备并读取扫描结果。 + * kernel_api 偏移布局: + * +0x48 net_api 指针 + * net_api 偏移布局(追加 scan 接口后): + * +0 register_device, +8 device_count, +16 device_info, + * +24 tx, +32 rx_poll, +40 scan_start, +48 scan_count, + * +56 scan_result, +64 is_wireless + */ +static void wifi_query_scan(u64 kernel_api) { + g_wifi_scan_tried = 1; + g_wifi_scan_count = 0; + if (!kernel_api) { logl("[FirstInit] no kernel_api, wifi scan skipped"); return; } + u64 net_api = *(u64 *)(kernel_api + 0x48); + if (!net_api) { logl("[FirstInit] no net_api, wifi scan skipped"); return; } + net_device_count_fn dcount = (net_device_count_fn)*(u64 *)(net_api + 8); + net_scan_start_fn sstart = (net_scan_start_fn)*(u64 *)(net_api + 40); + net_scan_count_fn scount = (net_scan_count_fn)*(u64 *)(net_api + 48); + net_scan_result_fn sresult = (net_scan_result_fn)*(u64 *)(net_api + 56); + net_is_wireless_fn iswl = (net_is_wireless_fn)*(u64 *)(net_api + 64); + if (!dcount || !iswl || !scount || !sresult) { + logl("[FirstInit] net scan api incomplete"); + return; + } + u32 ndev = dcount(); + /* 串口日志:设备数 */ + { + char b[24]; int p=0; + const char *s="[FirstInit] net devices="; + while(s[p]) b[p]=s[p], p++; + b[p++]='0'+(ndev/10); b[p++]='0'+(ndev%10); b[p]=0; + logl(b); + } + /* 找第一个无线设备 */ + int wifi_idx = -1; + for (u32 i = 0; i < ndev; i++) { + int rc = iswl(i); + if (rc == 1) { wifi_idx = (int)i; break; } + } + if (wifi_idx < 0) { + logl("[FirstInit] no wireless device found"); + g_no_wireless_device = 1; + return; + } + /* ath9k 驱动在 driver_init 时已同步扫描,但保险起见若 count=0 再触发一次 */ + int count = scount((u32)wifi_idx); + if (count <= 0 && sstart) { + logl("[FirstInit] triggering wifi scan..."); + int rc = sstart((u32)wifi_idx); + { + char b[32]; int p=0; + const char *s="[FirstInit] scan_start rc="; + while(s[p]) b[p]=s[p], p++; + if (rc < 0) { b[p++]='-'; b[p++]='0'+(-rc); } else { b[p++]='0'+rc; } + b[p]=0; logl(b); + } + count = scount((u32)wifi_idx); + } + if (count <= 0) { + logl("[FirstInit] wifi scan: no results"); + return; + } + if (count > WIFI_MAX_RESULTS) count = WIFI_MAX_RESULTS; + /* 读取每个扫描结果 */ + for (int i = 0; i < count; i++) { + net_scan_result r; + for (u32 k = 0; k < sizeof(r); k++) ((u8 *)&r)[k] = 0; + int rc = sresult((u32)wifi_idx, (u32)i, &r); + if (rc != 0) continue; + /* 拷贝 SSID */ + u32 j = 0; + while (r.ssid[j] && j + 1 < sizeof(g_wifi_ssids[i])) { + g_wifi_ssids[i][j] = r.ssid[j]; j++; + } + g_wifi_ssids[i][j] = 0; + /* 生成 meta 字符串 */ + wifi_meta_str(g_wifi_meta[i], sizeof(g_wifi_meta[i]), r.security, r.rssi); + g_wifi_scan_count++; + } + { + char b[32]; int p=0; + const char *s="[FirstInit] wifi scan results="; + while(s[p]) b[p]=s[p], p++; + b[p++]='0'+(g_wifi_scan_count/10); b[p++]='0'+(g_wifi_scan_count%10); b[p]=0; + logl(b); + } } -static void net_toggle(setup_net *n, int row) { - if (row == 0) n->mode = (n->mode + 1) % 3; - else if (row == 1) n->device = (n->device + 1) % 3; - else if (row == 2) n->ip ^= 1; - else if (row == 3) n->dns ^= 1; +static void net_copy(char *dst, u32 cap, const char *src) { + if (!cap) return; + u32 i = 0; + while (src[i] && i + 1 < cap) { + dst[i] = src[i]; + i++; + } + dst[i] = 0; } -static void draw_network_row(u32 *fb, i64 card_x, i64 card_y, setup_net *net, int row, int active, u32 fg) { +static void net_select(setup_net *net, int idx) { + if (idx < 0) idx = 0; + if (idx >= WIFI_SCAN_COUNT) idx = WIFI_SCAN_COUNT - 1; + if (net->selected != idx) { + net->password[0] = 0; + net->connected = 0; + } + net->selected = idx; + if (idx < g_wifi_scan_count) { + net_copy(net->ssid, sizeof(net->ssid), g_wifi_ssids[idx]); + } else { + net->ssid[0] = 0; + } +} + +static void draw_wifi_row(u32 *fb, i64 card_x, i64 card_y, setup_net *net, int row, int active, u32 fg) { u32 card = CARD_BG; - i64 ry = card_y + 150 + (i64)row * 88; - const u8 *label = g_txt_net_mode; i64 lw = g_txt_net_mode_w, lh = g_txt_net_mode_h; - if (row == 1) { label = g_txt_net_device; lw = g_txt_net_device_w; lh = g_txt_net_device_h; } - else if (row == 2) { label = g_txt_net_ip; lw = g_txt_net_ip_w; lh = g_txt_net_ip_h; } - else if (row == 3) { label = g_txt_net_dns; lw = g_txt_net_dns_w; lh = g_txt_net_dns_h; } - draw_option_row(fb, card_x+40, ry, 820, 42, label, lw, lh, net_value(row, net_get(net, row)), active==row, fg, card); + i64 x = card_x + 40; + i64 y = card_y + 132 + (i64)row * 64; + u32 fill = active == row ? 0xFFFFFFFF : (net->selected == row ? 0xFFDCECF8 : INPUT_BG); + fill_rounded_rect(fb, x, y, 390, 50, 9, fill); + stroke_rounded_rect(fb, x, y, 390, 50, 9, 1, active == row ? 0xFF7FA7C8 : card); + fb_text(fb, net->selected == row ? ">" : " ", x + 12, y + 16, fg, fill); + if (row < g_wifi_scan_count) { + fb_text(fb, g_wifi_ssids[row], x + 42, y + 8, fg, fill); + fb_text(fb, g_wifi_meta[row], x + 42, y + 28, 0xFF7F97AC, fill); + } else { + /* 无扫描结果时的占位行 */ + const char *placeholder = (g_wifi_scan_tried && g_wifi_scan_count == 0) + ? "No networks found" : "(empty)"; + fb_text(fb, placeholder, x + 42, y + 16, 0xFF9AACC0, fill); + } } static void redraw_network_card(u32 *fb, i64 card_x, i64 card_y, setup_net *net, int active, i64 mx, i64 my, u32 bg, u32 fg) { @@ -749,23 +828,42 @@ static void redraw_network_card(u32 *fb, i64 card_x, i64 card_y, setup_net *net, fill_gradient_rect(fb, card_x - 24, card_y - 24, 948, 648); draw_card(fb, card_x, card_y, 900, 600, card, bg); fb_bitmap_alpha(fb, g_txt_network_title, g_txt_network_title_w, g_txt_network_title_h, card_x+40, card_y+40, fg, card, 255); - for (int r=0; r<4; r++) draw_network_row(fb, card_x, card_y, net, r, active, fg); - fb_bitmap_alpha(fb, g_txt_net_hint, g_txt_net_hint_w, g_txt_net_hint_h, card_x+40, card_y+555, 0xFF7F97AC, card, 255); - cursor_draw(fb, mx, my); -} + fb_text(fb, "Choose a network", card_x + 40, card_y + 88, 0xFF7F97AC, card); + for (int r=0; rselected; + if (preview < g_wifi_scan_count) { + fb_text(fb, g_wifi_ssids[preview], card_x + 494, card_y + 182, fg, INPUT_BG); + fb_text(fb, g_wifi_meta[preview], card_x + 494, card_y + 210, 0xFF7F97AC, INPUT_BG); + } else { + fb_text(fb, g_wifi_scan_tried ? "No networks found" : "WiFi not initialized", + card_x + 494, card_y + 182, 0xFF9AACC0, INPUT_BG); + fb_text(fb, g_wifi_scan_tried ? "Check ath9k driver" : "Run ath9k driver first", + card_x + 494, card_y + 210, 0xFF9AACC0, INPUT_BG); + } + fb_text(fb, "Use Up/Down or click menu", card_x + 494, card_y + 260, 0xFF7F97AC, INPUT_BG); + fb_text(fb, "Enter selects highlighted item", card_x + 494, card_y + 288, 0xFF7F97AC, INPUT_BG); + + fb_text(fb, "Password", card_x + 470, card_y + 400, fg, card); + draw_rounded_input(fb, card_x + 470, card_y + 430, 390, 44, net->password, 1, fg, card, active == WIFI_SCAN_COUNT); + u32 btn = active == WIFI_SCAN_COUNT + 1 ? 0xFFFFFFFF : 0xFFDCECF8; + fill_rounded_rect(fb, card_x + 620, card_y + 500, 240, 48, 10, btn); + stroke_rounded_rect(fb, card_x + 620, card_y + 500, 240, 48, 10, 1, active == WIFI_SCAN_COUNT + 1 ? 0xFFB6C9DD : card); + fb_text(fb, "Connect", card_x + 690, card_y + 516, fg, btn); + if (net->connected) fb_text(fb, "Connection profile saved", card_x + 40, card_y + 515, 0xFF4F8A5F, card); + else fb_text(fb, "Select SSID, enter password, then Connect", card_x + 40, card_y + 555, 0xFF7F97AC, card); cursor_draw(fb, mx, my); } static void read_network_page(u32 *fb, i64 card_x, i64 card_y, setup_net *net, i64 *mx, i64 *my, u32 bg, u32 fg) { - int active = 0, mcnt = 0, e0 = 0; + int active = 0, mcnt = 0, e0 = 0, shift = 0; u8 mpkt[3]; u8 prev_btns = 0; + int len = 0; + while (net->password[len]) len++; + net_select(net, net->selected); redraw_network_card(fb, card_x, card_y, net, active, *mx, *my, bg, fg); for (;;) { u8 st = inb(0x64); @@ -787,28 +885,79 @@ static void read_network_page(u32 *fb, i64 card_x, i64 card_y, setup_net *net, i cursor_draw(fb, *mx, *my); } if ((btns & 1) && !(prev_btns & 1)) { - for (int r=0; r<4; r++) { - i64 ry = card_y + 150 + (i64)r * 88; - if (*mx >= card_x + 260 && *mx < card_x + 860 && *my >= ry && *my < ry + 42) { - int old = active; active = r; net_toggle(net, active); - if (old != active) redraw_network_row(fb, card_x, card_y, net, old, active, *mx, *my, fg); - redraw_network_row(fb, card_x, card_y, net, active, active, *mx, *my, fg); + for (int r=0; r= card_x + 40 && *mx < card_x + 430 && *my >= ry && *my < ry + 50) { + active = r; + net_select(net, r); + len = 0; + redraw_network_card(fb, card_x, card_y, net, active, *mx, *my, bg, fg); } } + if (*mx >= card_x + 470 && *mx < card_x + 860 && *my >= card_y + 430 && *my < card_y + 474) { + active = WIFI_SCAN_COUNT; + redraw_network_card(fb, card_x, card_y, net, active, *mx, *my, bg, fg); + } + if (*mx >= card_x + 620 && *mx < card_x + 860 && *my >= card_y + 500 && *my < card_y + 548) { + net->connected = 1; + logl("[FirstInit] wireless profile selected"); + redraw_network_card(fb, card_x, card_y, net, WIFI_SCAN_COUNT + 1, *mx, *my, bg, fg); + for (int i=0;i<30;i++) delay_frame(); + return; + } } prev_btns = btns; continue; } u8 sc = data; if (sc == 0xE0) { e0 = 1; continue; } + if (sc == 0x2A || sc == 0x36) { shift = 1; continue; } + if (sc == 0xAA || sc == 0xB6) { shift = 0; continue; } if (sc & 0x80) { e0 = 0; continue; } - if (e0 && sc == 0x48) { if (active > 0) { int old=active; active--; redraw_network_row(fb, card_x, card_y, net, old, active, *mx, *my, fg); redraw_network_row(fb, card_x, card_y, net, active, active, *mx, *my, fg); } e0=0; continue; } - if (e0 && sc == 0x50) { if (active < 3) { int old=active; active++; redraw_network_row(fb, card_x, card_y, net, old, active, *mx, *my, fg); redraw_network_row(fb, card_x, card_y, net, active, active, *mx, *my, fg); } e0=0; continue; } + if (e0 && sc == 0x48) { + if (active > 0) active--; + redraw_network_card(fb, card_x, card_y, net, active, *mx, *my, bg, fg); + e0=0; continue; + } + if (e0 && sc == 0x50) { + if (active < WIFI_SCAN_COUNT + 1) active++; + redraw_network_card(fb, card_x, card_y, net, active, *mx, *my, bg, fg); + e0=0; continue; + } e0 = 0; - if (sc == 0x39 || sc == 0x4D || sc == 0x4B) { net_toggle(net, active); redraw_network_row(fb, card_x, card_y, net, active, active, *mx, *my, fg); continue; } - if (sc == 0x1C) { - if (active < 3) { int old=active; active++; redraw_network_row(fb, card_x, card_y, net, old, active, *mx, *my, fg); redraw_network_row(fb, card_x, card_y, net, active, active, *mx, *my, fg); } - else return; + char c = scan_to_ascii(sc, shift); + if (!c) continue; + if (c == '\n') { + if (active < WIFI_SCAN_COUNT) { + net_select(net, active); + len = 0; + active = WIFI_SCAN_COUNT; + } else if (active == WIFI_SCAN_COUNT) { + active = WIFI_SCAN_COUNT + 1; + } else { + net->connected = 1; + logl("[FirstInit] wireless profile selected"); + redraw_network_card(fb, card_x, card_y, net, active, *mx, *my, bg, fg); + for (int i=0;i<30;i++) delay_frame(); + return; + } + redraw_network_card(fb, card_x, card_y, net, active, *mx, *my, bg, fg); + continue; + } + if (c == '\t') { + active = (active + 1) % (WIFI_SCAN_COUNT + 2); + redraw_network_card(fb, card_x, card_y, net, active, *mx, *my, bg, fg); + continue; + } + if (active == WIFI_SCAN_COUNT) { + if (c == 8) { + if (len > 0) net->password[--len] = 0; + } else if (len < 63 && c >= 32 && c <= 126) { + net->password[len++] = c; + net->password[len] = 0; + } + net->connected = 0; + redraw_network_card(fb, card_x, card_y, net, active, *mx, *my, bg, fg); } } } @@ -854,20 +1003,20 @@ static void build_user_conf(const char *pc, const char *user, const char *pass, const char *th0="\ntheme="; for(int i=0;th0[i];i++) conf[p++]=th0[i]; const char *v4 = prefs->theme ? "dark" : "light"; for(int i=0;v4[i];i++) conf[p++]=v4[i]; const char *nm="\nnetwork.mode="; for(int i=0;nm[i];i++) conf[p++]=nm[i]; - const char *nv0 = net->mode == 0 ? "disabled" : (net->mode == 1 ? "dhcp" : "manual"); for(int i=0;nv0[i];i++) conf[p++]=nv0[i]; - const char *nd="\nnetwork.device="; for(int i=0;nd[i];i++) conf[p++]=nd[i]; - const char *nv1 = net->device == 0 ? "auto" : (net->device == 1 ? "e1000" : "virtio-net"); for(int i=0;nv1[i];i++) conf[p++]=nv1[i]; - const char *ni="\nnetwork.ip="; for(int i=0;ni[i];i++) conf[p++]=ni[i]; - const char *nv2 = net->ip ? "manual" : "dhcp"; for(int i=0;nv2[i];i++) conf[p++]=nv2[i]; - const char *dns="\nnetwork.dns="; for(int i=0;dns[i];i++) conf[p++]=dns[i]; - const char *nv3 = net->dns ? "manual" : "auto"; for(int i=0;nv3[i];i++) conf[p++]=nv3[i]; + const char *nv0 = net->connected ? "wifi" : "disabled"; for(int i=0;nv0[i];i++) conf[p++]=nv0[i]; + const char *ns="\nnetwork.ssid="; for(int i=0;ns[i];i++) conf[p++]=ns[i]; + for(int i=0;net->ssid[i]&&p<740;i++) conf[p++]=net->ssid[i]; + const char *np="\nnetwork.password="; for(int i=0;np[i];i++) conf[p++]=np[i]; + for(int i=0;net->password[i]&&p<740;i++) conf[p++]=net->password[i]; + const char *ni="\nnetwork.ip=dhcp"; for(int i=0;ni[i];i++) conf[p++]=ni[i]; + const char *dns="\nnetwork.dns=auto"; for(int i=0;dns[i];i++) conf[p++]=dns[i]; conf[p++]='\n'; /* XOR encrypt buffer with hash-derived stream; real disk write waits for FAT32 write support. */ for(int i=0;idkm_kernel_api); setup_net net; - net.mode = 0; /* Disabled */ - net.device = 0; /* Auto */ - net.ip = 0; /* Auto DHCP */ - net.dns = 0; /* Auto DNS */ - read_network_page(fb, card_x, card_y, &net, &mx, &my, bg, fg); + net.selected = 0; + net.connected = 0; + net.ssid[0] = 0; + net.password[0] = 0; + if (g_no_wireless_device) { + /* QEMU 或无无线网卡场景:跳过 WiFi 设置页面,network.mode=disabled */ + logl("[FirstInit] no wireless device, skipping WiFi setup page"); + net.connected = 0; + } else { + cursor_bg_valid = 0; + cursor_cur_x = -100; + cursor_cur_y = -100; + logl("[FirstInit] entering wireless setup page"); + read_network_page(fb, card_x, card_y, &net, &mx, &my, bg, fg); + logl("[FirstInit] wireless setup page returned"); + } build_user_conf(pc, user, pass, &prefs, &net); cursor_erase(fb); @@ -978,5 +1140,6 @@ void dsk_entry(const dsk_boot_context *ctx) { fb_bitmap_alpha(fb, g_txt_done, g_txt_done_w, g_txt_done_h, cx - g_txt_done_w/2, cy - 20, fg, bg_at_y(cy), 255); logl("[FirstInit] user setup finished"); - for(;;) __asm__("hlt"); + /* 短暂停留后返回,由 DSK 接管加载 shell */ + for (int i = 0; i < 60; i++) delay_frame(); } diff --git a/CODE/firstInit/main.o b/CODE/firstInit/main.o index 082e6abf73fc30f227e006501a3c3238da5f0d41..582fbfa4470ebf6add2f6f09c6b6d9a8565da2fe 100644 GIT binary patch delta 42144 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 B&N%=8 delta 32088 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 diff --git a/CODE/firstInit/text_bitmaps.o b/CODE/firstInit/text_bitmaps.o deleted file mode 100644 index 44883d4a8f9207f991cbf9df9bda8fef7d806f17..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 744 zcmb_YJx{|h5Ovy$GBPq|EJK|pZIT8Fl%=o~Hs;E9;xv#iC8mN2eoe(s;&1Q^id}4K zxs06Tym#;3*>`zcEN^<21yoqz8XPjE0x;6VcrV9bD4e7~Rc3F}w#)0v_B`mqz#gp` zYr0Fv$#S-S#*oydQxuz@&$wyp$CNOek{Loti2VdlDWx;dO=yIoU>XJ!A4dfFK@iU8 z?!{q@BL=xL0AIV~0!v>Q5HFW$#Yt7O6yDxHj5~(N6M$lz6-p95cLs!ag>zP-rh^2) zj^TTRqXZ>9u*SzueUv-ud|rwZ9V7erErI8+yv5;fLgEFVQD_r&2$c_zil$M(F?m diff --git a/CODE/font/render_ascii.py b/CODE/font/render_ascii.py new file mode 100644 index 0000000..350ca87 --- /dev/null +++ b/CODE/font/render_ascii.py @@ -0,0 +1,54 @@ +"""render_ascii.py — render ASCII 32-126 from Consolas TTF to grayscale bitmaps +Output: CODE/firstInit/ascii_bitmaps.c (included by main.c) + +Glyph cell: 11x18. Consolas 18px has advance=10, ascender top ~3, descender ~18. +ASCII_STEP=11 gives 1px spacing between glyphs (10px glyph + 1px gap). +""" +from PIL import Image, ImageFont, ImageDraw + +ttf = r"C:\Windows\Fonts\consola.ttf" +GLYPH_W = 11 +GLYPH_H = 18 +FONT_SIZE = 18 + +font = ImageFont.truetype(ttf, FONT_SIZE) + +# Verify metrics on first glyph +probe = font.getbbox("A") +print(f"Consolas {FONT_SIZE}px — 'A' bbox={probe}, advance={font.getlength('A')}") + +lines = [] +lines.append("/* Auto-generated by render_ascii.py - Consolas 18px grayscale */") +lines.append("/* 95 glyphs: ASCII 32 (space) .. 126 (~), cell 11x18 */") +lines.append("/* Do not edit - regenerate via: py render_ascii.py */") +lines.append("") +lines.append(f"static const int g_ascii_w = {GLYPH_W};") +lines.append(f"static const int g_ascii_h = {GLYPH_H};") +lines.append(f"static const unsigned char g_ascii[95][{GLYPH_W}*{GLYPH_H}] = {{") + +for cp in range(32, 127): + ch = chr(cp) + img = Image.new("L", (GLYPH_W, GLYPH_H), 0) + draw = ImageDraw.Draw(img) + draw.text((0, 0), ch, font=font, fill=255) + pixels = list(img.tobytes()) + if ch.isalnum(): + comment = ch + elif ch == ' ': + comment = "space" + else: + comment = f"0x{cp:02x}" + lines.append(f" /* '{comment}' */ {{") + for y_row in range(GLYPH_H): + row = ", ".join(str(pixels[y_row * GLYPH_W + x_col]) for x_col in range(GLYPH_W)) + lines.append(f" {row},") + lines.append(" },") + +lines.append("};") +lines.append("") + +out_path = "CODE/firstInit/ascii_bitmaps.c" +with open(out_path, "w", encoding="ascii") as f: + f.write("\n".join(lines)) + +print(f"Generated {out_path} ({95*GLYPH_W*GLYPH_H} bytes of glyph data)") diff --git a/CODE/netman/main.c b/CODE/netman/main.c index 3e02cd5..63a4782 100644 --- a/CODE/netman/main.c +++ b/CODE/netman/main.c @@ -1,4 +1,4 @@ -/* netman — Deshab network manager initializer +/* netman — Deshab network manager initializer * 读取 /system/deshab64/network/conf/conf.conf(SATA 早期镜像里为 root 目录 NETCONF.CNF), * 解析网络配置;枚举内核 netdev 注册表;对具备收发能力的接口通过 e1000 真实收发 * 跑最小 DHCP 客户端拿到 IP。 @@ -50,6 +50,14 @@ static void log_hex(const char *p, u64 v) { static void *memset_nm(void *d, int c, u64 n) { u8 *p=(u8*)d; while(n--)*p++=(u8)c; return d; } static int streqn(const char *a, const char *b, u32 n) { for(u32 i=0;i> 16) sum = (sum & 0xffff) + (sum >> 16); + u16 out = (u16)(~sum); + return out ? out : 0xffff; +} + static void delay_spin(u32 loops) { - for (volatile u32 i=0;i>8); ip[11]=(u8)(ipck); + u16 udpck = udp_checksum_ipv4(ip, udp, udp_len); + udp[6]=(u8)(udpck>>8); udp[7]=(u8)(udpck); + u32 frame_len = 14 + ip_len; if (frame_len < 60) frame_len = 60; return g_net_tx(index, g_pktbuf, frame_len); } /* 解析收到的帧,若为本机 xid 的 DHCP 回复则返回 message type。 */ -static int dhcp_parse(const u8 *frame, u32 len, u32 *out_yiaddr, u32 *out_server) { +static int dhcp_parse(const u8 *frame, u32 len, u32 *out_yiaddr, u32 *out_server, u32 *out_router, u32 *out_dns) { if (len < 14+20+8+240) return -1; if (frame[12]!=0x08 || frame[13]!=0x00) return -1; const u8 *ip = frame + 14; if ((ip[0]>>4)!=4) return -1; u32 ihl = (ip[0]&0xf)*4; + if (ihl < 20 || len < 14 + ihl + 8 + 240) return -1; + u16 ip_total = (u16)((ip[2]<<8)|ip[3]); + if (ip_total < ihl + 8 + 240 || len < 14 + ip_total) return -1; if (ip[9]!=17) return -1; const u8 *udp = ip + ihl; + u16 udp_len = (u16)((udp[4]<<8)|udp[5]); + if (udp_len < 8 + 240 || udp_len > ip_total - ihl) return -1; u16 dport = (u16)((udp[2]<<8)|udp[3]); if (dport != 68) return -1; const u8 *dhcp = udp + 8; @@ -309,7 +340,9 @@ static int dhcp_parse(const u8 *frame, u32 len, u32 *out_yiaddr, u32 *out_server u32 o = 240; u8 msg_type = 0; u32 server = 0; - u32 cap = len - 14; + u32 router = 0; + u32 dns = 0; + u32 cap = udp_len - 8; while (o < cap) { u8 code = dhcp[o++]; if (code == 255) break; @@ -320,10 +353,16 @@ static int dhcp_parse(const u8 *frame, u32 len, u32 *out_yiaddr, u32 *out_server if (code == 53 && l >= 1) msg_type = dhcp[o]; if (code == 54 && l >= 4) server = ((u32)dhcp[o]<<24)|((u32)dhcp[o+1]<<16)|((u32)dhcp[o+2]<<8)|dhcp[o+3]; + if (code == 3 && l >= 4) + router = ((u32)dhcp[o]<<24)|((u32)dhcp[o+1]<<16)|((u32)dhcp[o+2]<<8)|dhcp[o+3]; + if (code == 6 && l >= 4) + dns = ((u32)dhcp[o]<<24)|((u32)dhcp[o+1]<<16)|((u32)dhcp[o+2]<<8)|dhcp[o+3]; o += l; } *out_yiaddr = yi; *out_server = server; + if (out_router) *out_router = router; + if (out_dns) *out_dns = dns; return msg_type; } @@ -350,60 +389,198 @@ static void log_ipv4(const char *pre, u32 ip_hostorder) { static int dhcp_run(u32 index, const u8 mac[6]) { if (!g_net_tx || !g_net_rx_poll) { logl("[netman] DHCP: net tx/rx api missing"); return -1; } for (int i=0;i<6;i++) g_local_mac[i]=mac[i]; - g_lease_ip = 0; g_server_ip = 0; + g_lease_ip = 0; g_server_ip = 0; g_gateway_ip = 0; g_dns_ip = 0; - logl("[netman] DHCP: sending DISCOVER"); - int txrc = dhcp_send(index, 1, 0, 0); - log_hex("[netman] DHCP DISCOVER tx rc=", (u64)(i8)txrc); - if (txrc != 0) { logl("[netman] DHCP: DISCOVER tx failed"); return -1; } - - u32 offer_ip = 0, offer_srv = 0; + u32 offer_ip = 0, offer_srv = 0, offer_router = 0, offer_dns = 0; int got_offer = 0; u32 rx_frames = 0; - for (u32 attempt=0; attempt<40000 && !got_offer; attempt++) { - u32 rlen = 0; - int rc = g_net_rx_poll(index, g_rxbuf, sizeof(g_rxbuf), &rlen); - if (rc == 0 && rlen > 0) { - rx_frames++; - int t = dhcp_parse(g_rxbuf, rlen, &offer_ip, &offer_srv); - if (t == 2) { got_offer = 1; break; } /* OFFER */ + for (u32 round=0; round<4 && !got_offer; round++) { + logl("[netman] DHCP: sending DISCOVER"); + int txrc = dhcp_send(index, 1, 0, 0); + log_hex("[netman] DHCP DISCOVER tx rc=", (u64)(i8)txrc); + if (txrc != 0) { logl("[netman] DHCP: DISCOVER tx failed"); return -1; } + + for (u32 attempt=0; attempt<120000 && !got_offer; attempt++) { + u32 rlen = 0; + int rc = g_net_rx_poll(index, g_rxbuf, sizeof(g_rxbuf), &rlen); + if (rc == 0 && rlen > 0) { + rx_frames++; + int t = dhcp_parse(g_rxbuf, rlen, &offer_ip, &offer_srv, &offer_router, &offer_dns); + if (t == 2) { got_offer = 1; break; } + } + delay_spin(300); } - delay_spin(200); } log_hex("[netman] DHCP rx frames seen=", rx_frames); if (!got_offer) { logl("[netman] DHCP: no OFFER received"); return -2; } log_ipv4("[netman] DHCP OFFER ip=", offer_ip); - logl("[netman] DHCP: sending REQUEST"); - if (dhcp_send(index, 3, offer_ip, offer_srv) != 0) { logl("[netman] DHCP: REQUEST tx failed"); return -3; } - - u32 ack_ip=0, ack_srv=0; + u32 ack_ip=0, ack_srv=0, ack_router=0, ack_dns=0; int got_ack=0; - for (u32 attempt=0; attempt<40000 && !got_ack; attempt++) { - u32 rlen=0; - int rc = g_net_rx_poll(index, g_rxbuf, sizeof(g_rxbuf), &rlen); - if (rc == 0 && rlen > 0) { - int t = dhcp_parse(g_rxbuf, rlen, &ack_ip, &ack_srv); - if (t == 5) { got_ack=1; break; } /* ACK */ + for (u32 round=0; round<4 && !got_ack; round++) { + logl("[netman] DHCP: sending REQUEST"); + if (dhcp_send(index, 3, offer_ip, offer_srv) != 0) { logl("[netman] DHCP: REQUEST tx failed"); return -3; } + + for (u32 attempt=0; attempt<120000 && !got_ack; attempt++) { + u32 rlen=0; + int rc = g_net_rx_poll(index, g_rxbuf, sizeof(g_rxbuf), &rlen); + if (rc == 0 && rlen > 0) { + int t = dhcp_parse(g_rxbuf, rlen, &ack_ip, &ack_srv, &ack_router, &ack_dns); + if (t == 5) { got_ack=1; break; } + } + delay_spin(300); } - delay_spin(200); } if (!got_ack) { logl("[netman] DHCP: no ACK received"); return -4; } g_lease_ip = ack_ip; g_server_ip = ack_srv; + g_gateway_ip = ack_router ? ack_router : (offer_router ? offer_router : 0x0a000202u); + g_dns_ip = ack_dns ? ack_dns : (offer_dns ? offer_dns : g_gateway_ip); log_ipv4("[netman] DHCP ACK, leased ip=", ack_ip); log_ipv4("[netman] DHCP server=", ack_srv); + log_ipv4("[netman] gateway=", g_gateway_ip); + log_ipv4("[netman] dns=", g_dns_ip); logl("[netman] DHCP: connected"); return 0; } -static void query_netdevs(void) { +static int arp_resolve_gateway(u32 index, u32 target_ip, u8 out_mac[6]) { + memset_nm(g_pktbuf, 0, sizeof(g_pktbuf)); + u8 *p = g_pktbuf; + for (int i=0;i<6;i++) p[i]=0xff; + for (int i=0;i<6;i++) p[6+i]=g_local_mac[i]; + p[12]=0x08; p[13]=0x06; + u8 *a = p + 14; + a[0]=0x00; a[1]=0x01; + a[2]=0x08; a[3]=0x00; + a[4]=6; a[5]=4; + a[6]=0x00; a[7]=0x01; + for (int i=0;i<6;i++) a[8+i]=g_local_mac[i]; + a[14]=(u8)(g_lease_ip>>24); a[15]=(u8)(g_lease_ip>>16); a[16]=(u8)(g_lease_ip>>8); a[17]=(u8)g_lease_ip; + a[24]=(u8)(target_ip>>24); a[25]=(u8)(target_ip>>16); a[26]=(u8)(target_ip>>8); a[27]=(u8)target_ip; + logl("[netman] ARP: who-has gateway"); + if (g_net_tx(index, g_pktbuf, 60) != 0) return -1; + for (u32 attempt=0; attempt<160000; attempt++) { + u32 rlen=0; + int rc = g_net_rx_poll(index, g_rxbuf, sizeof(g_rxbuf), &rlen); + if (rc == 0 && rlen >= 42 && g_rxbuf[12]==0x08 && g_rxbuf[13]==0x06) { + const u8 *r = g_rxbuf + 14; + u16 op = (u16)((r[6]<<8)|r[7]); + u32 spa = ((u32)r[14]<<24)|((u32)r[15]<<16)|((u32)r[16]<<8)|r[17]; + if (op == 2 && spa == target_ip) { + for (int i=0;i<6;i++) out_mac[i]=r[8+i]; + logl("[netman] ARP: gateway resolved"); + return 0; + } + } + delay_spin(300); + } + logl("[netman] ARP: timeout"); + return -2; +} + +static int dns_query_a(u32 index, const u8 dst_mac[6], u32 dns_ip, const char *name, u32 *out_ip) { + memset_nm(g_pktbuf, 0, sizeof(g_pktbuf)); + u8 *p = g_pktbuf; + for (int i=0;i<6;i++) p[i]=dst_mac[i]; + for (int i=0;i<6;i++) p[6+i]=g_local_mac[i]; + p[12]=0x08; p[13]=0x00; + u8 *ip = p + 14; + u8 *udp = ip + 20; + u8 *dns = udp + 8; + dns[0]=0x12; dns[1]=0x34; + dns[2]=0x01; dns[3]=0x00; + dns[4]=0x00; dns[5]=0x01; + u32 q=12; + const char *s=name; + while (*s) { + const char *dot=s; + u32 l=0; + while (dot[l] && dot[l] != '.') l++; + dns[q++]=(u8)l; + for (u32 i=0;i>8); udp[5]=(u8)udp_len; + udp[6]=0; udp[7]=0; + ip[0]=0x45; ip[1]=0; + ip[2]=(u8)(ip_len>>8); ip[3]=(u8)ip_len; + ip[4]=0x12; ip[5]=0x34; ip[6]=0; ip[7]=0; + ip[8]=64; ip[9]=17; ip[10]=0; ip[11]=0; + ip[12]=(u8)(g_lease_ip>>24); ip[13]=(u8)(g_lease_ip>>16); ip[14]=(u8)(g_lease_ip>>8); ip[15]=(u8)g_lease_ip; + ip[16]=(u8)(dns_ip>>24); ip[17]=(u8)(dns_ip>>16); ip[18]=(u8)(dns_ip>>8); ip[19]=(u8)dns_ip; + u16 ipck=ip_checksum(ip,20); ip[10]=(u8)(ipck>>8); ip[11]=(u8)ipck; + u16 udpck=udp_checksum_ipv4(ip,udp,udp_len); udp[6]=(u8)(udpck>>8); udp[7]=(u8)udpck; + logl("[netman] DNS: query example.com A"); + if (g_net_tx(index, g_pktbuf, 14+ip_len) != 0) return -1; + for (u32 attempt=0; attempt<240000; attempt++) { + u32 rlen=0; + int rc=g_net_rx_poll(index,g_rxbuf,sizeof(g_rxbuf),&rlen); + if (rc==0 && rlen>=14+20+8+12 && g_rxbuf[12]==0x08 && g_rxbuf[13]==0x00) { + const u8 *rip=g_rxbuf+14; + u32 ihl=(rip[0]&0xf)*4; + if ((rip[0]>>4)!=4 || rip[9]!=17 || rlen<14+ihl+8+12) { delay_spin(300); continue; } + const u8 *rudp=rip+ihl; + if (rudp[2]!=0xC0 || rudp[3]!=0x01) { delay_spin(300); continue; } + const u8 *rdns=rudp+8; + if (rdns[0]!=0x12 || rdns[1]!=0x34) { delay_spin(300); continue; } + u16 an=(u16)((rdns[6]<<8)|rdns[7]); + u32 off=12; + while (offrlen) break; + u16 typ=(u16)((rdns[off]<<8)|rdns[off+1]); + u16 cls=(u16)((rdns[off+2]<<8)|rdns[off+3]); + u16 rdlen=(u16)((rdns[off+8]<<8)|rdns[off+9]); + off += 10; + if (typ==1 && cls==1 && rdlen==4 && off+4<=rlen) { + *out_ip=((u32)rdns[off]<<24)|((u32)rdns[off+1]<<16)|((u32)rdns[off+2]<<8)|rdns[off+3]; + log_ipv4("[netman] DNS example.com=", *out_ip); + return 0; + } + off += rdlen; + } + } + delay_spin(300); + } + logl("[netman] DNS: timeout"); + return -2; +} + +static int network_full_connect(u32 index, const u8 mac[6]) { + int rc = dhcp_run(index, mac); + log_hex("[netman] DHCP final rc=", (u64)(i8)rc); + if (rc != 0) return rc; + u8 gw_mac[6]; + rc = arp_resolve_gateway(index, g_gateway_ip, gw_mac); + log_hex("[netman] ARP final rc=", (u64)(i8)rc); + if (rc != 0) return rc; + u32 resolved=0; + rc = dns_query_a(index, gw_mac, g_dns_ip, "example.com", &resolved); + log_hex("[netman] DNS final rc=", (u64)(i8)rc); + return rc; +} + +static void query_netdevs(const net_conf *cfg) { if (!g_net_device_count || !g_net_device_info) { logl("[netman] net API not ready; configuration staged only"); return; } u32 count = g_net_device_count(); + int dhcp_done = 0; + int dhcp_enabled = cfg && !str_eq(cfg->mode, "disabled") && !str_eq(cfg->mode, "off") && !str_eq(cfg->ip, "static"); log_hex("[netman] netdev_count=", count); for (u32 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 ZZvvpFg$%wGwEucKVc70o_!H;vKLKQ13tj*K literal 16856 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 diff --git a/CODE/shell/MAKEFILE b/CODE/shell/MAKEFILE new file mode 100644 index 0000000..135dee0 --- /dev/null +++ b/CODE/shell/MAKEFILE @@ -0,0 +1,25 @@ +CC := clang +LD := ld.lld + +ROOT := ../.. +OUT := $(ROOT)/SYSTEM/system/deshab64/shell.elf + +CFLAGS := -target x86_64-unknown-none -std=c11 -ffreestanding -fno-builtin -fno-stack-protector -fpie -mno-red-zone -mno-sse -mno-sse2 -mno-mmx -msoft-float -m64 -Wall -Wextra -I../UTSM/include -O2 +LDFLAGS := -nostdlib -static -pie -z max-page-size=0x1000 -T linker.ld + +C_SOURCES := main.c +OBJECTS := $(C_SOURCES:.c=.o) + +.PHONY: all clean + +all: $(OUT) + +$(OUT): $(OBJECTS) linker.ld + @if not exist "$(ROOT)\SYSTEM\system\deshab64" mkdir "$(ROOT)\SYSTEM\system\deshab64" + $(LD) $(LDFLAGS) -o $@ $(OBJECTS) + +%.o: %.c + $(CC) $(CFLAGS) -c $< -o $@ + +clean: + powershell -NoProfile -Command "Get-ChildItem -Path . -Recurse -Filter *.o | Remove-Item -Force; if (Test-Path '$(OUT)') { Remove-Item '$(OUT)' -Force }" diff --git a/CODE/shell/linker.ld b/CODE/shell/linker.ld new file mode 100644 index 0000000..825d5f1 --- /dev/null +++ b/CODE/shell/linker.ld @@ -0,0 +1,16 @@ +ENTRY(dsk_entry) + +PHDRS +{ + text PT_LOAD FLAGS(5); + rodata PT_LOAD FLAGS(4); + data PT_LOAD FLAGS(6); +} + +SECTIONS +{ + . = 0x1000; + .text : ALIGN(4K) { *(.text*) } :text + .rodata : ALIGN(4K) { *(.rodata*) } :rodata + .data : ALIGN(4K) { *(.data*) *(COMMON) *(.bss*) } :data +} diff --git a/CODE/shell/main.c b/CODE/shell/main.c new file mode 100644 index 0000000..9c8c4c8 --- /dev/null +++ b/CODE/shell/main.c @@ -0,0 +1,631 @@ +/* Deshab Shell — 命令行交互界面 + * 接收 PS/2 键盘输入,执行内置命令。 + * 显示在 framebuffer 上,使用 Consolas 18px 等宽字体。 + */ + +#include "../UTSM/include/utsm/dsk.h" + +typedef signed char i8; +typedef unsigned char u8; +typedef unsigned short u16; +typedef unsigned int u32; +typedef unsigned long long u64; +typedef long long i64; + +#define COM1 0x3F8 + +static __inline__ void outb(u16 port, u8 value) { __asm__ volatile("outb %0,%1"::"a"(value),"Nd"(port)); } +static __inline__ u8 inb(u16 port) { u8 v; __asm__ volatile("inb %1,%0":"=a"(v):"Nd"(port)); return v; } + +static void sputc(char c) { + for (unsigned int i=0; i<100000; i++) { if (inb(COM1+5)&0x20) break; } + outb(COM1, (unsigned char)c); +} +static void swrite(const char *s) { while(*s) { if(*s=='\n')sputc('\r'); sputc(*s++); } } +static void logl(const char *s) { swrite(s); swrite("\n"); } + +/* ASCII 字体 — Consolas 18px 灰度位图(与 FirstInit 共享) */ +#include "../firstInit/ascii_bitmaps.c" + +#define ASCII_W 11 +#define ASCII_H 18 +#define ASCII_STEP 11 + +/* 终端尺寸 */ +#define TERM_COLS 100 +#define TERM_ROWS 35 +#define TERM_MAX_CHARS (TERM_COLS * TERM_ROWS) + +/* 颜色 — 深色主题,类似经典终端 */ +#define BG_COLOR 0xFF0A1428u /* 深蓝黑 */ +#define TEXT_FG 0xFFE0E8F0u /* 浅灰白 */ +#define PROMPT_FG 0xFF66CCFFu /* 青色提示符 */ +#define ERROR_FG 0xFFFF6680u /* 红色错误 */ +#define OK_FG 0xFF66FF99u /* 绿色成功 */ +#define DIM_FG 0xFF7080A0u /* 灰色注释 */ + +static u64 fb_a, fb_w, fb_h, fb_p; +static u32 *g_fb; + +/* 终端缓冲区 — 字符 + 颜色 */ +static u8 term_ch[TERM_MAX_CHARS]; +static u32 term_fg[TERM_MAX_CHARS]; +static int term_w = TERM_COLS; +static int term_h = TERM_ROWS; +static int cur_col = 0; +static int cur_row = 0; +static int cur_visible = 1; + +/* 颜色混合 */ +static u32 blend(u32 c1, u32 c2, u32 a) { + u32 na=256-a; + u32 r=((c1&0xFF)*na+(c2&0xFF)*a)>>8; + u32 g=(((c1>>8)&0xFF)*na+((c2>>8)&0xFF)*a)>>8; + u32 b=(((c1>>16)&0xFF)*na+((c2>>16)&0xFF)*a)>>8; + return 0xFF000000|(b<<16)|(g<<8)|r; +} + +/* ---- 字符渲染(与 FirstInit 类似,但用纯色背景) ---- */ +static void fb_char(u32 *fb, u32 ch, i64 x, i64 y, u32 fg, u32 bg) { + if (ch < ' ' || ch > '~') ch = ' '; + u32 idx = (u32)(ch - ' '); + const u8 *g = g_ascii[idx]; + for (i64 r=0; r= fb_h) continue; + u32 *line = (u32 *)((u8 *)fb + (u64)yy * fb_p); + for (i64 c=0; c= fb_w) continue; + line[(u64)xx] = (a == 255) ? fg : blend(bg, fg, a); + } + } +} + +static void fill_rect(u32 *fb, i64 x, i64 y, i64 w, i64 h, u32 color) { + for (i64 r=0; r= fb_h) continue; + u32 *line = (u32 *)((u8 *)fb + (u64)yy * fb_p); + for (i64 c=0; c= fb_w) continue; + line[(u64)xx] = color; + } + } +} + +/* ---- 终端逻辑 ---- */ +static int term_start_x, term_start_y; /* 像素坐标 */ + +static void term_init_pos(void) { + /* 居中布局,每字符 11px 宽,行高 20px */ + int margin_x = 16; + int margin_y = 16; + term_start_x = margin_x; + term_start_y = margin_y; + /* 自适应行数/列数到 framebuffer */ + int avail_w = (int)fb_w - margin_x * 2; + int avail_h = (int)fb_h - margin_y * 2; + term_w = avail_w / ASCII_STEP; + term_h = avail_h / (ASCII_H + 2); + if (term_w < 10) term_w = 10; + if (term_h < 5) term_h = 5; + if (term_w > TERM_COLS) term_w = TERM_COLS; + if (term_h > TERM_ROWS) term_h = TERM_ROWS; +} + +static void term_clear(void) { + for (int i = 0; i < TERM_MAX_CHARS; i++) { + term_ch[i] = ' '; + term_fg[i] = TEXT_FG; + } + cur_col = 0; + cur_row = 0; +} + +static void term_scroll(void) { + /* 向上滚动一行 */ + for (int r = 1; r < term_h; r++) { + for (int c = 0; c < term_w; c++) { + int src = r * TERM_COLS + c; + int dst = (r-1) * TERM_COLS + c; + term_ch[dst] = term_ch[src]; + term_fg[dst] = term_fg[src]; + } + } + /* 清空最后一行 */ + for (int c = 0; c < term_w; c++) { + term_ch[(term_h-1) * TERM_COLS + c] = ' '; + term_fg[(term_h-1) * TERM_COLS + c] = TEXT_FG; + } + cur_row = term_h - 1; + cur_col = 0; +} + +static void term_newline(void) { + cur_col = 0; + cur_row++; + if (cur_row >= term_h) term_scroll(); +} + +static void term_putc_color(char c, u32 color) { + if (c == '\n') { term_newline(); return; } + if (c == '\r') { cur_col = 0; return; } + if (c == '\t') { + int spaces = 4 - (cur_col % 4); + for (int i = 0; i < spaces; i++) term_putc_color(' ', color); + return; + } + if (c < ' ' || c > '~') c = '?'; + if (cur_col >= term_w) term_newline(); + int idx = cur_row * TERM_COLS + cur_col; + term_ch[idx] = (u8)c; + term_fg[idx] = color; + cur_col++; +} + +static void term_putc(char c) { term_putc_color(c, TEXT_FG); } + +static void term_puts_color(const char *s, u32 color) { + while (*s) term_putc_color(*s++, color); +} + +static void term_puts(const char *s) { term_puts_color(s, TEXT_FG); } + +static void term_redraw_all(void) { + /* 清屏背景 */ + fill_rect(g_fb, 0, 0, (i64)fb_w, (i64)fb_h, BG_COLOR); + /* 渲染所有字符 */ + for (int r = 0; r < term_h; r++) { + for (int c = 0; c < term_w; c++) { + int idx = r * TERM_COLS + c; + u8 ch = term_ch[idx]; + if (ch == ' ') continue; + i64 x = term_start_x + (i64)c * ASCII_STEP; + i64 y = term_start_y + (i64)r * (ASCII_H + 2); + fb_char(g_fb, ch, x, y, term_fg[idx], BG_COLOR); + } + } + /* 渲染光标 */ + if (cur_visible && cur_col < term_w && cur_row < term_h) { + i64 x = term_start_x + (i64)cur_col * ASCII_STEP; + i64 y = term_start_y + (i64)cur_row * (ASCII_H + 2); + fill_rect(g_fb, x, y + ASCII_H - 3, ASCII_W, 2, TEXT_FG); + } +} + +static void term_redraw_cursor(void) { + if (cur_visible && cur_col < term_w && cur_row < term_h) { + i64 x = term_start_x + (i64)cur_col * ASCII_STEP; + i64 y = term_start_y + (i64)cur_row * (ASCII_H + 2); + fill_rect(g_fb, x, y + ASCII_H - 3, ASCII_W, 2, TEXT_FG); + } +} + +static void term_clear_cursor(void) { + if (cur_col < term_w && cur_row < term_h) { + i64 x = term_start_x + (i64)cur_col * ASCII_STEP; + i64 y = term_start_y + (i64)cur_row * (ASCII_H + 2); + fill_rect(g_fb, x, y + ASCII_H - 3, ASCII_W, 2, BG_COLOR); + } +} + +/* ---- 输入缓冲 ---- */ +static char input_buf[256]; +static int input_len = 0; +static int input_cursor = 0; /* 在 input_buf 中的位置(支持左右移动) */ + +/* ---- 键盘扫描码 → ASCII(Set 1,与 FirstInit 共享逻辑) ---- */ +static char scan_to_ascii(u8 sc, int shift) { + static const char normal[58] = { + 0, 27, '1','2','3','4','5','6','7','8','9','0','-','=', 8, '\t', + 'q','w','e','r','t','y','u','i','o','p','[',']','\n',0, + 'a','s','d','f','g','h','j','k','l',';','\'', '`',0,'\\', + 'z','x','c','v','b','n','m',',','.','/',0,'*',0,' ' + }; + static const char shifted[58] = { + 0, 27, '!','@','#','$','%','^','&','*','(',')','_','+', 8, '\t', + 'Q','W','E','R','T','Y','U','I','O','P','{','}','\n',0, + 'A','S','D','F','G','H','J','K','L',':','"','~',0,'|', + 'Z','X','C','V','B','N','M','<','>','?',0,'*',0,' ' + }; + if (sc >= 58) return 0; + return shift ? shifted[sc] : normal[sc]; +} + +/* ---- 内置命令 ---- */ +static int str_eq(const char *a, const char *b) { + while (*a && *b) { if (*a != *b) return 0; a++; b++; } + return *a == *b; +} + +static void cmd_help(void) { + term_puts_color("Deshab Shell — 内置命令列表:\n", PROMPT_FG); + term_puts(" help 显示此帮助\n"); + term_puts(" clear 清屏\n"); + term_puts(" echo 显示文本\n"); + term_puts(" version 显示系统版本\n"); + term_puts(" uname 显示系统名称\n"); + term_puts(" date 显示当前日期\n"); + term_puts(" ver 显示内核版本(同 version)\n"); + term_puts(" about 关于 Deshab\n"); + term_puts(" reboot 重启系统\n"); + term_puts(" halt 关机(停止 CPU)\n"); + term_puts(" ls 列出根目录文件(暂未实现,需要 VFS)\n"); + term_puts(" pci 列出 PCI 设备(暂未实现)\n"); +} + +static void cmd_version(void) { + term_puts_color("Deshab OS v0.1.0\n", OK_FG); + term_puts(" Kernel: DSK (Deshab System Kernel)\n"); + term_puts(" Architecture: x86_64\n"); + term_puts(" ABI: DKM (Deshab Kernel Module) v1\n"); + term_puts(" Bootloader: Limine\n"); +} + +static void cmd_uname(void) { + term_puts("Deshab\n"); +} + +static void cmd_about(void) { + term_puts_color("=== Deshab OS ===\n", PROMPT_FG); + term_puts("单地址空间 Ring0 内核实验系统\n"); + term_puts("架构: UTSM + DSK + DKM 驱动模型\n"); + term_puts_color("Built with Clang + lld\n", DIM_FG); + term_puts_color("© 2026 Deshab Project\n", DIM_FG); +} + +static void cmd_clear(void) { + term_clear(); +} + +static void cmd_reboot(void) { + term_puts_color("Rebooting...\n", PROMPT_FG); + term_redraw_all(); + /* ACPI 重启:写 0x64 = 0xFE 触发键盘控制器复位 */ + outb(0x64, 0xFE); + /* 备用:三重故障 */ + __asm__ volatile("int $0x03"); + for(;;) __asm__("hlt"); +} + +static void cmd_halt(void) { + term_puts_color("System halted.\n", PROMPT_FG); + term_redraw_all(); + for(;;) __asm__("hlt"); +} + +static void cmd_echo(const char *args) { + if (args && *args) term_puts_color(args, TEXT_FG); + term_putc('\n'); +} + +/* 前向声明 — CMOS RTC 读取 */ +static u8 read_rtc_reg(u8 reg); +static u8 bcd_to_bin_impl(u8 bcd); + +static void cmd_date(void) { + /* 通过 CMOS RTC 读取日期 */ + u8 year = bcd_to_bin_impl(read_rtc_reg(9)); + u8 month = bcd_to_bin_impl(read_rtc_reg(8)); + u8 day = bcd_to_bin_impl(read_rtc_reg(7)); + u8 hour = bcd_to_bin_impl(read_rtc_reg(4)); + u8 minute = bcd_to_bin_impl(read_rtc_reg(2)); + u8 second = bcd_to_bin_impl(read_rtc_reg(0)); + /* 简单打印 */ + char buf[32]; + int p = 0; + /* YYYY-MM-DD HH:MM:SS */ + buf[p++] = '2'; buf[p++] = '0'; + buf[p++] = '0' + year / 10; buf[p++] = '0' + year % 10; + buf[p++] = '-'; + buf[p++] = '0' + month / 10; buf[p++] = '0' + month % 10; + buf[p++] = '-'; + buf[p++] = '0' + day / 10; buf[p++] = '0' + day % 10; + buf[p++] = ' '; + buf[p++] = '0' + hour / 10; buf[p++] = '0' + hour % 10; + buf[p++] = ':'; + buf[p++] = '0' + minute / 10; buf[p++] = '0' + minute % 10; + buf[p++] = ':'; + buf[p++] = '0' + second / 10; buf[p++] = '0' + second % 10; + buf[p] = 0; + term_puts(buf); + term_putc('\n'); +} + +static void cmd_unknown(const char *cmd) { + term_puts_color("未知命令: ", ERROR_FG); + term_puts_color(cmd, ERROR_FG); + term_putc('\n'); + term_puts_color("输入 'help' 查看可用命令\n", DIM_FG); +} + +static void cmd_not_impl(const char *cmd) { + term_puts_color(cmd, ERROR_FG); + term_puts_color(": 此命令尚未实现\n", ERROR_FG); +} + +static void execute_command(const char *cmd) { + /* 跳过前导空格 */ + while (*cmd == ' ') cmd++; + if (*cmd == 0) return; + /* 找命令名(第一个单词) */ + const char *args = cmd; + while (*args && *args != ' ') args++; + int cmd_name_len = (int)(args - cmd); + /* 复制命令名到本地缓冲 */ + char name[32]; + if (cmd_name_len >= 32) cmd_name_len = 31; + for (int i = 0; i < cmd_name_len; i++) name[i] = cmd[i]; + name[cmd_name_len] = 0; + /* 跳过参数前的空格 */ + while (*args == ' ') args++; + /* 分派 */ + if (str_eq(name, "help") || str_eq(name, "?")) cmd_help(); + else if (str_eq(name, "version") || str_eq(name, "ver")) cmd_version(); + else if (str_eq(name, "uname")) cmd_uname(); + else if (str_eq(name, "about")) cmd_about(); + else if (str_eq(name, "clear") || str_eq(name, "cls")) cmd_clear(); + else if (str_eq(name, "reboot")) cmd_reboot(); + else if (str_eq(name, "halt") || str_eq(name, "shutdown")) cmd_halt(); + else if (str_eq(name, "echo")) cmd_echo(args); + else if (str_eq(name, "date") || str_eq(name, "time")) cmd_date(); + else if (str_eq(name, "ls") || str_eq(name, "dir")) cmd_not_impl("ls"); + else if (str_eq(name, "pci")) cmd_not_impl("pci"); + else if (str_eq(name, "cat")) cmd_not_impl("cat"); + else if (str_eq(name, "cd")) cmd_not_impl("cd"); + else if (str_eq(name, "pwd")) { term_puts("/\n"); } + else if (str_eq(name, "whoami")) { term_puts("root\n"); } + else if (str_eq(name, "id")) { term_puts("uid=0(root) gid=0(root)\n"); } + else cmd_unknown(name); +} + +/* ---- CMOS RTC 读取 ---- */ +static u8 read_rtc_reg(u8 reg) { + outb(0x70, reg); + return inb(0x71); +} + +static u8 bcd_to_bin_impl(u8 bcd) { + return (u8)((bcd >> 4) * 10 + (bcd & 0x0F)); +} + +/* ---- 提示符 ---- */ +static const char *PROMPT = "deshab# "; + +static void draw_prompt(void) { + term_puts_color(PROMPT, PROMPT_FG); +} + +/* ---- 输入行编辑 ---- */ +static void redraw_input_line(void) { + /* 清除当前行从提示符开始的部分并重绘 */ + int prompt_len = 0; + while (PROMPT[prompt_len]) prompt_len++; + /* 清除整行 */ + i64 y = term_start_y + (i64)cur_row * (ASCII_H + 2); + fill_rect(g_fb, term_start_x, y, (i64)term_w * ASCII_STEP, ASCII_H, BG_COLOR); + /* 重绘提示符 */ + for (int i = 0; i < prompt_len; i++) { + i64 x = term_start_x + (i64)i * ASCII_STEP; + fb_char(g_fb, PROMPT[i], x, y, PROMPT_FG, BG_COLOR); + } + /* 重绘输入缓冲 */ + for (int i = 0; i < input_len; i++) { + i64 x = term_start_x + (i64)(prompt_len + i) * ASCII_STEP; + fb_char(g_fb, input_buf[i], x, y, TEXT_FG, BG_COLOR); + } + /* 同步 cur_col 到输入光标 */ + cur_col = prompt_len + input_cursor; + term_redraw_cursor(); +} + +static void insert_char(char c) { + if (input_len >= 255) return; + /* 在 input_cursor 处插入 */ + for (int i = input_len; i > input_cursor; i--) { + input_buf[i] = input_buf[i-1]; + } + input_buf[input_cursor] = c; + input_len++; + input_cursor++; + redraw_input_line(); +} + +static void delete_char_back(void) { + if (input_cursor == 0) return; + /* 删除 input_cursor-1 处的字符 */ + for (int i = input_cursor - 1; i < input_len - 1; i++) { + input_buf[i] = input_buf[i+1]; + } + input_len--; + input_cursor--; + input_buf[input_len] = 0; + redraw_input_line(); +} + +static void delete_char_fwd(void) { + if (input_cursor >= input_len) return; + for (int i = input_cursor; i < input_len - 1; i++) { + input_buf[i] = input_buf[i+1]; + } + input_len--; + input_buf[input_len] = 0; + redraw_input_line(); +} + +static void cursor_left(void) { + if (input_cursor > 0) { + input_cursor--; + term_clear_cursor(); + cur_col--; + if (cur_col < 0) { cur_col = term_w - 1; cur_row--; } + term_redraw_cursor(); + } +} + +static void cursor_right(void) { + if (input_cursor < input_len) { + input_cursor++; + term_clear_cursor(); + cur_col++; + if (cur_col >= term_w) { cur_col = 0; cur_row++; } + term_redraw_cursor(); + } +} + +static void cursor_home(void) { + term_clear_cursor(); + int prompt_len = 0; + while (PROMPT[prompt_len]) prompt_len++; + cur_col = prompt_len; + input_cursor = 0; + term_redraw_cursor(); +} + +static void cursor_end(void) { + term_clear_cursor(); + int prompt_len = 0; + while (PROMPT[prompt_len]) prompt_len++; + cur_col = prompt_len + input_len; + input_cursor = input_len; + term_redraw_cursor(); +} + +static void kill_to_end(void) { + input_len = input_cursor; + input_buf[input_len] = 0; + redraw_input_line(); +} + +static void kill_line(void) { + input_len = 0; + input_cursor = 0; + input_buf[0] = 0; + redraw_input_line(); +} + +/* ---- 主入口 ---- */ +__attribute__((visibility("default"))) +void dsk_entry(const dsk_boot_context *ctx) { + __asm__ volatile("cli"); + logl("[shell] boot"); + + if (!ctx || ctx->magic != 0x44534B31424F4F54ULL) { + logl("[shell] bad context"); + for(;;) __asm__("hlt"); + } + + fb_a = ctx->framebuffer_address; + fb_w = ctx->framebuffer_width; + fb_h = ctx->framebuffer_height; + fb_p = ctx->framebuffer_pitch; + g_fb = (u32 *)(u64)fb_a; + + term_init_pos(); + term_clear(); + term_redraw_all(); + + /* 启动 banner */ + term_puts_color("=== Deshab OS v0.1.0 ===\n", PROMPT_FG); + term_puts_color("欢迎使用 Deshab Shell\n", OK_FG); + term_puts_color("输入 'help' 查看可用命令\n", DIM_FG); + term_putc('\n'); + + /* 主循环 */ + int shift = 0; + int e0 = 0; + for (;;) { + draw_prompt(); + input_len = 0; + input_cursor = 0; + input_buf[0] = 0; + int prompt_len = 0; + while (PROMPT[prompt_len]) prompt_len++; + cur_col = prompt_len; + term_redraw_cursor(); + + /* 等待命令输入 */ + int cmd_done = 0; + while (!cmd_done) { + u8 st = inb(0x64); + if (!(st & 1)) { __asm__("pause"); continue; } + u8 data = inb(0x60); + /* 忽略鼠标数据(AUX 位) */ + if (st & 0x20) continue; + u8 sc = data; + if (sc == 0xE0) { e0 = 1; continue; } + if (sc == 0x2A || sc == 0x36) { shift = 1; continue; } + if (sc == 0xAA || sc == 0xB6) { shift = 0; continue; } + if (sc & 0x80) { + /* 松开事件 — 仅处理扩展键 */ + if (e0 && sc == 0x9C) { e0 = 0; } /* Enter 释放 */ + else if (e0 && sc == 0xCB) { e0 = 0; } /* Left 释放 */ + else if (e0 && sc == 0xCD) { e0 = 0; } /* Right 释放 */ + else if (e0 && sc == 0xC8) { e0 = 0; } /* Up 释放 */ + else if (e0 && sc == 0xD0) { e0 = 0; } /* Down 释放 */ + else if (e0 && sc == 0xD3) { e0 = 0; } /* Delete 释放 */ + else if (e0 && sc == 0xC7) { e0 = 0; } /* Home 释放 */ + else if (e0 && sc == 0xCF) { e0 = 0; } /* End 释放 */ + e0 = 0; + continue; + } + /* 按下事件 */ + if (e0) { + /* 扩展键 */ + if (sc == 0x4B) cursor_left(); /* Left */ + else if (sc == 0x4D) cursor_right(); /* Right */ + else if (sc == 0x47) cursor_home(); /* Home */ + else if (sc == 0x4F) cursor_end(); /* End */ + else if (sc == 0x53) delete_char_fwd(); /* Delete */ + else if (sc == 0x48) { /* Up — 命令历史暂未实现 */ } + else if (sc == 0x50) { /* Down — 命令历史暂未实现 */ } + e0 = 0; + continue; + } + /* 控制键 */ + if (sc == 0x1C) { + /* Enter — 执行命令 */ + term_clear_cursor(); + input_buf[input_len] = 0; + term_putc('\n'); + execute_command(input_buf); + cmd_done = 1; + continue; + } + if (sc == 0x0E) { + /* Backspace */ + delete_char_back(); + continue; + } + if (sc == 0x01) { + /* Esc — 清行 */ + kill_line(); + continue; + } + /* Ctrl+U (kill line) */ + if (sc == 0x15 && !shift) { + kill_line(); + continue; + } + if (sc == 0x17 && !shift) { + /* Ctrl+W — 删除到行首 */ + while (input_cursor > 0) delete_char_back(); + continue; + } + if (sc == 0x0B && !shift) { + /* Ctrl+K — 删除到行尾 */ + kill_to_end(); + continue; + } + /* 普通字符 */ + char c = scan_to_ascii(sc, shift); + if (c && c >= 32 && c <= 126) { + insert_char(c); + } + } + } +} diff --git a/CODE/shell/main.o b/CODE/shell/main.o new file mode 100644 index 0000000000000000000000000000000000000000..7b43b8c08ae928f08ddf15d0fcb45b2ec75cf11e GIT binary patch literal 198200 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 N^UUA*WIZ^3{vXwLgJb{z literal 0 HcmV?d00001 diff --git a/LESSONS_LEARNED.md b/LESSONS_LEARNED.md index 772e719..b69e7c8 100644 --- a/LESSONS_LEARNED.md +++ b/LESSONS_LEARNED.md @@ -528,5 +528,69 @@ 1. **反推物理地址不可靠**: 用内核高半虚拟地址(BSS)反推物理地址喂给设备 DMA,物理页与 CPU 映射不保证一致,是之前 e1000 一启用 RCTL/TCTL 就卡死的根因;DMA 缓冲必须来自明确的物理页分配。 2. **低 4G 约束要显式**: e1000 等 32 位 BAR 设备的 DMA 描述符/缓冲需落在低 4G,bitmap 分配器直接把上界钳到 `0x100000000` 并保留最低 1MiB,避免 legacy 区。 +## 2026-07-02 — e1000 DHCP 联网闭环 + +### 经验 + +1. **PCI I/O 汇编必须 volatile**: `outl(0xCF8)` / `inl(0xCFC)` 访问 PCI config space 时必须使用 `asm volatile` 和 memory clobber,否则 `-O2` 下可能丢弃或重排,导致读取 BAR0 时仍拿到 vendor/device ID。 +2. **验证 TX 要看 descriptor 完成位**: 只看到写 TDT 不代表网卡已发送;轮询 TX descriptor `DD` 位可以区分“API 调用成功”和“设备真正消费 descriptor”。 +3. **DHCP 包要兼容 BOOTP 最小长度**: DHCP/BOOTP payload 补齐到 548 字节并计算 UDP checksum 后,QEMU user-net 能正常返回 OFFER/ACK。 + +### 教训 + +1. **BAR 读错会伪装成 DMA/RX 问题**: BAR0 被读成 `0x100e8086` 时,MMIO 实际指向错误地址,表现为 `STATUS=0`、TX descriptor 不完成,容易误判为 ring 或 DHCP 问题。 +2. **早期网络调试要分层验证**: 先确认 PCI BAR → MMIO STATUS → TX DD → RX frame → DHCP 解析,避免在协议层反复修改掩盖底层设备访问错误。 + +## 2026-07-02 — FirstInit 无线网络选择界面 + +### 经验 + +1. **复用现有卡片/输入框最稳**: FirstInit 已有 `draw_rounded_input`、鼠标包解析和键盘轮询,直接替换旧网络选项页为 SSID 列表 + 密码框 + Connect 按钮,可以避免新增 UI 框架。 +2. **配置先写入 user.conf 缓冲**: 当前缺 FAT32 write API,网络选择结果先进入加密配置缓冲,字段使用 `network.mode=wifi`、`network.ssid`、`network.password`、`network.ip=dhcp`、`network.dns=auto`,后续写盘可直接复用。 + +### 教训 + +1. **真实 Wi-Fi 扫描 API 尚未存在**: 当前界面只能展示模拟检测到的 SSID,后续需要网卡/无线驱动和扫描 API 后再替换数据源。 +2. **FirstInit 仍不应接管系统联网**: UI 只收集用户选择和密码;实际联网仍由 netman/驱动负责,避免用户向导和系统初始化职责混在一起。 + +## 2026-07-02 — netman 完整早期联网闭环 + +### 经验 + +1. **联网闭环要分层验收**: DHCP 只证明拿到 IP,还需要 ARP 解析网关 MAC、DNS 查询外部域名,才能证明 TX/RX 与 QEMU user-net 实际可用。 +2. **QEMU user-net 网关与 DNS 分离**: DHCP 返回网关 `10.0.2.2`,DNS 常为 `10.0.2.3`,应从 DHCP option 3/6 读取,缺失时再 fallback。 +3. **保留正式阶段日志有助于验证**: `DHCP final rc`、`ARP final rc`、`DNS final rc`、`network final rc` 足以判断完整联网状态,不需要保留临时 debug 日志。 + +### 教训 + +1. **DNS parser 边界必须谨慎**: DNS response 使用压缩 name,解析 answer 时必须处理 `0xC0` 指针并检查 `rdlen`,否则容易越界。 +2. **崩溃判断必须靠串口证据**: 用户观察到“崩溃重启”后,插桩证明确认联网阶段成功并进入 FirstInit;不能在没有阶段锚点证据时直接回滚功能。 + +## 2026-07-02 — 修复 FirstInit 网络页 SSID 指针数组崩溃 + +### 经验 + +1. **FirstInit PIE 中避免全局指针数组**: DSK 的 PIE loader 只处理有限重定位场景,UI 页面里的字符串列表应优先使用二维字符数组或栈上字符数组,避免 `const char *arr[]` 解引用崩溃。 +2. **绘制函数要用阶段锚点定位**: 对网络页按 `redraw → row → fill → stroke → ssid → meta` 分层插桩,可以精确定位崩溃在第一条 SSID 文本绘制。 + +### 教训 + +1. **看似普通的字符串数组也是重定位风险**: `static const char *g_wifi_ssids[]` 在普通 C 程序中安全,但在当前 DSK 加载 FirstInit 的 PIE 环境下会变成运行期指针重定位风险。 +2. **不要用跳过流程的调试改动进入最终版本**: 为了快速复现可以临时跳过账号/偏好页,但验证后必须恢复真实流程,避免引入与用户路径不一致的行为。 + +## 2026-07-03 — 用 Consolas TTF 替换 8x8 位图字体 + +### 经验 + +1. **裸金属 OS "用系统的 TTF" 的正确解读**: Deshab 没有 TTF 光栅化服务,"系统的 TTF" 应解读为构建时用宿主机 Windows 系统 TTF 字体(`C:\Windows\Fonts\consola.ttf`)预渲染为灰度位图,嵌入 FirstInit ELF,运行时仍走 alpha-blend 位图渲染路径。 +2. **Consolas 18px 渲染参数**: advance=10px,'A' bbox=(0,3,10,14),'g' bbox=(0,5,10,18),11x18 格子完美适配(10px 字形 + 1px 间距,ASCII_STEP=11)。等宽字体适合 UI 输入框/菜单/按钮。 +3. **统一灰度位图渲染路径**: 中文字体已用 `fb_bitmap_alpha()` 8bpp 灰度 alpha-blend,ASCII 字体改用同样的灰度位图 + alpha-blend 后,字形抗锯齿效果与中文一致,且 `fb_char()` 逻辑大幅简化(移除 8x8→10x16 缩放和边缘检测)。 +4. **构建时预渲染脚本**: 新增 `CODE/font/render_ascii.py`,用 PIL 从 Consolas TTF 渲染 ASCII 32-126 共 95 个字符到 `CODE/firstInit/ascii_bitmaps.c`(18810 字节 glyph data),通过 `#include` 嵌入 main.c。 + +### 教训 + +1. **8x8 位图字体缩放到 10x16 形状怪异**: 手工 8x8 位图字体在小写字母上使用满高设计,2x2 近邻缩放后字形不自然;TTF 预渲染直接生成目标尺寸的灰度位图,避免缩放失真。 +2. **字符范围要覆盖全部可打印 ASCII**: 原 `fb_char` 用 `ch >= ' ' && ch <= 'z'`(0x7a)截断,导致 `{` `|` `}` `~` 显示为空格;改为 `ch > '~'`(0x7e)后覆盖全部 95 个可打印字符。 +3. **布局偏移要跟随字高调整**: ASCII_H 从 16 变为 18 后,硬编码的 `y+(h-16)/2` 需改为 `y+(h-ASCII_H)/2`,wifi 行的 `y+17`(选中标记居中)需改为 `y+16`((50-18)/2=16)。 diff --git a/SYSTEM/boot/limine.conf b/SYSTEM/boot/limine.conf index 6f6ebf5..83457a7 100644 --- a/SYSTEM/boot/limine.conf +++ b/SYSTEM/boot/limine.conf @@ -34,6 +34,8 @@ verbose: yes module_cmdline: dkm:e1000 module_path: boot():/driver/net/virtio_net.drv module_cmdline: dkm:virtio_net + module_path: boot():/driver/net/ath9k.drv + module_cmdline: dkm:ath9k module_path: boot():/driver/input/ps2kbd.drv module_cmdline: dkm:ps2kbd module_path: boot():/system/deshab64/deshab.elf diff --git a/SYSTEM/boot/utsm.elf b/SYSTEM/boot/utsm.elf index d8e30e38d5d2bfbb0a5ea895f6fac3b38a7210a8..68557ecd9ebd4e72e78b61b2ea8c8be13a82de68 100644 GIT binary patch delta 9328 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 CmoUlz diff --git a/SYSTEM/driver/manifest.json b/SYSTEM/driver/manifest.json index d65002f..4bf2c39 100644 --- a/SYSTEM/driver/manifest.json +++ b/SYSTEM/driver/manifest.json @@ -155,6 +155,14 @@ "depends": ["pci", "irq"], "provides": ["netdev"] }, + { + "name": "ath9k", + "path": "driver/net/ath9k.drv", + "class": "net", + "required": false, + "depends": ["pci", "irq"], + "provides": ["netdev"] + }, { "name": "ps2kbd", "path": "driver/input/ps2kbd.drv", diff --git a/SYSTEM/driver/net/ath9k.drv b/SYSTEM/driver/net/ath9k.drv new file mode 100644 index 0000000000000000000000000000000000000000..b18300619efc61565dd4849540722ff59b2391ca GIT binary patch literal 14512 zcmcgz3v`r4maZldlMMKy^3oCcQ4qlx(>#Thu^ZF)w4soqmwk&cl9SbbNH^ z&X}JzGttpA4m<05W_L$s=j;eOXG36CNducEtPjkb^||LfkaI2z4hPf z{=3pWk27Y9(_K|x-MV$}t-7}!9iDJhubX7CFp(_m8!THBC}Wk&vt=7E+Sny*3cFj= ze{J7l|Car3`{ui}*e6@}ZL^*on&VUE#yORK%y;m7(HW)tyI9p?V`_Lybfhe@&|&RW z=GvI=;Fm?8>`^1r>}}`&_B$44@3dL$dT;RKy;FHdE!_u{z6!xl(7zNWPPjzurH3L$DW9!xSm&Zjni+D68{_wh&rm)beE8K zYFirB>%85fr-G-m%30GsOyl*-p&xqQt{<}}2dCLPqu&NCW9M&BcJ7|Un0{;lV;9nE zU_g+$0}y-c{3A;IaVT}RG4>2LO<~H;Mr`V|{dMeoiL&!P(eq1ec0tb#M$dbdcomeY z^uw_a&&3YE92+fCb}lj6Zdc;hLkT)mL5Dd|)Q`7)$kt@@Es`PP+K`A@9G&V$9?G~i`FL-;UMu=5HicVk26rq^Smla!r5g$fK` z3HS?8ME%}I1&TqxP9%(-uTbJKDA~HVIjsFdvwU5AdW+83mF|7G0-Ujt>%;fiqGM%| z1rFDBV%4 zb~xISr>4g~fI5he^__NT1^J>zHP+{dGp(~GI&?Yu2g$E=TW#^P;d{B{H*nuE{7bB_ z!u|vD|6%@P?MG&OIcD`m-<;Yu!q=|dq9=oAXzd!*U3uEexZ`sFm@(cz=1rLYJM{|( ziop@87M#?hJ}s5PIT&mOH@BZnS(Ns-p<1Pn z#XcI=bRGchv5zO)W9j0^@}B834`84VYsr(URtg~pf)24;1njf~?=GO30L5VCLQ>*` z@Xf9Db!9Fr7jC_5yj-fp&jG<#9Z&dxGr>RP@fL?S<5=^pX&xWN`ubDjSI-!Pt)b;L ziGQ8{BB(=#sMf~@%PwZDx`-R2TIX4Kjy9M0*x7qKt*V_NeURQg> zdRCb6&B;;#hY0D@x$)1vr@xb(-VTl_`*SAYY3QwV=Rl7G#4U4`*l)(!=j#(=A1z_5 znfBiB#VK1A1QFKrTZm+zccqU-Mtfd_y<-U1$LC$+|D}(GXK4w(!@-Uhem~*VVjmR6 zMkYnxGO;a7MVXB;ur})%1pcr^!~SRRA5R8UM3%chjib+z)(6sM<(*qlUM2n%@Hff) zPFCjE`2;}UD9SL-{#Pn-8`$N~<_}j-%zPV4%Kq1LfiL&5E0p-fu<}vK8~6!f-2U^& z`r{9B&l4So=74br&p^Di>wSl=#>xj!(T~{m|8mSbcW5pmZz7Vcnm6K$jie(L!tLIH zeLlsPD-bcCC|diiz19Kid)BiKI09bht1?2SPsgDc`DK-UHhHE*e>Zu$L{BD9mB7X~ zX4lWd*{0Y*dtgCuVjlrjw?P$&_c_{E zE8U+a0Yn4(+sQM)N%YlY9|EUmkWOTt&qc5IvK-pq3tu$29_rQlNoV4z`OkwG5CCk-bA3wp43sH8 z0F{(YJ!gxiEs?oO_o$ZY!-n)*z;jseeA3`?0Aqu+|7)rE)Q4eFVka$@EYV^kTOy~m zlp~6WvY$sCpI*~p#2>w8m(tC#&MBP`!I@su1;i*B=Gunortvf8-4x+=JneG@!rL_5ohyV3ZN9g(jWFmC8FCt7+L=0vdcbMI$%6=kR+E+)&8zj1^yQ#xJ6_+MiHn_` z8&rIoI0+Uh;_lW`Hk0+XI3Rnj4L2Ca5KE*E-xe4J?E_GRG>K*72|Wbp16uOKi_TWfoMs7^YnHdQ z)H=QdA?GVl(>IhDOw#Zar|5hY!_BI<*4lf-ykDYxJYbmOJ6-}Or!X9_9{h&blG#s? zev`45`F=Pq{?D2idvh7dNJ{&Q5X!7)554}{Yp+#zI?g*YhU&`NKMWVv;gLx^wx#>> z?RH-^#;?Ug3vdI@*el-{aNoYtw5hzG&{58FNr+u6{Kj|<_(b4kp5KS@e!LH!CkKWg zAdCAgT!VHYnKL#zJ-iKr$#o9vd(bY7Es zjQJkcl$TRla`0NEy8}nG^mZ0U_1`QytaR_hdWU1gmvW4ju^RpG&@}Nr1oD-^0n~+( zmE(ZoeMF4+Rmn#Opp5;6Uu`{*^(*uIRMSq+{mFi|>N0dFr)9vITFV^@a_&$y`W<;3 zsy}k*z_f*M>B)=-=aK%%Xu5Yz7tB|rEjBh)X>Wko$XzDid)eHH@fVnbFDuLX9BH^W zMlS5D9GWhj*DlH4N8kdJkzdTmANxM=Qttb}^G1BEJ%aJV+Ka&jBb^Zsp{;#DdA2up zYz#sRPnq#~JTih#zVo&?`x$W`PAWY9BV%shJnzM`C&s?DN4B}b4XYlg2)Z7sRoB|@ ztW+EP+Z)y}GaPKK^Edg!tJV65Pjv+X&2H7_@&~-0GFE9_vUJ(<6)SIEmD9r>Ztw=1 zL#oHS!|(R0zUD|15Sw{wT2)Q%DE{DsbwTfTe<b?CeFc-3XMm#gkbFz9UxtGFsun2@ICu-fQq$?cPoFz9dE9x@i9D~w{AP0l&PYR;p% zv3`&@uAwS#sKHgA)7QLOKpbySW*!Q=f?*h)uK~X+ptiU|Ay{F28I-X+lK4ZabE6t+ zak*gzBTb=5OG|TboX`%(raL#*s5SQ6P^}x+tt*EG76{b4+z+VXW{y1q4wcKF47{3m zdno7+hrLa?6u@r{YjOJ-EJARQ99_9js$vxy?o+OMz=0}Ta|knL_jp5Y$^FfSH^zWf zYPhi1Qw~=0cw2KPf=ujf^@nq1rD4W-zWvU+yEfbFoK;q}CD>dqt!Q+QIzB697(IV) zvevAxvaptdbnsrr;7~R$;hDyqFtlS5xlt?p9VmUUbVup8OQWUjrMpVMSNd$}4@+Mt z{kzgVr96~X%007j)m88IczvRMF%`ZB`Ft#sus0>ptnl&zaabEBGKcMFs6GX`uj=F;}X7s^j{6Xq+t^BJYZCrlW&bvZU!hl` z0RH0wcz*$WqyT;^JZ52%$Gx9$F)zCfGWM>7zf1Fb2L=X6g1(gW`b@$frgMYG;PXuQeCT(A=4*w7R})?>;n&i72uS#&0@WC`{!skK#A2|y95IL0KFV@f3BwVbY84|7%pPLBBVGxV* z7Z=7>N_ZFHw@Ub4!f%)GlUEo6Y?Sa}!na8HG!nGcgrgnSkey?M3w`l-T1=f1{XCk- z7bJYZAZPm|{F^jiOD{19&<%eO#@z+*u6`on!Vdh|5A=}jaGL0azCWjV@m$K)&d_5!rXLWF z^=sgBJ%W%9nsBt+3|bEhF5}G^KG=q-T*7ZLV78TT!G9CsbrODp*29k_KIKIJPZBQp zkDBOlT-fdB5-#Wu z6u?Ib7xI6f=6xZ>Zy}G+ca?+-Iqeco$1rve;ex-L=7r3`?_cO&A5c6I{ZI_Vhl!BF zPlo>2M6Xi2p#K-Di+Ce&{5xMvl}47~0}tLjX(oIgl`-2Y;X)4P4!GbM0p6xU{^lli z$r5;IS)pFv5DvG5R^N2fc7M1bQeWY2ZoDZF*kOF#2-n~Hy>57VaxZhaJS*#$d+L|M ztK+gIR*!F$Yt@RSD_2x5uV3YMEnfi-ayMHSG3Bz$r_Y!j~S-@yW*BotKRcc;CWt#d!P=70p;|a%YgbV%~2;W3_6zZ68{*jN^N245N%p>T@e@J}R#PK5d-xo#_^5CClp*=+s z>jnRK3~^Bx^8ASKF6toG3s#GuN4`{K;%{y*%B&7EihSsjmkR? z_&^LimFHp1@)E)QpejG?Q`P3`nXN;P_8q#FL<(BNwF1iV3Jdikw@(VAS1 zUf=`t$oXOF5N?I1#z=jZSxa*uQ0H%K2@sThjU0!%;U|#rIMjI3H^cr=-9vtuEpI4f zz+4J^dad&`x(u1HW|ZIunJf$y18qQS81|7Qs%U5c5|6jV+vGtTgYkCwfgKdxV2UN% z&4v(Ve5(~@KD1a{e?pEPh@b4vVlcBA89QipSippTQ#0RrFdEndK=~fkD3s9tgYg5$ z5phBI2OiH6UjiR<8l`cMXG0BdMp{fFpGRd1U%U;!PSAm7Uj{J>viyqx&L{s~+z=u4 z@^9l^1LR}dygnl=?0AZ?ZelgIxHpvk9ymx{eHmgFYUU+BLcnz3A&OU7C%48SMh zqMyh&Lwiwmx*4*lne=VyPuqB=N%RvyJrraN+y0083p$a@v_WW)OvFhCssAt;7MIaN m_{jEu1Tfk5$lx7XP)}jSl;rW_(3Y>iXQ|PtU+N%L+5ZCs;_M^< literal 0 HcmV?d00001 diff --git a/SYSTEM/driver/net/e1000.drv b/SYSTEM/driver/net/e1000.drv index 53162fee664012f33ada771cba785df5d96b29f7..d507d64dd32b77a38d45f5b06a44f5dd299e5ed4 100644 GIT binary patch literal 11488 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 literal 7944 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 E0TXO5TL1t6 delta 2321 zcmZ`)4NP0d9e?+LU@+t;2?Qv{=9`3;-~w4UB)9cenE{>x{s7z|ts9Qx7i;tyBV>c79HS3b5&9bpg?G$aMNEzK)6w<-l?;bST zChbY*``_>X^}GN5?>rKFa3uENP-o^$p=5nCg~z{Jm*GfVT{+JEBt+|#Y)NA3EDeW& zvdQ3+Pd@5?qTR>7n$pw5w2nA4JiE&zDOn-xbbp-h&e@tfq-b+2F}GbIbL`p@(^^mS z<2mIeFPFkFmL*BlDSbA->|SKo`i9t`|8$&9t2zAroB$b#72j7(LL-XaHxw`)i!)74 zu9zmW>@9(FF`OZXoRD+ulB8%0ir#ZNKu4#Tk(I(ULo!YCaU*q-k(=Br~%4MDFcS$1!cV7yj;F&6jssU)?=3 z-S|kbG81fYDV2u*C<|I?AA**idJHUtR(Uq@Q>E75Psgul#vufv<1?Dk4Iz=tq2qB) z{UPMQ^z^%!=JSx@Ui^SL9pDuk+w%`vZdbL))BoZ2T+G;bj_iC6kWV*65FZ_#@sdxy ziH~jGfsZB0l(ZXkbWC{r^oYm}i>qGp5AVQVBr_z@Q7a%FRl9jO-&15&LbocR=#duK zD|*L}s4Sn&zmY9!>RuF8(}#Qc+neGf$wLJPiH+YXSPr%JGh?8OpaO$)GNZ~2&by!K zdnVc7-2?PQ9L*C+*b?3OIEcAdQK|o_rQwk_;W&LMlJpj$AdKgCm6&PxuvJ!1qyXwl zsM+MsKwEn-uq%jjy3#f6|6qS`igy;R3!SwN&+sTB(vs!YcZC@}XPFUEz&`oyb#Om$ z6TOp!3Df?ZypDkCZ1)C0Tl3*{8D3WY`K{>Vvvja6Nv|8{z*}BmRf*}g3z?OpVRvbLSq5|ll_9p`84E>_dLS%i>vUMOTe|6 z7F>;tlAvQ3o6!5>W<;ogmWV2Rj&#)NgKBb@KiKNs<#M4EXHkkb{mPX8{7zh?8rZL9 z|K_R`l7QB)q0Hz*QO1N37zvAH=_(@+7_Gu%!Z`U-hQ+8#6iDpuh}f#dNT`9nm!7gu@|*&=f{Gb3&8}%R2&bTHO5KZD5PDe5R*^c_BxYO`X9adb2X;b{ z4C6UMz3Rbk6&}kG@UqQ<3z7FjQLjHClxVDbcSzBPg&~?Q#L$fMc1S`%XCG@7JsD>M zcTd5K@`NkVzeKtfbl$ddft(aN7a9b*@J!GCwG}fSB|wDt$#>M*78;pqaouQfz1Pye zLa2J?bE_=X8zgD|0q{zJplGwn+8u~#RqpzKF~k!HtDlRil?n^P@f0#OPQ%ZNCeb>E@MQhnMI<+F=>-V~U|v-Y ze1v$*R518!vTWHjy_Zl8)34s5YAf8j20W8xMB4MZ_Y&SJld2B`q!Vf95yvA5S*^ma z-gE(g_tGvzFx5pG&ILH=c>|zKkEUVaqZ6J1Ea`Y!ub4&`d}M`bJ#jkjcp3m`oWBp; zU$(gKiTm`*LoWX7vhS>)bwW#~<{!hBE;;!2@;rIc$@i8!<*1YYp!^R_Z-%!6D@#$DP25HPo>93wsfYQAPZGb-7(Gf|1+9_yD)a&iB;8?6Mop; z%L^MQZ+_@Cp3`udf70;%!Oq4PIvWXwjFEzoijjt~4kI1IhLM4hInmi@k2_9~H>0jL zdyRavA^Lcmy@V7;pJ}r@a~B#=JAAmd)|b_I{L@Vn=i2P)L_Xw*UTC*}OFmQ*EehIq zKD6LkwK=+G+>50w5vgCi|M=J%u0ck_N%4m0iJ<+Id?0&b%O3kFBF_{?-`Q(_M81|1 zmG{}}GHUwV#>ngbflV0Rll22sB6%J{JjOA*-2W2NqwzAf!?RAwmKX5J+}GRJxn& zj@jwDHMr7PAGJO|s;`eeQ6NFIup8)Z;HfVJYH1M*7BMll0=6-HH1qyvW;Y3G`}X%^ z?m7Q+&OP^@d+xb+CbaAt-2U$1`YbN%C%bZHIUk+QXunv?BJ*rAmjClBG1JjdJoCK_0zh+BV_L8XB|HcbwBQib99QMs& z`9&Q+n28}oWP0lQS)kzLXEQaNp1OV>;l=)6zYe>wOiv~(U!uk;G^TG~#4wWQoZ$Jh zk%l;!UkAYE_h0JS|8erChIure`9kB0Y<8Y^&lEYval}0n`1Gm8a8$+>qQ>)`> zm-I@U{oaLa$0C2)6i?vF$a`5&VA`mx$u!`;4kyGot;4%xoYvt)H83u9RN$piGuX_) zN29VP(b)S;--vOV=`UiOX8L9zBlUjv^}vSI+{qM)KC9dqr&-O4ahla^a+nghm^zc4 z85lWQnngh`_N{Cs^GQ84$=&@Cm5`tGA!(BcXb9{YeLwp`;M380lPSzF-k6N&?M?Y2 z#=-aAl*=&=zW1hl*%K(Z)0U^LOUG;9A-nvR_B@8M)f}gce^^{0t`r{;SFP+wz7we5 z^587w60gy@iRM7?&L@V_TD3bzlQ=hic3|FJ)d_Jrmue0)-{nZB07A&`LdV5|wYykR z+qb4_`d{7?SeoWY_;!@WCCm!6rEPasgEIx|m9QFgeUPh-&2bIBP2~IgFhm9&B(Lc6 z!P%5ZUH0!Gc_>Y+$~9rEN87%0k{rWqYEWoQwy7aco8uc-9%G*lo9vmo&Dj1n1q}4Z zkiN6Ofnx|9Pa(&jjl>vz{8(&%Y(c?u6i#ez%SiBy;CTm6wN*KW?t{>#A&u+nzCsvl zWyrzEfddjeGf-?`wfi33YK+aP&qNr@Sm#W4q{0I8HDM0u)bTYYA$U}*jnKH8k{YJt zF&|Hx{ciZ9#IBgy7Xd;3JyiQU?L-Hnu={w&F%tMKFU??>j@cs^rlQ8OehI^r{&F=6 z4I-%ua@sXKoA|QD3}az1OhFzD#Ft(PK*r|1brk{kO?VWn=(lI~)b#uf20~pa4X?>E z8Mx5MPo(DUv+T{#mkB~+O3LEE#}g!H2u0h*hC3~Ysayb>V!8nxnlPor*Gf2))6;6J z_#TbKru@O?+u4FWfgG!Y49fDoP{y=>XbN^F31eMY0IWwaV}Uthe@ti`5?^!u3qbZx z`GkGB>-r+c=R#v~{2i`~M#ov#(}9=nwmLg54MQ?17i@k*okc}Zw<9GI+fi$+zD%Pu z?!)M;x@cClHxLwr<`xFj;l84(lIc=soOpm0mCq1I4vSE%bhXb((POh5T^B3gN~W%2 z|CEF4I7TudIm=sY%F5QxOl^f$(TIg3S780R2^q$~bb%UPVE#<-q`}-4k+;B^HnE8O zL@z112(l{Q+587dK}{KXADjjq?;<+VG=}LMj)e0tfyraGCoct0<1%51Aucrq4vhKX zNX^bBcOiQ|n@XdwUV%}-HugtO&5;rcbr_)TM|L2VChx+1?hAs@XqfJm-U&xN? z?uJKiop5V8l0yUT zY5y&@ZFKw*c^OQ|VMJchORbJlrgK)`8#LI?bYZL$2ZDOcebXQ*wM&@ zeU3d!?*4~3#;1kEr>&EG!g-kEn8AYeBt{^$D2cV22D$LC<>Z+5pWi`?S>95- zhA#H!Nrd%O5Ovzlay&MXFa4|UbGVJfj@0`j28@C>aL{P8P?9>nj_{v9!Z8@LS`p#T zYb1`n!?5%6ZD57Q#Q!EHF|08uuM*=-IjnKHKLJFK>>LNnU;$$t*eaOrL-!qGhD8t{$u%R;*-kgLh+dHNH2ryLZ<;RLtEQ|*e>m98?wxyc zs_t=Fd=MKQIWCJEM7(1hZYMRMDndbH1@^lm$;a;iFSMA?3Z5Tpo!`FyYb)yYBz6A7 zzVD_(8Ld#$jJaoM?oiKHw@F*v62{FoT z_L1%zQ|-xWsGuj@`Bk62q;wZ~i?ecY=Gbr_+~>aTG*laqnbo3lLUdoeWm0cGy5qn?e}3Kq zHPRJ|6Fl@Wf3($PQzGW1|93=6BR!#E_B94_zgJygDjKlol*(Po4D4A z2C-kE-3maMK}aYAxg_SN<7qoIY$n4L;6TS`iVOh0#O}q&G`|hKDlMoHHia@K5h7xf z6bV_wko}aKIKsV&U3mIdz9K50ilMJlt=wUy?FUgNYQeNp6MUr*&Yvn zLX+l0XgY26X>-u_x5B>)1-u8;hrToljncDX=n~q4)De44VsDA;8Ik=^WRDm64HMnL zF{1lAC)Cy=ORUNXn>*BFJJmtu-u?t$rkpnAW1IVOkEyLtXnHzl{5+w_nv+=(iWr3^ zm5o%0C@G@Q6lA+?SXP@m+~fG9E5thP>s+MW22SI9O8aB@jD+3z+IY~9@YwlOH#!bp z$Iib63ZnP0Q9FJPfE;%YH?=7t__OAWcha1w{}vJ5+DBjkhZN!sC3}46qOmhhlW6tX z@mC97DU{H6rvz(DMJQyfP!fNM(;U4&h2GmQ8&Hkhk!dKPD8#rYs40`CYvZ~D^-2y# z>#-ouy-6=EYG)GNyqqp$aAf*sg=x0T?Dc)yBWzfgkPaqFd8{$tcTpS0KAJqs}f z?Xo05(J06cjrscn=tq#(gTZ-s6`XR?{rMI5jRHYdG>tESB@6P?U_uu;r~6{QfAM&8 zt5CZEh|Qm}4|3Pj$w}}<20&g0Vq;HX>4v&?#NlGSl{(sC945#O8kI0pko};9ro}^{ zWvmD_Bc|65QzcP;Cp6}pWz}wEZ>Y`Y=BmHQU4rKXB>pFp%@Ll*|MuRBg}QI(Y|`JM z2O6A#=pWo1{nz@h|1Ra0FlHm((ajv>_MVN&)sE!I-c*Q&pAO_h0ujm zf7KmA<4jZNa#BLrUpPcZjR{xW&56L16T;}}Nf~YZPR&-RoP7i)xEPG+veWDqKRW3 z8H=tGjurbYM<*lWx=|2YY(A-8X~u_w8&m;&(ktdxQ8{B33NPSG$9kw~Upf|{KT-L_ z=F_?q-o6&+Xw|-aY|F=0l0N3jlguJ>bWE7f#umh=Imql zf0g|gmb>?^z#k@UbC%kaFT{t$?=LA)ZkmF{{@k0`ef^GL+>=o^%rna`lik%)@p3xPaoi%Bu=qwe2PyOr&nlf z`cANL;6P(!793^ScxGH2#}+G=T3zBsP^|MHR)T?MEDsf zU2AHWtU<3+K<2UqPH%1&s6*dkiUqEoILE_~r2v^>P+GP7@DMOW;y%oQ^yX5@^QLs; z3DqlTW*M|&(aV1cMmrL{{CQHEa+aQnr|~>QuZM*Z>-3!2aco=#QB&hp+ab)KDdH$S z6#f^BQ1>Ey9U6(@3P5^5(n%cQYe7-K+mGVBiJprafgPI5kx__tQ0!#|C(Tk*CZ2ZF z-Fj(kW1A~NVbsVl+lqo|u+?18!OpAtZKuOxFYKYWJ59dh|ioG!{A z4}(EY95+6!eJ55g^uM+pqLJ39cOw0&*<&tClY|v?bh#{VgJ2wg#*!!g0;Hszbc#4F zL>w26vm%ZM+{yA$07ruiZwS_E^eG4=KvF3@BI}$t-<$Fo%8y^)!vnsgbpe`w2087hW=--Ls?412iV$DrT zLj3}*seOv?LFo23ToLN3d7eK%i{QF2v_#jmSa}Fp=t(cWuqo9T=OY< zwvUbzQI^GoFmUT=L+FChns7xE@BlluYBRqX=a>Q8sr_t0t{5E``cqQEu_AnR*G&`7 zYA~O0ti!Au#^VDTsCU(q;enS-Tb3Ad2GLEeOY+Ez%@jCuq30R^6|2b8C7cnj)G;QAAY>t zzNzf-l^JNaZ?4+VC;gv-12*fTB^l*Ulx^Cwv3%;9&6_MtulkzJk5^<=S3X&8VK!G( z^x;-pQAtMihRQ8l%GYK*x+8=1D%S-R@%6i_?Pd1LHGQfX8C6eMs>;jO?qK@)2ow}P zln@)DCEzVwFam>~ld*mM<|*sTsx#J=*~_<=?Fd8)%_*873S?B3S8v|9t$b~utI(H< zfzQd<>L{<;(SQ6IVqklbZB*|hqNWyRZDn=Yqk;E}rrqBwqp9}}qi@ifOMB&}^34u= zAF8>Fq1Ybub9WTgbri8p9Yx%rxeWy_E{j`V+VGf*8^i4_ZTJz|VXjhL6<2lbx2JU1 z)lp5ST-+em8N^!_{irn14__Yf7hQY?TumH9-!9e#}7$I1*&6CcMw!5zvHB-=e{I=$^}C`06O0q_-Pm z_?GAhqxiNcUgC}7nqyo<=`PBm(~@YEaL{Q#MZ*U%c`n@m#Z3D(L~-)~{Ea9+X8^uG ziq9Q@hX{9KX88jI{~I;148YGvajY?@=>I-I+m3%)ga+V$i{di};61=;DGT!S$urhi zL@-6_s3d9-Tbm@{WKd6kGr;eysQz71yuhO48=`n-6yJBBj=!rn%pBkhXJkt1qlyeO zN)t_oU>F?4{*T0=%!a`p?sP+$%nffi@8KTFShs3pd0BP&s>&_Qx>eErq%&(DuLjXD zN9HEpQMqN+n)T&tHdH${HB`%7X&~KtM?Z}>{)rZxPajuW^tz9HusZtCFm z(G|yjdGr}>*5KGaag{c_O8lOvd{1e^=irm~M)_FF29mAA8}jS8^jMb1c1=k`WgRzd zaBQL_^$oAqarY)%h)(>3W_YWP%S_p~OOHisG$cb1ko1OJFE=T{^@5I-Y1&VFxy->A NqfimskWQ+m2m4`fwinu@%9txY3B$^>+ zZ5be47xr+L;~v(Ri}*w2VwS?LCys&xDJsb3KPshX z4oGcyAj>4Ukc&MgO~};R39#b1cu|Eyof}f?Is2!mtavNlyjc83Z(OO?ewQiL ze`a3R%qHuZ!!<1&NzeA|8T)RWV+hw6=Mo5a7+k6I1C6D4P1yE+hp;cfI-`tkd(4+R zwn1tiFoSN8#t$qSLl)fa@bNgOID8_`DGn=SsW5Ax^ya{cbh-5Lz@pJ)?cJuI#5u+E zVw_Vj9<(|_6}+@;~eHbsO=l)F!w=iKUp&7 zn5QZG(zgFDs>^2>Uqn&nnzN+h7w`-DMf_s^*+p%I=|Fvkf)S`CtwQVdMN%MVQ##qJ z$Qx-ytlvFCnmwpGsh`Rv6iH_W+47XlH~ZKv52GwOQ>R6gBQ>dt{*9s1{9Id7g37%* zT)LRM(e6y4C|#gd49Wq=NxiZ*+ilFfX*lMtf~w;bnRA6r>o`S9<9S;uq=$&q3{$h0 z-Aqm+IS~6F0^u~Q1y6CVUEt&}F9yX{7+iz_IkNrXQE+!~u5Aztx{ehV-&T*sH+&;* z7K?A}07M)u;PQ=>nLFJMJIydmbPW@g%E8j&E1YW{nIt55$ce&O8yoA%XcF&Q#0Rpr z9p~E}u${Ds-$gUIe20`;xG^)2%pi6|3dyQ{`o^Ks&cfdhuzAex4y5aiLZF$m79X;hu-SXz3aE!>Mp7P_}@)JSfz3JS%CShk!g_GS1r-H(1;$T-#`TO1SKu~BQ z_48u^vPlwf`1PR!X(l~idTZELdp#uE2P3U@NM(mmPlG=RO=RH&U}T{)vY`(}d4>7| zjFp4*B_t=ZJO*hLs4*W8dEQz9c z@oXt~`X}hL=R)T_tV(cyV;u=#O96nlVT2OP%cH2a)A#Mlx%(IKwgZB;ckYurb`wr2 zd{QCzmKY`IWI^{$G_97GLwt>F+(S{;>X2}dXra(hLai2tIzcISYS}?qyK@YwI&Npc zyY--3=61Cdv^eh3wn4mn*6ceouu?- zp@9JB0IdHpT4NHwLnQmig1^FoLH`MnM4wP`55X^xw0PfA=P>Mr7{Z84sdPivAZQ%G z7uf3~vDvZs(+Tix62FS{#p>~i%Z1a$#L+gW#ZKfcn6>S=hvcX+hXefv8_+NWs($?Vs13A(yPRj>c?uHASu-De7zX*$$e0m5zuL+v)6Nty6j@W-pFq z)wuYCy)uN=S77zg;a=j#@FfXYM2fvO2UsIvUj_@he;#G0B7a8sB+9mrK9 z3Kv~9Z%CDRqEjEeQ%s$O)23e-5jrnl>NHM9VVTnTFN3m6RAWUxC z9R!Wge@5tSpSK}SOP^e`=vxb3%AcgwHUcw?yAD9jd5WO82Y6}rq`^5u5N=Yd#b;

Z{9LKvbMR)2LqO zMM?}iuO)!hRHx|9TYT&+=tbw$eDyP8QGkilq+64oXF^k@g_Flk@;{7;ynD&SK}`bL z?wfe2={h2Vu|ROA$Quq{DGqGgyofC0Q*dl@Ei*b>nydc^Y;PLg{d3t zV!NOV;2!t}RZh-rX-G4H9EVb8AhEQjliUopXr)>FmVb(WvLeJDgC1sKO5(6RAgi%uy=!7Z|XDAwqPBBPhrui5wlpVrfg)x8{ zJ~9;rg6v?Bn>}nc2HqK%faCaaA?CGVIo zp};j+Q#5?bcZqz`6)=_*R!F)P-7fYqiOEn5sMXV2wSSs{VWJt*^hehnUS_E^kWOrJ*@>2lg%Udt3Qi6;Ncwaj3~V)DOf6UL!fTN@O}7KPTK02pLWnnhL*&D3`I=ckKx8m zxSSLyw#jM4A(KcM9Wet{pGpAU7p~$d4rcjZ-pPFGU$To??7zB;8PRvuv&&z!Ra;lC zr&c|?cC~ext;(OVo2ir>m5KhfyO}H|Wr%2LB%$ CF8!nc diff --git a/SYSTEM/system/deshab64/shell.elf b/SYSTEM/system/deshab64/shell.elf new file mode 100644 index 0000000000000000000000000000000000000000..de56ea03aa2c16c48a74073e40f0fbf300e21c8d GIT binary patch literal 180160 zcmeEP349bq)}IM8%8}5pCL9tW-k{8RagJ( zy;t>Ky{f+7mN_iWXf!B)%?tw#jB3ME2K#T!em_phVi;`bV`zr|6AZ0bUNhbC=MVjL z@8@+Zzsh||HL-I3dfNp5CS6H44F=^mu-qwhW12sI$genzZW;{AuTqYuHvQj(KobH@ z2s9zkgg_GlO$an0(1bt}0!;`sA<%??J`or#O5aEgxkq*t-IwkvGm75V(H;C zjh{+qOr&zzTQVor-8^yP#7wD?hN23v$p6>Ox;W?}J@7XESKcUND!;o@o z%Fz`3*`-X^Z}i_0(OF^=2P{u881lX;H=l}<^jB`a?RkE+XAxjpGXQDD^2Zb9J++@2 z1IZtQ$sI(dsF^<|(yEdl5II35Ung?9N-iVv7L}Y&2$dW~ zjieV6_E_sXJ${6 z_Z+DNSxoQC$e)OOR3*P6@_UusM5Ie4ONe|yCEp;Dy#lN1IU>)eRx$EXBH1f2vVchT z3XIGnlDz^WClWbLEqgVQT2&&E+Jn-GWPHWSUO?nBwN;&n)EXI2B

a`RFE)OVrH! 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%0DUY#gAEWTn&}BAhiqA2T;{ zfnJ@e)R?pU$keT8`8f|Z9T+R3Fp)Yu&p=YU7x9rD$1jRpz&*vLlNfQae)huH1+!8U zi}*>Yzntd%M$`Xouoyq^ug7GAnDjH9^CDv+?`4O3$fs6wwulcK(X>CFY@TjJf^lPF zLBmPvp(UT_ZqMuFw9|ISTOyP?L z8y7BFpkElRkC+z)wwfQgAeN&=vUO51Z)uiZ#)q~3fzRYq!|M5d-p9LmrM^8Ew(hUzTklesJ2y~?$+ySw zEg*;x^wc84^Z4^ZivKkB%7rRs0Z00f1~5$RkWvaN1fq~7=MggHviQ%X7Rm|CDrEl{LtoKh8E_^pz$ zP@T~RstEO@EmrDzSJn1ykc}En`{C~VB)M6K@XJC>g=!UzP!qy-CuxgB?@ZG zx3mIxl$;uKH86a0ZbpA4e;O+U>k_X-(g}T){9&qPKupd$6s2TLQJWc{=J&TPQwl6n z*I9^~5cjr%Pg9M;Cse#XNx_%F#HJ9gRiyID42Fea4?Hzo_=sZqXOJzKD5=10cNOvQge|9ZDM(`PP@ zh>AirF-v2nYXxJXV&+Ar^3A*^r#g%IR;e?Zd3E+Gj80wF%xiF4RXTBMd{9c=-pp%I zD~Mf20}{Wtnt2UxrAb}HsY9Cs;n!8kfjD(BAt0bx5faoB{>FE34axIQJ>J}_ZCkyP zz-h6NTGJeDct|}MRqDJ#TO_NrFL3qF>y(%1OnQ8i3S gnu*-KQaiWs>fG|V;R@<|uT;a|_<+>?&AnRwKh{4o5&!@I delta 25033 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 K`EipC{r?YcnwYKt diff --git a/build.ps1 b/build.ps1 index d8082f8..8608bb3 100644 --- a/build.ps1 +++ b/build.ps1 @@ -549,9 +549,28 @@ if (-not (Test-Path $NetmanOutput)) { } Write-Host "[build] netman Output: $NetmanOutput" +$ShellDir = Join-Path $Root 'CODE\shell' +$ShellOutput = Join-Path $SystemDir 'system\deshab64\shell.elf' + +Write-Host '[build] Building shell.elf...' +try { + & $make -C "$ShellDir" -f MAKEFILE "CC=$clang" "LD=$lld" + if ($LASTEXITCODE -ne 0) { + throw "make shell failed with exit code $LASTEXITCODE" + } +} +finally { +} + +if (-not (Test-Path $ShellOutput)) { + throw "shell build did not produce $ShellOutput" +} +Write-Host "[build] shell Output: $ShellOutput" + Write-Host '[build] Building DKM network drivers...' Build-DkmDriver (Join-Path $Root 'CODE\DKM\e1000\e1000.c') (Join-Path $SystemDir 'driver\net\e1000.drv') Build-DkmDriver (Join-Path $Root 'CODE\DKM\virtio_net\virtio_net.c') (Join-Path $SystemDir 'driver\net\virtio_net.drv') +Build-DkmDriver (Join-Path $Root 'CODE\DKM\ath9k\ath9k.c') (Join-Path $SystemDir 'driver\net\ath9k.drv') Write-Host "[build] Output: $Output" Write-Host "[build] DSK Output: $DskOutput"