diff --git a/.gitignore b/.gitignore index 1811775e79..24d2434eae 100755 --- a/.gitignore +++ b/.gitignore @@ -60,8 +60,10 @@ modules.order vmlinux.*dom* **/include/config/** -# QEMU downloaded by build.sh script -qemu +# QEMU downloaded by build.sh script. Anchored: an unanchored "qemu" also +# matches the DIRECTORIES of that name inside the layers (meta-qemu/ +# recipes-qemu/qemu/), which silently swallowed the recipe's patch files. +/qemu # Backup files *~ diff --git a/build/meta-qemu/recipes-qemu/qemu/files/0001-qemu-8.2.2-r0-patches.inc b/build/meta-qemu/recipes-qemu/qemu/files/0001-qemu-8.2.2-r0-patches.inc index 2e9696e540..3dd7bb5dca 100644 --- a/build/meta-qemu/recipes-qemu/qemu/files/0001-qemu-8.2.2-r0-patches.inc +++ b/build/meta-qemu/recipes-qemu/qemu/files/0001-qemu-8.2.2-r0-patches.inc @@ -3,4 +3,5 @@ SRC_URI += "\ file://0001-virt.c.patch \ file://0002-virt.h.patch \ + file://0003-Kconfig.patch \ " diff --git a/build/meta-qemu/recipes-qemu/qemu/files/0001-qemu-8.2.2-r0/0001-virt.c.patch b/build/meta-qemu/recipes-qemu/qemu/files/0001-qemu-8.2.2-r0/0001-virt.c.patch index 4f73c201be..0b049dc709 100644 --- a/build/meta-qemu/recipes-qemu/qemu/files/0001-qemu-8.2.2-r0/0001-virt.c.patch +++ b/build/meta-qemu/recipes-qemu/qemu/files/0001-qemu-8.2.2-r0/0001-virt.c.patch @@ -1,6 +1,14 @@ --- a/hw/arm/virt.c +++ b/hw/arm/virt.c -@@ -82,6 +82,10 @@ +@@ -42,6 +42,7 @@ + #include "hw/vfio/vfio-amd-xgbe.h" + #include "hw/display/ramfb.h" + #include "net/net.h" ++#include "hw/net/lan9118.h" + #include "sysemu/device_tree.h" + #include "sysemu/numa.h" + #include "sysemu/runstate.h" +@@ -82,6 +83,10 @@ #include "hw/char/pl011.h" #include "qemu/guest-random.h" @@ -11,7 +19,7 @@ #define DEFINE_VIRT_MACHINE_LATEST(major, minor, latest) \ static void virt_##major##_##minor##_class_init(ObjectClass *oc, \ void *data) \ -@@ -145,6 +149,12 @@ +@@ -145,6 +150,20 @@ [VIRT_GIC_ITS] = { 0x08080000, 0x00020000 }, /* This redistributor space allows up to 2*64kB*123 CPUs */ [VIRT_GIC_REDIST] = { 0x080A0000, 0x00F60000 }, @@ -20,11 +28,19 @@ + [VIRT_KMI0] = { 0x08801000, 0x00001000 }, + [VIRT_KMI1] = { 0x08802000, 0x00001000 }, + [VIRT_ABSMOUSE] = { 0x08803000, 0x00001000 }, ++ ++ /* SMSC LAN9118 Ethernet MAC. It sits with the other SO3-added devices ++ * rather than at the 0x1a000000 the SO3 device trees inherited from ++ * vexpress-a15: that address falls inside VIRT_PCIE_MMIO below, whose ++ * gpex window covers the whole range and shadows anything mapped ++ * underneath it (the guest then reads back nothing and the chip is ++ * never detected). */ ++ [VIRT_ETH] = { 0x08804000, 0x00001000 }, + [VIRT_UART] = { 0x09000000, 0x00001000 }, [VIRT_RTC] = { 0x09010000, 0x00001000 }, [VIRT_FW_CFG] = { 0x09020000, 0x00000018 }, -@@ -199,6 +209,11 @@ +@@ -199,6 +218,12 @@ [VIRT_SECURE_UART] = 8, [VIRT_ACPI_GED] = 9, [VIRT_MMIO] = 16, /* ...to 16 + NUM_VIRTIO_TRANSPORTS - 1 */ @@ -32,11 +48,25 @@ + [VIRT_CLCD] = 10, + [VIRT_KMI0] = 11, + [VIRT_KMI1] = 12, ++ [VIRT_ETH] = 15, + [VIRT_GIC_V2M] = 48, /* ...to 48 + NUM_GICV2M_SPIS - 1 */ [VIRT_SMMU] = 74, /* ...to 74 + NUM_SMMU_IRQS - 1 */ [VIRT_PLATFORM_BUS] = 112, /* ...to 112 + PLATFORM_BUS_NUM_IRQS -1 */ -@@ -2022,6 +2037,109 @@ +@@ -1487,6 +1512,12 @@ + vms->bus = pci->bus; + if (vms->bus) { + for (i = 0; i < nb_nics; i++) { ++ /* Skip a slot an on-board NIC already took (the SO3 LAN9118 ++ * created just before this call): the PCI code would reject its ++ * model with "Unsupported NIC model: lan9118". */ ++ if (nd_table[i].instantiated) { ++ continue; ++ } + pci_nic_init_nofail(&nd_table[i], pci->bus, mc->default_nic, NULL); + } + } +@@ -2022,6 +2053,109 @@ } } @@ -146,7 +176,33 @@ static void machvirt_init(MachineState *machine) { VirtMachineState *vms = VIRT_MACHINE(machine); -@@ -2326,6 +2444,51 @@ +@@ -2287,6 +2421,25 @@ + + create_rtc(vms); + ++ /* SMSC LAN9118 Ethernet MAC — the NIC the SO3 smc911x driver ++ * (devices/net/smc911x_lwip.c, "smsc,smc911x") binds to, at the address ++ * and SPI the SO3 device trees declare. QEMU's own virt machine offers ++ * only virtio-net, which SO3 has no driver for. ++ * ++ * Taken from the legacy on-board NIC slot, so a run without -nic/-net ++ * leaves the machine exactly as it was and the guest simply finds no chip ++ * (smc911x_detect_chip() fails cleanly). scripts/st.sh passes ++ * "-nic user,model=lan9118". ++ * ++ * This has to happen BEFORE create_pcie(): that one hands every nd_table ++ * entry no on-board device has claimed to pci_nic_init_nofail(), which ++ * bails out with "Unsupported NIC model: lan9118". ++ */ ++ if (nd_table[0].used) { ++ lan9118_init(&nd_table[0], vms->memmap[VIRT_ETH].base, ++ qdev_get_gpio_in(vms->gic, vms->irqmap[VIRT_ETH])); ++ } ++ + create_pcie(vms); + + if (has_ged && aarch64 && firmware_loaded && virt_is_acpi_enabled(vms)) { +@@ -2326,6 +2479,51 @@ vms->fw_cfg, OBJECT(vms)); } diff --git a/build/meta-qemu/recipes-qemu/qemu/files/0001-qemu-8.2.2-r0/0002-virt.h.patch b/build/meta-qemu/recipes-qemu/qemu/files/0001-qemu-8.2.2-r0/0002-virt.h.patch index dcac85baf2..4bc2f97580 100644 --- a/build/meta-qemu/recipes-qemu/qemu/files/0001-qemu-8.2.2-r0/0002-virt.h.patch +++ b/build/meta-qemu/recipes-qemu/qemu/files/0001-qemu-8.2.2-r0/0002-virt.h.patch @@ -1,6 +1,6 @@ --- a/include/hw/arm/virt.h +++ b/include/hw/arm/virt.h -@@ -58,6 +58,12 @@ +@@ -58,6 +58,13 @@ VIRT_GIC_VCPU, VIRT_GIC_ITS, VIRT_GIC_REDIST, @@ -9,6 +9,7 @@ + VIRT_KMI0, + VIRT_KMI1, + VIRT_ABSMOUSE, ++ VIRT_ETH, + VIRT_SMMU, VIRT_UART, diff --git a/build/meta-qemu/recipes-qemu/qemu/files/0001-qemu-8.2.2-r0/0003-Kconfig.patch b/build/meta-qemu/recipes-qemu/qemu/files/0001-qemu-8.2.2-r0/0003-Kconfig.patch new file mode 100644 index 0000000000..cd09fd3606 --- /dev/null +++ b/build/meta-qemu/recipes-qemu/qemu/files/0001-qemu-8.2.2-r0/0003-Kconfig.patch @@ -0,0 +1,9 @@ +--- a/hw/arm/Kconfig ++++ b/hw/arm/Kconfig +@@ -1,5 +1,6 @@ + config ARM_VIRT + bool ++ select LAN9118 # SO3 Ethernet (smsc,smc911x) + imply PCI_DEVICES + imply TEST_DEVICES + imply VFIO_AMD_XGBE diff --git a/doc/source/lwip.rst b/doc/source/lwip.rst index 5a20de7ade..680c8d9d82 100644 --- a/doc/source/lwip.rst +++ b/doc/source/lwip.rst @@ -23,18 +23,72 @@ chapter. .. note:: - Networking is **opt-in**: it is enabled with ``CONFIG_NET`` and is *off* in - the default ``virt64_defconfig``. The smc911x MAC is present on boards such as - the ARM Versatile Express; QEMU's ``virt`` machine does not provide one, so a - different NIC driver is needed to exercise the stack there. + Networking is **opt-in**: ``CONFIG_NET`` plus ``CONFIG_SMC911X``. Both are on + in the four QEMU virt defconfigs (``virt32``/``virt64``, plain and ``_fb``), + and off everywhere else — rpi4 and verdin have no driver for their own MAC + yet. + +The NIC on QEMU virt +==================== + +QEMU's stock ``virt`` machine offers only virtio-net, which SO3 has no driver +for. The so3 QEMU patch therefore adds an **SMSC LAN9118** to the machine model, +next to the PL111/PL050 devices it already adds: + +- MMIO base ``0x08804000`` (``VIRT_ETH`` in the ``hw/arm/virt.c`` memmap, + ``ethernet@08804000`` in ``dts/virt{32,64}.dts``); +- interrupt SPI 15 (``irqmap[VIRT_ETH]``, and ``interrupts = <0 15 4>`` in the + same DT nodes). + +Two details are worth knowing. The MAC is **not** at the ``0x1a000000`` the SO3 +device trees inherited from vexpress-a15: that address is inside the PCIe MMIO +window, whose gpex alias covers the whole range and shadows anything mapped +underneath, so the guest read back nothing and never detected the chip. And it +is only instantiated when the command line fills the legacy on-board NIC slot +(``-nic``/``-net``); without it the machine is exactly as it was and +``smc911x_detect_chip()`` simply fails. Trying it out ============= -With ``CONFIG_NET`` enabled and a supported NIC, the ``ping`` application -exercises the stack end to end. Under QEMU, ``st.sh`` attaches a **user-mode -(slirp)** network device — QEMU itself plays DHCP, DNS and NAT, and forwards host -port ``2222`` to the guest's port ``22`` — so nothing has to be set up on the host -and no ``sudo`` is needed. The trade-off is that the guest is NAT'd and not -reachable from the LAN. (``scripts/qemu-ifup.sh`` / ``qemu-ifdown.sh`` are +``ping`` exercises the stack end to end:: + + / % ping -c 6 10.0.2.2 + 64 bytes from 10.0.2.2: icmp_seq=1 ttl=255 time=6.497070 ms + 64 bytes from 10.0.2.2: icmp_seq=2 ttl=255 time=0.601074 ms + ... + 6 packets transmitted, 6 received, 0.000000% packet loss + +``st.sh`` attaches two NICs on **user-mode (slirp)** networking — QEMU itself +plays DHCP, DNS and NAT, so nothing has to be set up on the host and no ``sudo`` +is needed. The LAN9118 (``-nic user,model=lan9118``) is the one SO3 drives; the +virtio-net device is there for the Linux agency of the AVZ boot chain, and +carries the host port ``2222`` → guest port ``22`` forward. Each guest ignores +the NIC it cannot drive. The trade-off of slirp is that the guest is NAT'd and +not reachable from the LAN. (``scripts/qemu-ifup.sh`` / ``qemu-ifdown.sh`` are leftovers from the earlier ``tap``-and-bridge setup and are no longer used.) + +The interface comes up by DHCP during ``netif_add()``, so the address is +announced on the console shortly after boot:: + + Network Interface Controller (NIC) found LAN9118 + smc911x: detected LAN9118 controller + IP Network up and running with address 10.0.2.15 + +Pinging past the slirp gateway (``10.0.2.2``) needs the **host** to let QEMU +open ICMP sockets, otherwise slirp emulates the echo over UDP and relays back +the port-unreachable it gets:: + + From 192.168.1.1 icmp_seq=1 Destination Port Unreachable + +The kernel decides that with ``net.ipv4.ping_group_range``, whose default on +several distributions is the empty range ``1 0``. Widening it (``sysctl -w +net.ipv4.ping_group_range="0 2147483647"``) lets slirp forward the echo for +real. Nothing in SO3 is involved either way. + +.. note:: + + The **RX Status Level** the driver programs into ``FIFO_INT`` must be 0: the + controller raises the RSFL interrupt when the RX status FIFO holds *more* + entries than that level, so a level of 1 only interrupts once a second frame + has arrived, and every reception stays one frame behind. diff --git a/scripts/st.sh b/scripts/st.sh index 7b581e21b3..0efd89f507 100755 --- a/scripts/st.sh +++ b/scripts/st.sh @@ -38,6 +38,10 @@ N_QEMU_INSTANCES=`ps -A | grep qemu-system | wc -l` launch_qemu() { QEMU_MAC_ADDR="$(printf 'DE:AD:BE:EF:%02X:%02X\n' $((N_QEMU_INSTANCES)) $((N_QEMU_INSTANCES)))" + # Second NIC (SO3's LAN9118, see ETH_OPT below) — must not collide with + # the virtio-net one above. + QEMU_ETH_MAC_ADDR="$(printf 'DE:AD:BE:EF:%02X:%02X\n' $((0x10 + N_QEMU_INSTANCES)) $((N_QEMU_INSTANCES)))" + GDB_PORT=$((${GDB_PORT_BASE} + ${N_QEMU_INSTANCES})) echo -e "\033[01;36mMAC addr: " ${QEMU_MAC_ADDR} "\033[0;37m" @@ -81,6 +85,24 @@ launch_qemu() { DISPLAY_OPT="-display none" fi + # Networking. Two NICs are attached, because the two kinds of guest this + # script boots need different ones: + # + # * virtio-net for the Linux agency (AVZ boot chain) — it has no + # smc911x node in its device tree, and hostfwd puts guest ssh on host + # port 2222; + # * an SMSC LAN9118 for SO3, whose only Ethernet driver is smc911x + # (devices/net/smc911x_lwip.c). QEMU's stock 'virt' machine has no + # such device; the so3 QEMU patch adds one at 0x08804000 / SPI 15 — + # the address and IRQ the SO3 device trees declare — and creates it + # only when the legacy on-board NIC slot is filled, which is what + # -nic does here. + # + # Both sit on their own user-mode (slirp) stack: QEMU plays DHCP, DNS and + # NAT internally, so the guest gets 10.0.2.15 with no host setup and no + # sudo. Whichever guest is booted simply ignores the NIC it cannot drive. + ETH_OPT="-nic user,model=lan9118,mac=${QEMU_ETH_MAC_ADDR}" + if [ "$IB_PLATFORM" == "virt64" ]; then QEMU_BIN="$IB_ROOT_DIR/qemu/build/qemu-system-aarch64" echo Starting on virt64 @@ -135,6 +157,7 @@ launch_qemu() { ${DISPLAY_OPT} \ -netdev user,id=n1,hostfwd=tcp::2222-:22 \ -device virtio-net-device,netdev=n1,mac=${QEMU_MAC_ADDR} \ + ${ETH_OPT} \ -gdb tcp::${GDB_PORT} fi @@ -158,6 +181,7 @@ launch_qemu() { ${DISPLAY_OPT} \ -netdev user,id=n1,hostfwd=tcp::2222-:22 \ -device virtio-net-device,netdev=n1,mac=${QEMU_MAC_ADDR} \ + ${ETH_OPT} \ -gdb tcp::${GDB_PORT} fi diff --git a/so3/so3/configs/virt32_defconfig b/so3/so3/configs/virt32_defconfig index 4e8b9060f5..c8328d9a8a 100644 --- a/so3/so3/configs/virt32_defconfig +++ b/so3/so3/configs/virt32_defconfig @@ -38,7 +38,7 @@ CONFIG_SCHED_FREQ_PREEMPTION=y CONFIG_UART=y CONFIG_IO_MAPPING_BASE=0xe0000000 # CONFIG_I2C is not set -# CONFIG_NET is not set +CONFIG_NET=y # CONFIG_FB is not set # CONFIG_INPUT is not set # CONFIG_NS16550 is not set @@ -52,6 +52,7 @@ CONFIG_GIC=y CONFIG_GIC_V2=y # CONFIG_PL050_KMI is not set # CONFIG_VIRT_INPUT is not set +CONFIG_SMC911X=y # # SO3 Applications diff --git a/so3/so3/configs/virt32_fb_defconfig b/so3/so3/configs/virt32_fb_defconfig index 89482db88a..0a9fb53a49 100644 --- a/so3/so3/configs/virt32_fb_defconfig +++ b/so3/so3/configs/virt32_fb_defconfig @@ -38,7 +38,7 @@ CONFIG_SCHED_FREQ_PREEMPTION=y CONFIG_UART=y CONFIG_IO_MAPPING_BASE=0xe0000000 # CONFIG_I2C is not set -# CONFIG_NET is not set +CONFIG_NET=y CONFIG_FB=y CONFIG_INPUT=y # CONFIG_NS16550 is not set @@ -55,6 +55,7 @@ CONFIG_PL111_CLCD=y # CONFIG_VIRTFB is not set CONFIG_PL050_KMI=y # CONFIG_VIRT_INPUT is not set +CONFIG_SMC911X=y # # SO3 Applications diff --git a/so3/so3/configs/virt64_defconfig b/so3/so3/configs/virt64_defconfig index 47bc26280d..bd524066f9 100644 --- a/so3/so3/configs/virt64_defconfig +++ b/so3/so3/configs/virt64_defconfig @@ -41,7 +41,7 @@ CONFIG_SCHED_FREQ_PREEMPTION=y CONFIG_UART=y CONFIG_IO_MAPPING_BASE=0xffff900000000000 # CONFIG_I2C is not set -# CONFIG_NET is not set +CONFIG_NET=y # CONFIG_FB is not set # CONFIG_INPUT is not set # CONFIG_NS16550 is not set @@ -55,6 +55,7 @@ CONFIG_GIC_V2=y # CONFIG_PL111_CLCD is not set # CONFIG_QEMU_RAMFB is not set # CONFIG_PL050_KMI is not set +CONFIG_SMC911X=y # # SO3 Applications diff --git a/so3/so3/configs/virt64_fb_defconfig b/so3/so3/configs/virt64_fb_defconfig index 00fde9945f..d8f1ba5f7e 100644 --- a/so3/so3/configs/virt64_fb_defconfig +++ b/so3/so3/configs/virt64_fb_defconfig @@ -41,7 +41,7 @@ CONFIG_SCHED_FREQ_PREEMPTION=y CONFIG_UART=y CONFIG_IO_MAPPING_BASE=0xffff900000000000 # CONFIG_I2C is not set -# CONFIG_NET is not set +CONFIG_NET=y CONFIG_FB=y CONFIG_INPUT=y # CONFIG_NS16550 is not set @@ -57,6 +57,7 @@ CONFIG_PL111_CLCD=y # CONFIG_VIRTFB is not set CONFIG_PL050_KMI=y # CONFIG_VIRT_INPUT is not set +CONFIG_SMC911X=y # # SO3 Applications diff --git a/so3/so3/devices/net/Kconfig b/so3/so3/devices/net/Kconfig index 916901c8a3..fae8b53161 100644 --- a/so3/so3/devices/net/Kconfig +++ b/so3/so3/devices/net/Kconfig @@ -1,7 +1,7 @@ config SMC911X bool "smc911x network interface" - depends on VIRT32 + depends on VIRT32 || VIRT64 depends on NET diff --git a/so3/so3/devices/net/smc911x_lwip.c b/so3/so3/devices/net/smc911x_lwip.c index 472cf098d2..13d8a2ae33 100644 --- a/so3/so3/devices/net/smc911x_lwip.c +++ b/so3/so3/devices/net/smc911x_lwip.c @@ -54,6 +54,17 @@ #include +/* Interrupt-path tracing, compiled out unless DEBUG is defined above. The + * kernel-wide DBG() lives in soo/debug.h, which is only on the include path + * under CONFIG_SOO, so this driver carries its own. */ +#ifdef DEBUG +#define DBG(fmt, ...) printk("%s:%i > " fmt, __func__, __LINE__, ##__VA_ARGS__) +#else +#define DBG(fmt, ...) \ + do { \ + } while (0) +#endif + #define ETHERNET_LAYER_2_MAX_LENGTH 1522 /* Basic mode control register. */ @@ -303,46 +314,46 @@ static irq_return_t smc911x_so3_interrupt(int irq, void *dummy) } if (status & INT_STS_SW_INT) { - DBG("STS_SW\n", status); + DBG("STS_SW: status %08x\n", status); smc911x_reg_write(dev, INT_STS, INT_STS_SW_INT); } /* Handle various error conditions */ if (status & INT_STS_RXE) { - DBG("STS_RXE\n", status); + DBG("STS_RXE: status %08x\n", status); smc911x_reg_write(dev, INT_STS, INT_STS_RXE); } if (status & INT_STS_RXDFH_INT) { - DBG("STS_RXDFH\n", status); + DBG("STS_RXDFH: status %08x\n", status); smc911x_reg_write(dev, INT_STS, INT_STS_RXDFH_INT); } /* Undocumented interrupt-what is the right thing to do here? */ if (status & INT_STS_RXDF_INT) { - DBG("STS_RXDF\n", status); + DBG("STS_RXDF: status %08x\n", status); smc911x_reg_write(dev, INT_STS, INT_STS_RXDF_INT); } /* Incoming frame */ if (status & INT_STS_RSFL) { - DBG("STS_RSFL\n", status); + DBG("STS_RSFL: status %08x\n", status); irq_return = IRQ_BOTTOM; smc911x_reg_write(dev, INT_STS, INT_STS_RSFL); } /* Rx Data FIFO exceeds set level */ if (status & INT_STS_RDFL) { - DBG("STS_RDFL\n", status); + DBG("STS_RDFL: status %08x\n", status); smc911x_reg_write(dev, INT_STS, INT_STS_RDFL); } if (status & INT_STS_RDFO) { - DBG("STS_RDFO\n", status); + DBG("STS_RDFO: status %08x\n", status); smc911x_reg_write(dev, INT_STS, INT_STS_RDFO); } if (status & (INT_STS_TSFL | INT_STS_GPT_INT)) { - DBG("STS_TSFL\n", status); + DBG("STS_TSFL: status %08x\n", status); smc911x_reg_write(dev, INT_STS, INT_STS_TSFL | INT_STS_GPT_INT); } if (status & INT_STS_PHY_INT) { - DBG("PHY_INT\n", status); + DBG("PHY_INT: status %08x\n", status); smc911x_reg_write(dev, INT_STS, INT_STS_PHY_INT); } } while (--timeout); @@ -450,8 +461,13 @@ err_t smc911x_lwip_init(struct netif *netif) netif_set_link_up(netif); netif_set_up(netif); + /* RX Status Level 0. The controller raises RSFL when the RX status FIFO + * holds MORE entries than this level, so the 1 that used to be written + * here only interrupted once a SECOND frame had arrived: every + * reception stayed one frame behind, and the reply to the second ping + * of a run was not delivered until the third one showed up. */ fifo = smc911x_reg_read(eth_dev, FIFO_INT); - smc911x_reg_write(eth_dev, FIFO_INT, 0x01 | (fifo & 0xFFFFFF00)); + smc911x_reg_write(eth_dev, FIFO_INT, fifo & ~FIFO_INT_RX_STS_LEVEL); /* Turn on relevant interrupts */ smc911x_reg_write(eth_dev, INT_EN, INT_EN_RSFL_EN | INT_EN_RSFF_EN); diff --git a/so3/so3/dts/virt32.dts b/so3/so3/dts/virt32.dts index d6d16231a7..00ecd248a3 100644 --- a/so3/so3/dts/virt32.dts +++ b/so3/so3/dts/virt32.dts @@ -147,19 +147,21 @@ status = "ok"; }; - /* - https://github.com/psawargaonkar/xvisor-next/blob/95b887c82a37c8d9ee126e061cf4d8f383ec7d01/arch/arm/board/generic/dts/vexpress/a15/vexpress-a15.dtsi - https://github.com/avpatel/xvisor-next/blob/master/tests/arm32/vexpress-a15/vexpress-a15-guest.dts - */ - ethernet@1a000000 { - compatible = "smsc,smc911x"; - reg = <0x1a000000 0x1000>; - - interrupt-parent = <&gic>; - interrupts = <0 15 4>; - switch = "br0"; - - status = "ok"; - }; + /* SMSC LAN9118 Ethernet MAC (so3 QEMU patch, VIRT_ETH @ 0x08804000). + * QEMU's stock 'virt' machine has no Ethernet controller SO3 can drive — + * only virtio-net — so the machine model adds one next to the other SO3 + * devices. Not at the 0x1a000000 vexpress-a15 uses: that address falls + * inside the PCIe MMIO window, whose gpex alias shadows anything mapped + * underneath it. */ + ethernet@08804000 { + compatible = "smsc,smc911x"; + reg = <0x08804000 0x1000>; + + interrupt-parent = <&gic>; + /* SPI 15 — matches irqmap[VIRT_ETH] in the so3 QEMU patch. */ + interrupts = <0 15 4>; + + status = "ok"; + }; }; diff --git a/so3/so3/dts/virt32_lvperf.dts b/so3/so3/dts/virt32_lvperf.dts index 96cea89856..98ce88117b 100644 --- a/so3/so3/dts/virt32_lvperf.dts +++ b/so3/so3/dts/virt32_lvperf.dts @@ -108,19 +108,21 @@ }; - /* - https://github.com/psawargaonkar/xvisor-next/blob/95b887c82a37c8d9ee126e061cf4d8f383ec7d01/arch/arm/board/generic/dts/vexpress/a15/vexpress-a15.dtsi - https://github.com/avpatel/xvisor-next/blob/master/tests/arm32/vexpress-a15/vexpress-a15-guest.dts - */ - ethernet@1a000000 { - compatible = "smsc,smc911x"; - reg = <0x1a000000 0x1000>; - - interrupt-parent = <&gic>; - interrupts = <0 15 4>; - switch = "br0"; + /* SMSC LAN9118 Ethernet MAC (so3 QEMU patch, VIRT_ETH @ 0x08804000). + * QEMU's stock 'virt' machine has no Ethernet controller SO3 can drive — + * only virtio-net — so the machine model adds one next to the other SO3 + * devices. Not at the 0x1a000000 vexpress-a15 uses: that address falls + * inside the PCIe MMIO window, whose gpex alias shadows anything mapped + * underneath it. */ + ethernet@08804000 { + compatible = "smsc,smc911x"; + reg = <0x08804000 0x1000>; - status = "ok"; - }; + interrupt-parent = <&gic>; + /* SPI 15 — matches irqmap[VIRT_ETH] in the so3 QEMU patch. */ + interrupts = <0 15 4>; + + status = "ok"; + }; }; diff --git a/so3/so3/dts/virt64.dts b/so3/so3/dts/virt64.dts index 709fcc8d85..d14ed9ea34 100644 --- a/so3/so3/dts/virt64.dts +++ b/so3/so3/dts/virt64.dts @@ -162,19 +162,21 @@ status = "ok"; }; - /* - https://github.com/psawargaonkar/xvisor-next/blob/95b887c82a37c8d9ee126e061cf4d8f383ec7d01/arch/arm/board/generic/dts/vexpress/a15/vexpress-a15.dtsi - https://github.com/avpatel/xvisor-next/blob/master/tests/arm32/vexpress-a15/vexpress-a15-guest.dts - */ - ethernet@1a000000 { - compatible = "smsc,smc911x"; - reg = <0x0 0x1a000000 0x0 0x1000>; - - interrupt-parent = <&gic>; - interrupts = <0 15 4>; - switch = "br0"; - - status = "ok"; - }; + /* SMSC LAN9118 Ethernet MAC (so3 QEMU patch, VIRT_ETH @ 0x08804000). + * QEMU's stock 'virt' machine has no Ethernet controller SO3 can drive — + * only virtio-net — so the machine model adds one next to the other SO3 + * devices. Not at the 0x1a000000 vexpress-a15 uses: that address falls + * inside the PCIe MMIO window, whose gpex alias shadows anything mapped + * underneath it. */ + ethernet@08804000 { + compatible = "smsc,smc911x"; + reg = <0x0 0x08804000 0x0 0x1000>; + + interrupt-parent = <&gic>; + /* SPI 15 — matches irqmap[VIRT_ETH] in the so3 QEMU patch. */ + interrupts = <0 15 4>; + + status = "ok"; + }; }; diff --git a/so3/so3/dts/virt64_lvperf.dts b/so3/so3/dts/virt64_lvperf.dts index 07f1f2c866..4d4a0ae72b 100644 --- a/so3/so3/dts/virt64_lvperf.dts +++ b/so3/so3/dts/virt64_lvperf.dts @@ -116,19 +116,21 @@ status = "ok"; }; - /* - https://github.com/psawargaonkar/xvisor-next/blob/95b887c82a37c8d9ee126e061cf4d8f383ec7d01/arch/arm/board/generic/dts/vexpress/a15/vexpress-a15.dtsi - https://github.com/avpatel/xvisor-next/blob/master/tests/arm32/vexpress-a15/vexpress-a15-guest.dts - */ - ethernet@1a000000 { - compatible = "smsc,smc911x"; - reg = <0x0 0x1a000000 0x0 0x1000>; - - interrupt-parent = <&gic>; - interrupts = <0 15 4>; - switch = "br0"; + /* SMSC LAN9118 Ethernet MAC (so3 QEMU patch, VIRT_ETH @ 0x08804000). + * QEMU's stock 'virt' machine has no Ethernet controller SO3 can drive — + * only virtio-net — so the machine model adds one next to the other SO3 + * devices. Not at the 0x1a000000 vexpress-a15 uses: that address falls + * inside the PCIe MMIO window, whose gpex alias shadows anything mapped + * underneath it. */ + ethernet@08804000 { + compatible = "smsc,smc911x"; + reg = <0x0 0x08804000 0x0 0x1000>; - status = "ok"; - }; + interrupt-parent = <&gic>; + /* SPI 15 — matches irqmap[VIRT_ETH] in the so3 QEMU patch. */ + interrupts = <0 15 4>; + + status = "ok"; + }; }; diff --git a/so3/so3/net/net.c b/so3/so3/net/net.c index 21effb26c0..6b8665db78 100644 --- a/so3/so3/net/net.c +++ b/so3/so3/net/net.c @@ -56,6 +56,20 @@ static int lwip_return(int ret) return ret; } +/** + * lwip_fds[] is indexed by GLOBAL file descriptor. + * + * @param Global file descriptor (gfd) + * @return Associated socket ID from lwip + */ +static int get_lwip_fd_from_gfd(int gfd) +{ + if ((gfd < 0) || (gfd >= MAX_FDS)) + return -1; + + return lwip_fds[gfd]; +} + /** * * @param Local file descriptor (fd) @@ -63,23 +77,25 @@ static int lwip_return(int ret) */ static int get_lwip_fd(int fd) { - int gfd; - /* Get the gfd from this fd */ - gfd = current()->pcb->fd_array[fd]; - - if (gfd < MAX_FDS) - return lwip_fds[gfd]; - else - return -1; + return get_lwip_fd_from_gfd(current()->pcb->fd_array[fd]); } /**************************** Network subsystem ***************************************/ -int read_sock(int fd, void *buffer, int count) +/* + * The file operations below are the ones the VFS calls, and it passes them the + * GLOBAL descriptor (see the fops->read/write/close call sites in fs/vfs.c) — + * unlike the socket syscalls, which get the process-local one. Translating a + * gfd a second time used to land on an unrelated entry of the process fd table, + * or on -1 once close() had already released it: lwip_close() then never + * reached the socket, leaking its pcb and every packet queued on it. + */ + +int read_sock(int gfd, void *buffer, int count) { int ret; - int lwip_fd = get_lwip_fd(fd); + int lwip_fd = get_lwip_fd_from_gfd(gfd); if (lwip_fd < 0) { return -EBADF; @@ -89,10 +105,10 @@ int read_sock(int fd, void *buffer, int count) return lwip_return(ret); } -int write_sock(int fd, const void *buffer, int count) +int write_sock(int gfd, const void *buffer, int count) { int ret; - int lwip_fd = get_lwip_fd(fd); + int lwip_fd = get_lwip_fd_from_gfd(gfd); if (lwip_fd < 0) { return -EBADF; @@ -102,15 +118,19 @@ int write_sock(int fd, const void *buffer, int count) return lwip_return(ret); } -int close_sock(int fd) +int close_sock(int gfd) { int ret; - int lwip_fd = get_lwip_fd(fd); + int lwip_fd = get_lwip_fd_from_gfd(gfd); if (lwip_fd < 0) { return -EBADF; } + /* Release the slot before the socket is gone, so a gfd handed out again + * later cannot be mistaken for this socket. */ + lwip_fds[gfd] = -1; + ret = lwip_close(lwip_fd); return lwip_return(ret); } @@ -156,6 +176,26 @@ struct sockaddr *user_to_lwip_sockadd(const struct usr_sockaddr_in *usr, struct return (struct sockaddr *) lwip; } +/** + * Adapt a lwip sockaddr back to a userspace one. + * The two layouts differ by lwip's leading sa_len byte, so the peer address + * an lwip call filled in cannot simply be memcpy'd out to user space. + * @param lwip + * @param usr + */ +void lwip_to_user_sockadd(const struct sockaddr_in *lwip, struct usr_sockaddr_in *usr) +{ + if (usr == NULL) { + return; + } + + memset(usr, 0, sizeof(struct usr_sockaddr_in)); + + usr->sin_family = lwip->sin_family; + usr->sin_port = lwip->sin_port; + usr->sin_addr = lwip->sin_addr; +} + int ioctl_sock(int fd, unsigned long cmd, unsigned long args) { int ret; @@ -504,9 +544,8 @@ SYSCALL_DEFINE3(accept, int, sockfd, struct usr_sockaddr_in *, addr, socklen_t * /* TODO check fd ok */ lwip_fds[gfd] = lwip_bind_fd; - /* Copy back our sockaddr info in the usr data */ - if (addr) - memcpy(addr, addr_ptr, sizeof(struct sockaddr_in)); + /* Copy the peer address back out, converting the layout */ + lwip_to_user_sockadd(&addr_lwip, addr); return fd; } @@ -539,6 +578,12 @@ SYSCALL_DEFINE6(recvfrom, int, sockfd, void *, mem, size_t, len, int, flags, str from_ptr = user_to_lwip_sockadd(from, &from_lwip); ret = lwip_recvfrom(lwip_fd, mem, len, flags, from_ptr, fromlen); + + /* lwip_recvfrom() filled the SOURCE address into our local sockaddr; + * without this it never reached the caller (ping printed 0.0.0.0). */ + if (ret >= 0) + lwip_to_user_sockadd(&from_lwip, from); + return lwip_return(ret); } @@ -593,6 +638,13 @@ static void network_tcpip_done(void *args) void net_init(void) { + int gfd; + + /* 0 is a perfectly valid lwip socket id, so the zero-initialised table + * would make every descriptor look like socket 0. */ + for (gfd = 0; gfd < MAX_FDS; gfd++) + lwip_fds[gfd] = -1; + tcpip_init(network_tcpip_done, NULL); } diff --git a/so3/usr/src/ping.c b/so3/usr/src/ping.c index ebe17144c9..c7cb8511e8 100644 --- a/so3/usr/src/ping.c +++ b/so3/usr/src/ping.c @@ -55,6 +55,47 @@ struct ping_pkt { char msg[PING_PKT_LEN - sizeof(struct icmphdr)]; }; +/** + * Human-readable reason for an ICMP message that is not an echo reply, or NULL + * when the type is one we have nothing better to say about than its numbers. + */ +static const char *icmp_reason(unsigned char type, unsigned char code) +{ + switch (type) { + case ICMP_DEST_UNREACH: + switch (code) { + case ICMP_NET_UNREACH: + return "Destination Net Unreachable"; + case ICMP_HOST_UNREACH: + return "Destination Host Unreachable"; + case ICMP_PROT_UNREACH: + return "Destination Protocol Unreachable"; + case ICMP_PORT_UNREACH: + return "Destination Port Unreachable"; + case ICMP_FRAG_NEEDED: + return "Fragmentation needed but DF set"; + case ICMP_SR_FAILED: + return "Source Route Failed"; + case ICMP_NET_ANO: + case ICMP_HOST_ANO: + case ICMP_PKT_FILTERED: + return "Communication administratively prohibited"; + default: + return "Destination Unreachable"; + } + case ICMP_SOURCE_QUENCH: + return "Source Quench"; + case ICMP_REDIRECT: + return "Redirect"; + case ICMP_TIME_EXCEEDED: + return (code == ICMP_EXC_TTL) ? "Time to live exceeded" : "Fragment reassembly time exceeded"; + case ICMP_PARAMETERPROB: + return "Parameter problem"; + default: + return NULL; + } +} + /** * Compute the checksum * From https://www.geeksforgeeks.org/ping-in-c/ @@ -170,6 +211,7 @@ int main(int argc, char **argv) unsigned int size = 0; float rtt = 0, rtt_total = 0.0, rtt_min = 1000000.0, rtt_max = 0.0; char ip[100]; + const char *reason; struct ping_pkt packet; char reply[sizeof(struct iphdr) + PING_PKT_LEN]; struct iphdr *iph; @@ -231,7 +273,9 @@ int main(int argc, char **argv) len = recvfrom(s, reply, sizeof(reply), 0, (struct sockaddr *) &recv_addr, &size); - if (len <= 0 && msg_count > 1) { + /* A timeout (SO_RCVTIMEO) lands here too, which is the normal + * outcome for a host that never answers. */ + if (len <= 0) { printf("Packet receive failed!!\n"); continue; } @@ -256,9 +300,7 @@ int main(int argc, char **argv) icmph = (struct icmphdr *) (reply + hlen); - if (!(icmph->type == ICMP_ECHOREPLY && icmph->code == 0)) { - printf("Error... Packet received with ICMP type %d code %d\n", icmph->type, icmph->code); - } else { + if (icmph->type == ICMP_ECHOREPLY && icmph->code == 0) { printf("%d bytes from %s: icmp_seq=%d ttl=%d time=%f ms\n", len - hlen, ip, msg_count, iph->ttl, rtt); rtt_max = fmaxf(rtt_max, rtt); @@ -266,6 +308,17 @@ int main(int argc, char **argv) rtt_total += rtt; msg_count_succeed++; + } else { + /* Not an echo reply: an ICMP error about the request we + * just sent, reported by a router or by the stack of the + * destination itself. */ + + reason = icmp_reason(icmph->type, icmph->code); + + if (reason != NULL) + printf("From %s icmp_seq=%d %s\n", ip, msg_count, reason); + else + printf("From %s icmp_seq=%d ICMP type %d code %d\n", ip, msg_count, icmph->type, icmph->code); } }