AbydOS开发日记 (3) - 基础驱动程序

3/13/2024 OSC++

# 开始写驱动吧!

上一篇我们已经搭建好了驱动框架,现在我们来实现三个基础驱动:根设备、内存、串口。

  • 注:上一篇后驱动框架有小修小补,请参见相关提交

# 根设备驱动

根设备并不是一个具体设备,而是设备树的根节点,包含了模组信息和兼容信息,可以根据它区别平台。为了方便起见,我们让根设备驱动一并处理 /chosen 节点,因为平台相关的一些额外参数可以直接这样传入。

首先定义一个基类 (顺便定义一个属性导出宏):

#define __K_PROP_EXPORT__(name, pri)                                                                                   \
    const auto &name() const                                                                                           \
    {                                                                                                                  \
        return pri;                                                                                                    \
    }

class SysRoot : public DriverBase
{
  public:
    virtual dev_type_t getDeviceType() override
    {
        return DEV_TYPE_SYS;
    }

    __K_PROP_EXPORT__(compatible, _compatible)
    __K_PROP_EXPORT__(model, _model)
    __K_PROP_EXPORT__(stdout_path, _stdout_path)

  protected:
    std::string _compatible;
    std::string _model;
    std::string _stdout_path;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

实现上,考虑提供一个通用的 fallback 驱动,在不匹配的时候也不会终止内核:

class GenericRoot : public SysRoot
{
  public:
    int probe(const char *name, const char *compatible) override
    {
        std::string id = name;
        if (id == "")
            return DRV_CAP_THIS;
        return DRV_CAP_NONE;
    }

    long addDevice(const void *fdt, int node) override
    {
        int len;
        if (node == 0) // root node
        {
            auto prop = fdt_get_property(fdt, node, "compatible", &len);
            if (!prop)
                _compatible = "Generic";
            else
                _compatible = std::string(prop->data);
            prop = fdt_get_property(fdt, node, "model", &len);
            if (!prop)
                _model = "Generic";
            else
                _model = std::string(prop->data);

            auto rc = fdt_path_offset(fdt, "/chosen"); // property of chosen node may diff on other platform
            if (rc >= 0)
            {
                auto prop = fdt_get_property(fdt, rc, "stdout-path", &len);
                if (prop)
                    _stdout_path = std::string(prop->data);
                DriverManager::markInstalled(fdt, rc, 0, this,DRV_CAP_THIS); // mark as installed

                return 0; // singleton device
            }
        }

        return K_ENODEV;
    }

    void removeDevice(long handler) override
    {
    }
};
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

可以看到,这里的 probe() 条件是名称为空,按照定义看,只有根节点没有名称。由于我们要一并接管 /chosen 节点,所以在安装时,利用传入的完整 fdt 查找到节点偏移,提取属性后向 DriverManager 报告额外安装的节点,避免再次安装。

# 内存驱动

理论上,内存是不需要驱动的,但是为了更好地管理系统上的内存,在当前架构下还是引入一个驱动。

内存驱动主要的工作如下:

  1. 获取并管理保留内存区、可用内存范围
  2. 提供接口,进行非连续内存分配(_sbrk)的底层调用

由于是系统驱动,还是定义一个基类:

class SysMem : public DriverBase
{
  public:
    dev_type_t getDeviceType() override
    {
        return DEV_TYPE_SYS;
    }

    virtual void addReservedMem(uint64_t addr,uint64_t size) = 0;

    __K_PROP_EXPORT__(reservedMem, _mem_rsv);
    __K_PROP_EXPORT__(availableMem, _mem_avail);

    protected:
        std::vector<std::pair<size_t,size_t>> _mem_rsv;
        std::vector<std::pair<size_t,size_t>> _mem_avail;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

然后提供一个通用实现:

See More
class GenericMem : public SysMem
{
  public:
    int probe(const char *name, const char *compatible) override
    {
        std::string id = name;
        if (id.find("memory") != 0 && id != "reserved-memory")
            return DRV_CAP_NONE;
        return DRV_CAP_COVER;
    }

