AbydOS开发日记 (2) - 驱动程序框架和系统参数配置

3/11/2024 OSC++FDT

# 设备与系统参数

所有连接到系统上的部件,都可以视为设备,从而并入设备驱动框架统一管理。

但是在现代操作系统中,如果没有 ACPI,如何发现设备呢?主线 Linux 已经给出了答案,利用设备树 (Device Tree) 描述。

在设备树中,我们可以描述平台的特性、外设的接入情况、CPU 和 Memory 的数量,还可以携带配置参数,如命令行参数。

由于树状结构不好存储,所以 Linux 推出了一个工具 DTC (opens new window) (Device Tree Compiler),用于将设备树源码 (DTS) 编译成扁平化的设备树 (Flattened Device Tree,FDT),并且提供了一个 libfdt 库进行操作,可以进行增删改查。

本篇记录设备发现、驱动框架搭建和系统参数配置的设计和过程。关于设备树的详细解释,请参阅 Linux设备树 (opens new window)设备树的解析 LibFDT (opens new window)

# 驱动框架搭建

用 CS 的思维来考虑,设备都存在共同之处,也就可以抽象成一个基类。这里给出定义:


#define DRV_INSTALL_FUNC(V) __attribute__((constructor(V)))

typedef enum
{
    DEV_TYPE_SYS = 1,
    DEV_TYPE_CHAR = 0x80,
    DEV_TYPE_BLOCK,
} dev_type_t;

class DriverBase
{
  public:
  // Returns 1 if driver can handle this device, or 2 for cover-all driver (will stop probing for sub nodes)
    virtual int probe(const char *name, const char *compatible) = 0;
    virtual long addDevice(const void *fdt) = 0; // returns handler
    virtual void removeDevice(long handler) = 0; // unregister device by handler
    virtual dev_type_t getDeviceType() = 0;

    virtual ~DriverBase() = default;
};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

通过这样的基类,我们就可以通过 probe() 函数进行设备驱动程序枚举,并在枚举成功时调用 addDevice() 请求驱动程序添加设备,使用 removeDevice() 删除设备。

比如,考虑一个系统根设备驱动,这个设备驱动继承 DriverBase 并重写其中的虚函数:

static void drv_register();

class SysDev : public DriverBase
{
  public:
    int probe(const char *name, const char *compatible) override
    {
        std::string id = name;
        if (id == "chosen") // Node to storage cmdline arguments
            return 2;
        if (id == "" && std::string(compatible).find("riscv") != std::string::npos)
            return 1;
        return 0;
    }

    long addDevice(const void *fdt) override
    {
        return ++hdl_count;
    }
    void removeDevice(long handler) override
    {
    }
    dev_type_t getDeviceType() override
    {
        return DEV_TYPE_SYS;
    }

  private:
    static int hdl_count;
    friend void drv_register();
};

int SysDev::hdl_count = -1;

