第九章 理一理驱动程序的编写-9.6-13driver注册-chrdev创建-节点获取及配置

9.6.platform_driver_register是怎么进行驱动注册的?     设备树需要uboot传递给内核,参考设备树专题的视频,大体的流程是uboot把设备树编译成的.dtb文件的地址传递给内核,内核提取.dtb的根...

9.6.platform_driver_register是怎么进行驱动注册的?

    设备树需要uboot传递给内核,参考设备树专题的视频,大体的流程是uboot把设备树编译成的.dtb文件的地址传递给内核,内核提取.dtb的根节点的compatible属性来匹配machine_desc,解析chosen节点、memory节点、#address-cells和#size-cells属性,为设备树分配内存。再去遍历整个.dts文件的节点将其转换成device_nodes并将properties填充到各个nodes,最终构建出device-tree。再根据compatible的转换规定将某些device_nodes转化成platform_devices,而不能转化成device_nodes的节点内核提供专门的处理函数。到这里设备树到内核的转换过程已经清楚了,回到leddrv.c中。先沿着leddrv.c的执行过程一路看下去。

    下午看了2篇连续的博客,写的很好!但是看的很是晕乎!自己把函数调用过程过了一遍就清楚了,整理出来一张执行过程。

    有篇博文写的很好记录下:https://blog.csdn.net/Richard_LiuJH/article/details/45825333

attachments-2020-05-pSiK0C4U5ec0e2dad35d0.png

 上图也解决了问题7_probe函数的形参是谁给的?

9.7.gpiod_get函数是怎么找到设备树中的led节点?

在probe函数中主要做了4件事:

   a.从leds_my节点中获取led-gpios属性;

   b.注册file_operations结构体,创建主设备;

   c.创建设备类;

   d.创建次设备;

   目前有1个相关文档:Linux-4.9.88-Docs-gpio-consumer.txt,依次看下程序是如何执行的?先追踪下gpiod_get()的执行过程:

gpiod_get()
--->gpiod_get_index()
----->of_find_gpio()
------->of_get_named_gpiod_flags()
--------->of_parse_phandle_with_args()
--------->of_find_gpiochip_by_xlate()
----------->gpiochip_find()
------------->list_for_each_entry()
--------->of_xlate_and_get_gpiod_flags()
----------->chip->of_xlate()  //of_xlate回调函数
----------->gpiochip_get_desc() //Get the GPIO descriptor corresponding to the given hw number for this chip.

 参考博文:https://blog.csdn.net/Guet_Kite/article/details/82263003通过回调函数leds-gpios中的3个参数提取出来。

static int sunxi_pinctrl_gpio_of_xlate(struct gpio_chip *gc,
				const struct of_phandle_args *gpiospec,
				u32 *flags)
{
	int pin, base;
 
	base = PINS_PER_BANK * gpiospec->args[0];
	pin = base + gpiospec->args[1];
 
	if (pin > gc->ngpio)
		return -EINVAL;
 
	if (flags)
		*flags = gpiospec->args[2];
 
	return pin;
}

  通过上面的一路调用可以将定义的leds-gpios信息提取出来。哪怎么去修改gpio呢?

9.8.gpiod_direction_output是怎么实现gpio初始化的?

    在led_drv_open()中先设置了gpiod_direction_output(),具体代码执行过程不分析了,需要知道它的作用就是把gpio设置为输出引脚就可以了。参考:https://blog.csdn.net/u012830148/article/details/80513693

    设置为输出之后,就是设置输出值了。

9.9.gpiod_set_value是怎么实现设置的gpio的?

__gpio_set_value
    -->gpiod_set_raw_value
        -->_gpiod_set_raw_value
            -->chip->set(chip, gpio_chip_hwgpio(desc), value);//调用set回调函数

参考:https://blog.csdn.net/Guet_Kite/article/details/82263003

static void sunxi_pinctrl_gpio_set(struct gpio_chip *chip,
unsigned offset, int value)
{
struct sunxi_pinctrl *pctl = gpiochip_get_data(chip);
u32 reg = sunxi_data_reg(offset);
u8 index = sunxi_data_offset(offset);
unsigned long flags;
u32 regval;
 
spin_lock_irqsave(&pctl->lock, flags);
 
regval = readl(pctl->membase + reg);
 
if (value)
regval |= BIT(index);
else
regval &= ~(BIT(index));
 
writel(regval, pctl->membase + reg);
 
spin_unlock_irqrestore(&pctl->lock, flags);
}

 里面基本就是:先读取GPIO寄存器的值,修改值,再写回去!

   gpio_direction_output vs gpio_set_value之间的使用关系?参考:https://blog.csdn.net/fu_shuwu/article/details/6123333

