[虚拟化/云][全栈demo] 为qemu增加一个PCI的watchdog外设(二)

时间:2023-03-09 13:24:48
[虚拟化/云][全栈demo] 为qemu增加一个PCI的watchdog外设(二)

这篇文章的理解,需要一些专业知识了。

我们可以创建模拟自己的外设吗?

我们已经知道什么是qemu了,我们可以通过qmeu的提供的外设,DIY一个计算机了。

但是我们可能还不满足,我们可以自己制造一个外设吗?

答案是可以的。而且这是了解计算机体系结构的一个很好的实践活动。

watchdog 外设

watchdog, 即看门狗。 如果狗饿了,便会”咬人“(CPU),让CPU重新启动。 为了不让狗狗”咬人“,我们需要不停的喂他。

我们将创建一个最简单的PCI的外设watchdog。如果你是一个硬件工程师或集成电路工程师,那么你肯定知道watchdog是什么东西, 并且可以很轻松的自行设计一个watchdog了。

但是我们这里创建的watchdog,是基于qemu架构的软件模拟的设备。

预备知识:

1. git 基本操作。

2. C 语言。

3. PCI的一些知识。

4. 阅读qemu 的文档 http://wiki.qemu.org/Manual

开发平台:

linux

实践:

1. clone一个qemu的仓库 http://wiki.qemu.org/Download

$ git clone git://git.qemu.org/qemu.git