    long addDevice(const void *fdt, int node) override
    {

        uint64_t address, size;
        if (!_fdt_rsv_mem_parsed)
        {
            // fdt command reserved memory
            int n = fdt_num_mem_rsv(fdt);
            for (int i = 0; i < n; ++i)
            {
                auto rc = fdt_get_mem_rsv(fdt, i, &address, &size);
                if (rc == 0)
                {
                    _mem_rsv.push_back(std::make_pair(address, size));
                    printf("(0x%lx - 0x%lx) ", address, address + size);
                }
            }
            _fdt_rsv_mem_parsed = true;

            // Some mark should be done?
        }

        auto name = fdt_get_name(fdt, node, NULL);
        if (name == std::string("reserved-memory"))
        {

            /* process reserved-memory node */
            auto nodeoffset = fdt_subnode_offset(fdt, 0, "reserved-memory");
            if (nodeoffset >= 0)
            {
                auto subnode = fdt_first_subnode(fdt, nodeoffset);
                while (subnode >= 0)
                {

                    if (fdt_get_node_addr_size(fdt, subnode, 0, &address, &size) >= 0)
                    { // each subnode should have only one addr and size
                        _mem_rsv.push_back(std::make_pair(address, size));
                        printf("(0x%lx - 0x%lx) ", address, address + size);
                    }
                    else
                    {
                        printf("Failed to get reserved memory in #%d\n", subnode);
                    }
                    subnode = fdt_next_subnode(fdt, subnode);
                }
            }
        }
        else
        { // memory node, could have multiple
            for (int i = 0; fdt_get_node_addr_size(fdt, node, i, &address, &size) >= 0; ++i)
            {
                _mem_avail.push_back(std::make_pair(address, size));
                printf("(0x%lx - 0x%lx) ", address, address + size);
            }
        }
        return 0; // Also singleton
    }

    void removeDevice(long handler) override
    {
    }

    void addReservedMem(uint64_t addr, uint64_t size) override
    {
        _mem_rsv.push_back(std::make_pair(addr, size));
    }

  private:
    bool _fdt_rsv_mem_parsed = false;
};
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
74
75
76
77
78
79
80
81

这里要考虑的问题在于,内存驱动在设备树上有多个设备与之对应,当存在平台特定驱动的时候,如果不作任何处理,那就容易导致不同的内存区可 probe() 到的驱动不一致,导致数据分散在多个驱动中。这时候,系统根设备就出场了,它总是能先于其他设备进行 probe(),并且知道兼容信息,我们只需要在系统根设备驱动 probe() 的时候,顺手将不兼容平台的驱动先行删除,保证其后不被 probe() 到。

# 串口驱动

接下来就是及其硬件的一个简单驱动了,串口驱动。在我们的系统上,参考 Linux 的设计,外设抽象成两种,即块设备和字符设备。这里首先继承 DriverChar:

class Drv_Uart8250 : public DriverChar
{

  private:
    struct uart8250_t
    {
        char *base;
        uint32_t freq;
        uint32_t baud;
        uint32_t reg_width;
        uint32_t reg_shift;
        // uint32_t reg_offset;

        bool opened = false;
    };

    const uint32_t default_freq = 0;
    const uint32_t default_baud = 115200;
    const uint32_t default_reg_shift = 0;
    const uint32_t default_reg_width = 1;
    const uint32_t default_reg_offset = 0;

    int hdl_count = -1;
    std::map<long, uart8250_t> hdl;
};
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

这里定义一个结构体存储数据。当然,也可以使用外部类的方法,更符合 C++ 的思想。

然后参照 openSBI 的实现,将几个 fdt_helper 函数以及 riscv_io 部分内容 cv 过来,然后定义两个辅助函数:

    static u32 get_reg(const uart8250_t &u, u32 num)
    {
        u32 offset = num << u.reg_shift;

        if (u.reg_width == 1)
            return readb(u.base + offset);
        else if (u.reg_width == 2)
            return readw(u.base + offset);
        else
            return readl(u.base + offset);
    }

