# Reading [The Linux Kernel Module Programming Guide](https://sysprog21.github.io/lkmpg/) (11) ## Modify QEMU RISCV virt machine to run bh_thread.c 前一篇提到直接改dts重新編譯dtb是不夠的,所以要先去clone QEMU下來並compile(有很多東西要裝,這邊去問chatgpt就知道要怎麼做了) ``` git clone https://github.com/qemu/qemu.git cd qemu mkdir build cd build ../configure --target-list=riscv64-softmmu make install make -j$(nproc) ``` 要編譯蠻久的可能要等一下,之後要重新編譯就在build路徑下 ``` make clean make -j$(nproc) ``` 首先要改的地方就是`qemu/include/hw/riscv/virt.h`,裡面有關GPIO的部份都是新加的 ```clike /* * QEMU RISC-V VirtIO machine interface * * Copyright (c) 2017 SiFive, Inc. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2 or later, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see <http://www.gnu.org/licenses/>. */ #ifndef HW_RISCV_VIRT_H #define HW_RISCV_VIRT_H #include "hw/boards.h" #include "hw/riscv/riscv_hart.h" #include "hw/sysbus.h" #include "hw/block/flash.h" #include "hw/intc/riscv_imsic.h" #include "hw/gpio/sifive_gpio.h" // for add gpio chip #include "hw/misc/gpio-timer.h" // for trigger button interrupt #define VIRT_CPUS_MAX_BITS 9 #define VIRT_CPUS_MAX (1 << VIRT_CPUS_MAX_BITS) #define VIRT_SOCKETS_MAX_BITS 2 #define VIRT_SOCKETS_MAX (1 << VIRT_SOCKETS_MAX_BITS) #define TYPE_RISCV_VIRT_MACHINE MACHINE_TYPE_NAME("virt") typedef struct RISCVVirtState RISCVVirtState; DECLARE_INSTANCE_CHECKER(RISCVVirtState, RISCV_VIRT_MACHINE, TYPE_RISCV_VIRT_MACHINE) typedef enum RISCVVirtAIAType { VIRT_AIA_TYPE_NONE = 0, VIRT_AIA_TYPE_APLIC, VIRT_AIA_TYPE_APLIC_IMSIC, } RISCVVirtAIAType; struct RISCVVirtState { /*< private >*/ MachineState parent; /*< public >*/ Notifier machine_done; DeviceState *platform_bus_dev; RISCVHartArrayState soc[VIRT_SOCKETS_MAX]; DeviceState *irqchip[VIRT_SOCKETS_MAX]; PFlashCFI01 *flash[2]; FWCfgState *fw_cfg; SIFIVEGPIOState gpio; // for add gpio chip GpioTimerState gpio_timer; // for trigger button interrupt int fdt_size; bool have_aclint; RISCVVirtAIAType aia_type; int aia_guests; char *oem_id; char *oem_table_id; OnOffAuto acpi; const MemMapEntry *memmap; struct GPEXHost *gpex_host; OnOffAuto iommu_sys; uint16_t pci_iommu_bdf; }; enum { VIRT_DEBUG, VIRT_MROM, VIRT_TEST, VIRT_RTC, VIRT_CLINT, VIRT_ACLINT_SSWI, VIRT_PLIC, VIRT_APLIC_M, VIRT_APLIC_S, VIRT_UART0, VIRT_VIRTIO, VIRT_FW_CFG, VIRT_IMSIC_M, VIRT_IMSIC_S, VIRT_FLASH, VIRT_DRAM, VIRT_PCIE_MMIO, VIRT_PCIE_PIO, VIRT_PLATFORM_BUS, VIRT_PCIE_ECAM, VIRT_IOMMU_SYS, VIRT_GPIO, // for add gpio chip }; enum { UART0_IRQ = 10, RTC_IRQ = 11, VIRTIO_IRQ = 1, VIRTIO_COUNT = 8, PCIE_IRQ = 0x20, IOMMU_SYS_IRQ = 0x24, VIRT_PLATFORM_BUS_IRQ = 64, VIRT_GPIO_IRQ = 96 // for add gpio chip }; #define VIRT_PLATFORM_BUS_NUM_IRQS 32 #define VIRT_IRQCHIP_NUM_MSIS 255 #define VIRT_IRQCHIP_NUM_SOURCES 127 // 增加一些,避免interrupt不夠 #define VIRT_IRQCHIP_NUM_PRIO_BITS 3 #define VIRT_IRQCHIP_MAX_GUESTS_BITS 3 #define VIRT_IRQCHIP_MAX_GUESTS ((1U << VIRT_IRQCHIP_MAX_GUESTS_BITS) - 1U) #define VIRT_PLIC_PRIORITY_BASE 0x00 #define VIRT_PLIC_PENDING_BASE 0x1000 #define VIRT_PLIC_ENABLE_BASE 0x2000 #define VIRT_PLIC_ENABLE_STRIDE 0x80 #define VIRT_PLIC_CONTEXT_BASE 0x200000 #define VIRT_PLIC_CONTEXT_STRIDE 0x1000 #define VIRT_PLIC_SIZE(__num_context) \ (VIRT_PLIC_CONTEXT_BASE + (__num_context) * VIRT_PLIC_CONTEXT_STRIDE) #define FDT_PCI_ADDR_CELLS 3 #define FDT_PCI_INT_CELLS 1 #define FDT_PLIC_ADDR_CELLS 0 #define FDT_PLIC_INT_CELLS 1 #define FDT_APLIC_INT_CELLS 2 #define FDT_APLIC_ADDR_CELLS 0 #define FDT_IMSIC_INT_CELLS 0 #define FDT_MAX_INT_CELLS 2 #define FDT_MAX_INT_MAP_WIDTH (FDT_PCI_ADDR_CELLS + FDT_PCI_INT_CELLS + \ 1 + FDT_MAX_INT_CELLS) #define FDT_PLIC_INT_MAP_WIDTH (FDT_PCI_ADDR_CELLS + FDT_PCI_INT_CELLS + \ 1 + FDT_PLIC_INT_CELLS) #define FDT_APLIC_INT_MAP_WIDTH (FDT_PCI_ADDR_CELLS + FDT_PCI_INT_CELLS + \ 1 + FDT_APLIC_INT_CELLS) bool virt_is_acpi_enabled(RISCVVirtState *s); bool virt_is_iommu_sys_enabled(RISCVVirtState *s); void virt_acpi_setup(RISCVVirtState *vms); uint32_t imsic_num_bits(uint32_t count); /* * The virt machine physical address space used by some of the devices * namely ACLINT, PLIC, APLIC, and IMSIC depend on number of Sockets, * number of CPUs, and number of IMSIC guest files. * * Various limits defined by VIRT_SOCKETS_MAX_BITS, VIRT_CPUS_MAX_BITS, * and VIRT_IRQCHIP_MAX_GUESTS_BITS are tuned for maximum utilization * of virt machine physical address space. */ #define VIRT_IMSIC_GROUP_MAX_SIZE (1U << IMSIC_MMIO_GROUP_MIN_SHIFT) #if VIRT_IMSIC_GROUP_MAX_SIZE < \ IMSIC_GROUP_SIZE(VIRT_CPUS_MAX_BITS, VIRT_IRQCHIP_MAX_GUESTS_BITS) #error "Can't accommodate single IMSIC group in address space" #endif #define VIRT_IMSIC_MAX_SIZE (VIRT_SOCKETS_MAX * \ VIRT_IMSIC_GROUP_MAX_SIZE) #if 0x4000000 < VIRT_IMSIC_MAX_SIZE #error "Can't accommodate all IMSIC groups in address space" #endif #endif // 底下是列出GPIO IRQ對應的PLIC line,一共16個 #define GPIO_IRQ0 96 #define GPIO_IRQ1 97 #define GPIO_IRQ2 98 #define GPIO_IRQ3 99 #define GPIO_IRQ4 100 #define GPIO_IRQ5 101 #define GPIO_IRQ6 102 #define GPIO_IRQ7 103 #define GPIO_IRQ8 104 #define GPIO_IRQ9 105 #define GPIO_IRQ10 106 #define GPIO_IRQ11 107 #define GPIO_IRQ12 108 #define GPIO_IRQ13 109 #define GPIO_IRQ14 110 #define GPIO_IRQ15 111 ``` 接下來要動的就是qemu/hw/riscv/virt.c 加入create_fdt_gpio和sifive_gpio_create ```clike static void create_fdt_gpio(RISCVVirtState *s, uint32_t *phandle, uint32_t irq_virtio_phandle) { uint32_t gpio_phandle = (*phandle)++; char *nodename = g_strdup_printf("/soc/gpio@%lx", (long)s->memmap[VIRT_GPIO].base); void *fdt = MACHINE(s)->fdt; qemu_fdt_add_subnode(fdt, nodename); qemu_fdt_setprop_cell(fdt, nodename, "phandle", gpio_phandle); // qemu_fdt_setprop_cells(fdt, nodename, "clocks", // prci_phandle, PRCI_CLK_TLCLK); qemu_fdt_setprop_cell(fdt, nodename, "#interrupt-cells", 2); qemu_fdt_setprop(fdt, nodename, "interrupt-controller", NULL, 0); qemu_fdt_setprop_cell(fdt, nodename, "#gpio-cells", 2); qemu_fdt_setprop(fdt, nodename, "gpio-controller", NULL, 0); qemu_fdt_setprop_cells(fdt, nodename, "reg", 0x0, s->memmap[VIRT_GPIO].base, 0x0, s->memmap[VIRT_GPIO].size); qemu_fdt_setprop_cells(fdt, nodename, "interrupts", GPIO_IRQ0, GPIO_IRQ1, GPIO_IRQ2, GPIO_IRQ3, GPIO_IRQ4, GPIO_IRQ5, GPIO_IRQ6, GPIO_IRQ7, GPIO_IRQ8, GPIO_IRQ9, GPIO_IRQ10, GPIO_IRQ11, GPIO_IRQ12, GPIO_IRQ13, GPIO_IRQ14, GPIO_IRQ15); qemu_fdt_setprop_cell(fdt, nodename, "interrupt-parent", irq_virtio_phandle); qemu_fdt_setprop_string(fdt, nodename, "compatible", "sifive,gpio0"); g_free(nodename); } static void sifive_gpio_create(MachineState *machine, RISCVVirtState *s, DeviceState *virtio_irqchip) { int i; object_initialize_child(OBJECT(machine), "gpio", &s->gpio, TYPE_SIFIVE_GPIO); qdev_prop_set_uint32(DEVICE(&s->gpio), "ngpio", 16); if (!sysbus_realize(SYS_BUS_DEVICE(&s->gpio), &error_fatal)) { return; } sysbus_mmio_map(SYS_BUS_DEVICE(&s->gpio), 0, s->memmap[VIRT_GPIO].base); /* Pass all GPIOs to the SOC layer so they are available to the board */ // qdev_pass_gpios(DEVICE(&s->gpio), DEVICE(machine), NULL); /* Connect GPIO interrupts to the PLIC */ for (i = 0; i < 16; i++) { sysbus_connect_irq(SYS_BUS_DEVICE(&s->gpio), i, qdev_get_gpio_in(virtio_irqchip, VIRT_GPIO_IRQ + i)); } object_initialize_child(OBJECT(machine), "gpio-timer", &s->gpio_timer, TYPE_GPIO_TIMER); if (!sysbus_realize(SYS_BUS_DEVICE(&s->gpio_timer), &error_fatal)) { return; } // Connect output to gpio[2] input of sifive-gpio qdev_connect_gpio_out(DEVICE(&s->gpio_timer), 0, qdev_get_gpio_in(DEVICE(&s->gpio), 2)); } ``` `create_fdt_gpio`可以放在`finalize_fdt`的最後,負責生成設備樹中gpio的node ```clike static void finalize_fdt(RISCVVirtState *s) { ... create_fdt_rtc(s, irq_mmio_phandle); create_fdt_gpio(s, &phandle, irq_virtio_phandle); } ``` `sifive_gpio_create`要放在`virt_machine_init`的中間,負責將GPIO在QEMU的實體初始化和PLIC連接起來 ```clike static void virt_machine_init(MachineState *machine) { ... /* VirtIO MMIO devices */ for (i = 0; i < VIRTIO_COUNT; i++) { sysbus_create_simple("virtio-mmio", s->memmap[VIRT_VIRTIO].base + i * s->memmap[VIRT_VIRTIO].size, qdev_get_gpio_in(virtio_irqchip, VIRTIO_IRQ + i)); } // create sifive_gpio here sifive_gpio_create(machine, s, virtio_irqchip); gpex_pcie_init(system_memory, pcie_irqchip, s); ... ``` 上面那兩個function是從`qemu/hw/riscv/sifive_u`抄出來的,另外gpio_timer則是我叫chatgpt寫的一個sysbus device,這個device有一個GPIO output port,每10秒會改變一次寫到GPIO值0->1 or 1->0,在sifive_gpio_create的最後一部份初始化並接上gpio的第二個pin。 這樣設定,只要bh_thread.ko這個kernel module裝上之後,每10秒會觸發一次interrupt handler。 接下來就是gpio_timer的部分,要新增的檔案有 * qemu/hw/misc/gpio_timer.c * qemu/include/hw/misc/gpio-timer.h 要改動的檔案有 * qemu/hw/misc/meson.build ```c // gpio_timer.h #ifndef HW_MISC_GPIO_TIMER_H #define HW_MISC_GPIO_TIMER_H #include "hw/sysbus.h" #define TYPE_GPIO_TIMER "gpio-timer" OBJECT_DECLARE_SIMPLE_TYPE(GpioTimerState, GPIO_TIMER) struct GpioTimerState { SysBusDevice parent_obj; QEMUTimer *timer; qemu_irq irq_out; uint64_t period_ns; // 10s in ns bool level; }; #endif ``` ```clike // gpio_timer.c #include "qemu/osdep.h" #include "hw/sysbus.h" #include "qapi/error.h" #include "qemu/timer.h" #include "hw/qdev-core.h" #include "hw/irq.h" #include "hw/misc/gpio-timer.h" static void gpio_timer_cb(void *opaque) { GpioTimerState *s = opaque; s->level = !s->level; qemu_set_irq(s->irq_out, s->level); timer_mod(s->timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + s->period_ns); } static void gpio_timer_realize(DeviceState *dev, Error **errp) { GpioTimerState *s = GPIO_TIMER(dev); s->period_ns = 10ULL * 1000 * 1000 * 1000; // 10 seconds s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, gpio_timer_cb, s); timer_mod(s->timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + s->period_ns); } static void gpio_timer_init(Object *obj) { GpioTimerState *s = GPIO_TIMER(obj); SysBusDevice *sbd = SYS_BUS_DEVICE(obj); qdev_init_gpio_out(DEVICE(obj), &s->irq_out, 1); sysbus_init_irq(sbd, &s->irq_out); } static void gpio_timer_class_init(ObjectClass *klass, const void *data) { DeviceClass *dc = DEVICE_CLASS(klass); dc->realize = gpio_timer_realize; dc->user_creatable = true; } static const TypeInfo gpio_timer_info = { .name = TYPE_GPIO_TIMER, .parent = TYPE_SYS_BUS_DEVICE, .instance_size = sizeof(GpioTimerState), .instance_init = gpio_timer_init, .class_init = gpio_timer_class_init, }; static void gpio_timer_register_types(void) { type_register_static(&gpio_timer_info); } type_init(gpio_timer_register_types) ``` 之後有空再來解釋以上的code在做什麼 最後要改qemu/hw/misc/meson.build才能成功編譯gpio_timer,我自己是在第25左右加上下面這行,告訴compiler要去編譯這個device system_ss.add(files('gpio_timer.c')) 然後rebuild,QEMU最麻煩的部分就結束了。 接下來還有要調整的東西,bh_thread.c裡面指定gpio number要改,但是要改的數值不是2,還要加上一段offset才行。 具體數值可以在QEMU開機後執行 ```bash cat /dev/kmsg | grep gpio ``` ![image](https://hackmd.io/_uploads/H1mYnNx4ee.png) 看到 `7,140,394987,-;gpio gpiochip0: registered GPIOs 512 to 527 on 10010000.gpio` 所以在bh_thread.c的gpio leds和buttons會像這樣 ```clike /* Define GPIOs for LEDs. * FIXME: Change the numbers for the GPIO on your board. */ static struct gpio leds[] = {{512, GPIOF_OUT_INIT_LOW, "LED 1"}}; /* Define GPIOs for BUTTONS * FIXME: Change the numbers for the GPIO on your board. */ static struct gpio buttons[] = { {514, GPIOF_IN, "LED 1 ON BUTTON"}, {516, GPIOF_IN, "LED 1 OFF BUTTON"}, }; ``` 其中buttons number 514會被gpio_timer每10秒改變訊號一次,觸發interrupt handler。 ### 執行結果 ``` /workspace # insmod bh_thread.ko [ 539.120880] bh_thread: loading out-of-tree module taints kernel. [ 539.132947] bottomhalf_init [ 539.133812] Current button1 value: 1 [ 539.134884] Successfully requested BUTTON1 IRQ # 31 [ 539.140767] Successfully requested BUTTON2 IRQ # 32 /workspace # [ 539.797935] Bottom half task starts [ 540.301821] Bottom half task ends [ 549.797565] Bottom half task starts [ 550.299475] Bottom half task ends [ 559.797880] Bottom half task starts [ 560.299556] Bottom half task ends [ 569.798899] Bottom half task starts [ 570.303493] Bottom half task ends ... ``` 到這邊就差不多了,有空再補充這些更動做了甚麼,為何要這些改動,參考了哪些資料。 ## murmur 這整個實驗花了好多時間,即便有chatgpt還是很困難,尤其是一開始甚麼都不知道的情況,常常被chatgpt騙,特別是在提出一個看似可行的目標,但實際上QEMU做不到的事情,chatgpt就會給一堆鬼打牆的方案,繞了一圈了解QEMU在做啥之後才發現chatgpt常常會亂支持我的不正確的提案。 特別是不要再想要做的目標前面加上自己亂七八糟的推理(人類的幻覺,或者說書沒讀透在考卷上胡說八道),會某種程度上誤導chatgpt,然後chatgpt再誤導你做一些奇奇怪怪的事情。所以整個實驗最大部份的改動基本上是我讀完sifive_u這個machine之後,將裡面的邏輯複製到virt machine上。不過chatgpt的幫助還是很多的,幫我生出gpio_timer,還有compile的過程語法有問題幫我修正,以及一開始甚麼都不了解幫我找到一些可以嘗試的方向。 體感上來說chatgpt對我的幫助大概分兩類 * 知識理解 * 工作效率 前者我認為chatgpt能做到的大概是上一個固定值的buff,如果是從0開始,chatgpt確實能給幾十幾百倍的效益讓我了解一個雛形,但是開始知道得越多,chatgpt能給的幫助效益就快速遞減,甚至會附和我錯誤的理解。 後者則是當我了解問題到一定程度,能給出精確的目的和要求之後,chatgpt就能快速地打好草稿,基於自身的理解可以快速修正chatgpt的錯誤後,工作的效率確實會極大程度的提升。 總結就是: 1. 先透過chatgpt掌握一個輪廓 2. 根據chatgpt提供的資訊廣泛搜索閱讀 3. 深入source code並嘗試編譯執行(這邊chatgpt的輔助效益最大) 4. 掌握一定知識後,將任務細分餵給chatgpt(這邊必須要對問題有一定的理解,否則會被chatgpt搞) 5. 修正chatgpt的結果並完成目標 Reading The Linux Kernel Module Programming Guide主要的部分就到這邊啦。之後看要不要補上debug的過程,和compile kernel時可以打開的一些選項,和qemu的一些理解。 下一個要看的應該會是下一個要看的應該會是`Linux containers in 500 lines of code`