static DRV_INSTALL_FUNC(200) void drv_register()
{
    static SysDev drv;
    drv.hdl_count = 0;
    DriverManager::addDriver(drv);
    printf("Driver SysRoot installed\n");
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

这里还有一个 drv_register() 函数,用于注册驱动到系统中。

# 设备发现

无论是 OpenSBI 还是 UBoot ,都将设备树加载到内存中并进行修补,随后通过一个寄存器传递到下一级。我们的系统目前从 OpenSBI 直接跳到内核的 _start 处开始执行,根据其文档,设备树地址通过寄存器 a1 传递。所以我们可以直接从 C 环境下的第二个参数接收设备树地址,并加以解析:

long k_early_boot(const unsigned long hart_id, const void *dtb_addr, void **sys_stack_base)
{

    int ret = fdt_check_header(dtb_addr);
    if (ret != 0)
    {
        puts("[EBOOT] FDT header check failed\n");
        return -1;
    }

    ...

}
1
2
3
4
5
6
7
8
9
10
11
12
13

这里要明确一点,由于内存的描述也在设备树中,所以我们需要先于 k_cstart() 将设备树解析并正确设置系统栈。这里我们通过修改汇编代码增加一个对 k_early_boot() 的调用,在该函数中调整系统栈底的地址 (sys_stack_base),并将设备树数据全部转移到不会冲突的系统内存区域,供后续正常环境使用,随后返回到汇编中调整系统栈。这项工作稍后再讲述。

进入 k_main() 之后,我们就可以开始进行设备发现了。定义这样一个驱动管理类:

class DriverManager
{
  public:
    static void addDriver(DriverBase &drv)
    {
        _drvlist.push_back(&drv);
    }
    static void removeDriver(DriverBase &drv);

  protected:
    static int probe(const void *fdt,int node = 0);
    friend int k_main(int argc, const char *argv[]);

  private:
    static std::vector<DriverBase *> _drvlist;
    static char _depth[32];

    static int _try(const void *fdt, int node);
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

其中的 probe() 函数将递归地解析设备树,然后调用 _try() 依次调用注册的设备驱动的 probe() 函数,根据其返回值决定是否进行安装和跳过子节点解析。

到这里,我们只要在 k_main() 中调用 DriverManager::probe(),就可以完成设备发现了。

不过还有一个问题要解决,就是驱动程序要如何将自己在 k_main() 之前注册到 DriverManager 中,也就是由谁来调用 drv_register()。相信有经验的同志已经注意到上面的一个宏定义:

#define DRV_INSTALL_FUNC(V) __attribute__((constructor(V)))
1

这里的 __attribute__((constructor(V))) 是一个 GCC 支持的函数属性描述宏,指示 GCC 将函数当作全局构造器 (Global Constructor),其中的 V 是优先级,越小的越早执行。通过这个修饰,函数指针将被放入 .init_array 段中,并在 k_before_main() 被执行。

此外,我们还需保证 _drvlist 能够较早地初始化,使用下面的定义:

__attribute__((init_priority(K_PR_INIT_DRV_LIST))) std::vector<DriverBase *> DriverManager::_drvlist;
1

指示 GCC 将这个全局对象的构造函数优先级提升到 K_PR_INIT_DRV_LIST

# 系统参数配置

前文提到要检测系统内存并设置栈,这里首先修改汇编启动代码,设置临时堆栈并调用 k_early_boot()

	/* Setup boot C stack */
	lla	a3, _kernel_end
	lla	a4, _KERNEL_BOOT_STACK_SIZE /* this is fake a address provided from ld */
	add	sp, a3, a4
	lla a3, _sys_stack_base
	REG_S	sp, 0(a3)

	/* Early boot in C */
	lla	a3, _boot_a0
	REG_L	a0, 0(a3)
	lla	a3, _boot_a1
	REG_L	a1, 0(a3)
	lla a2, _sys_stack_base
	call k_early_boot

	bne a0, zero, _start_hang 

_start_warm:
    ...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

随后在 k_early_boot 中解析内存节点:

    // Detect system memory size
    ret = fdt_path_offset(dtb_addr, "/memory");
    if (ret < 0)
    {
        puts("[EBOOT] FDT memory node not found\n");
        return -3;
    }

    int regsize = 0;
    const void *memreg = fdt_getprop(dtb_addr, ret, "reg", &regsize);
    if (!memreg || regsize < 16)
    {
        puts("[EBOOT] FDT memory reg invalid\n");
        return -4;
    }

    unsigned long mem_start = be2le64(((const unsigned long *)memreg)[0]);
    unsigned long mem_len = be2le64(((const unsigned long *)memreg)[1]);

    printf("[EBOOT] Memory Range: base= 0x%lx, len= 0x%lx\n", mem_start, mem_len);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
  • 通过调试发现设备树的数据是大端序的,通过函数简单转换一下即可。

系统命令行参数也包含在设备树中,通过节点 /chosen 下的 bootargs属性提供。所以我们还可以先在这里解析了命令行参数,允许手动指定系统可用内存:

    // Get the kernel command line
    ret = fdt_path_offset(dtb_addr, "/chosen");
    if (ret < 0)
    {
        puts("[EBOOT] FDT chosen node not found\n");
        return -5;
    }
    const void *kcmdline = fdt_getprop(dtb_addr, ret, "bootargs", &regsize);
    if (!kcmdline || regsize < 1)
    {
        puts("[EBOOT] FDT bootargs invalid\n");
        return -6;
    }
    memcpy(kernel_args, kcmdline, regsize);
    puts("[EBOOT] Kernel command line: ");
    puts(kernel_args);

    // Parse Kerenl command line args for memory size (force override if specified in cmdline, only the first one is
    // used!)
    kernel_cmdargc = split_args((char *)kernel_args, kernel_args_array);
    unsigned long mem_len_cmdarg = 0;
    char mem_suffix = 'M';
    for (int i = 0; i < kernel_cmdargc; ++i)
    {
        if (strncmp(kernel_args_array[i], "-mem", 4) == 0)
        {
            if (i < kernel_cmdargc - 1)
            {
                int res = sscanf(kernel_args_array[i + 1], "%ld %c", &mem_len_cmdarg, &mem_suffix);
                if (res == 0)
                {
                    puts("[EBOOT] Invalid memory size specified, skipping\n");
                    mem_len_cmdarg = 0;
                    break;
                }
                if (res == 1)
                {
                    puts("[EBOOT] Memory size specified without suffix, assuming MB\n");
                    mem_len_cmdarg *= 1024 * 1024;
                    break;
                }
                if (res == 2)
                {
                    switch (mem_suffix)
                    {
                    case 'K':
                    case 'k':
                        mem_len_cmdarg *= 1024;
                        break;
                    case 'M':
                    case 'm':
                        mem_len_cmdarg *= 1024 * 1024;
                        break;
                    case 'G':
                    case 'g':
                        mem_len_cmdarg *= 1024 * 1024 * 1024;
                        break;
                    default:
                        puts("[EBOOT] Invalid memory size suffix, skipping\n");
                        mem_len_cmdarg = 0;
                        break;
                    }
                    break;
                }
            }
            else
            {
                puts("[EBOOT] Memory size not specified, skipping\n");
                mem_len_cmdarg = 0;
                break;
            }
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73

随后计算系统栈底:

    k_mem_size = (mem_len_cmdarg > 0) ? mem_len_cmdarg : mem_len;

    *sys_stack_base = (void *)(mem_start + k_mem_size);
    printf("[EBOOT] Set SYS_SP: 0x%lx\n", (unsigned long)*sys_stack_base);
1
2
3
4

至此,这一部分宣告结束,来一段 Log:

See More
[EBOOT] Memory Range: base= 0x80000000, len= 0x10000000
[EBOOT] Kernel command line:

[EBOOT] Set SYS_SP: 0x90000000

===== Entered Test Kernel =====
a0: 0x0
Calling init_array...
Driver SysMem installed
Driver SysRoot installed
Driver UART8250 installed
cmd args: ''
Finding driver for #0  [riscv-virtio] ... Found! Installing... OK, handler: 1
Finding driver for #100 reserved-memory [] ... Found! Installing... OK, handler: 1
Finding driver for #164 mmode_resv1@80000000 [] ... Failed
Finding driver for #236 mmode_resv0@80040000 [] ... Failed
Finding driver for #312 flash@20000000 [cfi-flash] ... Failed
Finding driver for #420 chosen [] ... Found! Installing... OK, handler: 2
Driver has cover the node with offset: 420
Finding driver for #480 uart@10000000 [ns16550a] ... Found! Installing... OK, handler: 1
Finding driver for #604 test@100000 [sifive,test1] ... Failed
Finding driver for #692 virtio_mmio@10008000 [virtio,mmio] ... Failed
Finding driver for #808 virtio_mmio@10007000 [virtio,mmio] ... Failed
Finding driver for #924 virtio_mmio@10006000 [virtio,mmio] ... Failed
Finding driver for #1040 virtio_mmio@10005000 [virtio,mmio] ... Failed
Finding driver for #1156 virtio_mmio@10004000 [virtio,mmio] ... Failed
Finding driver for #1272 virtio_mmio@10003000 [virtio,mmio] ... Failed
Finding driver for #1388 virtio_mmio@10002000 [virtio,mmio] ... Failed
Finding driver for #1504 virtio_mmio@10001000 [virtio,mmio] ... Failed
Finding driver for #1620 cpus [] ... Failed
Finding driver for #1680 cpu-map [] ... Failed
Finding driver for #1692 cluster0 [] ... Failed
Finding driver for #1708 core0 [] ... Failed
Finding driver for #1748 cpu@0 [riscv] ... Failed
Finding driver for #1900 interrupt-controller [riscv,cpu-intc] ... Failed
Finding driver for #2012 memory@80000000 [] ... Found! Installing... OK, handler: 2
Finding driver for #2084 soc [simple-bus] ... Failed
Finding driver for #2160 pci@30000000 [pci-host-ecam-generic] ... Failed
Finding driver for #2852 interrupt-controller@c000000 [riscv,plic0] ... Failed
Finding driver for #3048 clint@2000000 [riscv,clint0] ... Failed

Reached k_after_main, clearing up...
===== Test Kernel exited with 0 =====
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43