瀏覽代碼

Add x86_64 example

Yuekai Jia 1 年之前
父節點
當前提交
e3cbde9732

+ 6 - 1
README.md

@@ -44,7 +44,12 @@ VirtIO guest drivers in Rust. For **no_std** environment.
 
 ## Examples & Tests
 
-- x86_64 (TODO)
+### [x86_64](./examples/x86_64)
+
+```bash
+cd examples/x86_64
+make qemu
+```
 
 ### [aarch64](./examples/aarch64)
 

+ 2 - 0
examples/x86_64/.cargo/config

@@ -0,0 +1,2 @@
+[build]
+target = "x86_64-unknown-none"

+ 29 - 0
examples/x86_64/Cargo.toml

@@ -0,0 +1,29 @@
+[package]
+name = "x86_64"
+version = "0.1.0"
+authors = ["Yuekai Jia <[email protected]>"]
+edition = "2021"
+
+[features]
+tcp = ["smoltcp"]
+default = ["tcp"]
+
+[dependencies]
+log = "0.4.17"
+spin = "0.9"
+x86_64 = "0.14"
+uart_16550 = "0.2"
+linked_list_allocator = "0.10"
+lazy_static = { version = "1.4.0", features = ["spin_no_std"] }
+virtio-drivers = { path = "../.." }
+
+[dependencies.smoltcp]
+version = "0.9.1"
+optional = true
+default-features = false
+features = [
+  "alloc", "log",   # no std
+  "medium-ethernet",
+  "proto-ipv4",
+  "socket-raw", "socket-icmp", "socket-udp", "socket-tcp",
+]

+ 63 - 0
examples/x86_64/Makefile

@@ -0,0 +1,63 @@
+arch := x86_64
+target := x86_64-unknown-none
+mode := release
+kernel := target/$(target)/$(mode)/$(arch)
+img := target/$(target)/$(mode)/img
+accel := on
+
+sysroot := $(shell rustc --print sysroot)
+objdump := $(shell find $(sysroot) -name llvm-objdump) --arch-name=$(arch)
+objcopy := $(shell find $(sysroot) -name llvm-objcopy)
+
+BUILD_ARGS += --target $(target)
+ifeq ($(mode), release)
+	BUILD_ARGS += --release
+endif
+
+VSOCK_BUILD_ARGS =
+ifeq ($(mode), release)
+	VSOCK_BUILD_ARGS += --release
+endif
+
+QEMU_ARGS += \
+	-machine q35 \
+	-serial mon:stdio \
+	-kernel $(kernel) \
+	-device virtio-gpu-pci -vga none \
+	-device virtio-blk-pci,drive=x0 -drive file=$(img),if=none,format=raw,id=x0 \
+	-device virtio-net-pci,netdev=net0 -netdev user,id=net0,hostfwd=tcp::5555-:5555
+
+ifeq ($(accel), on)
+	QEMU_ARGS += -cpu host -accel kvm
+endif
+
+.PHONY: kernel clean qemu run env
+
+kernel:
+	cargo build $(BUILD_ARGS) --config 'build.rustflags="--cfg platform=\"qemu\" -Clink-args=-Tlinker.ld -Clink-args=-no-pie"'
+
+env:
+	rustup component add llvm-tools-preview rustfmt
+	rustup target add $(target)
+
+asm: kernel
+	$(objdump) -d $(kernel) | less
+
+sym: kernel
+	$(objdump) -t $(kernel) | less
+
+header: kernel
+	$(objdump) -x $(kernel) | less
+
+clean:
+	cargo clean
+
+qemu: kernel $(img)
+# Wait a few seconds, then try to open a connection to the VM so it can test its networking.
+	( sleep 4 && echo "hello" | nc localhost 5555 -N -v) &
+	qemu-system-$(arch) $(QEMU_ARGS)
+
+$(img):
+	dd if=/dev/zero of=$@ bs=512 count=32
+
+run: qemu

+ 47 - 0
examples/x86_64/linker.ld

