AbydOS开发日记 (3) - 基础驱动程序
开始写驱动吧!
上一篇我们已经搭建好了驱动框架,现在我们来实现三个基础驱动:根设备、内存、串口。
- 注:上一篇后驱动框架有小修小补,请参见相关提交
根设备驱动
根设备并不是一个具体设备,而是设备树的根节点,包含了模组信息和兼容信息,可以根据它区别平台。为了方便起见,我们让根设备驱动一并处理 /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;
};
实现上,考虑提供一个通用的 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
{
}
};
可以看到,这里的 probe()
条件是名称为空,按照定义看,只有根节点没有名称。由于我们要一并接管 /chosen
节点,所以在安装时,利用传入的完整 fdt 查找到节点偏移,提取属性后向 DriverManager
报告额外安装的节点,避免再次安装。
内存驱动
理论上,内存是不需要驱动的,但是为了更好地管理系统上的内存,在当前架构下还是引入一个驱动。
内存驱动主要的工作如下:
- 获取并管理保留内存区、可用内存范围
- 提供接口,进行非连续内存分配(_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;
};
然后提供一个通用实现:
详情
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;
};
这里要考虑的问题在于,内存驱动在设备树上有多个设备与之对应,当存在平台特定驱动的时候,如果不作任何处理,那就容易导致不同的内存区可 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;
};
这里定义一个结构体存储数据。当然,也可以使用外部类的方法,更符合 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);
}
这两个函数非常简单,就是进行寄存器的读写。后面的所以步骤都利用这两个函数进行 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, ®size);
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;
}
关于各个寄存器操作的解释,可以参阅 UART 16650D Datasheet。
实例化解决,把剩下的虚函数补完:
详情
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;
}
当然这里的 open() 和 close() 也是雏形,没有实质的意义。
编译运行,设备安装的时候就能打印出 "TEST UART8250" 了!
后记
这个驱动移植花了两个多小时,真是不容易。实现了各个资源的管理,下一步就可以考虑 MMU 相关的操作了。
- 实际上,设备的安装目前还分成了两步,先安装系统设备,再安装外设。这是一点小小的更改,以适应启动流程的需要。