gpio_set_value(port_num,0/1)一般只是在这个GPIO口的寄存器上写上某个值,至于这个端口是否设置为输出,它就管不了!而gpio_direction_output (port_num,0/1),在某个GPIO口写上某个值之后,还会把这个端口设置为输出模式。因此,有人也许就会建议,把gpio_set_value这个函数直接去掉不用,是否可以,显然是可以的。
     但是为什么系统还要用呢?我个人分析是,系统开发人员在要结合这两者来使用,以便提高效率。一般某个端口设置好了输入与输出模式后,最好不要经常变动。首先要调用gpio_direction_output(),以后要设置高低电平时,直接使用gpio_set_value()就可以了,这样可以省却再次调用设置输出模式的操作,从而提高运行效率!

9.10.register_chrdev怎么把100ask_led注册到内核中的?

    probe函数中获取了.dts中的gpio之后进行register_chrdev,为什么要去注册字符串设备呢???设备和gpio是什么关系?在这个100sak_led中调用gpio资源去完成一些操作?

register_chrdev()
---->__register_chrdev(); //create and register a cdev occupying a range of minors
------>__register_chrdev_region(major, baseminor, count, name); //Register a single major with a specified minor range.
---------->kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);
---------->major_to_index(major);
------>cdev = cdev_alloc(); //allocate a cdev structure
------>cdev_add(cdev, MKDEV(cd->major, baseminor), count); //add a char device to the system

 有一篇很好的博文可以参考下,https://blog.csdn.net/lizuobin2/article/details/52695533###,就是申请主次设备号,申请cdev结构体,添加到内核中。

9.11为什么要创建设备类class?class_create是怎么在内核中创建的class?

    创建了chrdev之后为什么还要创建class_create?是不是便于管理?先看下class_create的过程:

class_create();
---->__class_create(); //create a struct class structure
    {
        Kzalloc();
        cls->name = name;
        cls->owner = owner;
        __class_register();
        {
            Kzalloc();
            add_class_attrs()
            {
                class_get();
                class_create_file();
            }
        class_put();
       }
    }

 参考:https://www.cnblogs.com/LxwEmbedded/p/4854714.html

9.12.用register_chrdev要创建主设备,device_create创建次设备,怎么创建的?

    内核同时提供了class_create(…) 函数,可以用它来创建一个类,这个类存放于sysfs下面,一旦创建好了这个类,再调用device_create(…)函数来在/dev目录下创建相应的设备节点。这样,加载模块的时候,用户空间中的udev会自动响应device_create(…)函数,去/sysfs下寻找对应的类从而创建设备节点。参考: https://blog.csdn.net/lizuobin2/article/details/52695533

device_create - creates a device and registers it with sysfs
{
    device_create_vargs()
    {
        device_create_groups_vargs()
        {
           Kzalloc();
           device_initialize();
 
           dev->devt = devt;
      dev->class = class;
      dev->parent = parent;
      dev->groups = groups;
      
          dev_set_drvdata();
         kobject_set_name_vargs();
         device_add();
         put_device();
        }
    }    
}

 register_chrdev-->class_create--->device_create,

之前写的字符类设备驱动,没有自动创建设备节点,因为只使用了register_chrdev()函数,只是注册了这个设备。然后在系统启动后,就要自己创建设备节点mknod,这样虽然是可行的,但是比较麻烦。于是想在__init函数里面,自动创建设备节点。经过查阅资料,发现创建设备节点使用了两个函数 class_create()和class_device_create(),当然在__exit()函数里,要使用class_destory()和class_device_desotry()注销创建的设备节点!参考:https://www.jianshu.com/p/a99c278fc6e4

9.13.假如有2个led1/2呢?怎么去区分led0和led1的?

   led1-gpio = <>;

   led2-gpio = <>;

   使用gpiod_get()分别获取!居然就这么快速的结束了好吧!

   前面信誓旦旦要写出好的博文,哈,发现自己那点水平读内核代码太吃力了,只有去借鉴别人写的博文了。期间参考了好多大牛的博文,不一一列写了,该加的参考链接都贴出来了,向他们学习!加油!


1 条评论&回复

请先 登录 后评论
zxq
zxq

11 篇文章

作家榜 »

  1. 百问网-周老师 18 文章
  2. st_ashang 14 文章
  3. 渐进 12 文章
  4. zxq 11 文章
  5. helloworld 8 文章
  6. 星星之火 6 文章
  7. 谢工 5 文章
  8. Litchi_Zheng 5 文章