Skip to content

Commit 20bf18f

Browse files
authored
Merge pull request #47 from sysprog21/arm-virtio-net
End-to-end network for Arm/virtio
2 parents a80bc33 + 430867a commit 20bf18f

5 files changed

Lines changed: 185 additions & 16 deletions

File tree

README.md

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@ capable of running Linux kernel partially.
1010

1111
## Build and Run
1212

13+
A working C toolchain plus the kernel build prerequisites (`flex`, `bison`, and
14+
the `libelf` development headers) are required. On Debian/Ubuntu:
15+
```shell
16+
$ sudo apt install -y flex bison libelf-dev
17+
```
18+
Other distributions provide equivalent packages.
19+
1320
Fetch required submodules (only necessary for ARM build):
1421
```shell
1522
git submodule update --init --recursive
@@ -33,17 +40,75 @@ make check
3340

3441
## Usage
3542

43+
### Start Emulator
44+
3645
```
37-
build/kvm-host -k bzImage [-i initrd] [-d disk-image]
46+
$ build/kvm-host -k bzImage [-i initrd] [-d disk-image]
3847
```
3948

4049
`bzImage` is the path to linux kernel bzImage. The bzImage file is in a specific format,
4150
containing concatenated `bootsect.o + setup.o + misc.o + piggy.o`. `initrd` is the path to
4251
initial RAM disk image, which is an optional argument.
4352
`disk-image` is the path to disk image which can be mounted as a block device via virtio. For the reference Linux guest, ext4 filesystem is used for disk image.
4453

54+
### Exit Emulator
55+
4556
To exit kvm-host, press "Ctrl-A", release both keys, and then press "x".
4657

58+
### Test the Guest virtio-net Interface
59+
60+
The guest is reachable from the host through a dedicated bridge
61+
(`kvmbr0` by default) that owns the TAP interface kvm-host creates at
62+
startup. The bridge is a host-guest link only — no internet egress —
63+
so the helpers never modify the host's default route.
64+
65+
1. Start `kvm-host` and locate the TAP it created. `kvm-host` requests
66+
`tap%d` from `TUNSETIFF`, so the kernel assigns the first free
67+
`tapN`; it is usually but not always `tap0`. The interface comes up
68+
`DOWN` because nothing has claimed it yet:
69+
70+
```shell
71+
$ ip a
72+
...
73+
11: tap0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
74+
link/ether 5a:1d:bd:2d:7c:1f brd ff:ff:ff:ff:ff:ff
75+
```
76+
77+
2. From the host, run `scripts/set-host-bridge.sh [TAP] [BRIDGE]`. It
78+
creates `kvmbr0`, assigns `10.0.0.1/24`, and enslaves the TAP. If
79+
the bridge already exists the script reuses it; if a non-bridge
80+
interface owns the name it refuses to proceed. Override `TAP` if
81+
the kernel handed kvm-host a different name in step 1:
82+
83+
```shell
84+
$ ./scripts/set-host-bridge.sh # uses tap0 + kvmbr0
85+
$ ./scripts/set-host-bridge.sh tap1 # if kvm-host got tap1
86+
$ ip a
87+
...
88+
11: tap0: <BROADCAST,MULTICAST,UP,LOWER_UP> ...
89+
12: kvmbr0: <BROADCAST,MULTICAST,UP,LOWER_UP> ...
90+
inet 10.0.0.1/24 scope global kvmbr0
91+
```
92+
93+
3. Inside the guest, paste the contents of `scripts/set-guest-route.sh`
94+
to assign `10.0.0.2/24`:
95+
96+
```shell
97+
$ ip a
98+
...
99+
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> ...
100+
inet 10.0.0.2/24 scope global eth0
101+
```
102+
103+
4. Verify connectivity from the guest:
104+
105+
```shell
106+
$ ping 10.0.0.1
107+
```
108+
109+
For traffic beyond `10.0.0.0/24`, configure NAT and IPv4 forwarding
110+
on the host first; the helpers stop at the host-guest link.
111+
47112
## License
48113

49114
`kvm-host` is released under the BSD 2 clause license. Use of this source code is governed by
@@ -52,7 +117,7 @@ a BSD-style license that can be found in the LICENSE file.
52117
## References
53118
* [kvmtool](https://github.com/kvmtool/kvmtool)
54119
* [KVM (Kernel-based Virtual Machine) API](https://www.kernel.org/doc/Documentation/virtual/kvm/api.txt)
55-
* [The Linux/x86 Boot Protocol](https://www.kernel.org/doc/html/latest/x86/boot.html)
120+
* [The Linux/x86 Boot Protocol](https://www.kernel.org/doc/html/latest/arch/x86/boot.html)
56121
* [Using the KVM API](https://lwn.net/Articles/658511/)
57122
* [gokvm](https://github.com/bobuhiro11/gokvm)
58123
* [KVM Host in a few lines of code](https://zserge.com/posts/kvm/)

configs/linux-arm64.config

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ CONFIG_CC_OPTIMIZE_FOR_SIZE=y
1212
# CONFIG_MULTIUSER is not set
1313
# CONFIG_SYSFS_SYSCALL is not set
1414
# CONFIG_FHANDLE is not set
15-
# CONFIG_POSIX_TIMERS is not set
15+
CONFIG_POSIX_TIMERS=y
1616
# CONFIG_BUG is not set
1717
CONFIG_BASE_SMALL=y
1818
# CONFIG_FUTEX is not set
@@ -65,3 +65,11 @@ CONFIG_EXT4_FS=y
6565
# CONFIG_DEBUG_MISC is not set
6666
# CONFIG_FTRACE is not set
6767
# CONFIG_STRICT_DEVMEM is not set
68+
CONFIG_NET=y
69+
CONFIG_INET=y
70+
CONFIG_IP_MULTICAST=y
71+
CONFIG_IP_PNP=y
72+
CONFIG_IP_PNP_DHCP=y
73+
CONFIG_NETDEVICES=y
74+
CONFIG_VIRTIO=y
75+
CONFIG_VIRTIO_NET=y

scripts/set-guest-route.sh

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#!/bin/sh
2+
# Configure the guest's virtio-net interface so it can reach the host
3+
# bridge created by set-host-bridge.sh. Paste these commands at the
4+
# guest shell; the BusyBox initramfs does not ship the script itself.
5+
#
6+
# No default route is installed: the host bridge is a dedicated
7+
# host-guest link, not an internet gateway, and pointing default
8+
# traffic at it would silently black-hole everything outside
9+
# 10.0.0.0/24. Add NAT/forwarding on the host first if you want the
10+
# guest reaching beyond the bridge.
11+
12+
set -eu
13+
14+
ip addr replace 10.0.0.2/24 dev eth0
15+
ip link set eth0 up

scripts/set-host-bridge.sh

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#!/bin/sh
2+
# Bring up a host-side bridge and enslave the kvm-host TAP interface
3+
# to it so the guest can ping the host. Run after kvm-host has created
4+
# its TAP. The bridge is dedicated to this 10.0.0.0/24 link; the
5+
# script never touches the host's default route.
6+
#
7+
# Usage: set-host-bridge.sh [TAP] [BRIDGE]
8+
# TAP defaults to "tap0". kvm-host opens the first free
9+
# tap%d via TUNSETIFF, so this may be tap1, tap2, ...
10+
# if the host already has a tap0. Check `ip a` after
11+
# starting kvm-host and pass the actual name if it
12+
# differs.
13+
# BRIDGE defaults to "kvmbr0". The script refuses to touch a
14+
# bridge it did not create, so picking a dedicated name
15+
# protects pre-existing br0/virbr0/etc.
16+
17+
set -eu
18+
19+
TAP=${1:-tap0}
20+
BRIDGE=${2:-kvmbr0}
21+
ADDR=10.0.0.1/24
22+
23+
if ip link show "$BRIDGE" >/dev/null 2>&1; then
24+
# `ip link show NAME type bridge` does not filter by type when a name
25+
# is given, so probe the kernel's bridge sysfs directory instead.
26+
if [ ! -d "/sys/class/net/$BRIDGE/bridge" ]; then
27+
echo "set-host-bridge: '$BRIDGE' exists and is not a bridge; refusing to touch it" >&2
28+
exit 1
29+
fi
30+
# The bridge already exists. Reuse it instead of deleting so we
31+
# don't disturb other slaves (libvirt, containers, ...) that may
32+
# share the name.
33+
echo "set-host-bridge: reusing existing bridge '$BRIDGE'" >&2
34+
else
35+
sudo ip link add name "$BRIDGE" type bridge
36+
fi
37+
38+
if ! ip -o addr show dev "$BRIDGE" | grep -q " ${ADDR%/*}/"; then
39+
sudo ip addr add "$ADDR" dev "$BRIDGE"
40+
fi
41+
sudo ip link set "$BRIDGE" up
42+
43+
if ! ip link show "$TAP" >/dev/null 2>&1; then
44+
echo "set-host-bridge: TAP '$TAP' does not exist; start kvm-host first" >&2
45+
exit 1
46+
fi
47+
sudo ip link set "$TAP" master "$BRIDGE"
48+
sudo ip link set "$TAP" up

src/arch/arm64/vm.c

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,13 @@ int vm_arch_init_platform_device(vm_t *v)
142142
if (serial_init(&v->serial, &v->io_bus))
143143
return throw_err("Failed to init UART device");
144144

145+
/* Zero virtio_blk_dev so pci_dev_is_registered() observes a clean
146+
* state when the user boots without -d. virtio_net_init memsets
147+
* inside vm_enable_net, so virtio_net_dev is covered by that path.
148+
* x86 already does the same call in its vm_arch_init_platform_device.
149+
*/
150+
virtio_blk_init(&v->virtio_blk_dev);
151+
145152
if (finalize_irqchip(v) < 0)
146153
return -1;
147154

@@ -218,6 +225,11 @@ static int get_mpidr(vm_t *v, uint64_t *mpidr)
218225
return 0;
219226
}
220227

228+
static bool pci_dev_is_registered(struct pci_dev *dev)
229+
{
230+
return dev->config_dev.len == PCI_CFG_SPACE_SIZE;
231+
}
232+
221233
/* The phandle of interrupt controller */
222234
#define FDT_PHANDLE_GIC 1
223235

@@ -376,12 +388,14 @@ static int generate_fdt(vm_t *v)
376388
cpu_to_fdt64(ARM_PCI_MMIO_BASE), cpu_to_fdt64(ARM_PCI_MMIO_SIZE)},
377389
};
378390
__FDT(property, "ranges", &pci_ranges, sizeof(pci_ranges));
379-
/* interrupt-map contains the interrupt mapping between the PCI device and
380-
* the IRQ number of interrupt controller.
381-
* virtio-blk is the only PCI device.
391+
/* interrupt-map routes each PCI device's INTA# to a GIC SPI so the
392+
* guest can wire up MSI-less virtio devices. One entry per emulated
393+
* PCI function.
382394
*/
383395
struct virtio_blk_dev *virtio_blk = &v->virtio_blk_dev;
384396
struct pci_dev *virtio_blk_pci = (struct pci_dev *) virtio_blk;
397+
struct virtio_net_dev *virtio_net = &v->virtio_net_dev;
398+
struct pci_dev *virtio_net_pci = (struct pci_dev *) virtio_net;
385399
struct {
386400
uint32_t pci_hi;
387401
uint64_t pci_addr;
@@ -390,16 +404,35 @@ static int generate_fdt(vm_t *v)
390404
uint32_t gic_type;
391405
uint32_t gic_irqn;
392406
uint32_t gic_irq_type;
393-
} __attribute__((packed)) pci_irq_map[] = {{
394-
cpu_to_fdt32(virtio_blk_pci->config_dev.base & ~(1UL << 31)),
395-
0,
396-
cpu_to_fdt32(1),
397-
cpu_to_fdt32(FDT_PHANDLE_GIC),
398-
cpu_to_fdt32(ARM_FDT_IRQ_TYPE_SPI),
399-
cpu_to_fdt32(VIRTIO_BLK_IRQ),
400-
cpu_to_fdt32(ARM_FDT_IRQ_EDGE_TRIGGER),
401-
}};
402-
__FDT(property, "interrupt-map", &pci_irq_map, sizeof(pci_irq_map));
407+
} __attribute__((packed)) pci_irq_map[2];
408+
size_t pci_irq_map_len = 0;
409+
410+
if (pci_dev_is_registered(virtio_blk_pci)) {
411+
pci_irq_map[pci_irq_map_len++] = (__typeof__(pci_irq_map[0])) {
412+
cpu_to_fdt32(virtio_blk_pci->config_dev.base & ~(1UL << 31)),
413+
0,
414+
cpu_to_fdt32(1),
415+
cpu_to_fdt32(FDT_PHANDLE_GIC),
416+
cpu_to_fdt32(ARM_FDT_IRQ_TYPE_SPI),
417+
cpu_to_fdt32(VIRTIO_BLK_IRQ),
418+
cpu_to_fdt32(ARM_FDT_IRQ_EDGE_TRIGGER),
419+
};
420+
}
421+
if (pci_dev_is_registered(virtio_net_pci)) {
422+
pci_irq_map[pci_irq_map_len++] = (__typeof__(pci_irq_map[0])) {
423+
cpu_to_fdt32(virtio_net_pci->config_dev.base & ~(1UL << 31)),
424+
0,
425+
cpu_to_fdt32(1),
426+
cpu_to_fdt32(FDT_PHANDLE_GIC),
427+
cpu_to_fdt32(ARM_FDT_IRQ_TYPE_SPI),
428+
cpu_to_fdt32(VIRTIO_NET_IRQ),
429+
cpu_to_fdt32(ARM_FDT_IRQ_EDGE_TRIGGER),
430+
};
431+
}
432+
if (pci_irq_map_len > 0) {
433+
__FDT(property, "interrupt-map", &pci_irq_map,
434+
pci_irq_map_len * sizeof(pci_irq_map[0]));
435+
}
403436
__FDT(end_node); /* End of /pci node */
404437

405438
/* Finalize the device tree */

0 commit comments

Comments
 (0)