@@ -0,0 +1,47 @@
+OUTPUT_ARCH(riscv)
+ENTRY(_start)
+
+BASE_ADDRESS = 0x200000;
+
+SECTIONS
+{
+    /* Load the kernel at this address: "." means the current address */
+    . = BASE_ADDRESS;
+    start = .;
+
+    .text : {
+        stext = .;
+        *(.text.entry)
+        *(.text .text.*)
+        . = ALIGN(4K);
+        etext = .;
+    }
+
+    .rodata : {
+        srodata = .;
+        *(.rodata .rodata.*)
+        . = ALIGN(4K);
+        erodata = .;
+    }
+
+    .data : {
+        sdata = .;
+        *(.data .data.*)
+        *(.sdata .sdata.*)
+        edata = .;
+    }
+
+    .stack : {
+        *(.bss.stack)
+    }
+
+    .bss : {
+        sbss = .;
+        *(.bss .bss.*)
+        *(.sbss .sbss.*)
+        ebss = .;
+    }
+
+    . = ALIGN(4K);
+    PROVIDE(end = .);
+}

+ 43 - 0
examples/x86_64/src/boot.rs

@@ -0,0 +1,43 @@
+use core::arch::global_asm;
+
+use x86_64::registers::control::{Cr0Flags, Cr4Flags};
+use x86_64::registers::model_specific::EferFlags;
+
+const BOOT_STACK_SIZE: usize = 0x4000; // 16K
+
+/// Flags set in the ’flags’ member of the multiboot header.
+///
+/// (bits 1, 16: memory information, address fields in header)
+const MULTIBOOT_HEADER_FLAGS: usize = 0x0001_0002;
+
+/// The magic field should contain this.
+const MULTIBOOT_HEADER_MAGIC: usize = 0x1BADB002;
+
+/// This should be in EAX.
+const MULTIBOOT_BOOTLOADER_MAGIC: usize = 0x2BADB002;
+
+const CR0: u64 = Cr0Flags::PROTECTED_MODE_ENABLE.bits()
+    | Cr0Flags::MONITOR_COPROCESSOR.bits()
+    | Cr0Flags::NUMERIC_ERROR.bits()
+    | Cr0Flags::WRITE_PROTECT.bits()
+    | Cr0Flags::PAGING.bits();
+
+const CR4: u64 = Cr4Flags::PHYSICAL_ADDRESS_EXTENSION.bits() | Cr4Flags::PAGE_GLOBAL.bits();
+
+const EFER: u64 = EferFlags::LONG_MODE_ENABLE.bits() | EferFlags::NO_EXECUTE_ENABLE.bits();
+
+global_asm!(
+    include_str!("multiboot.S"),
+    mb_magic = const MULTIBOOT_BOOTLOADER_MAGIC,
+    mb_hdr_magic = const MULTIBOOT_HEADER_MAGIC,
+    mb_hdr_flags = const MULTIBOOT_HEADER_FLAGS,
+    entry = sym super::main,
+
+    offset = const 0, // virt_addr == phys_addr
+    boot_stack_size = const BOOT_STACK_SIZE,
+
+    cr0 = const CR0,
+    cr4 = const CR4,
+    efer_msr = const 0xC000_0080u32,
+    efer = const EFER,
+);

+ 50 - 0
examples/x86_64/src/hal.rs

