From 5d8067991dca171da9dc32b0a04af02251fef754 Mon Sep 17 00:00:00 2001 From: Mark Poliakov Date: Sat, 20 Jan 2024 19:40:27 +0200 Subject: [PATCH] net: Basic UDP/ICMP over IPv4 networking using virtio-net --- Cargo.lock | 1055 +++++++++++++++++++++++ Cargo.toml | 2 + driver/bus/pci/src/capability.rs | 141 +++ driver/bus/pci/src/lib.rs | 33 + driver/bus/pci/src/space/mod.rs | 11 +- driver/fs/kernel-fs/src/devfs.rs | 84 +- driver/net/core/Cargo.toml | 16 + driver/net/core/src/ethernet.rs | 100 +++ driver/net/core/src/interface.rs | 116 +++ driver/net/core/src/l3/arp.rs | 142 +++ driver/net/core/src/l3/ip.rs | 148 ++++ driver/net/core/src/l3/mod.rs | 187 ++++ driver/net/core/src/l4/icmp.rs | 104 +++ driver/net/core/src/l4/mod.rs | 2 + driver/net/core/src/l4/udp.rs | 79 ++ driver/net/core/src/lib.rs | 139 +++ driver/net/core/src/queue.rs | 53 ++ driver/net/core/src/socket.rs | 198 +++++ driver/net/core/src/types.rs | 225 +++++ driver/virtio/core/Cargo.toml | 21 + driver/virtio/core/src/error.rs | 20 + driver/virtio/core/src/lib.rs | 54 ++ driver/virtio/core/src/queue.rs | 344 ++++++++ driver/virtio/core/src/transport/mod.rs | 92 ++ driver/virtio/core/src/transport/pci.rs | 119 +++ driver/virtio/net/Cargo.toml | 24 + driver/virtio/net/src/lib.rs | 316 +++++++ lib/kernel-util/src/api.rs | 2 + lib/kernel-util/src/mem/device.rs | 34 + lib/kernel-util/src/mem/mod.rs | 11 +- lib/kernel-util/src/util/ring.rs | 7 +- lib/vfs/src/file/mod.rs | 52 +- lib/vfs/src/lib.rs | 2 + lib/vfs/src/node/impls.rs | 4 + lib/vfs/src/socket.rs | 53 ++ src/arch/mod.rs | 8 + src/arch/x86_64/mem/mod.rs | 17 + src/arch/x86_64/mod.rs | 11 + src/init.rs | 2 + src/syscall/mod.rs | 43 +- 40 files changed, 4054 insertions(+), 17 deletions(-) create mode 100644 Cargo.lock create mode 100644 driver/net/core/Cargo.toml create mode 100644 driver/net/core/src/ethernet.rs create mode 100644 driver/net/core/src/interface.rs create mode 100644 driver/net/core/src/l3/arp.rs create mode 100644 driver/net/core/src/l3/ip.rs create mode 100644 driver/net/core/src/l3/mod.rs create mode 100644 driver/net/core/src/l4/icmp.rs create mode 100644 driver/net/core/src/l4/mod.rs create mode 100644 driver/net/core/src/l4/udp.rs create mode 100644 driver/net/core/src/lib.rs create mode 100644 driver/net/core/src/queue.rs create mode 100644 driver/net/core/src/socket.rs create mode 100644 driver/net/core/src/types.rs create mode 100644 driver/virtio/core/Cargo.toml create mode 100644 driver/virtio/core/src/error.rs create mode 100644 driver/virtio/core/src/lib.rs create mode 100644 driver/virtio/core/src/queue.rs create mode 100644 driver/virtio/core/src/transport/mod.rs create mode 100644 driver/virtio/core/src/transport/pci.rs create mode 100644 driver/virtio/net/Cargo.toml create mode 100644 driver/virtio/net/src/lib.rs create mode 100644 lib/vfs/src/socket.rs diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 00000000..245875f0 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1055 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aarch64-cpu" +version = "9.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac42a04a61c19fc8196dd728022a784baecc5d63d7e256c01ad1b3fbfab26287" +dependencies = [ + "tock-registers", +] + +[[package]] +name = "accessor" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd8b2abd55bf1f9cffbf00fd594566c51a9d31402553284920c1309ca8351086" + +[[package]] +name = "acpi" +version = "4.1.1" +source = "git+https://github.com/alnyan/acpi.git?branch=acpi-system#5efda0f9c17bccb922cf2b6b5fd73088bb887965" +dependencies = [ + "bit_field", + "log", + "rsdp", +] + +[[package]] +name = "acpi-system" +version = "0.1.0" +source = "git+https://github.com/alnyan/acpi-system.git#17192e5f603fdc7d78c9b83885e8a9d9aac45af7" +dependencies = [ + "acpi", + "aml", + "bit_field", + "enum-map", + "log", +] + +[[package]] +name = "aml" +version = "0.16.4" +source = "git+https://github.com/alnyan/acpi.git?branch=acpi-system#5efda0f9c17bccb922cf2b6b5fd73088bb887965" +dependencies = [ + "bit_field", + "bitvec", + "byteorder", + "log", + "spinning_top", +] + +[[package]] +name = "atomic_enum" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6227a8d6fdb862bcb100c4314d0d9579e5cd73fa6df31a2e6f6e1acd3c5f1207" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bit_field" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "btree_monstrousity" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4d0977e9c15f276380f16f2e9594257c258172b23af39ffd2e4cf5971cb38c7" +dependencies = [ + "cfg-if", + "rustversion", +] + +[[package]] +name = "bytemuck" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "crossbeam-queue" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" + +[[package]] +name = "device-api" +version = "0.1.0" +dependencies = [ + "device-api-macros", + "yggdrasil-abi", +] + +[[package]] +name = "device-api-macros" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "discrete_range_map" +version = "0.6.2" +source = "git+https://git.alnyan.me/yggdrasil/discrete_range_map.git#10fd79828d2918bd079a11c2b2c623eede170c3f" +dependencies = [ + "btree_monstrousity", + "either", + "itertools 0.12.0", + "serde", +] + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "elf" +version = "0.7.2" +source = "git+https://git.alnyan.me/yggdrasil/yggdrasil-elf.git#419cd311de2e9514b5033677cde9a33f7d0ba4a2" +dependencies = [ + "hashbrown", +] + +[[package]] +name = "endian-type-rs" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6419a5c75e40011b9fe0174db3fe24006ab122fbe1b7e9cc5974b338a755c76" + +[[package]] +name = "enum-map" +version = "2.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6866f3bfdf8207509a033af1a75a7b08abda06bbaaeae6669323fd5a097df2e9" +dependencies = [ + "enum-map-derive", +] + +[[package]] +name = "enum-map-derive" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f282cfdfe92516eb26c2af8589c274c7c17681f5ecc03c18255fe741c6aa64eb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + +[[package]] +name = "fdt-rs" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99a40cabc11c8258822a593f5c51f2d9f4923e715ca9e2a0630cf77ae15f390b" +dependencies = [ + "endian-type-rs", + "fallible-iterator", + "memoffset 0.5.6", + "num-derive", + "num-traits", + "rustc_version", + "static_assertions", + "unsafe_unwrap", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "getrandom" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "git-version" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad568aa3db0fcbc81f2f116137f263d7304f512a1209b35b85150d3ef88ad19" +dependencies = [ + "git-version-macro", +] + +[[package]] +name = "git-version-macro" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53010ccb100b96a67bc32c0175f0ed1426b31b655d562898e57325f81c023ac0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + +[[package]] +name = "hosted-tests" +version = "0.1.0" +dependencies = [ + "yggdrasil-abi", +] + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0" +dependencies = [ + "either", +] + +[[package]] +name = "kernel-fs" +version = "0.1.0" +dependencies = [ + "kernel-util", + "log", + "vfs", + "ygg_driver_block", + "yggdrasil-abi", +] + +[[package]] +name = "kernel-util" +version = "0.1.0" +dependencies = [ + "crossbeam-queue", + "device-api", + "futures-util", + "log", + "yggdrasil-abi", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" + +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + +[[package]] +name = "linked_list_allocator" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afa463f5405ee81cdb9cc2baf37e08ec7e4c8209442b5d72c04cfb2cd6e6286" +dependencies = [ + "spinning_top", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" + +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "memfs" +version = "0.1.0" +dependencies = [ + "kernel-util", + "log", + "static_assertions", + "vfs", + "yggdrasil-abi", +] + +[[package]] +name = "memoffset" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "043175f069eda7b85febe4a74abbaeff828d9f8b448515d3151a14a3542811aa" +dependencies = [ + "autocfg", +] + +[[package]] +name = "memoffset" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "memtables" +version = "0.1.0" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "num-derive" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "num-traits" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proptest" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31b476131c3c86cb68032fdc5cb6d5a1045e3e42d96b69fa599fd77701e1f5bf" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags 2.4.1", + "lazy_static", + "num-traits", + "rand", + "rand_chacha", + "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", + "unarray", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "rsdp" +version = "2.0.0" +source = "git+https://github.com/alnyan/acpi.git?branch=acpi-system#5efda0f9c17bccb922cf2b6b5fd73088bb887965" +dependencies = [ + "log", +] + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca" +dependencies = [ + "bitflags 2.4.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + +[[package]] +name = "rusty-fork" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "serde" +version = "1.0.195" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.195" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "spinning_top" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b9eb1a2f4c41445a3a0ff9abc5221c5fcd28e1f13cd7c0397706f9ac938ddb0" +dependencies = [ + "lock_api", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tempfile" +version = "3.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall", + "rustix", + "windows-sys", +] + +[[package]] +name = "tock-registers" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "696941a0aee7e276a165a978b37918fd5d22c55c3d6bda197813070ca9c0f21c" + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unsafe_unwrap" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1230ec65f13e0f9b28d789da20d2d419511893ea9dac2c1f4ef67b8b14e5da80" + +[[package]] +name = "uuid" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "vfs" +version = "0.1.0" +dependencies = [ + "futures-util", + "hosted-tests", + "kernel-util", + "log", + "ygg_driver_block", + "yggdrasil-abi", +] + +[[package]] +name = "vmalloc" +version = "0.1.0" +dependencies = [ + "discrete_range_map", + "itertools 0.11.0", + "proptest", + "yggdrasil-abi", +] + +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "xhci" +version = "0.9.2" +source = "git+https://github.com/rust-osdev/xhci.git#6144829ff13c2499dc812cea09df4e5a1d0862cc" +dependencies = [ + "accessor", + "bit_field", + "num-derive", + "num-traits", + "paste", +] + +[[package]] +name = "yboot-proto" +version = "0.1.0" +source = "git+https://git.alnyan.me/yggdrasil/yboot-proto.git#edf5db1b1d5d5dcb9d737cd7ec23b2e124b3b027" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "ygg_driver_ahci" +version = "0.1.0" +dependencies = [ + "bytemuck", + "device-api", + "futures-util", + "kernel-fs", + "kernel-util", + "log", + "memoffset 0.9.0", + "static_assertions", + "tock-registers", + "vfs", + "ygg_driver_block", + "ygg_driver_pci", + "yggdrasil-abi", +] + +[[package]] +name = "ygg_driver_block" +version = "0.1.0" +dependencies = [ + "bytemuck", + "futures-util", + "kernel-util", + "log", + "static_assertions", + "uuid", + "yggdrasil-abi", +] + +[[package]] +name = "ygg_driver_nvme" +version = "0.1.0" +dependencies = [ + "bytemuck", + "device-api", + "futures-util", + "kernel-fs", + "kernel-util", + "log", + "static_assertions", + "tock-registers", + "vfs", + "ygg_driver_block", + "ygg_driver_pci", + "yggdrasil-abi", +] + +[[package]] +name = "ygg_driver_pci" +version = "0.1.0" +dependencies = [ + "acpi", + "bitflags 2.4.1", + "device-api", + "kernel-util", + "log", + "yggdrasil-abi", +] + +[[package]] +name = "ygg_driver_virtio_net" +version = "0.1.0" +dependencies = [ + "ygg_driver_pci", +] + +[[package]] +name = "yggdrasil-abi" +version = "0.1.0" +source = "git+https://git.alnyan.me/yggdrasil/yggdrasil-abi.git#d2173e1e9e92e668399cf83aeb32e09823826821" + +[[package]] +name = "yggdrasil-kernel" +version = "0.1.0" +dependencies = [ + "aarch64-cpu", + "acpi", + "acpi-system", + "aml", + "atomic_enum", + "bitflags 2.4.1", + "bytemuck", + "cfg-if", + "crossbeam-queue", + "device-api", + "device-api-macros", + "elf", + "fdt-rs", + "futures-util", + "git-version", + "kernel-fs", + "kernel-util", + "linked_list_allocator", + "log", + "memfs", + "memtables", + "spinning_top", + "static_assertions", + "tock-registers", + "vfs", + "vmalloc", + "xhci", + "yboot-proto", + "ygg_driver_ahci", + "ygg_driver_block", + "ygg_driver_nvme", + "ygg_driver_pci", + "ygg_driver_virtio_net", + "yggdrasil-abi", +] diff --git a/Cargo.toml b/Cargo.toml index cf505ff5..c69cca0e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ device-api-macros = { path = "lib/device-api/macros" } # Drivers ygg_driver_block = { path = "driver/block/core" } +ygg_driver_net_core = { path = "driver/net/core" } kernel-fs = { path = "driver/fs/kernel-fs" } memfs = { path = "driver/fs/memfs" } @@ -57,6 +58,7 @@ xhci_lib = { git = "https://github.com/rust-osdev/xhci.git", package = "xhci" } ygg_driver_pci = { path = "driver/bus/pci" } ygg_driver_nvme = { path = "driver/block/nvme" } ygg_driver_ahci = { path = "driver/block/ahci" } +ygg_driver_virtio_net = { path = "driver/virtio/net", features = ["pci"] } [features] default = ["fb_console"] diff --git a/driver/bus/pci/src/capability.rs b/driver/bus/pci/src/capability.rs index cb62c932..387e070d 100644 --- a/driver/bus/pci/src/capability.rs +++ b/driver/bus/pci/src/capability.rs @@ -11,12 +11,48 @@ use yggdrasil_abi::error::Error; use super::{PciCapability, PciCapabilityId, PciConfigurationSpace}; use crate::PciBaseAddress; +pub trait VirtioCapabilityData<'s, S: PciConfigurationSpace + ?Sized + 's>: Sized { + fn from_space_offset(space: &'s S, offset: usize) -> Self; + + fn space(&self) -> &'s S; + fn offset(&self) -> usize; + + fn bar_index(&self) -> Option { + let value = self.space().read_u8(self.offset() + 4); + (value <= 0x5).then_some(value as _) + } + + fn bar_offset(&self) -> usize { + let value = self.space().read_u32(self.offset() + 8); + value as _ + } + + fn length(&self) -> usize { + let value = self.space().read_u32(self.offset() + 12); + value as _ + } +} + +pub trait VirtioCapability { + const CFG_TYPE: u8; + const MIN_LEN: usize = 0; + type Output<'a, S: PciConfigurationSpace + ?Sized + 'a>: VirtioCapabilityData<'a, S>; +} + /// MSI-X capability query pub struct MsiXCapability; /// MSI capability query pub struct MsiCapability; +// VirtIO-over-PCI capabilities +/// VirtIO PCI configuration access +pub struct VirtioDeviceConfigCapability; +/// VirtIO common configuration +pub struct VirtioCommonConfigCapability; +/// VirtIO notify configuration +pub struct VirtioNotifyConfigCapability; + /// Represents an entry in MSI-X vector table #[repr(C)] pub struct MsiXEntry { @@ -44,6 +80,39 @@ pub struct MsiData<'s, S: PciConfigurationSpace + ?Sized + 's> { offset: usize, } +pub struct VirtioDeviceConfigData<'s, S: PciConfigurationSpace + ?Sized + 's> { + space: &'s S, + offset: usize, +} + +pub struct VirtioCommonConfigData<'s, S: PciConfigurationSpace + ?Sized + 's> { + space: &'s S, + offset: usize, +} + +pub struct VirtioNotifyConfigData<'s, S: PciConfigurationSpace + ?Sized + 's> { + space: &'s S, + offset: usize, +} + +impl PciCapability for T { + const ID: PciCapabilityId = PciCapabilityId::VendorSpecific; + type CapabilityData<'a, S: PciConfigurationSpace + ?Sized + 'a> = T::Output<'a, S>; + + fn check(space: &S, offset: usize, len: usize) -> bool { + let cfg_type = space.read_u8(offset + 3); + cfg_type == T::CFG_TYPE && len >= T::MIN_LEN + } + + fn data<'s, S: PciConfigurationSpace + ?Sized + 's>( + space: &'s S, + offset: usize, + _len: usize, + ) -> Self::CapabilityData<'s, S> { + T::Output::from_space_offset(space, offset) + } +} + impl PciCapability for MsiXCapability { const ID: PciCapabilityId = PciCapabilityId::MsiX; type CapabilityData<'a, S: PciConfigurationSpace + ?Sized + 'a> = MsiXData<'a, S>; @@ -51,6 +120,7 @@ impl PciCapability for MsiXCapability { fn data<'s, S: PciConfigurationSpace + ?Sized + 's>( space: &'s S, offset: usize, + _len: usize, ) -> Self::CapabilityData<'s, S> { MsiXData { space, offset } } @@ -63,11 +133,82 @@ impl PciCapability for MsiCapability { fn data<'s, S: PciConfigurationSpace + ?Sized + 's>( space: &'s S, offset: usize, + _len: usize, ) -> Self::CapabilityData<'s, S> { MsiData { space, offset } } } +impl VirtioCapability for VirtioDeviceConfigCapability { + const CFG_TYPE: u8 = 0x04; + type Output<'a, S: PciConfigurationSpace + ?Sized + 'a> = VirtioDeviceConfigData<'a, S>; +} + +impl<'s, S: PciConfigurationSpace + ?Sized + 's> VirtioCapabilityData<'s, S> + for VirtioDeviceConfigData<'s, S> +{ + fn from_space_offset(space: &'s S, offset: usize) -> Self { + Self { space, offset } + } + + fn space(&self) -> &'s S { + self.space + } + + fn offset(&self) -> usize { + self.offset + } +} + +impl VirtioCapability for VirtioCommonConfigCapability { + const CFG_TYPE: u8 = 0x01; + type Output<'a, S: PciConfigurationSpace + ?Sized + 'a> = VirtioCommonConfigData<'a, S>; +} + +impl<'s, S: PciConfigurationSpace + ?Sized + 's> VirtioCapabilityData<'s, S> + for VirtioCommonConfigData<'s, S> +{ + fn from_space_offset(space: &'s S, offset: usize) -> Self { + Self { space, offset } + } + + fn space(&self) -> &'s S { + self.space + } + + fn offset(&self) -> usize { + self.offset + } +} + +impl VirtioCapability for VirtioNotifyConfigCapability { + const CFG_TYPE: u8 = 0x02; + const MIN_LEN: usize = 0x14; + type Output<'a, S: PciConfigurationSpace + ?Sized + 'a> = VirtioNotifyConfigData<'a, S>; +} + +impl<'s, S: PciConfigurationSpace + ?Sized + 's> VirtioNotifyConfigData<'s, S> { + pub fn offset_multiplier(&self) -> usize { + self.space.read_u32(self.offset + 16) as usize + } +} + +impl<'s, S: PciConfigurationSpace + ?Sized + 's> VirtioCapabilityData<'s, S> + for VirtioNotifyConfigData<'s, S> +{ + fn from_space_offset(space: &'s S, offset: usize) -> Self { + Self { space, offset } + } + + fn space(&self) -> &'s S { + self.space + } + + fn offset(&self) -> usize { + self.offset + } +} + impl<'s, S: PciConfigurationSpace + ?Sized + 's> MsiXData<'s, S> { // TODO use pending bits as well /// Maps and returns the vector table associated with the device's MSI-X capability diff --git a/driver/bus/pci/src/lib.rs b/driver/bus/pci/src/lib.rs index c01b1dbb..c76ae9ad 100644 --- a/driver/bus/pci/src/lib.rs +++ b/driver/bus/pci/src/lib.rs @@ -73,6 +73,8 @@ pub enum PciBaseAddress { pub enum PciCapabilityId { /// MSI (32-bit or 64-bit) Msi = 0x05, + /// Vendor-specific capability + VendorSpecific = 0x09, /// MSI-X MsiX = 0x11, /// Unknown capability missing from this list @@ -86,10 +88,15 @@ pub trait PciCapability { /// Wrapper for accessing the capability data structure type CapabilityData<'a, S: PciConfigurationSpace + ?Sized + 'a>; + fn check(space: &S, offset: usize, len: usize) -> bool { + true + } + /// Constructs an access wrapper for this capability with given offset fn data<'s, S: PciConfigurationSpace + ?Sized + 's>( space: &'s S, offset: usize, + len: usize, ) -> Self::CapabilityData<'s, S>; } @@ -104,6 +111,7 @@ pub struct PciDeviceInfo { pub enum PciMatch { Generic(fn(&PciDeviceInfo) -> bool), + Vendor(u16, u16), Class(u8, Option, Option), } @@ -134,6 +142,15 @@ pub struct PciBusManager { segments: Vec, } +impl PciBaseAddress { + pub fn as_memory(self) -> usize { + match self { + Self::Memory(address) => address, + _ => panic!("Not a memory BAR"), + } + } +} + impl PciBusSegment { fn probe_config_space(&self, address: PciAddress) -> Result, Error> { match self.ecam_phys_base { @@ -344,6 +361,9 @@ impl PciMatch { pub fn check_device(&self, info: &PciDeviceInfo, class: u8, subclass: u8, prog_if: u8) -> bool { match self { Self::Generic(f) => f(info), + &Self::Vendor(vendor_, device_) => { + info.config_space.vendor_id() == vendor_ && info.config_space.device_id() == device_ + } &Self::Class(class_, Some(subclass_), Some(prog_if_)) => { class_ == class && subclass_ == subclass && prog_if_ == prog_if } @@ -367,6 +387,19 @@ pub fn register_class_driver( }); } +pub fn register_vendor_driver( + name: &'static str, + vendor_id: u16, + device_id: u16, + probe: fn(&PciDeviceInfo) -> Result<&'static dyn Device, Error>, +) { + PCI_DRIVERS.lock().push(PciDriver { + name, + check: PciMatch::Vendor(vendor_id, device_id), + probe, + }); +} + pub fn register_generic_driver( name: &'static str, check: fn(&PciDeviceInfo) -> bool, diff --git a/driver/bus/pci/src/space/mod.rs b/driver/bus/pci/src/space/mod.rs index 182f911f..206d5326 100644 --- a/driver/bus/pci/src/space/mod.rs +++ b/driver/bus/pci/src/space/mod.rs @@ -79,12 +79,13 @@ pub struct CapabilityIterator<'s, S: PciConfigurationSpace + ?Sized> { } impl<'s, S: PciConfigurationSpace + ?Sized> Iterator for CapabilityIterator<'s, S> { - type Item = (PciCapabilityId, usize); + type Item = (PciCapabilityId, usize, usize); fn next(&mut self) -> Option { let offset = self.current? & !0x3; let id = unsafe { core::mem::transmute(self.space.read_u8(offset)) }; + let len = self.space.read_u8(offset + 2); let next_pointer = self.space.read_u8(offset + 1); self.current = if next_pointer != 0 { @@ -93,7 +94,7 @@ impl<'s, S: PciConfigurationSpace + ?Sized> Iterator for CapabilityIterator<'s, None }; - Some((id, offset)) + Some((id, offset, len as usize)) } } @@ -301,9 +302,9 @@ pub trait PciConfigurationSpace { /// Locates a capability within this configuration space fn capability(&self) -> Option> { - self.capability_iter().find_map(|(id, offset)| { - if id == C::ID { - Some(C::data(self, offset)) + self.capability_iter().find_map(|(id, offset, len)| { + if id == C::ID && C::check(self, offset, len) { + Some(C::data(self, offset, len)) } else { None } diff --git a/driver/fs/kernel-fs/src/devfs.rs b/driver/fs/kernel-fs/src/devfs.rs index 1adc7203..7833f6aa 100644 --- a/driver/fs/kernel-fs/src/devfs.rs +++ b/driver/fs/kernel-fs/src/devfs.rs @@ -1,9 +1,15 @@ //! Device virtual file system -use core::sync::atomic::{AtomicUsize, Ordering}; +use core::{ + net::IpAddr, + sync::atomic::{AtomicUsize, Ordering}, +}; -use alloc::{format, string::String}; +use alloc::{format, string::String, sync::Arc}; use kernel_util::util::OneTimeInit; -use vfs::{impls::MemoryDirectory, CharDevice, Node, NodeFlags, NodeRef}; +use vfs::{ + impls::{mdir, FnValueNode, MemoryDirectory, ReadOnlyFnValueNode}, + CharDevice, Node, NodeFlags, NodeRef, +}; use ygg_driver_block::BlockDevice; use yggdrasil_abi::error::Error; @@ -16,11 +22,28 @@ pub enum CharDeviceType { TtySerial, } +/// Describes the kind of a network device +#[derive(Debug)] +pub enum NetworkDeviceType { + /// Ethernet device + Ethernet, +} + +pub trait NetworkConfigInterface: Send + Sync { + fn address(&self) -> Result; + fn set_address(&self, addr: IpAddr) -> Result<(), Error>; + + fn mac(&self) -> [u8; 6]; +} + static DEVFS_ROOT: OneTimeInit = OneTimeInit::new(); +static NET_ROOT: OneTimeInit = OneTimeInit::new(); /// Sets up the device filesystem pub fn init() { - let root = MemoryDirectory::empty(); + let net = MemoryDirectory::empty(); + NET_ROOT.init(net.clone()); + let root = mdir([("net", net)]); DEVFS_ROOT.init(root); } @@ -55,6 +78,59 @@ pub fn add_named_block_device>( DEVFS_ROOT.get().add_child(name, node) } +pub fn add_network_config>(name: S, node: NodeRef) -> Result<(), Error> { + let name = name.into(); + NET_ROOT.get().add_child(name, node) +} + +pub fn add_named_network_device>( + name: S, + dev: Arc, +) -> Result<(), Error> { + let name = name.into(); + log::info!("Add network device: {}", name); + + let read_dev = dev.clone(); + let write_dev = dev.clone(); + let address = FnValueNode::new( + move || read_dev.address(), + move |value| write_dev.set_address(value), + ); + + let read_dev = dev.clone(); + let mac = ReadOnlyFnValueNode::new(move || { + let bytes = read_dev.mac(); + let mut out = String::new(); + for (i, byte) in bytes.into_iter().enumerate() { + if i != 0 { + out.push(':'); + } + out.push_str(&format!("{:02X}", byte)); + } + Ok(out) + }); + + let dev_dir = mdir([("address", address), ("mac", mac)]); + + NET_ROOT.get().add_child(name, dev_dir) +} + +pub fn add_network_device( + dev: Arc, + kind: NetworkDeviceType, +) -> Result<(), Error> { + static ETH_COUNT: AtomicUsize = AtomicUsize::new(0); + + let (count, prefix) = match kind { + NetworkDeviceType::Ethernet => (Ð_COUNT, "eth"), + }; + + let value = count.fetch_add(1, Ordering::AcqRel); + let name = format!("{}{}", prefix, value); + + add_named_network_device(name, dev) +} + pub fn add_block_device_partition>( base_name: S, index: usize, diff --git a/driver/net/core/Cargo.toml b/driver/net/core/Cargo.toml new file mode 100644 index 00000000..7c6be1f8 --- /dev/null +++ b/driver/net/core/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "ygg_driver_net_core" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +yggdrasil-abi = { git = "https://git.alnyan.me/yggdrasil/yggdrasil-abi.git" } +kernel-util = { path = "../../../lib/kernel-util" } +vfs = { path = "../../../lib/vfs" } + +kernel-fs = { path = "../../fs/kernel-fs" } + +log = "0.4.20" +bytemuck = { version = "1.14.0", features = ["derive"] } diff --git a/driver/net/core/src/ethernet.rs b/driver/net/core/src/ethernet.rs new file mode 100644 index 00000000..c7653dab --- /dev/null +++ b/driver/net/core/src/ethernet.rs @@ -0,0 +1,100 @@ +use core::{fmt, mem::size_of}; + +use bytemuck::{Pod, Zeroable}; +use kernel_util::mem::PageBox; +use yggdrasil_abi::error::Error; + +use crate::{ + interface::{self, NetworkInterface}, + l3, + types::{MacAddress, NetValue, Value}, +}; + +pub struct L2Packet { + pub interface_id: u32, + + pub source_address: MacAddress, + pub destination_address: MacAddress, + + pub l2_offset: usize, + pub l3_offset: usize, + + pub data: PageBox<[u8]>, +} + +impl L2Packet { + pub fn ethernet_frame(&self) -> &EthernetFrame { + bytemuck::from_bytes( + &self.data[self.l2_offset..self.l2_offset + size_of::()], + ) + } + + pub fn l2_data(&self) -> &[u8] { + &self.data[self.l3_offset..] + } +} + +#[derive(Clone, Copy, Debug, Pod, Zeroable)] +#[repr(C)] +pub struct EthernetFrame { + pub destination_mac: MacAddress, + pub source_mac: MacAddress, + pub ethertype: Value, +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug, Pod, Zeroable)] +#[repr(transparent)] +pub struct EtherType(u16); + +impl EtherType { + pub const ARP: Self = Self(0x0806); + pub const IPV4: Self = Self(0x0800); +} + +impl NetValue for EtherType { + fn to_network_order(self) -> Value { + Value(Self(self.0.to_be())) + } + + fn from_network_order(value: Value) -> Self { + Self(u16::from_be(value.0 .0)) + } +} + +pub fn send_l2( + interface: &NetworkInterface, + source_mac: MacAddress, + destination_mac: MacAddress, + ethertype: EtherType, + l2_data: &T, +) -> Result<(), Error> { + let l2_frame = EthernetFrame { + source_mac, + destination_mac, + ethertype: ethertype.to_network_order(), + }; + + log::debug!( + "send_l2: {} -> {}", + l2_frame.source_mac, + l2_frame.destination_mac + ); + + interface.send_l2(&l2_frame, bytemuck::bytes_of(l2_data)) +} + +pub fn handle(packet: L2Packet) { + let frame = packet.ethernet_frame(); + let ty = EtherType::from_network_order(frame.ethertype); + + match ty { + EtherType::ARP => l3::arp::handle_packet(packet), + EtherType::IPV4 => l3::ip::handle_v4_packet(packet), + p => { + log::debug!( + "Unrecognized L2 protocol: {:#06x}", + bytemuck::cast::<_, u16>(p) + ); + } + } +} diff --git a/driver/net/core/src/interface.rs b/driver/net/core/src/interface.rs new file mode 100644 index 00000000..4a843119 --- /dev/null +++ b/driver/net/core/src/interface.rs @@ -0,0 +1,116 @@ +use core::{ + mem::size_of, + net::{IpAddr, Ipv4Addr}, + sync::atomic::{AtomicU32, Ordering}, +}; + +use alloc::{collections::BTreeMap, sync::Arc, vec}; +use bytemuck::Pod; +use kernel_fs::devfs::{self, NetworkConfigInterface, NetworkDeviceType}; +// TODO: link state management? +use kernel_util::{ + mem::PageBox, + sync::{mutex::Mutex, spin_rwlock::IrqSafeRwLock}, +}; +use yggdrasil_abi::error::Error; + +use crate::{ + ethernet::EthernetFrame, + l3::{self, arp::ARP_TABLE, IpFrame, Route}, + types::{MacAddress, SubnetAddress, SubnetV4Address}, +}; + +pub trait NetworkDevice: Sync { + fn transmit(&self, packet: PageBox<[u8]>) -> Result<(), Error>; + fn packet_prefix_size(&self) -> usize; + + fn read_hardware_address(&self) -> MacAddress; +} + +pub struct NetworkInterface { + pub(crate) device: &'static dyn NetworkDevice, + pub(crate) mac: MacAddress, + + pub(crate) address: IrqSafeRwLock>, + pub(crate) id: u32, +} + +impl NetworkConfigInterface for NetworkInterface { + fn address(&self) -> Result { + if let Some(address) = self.address.read().as_ref() { + Ok(*address) + } else { + Err(Error::InvalidOperation) + } + } + + fn set_address(&self, addr: IpAddr) -> Result<(), Error> { + // Remove old ARP entry, if exists + ARP_TABLE.write().remove_ip(self.id, addr); + *self.address.write() = Some(addr); + ARP_TABLE.write().insert_ip(self.id, self.mac, addr, true); + match addr { + IpAddr::V4(v4) => { + l3::add_route(Route { + subnet: SubnetAddress::V4(SubnetV4Address::from_address_mask(v4, 24)), + interface: self.id, + // TODO manual configuration for this + gateway: Some(IpAddr::V4(Ipv4Addr::new(11, 0, 0, 1))), + }); + } + IpAddr::V6(_) => todo!(), + } + Ok(()) + } + + fn mac(&self) -> [u8; 6] { + self.mac.into() + } +} + +static ETH_INTERFACES: IrqSafeRwLock>> = + IrqSafeRwLock::new(BTreeMap::new()); +static ETH_INTERFACE_ID: AtomicU32 = AtomicU32::new(1); + +impl NetworkInterface { + pub fn get(id: u32) -> Result, Error> { + ETH_INTERFACES + .read() + .get(&id) + .cloned() + .ok_or(Error::DoesNotExist) + } + + pub fn send_l2(&self, l2_frame: &EthernetFrame, l2_data: &[u8]) -> Result<(), Error> { + let l2_offset = self.device.packet_prefix_size(); + let l2_data_offset = l2_offset + size_of::(); + + let mut packet = PageBox::new_slice(0, l2_data_offset + l2_data.len())?; + + packet[l2_offset..l2_data_offset].copy_from_slice(bytemuck::bytes_of(l2_frame)); + packet[l2_data_offset..].copy_from_slice(l2_data); + + self.device.transmit(packet) + } +} + +pub fn register_interface(dev: &'static dyn NetworkDevice) -> u32 { + let mac = dev.read_hardware_address(); + + let id = ETH_INTERFACE_ID.fetch_add(1, Ordering::SeqCst); + + let iface = NetworkInterface { + device: dev, + mac, + address: IrqSafeRwLock::new(None), + id, + }; + log::info!("Registered network interface #{}: {}", id, mac); + + let interface = Arc::new(iface); + + devfs::add_network_device(interface.clone(), NetworkDeviceType::Ethernet).unwrap(); + ETH_INTERFACES.write().insert(id, interface); + + id +} diff --git a/driver/net/core/src/l3/arp.rs b/driver/net/core/src/l3/arp.rs new file mode 100644 index 00000000..b89a1656 --- /dev/null +++ b/driver/net/core/src/l3/arp.rs @@ -0,0 +1,142 @@ +use core::{ + borrow::Borrow, + mem::size_of, + net::{IpAddr, Ipv4Addr}, +}; + +use alloc::{collections::BTreeMap, sync::Arc}; +use bytemuck::{Pod, Zeroable}; +use kernel_util::{mem::PageBox, sync::spin_rwlock::IrqSafeRwLock}; +use yggdrasil_abi::error::Error; + +use crate::{ + ethernet::{self, EtherType, EthernetFrame}, + interface::NetworkInterface, + types::{MacAddress, NetValue, Value}, + L2Packet, +}; + +#[derive(Clone, Copy, Debug, Pod, Zeroable)] +#[repr(C, packed)] +struct ArpFrame { + pub hardware_type: Value, + pub protocol: Value, + pub hardware_size: u8, + pub protocol_size: u8, + pub opcode: Value, + pub sender_mac: MacAddress, + pub sender_ip: Value, + pub target_mac: MacAddress, + // TODO handle IPv6 + pub target_ip: Value, +} + +pub struct ArpTable { + entries_v4: BTreeMap<(u32, Ipv4Addr), (MacAddress, bool)>, + reverse_v4: BTreeMap<(u32, MacAddress), (Ipv4Addr, bool)>, +} + +impl ArpTable { + pub const fn new() -> Self { + Self { + reverse_v4: BTreeMap::new(), + entries_v4: BTreeMap::new(), + } + } + + // TODO multiple IPs per MAC? + pub fn insert_v4(&mut self, interface: u32, mac: MacAddress, ip: Ipv4Addr, own: bool) { + if !self.reverse_v4.contains_key(&(interface, mac)) { + log::debug!("Insert {} <-> {}", mac, ip); + } + + self.reverse_v4.insert((interface, mac), (ip, own)); + self.entries_v4.insert((interface, ip), (mac, own)); + } + + pub fn insert_ip(&mut self, interface: u32, mac: MacAddress, ip: IpAddr, own: bool) { + match ip { + IpAddr::V4(v4) => self.insert_v4(interface, mac, v4, own), + IpAddr::V6(_) => todo!(), + } + } + + pub fn remove_ip(&mut self, interface: u32, ip: IpAddr) { + match ip { + IpAddr::V4(v4) => { + let entry = self.entries_v4.remove(&(interface, v4)); + if let Some((mac, _)) = entry { + self.reverse_v4.remove(&(interface, mac)).unwrap(); + } + } + IpAddr::V6(_) => todo!(), + } + } + + pub fn get_v4(&self, interface: u32, ip: Ipv4Addr) -> Option<(MacAddress, bool)> { + self.entries_v4.get(&(interface, ip)).cloned() + } +} + +pub(crate) static ARP_TABLE: IrqSafeRwLock = IrqSafeRwLock::new(ArpTable::new()); + +pub fn lookup(interface: u32, ip: IpAddr) -> Option { + // TODO send a request if MAC is not yet in the table + match ip { + IpAddr::V4(v4) => Some(ARP_TABLE.read().get_v4(interface, v4)?.0), + IpAddr::V6(_) => todo!(), + } +} + +fn send_reply(interface_id: u32, arp: &ArpFrame, target_mac: MacAddress) -> Result<(), Error> { + let interface = NetworkInterface::get(interface_id)?; + let reply = ArpFrame { + protocol: arp.protocol, + hardware_type: arp.hardware_type, + hardware_size: arp.hardware_size, + protocol_size: arp.protocol_size, + opcode: 2u16.to_network_order(), + + sender_mac: target_mac, + sender_ip: arp.target_ip, + + target_ip: arp.sender_ip, + target_mac: arp.sender_mac, + }; + + ethernet::send_l2( + &interface, + target_mac, + arp.sender_mac, + EtherType::ARP, + &reply, + ) +} + +pub fn handle_packet(packet: L2Packet) { + let arp: &ArpFrame = bytemuck::from_bytes(&packet.l2_data()[..size_of::()]); + let proto = EtherType::from_network_order(arp.protocol); + let opcode = u16::from_network_order(arp.opcode); + + let (target_address, sender_address) = match proto { + EtherType::IPV4 => ( + Ipv4Addr::from(u32::from_network_order(arp.target_ip)), + Ipv4Addr::from(u32::from_network_order(arp.sender_ip)), + ), + _ => { + log::warn!("TODO: unhandled ARP proto: {:#x?}", proto); + return; + } + }; + + let mut table = ARP_TABLE.write(); + table.insert_v4(packet.interface_id, arp.sender_mac, sender_address, false); + + if opcode == 1 { + // Don't answer with non-owned addresses + if let Some((mac, true)) = table.get_v4(packet.interface_id, target_address) { + // Reply with own address + send_reply(packet.interface_id, arp, mac).ok(); + } + } +} diff --git a/driver/net/core/src/l3/ip.rs b/driver/net/core/src/l3/ip.rs new file mode 100644 index 00000000..88945bb9 --- /dev/null +++ b/driver/net/core/src/l3/ip.rs @@ -0,0 +1,148 @@ +use core::{ + fmt, + mem::size_of, + net::{IpAddr, Ipv4Addr}, +}; + +use alloc::sync::Arc; +use bytemuck::{Pod, Zeroable}; +use yggdrasil_abi::error::Error; + +use crate::{ + ethernet::EthernetFrame, + interface::NetworkInterface, + l3, + l4::udp, + types::{InetChecksum, NetValue, Value}, + L2Packet, L3Packet, ACCEPT_QUEUE, +}; + +use super::IpFrame; + +#[derive(Clone, Copy, Debug, Pod, Zeroable)] +#[repr(C)] +pub struct Ipv4Frame { + pub version_length: u8, + pub dscp_flags: u8, + pub total_length: Value, + pub id: Value, + pub flags_frag: Value, + pub ttl: u8, + pub protocol: Protocol, + pub header_checksum: Value, + pub source_address: Value, + pub destination_address: Value, +} + +#[derive(Clone, Copy, PartialEq, Eq, Pod, Zeroable)] +#[repr(transparent)] +pub struct Protocol(pub u8); + +impl Protocol { + pub const ICMP: Self = Self(1); + pub const UDP: Self = Self(17); +} + +impl fmt::Debug for Protocol { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + Self::ICMP => f.write_str("ICMP"), + Self::UDP => f.write_str("UDP"), + _ => f.write_str(""), + } + } +} + +impl Ipv4Frame { + fn header_length(&self) -> usize { + core::cmp::min( + (self.version_length & 0xF) << 2, + size_of::() as u8, + ) as usize + } + + fn total_length(&self) -> usize { + u16::from_network_order(self.total_length) as usize + } +} + +impl IpFrame for Ipv4Frame { + fn destination_ip(&self) -> IpAddr { + IpAddr::V4(Ipv4Addr::from(u32::from_network_order( + self.destination_address, + ))) + } + + fn source_ip(&self) -> IpAddr { + IpAddr::V4(Ipv4Addr::from(u32::from_network_order(self.source_address))) + } + + fn data_length(&self) -> usize { + self.total_length() + .checked_sub(self.header_length()) + .unwrap_or(0) + } +} + +pub fn handle_v4_packet(packet: L2Packet) { + let Ok(interface) = NetworkInterface::get(packet.interface_id) else { + log::debug!("Invalid interface ID in L2 packet"); + return; + }; + + let l2_data = packet.l2_data(); + let l3_frame: &Ipv4Frame = bytemuck::from_bytes(&l2_data[..size_of::()]); + let header_length = l3_frame.header_length(); + let l3_data = &l2_data[size_of::()..]; + + let is_input = interface + .address + .read() + .map(|address| address == l3_frame.destination_ip()) + .unwrap_or(false); + + if is_input { + // Extract ports from L4 proto + let (source_port, destination_port) = match l3_frame.protocol { + Protocol::UDP => { + // TODO check size + let l4_frame: &udp::UdpFrame = + bytemuck::from_bytes(&l3_data[..size_of::()]); + ( + Some(u16::from_network_order(l4_frame.source_port)), + Some(u16::from_network_order(l4_frame.destination_port)), + ) + } + Protocol::ICMP => (None, None), + _ => (None, None), + }; + + let l3_packet = L3Packet { + interface_id: packet.interface_id, + + protocol: l3_frame.protocol, + + source_address: l3_frame.source_ip(), + destination_address: l3_frame.destination_ip(), + + source_port, + destination_port, + + l2_offset: packet.l2_offset, + l3_offset: packet.l3_offset, + l4_offset: packet.l3_offset + header_length, + data_length: l3_frame.data_length(), + + data: packet.data, + }; + + ACCEPT_QUEUE.receive_packet(l3_packet).ok(); + } else { + // TODO forwarding + log::debug!( + "Dropped forwarded IPv4: {} -> {}", + l3_frame.source_ip(), + l3_frame.destination_ip() + ); + } +} diff --git a/driver/net/core/src/l3/mod.rs b/driver/net/core/src/l3/mod.rs new file mode 100644 index 00000000..321b93b4 --- /dev/null +++ b/driver/net/core/src/l3/mod.rs @@ -0,0 +1,187 @@ +use core::{ + fmt, + mem::size_of, + net::{IpAddr, Ipv4Addr}, + str::FromStr, +}; + +use alloc::{sync::Arc, vec::Vec}; +use bytemuck::Pod; +use kernel_util::{mem::PageBox, sync::spin_rwlock::IrqSafeRwLock}; +use yggdrasil_abi::error::Error; + +use crate::{ + ethernet::{EtherType, EthernetFrame}, + interface::NetworkInterface, + l3::ip::Ipv4Frame, + l4, + types::{InetChecksum, NetValue, SubnetAddress}, +}; + +use self::ip::Protocol; + +pub mod arp; +pub mod ip; + +pub struct L3Packet { + pub interface_id: u32, + + pub protocol: Protocol, + + pub source_address: IpAddr, + pub destination_address: IpAddr, + + pub source_port: Option, + pub destination_port: Option, + + pub l2_offset: usize, + pub l3_offset: usize, + pub l4_offset: usize, + pub data_length: usize, + + pub data: PageBox<[u8]>, +} + +pub trait IpFrame: Pod { + fn destination_ip(&self) -> IpAddr; + fn source_ip(&self) -> IpAddr; + fn data_length(&self) -> usize; +} + +// TODO use range map for this? +pub struct Route { + pub subnet: SubnetAddress, + pub interface: u32, + pub gateway: Option, +} + +static ROUTES: IrqSafeRwLock> = IrqSafeRwLock::new(Vec::new()); + +impl L3Packet { + pub fn l3_data(&self) -> &[u8] { + &self.data[self.l4_offset..] + } +} + +pub fn lookup_route(address: IpAddr) -> Option<(u32, Option)> { + let routes = ROUTES.read(); + for route in routes.iter() { + if route.subnet.contains(&address) { + return Some((route.interface, route.gateway)); + } + } + None +} + +pub fn list_routes(mut f: F) { + let routes = ROUTES.read(); + for route in routes.iter() { + f(route); + } +} + +pub fn add_route(route: Route) -> Result<(), Error> { + // TODO check for conflicts + log::debug!("Add route: {}", route); + ROUTES.write().push(route); + Ok(()) +} + +impl fmt::Display for Route { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{} ", self.subnet)?; + if let Some(gw) = self.gateway { + write!(f, " via {}", gw)?; + } + Ok(()) + } +} + +pub fn handle_accepted(l3_packet: L3Packet) -> Result<(), Error> { + match l3_packet.protocol { + Protocol::UDP => l4::udp::handle(l3_packet), + Protocol::ICMP => l4::icmp::handle(l3_packet), + _ => todo!(), + } +} + +pub fn send_l4_ip( + destination_ip: IpAddr, + protocol: ip::Protocol, + l4_frame: &L4, + l4_data: &[u8], +) -> Result<(), Error> { + send_l4_ip_opt(destination_ip, protocol, l4_frame, &[], l4_data) +} + +pub fn send_l4_ip_opt( + destination_ip: IpAddr, + protocol: ip::Protocol, + l4_frame: &L4, + l4_options: &[u8], + l4_data: &[u8], +) -> Result<(), Error> { + let IpAddr::V4(destination_v4) = destination_ip else { + log::debug!("Destination: {}", destination_ip); + todo!(); + }; + + // Lookup route to destination + let (iface_id, gateway) = lookup_route(destination_ip).unwrap(); + let iface = NetworkInterface::get(iface_id).unwrap(); + + let Some(IpAddr::V4(gateway_v4)) = gateway else { + todo!(); + }; + let Some(IpAddr::V4(source_v4)) = *iface.address.read() else { + todo!(); + }; + + // Lookup gateway MAC + let gateway_mac = arp::lookup(iface.id, IpAddr::V4(gateway_v4)).unwrap(); + + let l2_offset = iface.device.packet_prefix_size(); + let l3_offset = l2_offset + size_of::(); + let l4_offset = l3_offset + size_of::(); + let l4_data_offset = l4_offset + size_of::(); + + let mut packet = PageBox::new_slice(0, l4_data_offset + l4_data.len())?; + + let l2_frame: &mut EthernetFrame = bytemuck::from_bytes_mut(&mut packet[l2_offset..l3_offset]); + + l2_frame.source_mac = iface.mac; + l2_frame.destination_mac = gateway_mac; + l2_frame.ethertype = EtherType::IPV4.to_network_order(); + + let l3_frame: &mut Ipv4Frame = bytemuck::from_bytes_mut(&mut packet[l3_offset..l4_offset]); + + l3_frame.source_address = u32::to_network_order(u32::from(source_v4)); + l3_frame.destination_address = u32::to_network_order(u32::from(destination_v4)); + l3_frame.protocol = protocol; + l3_frame.version_length = 0x45; + l3_frame.dscp_flags = 0; + l3_frame.total_length = u16::to_network_order( + (size_of::() + size_of::() + l4_data.len()) + .try_into() + .unwrap(), + ); + // Disable fragmentation + l3_frame.flags_frag = u16::to_network_order(0x4000); + l3_frame.id = u16::to_network_order(0); + l3_frame.header_checksum = u16::to_network_order(0); + l3_frame.ttl = 64; + + let l3_frame_bytes = &packet[l3_offset..l4_offset]; + let mut ip_checksum = InetChecksum::new(); + ip_checksum.add_bytes(l3_frame_bytes, true); + let ip_checksum = ip_checksum.finish(); + + let l3_frame: &mut Ipv4Frame = bytemuck::from_bytes_mut(&mut packet[l3_offset..l4_offset]); + l3_frame.header_checksum = u16::to_network_order(ip_checksum); + + packet[l4_offset..l4_data_offset].copy_from_slice(bytemuck::bytes_of(l4_frame)); + packet[l4_data_offset..l4_data_offset + l4_options.len()].copy_from_slice(l4_options); + packet[l4_data_offset + l4_options.len()..].copy_from_slice(l4_data); + + iface.device.transmit(packet) +} diff --git a/driver/net/core/src/l4/icmp.rs b/driver/net/core/src/l4/icmp.rs new file mode 100644 index 00000000..d4a0c95c --- /dev/null +++ b/driver/net/core/src/l4/icmp.rs @@ -0,0 +1,104 @@ +use core::{ + mem::size_of, + net::{IpAddr, Ipv4Addr}, +}; + +use bytemuck::{Pod, Zeroable}; +use yggdrasil_abi::error::Error; + +use crate::{ + l3::{self, ip::Protocol}, + types::{InetChecksum, NetValue, Value}, + L3Packet, +}; + +#[derive(Clone, Copy, Debug, Pod, Zeroable)] +#[repr(C)] +pub struct IcmpV4Frame { + ty: u8, + code: u8, + checksum: Value, + rest: Value, +} + +fn send_v4_reply( + destination_ip: Ipv4Addr, + icmp_frame: &IcmpV4Frame, + icmp_data: &[u8], +) -> Result<(), Error> { + let mut reply_frame = IcmpV4Frame { + ty: 0, + code: 0, + checksum: u16::to_network_order(0), + rest: icmp_frame.rest, + }; + + if icmp_data.len() % 2 != 0 { + todo!(); + } + + let l4_bytes = bytemuck::bytes_of(&reply_frame); + let mut checksum = InetChecksum::new(); + checksum.add_bytes(l4_bytes, true); + checksum.add_bytes(icmp_data, true); + + reply_frame.checksum = checksum.finish().to_network_order(); + + l3::send_l4_ip( + IpAddr::V4(destination_ip), + Protocol::ICMP, + &reply_frame, + icmp_data, + ) +} + +fn handle_v4(source_address: Ipv4Addr, l3_packet: L3Packet) -> Result<(), Error> { + if l3_packet.data_length < size_of::() { + log::debug!("Truncated ICMPv4 packet"); + return Err(Error::MissingData); + } + + if l3_packet.data_length - size_of::() > 576 { + log::debug!("ICMPv4 packet too large"); + return Err(Error::MissingData); + } + + let l3_data = l3_packet.l3_data(); + let icmp_frame: &IcmpV4Frame = bytemuck::from_bytes(&l3_data[..size_of::()]); + let icmp_data = &l3_data[size_of::()..l3_packet.data_length]; + + match (icmp_frame.ty, icmp_frame.code) { + (8, 0) => send_v4_reply(source_address, icmp_frame, icmp_data), + _ => { + log::debug!( + "Ignoring unknown ICMPv4 type:code: {}:{}", + icmp_frame.ty, + icmp_frame.code + ); + return Ok(()); + } + } +} + +pub fn handle(l3_packet: L3Packet) -> Result<(), Error> { + match l3_packet.source_address { + IpAddr::V4(v4) => handle_v4(v4, l3_packet), + IpAddr::V6(_) => todo!(), + } +} + +// fn send_icmp_v4_reply( +// l3_frame: &Ipv4Frame, +// icmp_frame: &IcmpV4Frame, +// icmp_data: &[u8], +// ) -> Result<(), Error> { +// +// l3::send_l4_ip(l3_frame.source_ip(), Protocol::ICMP, &l4_frame, icmp_data) +// } +// +// pub fn handle_icmp_v4_packet( +// interface: Arc, +// l3_frame: &Ipv4Frame, +// l3_data: &[u8], +// ) { +// } diff --git a/driver/net/core/src/l4/mod.rs b/driver/net/core/src/l4/mod.rs new file mode 100644 index 00000000..2b647e5c --- /dev/null +++ b/driver/net/core/src/l4/mod.rs @@ -0,0 +1,2 @@ +pub mod icmp; +pub mod udp; diff --git a/driver/net/core/src/l4/udp.rs b/driver/net/core/src/l4/udp.rs new file mode 100644 index 00000000..66b2beeb --- /dev/null +++ b/driver/net/core/src/l4/udp.rs @@ -0,0 +1,79 @@ +use core::{ + mem::size_of, + net::{IpAddr, SocketAddr}, +}; + +use bytemuck::{Pod, Zeroable}; +use yggdrasil_abi::error::Error; + +use crate::{ + l3::{self, ip::Protocol, IpFrame}, + socket::{self, UdpSocket}, + types::{NetValue, Value}, + L3Packet, +}; + +#[derive(Clone, Copy, Pod, Zeroable)] +#[repr(C)] +pub struct UdpFrame { + pub source_port: Value, + pub destination_port: Value, + pub length: Value, + pub checksum: Value, +} + +impl UdpFrame { + fn data_length(&self) -> usize { + (u16::from_network_order(self.length) as usize) + .checked_sub(0) + .unwrap_or(0) + } +} + +pub fn send( + source_port: u16, + destination_ip: IpAddr, + destination_port: u16, + data: &[u8], +) -> Result<(), Error> { + let length: u16 = (data.len() + size_of::()).try_into().unwrap(); + let udp_frame = UdpFrame { + source_port: source_port.to_network_order(), + destination_port: destination_port.to_network_order(), + length: length.to_network_order(), + checksum: 0u16.to_network_order(), + }; + + l3::send_l4_ip(destination_ip, Protocol::UDP, &udp_frame, data) +} + +pub fn handle(l3_packet: L3Packet) -> Result<(), Error> { + if l3_packet.data_length < size_of::() { + log::warn!("Truncated UDP frame received"); + return Err(Error::MissingData); + } + + let l3_data = l3_packet.l3_data(); + + let udp_frame: &UdpFrame = bytemuck::from_bytes(&l3_data[..size_of::()]); + let data_size = core::cmp::min( + udp_frame.data_length(), + l3_packet.data_length - size_of::(), + ); + let udp_data = &l3_data[size_of::()..data_size + size_of::()]; + + let source = SocketAddr::new( + l3_packet.source_address, + u16::from_network_order(udp_frame.source_port), + ); + let destination = SocketAddr::new( + l3_packet.destination_address, + u16::from_network_order(udp_frame.destination_port), + ); + + if let Some(socket) = UdpSocket::get(&destination) { + socket.packet_received(source, udp_data); + } + + Ok(()) +} diff --git a/driver/net/core/src/lib.rs b/driver/net/core/src/lib.rs new file mode 100644 index 00000000..bce65a9a --- /dev/null +++ b/driver/net/core/src/lib.rs @@ -0,0 +1,139 @@ +#![feature(map_try_insert)] +#![no_std] + +extern crate alloc; + +use core::{ + fmt, + future::Future, + mem::size_of, + net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4}, + pin::Pin, + str::FromStr, + sync::atomic::{AtomicU32, Ordering}, + task::{Context, Poll}, +}; + +use alloc::{collections::BTreeMap, format, string::String, vec::Vec}; +use ethernet::{EtherType, EthernetFrame, L2Packet}; +use interface::{NetworkDevice, NetworkInterface}; +use kernel_fs::devfs; +use kernel_util::{ + mem::PageBox, + runtime::{self, QueueWaker}, + sync::{mutex::Mutex, spin_rwlock::IrqSafeRwLock, IrqSafeSpinlock}, + util::ring::RingBuffer, +}; +use l3::{ip::Protocol, L3Packet, Route}; +use queue::Queue; +use socket::UdpSocket; +use types::{MacAddress, NetValue, SubnetAddress, Value}; +use vfs::{impls::FnValueNode, PacketSocket}; +use yggdrasil_abi::error::Error; + +use crate::types::SubnetV4Address; + +pub mod ethernet; +pub mod l3; +pub mod l4; + +pub mod socket; + +pub mod interface; +pub mod queue; +pub mod types; + +pub use interface::register_interface; + +pub struct Packet { + // TODO info about "received" interface + buffer: PageBox<[u8]>, + offset: usize, + iface: u32, +} + +impl Packet { + #[inline] + pub fn new(buffer: PageBox<[u8]>, offset: usize, iface: u32) -> Self { + Self { + buffer, + offset, + iface, + } + } +} + +static PACKET_QUEUE: Queue = Queue::new(); +static ACCEPT_QUEUE: Queue = Queue::new(); + +#[inline] +pub fn receive_packet(packet: Packet) -> Result<(), Error> { + PACKET_QUEUE.receive_packet(packet) +} + +pub fn start_network_tasks() -> Result<(), Error> { + devfs::add_network_config( + "routes", + FnValueNode::new( + || { + let mut output = String::new(); + l3::list_routes(|route| { + output.push_str(&format!("{}\n", route)); + }); + + Ok(output) + }, + |_value| Err(Error::NotImplemented), + ), + ); + + runtime::spawn(l2_packet_handler_worker())?; + for _ in 0..1 { + runtime::spawn(l3_accept_worker())?; + } + + Ok(()) +} + +async fn l2_packet_handler_worker() { + loop { + let packet = PACKET_QUEUE.wait().await; + + let eth_frame: &EthernetFrame = bytemuck::from_bytes( + &packet.buffer[packet.offset..packet.offset + size_of::()], + ); + + let l2_packet = L2Packet { + interface_id: packet.iface, + + source_address: eth_frame.source_mac, + destination_address: eth_frame.destination_mac, + + l2_offset: packet.offset, + l3_offset: packet.offset + size_of::(), + + data: packet.buffer, + }; + + ethernet::handle(l2_packet); + } +} + +async fn l3_accept_worker() { + loop { + let l3_packet = ACCEPT_QUEUE.wait().await; + + log::debug!( + "INPUT {:?} {}:{:?} -> {}:{:?}: ACCEPT", + l3_packet.protocol, + l3_packet.source_address, + l3_packet.source_port, + l3_packet.destination_address, + l3_packet.destination_port + ); + + if let Err(error) = l3::handle_accepted(l3_packet) { + log::error!("L3 handle error: {:?}", error); + } + } +} diff --git a/driver/net/core/src/queue.rs b/driver/net/core/src/queue.rs new file mode 100644 index 00000000..759754ed --- /dev/null +++ b/driver/net/core/src/queue.rs @@ -0,0 +1,53 @@ +use core::{ + future::Future, + pin::Pin, + task::{Context, Poll}, +}; + +use kernel_util::{runtime::QueueWaker, sync::IrqSafeSpinlock, util::ring::RingBuffer}; +use yggdrasil_abi::error::Error; + +pub struct Queue { + queue: IrqSafeSpinlock>, + notify: QueueWaker, +} + +impl Queue { + pub const fn new() -> Self { + Self { + queue: IrqSafeSpinlock::new(RingBuffer::with_capacity(1024)), + notify: QueueWaker::new(), + } + } + + pub fn receive_packet(&self, packet: T) -> Result<(), Error> { + self.queue.lock().write(packet); + self.notify.wake_one(); + // TODO notify of dropped packets + Ok(()) + } + + pub fn wait(&self) -> impl Future + '_ { + struct F<'f, T> { + queue: &'f Queue, + } + + impl<'f, T> Future for F<'f, T> { + type Output = T; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + self.queue.notify.register(cx.waker()); + let mut lock = self.queue.queue.lock(); + + if lock.is_readable() { + self.queue.notify.remove(cx.waker()); + Poll::Ready(unsafe { lock.read_single_unchecked() }) + } else { + Poll::Pending + } + } + } + + F { queue: self } + } +} diff --git a/driver/net/core/src/socket.rs b/driver/net/core/src/socket.rs new file mode 100644 index 00000000..fe851462 --- /dev/null +++ b/driver/net/core/src/socket.rs @@ -0,0 +1,198 @@ +use core::{ + future::Future, + net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4}, + pin::Pin, + sync::atomic::{AtomicBool, Ordering}, + task::{Context, Poll}, +}; + +use alloc::{collections::BTreeMap, sync::Arc, vec::Vec}; +use kernel_util::{ + block, + runtime::QueueWaker, + sync::{ + mutex::{Mutex, MutexGuard}, + spin_rwlock::{IrqSafeRwLock, IrqSafeRwLockReadGuard}, + IrqSafeSpinlock, LockMethod, + }, + util::ring::RingBuffer, +}; +use vfs::{FileReadiness, PacketSocket, Socket}; +use yggdrasil_abi::error::Error; + +use crate::{l4, queue::Queue}; + +pub struct UdpSocket { + local: SocketAddr, + remote: Option, + // TODO just place packets here for one less copy? + receive_queue: Mutex)>>, + receive_notify: QueueWaker, +} + +pub struct SocketTable { + inner: BTreeMap>, +} + +impl SocketTable { + pub const fn new() -> Self { + Self { + inner: BTreeMap::new(), + } + } + + pub fn insert(&mut self, address: SocketAddr, socket: Arc) -> Result<(), Error> { + match self.inner.try_insert(address, socket) { + Ok(_) => Ok(()), + Err(_) => return Err(Error::AlreadyExists), + } + } + + pub fn remove(&mut self, local: SocketAddr) -> Result<(), Error> { + match self.inner.remove(&local) { + Some(_) => Ok(()), + None => Err(Error::DoesNotExist), + } + } + + pub fn get_exact(&self, local: &SocketAddr) -> Option> { + self.inner.get(local).cloned() + } + + pub fn get(&self, local: &SocketAddr) -> Option> { + if let Some(socket) = self.inner.get(local) { + return Some(socket.clone()); + } + + match local { + SocketAddr::V4(v4) => { + let unspec_v4 = SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, local.port()); + self.inner.get(&SocketAddr::V4(unspec_v4)).cloned() + } + SocketAddr::V6(_) => todo!(), + } + } +} + +static UDP_SOCKETS: IrqSafeRwLock> = IrqSafeRwLock::new(SocketTable::new()); + +impl UdpSocket { + pub fn bind(address: SocketAddr) -> Result, Error> { + let mut sockets = UDP_SOCKETS.write(); + + if sockets.get(&address).is_some() { + // TODO use network-specific error + return Err(Error::AlreadyExists); + } + + let socket = Arc::new(Self { + local: address, + remote: None, + receive_queue: Mutex::new(RingBuffer::try_with_capacity(128)?), + receive_notify: QueueWaker::new(), + }); + + sockets.insert(address, socket.clone()); + + log::debug!("UDP socket opened: {}", address); + + Ok(socket) + } + + pub fn connect(address: SocketAddr) -> Result, Error> { + todo!() + } + + pub fn get(local: &SocketAddr) -> Option> { + UDP_SOCKETS.read().get(local) + } + + fn poll_receive( + &self, + cx: &mut Context<'_>, + ) -> Poll)>>, Error>> { + self.receive_notify.register(cx.waker()); + let mut lock = self.receive_queue.lock()?; + if lock.is_readable() { + self.receive_notify.remove(cx.waker()); + Poll::Ready(Ok(lock)) + } else { + Poll::Pending + } + } + + pub fn receive_raw<'a>( + &'a self, + ) -> impl Future), Error>> + 'a { + struct F<'f> { + socket: &'f UdpSocket, + } + + impl<'f> Future for F<'f> { + type Output = Result<(SocketAddr, Vec), Error>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match self.socket.poll_receive(cx)? { + Poll::Ready(mut lock) => { + let (source, data) = unsafe { lock.read_single_unchecked() }; + Poll::Ready(Ok((source, data))) + } + Poll::Pending => Poll::Pending, + } + } + } + + F { socket: self } + } + + pub fn packet_received(&self, source: SocketAddr, data: &[u8]) -> Result<(), Error> { + let mut lock = self.receive_queue.lock()?; + lock.write((source, Vec::from(data))); + self.receive_notify.wake_one(); + Ok(()) + } +} + +impl FileReadiness for UdpSocket { + fn poll_read(&self, cx: &mut Context<'_>) -> Poll> { + self.poll_receive(cx).map_ok(|_| ()) + } +} + +impl PacketSocket for UdpSocket { + fn send(&self, destination: SocketAddr, data: &[u8]) -> Result { + // TODO check that destnation family matches self family + l4::udp::send( + self.local.port(), + destination.ip(), + destination.port(), + data, + )?; + + Ok(data.len()) + } + + fn receive(&self, buffer: &mut [u8]) -> Result<(SocketAddr, usize), Error> { + let (source, data) = block!(self.receive_raw().await)??; + if data.len() > buffer.len() { + todo!() + } + buffer[..data.len()].copy_from_slice(&data); + Ok((source, data.len())) + } +} + +impl Socket for UdpSocket { + fn local_address(&self) -> SocketAddr { + self.local + } + + fn remote_address(&self) -> Option { + self.remote + } + + fn close(&self) -> Result<(), Error> { + log::debug!("UDP socket closed: {}", self.local); + UDP_SOCKETS.write().remove(self.local) + } +} diff --git a/driver/net/core/src/types.rs b/driver/net/core/src/types.rs new file mode 100644 index 00000000..fa439d54 --- /dev/null +++ b/driver/net/core/src/types.rs @@ -0,0 +1,225 @@ +use core::{ + fmt, + net::{IpAddr, Ipv4Addr}, + str::FromStr, +}; + +use bytemuck::{Pod, Zeroable}; +use yggdrasil_abi::error::Error; + +#[macro_export] +macro_rules! wrap_value { + ($vis:vis struct $name:ident($ty_vis:vis $ty:ty)) => { + #[derive(Clone, Copy, Debug, bytemuck::Pod, bytemuck::Zeroable)] + $vis struct $name($ty_vis $ty); + + impl $crate::types::NetValue for $name { + fn from_network_order(value: $crate::types::Value) -> Self { + todo!() + } + + fn to_network_order(self) -> $crate::types::Value { + todo!() + } + } + }; +} + +pub trait NetValue: Copy + Eq + Pod + Zeroable { + fn from_network_order(value: Value) -> Self; + fn to_network_order(self) -> Value; +} + +#[derive(Clone, Copy, PartialEq, Eq, Pod, Zeroable)] +#[repr(transparent)] +pub struct Value(pub T); + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Pod, Zeroable)] +#[repr(transparent)] +pub struct MacAddress([u8; 6]); + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub struct SubnetV4Address { + pub root: Ipv4Addr, + pub mask_bits: u8, + pub mask: u32, +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum SubnetAddress { + V4(SubnetV4Address), +} + +impl fmt::Debug for Value { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let value = T::from_network_order(*self); + fmt::Debug::fmt(&value, f) + } +} + +impl NetValue for u16 { + fn from_network_order(value: Value) -> Self { + Self::from_be(value.0) + } + + fn to_network_order(self) -> Value { + Value(self.to_be()) + } +} + +impl NetValue for u32 { + fn from_network_order(value: Value) -> Self { + Self::from_be(value.0) + } + + fn to_network_order(self) -> Value { + Value(self.to_be()) + } +} + +impl fmt::Display for MacAddress { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use fmt::Write; + + for (i, &v) in self.0.iter().enumerate() { + if i != 0 { + write!(f, ":")?; + } + write!(f, "{:02X}", v)?; + } + + Ok(()) + } +} + +impl From for [u8; 6] { + fn from(value: MacAddress) -> Self { + value.0 + } +} + +impl From<[u8; 6]> for MacAddress { + fn from(value: [u8; 6]) -> Self { + Self(value) + } +} + +impl SubnetV4Address { + pub const UNSPECIFIED: Self = Self { + root: Ipv4Addr::new(0, 0, 0, 0), + mask: 0, + mask_bits: 0, + }; + + pub fn from_address_mask(root: Ipv4Addr, mask_bits: u8) -> Self { + let root = u32::from(root); + let mask = Self::make_mask(mask_bits); + // Clear masked bits + let root = root & mask; + + Self { + root: Ipv4Addr::from(root), + mask_bits, + mask, + } + } + + pub fn contains(&self, address: &Ipv4Addr) -> bool { + let root = u32::from(self.root); + let address = u32::from(*address); + + (address & self.mask) == root + } + + fn make_mask(bits: u8) -> u32 { + ((1 << bits) - 1) << (32 - bits) + } +} + +impl fmt::Display for SubnetV4Address { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}/{}", self.root, self.mask_bits) + } +} + +impl FromStr for SubnetV4Address { + type Err = Error; + + fn from_str(s: &str) -> Result { + let (addr, mask) = s.split_once('/').ok_or(Error::InvalidArgument)?; + let addr = Ipv4Addr::from_str(addr).map_err(|_| Error::InvalidArgument)?; + let mask = u8::from_str(mask).map_err(|_| Error::InvalidArgument)?; + + Ok(Self::from_address_mask(addr, mask)) + } +} + +impl SubnetAddress { + pub const UNSPECIFIED_V4: Self = Self::V4(SubnetV4Address::UNSPECIFIED); + + pub fn contains(&self, address: &IpAddr) -> bool { + match (self, address) { + (Self::V4(subnet), IpAddr::V4(address)) => subnet.contains(address), + _ => false, + } + } + + pub fn is_v4(&self) -> bool { + true + } +} + +impl From for SubnetAddress { + fn from(value: SubnetV4Address) -> Self { + Self::V4(value) + } +} + +impl FromStr for SubnetAddress { + type Err = Error; + + fn from_str(s: &str) -> Result { + // TODO v6 + let v4 = SubnetV4Address::from_str(s)?; + Ok(Self::from(v4)) + } +} + +impl fmt::Display for SubnetAddress { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::V4(v4) => fmt::Display::fmt(v4, f), + } + } +} + +pub struct InetChecksum { + state: u32, +} + +impl InetChecksum { + pub fn new() -> Self { + Self { state: 0 } + } + + pub fn add_bytes(&mut self, bytes: &[u8], reorder: bool) { + let len = bytes.len(); + for i in 0..len / 2 { + let word = if reorder { + ((bytes[i * 2] as u16) << 8) | (bytes[i * 2 + 1] as u16) + } else { + (bytes[i * 2] as u16) | ((bytes[i * 2 + 1] as u16) << 8) + }; + self.state = self.state.wrapping_add(word as u32); + } + if len % 2 != 0 { + self.state += self.state.wrapping_add(bytes[len - 1] as u32); + } + } + + pub fn finish(self) -> u16 { + let sum = (self.state >> 16) + (self.state & 0xFFFF); + + (!(sum & 0xFFFF)) as u16 + } +} diff --git a/driver/virtio/core/Cargo.toml b/driver/virtio/core/Cargo.toml new file mode 100644 index 00000000..f2d0bc67 --- /dev/null +++ b/driver/virtio/core/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "ygg_driver_virtio_core" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +yggdrasil-abi = { git = "https://git.alnyan.me/yggdrasil/yggdrasil-abi.git" } +kernel-util = { path = "../../../lib/kernel-util" } +device-api = { path = "../../../lib/device-api", features = ["derive"] } + +ygg_driver_pci = { path = "../../bus/pci", optional = true } + +log = "0.4.20" +bitflags = "2.4.2" +tock-registers = "0.8.1" + +[features] +default = [] +pci = ["ygg_driver_pci"] diff --git a/driver/virtio/core/src/error.rs b/driver/virtio/core/src/error.rs new file mode 100644 index 00000000..6538e069 --- /dev/null +++ b/driver/virtio/core/src/error.rs @@ -0,0 +1,20 @@ +#[derive(Debug)] +pub enum Error { + OsError(yggdrasil_abi::error::Error), + InvalidPciConfiguration, + NoCommonConfigCapability, + NoNotifyConfigCapability, + NoDeviceConfigCapability, + QueueTooLarge, + InvalidQueueSize, + EmptyTransaction, + QueueFull, + QueueEmpty, + WrongToken, +} + +impl From for Error { + fn from(value: yggdrasil_abi::error::Error) -> Self { + Self::OsError(value) + } +} diff --git a/driver/virtio/core/src/lib.rs b/driver/virtio/core/src/lib.rs new file mode 100644 index 00000000..e0505ccc --- /dev/null +++ b/driver/virtio/core/src/lib.rs @@ -0,0 +1,54 @@ +#![no_std] + +extern crate alloc; + +pub mod error; +pub mod queue; +pub mod transport; + +use bitflags::bitflags; +use tock_registers::{ + interfaces::{Readable, Writeable}, + register_structs, + registers::{ReadOnly, ReadWrite, WriteOnly}, +}; + +register_structs! { + pub CommonConfiguration { + (0x00 => device_feature_select: ReadWrite), + (0x04 => device_feature: ReadOnly), + (0x08 => driver_feature_select: ReadWrite), + (0x0C => driver_feature: ReadWrite), + (0x10 => msix_config: ReadWrite), + (0x12 => num_queues: ReadOnly), + (0x14 => device_status: ReadWrite), + (0x15 => config_generation: ReadOnly), + + (0x16 => queue_select: ReadWrite), + (0x18 => queue_size: ReadWrite), + (0x1A => queue_msix_vector: ReadWrite), + (0x1C => queue_enable: ReadWrite), + (0x1E => queue_notify_off: ReadWrite), + (0x20 => queue_desc: ReadWrite), + (0x28 => queue_driver: ReadWrite), + (0x30 => queue_device: ReadWrite), + + (0x38 => @END), + } +} + +bitflags! { + #[derive(Clone, Copy)] + pub struct DeviceStatus: u8 { + const ACKNOWLEDGE = 1 << 0; + const DRIVER = 1 << 1; + const DRIVER_OK = 1 << 2; + const FEATURES_OK = 1 << 3; + const DEVICE_NEEDS_RESET = 1 << 6; + const FAILED = 1 << 7; + } +} + +impl DeviceStatus { + pub const RESET_VALUE: Self = Self::empty(); +} diff --git a/driver/virtio/core/src/queue.rs b/driver/virtio/core/src/queue.rs new file mode 100644 index 00000000..4fd27108 --- /dev/null +++ b/driver/virtio/core/src/queue.rs @@ -0,0 +1,344 @@ +//! VirtIO queue implementation. +//! +//! # Note +//! +//! The code is poorly borrowed from `virtio-drivers` crate. I want to rewrite it properly myself. +use core::{ + mem::MaybeUninit, + sync::atomic::{fence, Ordering}, +}; + +use alloc::boxed::Box; +use kernel_util::mem::{ + address::{AsPhysicalAddress, IntoRaw}, + PageBox, +}; + +use crate::{error::Error, transport::Transport}; + +#[derive(Debug)] +#[repr(C)] +struct Descriptor { + address: u64, + len: u32, + flags: u16, + next: u16, +} + +// Layout: +// { +// flags: u16, +// idx: u16, +// ring: [u16; QUEUE_SIZE], +// used_event: u16 +// } +struct AvailableRing { + data: PageBox<[MaybeUninit]>, +} + +// Layout: +// { +// flags: u16, +// idx: u16, +// ring: [UsedElem; QUEUE_SIZE], +// avail_event: u16, +// _pad: u16 +// } +struct UsedRing { + data: PageBox<[MaybeUninit]>, + + used_count: usize, +} + +pub struct VirtQueue { + descriptor_table: PageBox<[MaybeUninit]>, + available: AvailableRing, + used: UsedRing, + + capacity: usize, + + queue_index: u16, + free_head: u16, + + avail_idx: u16, + last_used_idx: u16, + + msix_vector: u16, +} + +impl AvailableRing { + pub fn with_capacity(capacity: usize) -> Result { + let mut data = PageBox::new_uninit_slice(capacity + 3)?; + + data[1].write(0); + + Ok(Self { data }) + } + + pub fn set_head(&mut self, slot: u16, head: u16) { + self.data[slot as usize + 2].write(head); + } + + pub fn set_index(&mut self, index: u16) { + self.data[1].write(index); + } +} + +impl UsedRing { + pub fn with_capacity(capacity: usize) -> Result { + let mut data = PageBox::new_uninit_slice(capacity * 2 + 2)?; + + data[0].write(0); + + Ok(Self { + data, + used_count: 0, + }) + } + + pub fn read_slot(&self, index: u16) -> (u32, u32) { + let index = unsafe { self.data[1 + index as usize * 2].assume_init() }; + let len = unsafe { self.data[2 + index as usize * 2].assume_init() }; + (index, len) + } + + pub fn index(&self) -> u16 { + unsafe { (self.data[0].assume_init() >> 16) as u16 } + } +} + +impl VirtQueue { + pub fn with_capacity( + transport: &mut T, + index: u16, + capacity: usize, + msix_vector: Option, + ) -> Result { + // TODO check if queue is already set up + + let max_capacity = transport.max_queue_size(index); + + if !capacity.is_power_of_two() || capacity > u16::MAX.into() { + return Err(Error::InvalidQueueSize); + } + + if capacity > max_capacity as usize { + return Err(Error::QueueTooLarge); + } + + let descriptor_table = PageBox::new_uninit_slice(capacity)?; + let available = AvailableRing::with_capacity(capacity)?; + let used = UsedRing::with_capacity(capacity)?; + + transport.set_queue( + index, + capacity as u16, + unsafe { descriptor_table.as_physical_address() }, + unsafe { available.data.as_physical_address() }, + unsafe { used.data.as_physical_address() }, + msix_vector, + ); + + Ok(Self { + descriptor_table, + available, + used, + + capacity, + + queue_index: index, + free_head: 0, + + avail_idx: 0, + last_used_idx: 0, + + msix_vector: msix_vector.unwrap_or(0xFFFF), + }) + } + + pub fn capacity(&self) -> usize { + self.capacity + } + + pub fn with_max_capacity( + transport: &mut T, + index: u16, + capacity: usize, + msix_vector: Option, + ) -> Result { + let max_capacity = transport.max_queue_size(index); + let capacity = capacity.min(max_capacity as usize); + + Self::with_capacity(transport, index, capacity, msix_vector) + } + + pub unsafe fn add<'a, 'b>( + &mut self, + input: &'a [&'b mut PageBox<[u8]>], + output: &'a [&'b PageBox<[u8]>], + ) -> Result { + if input.is_empty() && output.is_empty() { + return Err(Error::EmptyTransaction); + } + let n_desc = input.len() + output.len(); + + if self.used.used_count + 1 > self.capacity || self.used.used_count + n_desc > self.capacity + { + return Err(Error::QueueFull); + } + + let head = self.add_direct(input, output); + let avail_slot = self.avail_idx % self.capacity as u16; + + self.available.set_head(avail_slot, head); + + fence(Ordering::SeqCst); + + self.avail_idx = self.avail_idx.wrapping_add(1); + self.available.set_index(self.avail_idx); + + fence(Ordering::SeqCst); + + Ok(head) + } + + unsafe fn add_direct<'a, 'b>( + &mut self, + input: &'a [&'b mut PageBox<[u8]>], + output: &'a [&'b PageBox<[u8]>], + ) -> u16 { + let head = self.free_head; + let mut last = self.free_head; + + for item in input { + assert_ne!(item.len(), 0); + let desc = &mut self.descriptor_table[usize::from(self.free_head)]; + let next = (self.free_head + 1) % self.capacity as u16; + + let desc = desc.write(Descriptor { + address: item.as_physical_address().into_raw(), + len: item.len().try_into().unwrap(), + // TODO MAGIC + flags: (1 << 0) | (1 << 1), + next, + }); + + last = self.free_head; + self.free_head = next; + } + + for item in output { + assert_ne!(item.len(), 0); + let desc = &mut self.descriptor_table[usize::from(self.free_head)]; + let next = (self.free_head + 1) % self.capacity as u16; + + let desc = desc.write(Descriptor { + address: item.as_physical_address().into_raw(), + len: item.len().try_into().unwrap(), + // TODO + flags: (1 << 0), + next, + }); + + last = self.free_head; + self.free_head = next; + } + + { + let last_desc = self.descriptor_table[last as usize].assume_init_mut(); + + // TODO + last_desc.flags &= !(1 << 0); + } + + self.used.used_count += (input.len() + output.len()); + + head + } + + pub fn add_notify_wait_pop<'a, 'b, T: Transport>( + &mut self, + input: &'a [&'b mut PageBox<[u8]>], + output: &'a [&'b PageBox<[u8]>], + transport: &mut T, + ) -> Result { + let token = unsafe { self.add(input, output) }?; + + transport.notify(self.queue_index); + + while self.is_used_empty() { + core::hint::spin_loop(); + } + + unsafe { self.pop_used(token) } + } + + pub fn is_used_empty(&self) -> bool { + fence(Ordering::SeqCst); + + self.last_used_idx == self.used.index() + } + + pub unsafe fn pop_last_used(&mut self) -> Option<(u16, u32)> { + let token = self.peek_used()?; + let len = self.pop_used(token).unwrap(); + + Some((token, len)) + } + + pub unsafe fn peek_used(&mut self) -> Option { + if !self.is_used_empty() { + let last_used = self.last_used_idx % self.capacity as u16; + Some(self.used.read_slot(last_used).0 as u16) + } else { + None + } + } + + pub unsafe fn pop_used(&mut self, token: u16) -> Result { + if self.is_used_empty() { + return Err(Error::QueueEmpty); + } + + let last_used_slot = self.last_used_idx % self.capacity as u16; + let (index, len) = self.used.read_slot(last_used_slot); + + if index != token as u32 { + return Err(Error::WrongToken); + } + + let freed_count = self.free_descriptor_chain(token); + + self.last_used_idx = self.last_used_idx.wrapping_add(1); + + Ok(len) + } + + unsafe fn free_descriptor_chain(&mut self, head: u16) -> usize { + let mut current_node = Some(self.descriptor_table[usize::from(head)].assume_init_mut()); + let mut count = 0; + + while let Some(current) = current_node { + assert_ne!(current.len, 0); + let next_head = (current.flags & (1 << 0) != 0).then_some(current.next); + + current.address = 0; + current.flags = 0; + current.next = 0; + current.len = 0; + + self.used.used_count -= 1; + count += 1; + + current_node = + next_head.map(|head| self.descriptor_table[usize::from(head)].assume_init_mut()); + } + + self.free_head = head; + count + } + + pub fn msix_vector(&self) -> u16 { + self.msix_vector + } +} diff --git a/driver/virtio/core/src/transport/mod.rs b/driver/virtio/core/src/transport/mod.rs new file mode 100644 index 00000000..b4db67ab --- /dev/null +++ b/driver/virtio/core/src/transport/mod.rs @@ -0,0 +1,92 @@ +use core::mem::size_of; + +use kernel_util::mem::{ + address::{IntoRaw, PhysicalAddress}, + device::DeviceMemoryIo, +}; +use tock_registers::{ + interfaces::{Readable, Writeable}, + registers::{ReadWrite, WriteOnly}, +}; + +use crate::{CommonConfiguration, DeviceStatus}; + +pub mod pci; + +pub trait Transport { + fn common_cfg(&self) -> &CommonConfiguration; + fn notify_cfg(&self) -> &[WriteOnly]; + fn notify_off_mul(&self) -> usize; + fn supports_msix(&self) -> bool; + fn device_cfg(&self) -> Option<&DeviceMemoryIo<[u8]>>; + + fn read_device_features(&mut self) -> u64 { + let cfg = self.common_cfg(); + cfg.device_feature_select.set(0); + let low = cfg.device_feature.get(); + cfg.device_feature_select.set(1); + let high = cfg.device_feature.get(); + + (low as u64) | ((high as u64) << 32) + } + + fn write_driver_features(&mut self, value: u64) { + let cfg = self.common_cfg(); + cfg.driver_feature_select.set(0); + cfg.driver_feature.set(value as u32); + cfg.driver_feature_select.set(1); + cfg.driver_feature.set((value >> 32) as u32); + } + + fn read_device_status(&mut self) -> DeviceStatus { + let cfg = self.common_cfg(); + DeviceStatus::from_bits_retain(cfg.device_status.get()) + } + + fn write_device_status(&mut self, value: DeviceStatus) { + let cfg = self.common_cfg(); + cfg.device_status.set(value.bits()); + } + + fn max_queue_size(&mut self, queue: u16) -> u32 { + let cfg = self.common_cfg(); + cfg.queue_select.set(queue); + cfg.queue_size.get().into() + } + + fn set_queue( + &mut self, + queue: u16, + capacity: u16, + descriptor_table_phys: PhysicalAddress, + available_ring_phys: PhysicalAddress, + used_ring_phys: PhysicalAddress, + msix_vector: Option, + ) { + let cfg = self.common_cfg(); + cfg.queue_select.set(queue); + cfg.queue_size.set(capacity); + cfg.queue_desc.set(descriptor_table_phys.into_raw()); + cfg.queue_driver.set(available_ring_phys.into_raw()); + cfg.queue_device.set(used_ring_phys.into_raw()); + if self.supports_msix() { + cfg.queue_msix_vector.set(msix_vector.unwrap_or(0xFFFF)); + } + cfg.queue_enable.set(1); + } + + fn unset_queue(&mut self, queue: u16) { + todo!() + } + + fn notify(&mut self, queue: u16) { + let cfg = self.common_cfg(); + let notify = self.notify_cfg(); + + cfg.queue_select.set(queue); + let notify_off = cfg.queue_notify_off.get() as usize; + let index = (notify_off * self.notify_off_mul()) / size_of::(); + + notify[index].set(queue); + } +} diff --git a/driver/virtio/core/src/transport/pci.rs b/driver/virtio/core/src/transport/pci.rs new file mode 100644 index 00000000..aac330d1 --- /dev/null +++ b/driver/virtio/core/src/transport/pci.rs @@ -0,0 +1,119 @@ +use kernel_util::mem::{ + address::{FromRaw, PhysicalAddress}, + device::DeviceMemoryIo, +}; +use tock_registers::registers::WriteOnly; +use ygg_driver_pci::{ + capability::{ + MsiXCapability, VirtioCapabilityData, VirtioCommonConfigCapability, + VirtioDeviceConfigCapability, VirtioNotifyConfigCapability, + }, + PciCommandRegister, PciConfigurationSpace, +}; + +use crate::{error::Error, CommonConfiguration}; + +use super::Transport; + +pub struct PciTransport { + common_cfg: DeviceMemoryIo<'static, CommonConfiguration>, + device_cfg: DeviceMemoryIo<'static, [u8]>, + notify_cfg: DeviceMemoryIo<'static, [WriteOnly]>, + notify_cfg_mul: usize, +} + +impl Transport for PciTransport { + fn common_cfg(&self) -> &CommonConfiguration { + &self.common_cfg + } + + fn notify_cfg(&self) -> &[WriteOnly] { + &self.notify_cfg + } + + fn notify_off_mul(&self) -> usize { + self.notify_cfg_mul + } + + fn supports_msix(&self) -> bool { + true + } + + fn device_cfg(&self) -> Option<&DeviceMemoryIo<[u8]>> { + Some(&self.device_cfg) + } +} + +impl PciTransport { + pub fn from_config_space(space: &S) -> Result { + // Transitional devices MUST have a PCI Revision ID of 0. + // Transitional devices MUST have the PCI Subsystem Device ID + // matching the Virtio Device ID, as indicated in section 5. + // Transitional devices MUST have the Transitional PCI + // Device ID in the range 0x1000 to 0x103f. + // TODO check PCI subsystem ID + if space.rev_id() != 0 { + return Err(Error::InvalidPciConfiguration); + } + + let mut cmd = PciCommandRegister::from_bits_retain(space.command()); + cmd &= !(PciCommandRegister::DISABLE_INTERRUPTS | PciCommandRegister::ENABLE_IO); + cmd |= PciCommandRegister::ENABLE_MEMORY | PciCommandRegister::BUS_MASTER; + space.set_command(cmd.bits()); + + // Extract capabilities + + let common_cfg_cap = space + .capability::() + .ok_or(Error::NoCommonConfigCapability)?; + // TODO this is not mandatory + let device_cfg_cap = space + .capability::() + .ok_or(Error::NoDeviceConfigCapability)?; + let notify_cfg_cap = space + .capability::() + .ok_or(Error::NoNotifyConfigCapability)?; + + // TODO MSI/MSI-X + + // Map the regions + + let common_cfg_base = space + .bar(common_cfg_cap.bar_index().unwrap()) + .unwrap() + .as_memory() + + common_cfg_cap.bar_offset(); + let device_cfg_base = space + .bar(device_cfg_cap.bar_index().unwrap()) + .unwrap() + .as_memory() + + device_cfg_cap.bar_offset(); + let device_cfg_len = device_cfg_cap.length(); + let notify_cfg_base = space + .bar(notify_cfg_cap.bar_index().unwrap()) + .unwrap() + .as_memory() + + notify_cfg_cap.bar_offset(); + let notify_cfg_len = notify_cfg_cap.length(); + let notify_cfg_mul = notify_cfg_cap.offset_multiplier(); + + let common_cfg_base = PhysicalAddress::from_raw(common_cfg_base); + let device_cfg_base = PhysicalAddress::from_raw(device_cfg_base); + let notify_cfg_base = PhysicalAddress::from_raw(notify_cfg_base); + + assert_eq!(notify_cfg_len % 2, 0); + + let common_cfg = unsafe { DeviceMemoryIo::map(common_cfg_base) }.unwrap(); + let device_cfg = + unsafe { DeviceMemoryIo::map_slice(device_cfg_base, device_cfg_len) }.unwrap(); + let notify_cfg = + unsafe { DeviceMemoryIo::map_slice(notify_cfg_base, notify_cfg_len / 2) }.unwrap(); + + Ok(Self { + common_cfg, + device_cfg, + notify_cfg, + notify_cfg_mul, + }) + } +} diff --git a/driver/virtio/net/Cargo.toml b/driver/virtio/net/Cargo.toml new file mode 100644 index 00000000..b8008284 --- /dev/null +++ b/driver/virtio/net/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "ygg_driver_virtio_net" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +yggdrasil-abi = { git = "https://git.alnyan.me/yggdrasil/yggdrasil-abi.git" } +kernel-util = { path = "../../../lib/kernel-util" } +device-api = { path = "../../../lib/device-api", features = ["derive"] } + +ygg_driver_virtio_core = { path = "../core" } +ygg_driver_net_core = { path = "../../net/core" } +ygg_driver_pci = { path = "../../bus/pci", optional = true } + +log = "0.4.20" +bitflags = "2.4.2" +tock-registers = "0.8.1" +bytemuck = { version = "1.14.0", features = ["derive"] } + +[features] +default = [] +pci = ["ygg_driver_pci", "ygg_driver_virtio_core/pci"] diff --git a/driver/virtio/net/src/lib.rs b/driver/virtio/net/src/lib.rs new file mode 100644 index 00000000..9b4b5d6f --- /dev/null +++ b/driver/virtio/net/src/lib.rs @@ -0,0 +1,316 @@ +// TODO use more fancy features of virtio-net, TCP/IP checksum offloading would be nice +#![feature(strict_provenance)] +#![no_std] + +extern crate alloc; + +use core::{ + future::Future, + mem::size_of, + pin::Pin, + ptr::NonNull, + task::{Context, Poll}, +}; + +use alloc::{boxed::Box, collections::BTreeMap}; +use bitflags::Flags; +use bytemuck::{Pod, Zeroable}; +use device_api::{ + interrupt::{InterruptAffinity, InterruptHandler, MsiHandler}, + Device, +}; +use kernel_util::{ + block, + mem::{ + self, + address::{FromRaw, IntoRaw, PhysicalAddress}, + device::{DeviceMemoryIo, RawDeviceMemoryMapping}, + PageBox, + }, + message_interrupt_controller, + runtime::{self, QueueWaker}, + sync::{spin_rwlock::IrqSafeRwLock, IrqSafeSpinlock, IrqSafeSpinlockGuard}, + util::{ring::RingBuffer, OneTimeInit}, +}; +use ygg_driver_net_core::{interface::NetworkDevice, types::MacAddress, Packet}; +use ygg_driver_pci::{ + capability::{ + MsiXCapability, MsiXVectorTable, VirtioCapabilityData, VirtioCommonConfigCapability, + VirtioDeviceConfigCapability, VirtioNotifyConfigCapability, + }, + PciBaseAddress, PciCommandRegister, PciConfigurationSpace, PciDeviceInfo, +}; +use ygg_driver_virtio_core::{ + queue::VirtQueue, + transport::{pci::PciTransport, Transport}, + DeviceStatus, +}; +use yggdrasil_abi::error::Error; + +struct Queues { + receive: IrqSafeSpinlock, + transmit: IrqSafeSpinlock, + + configuration_vector: usize, + receive_vector: usize, +} + +pub struct VirtioNet { + transport: IrqSafeSpinlock, + queues: OneTimeInit, + interface_id: OneTimeInit, + + mac: IrqSafeRwLock, + + pending_packets: IrqSafeRwLock>>, + pending_tx_packets: IrqSafeRwLock>>, + + vector_table: IrqSafeRwLock>, +} + +#[derive(Clone, Copy, Debug, Pod, Zeroable)] +#[repr(C)] +struct VirtioPacketHeader { + flags: u8, + gso_type: u8, + hdr_len: u16, + gso_size: u16, + csum_start: u16, + csum_offset: u16, +} + +impl Queues { + pub fn try_receive(&self, _index: usize) -> Option<(u16, IrqSafeSpinlockGuard)> { + let mut queue = self.receive.lock(); + let (token, _) = unsafe { queue.pop_last_used() }?; + Some((token, queue)) + } +} + +impl VirtioNet { + const PACKET_SIZE: usize = 4096; + + pub fn new(mut transport: T, vector_table: MsiXVectorTable<'static>) -> Self { + // Read MAC from device config + let device_cfg = transport + .device_cfg() + .expect("virtio-net must have device-specific configuration section"); + let mut mac_bytes = [0; 6]; + mac_bytes.copy_from_slice(&device_cfg[..6]); + let mac = MacAddress::from(mac_bytes); + + Self { + transport: IrqSafeSpinlock::new(transport), + queues: OneTimeInit::new(), + interface_id: OneTimeInit::new(), + + mac: IrqSafeRwLock::new(mac), + + pending_packets: IrqSafeRwLock::new(BTreeMap::new()), + pending_tx_packets: IrqSafeRwLock::new(BTreeMap::new()), + vector_table: IrqSafeRwLock::new(vector_table), + } + } + + pub fn listen(&self, buffers: usize) { + let queues = self.queues.get(); + let mut queue = queues.receive.lock(); + let mut packets = self.pending_packets.write(); + + for _ in 0..buffers { + let mut packet = PageBox::new_slice(0, Self::PACKET_SIZE).unwrap(); + let token = unsafe { queue.add(&[&mut packet], &[]).unwrap() }; + packets.insert(token, packet); + } + } + + fn handle_receive_interrupt(&self, queue: usize) -> bool { + let queues = self.queues.get(); + let interface_id = *self.interface_id.get(); + let mut count = 0; + + while let Some((token, mut queue)) = queues.try_receive(queue) { + let mut pending_packets = self.pending_packets.write(); + let packet = pending_packets.remove(&token).unwrap(); + + let mut buffer = PageBox::new_slice(0, Self::PACKET_SIZE).unwrap(); + + let token = unsafe { queue.add(&[&mut buffer], &[]).unwrap() }; + pending_packets.insert(token, buffer); + + let packet = Packet::new(packet, size_of::(), interface_id); + ygg_driver_net_core::receive_packet(packet).unwrap(); + count += 1 + } + + if count != 0 { + self.transport.lock().notify(1); + } + + count != 0 + } + + fn begin_init(&self) -> Result { + let mut transport = self.transport.lock(); + let mut status = DeviceStatus::RESET_VALUE; + + log::debug!("Reset device"); + transport.write_device_status(status); + status |= DeviceStatus::ACKNOWLEDGE; + transport.write_device_status(status); + status |= DeviceStatus::DRIVER; + transport.write_device_status(status); + + let device_features = transport.read_device_features(); + + // TODO blah blah blah + + transport.write_driver_features(0); + + status |= DeviceStatus::FEATURES_OK; + transport.write_device_status(status); + + if !transport + .read_device_status() + .contains(DeviceStatus::FEATURES_OK) + { + return Err(Error::InvalidOperation); + } + + Ok(status) + } + + fn finish_init(&self, status: DeviceStatus) { + let mut transport = self.transport.lock(); + + transport.write_device_status(status | DeviceStatus::DRIVER_OK); + } + + unsafe fn setup_queues( + &'static self, + receive_count: usize, + transmit_count: usize, + ) -> Result<(), Error> { + // TODO multiqueue capability + assert_eq!(receive_count, 1); + assert_eq!(transmit_count, 1); + + let mut transport = self.transport.lock(); + let mut vt = self.vector_table.write(); + + let msix_range = vt.register_range( + 0, + 1 + receive_count, + message_interrupt_controller(), + InterruptAffinity::Any, + self, + )?; + + // TODO set the configuration vector in virtio common cfg + let receive_vector: u16 = msix_range[1].vector.try_into().unwrap(); + + // Setup the virtqs + let rx = VirtQueue::with_max_capacity(&mut *transport, 0, 128, Some(receive_vector)) + .map_err(cvt_error)?; + let tx = VirtQueue::with_max_capacity(&mut *transport, 1, 128, None).map_err(cvt_error)?; + + self.queues.init(Queues { + receive: IrqSafeSpinlock::new(rx), + transmit: IrqSafeSpinlock::new(tx), + + configuration_vector: msix_range[0].vector, + receive_vector: msix_range[1].vector, + }); + + Ok(()) + } +} + +impl NetworkDevice for VirtioNet { + fn transmit(&self, mut packet: PageBox<[u8]>) -> Result<(), Error> { + let queues = self.queues.get(); + let mut tx = queues.transmit.lock(); + let mut transport = self.transport.lock(); + packet[..size_of::()].fill(0); + let len = tx + .add_notify_wait_pop(&[], &[&packet], &mut *transport) + .unwrap(); + + Ok(()) + } + + fn read_hardware_address(&self) -> MacAddress { + *self.mac.read() + } + + fn packet_prefix_size(&self) -> usize { + size_of::() + } +} + +impl MsiHandler for VirtioNet { + fn handle_msi(&self, vector: usize) -> bool { + let Some(queues) = self.queues.try_get() else { + return false; + }; + + if vector == queues.receive_vector { + self.handle_receive_interrupt(0) + } else { + false + } + } +} + +impl Device for VirtioNet { + fn display_name(&self) -> &'static str { + "VirtIO Network Device" + } + + unsafe fn init(&'static self) -> Result<(), Error> { + let status = self.begin_init()?; + + // TODO multiqueue + self.setup_queues(1, 1)?; + + self.finish_init(status); + + let id = ygg_driver_net_core::register_interface(self); + self.interface_id.init(id); + self.listen(64); + + Ok(()) + } + + unsafe fn init_irq(&'static self) -> Result<(), Error> { + Ok(()) + } +} + +fn cvt_error(error: ygg_driver_virtio_core::error::Error) -> Error { + use ygg_driver_virtio_core::error::Error as VirtioError; + match error { + VirtioError::OsError(err) => err, + _ => Error::InvalidOperation, + } +} + +pub fn probe(info: &PciDeviceInfo) -> Result<&'static dyn Device, Error> { + let space = &info.config_space; + + let mut msix = space.capability::().unwrap(); + let mut vt = msix.vector_table()?; + + // TODO is this really needed? PCI spec says this is masked on reset, though I'm not sure if + // firmware puts it back in masked state after loading the kernel + vt.mask_all(); + msix.set_function_mask(false); + msix.set_enabled(true); + + let transport = PciTransport::from_config_space(space).unwrap(); + let device = VirtioNet::new(transport, vt); + + let device = Box::leak(Box::new(device)); + + Ok(device) +} diff --git a/lib/kernel-util/src/api.rs b/lib/kernel-util/src/api.rs index fae48fe4..b84cb34b 100644 --- a/lib/kernel-util/src/api.rs +++ b/lib/kernel-util/src/api.rs @@ -27,6 +27,8 @@ extern "Rust" { pub fn __virtualize(phys: u64) -> usize; pub fn __physicalize(virt: usize) -> u64; + pub fn __translate_kernel(virt: usize) -> Option; + pub fn __map_device_pages( base: PhysicalAddress, count: usize, diff --git a/lib/kernel-util/src/mem/device.rs b/lib/kernel-util/src/mem/device.rs index 7d5433ce..a39c073a 100644 --- a/lib/kernel-util/src/mem/device.rs +++ b/lib/kernel-util/src/mem/device.rs @@ -2,6 +2,7 @@ use core::{ alloc::Layout, mem::size_of, ops::{Deref, DerefMut}, + ptr::NonNull, }; use alloc::sync::Arc; @@ -66,6 +67,39 @@ impl RawDeviceMemoryMapping { core::mem::forget(self); address } + + pub fn into_raw_parts(self) -> (usize, usize, usize, usize) { + let address = self.address; + let base_address = self.base_address; + let page_count = self.page_count; + let page_size = self.page_size; + + core::mem::forget(self); + + (address, base_address, page_count, page_size) + } + + pub unsafe fn from_raw_parts( + address: usize, + base_address: usize, + page_count: usize, + page_size: usize, + ) -> Self { + Self { + address, + base_address, + page_count, + page_size, + } + } + + /// "Casts" the mapping to a specific type T and returns a [NonNull] pointer to it + pub unsafe fn as_non_null(&self) -> NonNull { + if self.page_size * self.page_count < size_of::() { + panic!(); + } + NonNull::new_unchecked(self.address as *mut T) + } } impl Drop for RawDeviceMemoryMapping { diff --git a/lib/kernel-util/src/mem/mod.rs b/lib/kernel-util/src/mem/mod.rs index bb96ffef..239707a0 100644 --- a/lib/kernel-util/src/mem/mod.rs +++ b/lib/kernel-util/src/mem/mod.rs @@ -5,11 +5,12 @@ use core::{ ops::{Deref, DerefMut}, }; +use alloc::boxed::Box; use yggdrasil_abi::error::Error; use crate::api::{self, __allocate_contiguous_pages, __free_page, __physicalize}; -use self::address::{AsPhysicalAddress, PhysicalAddress}; +use self::address::{AsPhysicalAddress, FromRaw, PhysicalAddress}; pub mod address; pub mod device; @@ -258,3 +259,11 @@ unsafe impl Sync for PageBox {} pub fn allocate_page() -> Result { unsafe { api::__allocate_page() } } + +pub fn allocate_contiguous_pages(count: usize) -> Result { + unsafe { api::__allocate_contiguous_pages(count) } +} + +pub fn translate_kernel_address(virt: usize) -> Option { + unsafe { api::__translate_kernel(virt) } +} diff --git a/lib/kernel-util/src/util/ring.rs b/lib/kernel-util/src/util/ring.rs index 1fb8c70b..009e2a80 100644 --- a/lib/kernel-util/src/util/ring.rs +++ b/lib/kernel-util/src/util/ring.rs @@ -89,12 +89,9 @@ impl RingBuffer { /// /// The caller must perform the necessary checks to avoid reading beyond the write head. #[inline] - pub unsafe fn read_single_unchecked(&mut self) -> T - where - T: Copy, - { + pub unsafe fn read_single_unchecked(&mut self) -> T { let data = self.data.as_ref().unwrap(); - let res = data[self.rd].assume_init(); + let res = data[self.rd].assume_init_read(); self.rd = (self.rd + 1) % self.capacity; res } diff --git a/lib/vfs/src/file/mod.rs b/lib/vfs/src/file/mod.rs index 6d670e85..06c28e68 100644 --- a/lib/vfs/src/file/mod.rs +++ b/lib/vfs/src/file/mod.rs @@ -2,6 +2,7 @@ use core::{ any::Any, fmt, mem::MaybeUninit, + net::SocketAddr, task::{Context, Poll}, }; @@ -24,8 +25,10 @@ use crate::{ channel::ChannelDescriptor, device::{BlockDeviceWrapper, CharDeviceWrapper}, node::NodeRef, + socket::PacketSocketWrapper, traits::{Read, Seek, Write}, - FdPoll, FileReadiness, PseudoTerminal, PseudoTerminalMaster, PseudoTerminalSlave, SharedMemory, + FdPoll, FileReadiness, PacketSocket, PseudoTerminal, PseudoTerminalMaster, PseudoTerminalSlave, + SharedMemory, Socket, }; use self::{ @@ -63,6 +66,8 @@ pub enum File { Block(BlockFile), Char(CharFile), + PacketSocket(Arc), + AnonymousPipe(PipeEnd), Poll(FdPoll), Channel(ChannelDescriptor), @@ -115,6 +120,11 @@ impl File { )) } + /// Constructs a [File] from a [PacketSocket] + pub fn from_packet_socket(socket: Arc) -> Arc { + Arc::new(Self::PacketSocket(Arc::new(PacketSocketWrapper(socket)))) + } + pub(crate) fn directory(node: NodeRef, position: DirectoryOpenPosition) -> Arc { let position = IrqSafeSpinlock::new(position.into()); Arc::new(Self::Directory(DirectoryFile { node, position })) @@ -261,6 +271,39 @@ impl File { Err(Error::InvalidOperation) } } + + /// Interprets the file as a packet-based socket + pub fn as_packet_socket(&self) -> Result<&PacketSocketWrapper, Error> { + if let Self::PacketSocket(sock) = self { + Ok(sock) + } else { + Err(Error::InvalidOperation) + } + } + + /// Sends data to a socket + pub fn send_to(&self, buffer: &[u8], recepient: Option) -> Result { + match recepient { + Some(recepient) => self.as_packet_socket()?.send(recepient, buffer), + None => todo!(), + } + } + + /// Receives data from a socket + pub fn receive_from( + &self, + buffer: &mut [u8], + remote: &mut MaybeUninit, + ) -> Result { + match self { + Self::PacketSocket(socket) => { + let (addr, len) = socket.receive(buffer)?; + remote.write(addr); + Ok(len) + } + _ => Err(Error::InvalidOperation), + } + } } impl PageProvider for File { @@ -295,6 +338,7 @@ impl Read for File { // TODO maybe allow reading messages from Channels? Self::Channel(_) => Err(Error::InvalidOperation), Self::SharedMemory(_) => Err(Error::InvalidOperation), + Self::PacketSocket(_) => Err(Error::InvalidOperation), Self::Directory(_) => Err(Error::IsADirectory), } } @@ -314,6 +358,7 @@ impl Write for File { // TODO maybe allow writing messages to Channels? Self::Channel(_) => Err(Error::InvalidOperation), Self::SharedMemory(_) => Err(Error::InvalidOperation), + Self::PacketSocket(_) => Err(Error::InvalidOperation), Self::Directory(_) => Err(Error::IsADirectory), } } @@ -366,6 +411,11 @@ impl fmt::Debug for File { Self::SharedMemory(_) => f.debug_struct("SharedMemory").finish_non_exhaustive(), Self::PtySlave(_) => f.debug_struct("PtySlave").finish_non_exhaustive(), Self::PtyMaster(_) => f.debug_struct("PtyMaster").finish_non_exhaustive(), + Self::PacketSocket(sock) => f + .debug_struct("PacketSocket") + .field("local", &sock.local_address()) + .field("remote", &sock.remote_address()) + .finish_non_exhaustive(), } } } diff --git a/lib/vfs/src/lib.rs b/lib/vfs/src/lib.rs index 1e63cfd9..a55e5e15 100644 --- a/lib/vfs/src/lib.rs +++ b/lib/vfs/src/lib.rs @@ -19,6 +19,7 @@ pub(crate) mod path; pub(crate) mod poll; pub(crate) mod pty; pub(crate) mod shared_memory; +pub(crate) mod socket; pub(crate) mod traits; pub use channel::MessagePayload; @@ -32,4 +33,5 @@ pub use node::{ pub use poll::FdPoll; pub use pty::{PseudoTerminal, PseudoTerminalMaster, PseudoTerminalSlave}; pub use shared_memory::SharedMemory; +pub use socket::{ConnectionSocket, ListenerSocket, PacketSocket, Socket}; pub use traits::{FileReadiness, Read, Seek, Write}; diff --git a/lib/vfs/src/node/impls.rs b/lib/vfs/src/node/impls.rs index 91afa247..21bfb1c5 100644 --- a/lib/vfs/src/node/impls.rs +++ b/lib/vfs/src/node/impls.rs @@ -292,6 +292,10 @@ where let instance = instance.unwrap().downcast_ref::().unwrap(); Ok(instance.as_write()?.lock().write_slice(pos as _, buf)) } + + fn truncate(&self, _node: &NodeRef, _new_size: u64) -> Result<(), Error> { + Ok(()) + } } // Byte read-only node diff --git a/lib/vfs/src/socket.rs b/lib/vfs/src/socket.rs new file mode 100644 index 00000000..46e3d3bb --- /dev/null +++ b/lib/vfs/src/socket.rs @@ -0,0 +1,53 @@ +use core::{ + net::SocketAddr, + ops::Deref, + task::{Context, Poll}, +}; + +use alloc::sync::Arc; +use yggdrasil_abi::error::Error; + +use crate::FileReadiness; + +/// Interface for interacting with network sockets +pub trait Socket: FileReadiness + Send { + /// Socket listen/receive address + fn local_address(&self) -> SocketAddr; + + /// Socket remote address + fn remote_address(&self) -> Option; + + /// Closes a socket + fn close(&self) -> Result<(), Error>; +} + +/// Stateless/packet-based socket interface +pub trait PacketSocket: Socket { + /// Receives a packet into provided buffer. Will return an error if packet cannot be placed + /// within the buffer. + fn receive(&self, buffer: &mut [u8]) -> Result<(SocketAddr, usize), Error>; + + /// Sends provided data to the recepient specified by `destination` + fn send(&self, destination: SocketAddr, data: &[u8]) -> Result; +} + +/// Connection-based client socket interface +pub trait ConnectionSocket: Socket {} +/// Connection-based listener socket interface +pub trait ListenerSocket: Socket {} + +pub struct PacketSocketWrapper(pub Arc); + +impl Deref for PacketSocketWrapper { + type Target = dyn PacketSocket; + + fn deref(&self) -> &Self::Target { + self.0.as_ref() + } +} + +impl Drop for PacketSocketWrapper { + fn drop(&mut self) { + self.0.close().ok(); + } +} diff --git a/src/arch/mod.rs b/src/arch/mod.rs index 2332174f..707c9cfb 100644 --- a/src/arch/mod.rs +++ b/src/arch/mod.rs @@ -122,6 +122,9 @@ pub trait Architecture { /// Converts a virtual address created by [Architecture::virtualize] back to its physical form fn physicalize(address: usize) -> u64; + /// Translates a kernel virtual address into its physical page + fn translate_kernel_address(address: usize) -> Option; + // Architecture intrinsics /// Suspends CPU until an interrupt is received @@ -356,3 +359,8 @@ fn __monotonic_timestamp() -> Result { fn __message_interrupt_controller() -> &'static dyn MessageInterruptController { ARCHITECTURE.message_interrupt_controller() } + +#[no_mangle] +fn __translate_kernel(address: usize) -> Option { + ArchitectureImpl::translate_kernel_address(address) +} diff --git a/src/arch/x86_64/mem/mod.rs b/src/arch/x86_64/mem/mod.rs index a2886899..7eed2df3 100644 --- a/src/arch/x86_64/mem/mod.rs +++ b/src/arch/x86_64/mem/mod.rs @@ -311,6 +311,23 @@ fn clone_kernel_tables(dst: &mut PageTable) { } } +pub(super) fn translate_kernel_address(address: usize) -> Option { + let l0i = address.page_index::(); + let l1i = address.page_index::(); + let l2i = address.page_index::(); + let l3i = address.page_index::(); + + match l0i { + KERNEL_L0_INDEX => match l1i { + HEAP_MAPPING_L1I => Some(PhysicalAddress::from_raw(address - HEAP_MAPPING_OFFSET)), + DEVICE_MAPPING_L1I => todo!(), + _ => todo!(), + }, + RAM_MAPPING_L0I => todo!(), + _ => None, + } +} + /// Sets up the following memory map: /// ...: KERNEL_TABLES.l0: /// * 0xFFFFFF0000000000 .. 0xFFFFFFFF8000000000 : RAM_MAPPING_L1 diff --git a/src/arch/x86_64/mod.rs b/src/arch/x86_64/mod.rs index 689a0597..7d168791 100644 --- a/src/arch/x86_64/mod.rs +++ b/src/arch/x86_64/mod.rs @@ -275,6 +275,11 @@ impl Architecture for X86_64 { (address - RAM_MAPPING_OFFSET) as _ } + #[inline] + fn translate_kernel_address(address: usize) -> Option { + mem::translate_kernel_address(address) + } + fn external_interrupt_controller( &'static self, ) -> &'static dyn ExternalInterruptController { @@ -394,6 +399,12 @@ impl X86_64 { Some(0x01), ygg_driver_ahci::probe, ); + ygg_driver_pci::register_vendor_driver( + "Virtio PCI Network Device", + 0x1AF4, + 0x1000, + ygg_driver_virtio_net::probe, + ); match self.boot_data.get() { &BootData::YBoot(data) => { diff --git a/src/init.rs b/src/init.rs index c0f6a092..91149743 100644 --- a/src/init.rs +++ b/src/init.rs @@ -28,6 +28,8 @@ fn setup_root() -> Result { pub fn kinit() -> Result<(), Error> { infoln!("In main"); + ygg_driver_net_core::start_network_tasks()?; + #[cfg(feature = "fb_console")] { use crate::device::display::console::update_consoles_task; diff --git a/src/syscall/mod.rs b/src/syscall/mod.rs index f1d27c7d..cf8b9ff9 100644 --- a/src/syscall/mod.rs +++ b/src/syscall/mod.rs @@ -1,5 +1,5 @@ //! System function call handlers -use core::{mem::MaybeUninit, time::Duration}; +use core::{mem::MaybeUninit, net::SocketAddr, time::Duration}; use abi::{ error::Error, @@ -9,6 +9,7 @@ use abi::{ TerminalOptions, TerminalSize, }, mem::MappingSource, + net::SocketType, process::{ ExecveOptions, ExitCode, MutexOperation, Signal, SpawnOption, SpawnOptions, ThreadSpawnOptions, @@ -18,6 +19,7 @@ use abi::{ use alloc::{borrow::ToOwned, boxed::Box, sync::Arc, vec::Vec}; use kernel_util::{block, mem::table::EntryLevelExt, runtime, sync::IrqSafeSpinlockGuard}; use vfs::{File, IoContext, MessagePayload, NodeRef, Read, Seek, Write}; +use ygg_driver_net_core::socket::UdpSocket; use yggdrasil_abi::{error::SyscallResult, io::MountOptions}; use crate::{ @@ -658,6 +660,45 @@ fn syscall_handler(func: SyscallFunction, args: &[u64]) -> Result Ok(0) } + // Networking + SyscallFunction::BindSocket => { + let listen = arg_user_ref::(args[0] as usize)?; + let ty = SocketType::try_from(args[1] as u32).map_err(|_| Error::InvalidArgument)?; + + run_with_io(process, |mut io| { + let socket = match ty { + SocketType::UdpPacket => UdpSocket::bind(*listen)?, + _ => todo!(), + }; + let file = File::from_packet_socket(socket); + let fd = io.files.place_file(file, true)?; + Ok(fd.0 as usize) + }) + } + SyscallFunction::ConnectSocket => todo!(), + SyscallFunction::SendTo => { + let socket_fd = RawFd::from(args[0] as u32); + let buffer = arg_buffer_ref(args[1] as usize, args[2] as usize)?; + let recepient = arg_user_ref::>(args[3] as usize)?; + + run_with_io(process, |mut io| { + let file = io.files.file(socket_fd)?; + + file.send_to(buffer, *recepient) + }) + } + SyscallFunction::ReceiveFrom => { + let socket_fd = RawFd::from(args[0] as u32); + let buffer = arg_buffer_mut(args[1] as usize, args[2] as usize)?; + let remote = arg_user_mut::>(args[3] as usize)?; + + run_with_io(process, |mut io| { + let file = io.files.file(socket_fd)?; + + file.receive_from(buffer, remote) + }) + } + SyscallFunction::Fork => unreachable!(), } }