文章目录
1. design
前面两篇文章,属于在国庆期间胡乱 copy 而成,基本是把内核代码 copy 出来随便改改,还是随便练习一下,写个简单的架构。
练习一下如何自定义设备树,自定义属性,获取这些属性并使用他们。
就结合前面两篇,定义一个总设备,和两类子设备。
- 第一类子设备:btn + led ,使用 button 控制 led
- 第二类子设备:only btn,只有 button,按下后打印信息
1.1 dts
在 arch/arm/boot/dts/exynos4412-xhr-elite.dts
中添加设备节点。共 5 个子设备,前两个为第一类,其他为第二类。
btn-led
该属性表明是第一类,only-btn
表示第二类led-name
led 描述,btn-name
button 描述led-gpios
led 使用的 GPIO,btn-gpios
button 使用的 GPIO
xhr-test-node {
compatible = "xhr-test-dev";
dev1 {
btn-led;
led-name = "red:system";
btn-name = "GPIO Key Home";
led-gpios = <&gpl2 0 GPIO_ACTIVE_HIGH>;
btn-gpios = <&gpx1 1 GPIO_ACTIVE_LOW>;
};
dev2 {
btn-led;
led-name = "red:user";
btn-name = "GPIO Key Back";
led-gpios = <&gpk1 1 GPIO_ACTIVE_HIGH>;
btn-gpios = <&gpx1 2 GPIO_ACTIVE_LOW>;
};
dev3 {
only-btn;
btn-name = "GPIO Key Sleep";
btn-gpios = <&gpx3 3 GPIO_ACTIVE_LOW>;
};
dev4 {
only-btn;
btn-name = "GPIO Key Vol+";
btn-gpios = <&gpx2 1 GPIO_ACTIVE_LOW>;
};
dev5 {
only-btn;
btn-name = "GPIO Key Vol-";
btn-gpios = <&gpx2 0 GPIO_ACTIVE_LOW>;
};
};
1.2 files
testdrv.c
主要是支持 linux kernel 的架构与实现两类子设备的抽象,不做具体的设备实现,另外两个 .c
分别对两类设备进行具体实现。
.
├── class // 子设备
│ ├── btn_led.c // 第一类子设备
│ └── only_btn.c // 第二类子设备
├── include
│ ├── class_btn_led.h // 第一类子设备头文件
│ ├── class_only_btn.h // 第二类子设备头文件
│ └── xhrdev.h // 抽象类头文件
├── Makefile
└── testdrv.c // 抽象类
2 directories, 7 files
1.3 Makefile
将三个源文件编译进去就可以了,最后编译出 xhrdev.ko
。
PWD := $(shell pwd)
INCLUDE := -I$(PWD)/include
KDIR := /home/xhr/iTop4412/xhr4412/linux/xhr4412-linux-5.8.5
MODULE := xhrdev
ALLFLAGS := $(INCLUDE)
obj-m := $(MODULE).o
all:
$(MAKE) -C $(KDIR) EXTRA_CFLAGS=$(ALLFLAGS) M=$(PWD) modules
.PHONY:clean
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
help:
$(MAKE) -C $(KDIR) M=$(PWD) help
$(MODULE)-objs += testdrv.o
$(MODULE)-objs += class/only_btn.o \
class/btn_led.o
2. implementation
具体实现就比较简单了,每个模块只干自己的事就好了。
2.1 struct
几个头文件中重要的数据结构:
struct subdev_ops
抽象类的操作函数集,每一类子设备操作集不同struct cls_btn_led_dev
第一类子设备struct cls_only_btn_dev
第二类子设备
struct subdev_ops {
int (*const parse_dts)(struct device* dev,
struct fwnode_handle *child, void *__res);
int (*const probe)(struct device* dev, void *__res);
void (*const remove)(struct device* dev, void *__res);
};
struct cls_btn_led_dev {
struct gpio_desc *led_gpiod;
const char *led_desc;
const char *btn_desc;
int btn_irq;
int led_state;
int count;
};
struct cls_only_btn_dev {
struct gpio_desc *gpiod;
const char *desc;
int gpio;
int irq;
int count;
};
2.2 testdrv.c
主要实现 Linux 设备驱动总线模型的 code,并且对所有设备进行抽象。
#include <linux/module.h>
#include <linux/init.h>
#include <class_btn_led.h>
#include <class_only_btn.h>
#define xprint(fmt, args...) printk("[xhr4412]\033[1;31m[%s][%d] " fmt "\033[0m \n", __func__, __LINE__, ##args)
enum dev_type {
BTN_LED = 0,
ONLY_BTN,
MAX_TYPE,
};
struct subdevdata {
enum dev_type type;
const struct subdev_ops *ops;
union {
struct cls_only_btn_dev ob;
struct cls_btn_led_dev bl;
} dev;
};
struct xhrdev_data {
int nsubdev;
struct device *dev;
struct platform_device *platdev;
struct subdevdata subdev[];
};
static struct xhrdev_data* xhrdev_get_devtree_data(struct device *dev)
{
struct fwnode_handle *child;
struct xhrdev_data *xdev;
struct subdevdata *subdev;
int count = device_get_child_node_count(dev);
int ret;
xprint("count = %d size=%d", count, struct_size(xdev, subdev, count));
xprint("size(xhrdev_data)=%d size(subdevdata)=%d",
sizeof(struct xhrdev_data), sizeof(struct subdevdata));
xdev = devm_kzalloc(dev, struct_size(xdev, subdev, count), GFP_KERNEL);
if (!xdev) {
xprint("no memory !");
return NULL;
}
device_for_each_child_node(dev, child) {
subdev = &xdev->subdev[xdev->nsubdev++];
if (fwnode_property_present(child, "btn-led")) {
subdev->type = BTN_LED;
subdev->ops = bl_dev_ops;
} else if (fwnode_property_present(child, "only-btn")) {
subdev->type = ONLY_BTN;
subdev->ops = ob_dev_ops;
} else {
xprint("error: no valid property");
goto _error;
}
ret = subdev->ops->parse_dts(dev, child, &subdev->dev);
if (ret) {
xprint("xdev->nsubdev(%d) fail", xdev->nsubdev);
goto _error;
}
}
return xdev;
_error:
devm_kfree(dev, xdev);
return NULL;
}
static int xhrdev_probe(struct platform_device * pdev)
{
struct device *dev = &pdev->dev;
struct xhrdev_data *xdev = dev_get_platdata(dev);
struct subdevdata *subdev;
int i, ret;
if (xdev) {
xprint("platdata is not NULL xdev = %p", xdev);
return -EINVAL;
}
xdev = xhrdev_get_devtree_data(dev);
if (!xdev) {
xprint("can't get valid devtree data");
return -EINVAL;
}
xdev->dev = dev;
xdev->platdev = pdev;
for (i = 0; i < xdev->nsubdev; i++) {
subdev = &xdev->subdev[i];
ret = subdev->ops->probe(dev, &subdev->dev);
if (ret) {
xprint("xdev->subdev[%d] probe fail: ret = %d", i, ret);
goto _error_probe;
}
}
platform_set_drvdata(pdev, xdev);
return 0;
_error_probe:
for (i--; i >= 0; i--) {
subdev = &xdev->subdev[i];
subdev->ops->remove(dev, subdev);
}
return ret;
}
static int xhrdev_remove(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct xhrdev_data *xdev;
int i;
xdev = platform_get_drvdata(pdev);
for (i = 0; i < xdev->nsubdev; i++) {
xdev->subdev[i].ops->remove(dev, &xdev->subdev[i]);
}
return 0;
}
static const struct of_device_id xhrdev_of_match[] = {
{ .compatible = "xhr-test-dev", },
{ },
};
MODULE_DEVICE_TABLE(of, xhrdev_of_match);
static struct platform_driver xhrdev_platform_driver = {
.probe = xhrdev_probe,
.remove = xhrdev_remove,
.driver = {
.name = "xhr-test-node", // match dts node name
.of_match_table = xhrdev_of_match,
}
};
static int __init xhrdev_init(void)
{
return platform_driver_register(&xhrdev_platform_driver);
}
static void __exit xhrdev_exit(void)
{
platform_driver_unregister(&xhrdev_platform_driver);
}
late_initcall(xhrdev_init);
module_exit(xhrdev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("xhr <xhr@xhr.com>");
MODULE_DESCRIPTION("xhr4412 driver for xhr-test-dev");
MODULE_ALIAS("xhr platform:xhr-test-node");
2.3 btn_led.c
#include <linux/of.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <class_btn_led.h>
#define xprint(fmt, args...) printk("[xhr4412]\033[1;31m[%s][%d] " fmt "\033[0m \n", __func__, __LINE__, ##args)
static irqreturn_t bl_dev_irq_handler(int irqnum, void *data)
{
struct cls_btn_led_dev *bldev = data;
int ret;
bldev->count++;
bldev->led_state = !bldev->led_state;
ret = gpiod_direction_output(bldev->led_gpiod, bldev->led_state);
if (unlikely(ret)) {
xprint("gpiod_direction_output fail, %s(%d) ret = %d\n",
bldev->led_desc, bldev->led_state, ret);
}
return IRQ_HANDLED;
}
static int bl_dev_probe(struct device* dev, void *__res)
{
struct cls_btn_led_dev *bldev = __res;
int ret;
ret = gpiod_direction_output(bldev->led_gpiod, bldev->led_state);
if (ret) {
xprint("gpiod_direction_output fail, led_state(%d) ret = %d\n",
bldev->led_state, ret);
return ret;
}
return devm_request_irq(dev, bldev->btn_irq, bl_dev_irq_handler,
IRQF_TRIGGER_FALLING, bldev->btn_desc, bldev);
}
static void bl_dev_remove(struct device* dev, void *__res)
{
}
static int bl_dev_parse_dts(struct device* dev, struct fwnode_handle *child, void *__res)
{
struct cls_btn_led_dev *bldev = __res;
struct gpio_desc *btn_gpiod;
if (!bldev || !dev || !child) {
xprint("input paras mustn't be NULL dev=%p child=%p bldev=%p",
dev, child, bldev);
return -EINVAL;
}
if (!fwnode_property_present(child, "btn-led")) {
xprint("no <btn-led> property");
return -EINVAL;
}
bldev->led_gpiod = devm_fwnode_get_gpiod_from_child(dev, "led", child, GPIOD_ASIS, NULL);
if (IS_ERR_OR_NULL(bldev->led_gpiod)) {
xprint("can't get bldev->led_gpiod");
return -EINVAL;
}
btn_gpiod = devm_fwnode_get_gpiod_from_child(dev, "btn", child, GPIOD_ASIS, NULL);
if (IS_ERR_OR_NULL(btn_gpiod)) {
xprint("can't get btn_gpiod");
return -EINVAL;
}
bldev->btn_irq = gpio_to_irq(desc_to_gpio(btn_gpiod));
fwnode_property_read_string(child, "btn-name", &bldev->btn_desc);
fwnode_property_read_string(child, "led-name", &bldev->led_desc);
xprint("BL: led_desc=%s btn_irq=%d", bldev->led_desc, bldev->btn_irq);
return 0;
}
static const struct subdev_ops _bl_dev_ops = {
.parse_dts = bl_dev_parse_dts,
.probe = bl_dev_probe,
.remove = bl_dev_remove,
};
const struct subdev_ops *const bl_dev_ops = &_bl_dev_ops;
2.4 only_btn.c
#include <linux/of.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <class_only_btn.h>
#define xprint(fmt, args...) printk("[xhr4412]\033[1;31m[%s][%d] " fmt "\033[0m \n", __func__, __LINE__, ##args)
static irqreturn_t ob_dev_irq_handler(int irqnum, void *data)
{
struct cls_only_btn_dev *obdev = data;
xprint("btn-desc=%s count=%d", obdev->desc, obdev->count++);
return IRQ_HANDLED;
}
static int ob_dev_probe(struct device* dev, void *__res)
{
struct cls_only_btn_dev *obdev = __res;
return devm_request_irq(dev, obdev->irq, ob_dev_irq_handler,
IRQF_TRIGGER_FALLING, obdev->desc, obdev);
}
static void ob_dev_remove(struct device* dev, void *__res)
{
}
static int ob_dev_parse_dts(struct device* dev, struct fwnode_handle *child, void *__res)
{
struct cls_only_btn_dev *obdev = __res;
if (!obdev || !dev || !child) {
xprint("input paras mustn't be NULL dev=%p child=%p obdev=%p",
dev, child, obdev);
return -EINVAL;
}
if (!fwnode_property_present(child, "only-btn")) {
xprint("no <only-btn> property");
return -EINVAL;
}
obdev->gpiod = devm_fwnode_get_gpiod_from_child(dev, "btn", child, GPIOD_ASIS, NULL);
if (IS_ERR_OR_NULL(obdev->gpiod)) {
xprint("can't get gpiod");
return -EINVAL;
}
obdev->gpio = desc_to_gpio(obdev->gpiod);
obdev->irq = gpio_to_irq(obdev->gpio);
fwnode_property_read_string(child, "btn-name", &obdev->desc);
xprint("OB: gpiod=%p gpio=%d irq=%d desc=%s",
obdev->gpiod, obdev->gpio, obdev->irq, obdev->desc);
return 0;
}
static const struct subdev_ops _ob_dev_ops = {
.parse_dts = ob_dev_parse_dts,
.probe = ob_dev_probe,
.remove = ob_dev_remove,
};
const struct subdev_ops *const ob_dev_ops = &_ob_dev_ops;
3. test
3.1 insmod & probe
3.2 button
btn_led
的设备只改变 led,没有 log。
4. issue
4.1 unexpected IRQ
unexpected IRQ trap at vector 00
红字 log 疯狂刷屏,其实不是 bug,这是由于我的 uboot 把 home
按键使能了中断。uboot 跳转到 linux 后并不会初始化关闭所有中断,所以当按下这个按键时,就会出现 “unexpected IRQ”,因为 linux 并没有分配该中断的中断号,linux 认为并不应该发生这个中断。
解决办法是将 uboot 中的 enable_home_btn_irq()
函数去掉,或是跳转到 linux 前关闭这个中断,或者在注册这个中断前不要手欠按这个 button,注册成功后就没有问题了。