@@ -0,0 +1,50 @@
+use core::{
+    ptr::NonNull,
+    sync::atomic::{AtomicUsize, Ordering},
+};
+use lazy_static::lazy_static;
+use log::trace;
+use virtio_drivers::{BufferDirection, Hal, PhysAddr, PAGE_SIZE};
+
+extern "C" {
+    fn end();
+}
+
+lazy_static! {
+    static ref DMA_PADDR: AtomicUsize = AtomicUsize::new(end as usize);
+}
+
+pub struct HalImpl;
+
+unsafe impl Hal for HalImpl {
+    fn dma_alloc(pages: usize, _direction: BufferDirection) -> (PhysAddr, NonNull<u8>) {
+        let paddr = DMA_PADDR.fetch_add(PAGE_SIZE * pages, Ordering::SeqCst);
+        trace!("alloc DMA: paddr={:#x}, pages={}", paddr, pages);
+        let vaddr = NonNull::new(paddr as _).unwrap();
+        (paddr, vaddr)
+    }
+
+    unsafe fn dma_dealloc(paddr: PhysAddr, _vaddr: NonNull<u8>, pages: usize) -> i32 {
+        trace!("dealloc DMA: paddr={:#x}, pages={}", paddr, pages);
+        0
+    }
+
+    unsafe fn mmio_phys_to_virt(paddr: PhysAddr, _size: usize) -> NonNull<u8> {
+        NonNull::new(paddr as _).unwrap()
+    }
+
+    unsafe fn share(buffer: NonNull<[u8]>, _direction: BufferDirection) -> PhysAddr {
+        let vaddr = buffer.as_ptr() as *mut u8 as usize;
+        // Nothing to do, as the host already has access to all memory.
+        virt_to_phys(vaddr)
+    }
+
+    unsafe fn unshare(_paddr: PhysAddr, _buffer: NonNull<[u8]>, _direction: BufferDirection) {
+        // Nothing to do, as the host already has access to all memory and we didn't copy the buffer
+        // anywhere else.
+    }
+}
+
+fn virt_to_phys(vaddr: usize) -> PhysAddr {
+    vaddr
+}

+ 15 - 0
examples/x86_64/src/heap.rs

@@ -0,0 +1,15 @@
+use linked_list_allocator::LockedHeap;
+
+#[global_allocator]
+static ALLOCATOR: LockedHeap = LockedHeap::empty();
+
+const HEAP_SIZE: usize = 0x10000; // 64K
+
+static mut HEAP: [u64; HEAP_SIZE / 8] = [0; HEAP_SIZE / 8];
+
+pub fn init_heap() {
+    unsafe {
+        let heap_start = HEAP.as_ptr() as *mut u8;
+        ALLOCATOR.lock().init(heap_start, HEAP_SIZE);
+    }
+}

+ 45 - 0
examples/x86_64/src/logger.rs

@@ -0,0 +1,45 @@
+//! Log implementation using the UART.
+
+use core::fmt::Write;
+use log::{LevelFilter, Log, Metadata, Record, SetLoggerError};
+use spin::mutex::SpinMutex;
+use uart_16550::SerialPort;
+
+static LOGGER: Logger = Logger {
+    uart: SpinMutex::new(None),
+};
+
+struct Logger {
+    uart: SpinMutex<Option<SerialPort>>,
+}
+
+/// Initialises UART logger.
+pub fn init(max_level: LevelFilter) -> Result<(), SetLoggerError> {
+    // Safe because BASE_ADDRESS is the base of the MMIO region for a UART and is mapped as device
+    // memory.
+    let mut uart = unsafe { SerialPort::new(0x3f8) };
+    uart.init();
+    LOGGER.uart.lock().replace(uart);
+
+    log::set_logger(&LOGGER)?;
+    log::set_max_level(max_level);
+    Ok(())
+}
+
+impl Log for Logger {
+    fn enabled(&self, _metadata: &Metadata) -> bool {
+        true
+    }
+
+    fn log(&self, record: &Record) {
+        writeln!(
+            LOGGER.uart.lock().as_mut().unwrap(),
+            "[{}] {}",
+            record.level(),
+            record.args()
+        )
+        .unwrap();
+    }
+
+    fn flush(&self) {}
+}

+ 202 - 0
examples/x86_64/src/main.rs