2. 切换到一个新分支,一定从5d92c74 检出(checkout

$ git checkout -b watchdog 5d92c74

3. 写源代码。

将下面的代码watchdog_source.patch(见代码目录)保存到本地, 注意去掉代码前面的行好。 现在不分析代码。以后会分析。这个代码已经测试过是可以运行的。

然后应用到qemu中。

$ git apply watchdog_source.patch

4. 配置qemu  http://wiki.qemu.org/Documentation/9psetup

$ ./configure '--target-list=x86_64-softmmu' '--enable-debug' '--enable-kvm' '--enable-spice' '--prefix=/home/shhfeng/qemu/'

4. 编译源代码。 http://wiki.qemu.org/Hosts/Linux

$ make

5. 测试代码 http://wiki.qemu.org/Documentation/QemuIoTests

将下面的代码watchdog_testcase.patch(见代码目录)保存到本地, 注意去掉代码前面的行好。 现在不分析代码。以后会分析。这个代码已经测试过是可以运行的。

然后应用到qemu中。

$ git apply watchdog_testcase.patch

$ make check-qtest-x86_64

6. 启动qemu with cstl-watchdog

$ x86_64-softmmu/qemu-system-x86_64 -device cstl-watchdog

注意:这里不能使用: $ x86_64-softmmu/qemu-system-x86_64 -watchdog cstl-watchdog

是因为没有在cstl-watchdog.c 中定义: WatchdogTimerModel

此外watchdog没有支持中断, 请参考 http://www.cnblogs.com/lihuidashen/p/4462220.html

代码目录:

1. watchdog_source.patch

 diff --git a/hw/watchdog/Makefile.objs b/hw/watchdog/Makefile.objs
index 4b0374a..8f34e78
--- a/hw/watchdog/Makefile.objs
+++ b/hw/watchdog/Makefile.objs
@@ -, +, @@
-common-obj-y += watchdog.o
+common-obj-y += watchdog.o cstl-watchdog.o
common-obj-$(CONFIG_WDT_IB6300ESB) += wdt_i6300esb.o
common-obj-$(CONFIG_WDT_IB700) += wdt_ib700.o
diff --git a/hw/watchdog/cstl-watchdog.c b/hw/watchdog/cstl-watchdog.c
new file mode
index ..3ce043a
--- /dev/null
+++ b/hw/watchdog/cstl-watchdog.c
@@ -, +, @@
+/*
+ * Watch Dog Timer Demo
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ */
+
+#include "qemu/timer.h"
+#include "hw/hw.h"
+#include "hw/pci/pci.h"
+#include "sysemu/sysemu.h"
+
+typedef struct CSTLWatchdogState {
+ PCIDevice dev;
+
+ uint8_t activated;
+
+ uint8_t triggered;
+
+ uint32_t missed_ticks;
+
+ QEMUTimer *watchdog_timer;
+
+ uint32_t expiration_ticks;
+
+ MemoryRegion io;
+} CSTLWatchdogState;
+#define TYPE_CSTL_WATCHDOG "cstl-watchdog"
+#define CSTL_WATCHDOG(obj) \
+ OBJECT_CHECK(CSTLWatchdogState, (obj), TYPE_CSTL_WATCHDOG)
+
+static void cwd_timer_event(void *opaque)
+{
+ CSTLWatchdogState *s = CSTL_WATCHDOG(opaque);
+
+ (void)s;
+
+ printf("watch dog fire!\n");
+ if (!s->triggered) {
+ s->missed_ticks++;
+ }
+
+ s->triggered = ;
+
+ if (s->missed_ticks > ) {
+ printf("WARNING: missed watchdog tick\n");
+ }
+
+ if (s->missed_ticks > s->expiration_ticks) {
+ printf("Watchdog expired!\n");
+ qemu_system_reset_request();
+ }
+
+
+ if (s->activated) {
+ timer_mod(s->watchdog_timer,
+ qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + );
+ }
+}
+
+static uint64_t cwd_io_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ CSTLWatchdogState *s = CSTL_WATCHDOG(opaque);
+
+ switch (addr) {
+ case 0x00:
+ return 0x42;
+ case 0x01:
+ return s->activated;
+ default:
+ break;
+ }
+
+ return ;
+}
+
+static void cwd_io_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ CSTLWatchdogState *s = CSTL_WATCHDOG(opaque);
+
+ switch (addr) {
+ case 0x00:
+ /* read-only */
+ break;
+ case 0x01:
+ s->activated = !!val;
+
+ if (s->activated) {
+ printf("Activated!\n");
+ cwd_timer_event(s);
+ } else {
+ printf("Deactivated!\n");
+ timer_del(s->watchdog_timer);
+ }
+ break;
+ case 0x02:
+ s->triggered = ;
+ s->missed_ticks = ;
+ break;
+ default:
+ break;
+ }
+}
+
+static const MemoryRegionOps cwd_io_ops = {
+ .read = cwd_io_read,
+ .write = cwd_io_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static const VMStateDescription vmstate_cwd = {
+ .name = TYPE_CSTL_WATCHDOG,
+ .version_id = ,
+ .fields = (VMStateField []) {
+ VMSTATE_PCI_DEVICE(dev, CSTLWatchdogState),
+ VMSTATE_UINT8(activated, CSTLWatchdogState),
+ VMSTATE_TIMER(watchdog_timer, CSTLWatchdogState),
+ VMSTATE_UINT8(triggered, CSTLWatchdogState),
+ VMSTATE_UINT32(missed_ticks, CSTLWatchdogState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void cwd_unrealize(PCIDevice *dev)
+{
+}
+
+static int cwd_realize(PCIDevice *pci_dev)
+{
+ CSTLWatchdogState *s = CSTL_WATCHDOG(pci_dev);
+
+ pci_register_bar(pci_dev, , PCI_BASE_ADDRESS_SPACE_IO, &s->io);
+
+ return ;
+}
+
+static void cwd_reset(DeviceState *dev)
+{
+ CSTLWatchdogState *s = CSTL_WATCHDOG(dev);
+
+ s->activated = ;
+ s->triggered = ;
+ s->missed_ticks = ;
+}
+
+static void cwd_initfn(Object *obj)
+{
+ CSTLWatchdogState *s = CSTL_WATCHDOG(obj);
+
+ memory_region_init_io(&s->io, OBJECT(s), &cwd_io_ops, s, "cstl-watchdog-io", );
+
+ s->watchdog_timer = timer_new_ms(QEMU_CLOCK_REALTIME, cwd_timer_event, s);
+}
+
+static Property cwd_properties[] = {
+ DEFINE_PROP_UINT32("expiration-ticks", CSTLWatchdogState,
+ expiration_ticks, ),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void cwd_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->init = cwd_realize;
+ k->exit = cwd_unrealize;
+ k->vendor_id = PCI_VENDOR_ID_REDHAT_QUMRANET;
+ k->device_id = 0x0101;
+ k->revision = 0x01;
+ k->class_id = PCI_CLASS_SYSTEM_OTHER;
+ dc->reset = cwd_reset;
+ dc->vmsd = &vmstate_cwd;
+ dc->props = cwd_properties;
+}
+
+static TypeInfo cstl_watchdog_info = {
+ .name = TYPE_CSTL_WATCHDOG,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_init = cwd_initfn,
+ .instance_size = sizeof(CSTLWatchdogState),
+ .class_init = cwd_class_init,
+};
+
+static void register_types(void)
+{
+ type_register_static(&cstl_watchdog_info);
+}
+
+type_init(register_types)

2. watchdog_testcase.patch

 diff --git a/tests/Makefile b/tests/Makefile
index 471b4c8..0a8f2cd
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -, +, @@ gcov-files-i386-y += hw/block/hd-geometry.c
check-qtest-i386-y += tests/boot-order-test$(EXESUF)
check-qtest-i386-y += tests/acpi-test$(EXESUF)
check-qtest-i386-y += tests/rtc-test$(EXESUF)
+check-qtest-i386-y += tests/cwd-test$(EXESUF)
check-qtest-i386-y += tests/i440fx-test$(EXESUF)
check-qtest-i386-y += tests/fw_cfg-test$(EXESUF)
check-qtest-i386-y += tests/blockdev-test$(EXESUF)
@@ -, +, @@ libqos-pc-obj-y += tests/libqos/malloc-pc.o
libqos-omap-obj-y = $(libqos-obj-y) tests/libqos/i2c-omap.o tests/rtc-test$(EXESUF): tests/rtc-test.o
+tests/cwd-test$(EXESUF): tests/cwd-test.o
tests/m48t59-test$(EXESUF): tests/m48t59-test.o
tests/endianness-test$(EXESUF): tests/endianness-test.o
tests/spapr-phb-test$(EXESUF): tests/spapr-phb-test.o $(libqos-obj-y)
diff --git a/tests/cwd-test.c b/tests/cwd-test.c
new file mode
index ..380a313
--- /dev/null
+++ b/tests/cwd-test.c
@@ -, +, @@
+/*
+ * QTest testcase for the CSTL Watchdog
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ */
+#include "libqtest.h"
+#include "hw/pci/pci_ids.h"
+#include "hw/pci/pci_regs.h"
+
+#include <glib.h>
+#include <unistd.h>
+
+static uint32_t pci_config_read(uint8_t bus, uint8_t devfn,
+ uint8_t addr, int size)
+{
+ outl(0xcf8, (bus << ) | (devfn << ) | addr | (1u << ));
+ if (size == ) {
+ return inb(0xcfc);
+ } else if (size == ) {
+ return inw(0xcfc);
+ }
+ return inl(0xcfc);
+}
+
+static void pci_config_write(uint8_t bus, uint8_t devfn,
+ uint32_t addr, int size, uint32_t value)
+{
+ outl(0xcf8, (bus << ) | (devfn << ) | addr | (1u << ));
+ if (size == ) {
+ outb(0xcfc, value);
+ } else if (size == ) {
+ outw(0xcfc, value);
+ } else {
+ outl(0xcfc, value);
+ }
+}
+
+static void cwd_probe(uint8_t bus, uint8_t devfn)
+{
+ uint32_t bar0 = 0xc000;
+ int i;
+
+ pci_config_write(bus, devfn, PCI_COMMAND, ,
+ (PCI_COMMAND_IO | PCI_COMMAND_MEMORY));
+ pci_config_write(bus, devfn, PCI_BASE_ADDRESS_0, , bar0);
+
+ g_assert_cmpint(inb(bar0 + 0x00), ==, 0x42);
+
+ outb(bar0 + 0x01, 0x03); // activate device
+ g_assert_cmpint(inb(bar0 + 0x01), ==, 0x01); // confirm activation
+
+ for (i = ; i < * ; i++) {
+ outb(bar0 + 0x02, 0x32);
+ g_usleep();
+ }
+
+ outb(bar0 + 0x01, 0x00); // deactivate device
+}
+
+static void basic_init(void)
+{
+ int slot;
+
+ for (slot = ; slot < ; slot++) {
+ uint8_t fn;
+
+ for (fn = ; fn < ; fn++) {
+ uint8_t devfn = (slot << ) | fn;
+ uint16_t device_id;
+ uint16_t vendor_id;
+
+ vendor_id = pci_config_read(, devfn, PCI_VENDOR_ID, );
+ device_id = pci_config_read(, devfn, PCI_DEVICE_ID, );
+
+ if (vendor_id == 0xFFFF || device_id == 0xFFFF) {
+ break;
+ }
+
+ if (vendor_id == 0x1af4 && device_id == 0x0101) {
+ cwd_probe(, devfn);
+ return;
+ }
+ }
+ }
+
+ g_assert_not_reached();
+}
+
+int main(int argc, char **argv)
+{
+ QTestState *s = NULL;
+ char *cmd;
+ int ret;
+
+ g_test_init(&argc, &argv, NULL);
+
+ cmd = g_strdup_printf("-device cstl-watchdog,expiration-ticks=%d",
+ g_test_rand_int_range(, ));
+
+ s = qtest_start(cmd);
+
+ g_free(cmd);
+
+ qtest_add_func("/basic/init", basic_init);
+
+ ret = g_test_run();
+
+ if (s) {
+ qtest_quit(s);
+ }
+
+ return ret;
+}