基于qemu-riscv从0开始构建嵌入式linux系统ch18. RTC设备与系统控制设备

RTC设备

我们当前的qemu环境中使用date查看时间发现时间错误,而且即使主动修改时间也不能保存,下次重新启动qemu时间依旧会重置,这会有什么影响呢,目前最大的影响就是使用make工具处理一些工具的源码安装时,时间的错误导致一些错误的依赖检查,影响我们自动化安装脚本。因此我们需要向qemu添加一个虚拟的RTC时钟的组件,使得系统启动后获取到主机的时间,这样每次时间就不会错乱。

  • qemu-6.0.0/hw/riscv/quard_star.c添加如下内容:

……
[QUARD_STAR_RTC]    = { 0x10003000,        0x1000 },
……
sysbus_create_simple("goldfish_rtc", memmap[QUARD_STAR_RTC].base,
qdev_get_gpio_in(DEVICE(mmio_plic), QUARD_STAR_RTC_IRQ));
……
  • qemu-6.0.0/hw/riscv/quard_star.h添加中断号

QUARD_STAR_RTC_IRQ = 13,
  • 设备树文件增加google,goldfish-rtc设备,配置寄存器地址和中断号即可。

rtc@10003000 {
    interrupts = <0xb>;
    interrupt-parent = <0x11>;
    reg = <0x0 0x10003000 0x0 0x1000>;
    compatible = "google,goldfish-rtc";
};
  • 当前版本的linux内核已经集成了该设备的驱动,就没什么好说的了,这里比较简单了。

syscon

我们的系统目前还存在一个问题,就是使用reboot命令和halt命令不能实现重启系统和关机,这是由于qemu仿真环境下,重启需要调用qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);来重置仿真,而关机则只需要exit(0);退出即可。那么如何能让我们的目标系统内使用reboot命令和halt命令来实现呢?其实你自己实现一个linux中syscon的ip然后编写驱动是非常简单的,在qemu中提供了hw/misc/sifive_test已经给出了一个简单的实现,那么我们就直接使用它即可。

  • qemu-6.0.0/hw/riscv/quard_star.c添加如下内容:

……
[QUARD_STAR_TEST]   = {   0x100000,        0x1000 },
……
sifive_test_create(memmap[QUARD_STAR_TEST].base);
……
  • 阅读sifive_test.c的源码可以看到实现很简单,只要向这个寄存器写入0x7777就可以复位系统,写入0x5555可以退出qemu。

static void sifive_test_write(void *opaque, hwaddr addr,
           uint64_t val64, unsigned int size)
{
    if (addr == 0) {
        int status = val64 & 0xffff;
        int code = (val64 >> 16) & 0xffff;
        switch (status) {
        case FINISHER_FAIL:
            exit(code);
        case FINISHER_PASS:
            exit(0);
        case FINISHER_RESET:
            qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);
            return;
        default:
            break;
        }
    }
    qemu_log_mask(LOG_GUEST_ERROR, "%s: write: addr=0x%x val=0x%016" PRIx64 "\n",
                  __func__, (int)addr, val64);
}
  • 设备树文件增加sifive,test1设备,syscon-reboot、syscon-poweroff设备。

reboot {
    value = <0x7777>;
    offset = <0x0>;
    regmap = <0x12>;
    compatible = "syscon-reboot";
};

poweroff {
    value = <0x5555>;
    offset = <0x0>;
    regmap = <0x12>;
    compatible = "syscon-poweroff";
};

test@100000 {
    phandle = <0x12>;
    reg = <0x0 0x100000 0x0 0x1000>;
    compatible = "sifive,test1", "sifive,test0", "syscon";
};
  • 这里要提一句syscon-reboot、syscon-poweroff这类设备其实就是一个寄存和触发key值,可以稍微阅读下相关的设备驱动probe代码,这种写法平时我们自己开发简单的驱动或测试驱动时也可以使用类似方案。

static int syscon_reboot_probe(struct platform_device *pdev)
{
	struct syscon_reboot_context *ctx;
	struct device *dev = &pdev->dev;
	int mask_err, value_err;
	int err;

	ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL);
	if (!ctx)
		return -ENOMEM;

	ctx->map = syscon_regmap_lookup_by_phandle(dev->of_node, "regmap");
	if (IS_ERR(ctx->map)) {
		ctx->map = syscon_node_to_regmap(dev->parent->of_node);
		if (IS_ERR(ctx->map))
			return PTR_ERR(ctx->map);
	}

	if (of_property_read_u32(pdev->dev.of_node, "offset", &ctx->offset))
		return -EINVAL;

	value_err = of_property_read_u32(pdev->dev.of_node, "value", &ctx->value);
	mask_err = of_property_read_u32(pdev->dev.of_node, "mask", &ctx->mask);
	if (value_err && mask_err) {
		dev_err(dev, "unable to read 'value' and 'mask'");
		return -EINVAL;
	}

	if (value_err) {
		/* support old binding */
		ctx->value = ctx->mask;
		ctx->mask = 0xFFFFFFFF;
	} else if (mask_err) {
		/* support value without mask*/
		ctx->mask = 0xFFFFFFFF;
	}

	ctx->restart_handler.notifier_call = syscon_restart_handle;
	ctx->restart_handler.priority = 192;
	err = register_restart_handler(&ctx->restart_handler);
	if (err)
		dev_err(dev, "can't register restart notifier (err=%d)\n", err);

	return err;
}

本教程的
github仓库:https://github.com/QQxiaoming/quard_star_tutorial
gitee仓库:https://gitee.com/QQxiaoming/quard_star_tutorial
本节所在tag:ch18