@@ -0,0 +1,202 @@
+#![no_std]
+#![no_main]
+#![feature(asm_const)]
+#![feature(abi_x86_interrupt)]
+
+#[macro_use]
+extern crate log;
+extern crate alloc;
+
+mod boot;
+mod hal;
+mod heap;
+mod logger;
+mod trap;
+
+#[cfg(feature = "tcp")]
+mod tcp;
+
+use self::hal::HalImpl;
+use virtio_drivers::{
+    device::{blk::VirtIOBlk, gpu::VirtIOGpu, net::VirtIONet},
+    transport::{
+        pci::{
+            bus::{BarInfo, Cam, Command, DeviceFunction, PciRoot},
+            virtio_device_type, PciTransport,
+        },
+        DeviceType, Transport,
+    },
+};
+
+/// Memory mapped address space to access PCI configuration.
+///
+/// Currently it is hardcoded (from qemu/roms/seabios/src/hw/dev-q35.h)
+///
+/// TODO: get it from ACPI MCFG table.
+const MMCONFIG_BASE: usize = 0xB000_0000;
+
+const NET_BUFFER_LEN: usize = 2048;
+const NET_QUEUE_SIZE: usize = 16;
+
+fn system_off() -> ! {
+    use x86_64::instructions::{hlt, port::PortWriteOnly};
+    unsafe {
+        PortWriteOnly::new(0x604).write(0x2000u16);
+        loop {
+            hlt();
+        }
+    }
+}
+
+#[no_mangle]
+extern "C" fn main(_mbi: *const u8) -> ! {
+    logger::init(log::LevelFilter::Info).unwrap();
+    info!("virtio-drivers example started.");
+
+    trap::init();
+    heap::init_heap();
+
+    enumerate_pci(MMCONFIG_BASE as _);
+
+    info!("test end");
+    system_off();
+}
+
+fn virtio_device(transport: impl Transport) {
+    match transport.device_type() {
+        DeviceType::Block => virtio_blk(transport),
+        DeviceType::GPU => virtio_gpu(transport),
+        DeviceType::Network => virtio_net(transport),
+        t => warn!("Unrecognized virtio device: {:?}", t),
+    }
+}
+
+fn virtio_blk<T: Transport>(transport: T) {
+    let mut blk = VirtIOBlk::<HalImpl, T>::new(transport).expect("failed to create blk driver");
+    assert!(!blk.readonly());
+    let mut input = [0xffu8; 512];
+    let mut output = [0; 512];
+    for i in 0..32 {
+        for x in input.iter_mut() {
+            *x = i as u8;
+        }
+        blk.write_block(i, &input).expect("failed to write");
+        blk.read_block(i, &mut output).expect("failed to read");
+        assert_eq!(input, output);
+    }
+    info!("virtio-blk test finished");
+}
+
+fn virtio_gpu<T: Transport>(transport: T) {
+    let mut gpu = VirtIOGpu::<HalImpl, T>::new(transport).expect("failed to create gpu driver");
+    let (width, height) = gpu.resolution().expect("failed to get resolution");
+    let width = width as usize;
+    let height = height as usize;
+    info!("GPU resolution is {}x{}", width, height);
+    let fb = gpu.setup_framebuffer().expect("failed to get fb");
+    for y in 0..height {
+        for x in 0..width {
+            let idx = (y * width + x) * 4;
+            fb[idx] = x as u8;
+            fb[idx + 1] = y as u8;
+            fb[idx + 2] = (x + y) as u8;
+        }
+    }
+    gpu.flush().expect("failed to flush");
+    //delay some time
+    info!("virtio-gpu show graphics....");
+    for _ in 0..10000 {
+        for _ in 0..100000 {
+            unsafe {
+                core::arch::asm!("nop");
+            }
+        }
+    }
+
+    info!("virtio-gpu test finished");
+}
+
+fn virtio_net<T: Transport>(transport: T) {
+    let net = VirtIONet::<HalImpl, T, NET_QUEUE_SIZE>::new(transport, NET_BUFFER_LEN)
+        .expect("failed to create net driver");
+    info!("MAC address: {:02x?}", net.mac_address());
+
+    #[cfg(not(feature = "tcp"))]
+    {
+        let mut net = net;
+        loop {
+            match net.receive() {
+                Ok(buf) => {
+                    info!("RECV {} bytes: {:02x?}", buf.packet_len(), buf.packet());
+                    let tx_buf = virtio_drivers::device::net::TxBuffer::from(buf.packet());
+                    net.send(tx_buf).expect("failed to send");
+                    net.recycle_rx_buffer(buf).unwrap();
+                    break;
+                }
+                Err(virtio_drivers::Error::NotReady) => continue,
+                Err(err) => panic!("failed to recv: {:?}", err),
+            }
+        }
+        info!("virtio-net test finished");
+    }
+
+    #[cfg(feature = "tcp")]
+    tcp::test_echo_server(net);
+}
+
+fn enumerate_pci(mmconfig_base: *mut u8) {
+    info!("mmconfig_base = {:#x}", mmconfig_base as usize);
+
+    let mut pci_root = unsafe { PciRoot::new(mmconfig_base, Cam::Ecam) };
+    for (device_function, info) in pci_root.enumerate_bus(0) {
+        let (status, command) = pci_root.get_status_command(device_function);
+        info!(
+            "Found {} at {}, status {:?} command {:?}",
+            info, device_function, status, command
+        );
+        if let Some(virtio_type) = virtio_device_type(&info) {
+            info!("  VirtIO {:?}", virtio_type);
+
+            // Enable the device to use its BARs.
+            pci_root.set_command(
+                device_function,
+                Command::IO_SPACE | Command::MEMORY_SPACE | Command::BUS_MASTER,
+            );
+            dump_bar_contents(&mut pci_root, device_function, 4);
+
+            let mut transport =
+                PciTransport::new::<HalImpl>(&mut pci_root, device_function).unwrap();
+            info!(
+                "Detected virtio PCI device with device type {:?}, features {:#018x}",
+                transport.device_type(),
+                transport.read_device_features(),
+            );
+            virtio_device(transport);
+        }
+    }
+}
+
+fn dump_bar_contents(root: &mut PciRoot, device_function: DeviceFunction, bar_index: u8) {
+    let bar_info = root.bar_info(device_function, bar_index).unwrap();
+    trace!("Dumping bar {}: {:#x?}", bar_index, bar_info);
+    if let BarInfo::Memory { address, size, .. } = bar_info {
+        let start = address as *const u8;
+        unsafe {
+            let mut buf = [0u8; 32];
+            for i in 0..size / 32 {
+                let ptr = start.add(i as usize * 32);
+                core::ptr::copy(ptr, buf.as_mut_ptr(), 32);
+                if buf.iter().any(|b| *b != 0xff) {
+                    trace!("  {:?}: {:x?}", ptr, buf);
+                }
+            }
+        }
+    }
+    trace!("End of dump");
+}
+
+#[panic_handler]
+fn panic(info: &core::panic::PanicInfo) -> ! {
+    error!("{}", info);
+    system_off()
+}