    static void set_reg(const uart8250_t &u, u32 num, u32 val)
    {
        u32 offset = num << u.reg_shift;

        if (u.reg_width == 1)
            writeb(val, u.base + offset);
        else if (u.reg_width == 2)
            writew(val, u.base + offset);
        else
            writel(val, u.base + offset);
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

这两个函数非常简单,就是进行寄存器的读写。后面的所以步骤都利用这两个函数进行 IO。

接着实现设备实例化功能:

long addDevice(const void *fdt, int node) override
    {
        uart8250_t uart;
        uint64_t regsize;
        int len, rc;
        const fdt32_t *val;
        rc = fdt_get_node_addr_size(fdt, node, 0, (uint64_t *)&uart.base, &regsize);

        if (rc < 0 || !uart.base || !regsize)
            return K_ENODEV;

        val = (fdt32_t *)fdt_getprop(fdt, node, "clock-frequency", &len);
        if (len > 0 && val)
            uart.freq = fdt32_to_cpu(*val);
        else
            uart.freq = default_freq;

        val = (fdt32_t *)fdt_getprop(fdt, node, "current-speed", &len);
        if (len > 0 && val)
            uart.baud = fdt32_to_cpu(*val);
        else
            uart.baud = default_baud;

        val = (fdt32_t *)fdt_getprop(fdt, node, "reg-shift", &len);
        if (len > 0 && val)
            uart.reg_shift = fdt32_to_cpu(*val);
        else
            uart.reg_shift = default_reg_shift;

        val = (fdt32_t *)fdt_getprop(fdt, node, "reg-io-width", &len);
        if (len > 0 && val)
            uart.reg_width = fdt32_to_cpu(*val);
        else
            uart.reg_width = default_reg_width;

        val = (fdt32_t *)fdt_getprop(fdt, node, "reg-offset", &len);
        if (len > 0 && val)
            uart.base += fdt32_to_cpu(*val);
        else
            uart.base += default_reg_offset;

        u16 bdiv = 0;

        if (uart.baud)
            bdiv = (uart.freq + 8 * uart.baud) / (16 * uart.baud);

        /* Disable all interrupts */
        set_reg(uart, UART_IER_OFFSET, 0x00);
        /* Enable DLAB */
        set_reg(uart, UART_LCR_OFFSET, 0x80);

        if (bdiv)
        {
            /* Set divisor low byte */
            set_reg(uart, UART_DLL_OFFSET, bdiv & 0xff);
            /* Set divisor high byte */
            set_reg(uart, UART_DLM_OFFSET, (bdiv >> 8) & 0xff);
        }

        /* 8 bits, no parity, one stop bit */
        set_reg(uart, UART_LCR_OFFSET, 0x03);
        /* Enable FIFO */
        set_reg(uart, UART_FCR_OFFSET, 0x01);
        /* No modem control DTR RTS */
        set_reg(uart, UART_MCR_OFFSET, 0x00);
        /* Clear line status */
        get_reg(uart, UART_LSR_OFFSET);
        /* Read receive buffer */
        get_reg(uart, UART_RBR_OFFSET);
        /* Set scratchpad */
        set_reg(uart, UART_SCR_OFFSET, 0x00);

        hdl.insert(std::pair<long, uart8250_t>(++hdl_count, uart));

        // Test send here
        write(hdl_count, "TEST UART8250\n", 14);

        return hdl_count;
    }
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
74
75
76
77
78
79

关于各个寄存器操作的解释,可以参阅 UART 16650D Datasheet (opens new window)

实例化解决,把剩下的虚函数补完:

See More
   int probe(const char *name, const char *compatible) override
    {
        std::string id = name;
        if (id.find("uart") == std::string::npos)
            return DRV_CAP_NONE;
        id = compatible;
        return (id == "ns16550" || id == "ns16550a" || id == "snps,dw-apb-uart") ? DRV_CAP_THIS : DRV_CAP_NONE;
    }

    int open(long handler) override
    {

        auto u = hdl.find(handler);
        if (u == hdl.end())
            return K_ENODEV;

        auto &uart = u->second;
        if (uart.opened)
            return K_EALREADY;

        // lock here?
        uart.opened = true;

        printf("UART8250 opened\n");
        return 0;
    }

    int close(long handler) override
    {
        auto u = hdl.find(handler);
        if (u == hdl.end())
            return K_ENODEV;

        auto &uart = u->second;
        if (!uart.opened)
            return K_EALREADY;

        // lock here?
        uart.opened = false;

        printf("UART8250 closed\n");
        return 0;
    }

    int read(long handler, void *buf, int len) override
    {
        auto u = hdl.find(handler);
        if (u == hdl.end())
            return K_ENODEV;

        auto &uart = u->second;

        for (int i = 0; i < len; ++i)
        {
            if (get_reg(uart, UART_LSR_OFFSET) & UART_LSR_DR)
                ((char *)buf)[i] = get_reg(uart, UART_RBR_OFFSET);
            else
                return i;
        }

        // printf("UART8250 read\n");
        return len;
    }

    int write(long handler, const void *buf, int len) override
    {
        auto u = hdl.find(handler);
        if (u == hdl.end())
            return K_ENODEV;

        auto &uart = u->second;

        for (int i = 0; i < len; ++i)
        {
            while ((get_reg(uart, UART_LSR_OFFSET) & UART_LSR_THRE) == 0)
                ;

            set_reg(uart, UART_THR_OFFSET, ((const char *)buf)[i]);
        }

        // printf("UART8250 write\n");
        return 0;
    }

    int ioctl(long handler, int cmd, void *arg) override
    {
        printf("UART8250 ioctl -- not supported\n");
        return 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
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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89

当然这里的 open() 和 close() 也是雏形,没有实质的意义。

编译运行,设备安装的时候就能打印出 "TEST UART8250" 了!

# 后记

这个驱动移植花了两个多小时,真是不容易。实现了各个资源的管理,下一步就可以考虑 MMU 相关的操作了。

  • 实际上,设备的安装目前还分成了两步,先安装系统设备,再安装外设。这是一点小小的更改,以适应启动流程的需要。