diff --git a/Cargo.lock b/Cargo.lock index 8977fed3..29db9ad7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2693,6 +2693,21 @@ dependencies = [ "yggdrasil-abi", ] +[[package]] +name = "ygg_driver_net_rtl81xx" +version = "0.1.0" +dependencies = [ + "device-api", + "libk", + "libk-mm", + "libk-util", + "log", + "tock-registers 0.9.0", + "ygg_driver_net_core", + "ygg_driver_pci", + "yggdrasil-abi", +] + [[package]] name = "ygg_driver_nvme" version = "0.1.0" @@ -2867,6 +2882,7 @@ dependencies = [ "ygg_driver_input", "ygg_driver_net_core", "ygg_driver_net_loopback", + "ygg_driver_net_rtl81xx", "ygg_driver_nvme", "ygg_driver_pci", "ygg_driver_usb", diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index 06e36fc8..acb12b85 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -64,6 +64,7 @@ kernel-arch-x86.workspace = true ygg_driver_acpi.path = "driver/acpi" ygg_driver_nvme = { path = "driver/block/nvme" } +ygg_driver_net_rtl81xx.path = "driver/net/rtl81xx" acpi.workspace = true @@ -87,6 +88,7 @@ kernel-arch-aarch64.workspace = true kernel-arch-riscv64.workspace = true ygg_driver_acpi.path = "driver/acpi" +ygg_driver_net_rtl81xx.path = "driver/net/rtl81xx" [features] default = ["fb_console"] diff --git a/kernel/driver/net/rtl81xx/Cargo.toml b/kernel/driver/net/rtl81xx/Cargo.toml new file mode 100644 index 00000000..5622e0b3 --- /dev/null +++ b/kernel/driver/net/rtl81xx/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "ygg_driver_net_rtl81xx" +version = "0.1.0" +edition = "2024" + +[dependencies] +device-api.workspace = true +yggdrasil-abi.workspace = true +libk-mm.workspace = true +libk-util.workspace = true +libk.workspace = true + +ygg_driver_pci.path = "../../bus/pci" +ygg_driver_net_core.path = "../core" + +tock-registers.workspace = true +log.workspace = true diff --git a/kernel/driver/net/rtl81xx/src/lib.rs b/kernel/driver/net/rtl81xx/src/lib.rs new file mode 100644 index 00000000..1715f262 --- /dev/null +++ b/kernel/driver/net/rtl81xx/src/lib.rs @@ -0,0 +1,37 @@ +#![no_std] + +use alloc::sync::Arc; +use device_api::device::Device; +use libk::error::Error; +use rtl8139::Rtl8139; +use ygg_driver_pci::{ + device::{PciDeviceInfo, PreferredInterruptMode}, + PciBaseAddress, PciCommandRegister, PciConfigurationSpace, +}; + +extern crate alloc; + +pub mod rtl8139; + +pub fn probe_8169(_info: &PciDeviceInfo) -> Result, Error> { + todo!() +} + +pub fn probe_8139(info: &PciDeviceInfo) -> Result, Error> { + info.init_interrupts(PreferredInterruptMode::Msi)?; + + // Enable MMIO + interrupts + bus mastering + let mut command = info.config_space.command(); + command |= (PciCommandRegister::BUS_MASTER | PciCommandRegister::ENABLE_MEMORY).bits(); + command &= !(PciCommandRegister::ENABLE_IO | PciCommandRegister::DISABLE_INTERRUPTS).bits(); + info.config_space.set_command(command); + + let base = info + .config_space + .bar(1) + .and_then(PciBaseAddress::as_memory) + .ok_or(Error::InvalidArgument)?; + + let device = Rtl8139::new(base, info.clone())?; + Ok(Arc::new(device)) +} diff --git a/kernel/driver/net/rtl81xx/src/rtl8139.rs b/kernel/driver/net/rtl81xx/src/rtl8139.rs new file mode 100644 index 00000000..cd0c1557 --- /dev/null +++ b/kernel/driver/net/rtl81xx/src/rtl8139.rs @@ -0,0 +1,422 @@ +use core::mem::MaybeUninit; + +use alloc::sync::Arc; +use device_api::{device::Device, interrupt::InterruptHandler}; +use libk::error::Error; +use libk_mm::{ + address::{AsPhysicalAddress, PhysicalAddress}, + device::DeviceMemoryIo, + PageBox, +}; +use libk_util::{queue::BoundedQueue, sync::IrqSafeSpinlock, OneTimeInit}; +use tock_registers::{ + interfaces::{ReadWriteable, Readable, Writeable}, + register_bitfields, register_structs, + registers::{ReadOnly, ReadWrite}, +}; +use ygg_driver_net_core::{ + interface::{NetworkDevice, NetworkInterfaceType}, + Packet, +}; +use ygg_driver_pci::device::PciDeviceInfo; +use yggdrasil_abi::net::MacAddress; + +register_bitfields! { + u8, + + pub CR [ + /// Reset. Setting this bit forces the RTL8139D(L) to perform a software reset. This bit is + /// cleared by the RTL8139D(L) after the reset completes. + RST OFFSET(4) NUMBITS(1) [], + /// Receiver enable + RE OFFSET(3) NUMBITS(1) [], + /// Transmitter enable + TE OFFSET(2) NUMBITS(1) [], + /// Rx buffer empty + BUFE OFFSET(0) NUMBITS(1) [], + ], + + pub CONFIG1 [ + /// LED pin control + LEDS1 OFFSET(7) NUMBITS(1) [], + /// LED pin control + LEDS0 OFFSET(6) NUMBITS(1) [], + /// Driver load. Software may use this bit to make sure that the driver has been loaded. + DVRLOAD OFFSET(5) NUMBITS(1) [], + /// LWAKE active mode. The LWACT bit and LWPTN bit in CONFIG4 are used to program the LWAKE + /// pin's output signal. + LWACT OFFSET(4) NUMBITS(1) [], + /// The operational registers are mapped into PCI memory space + MEMMAP OFFSET(3) NUMBITS(1) [], + /// The operational registers are mapped into PCI I/O space + IOMAP OFFSET(2) NUMBITS(1) [], + /// Set to enable Vital Product Data + VPD OFFSET(1) NUMBITS(1) [], + /// Power management enable + PMEn OFFSET(0) NUMBITS(1) [], + ], +} + +register_bitfields! { + u16, + + pub IMR_ISR [ + /// System error interrupt + SERR OFFSET(15) NUMBITS(1) [], + /// Time out interrupt + TIMEOUT OFFSET(14) NUMBITS(1) [], + /// Cable length change interrupt + LENCHG OFFSET(13) NUMBITS(1) [], + /// Rx FIFO overflow interrupt + FOVW OFFSET(6) NUMBITS(1) [], + /// Packet underrun/Link change interrupt + PUN_LINKCHG OFFSET(5) NUMBITS(1) [], + /// Rx buffer overflow + RXOVW OFFSET(4) NUMBITS(1) [], + /// Tx error interrupt + TER OFFSET(3) NUMBITS(1) [], + /// Tx OK interrupt + TOK OFFSET(2) NUMBITS(1) [], + /// Rx error interrupt + RER OFFSET(1) NUMBITS(1) [], + /// Rx OK interrupt + ROK OFFSET(0) NUMBITS(1) [], + ], +} + +register_bitfields! { + u32, + + pub TSDn [ + /// Carrier sense lost + CRS OFFSET(31) NUMBITS(1) [], + /// Tx aborted + TABT OFFSET(30) NUMBITS(1) [], + /// Out of window collision + OWC OFFSET(29) NUMBITS(1) [], + /// CD heart beat + CDH OFFSET(28) NUMBITS(1) [], + /// Number of collision count + NCC OFFSET(24) NUMBITS(4) [], + /// Early Tx threshold + ERTXTH OFFSET(16) NUMBITS(6) [], + /// Tx OK + TOK OFFSET(15) NUMBITS(1) [], + /// Tx FIFO underrun + TUN OFFSET(14) NUMBITS(1) [], + /// Tx descriptor is owned by the DMA. Set to 0 by the driver to initiate a send + OWN OFFSET(13) NUMBITS(1) [], + /// Tx size + SIZE OFFSET(0) NUMBITS(13) [], + ], + + pub RCR [ + /// Early Rx threshold bits + ERTH OFFSET(24) NUMBITS(4) [], + /// Multiple early interrupt select + MULERINT OFFSET(17) NUMBITS(1) [], + /// TBD, didn't really understand the docs, lmao + RER8 OFFSET(16) NUMBITS(1) [], + /// Rx FIFO threshold + RXFTH OFFSET(13) NUMBITS(3) [], + /// Rx buffer length (+16 bytes) + RBLEN OFFSET(11) NUMBITS(2) [ + Len8K = 0, + Len16K = 1, + Len32K = 2, + Len64K = 3, + ], + /// Max DMA burst size per Rx DMA burst + MXDMA OFFSET(8) NUMBITS(3) [], + /// When 0: the packet will get split if crossing the Rx buffer end + /// When 1: the packet will get placed contiguously + WRAP OFFSET(7) NUMBITS(1) [], + /// Accept error packet + AER OFFSET(5) NUMBITS(1) [], + /// Accept runt (packets smaller than 64 bytes) + AR OFFSET(4) NUMBITS(1) [], + /// Accept broadcast packets + AB OFFSET(3) NUMBITS(1) [], + /// Accept multicast packets + AM OFFSET(2) NUMBITS(1) [], + /// Accept physical match + APM OFFSET(1) NUMBITS(1) [], + /// Accept all packets + AAP OFFSET(0) NUMBITS(1) [], + ], +} + +register_structs! { + #[allow(non_snake_case)] + pub Regs { + (0x0000 => pub IDRn: [ReadWrite; 2]), + (0x0008 => pub MARn: [ReadWrite; 8]), + (0x0010 => pub TSDn: [ReadWrite; 4]), + (0x0020 => pub TSADn: [ReadWrite; 4]), + (0x0030 => pub RBSTART: ReadWrite), + (0x0034 => pub ERBCR: ReadOnly), + (0x0036 => pub ERSR: ReadOnly), + (0x0037 => pub CR: ReadWrite), + (0x0038 => pub CAPR: ReadWrite), + (0x003A => pub CBR: ReadOnly), + (0x003C => pub IMR: ReadWrite), + (0x003E => pub ISR: ReadWrite), + (0x0040 => pub TCR: ReadWrite), + (0x0044 => pub RCR: ReadWrite), + (0x0048 => pub TCTR: ReadWrite), + (0x004C => pub MPC: ReadWrite), + (0x0050 => pub r9346CR: ReadWrite), + (0x0051 => pub CONFIG0: ReadOnly), + (0x0052 => pub CONFIG1: ReadWrite), + (0x0053 => _1), + (0x0054 => pub TIMERINT: ReadWrite), + (0x0058 => pub MSR: ReadWrite), + (0x0059 => pub CONFIG3: ReadWrite), + (0x005A => pub CONFIG4: ReadWrite), + (0x005B => _2), + (0x005C => pub MULINT: ReadWrite), + (0x005E => pub RERID: ReadOnly), + (0x005F => _3), + (0x0060 => pub TSAD: ReadOnly), + (0x0062 => pub BMCR: ReadWrite), + (0x0064 => pub BMSR: ReadOnly), + (0x0066 => pub ANAR: ReadWrite), + (0x0068 => pub ANLPAR: ReadOnly), + (0x006A => pub ANER: ReadOnly), + (0x006C => pub DIS: ReadOnly), + (0x006E => pub FCSC: ReadOnly), + (0x0070 => pub NWAYTR: ReadWrite), + (0x0072 => pub REC: ReadOnly), + (0x0074 => pub CSCR: ReadWrite), + (0x0076 => _4), + (0x0078 => pub PHY1_PARM: ReadWrite), + (0x007C => pub TW_PARM: ReadWrite), + (0x0080 => pub PHY2_PARM: ReadWrite), + (0x0081 => _5), + (0x0084 => pub CRCn: [ReadWrite; 8]), + (0x008C => pub WAKEUPn: [ReadWrite; 16]), + (0x00CC => pub LSBCRCn: [ReadWrite; 8]), + (0x00D4 => _6), + (0x00D8 => pub CONFIG5: ReadWrite), + (0x00D9 => _7), + (0x0100 => @END), + } +} + +struct Rx { + buffer: PageBox<[MaybeUninit]>, + rd: usize, +} + +// TODO place a secondary Tx queue here, to send the queued packets when more slots become +// available +struct Tx { + buffers: [Option>; 4], + wr: usize, + rd: usize, + queue: BoundedQueue>, +} + +pub struct Rtl8139 { + regs: IrqSafeSpinlock>, + mac: MacAddress, + pci: PciDeviceInfo, + + nic: OneTimeInit, + rx: OneTimeInit>, + tx: IrqSafeSpinlock, +} + +impl Tx { + pub fn tx_now(&mut self, regs: &Regs, packet: PageBox<[u8]>) -> Result<(), Error> { + let packet_address = unsafe { packet.as_physical_address() } + .try_into_u32() + .map_err(|_| Error::InvalidArgument)?; + let packet_len = packet.len(); + if packet_len > 1500 { + return Err(Error::InvalidArgument); + } + + self.buffers[self.wr] = Some(packet); + regs.TSADn[self.wr].set(packet_address); + regs.TSDn[self.wr].write(TSDn::SIZE.val(packet_len as u32)); + + self.wr = (self.wr + 1) % 4; + + Ok(()) + } + + pub fn is_full(&self) -> bool { + (self.wr + 1) % 4 == self.rd + } +} + +impl Rtl8139 { + // 8K + overflow space + const RX_BUFFER_LEN: usize = 8192; + const RX_BUFFER_OVERFLOW: usize = 4096; + + pub fn new(base: PhysicalAddress, info: PciDeviceInfo) -> Result { + let regs = unsafe { DeviceMemoryIo::::map(base, Default::default()) }?; + let mac0 = regs.IDRn[0].get().to_le_bytes(); + let mac1 = regs.IDRn[1].get().to_le_bytes(); + let mac = MacAddress::from([mac0[0], mac0[1], mac0[2], mac0[3], mac1[0], mac1[1]]); + + Ok(Self { + mac, + regs: IrqSafeSpinlock::new(regs), + pci: info, + + nic: OneTimeInit::new(), + rx: OneTimeInit::new(), + tx: IrqSafeSpinlock::new(Tx { + buffers: [const { None }; 4], + wr: 0, + rd: 0, + queue: BoundedQueue::new(64), + }), + }) + } +} + +impl InterruptHandler for Rtl8139 { + fn handle_irq(self: Arc, _vector: Option) -> bool { + let regs = self.regs.lock(); + let status = regs.ISR.extract(); + // Clear ISR bits + regs.ISR.write(IMR_ISR::TOK::SET + IMR_ISR::ROK::SET); + + let mut any = false; + if status.matches_all(IMR_ISR::TOK::SET) { + let mut tx = self.tx.lock(); + + tx.rd = (tx.rd + 1) % 4; + if let Some(packet) = tx.queue.pop() { + // Should not fail, we just freed a single descriptor + tx.tx_now(®s, packet).unwrap(); + } + + any = true; + } + if status.matches_all(IMR_ISR::ROK::SET) { + let nic = *self.nic.get(); + let mut rx = self.rx.get().lock(); + let cbr = regs.CBR.get(); + + loop { + let rx_pos = rx.rd % Self::RX_BUFFER_LEN; + if rx_pos == cbr as usize { + break; + } + + // 4-byte preamble + let rx_len_0 = unsafe { rx.buffer[rx_pos + 2].assume_init() }; + let rx_len_1 = unsafe { rx.buffer[rx_pos + 3].assume_init() }; + let rx_len = u16::from_le_bytes([rx_len_0, rx_len_1]) as usize; + + if rx_len >= 16 { + if let Ok(mut packet_buf) = PageBox::new_uninit_slice(rx_len) { + packet_buf.copy_from_slice(&rx.buffer[rx_pos + 4..rx_pos + rx_len + 4]); + let packet_buf = unsafe { packet_buf.assume_init_slice() }; + let packet = Packet::new(packet_buf, 0, nic); + ygg_driver_net_core::receive_packet(packet).ok(); + } + } + + // rx_len + 4, aligned to 4 bytes + let total_len = (rx_len + 7) & !3; + rx.rd = rx.rd.wrapping_add(total_len); + } + + regs.CAPR.set((rx.rd as u16).max(cbr).wrapping_sub(0x10)); + rx.rd %= Self::RX_BUFFER_LEN; + + any = true; + } + + any + } +} + +impl Device for Rtl8139 { + unsafe fn init(self: Arc) -> Result<(), Error> { + log::info!("Initialize rtl8139 driver"); + log::info!("MAC: {}", self.mac); + + // Setup the initial Rx buffer + let rx_buffer = PageBox::new_uninit_slice(Self::RX_BUFFER_LEN + Self::RX_BUFFER_OVERFLOW)?; + let rx_buffer_address = unsafe { rx_buffer.as_physical_address() } + .try_into_u32() + .map_err(|_| Error::InvalidArgument)?; + + self.pci.map_interrupt(Default::default(), self.clone())?; + + let regs = self.regs.lock(); + + // Power up the device + regs.CONFIG1.set(0); + + // Reset the device + let mut timeout = 1000000; + regs.CR.write(CR::RST::SET); + while timeout > 0 && regs.CR.matches_all(CR::RST::SET) { + core::hint::spin_loop(); + timeout -= 1; + } + if timeout == 0 { + log::error!("rtl8139: reset timed out"); + return Err(Error::TimedOut); + } + + // Configure Rx + regs.RBSTART.set(rx_buffer_address); + regs.RCR + .write(RCR::WRAP::SET + RCR::AB::SET + RCR::APM::SET + RCR::AR::SET + RCR::AM::SET); + + // Enable Rx/Tx interrupts + regs.IMR.write(IMR_ISR::ROK::SET + IMR_ISR::TOK::SET); + + // Start Rx/Tx + regs.CR.modify(CR::TE::SET + CR::RE::SET); + + self.rx.init(IrqSafeSpinlock::new(Rx { + buffer: rx_buffer, + rd: 0, + })); + let nic = + ygg_driver_net_core::register_interface(NetworkInterfaceType::Ethernet, self.clone()); + self.nic.init(nic.id()); + + Ok(()) + } + + fn display_name(&self) -> &str { + "Realtek RTL8139 10/100Mbps Ethernet Controller" + } +} + +impl NetworkDevice for Rtl8139 { + fn transmit(&self, packet: PageBox<[u8]>) -> Result<(), Error> { + let mut tx = self.tx.lock(); + + // Buffer still in Tx, cannot send + if tx.is_full() { + if tx.queue.push(packet).is_err() { + log::warn!("rtl8139: out of tx buffers + queue full"); + return Err(Error::WouldBlock); + } else { + return Ok(()); + } + } + let regs = self.regs.lock(); + tx.tx_now(®s, packet) + } + + fn packet_prefix_size(&self) -> usize { + 0 + } + + fn read_hardware_address(&self) -> MacAddress { + self.mac + } +} diff --git a/kernel/src/arch/x86/mod.rs b/kernel/src/arch/x86/mod.rs index 132ac6cd..363dd214 100644 --- a/kernel/src/arch/x86/mod.rs +++ b/kernel/src/arch/x86/mod.rs @@ -89,6 +89,18 @@ pub fn register_pci_drivers() { 0x1000, ygg_driver_virtio_net::probe, ); + ygg_driver_pci::register_vendor_driver( + "Realtek RTL8139 10/100Mbps Ethernet", + 0x10EC, + 0x8139, + ygg_driver_net_rtl81xx::probe_8139, + ); + ygg_driver_pci::register_vendor_driver( + "Realtek RTL8169/8110 Gigabit Ethernet", + 0x10EC, + 0x8169, + ygg_driver_net_rtl81xx::probe_8169, + ); } // Initialize the bare minimum required to: diff --git a/lib/qemu/src/device.rs b/lib/qemu/src/device.rs index c347dbff..e772189d 100644 --- a/lib/qemu/src/device.rs +++ b/lib/qemu/src/device.rs @@ -5,6 +5,7 @@ use crate::IntoArgs; #[derive(Debug)] pub enum QemuNic { VirtioPci { mac: Option }, + Rtl8139 { mac: Option }, } #[derive(Debug, PartialEq, Eq)] @@ -46,6 +47,15 @@ impl IntoArgs for QemuNic { } command.arg(val); } + Self::Rtl8139 { mac } => { + command.arg("-device"); + let mut val = "rtl8139,netdev=net0".to_owned(); + if let Some(mac) = mac { + val.push_str(",mac="); + val.push_str(mac); + } + command.arg(val); + } } } } diff --git a/xtask/src/qemu.rs b/xtask/src/qemu.rs index c53588fc..03d2349e 100644 --- a/xtask/src/qemu.rs +++ b/xtask/src/qemu.rs @@ -17,12 +17,21 @@ use crate::{ util::run_external_command, }; +#[derive(Debug, Default, serde::Deserialize, serde::Serialize)] +#[serde(rename_all = "kebab-case")] +enum QemuNetworkInterface { + #[default] + VirtioNet, + Rtl8139, +} + #[derive(Debug, serde::Deserialize, serde::Serialize)] #[serde(rename_all = "kebab-case", default)] struct QemuNetworkConfig { enable: bool, interface_name: String, mac: String, + interface: QemuNetworkInterface, } #[derive(Debug, Default, Clone, Copy, serde::Deserialize, serde::Serialize)] @@ -119,6 +128,7 @@ impl Default for QemuNetworkConfig { enable: true, interface_name: "qemu-tap0".into(), mac: "12:34:56:65:43:21".into(), + interface: QemuNetworkInterface::default(), } } } @@ -307,10 +317,13 @@ fn add_devices_from_config( config: &QemuConfig, ) -> Result<(), Error> { if config.network.enable { + let mac = Some(config.network.mac.clone()); + let nic = match config.network.interface { + QemuNetworkInterface::VirtioNet => QemuNic::VirtioPci { mac }, + QemuNetworkInterface::Rtl8139 => QemuNic::Rtl8139 { mac }, + }; devices.push(QemuDevice::NetworkTap { - nic: QemuNic::VirtioPci { - mac: Some(config.network.mac.clone()), - }, + nic, script: Some("xtask/scripts/qemu-ifup".into()), ifname: Some(config.network.interface_name.clone()), });