+ 114 - 0
examples/x86_64/src/multiboot.S

@@ -0,0 +1,114 @@
+# Bootstrapping from 32-bit with the Multiboot specification.
+# See https://www.gnu.org/software/grub/manual/multiboot/multiboot.html
+
+.section .text.entry
+.code32
+.global _start
+_start:
+    mov     ecx, {mb_magic}
+    cmp     ecx, eax
+    jnz     1f
+    mov     edi, ebx        # arg1: multiboot info
+    jmp     entry32
+1:  hlt
+    jmp     1b
+
+.balign 4
+.type multiboot_header, STT_OBJECT
+multiboot_header:
+    .int    {mb_hdr_magic}                      # magic: 0x1BADB002
+    .int    {mb_hdr_flags}                      # flags
+    .int    -({mb_hdr_magic} + {mb_hdr_flags})  # checksum
+    .int    multiboot_header - {offset}         # header_addr
+    .int    start - {offset}                    # load_addr
+    .int    edata - {offset}                    # load_end
+    .int    ebss - {offset}                     # bss_end_addr
+    .int    _start - {offset}                   # entry_addr
+
+.code32
+entry32:
+    lgdt    [.Ltmp_gdt_desc - {offset}]             # load the temporary GDT
+
+    # set data segment selectors
+    mov     ax, 0x18
+    mov     ss, ax
+    mov     ds, ax
+    mov     es, ax
+    mov     fs, ax
+    mov     gs, ax
+
+    # set PAE, PGE bit in CR4
+    mov     eax, {cr4}
+    mov     cr4, eax
+
+    # load the temporary page table
+    lea     eax, [.Ltmp_pml4 - {offset}]
+    mov     cr3, eax
+
+    # set LME, NXE bit in IA32_EFER
+    mov     ecx, {efer_msr}
+    mov     edx, 0
+    mov     eax, {efer}
+    wrmsr
+
+    # set protected mode, write protect, paging bit in CR0
+    mov     eax, {cr0}
+    mov     cr0, eax
+
+    ljmp    0x10, offset entry64 - {offset}    # 0x10 is code64 segment
+
+.code64
+entry64:
+    # clear segment selectors
+    xor     ax, ax
+    mov     ss, ax
+    mov     ds, ax
+    mov     es, ax
+    mov     fs, ax
+    mov     gs, ax
+
+    # set RSP to boot stack top
+    movabs  rsp, offset boot_stack_top
+
+    # call main(magic, mbi)
+    movabs  rax, offset {entry}
+    call    rax
+    jmp     .Lhlt
+
+.Lhlt:
+    hlt
+    jmp     .Lhlt
+
+.section .rodata
+.balign 8
+.Ltmp_gdt_desc:
+    .short  .Ltmp_gdt_end - .Ltmp_gdt - 1   # limit
+    .long   .Ltmp_gdt - {offset}            # base
+
+.section .data
+.balign 16
+.Ltmp_gdt:
+    .quad 0x0000000000000000    # 0x00: null
+    .quad 0x00cf9b000000ffff    # 0x08: code segment (base=0, limit=0xfffff, type=32bit code exec/read, DPL=0, 4k)
+    .quad 0x00af9b000000ffff    # 0x10: code segment (base=0, limit=0xfffff, type=64bit code exec/read, DPL=0, 4k)
+    .quad 0x00cf93000000ffff    # 0x18: data segment (base=0, limit=0xfffff, type=32bit data read/write, DPL=0, 4k)
+.Ltmp_gdt_end:
+
+.balign 4096
+.Ltmp_pml4:
+    # 0x0000_0000 ~ 0xffff_ffff
+    .quad .Ltmp_pdpt_low - {offset} + 0x3   # PRESENT | WRITABLE | paddr(tmp_pdpt)
+    .zero 8 * 511
+
+.Ltmp_pdpt_low:
+    .quad 0x0000 | 0x83         # PRESENT | WRITABLE | HUGE_PAGE | paddr(0x0)
+    .quad 0x40000000 | 0x83     # PRESENT | WRITABLE | HUGE_PAGE | paddr(0x4000_0000)
+    .quad 0x80000000 | 0x83     # PRESENT | WRITABLE | HUGE_PAGE | paddr(0x8000_0000)
+    .quad 0xc0000000 | 0x83     # PRESENT | WRITABLE | HUGE_PAGE | paddr(0xc000_0000)
+    .zero 8 * 508
+
+.section .bss.stack
+.balign 4096
+.boot_stack:
+    .space {boot_stack_size}
+boot_stack_top:

+ 181 - 0
examples/x86_64/src/tcp.rs

@@ -0,0 +1,181 @@
+//! Simple echo server over TCP.
+//!
+//! Ref: https://github.com/smoltcp-rs/smoltcp/blob/master/examples/server.rs
+
+use alloc::{borrow::ToOwned, rc::Rc, vec, vec::Vec};
+use core::{cell::RefCell, str::FromStr};
+
+use smoltcp::iface::{Config, Interface, SocketSet};
+use smoltcp::phy::{Device, DeviceCapabilities, Medium, RxToken, TxToken};
+use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr, Ipv4Address};
+use smoltcp::{socket::tcp, time::Instant};
+use virtio_drivers::device::net::{RxBuffer, VirtIONet};
+use virtio_drivers::{transport::Transport, Error};
+
+use super::{HalImpl, NET_QUEUE_SIZE};
+
+type DeviceImpl<T> = VirtIONet<HalImpl, T, NET_QUEUE_SIZE>;
+
+const IP: &str = "10.0.2.15"; // QEMU user networking default IP
+const GATEWAY: &str = "10.0.2.2"; // QEMU user networking gateway
+const PORT: u16 = 5555;
+
+struct DeviceWrapper<T: Transport> {
+    inner: Rc<RefCell<DeviceImpl<T>>>,
+}
+
+impl<T: Transport> DeviceWrapper<T> {
+    fn new(dev: DeviceImpl<T>) -> Self {
+        DeviceWrapper {
+            inner: Rc::new(RefCell::new(dev)),
+        }
+    }
+
+    fn mac_address(&self) -> EthernetAddress {
+        EthernetAddress(self.inner.borrow().mac_address())
+    }
+}
+
+impl<T: Transport> Device for DeviceWrapper<T> {
+    type RxToken<'a> = VirtioRxToken<T> where Self: 'a;
+    type TxToken<'a> = VirtioTxToken<T> where Self: 'a;
+
+    fn receive(&mut self, _timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
+        match self.inner.borrow_mut().receive() {
+            Ok(buf) => Some((
+                VirtioRxToken(self.inner.clone(), buf),
+                VirtioTxToken(self.inner.clone()),
+            )),
+            Err(Error::NotReady) => None,
+            Err(err) => panic!("receive failed: {}", err),
+        }
+    }
+
+    fn transmit(&mut self, _timestamp: Instant) -> Option<Self::TxToken<'_>> {
+        Some(VirtioTxToken(self.inner.clone()))
+    }
+
+    fn capabilities(&self) -> DeviceCapabilities {
+        let mut caps = DeviceCapabilities::default();
+        caps.max_transmission_unit = 1536;
+        caps.max_burst_size = Some(1);
+        caps.medium = Medium::Ethernet;
+        caps
+    }
+}
+
+struct VirtioRxToken<T: Transport>(Rc<RefCell<DeviceImpl<T>>>, RxBuffer);
+struct VirtioTxToken<T: Transport>(Rc<RefCell<DeviceImpl<T>>>);
+
+impl<T: Transport> RxToken for VirtioRxToken<T> {
+    fn consume<R, F>(self, f: F) -> R
+    where
+        F: FnOnce(&mut [u8]) -> R,
+    {
+        let mut rx_buf = self.1;
+        trace!(
+            "RECV {} bytes: {:02X?}",
+            rx_buf.packet_len(),
+            rx_buf.packet()
+        );
+        let result = f(rx_buf.packet_mut());
+        self.0.borrow_mut().recycle_rx_buffer(rx_buf).unwrap();
+        result
+    }
+}
+
+impl<T: Transport> TxToken for VirtioTxToken<T> {
+    fn consume<R, F>(self, len: usize, f: F) -> R
+    where
+        F: FnOnce(&mut [u8]) -> R,
+    {
+        let mut dev = self.0.borrow_mut();
+        let mut tx_buf = dev.new_tx_buffer(len);
+        let result = f(tx_buf.packet_mut());
+        trace!("SEND {} bytes: {:02X?}", len, tx_buf.packet());
+        dev.send(tx_buf).unwrap();
+        result
+    }
+}
+
+pub fn test_echo_server<T: Transport>(dev: DeviceImpl<T>) {
+    let mut device = DeviceWrapper::new(dev);
+
+    // Create interface
+    let mut config = Config::new();
+    config.random_seed = 0x2333;
+    if device.capabilities().medium == Medium::Ethernet {
+        config.hardware_addr = Some(device.mac_address().into());
+    }
+
+    let mut iface = Interface::new(config, &mut device);
+    iface.update_ip_addrs(|ip_addrs| {
+        ip_addrs
+            .push(IpCidr::new(IpAddress::from_str(IP).unwrap(), 24))
+            .unwrap();
+    });
+
+    iface
+        .routes_mut()
+        .add_default_ipv4_route(Ipv4Address::from_str(GATEWAY).unwrap())
+        .unwrap();
+
+    // Create sockets
+    let tcp_rx_buffer = tcp::SocketBuffer::new(vec![0; 1024]);
+    let tcp_tx_buffer = tcp::SocketBuffer::new(vec![0; 1024]);
+    let tcp_socket = tcp::Socket::new(tcp_rx_buffer, tcp_tx_buffer);
+
+    let mut sockets = SocketSet::new(vec![]);
+    let tcp_handle = sockets.add(tcp_socket);
+
+    info!("start a reverse echo server...");
+    let mut tcp_active = false;
+    loop {
+        let timestamp =
+            unsafe { Instant::from_micros_const(core::arch::x86_64::_rdtsc() as i64 / 2_500) };
+        iface.poll(timestamp, &mut device, &mut sockets);
+
+        // tcp:PORT: echo with reverse
+        let socket = sockets.get_mut::<tcp::Socket>(tcp_handle);
+        if !socket.is_open() {
+            info!("listening on port {}...", PORT);
+            socket.listen(PORT).unwrap();
+        }
+
+        if socket.is_active() && !tcp_active {
+            info!("tcp:{} connected", PORT);
+        } else if !socket.is_active() && tcp_active {
+            info!("tcp:{} disconnected", PORT);
+        }
+        tcp_active = socket.is_active();
+
+        if socket.may_recv() {
+            let data = socket
+                .recv(|buffer| {
+                    let recvd_len = buffer.len();
+                    if !buffer.is_empty() {
+                        debug!("tcp:{} recv {} bytes: {:?}", PORT, recvd_len, buffer);
+                        let mut lines = buffer
+                            .split(|&b| b == b'\n')
+                            .map(ToOwned::to_owned)
+                            .collect::<Vec<_>>();
+                        for line in lines.iter_mut() {
+                            line.reverse();
+                        }
+                        let data = lines.join(&b'\n');
+                        (recvd_len, data)
+                    } else {
+                        (0, vec![])
+                    }
+                })
+                .unwrap();
+            if socket.can_send() && !data.is_empty() {
+                debug!("tcp:{} send data: {:?}", PORT, data);
+                socket.send_slice(&data[..]).unwrap();
+            }
+        } else if socket.may_send() {
+            info!("tcp:{} close", PORT);
+            socket.close();
+        }
+    }
+}

+ 29 - 0
examples/x86_64/src/trap.rs

@@ -0,0 +1,29 @@
+use x86_64::set_general_handler;
+use x86_64::structures::idt::{ExceptionVector, InterruptDescriptorTable, InterruptStackFrame};
+
+static mut IDT: InterruptDescriptorTable = InterruptDescriptorTable::new();
+
+fn trap_handler(isf: InterruptStackFrame, index: u8, error_code: Option<u64>) {
+    match index {
+        x if x == ExceptionVector::Page as u8 => {
+            let cr2 = x86_64::registers::control::Cr2::read();
+            panic!(
+                "#PF at {:#x}, fault_vaddr={:#x}, err_code={:#x?}",
+                isf.instruction_pointer, cr2, error_code
+            );
+        }
+        _ => {
+            panic!(
+                "Unhandled exception {} (error_code = {:#x?}) at {:#x}:\n{:#x?}",
+                index, error_code, isf.instruction_pointer, isf
+            );
+        }
+    }
+}
+
+pub fn init() {
+    unsafe {
+        set_general_handler!(&mut IDT, trap_handler);
+        IDT.load();
+    }
+}