设备驱动畅想

简介: 设备驱动畅想

一、设备驱动程序

1、介绍

设备驱动程序的简介

Essential Linux Device Drivers <==> 精通Linux驱动开发

2、字符设备驱动基础

字符设备驱动基础1——简单的驱动源代码分析

字符设备驱动基础2——用开发板来调试驱动的步骤

字符设备驱动基础3——使用register_chrdev()函数注册字符设备

字符设备驱动基础4——读写接口的操作实践

字符设备驱动基础5——驱动如何操控硬件

3、字符设备驱动高级篇

字符设备驱动高级篇1——注册字符设备驱动的新接口

字符设备驱动高级篇2——注册字符设备驱动的函数代码分析

字符设备驱动高级篇3——自动创建设备文件

字符设备驱动高级篇4——自动创建设备文件的函数代码分析

字符设备驱动高级篇5——静态映射表的建立过程,动态映射结构体方式操作寄存器

字符设备驱动高级篇6——内核提供的读写寄存器接口

4、设备驱动框架

设备驱动框架1——LED驱动框架的分析(核心层)

设备驱动框架2——基于驱动框架写LED驱动(具体操作层)

设备驱动框架3——使用gpiolib完成LED驱动

设备驱动框架4——将驱动集成到内核中

Linux设备驱动模型1——设备驱动模型的简介与底层架构

Linux设备驱动模型2——总线式设备驱动组织方式(总线、设备、驱动、类等结构体)

Linux设备驱动模型3——平台总线的工作原理

Linux设备驱动模型4——基于平台总线的LED驱动实践

5、misc类设备驱动

misc类设备驱动1——板载蜂鸣器驱动测试

misc类设备驱动2——misc类设备的简介

misc类设备驱动3——misc驱动框架源码分析(核心层+具体操作层)

6、framebuffer驱动详解

framebuffer驱动详解0——framebuffer的简介

framebuffer驱动详解2——fb驱动框架分析(核心层)

framebuffer驱动详解3——fb驱动分析(具体操作层)

7、input子系统详解

input子系统详解1——input子系统简介

input子系统详解2——应用层代码实践

input子系统详解3——input子系统框架核心层分析

input子系统详解4——输入事件驱动层源码分析

input子系统详解5——参考驱动模板编写按键驱动

8、I2C子系统详解

I2C子系统详解1——I2C总线设备的驱动框架

I2C子系统详解2——I2C核心层源码分析

I2C子系统详解3——I2C总线驱动层代码分析

I2C子系统详解4——I2C设备驱动层代码分析

9、块设备驱动

块设备驱动介绍

10、网络设备驱动

网络设备驱动介绍

11、实践

Linux字符设备驱动剖析

Linux总线设备驱动模型

Linux设备文件的创建和mdev

file_operations结构体

平台设备与平台驱动的注册

platform驱动开发套路、DM9000的一些分析

应用层为何不能设置分辨率

中断的上下半部

设备驱动,字符设备驱动、(总线)设备驱动模型、sysfs文件系统、平台设备驱动

二、字符设备驱动示例

Skipping BTF generation xxx. due to unavailability of vmlinux on Ubuntu 21.04

apt install dwarves

cp /sys/kernel/btf/vmlinux /usr/lib/modules/uname -r/build/

Skipping BTF generation xxx. due to unavailability of vmlinux on Ubuntu 21.04

linux UIO驱动实践

1、自动创建设备节点

Makefile

obj-m := hello.o              # 要生成的模块名      
# hello-objs:= a.o b.o          # 生成这个模块名所需要的目标文件

KDIR := /lib/modules/`uname -r`/build
# KDIR := /home/liuqz/learnLinux/linux-4.15.18
PWD := $(shell pwd)

default:
  make -C $(KDIR) M=$(PWD) modules

clean:
  make -C $(KDIR) M=$(PWD) clean
  rm -rf *.o *.o.cmd *.ko *.mod.c .tmp_versions modules.order Module.symvers
  rm -rf .cache.mk .*ko.cmd .*.mod.o.cmd .*.o.cmd

hello.c

// hello.c
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/init.h>  // __init   __exit
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/leds.h>
#include <linux/miscdevice.h>
#include <linux/module.h>  // module_init  module_exit
#include <linux/platform_device.h>
#include <linux/proc_fs.h>
#include <linux/slab.h>

#define MYMAJOR 190  // 注意通过cat /proc/devices查看这个200主设备号是否被占用
#define MYCNT 1      // 表示次设备号只有一个
#define MYNAME "tc"  // 表示设备或者说驱动(混在一起的)的名字

static dev_t hello_dev;
static struct cdev hello_cdev;
static struct class *hello_class;

static const struct file_operations efi_rtc_fops = {
    .owner = THIS_MODULE,
};

static int __init hello_init(void) {
  // 使用新的cdev接口来注册字符设备驱动,需要2步
  // 第1步:注册主次设备号
  // MYMAJOR在这里是200,这里合成了要申请的设备号,即200,0
  hello_dev = MKDEV(MYMAJOR, 0);
  int retval = register_chrdev_region(hello_dev, MYCNT,
                                      MYNAME);  // 这里申请刚才合成的设备号
  if (retval) {
    printk(KERN_ERR "Unable to register minors for %s\n", MYNAME);
    return -EINVAL;
  }
  printk(KERN_INFO "major = %d, minor = %d.\n", MAJOR(hello_dev),
         MINOR(hello_dev));
  printk(KERN_INFO "register_chrdev_region success\n");

  // 第2步:注册字符设备驱动
  cdev_init(&hello_cdev, &efi_rtc_fops);  // 这步其实可以用其他代码来代替
  retval = cdev_add(&hello_cdev, hello_dev, MYCNT);  // 注册字符设备驱动
  if (retval) {
    printk(KERN_ERR "Unable to cdev_add\n");
    return -EINVAL;
  }
  printk(KERN_INFO "cdev_add success\n");

  // 注册字符设备驱动完成后,添加设备类的操作,以让内核帮我们发信息!!
  // 给udev,让udev自动创建和删除设备文件

  hello_class = class_create(THIS_MODULE, "tcls");
  // 该函数将创建sys/class/tcls目录
  // 此目录中会有以设备文件名命名的文件夹

  if (IS_ERR(hello_class)) return -EINVAL;
  // 最后1个参数字符串,就是我们将来要在/dev目录下创建的设备文件的名字
  // 所以我们这里要的文件名是/dev/tc
  device_create(hello_class, NULL, hello_dev, NULL, "tc");

  return 0;
}

static void __exit hello_exit(void) {
  device_destroy(hello_class, hello_dev);
  class_destroy(hello_class);

  cdev_del(&hello_cdev);
  // 第二步去注销申请的主次设备号
  unregister_chrdev_region(hello_dev, MYCNT);
}

module_init(hello_init);
module_exit(hello_exit);

// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL");  // 描述模块的许可证
// MODULE_AUTHOR("aston");        // 描述模块的作者
// MODULE_DESCRIPTION("module hello");  // 描述模块的介绍信息
// MODULE_ALIAS("alias hello1");      // 描述模块的别名信息

2、misc

驱动

// misc_t.c
#include <linux/fs.h>
#include <linux/init.h>  // __init   __exit
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/leds.h>
#include <linux/miscdevice.h>
#include <linux/module.h>  // module_init  module_exit
#include <linux/platform_device.h>
#include <linux/proc_fs.h>
#include <linux/slab.h>

#define EFI_RTC_VERSION "0.4"
#define EFI_RTC_MINOR 255 /* EFI Time services */

static const struct file_operations efi_rtc_fops = {
    .owner = THIS_MODULE,
};

static struct miscdevice efi_rtc_dev = {EFI_RTC_MINOR, "efirtc", &efi_rtc_fops};

static int __init misc_t_init(void) {
  int ret;
  struct proc_dir_entry *dir;

  printk(KERN_INFO "EFI Time Services Driver v%s\n", EFI_RTC_VERSION);

  ret = misc_register(&efi_rtc_dev);
  if (ret) {
    printk(KERN_ERR "efirtc: can't misc_register on minor=%d\n", EFI_RTC_MINOR);
    return ret;
  }

  return 0;
}

static void __exit misc_t_exit(void) { misc_deregister(&efi_rtc_dev); }

module_init(misc_t_init);
module_exit(misc_t_exit);

// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL");                     // 描述模块的许可证
MODULE_AUTHOR("xjh <735503242@qq.com>");   // 描述模块的作者
MODULE_DESCRIPTION("s5pv210 led driver");  // 描述模块的介绍信息
MODULE_ALIAS("s5pv210_led");               // 描述模块的别名信息

加载模块

# /dev 下自动创建了 efirtc 设备节点
ll /dev/efirtc
crw------- 1 root root 10, 123 10月 16 16:15 /dev/efirtc

# /sys/class/misc 创建了 efirtc
ll /sys/class/misc/efirtc/
总计 0
drwxr-xr-x  3 root root    0 10月 16 16:15 ./
drwxr-xr-x 21 root root    0 10月 11 00:30 ../
-r--r--r--  1 root root 4096 10月 16 16:16 dev
drwxr-xr-x  2 root root    0 10月 16 16:16 power/
lrwxrwxrwx  1 root root    0 10月 16 16:16 subsystem -> ../../../../class/misc/
-rw-r--r--  1 root root 4096 10月 16 16:15 uevent

原理

  1. misc_init
static int __init misc_init(void)
{
  int err;

#ifdef CONFIG_PROC_FS
  proc_create("misc", 0, NULL, &misc_proc_fops);
#endif
  misc_class = class_create(THIS_MODULE, "misc");
  err = PTR_ERR(misc_class);
  if (IS_ERR(misc_class))
    goto fail_remove;

  err = -EIO;
  if (register_chrdev(MISC_MAJOR,"misc",&misc_fops))
    goto fail_printk;
  misc_class->devnode = misc_devnode;
  return 0;

fail_printk:
  printk("unable to get major %d for misc devices\n", MISC_MAJOR);
  class_destroy(misc_class);
fail_remove:
  remove_proc_entry("misc", NULL);
  return err;
}
subsys_initcall(misc_init);
  • 创建了/sys/class/misc
  • 注册了 misc 和 misc_fops
  1. misc_open
static int misc_open(struct inode * inode, struct file * file)
{
  int minor = iminor(inode);
  struct miscdevice *c;
  int err = -ENODEV;
  const struct file_operations *old_fops, *new_fops = NULL;

  mutex_lock(&misc_mtx);
  
  list_for_each_entry(c, &misc_list, list) {
    if (c->minor == minor) {
      new_fops = fops_get(c->fops);   
      break;
    }
  }
    
  if (!new_fops) {
    mutex_unlock(&misc_mtx);
    request_module("char-major-%d-%d", MISC_MAJOR, minor);
    mutex_lock(&misc_mtx);

    list_for_each_entry(c, &misc_list, list) {
      if (c->minor == minor) {
        new_fops = fops_get(c->fops);
        break;
      }
    }
    if (!new_fops)
      goto fail;
  }

  err = 0;
  old_fops = file->f_op;
  file->f_op = new_fops;
  if (file->f_op->open) {
    err=file->f_op->open(inode,file);
    if (err) {
      fops_put(file->f_op);
      file->f_op = fops_get(old_fops);
    }
  }
  fops_put(old_fops);
fail:
  mutex_unlock(&misc_mtx);
  return err;
}

static const struct file_operations misc_fops = {
  .owner    = THIS_MODULE,
  .open   = misc_open,
};
  • 打开设备节点时,先调用 misc_open
  • misc_open 中遍历 misc_list, 找到 minor 相同的项,然后调用各个具体的 fops
  1. misc_register
int misc_register(struct miscdevice * misc)
{
  struct miscdevice *c;
  dev_t dev;
  int err = 0;

  INIT_LIST_HEAD(&misc->list);

  mutex_lock(&misc_mtx);
  list_for_each_entry(c, &misc_list, list) {
    if (c->minor == misc->minor) {
      mutex_unlock(&misc_mtx);
      return -EBUSY;
    }
  }

  if (misc->minor == MISC_DYNAMIC_MINOR) {
    int i = find_first_zero_bit(misc_minors, DYNAMIC_MINORS);
    if (i >= DYNAMIC_MINORS) {
      mutex_unlock(&misc_mtx);
      return -EBUSY;
    }
    misc->minor = DYNAMIC_MINORS - i - 1;
    set_bit(i, misc_minors);
  }

  dev = MKDEV(MISC_MAJOR, misc->minor);

  misc->this_device = device_create(misc_class, misc->parent, dev,
            misc, "%s", misc->name);
  if (IS_ERR(misc->this_device)) {
    int i = DYNAMIC_MINORS - misc->minor - 1;
    if (i < DYNAMIC_MINORS && i >= 0)
      clear_bit(i, misc_minors);
    err = PTR_ERR(misc->this_device);
    goto out;
  }

  /*
   * Add it to the front, so that later devices can "override"
   * earlier defaults
   */
  list_add(&misc->list, &misc_list);
 out:
  mutex_unlock(&misc_mtx);
  return err;
}
  • miscdevice 加入到 misc_list 中
  • device_create 创建 /sys 目录下的文件

3、platform

(1)platform_driver_register

驱动
// led_driver.c
#include <linux/fs.h>
#include <linux/init.h>  // __init   __exit
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/leds.h>
#include <linux/module.h>  // module_init  module_exit
#include <linux/platform_device.h>
#include <linux/slab.h>

#define X210_LED_OFF 1  // X210中LED是正极接电源,负极节GPIO
#define X210_LED_ON 0   // 所以1是灭,0是亮

struct s5pv210_led_platdata {
  unsigned int     gpio;
  unsigned int     flags;

  char      *name;
  char      *def_trigger;
};

struct s5pv210_gpio_led {
  struct led_classdev cdev;
  struct s5pv210_led_platdata *pdata;
};

static inline struct s5pv210_gpio_led *pdev_to_gpio(
    struct platform_device *dev) {
  return platform_get_drvdata(dev);
}

static inline struct s5pv210_gpio_led *to_gpio(struct led_classdev *led_cdev) {
  return container_of(led_cdev, struct s5pv210_gpio_led, cdev);
}

// 这个函数就是要去完成具体的硬件读写任务的
static void s5pv210_led_set(struct led_classdev *led_cdev,
                            enum led_brightness value) {
  struct s5pv210_gpio_led *p = to_gpio(led_cdev);

  printk(KERN_INFO "s5pv210_led_set\n");

  // 在这里根据用户设置的值来操作硬件
  // 用户设置的值就是value
  // if (value == LED_OFF) {
  //   // 用户给了个0,希望LED灭
  //   gpio_set_value(p->pdata->gpio, X210_LED_OFF);
  // } else {
  //   // 用户给的是非0,希望LED亮
  //   gpio_set_value(p->pdata->gpio, X210_LED_ON);
  // }
}

static int s5pv210_led_probe(struct platform_device *dev) {
  // 用户insmod安装驱动模块时会调用该函数
  // 该函数的主要任务就是去使用led驱动框架提供的设备注册函数来注册一个设备
  int ret = -1;
  struct s5pv210_led_platdata *pdata = dev->dev.platform_data;
  struct s5pv210_gpio_led *led;

  printk(KERN_INFO "----s5pv210_led_probe---\n");

  led = kzalloc(sizeof(struct s5pv210_gpio_led), GFP_KERNEL);
  if (led == NULL) {
    dev_err(&dev->dev, "No memory for device\n");
    return -ENOMEM;
  }

  platform_set_drvdata(dev, led);

  // 在这里去申请驱动用到的各种资源,当前驱动中就是GPIO资源
  // if (gpio_request(pdata->gpio, pdata->name)) {
  //   printk(KERN_ERR "gpio_request failed\n");
  // } else {
  //   // 设置为输出模式,并且默认输出1让LED灯灭
  //   // gpio_direction_output(pdata->gpio, 1);
  // }

  // led1
  led->cdev.name = pdata->name;
  led->cdev.brightness = 0;
  led->cdev.brightness_set = s5pv210_led_set;
  led->pdata = pdata;

  ret = led_classdev_register(&dev->dev, &led->cdev);
  if (ret < 0) {
    printk(KERN_ERR "led_classdev_register failed\n");
    return ret;
  }

  return 0;
}

static int s5pv210_led_remove(struct platform_device *dev) {
  struct s5pv210_gpio_led *p = pdev_to_gpio(dev);

  led_classdev_unregister(&p->cdev);
  kfree(p);  // kfee放在最后一步

  return 0;
}

static struct platform_driver s5pv210_led_driver = {
    .probe = s5pv210_led_probe,
    .remove = s5pv210_led_remove,
    .driver =
        {
            .name = "s5pv210_led",
            .owner = THIS_MODULE,
        },
};

static int __init s5pv210_led_init(void) {
  return platform_driver_register(&s5pv210_led_driver);
}

static void __exit s5pv210_led_exit(void) {
  platform_driver_unregister(&s5pv210_led_driver);
}

module_init(s5pv210_led_init);
module_exit(s5pv210_led_exit);

// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL");                     // 描述模块的许可证
MODULE_AUTHOR("xjh <735503242@qq.com>");   // 描述模块的作者
MODULE_DESCRIPTION("s5pv210 led driver");  // 描述模块的介绍信息
MODULE_ALIAS("s5pv210_led");               // 描述模块的别名信息
加载模块
sudo insmod led_driver.ko

~$ ls /sys/bus/platform/drivers/s5pv210_led/ -l
总计 0
--w------- 1 root root 4096 10月 18 09:47 bind
lrwxrwxrwx 1 root root    0 10月 18 09:47 module -> ../../../../module/led_driver
--w------- 1 root root 4096 10月 18 09:47 uevent
--w------- 1 root root 4096 10月 18 09:47 unbind
原理
// drivers/base/platform.c
/**
 * platform_driver_register - register a driver for platform-level devices
 * @drv: platform driver structure
 */
int platform_driver_register(struct platform_driver *drv)
{
  drv->driver.bus = &platform_bus_type;
  if (drv->probe)
    drv->driver.probe = platform_drv_probe;
  if (drv->remove)
    drv->driver.remove = platform_drv_remove;
  if (drv->shutdown)
    drv->driver.shutdown = platform_drv_shutdown;

  return driver_register(&drv->driver);
}
// drivers/base/driver.c
int driver_register(struct device_driver *drv)
{
  int ret;
  struct device_driver *other;

  BUG_ON(!drv->bus->p);

  if ((drv->bus->probe && drv->probe) ||
      (drv->bus->remove && drv->remove) ||
      (drv->bus->shutdown && drv->shutdown))
    printk(KERN_WARNING "Driver '%s' needs updating - please use "
      "bus_type methods\n", drv->name);

  other = driver_find(drv->name, drv->bus);
  if (other) {
    put_driver(other);
    printk(KERN_ERR "Error: Driver '%s' is already registered, "
      "aborting...\n", drv->name);
    return -EBUSY;
  }

  ret = bus_add_driver(drv);
  if (ret)
    return ret;
  ret = driver_add_groups(drv, drv->groups);
  if (ret)
    bus_remove_driver(drv);
  return ret;
}
// drivers/leds/led-class.c
/**
 * led_classdev_register - register a new object of led_classdev class.
 * @parent: The device to register.
 * @led_cdev: the led_classdev structure for this device.
 */
int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
{
  led_cdev->dev = device_create(leds_class, parent, 0, led_cdev,
              "%s", led_cdev->name);
  if (IS_ERR(led_cdev->dev))
    return PTR_ERR(led_cdev->dev);

#ifdef CONFIG_LEDS_TRIGGERS
  init_rwsem(&led_cdev->trigger_lock);
#endif
  /* add to the list of leds */
  down_write(&leds_list_lock);
  list_add_tail(&led_cdev->node, &leds_list);
  up_write(&leds_list_lock);

  if (!led_cdev->max_brightness)
    led_cdev->max_brightness = LED_FULL;

  led_update_brightness(led_cdev);

#ifdef CONFIG_LEDS_TRIGGERS
  led_trigger_set_default(led_cdev);
#endif

  printk(KERN_DEBUG "Registered led device: %s\n",
      led_cdev->name);

  return 0;
}

static int __init leds_init(void)
{
  leds_class = class_create(THIS_MODULE, "leds");
  if (IS_ERR(leds_class))
    return PTR_ERR(leds_class);
  leds_class->suspend = led_suspend;
  leds_class->resume = led_resume;
  leds_class->dev_attrs = led_class_attrs;
  return 0;
}

(2)platform_device_register

驱动
// led_device.c
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/init.h>  // __init   __exit
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/leds.h>
#include <linux/miscdevice.h>
#include <linux/module.h>  // module_init  module_exit
#include <linux/platform_device.h>
#include <linux/proc_fs.h>
#include <linux/slab.h>

struct s5pv210_led_platdata {
  unsigned int gpio;
  unsigned int flags;

  char *name;
  char *def_trigger;
};

/* LEDS */
static struct s5pv210_led_platdata x210_led1_pdata = {
    .name = "led1",
    // .gpio = S5PV210_GPJ0(3),  // 这个要根据数据手册得知
    // .flags = S5PV210_LEDF_ACTLOW | S5PV210_LEDF_TRISTATE,
    .def_trigger = "heartbeat",  // 上面以及这个都是私有的扩展数据
};

static struct platform_device x210_led1 = {
    .name = "s5pv210_led",  // 注意这里的设备名字要与驱动的名字一致才能匹配
    .id = 1,  // 这个id最终影响为s5pv210_led.1的后缀
    .dev = {
        .platform_data = &x210_led1_pdata,
    }};

static struct platform_device *ab3100_platform_devs[] = {
    &x210_led1,
    // &ab3100_power_device,     &ab3100_regulators_device,
    // &ab3100_sim_device,       &ab3100_uart_device,
    // &ab3100_rtc_device,       &ab3100_charger_device,
    // &ab3100_boost_device,     &ab3100_adc_device,
    // &ab3100_fuelgauge_device, &ab3100_vibrator_device,
    // &ab3100_otp_device,       &ab3100_codec_device,
};

static int __init hello_init(void) {
  /* Register the platform devices */
  platform_add_devices(ab3100_platform_devs, ARRAY_SIZE(ab3100_platform_devs));

  return 0;
}

static void __exit hello_exit(void) {
  int i;
  for (i = 0; i < ARRAY_SIZE(ab3100_platform_devs); i++)
    platform_device_unregister(ab3100_platform_devs[i]);
}

module_init(hello_init);
module_exit(hello_exit);

// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL");  // 描述模块的许可证
// MODULE_AUTHOR("aston");        // 描述模块的作者
// MODULE_DESCRIPTION("module hello");  // 描述模块的介绍信息
// MODULE_ALIAS("alias hello1");      // 描述模块的别名信息


加载模块
sudo insmod led_device.ko

~$ ls /sys/bus/platform/devices/s5pv210_led.1/ -l
总计 0
-rw-r--r-- 1 root root 4096 10月 18 10:11 driver_override
-r--r--r-- 1 root root 4096 10月 18 10:11 modalias
drwxr-xr-x 2 root root    0 10月 18 10:11 power
lrwxrwxrwx 1 root root    0 10月 18 10:11 subsystem -> ../../../bus/platform
-rw-r--r-- 1 root root 4096 10月 18 10:11 uevent
原理
// drivers/base/platform.c
/**
 * platform_add_devices - add a numbers of platform devices
 * @devs: array of platform devices to add
 * @num: number of platform devices in array
 */
int platform_add_devices(struct platform_device **devs, int num)
{
  int i, ret = 0;

  for (i = 0; i < num; i++) {
    ret = platform_device_register(devs[i]);
    if (ret) {
      while (--i >= 0)
        platform_device_unregister(devs[i]);
      break;
    }
  }

  return ret;
}
// drivers/base/platform.c
/**
 * platform_device_register - add a platform-level device
 * @pdev: platform device we're adding
 */
int platform_device_register(struct platform_device *pdev)
{
  device_initialize(&pdev->dev);
  return platform_device_add(pdev);
}

(3)驱动和设备都加载

  1. 只加载设备
~$ ls /sys/bus/platform/devices/s5pv210_led.1/ -l
总计 0
-rw-r--r-- 1 root root 4096 10月 18 10:19 driver_override
-r--r--r-- 1 root root 4096 10月 18 10:19 modalias
drwxr-xr-x 2 root root    0 10月 18 10:19 power
lrwxrwxrwx 1 root root    0 10月 18 10:19 subsystem -> ../../../bus/platform
-rw-r--r-- 1 root root 4096 10月 18 10:19 uevent
  1. 加载设备和驱动后
~$ ls /sys/bus/platform/devices/s5pv210_led.1/ -l
总计 0
lrwxrwxrwx 1 root root    0 10月 18 10:20 driver -> ../../../bus/platform/drivers/s5pv210_led
-rw-r--r-- 1 root root 4096 10月 18 10:19 driver_override
drwxr-xr-x 3 root root    0 10月 18 10:20 leds
-r--r--r-- 1 root root 4096 10月 18 10:19 modalias
drwxr-xr-x 2 root root    0 10月 18 10:19 power
lrwxrwxrwx 1 root root    0 10月 18 10:19 subsystem -> ../../../bus/platform
-rw-r--r-- 1 root root 4096 10月 18 10:19 uevent


相比,增加了driver 和 leds 目录。

$ ls /sys/bus/platform/devices/s5pv210_led.1/leds
led1
$ ll /sys/bus/platform/devices/s5pv210_led.1/leds/led1/
总计 0
drwxr-xr-x 3 root root    0 10月 18 10:20 ./
drwxr-xr-x 3 root root    0 10月 18 10:20 ../
-rw-r--r-- 1 root root 4096 10月 18 10:22 brightness
lrwxrwxrwx 1 root root    0 10月 18 10:22 device -> ../../../s5pv210_led.1/
-r--r--r-- 1 root root 4096 10月 18 10:22 max_brightness
drwxr-xr-x 2 root root    0 10月 18 10:22 power/
lrwxrwxrwx 1 root root    0 10月 18 10:22 subsystem -> ../../../../../class/leds/
-rw-r--r-- 1 root root    0 10月 18 10:22 trigger
-rw-r--r-- 1 root root 4096 10月 18 10:20 uevent

Linux设备驱动模型4——基于平台总线的LED驱动实践

平台设备与平台驱动的注册

(4)改生成 /dev 下节点

// led_driver.c
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/init.h>  // __init   __exit
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/leds.h>
#include <linux/module.h>  // module_init  module_exit
#include <linux/platform_device.h>
#include <linux/slab.h>

#define X210_LED_OFF 1  // X210中LED是正极接电源,负极节GPIO
#define X210_LED_ON 0   // 所以1是灭,0是亮

static struct class *leds_class;

struct s5pv210_led_platdata {
  unsigned int gpio;
  unsigned int flags;

  char *name;
  char *def_trigger;

  dev_t dev_num;
  struct cdev led_cdev;
};

struct s5pv210_gpio_led {
  struct led_classdev cdev;
  struct s5pv210_led_platdata *pdata;
};

static inline struct s5pv210_gpio_led *pdev_to_gpio(
    struct platform_device *dev) {
  return platform_get_drvdata(dev);
}

static inline struct s5pv210_gpio_led *to_gpio(struct led_classdev *led_cdev) {
  return container_of(led_cdev, struct s5pv210_gpio_led, cdev);
}

// 这个函数就是要去完成具体的硬件读写任务的
static void s5pv210_led_set(struct led_classdev *led_cdev,
                            enum led_brightness value) {
  // struct s5pv210_gpio_led *p = to_gpio(led_cdev);

  printk(KERN_INFO "s5pv210_led_set\n");

  // 在这里根据用户设置的值来操作硬件
  // 用户设置的值就是value
  // if (value == LED_OFF) {
  //   // 用户给了个0,希望LED灭
  //   gpio_set_value(p->pdata->gpio, X210_LED_OFF);
  // } else {
  //   // 用户给的是非0,希望LED亮
  //   gpio_set_value(p->pdata->gpio, X210_LED_ON);
  // }
}

static const struct file_operations leds_fops = {
    .owner = THIS_MODULE,
};

static int s5pv210_led_probe(struct platform_device *dev) {
  // 用户insmod安装驱动模块时会调用该函数
  // 该函数的主要任务就是去使用led驱动框架提供的设备注册函数来注册一个设备
  int ret = -1;
  struct s5pv210_led_platdata *pdata = dev->dev.platform_data;
  struct s5pv210_gpio_led *led;

  printk(KERN_INFO "----s5pv210_led_probe---\n");

  led = kzalloc(sizeof(struct s5pv210_gpio_led), GFP_KERNEL);
  if (led == NULL) {
    dev_err(&dev->dev, "No memory for device\n");
    return -ENOMEM;
  }

  platform_set_drvdata(dev, led);

  // 在这里去申请驱动用到的各种资源,当前驱动中就是GPIO资源
  // if (gpio_request(pdata->gpio, pdata->name)) {
  //   printk(KERN_ERR "gpio_request failed\n");
  // } else {
  //   // 设置为输出模式,并且默认输出1让LED灯灭
  //   // gpio_direction_output(pdata->gpio, 1);
  // }

  // led1
  led->cdev.name = pdata->name;
  led->cdev.brightness = 0;
  led->cdev.brightness_set = s5pv210_led_set;
  led->pdata = pdata;

  cdev_init(&led->pdata->led_cdev, &leds_fops);  // 这步其实可以用其他代码来代替
  ret = cdev_add(&led->pdata->led_cdev, led->pdata->dev_num,
                 1);  // 注册字符设备驱动
  if (ret) {
    printk(KERN_ERR "Unable to cdev_add\n");
    return -EINVAL;
  }
  printk(KERN_INFO "cdev_add success\n");

  // ret = led_classdev_register(&dev->dev, &led->cdev);
  led->cdev.dev = device_create(leds_class, &dev->dev, led->pdata->dev_num,
                                &led->pdata->led_cdev, "%s", led->pdata->name);
  if (ret < 0) {
    printk(KERN_ERR "led_classdev_register failed\n");
    return ret;
  }

  return 0;
}

static int s5pv210_led_remove(struct platform_device *dev) {
  struct s5pv210_gpio_led *p = pdev_to_gpio(dev);

  // led_classdev_unregister(&p->cdev);
  device_unregister(p->cdev.dev);
  kfree(p);  // kfee放在最后一步

  return 0;
}

static struct platform_driver s5pv210_led_driver = {
    .probe = s5pv210_led_probe,
    .remove = s5pv210_led_remove,
    .driver =
        {
            .name = "s5pv210_led",
            .owner = THIS_MODULE,
        },
};

static int __init s5pv210_led_init(void) {
  leds_class = class_create(THIS_MODULE, "my_leds");
  return platform_driver_register(&s5pv210_led_driver);
}

static void __exit s5pv210_led_exit(void) {
  platform_driver_unregister(&s5pv210_led_driver);
  class_destroy(leds_class);
}

module_init(s5pv210_led_init);
module_exit(s5pv210_led_exit);

// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL");                     // 描述模块的许可证
MODULE_AUTHOR("xjh <735503242@qq.com>");   // 描述模块的作者
MODULE_DESCRIPTION("s5pv210 led driver");  // 描述模块的介绍信息
MODULE_ALIAS("s5pv210_led");               // 描述模块的别名信息
// led_device.c
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/init.h>  // __init   __exit
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/leds.h>
#include <linux/miscdevice.h>
#include <linux/module.h>  // module_init  module_exit
#include <linux/platform_device.h>
#include <linux/proc_fs.h>
#include <linux/slab.h>
#include <linux/cdev.h>

struct s5pv210_led_platdata {
  unsigned int gpio;
  unsigned int flags;

  char *name;
  char *def_trigger;

  dev_t dev_num;
  struct cdev led_cdev;
};

/* LEDS */
static struct s5pv210_led_platdata x210_led1_pdata = {
    .name = "led1",
    // .gpio = S5PV210_GPJ0(3),  // 这个要根据数据手册得知
    // .flags = S5PV210_LEDF_ACTLOW | S5PV210_LEDF_TRISTATE,
    .def_trigger = "heartbeat",  // 上面以及这个都是私有的扩展数据
    .dev_num = MKDEV(190, 0),
};

static struct platform_device x210_led1 = {
    .name = "s5pv210_led",  // 注意这里的设备名字要与驱动的名字一致才能匹配
    .id = 1,  // 这个id最终影响为s5pv210_led.1的后缀
    .dev = {
        .platform_data = &x210_led1_pdata,
    }};

static struct platform_device *ab3100_platform_devs[] = {
    &x210_led1,
    // &ab3100_power_device,     &ab3100_regulators_device,
    // &ab3100_sim_device,       &ab3100_uart_device,
    // &ab3100_rtc_device,       &ab3100_charger_device,
    // &ab3100_boost_device,     &ab3100_adc_device,
    // &ab3100_fuelgauge_device, &ab3100_vibrator_device,
    // &ab3100_otp_device,       &ab3100_codec_device,
};

static int __init hello_init(void) {
  /* Register the platform devices */
  platform_add_devices(ab3100_platform_devs, ARRAY_SIZE(ab3100_platform_devs));

  return 0;
}

static void __exit hello_exit(void) {
  int i;
  for (i = 0; i < ARRAY_SIZE(ab3100_platform_devs); i++)
    platform_device_unregister(ab3100_platform_devs[i]);
}

module_init(hello_init);
module_exit(hello_exit);

// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL");  // 描述模块的许可证
// MODULE_AUTHOR("aston");        // 描述模块的作者
// MODULE_DESCRIPTION("module hello");  // 描述模块的介绍信息
// MODULE_ALIAS("alias hello1");      // 描述模块的别名信息

三、内核函数

浅谈ioremap,vmalloc,mmap三者之间的实现&区别

内存之ioremap内存映射

remap_pfn_range使用详解

Linux内核机制总结内存管理之内存映射(十一)

IORESOURCE_IO和IORESOURCE_MEM


目录
相关文章
|
传感器 网络协议 物联网
华为鸿蒙OS尖刀武器之分布式软总线技术
华为鸿蒙OS尖刀武器之分布式软总线技术
华为鸿蒙OS尖刀武器之分布式软总线技术
|
16天前
设备驱动基础设施 【ChatGPT】
设备驱动基础设施 【ChatGPT】
|
4月前
|
JSON Linux C语言
内核雏形
内核雏形
40 0
|
传感器 Linux 芯片
那些只有芯片原厂才能做的驱动开发工作
那些只有芯片原厂才能做的驱动开发工作
|
存储 编译器 C语言
聊聊身边的嵌入式,工控大脑PLC
聊聊身边的嵌入式,工控大脑PLC
|
存储 Ubuntu Linux
荔枝派Zero(全志V3S)驱动开发之hello驱动程序
字符设备驱动是占用篇幅最大的一类驱动,因为字符设备最多,从最简单的点灯到 I2C、SPI、音频等都属于字符设备驱动的类型。
195 1
|
物联网 数据处理
嵌入式系统与硬件设计:连接物联世界的智慧之源
本篇深入研究了物联网中嵌入式系统与硬件设计的关键内容。我们探讨了嵌入式系统的概述,介绍了微控制器与嵌入式开发板在物联网应用中的应用,以及硬件设计的基本原则和接口。通过Arduino示例代码,读者可以了解如何使用嵌入式开发板控制LED灯。设计原则和硬件接口部分帮助读者更好地理解硬件设计的关键考虑因素,包括电路设计、电源管理和接口选择。通过本篇内容,读者将更加了解嵌入式系统与硬件设计在物联网中的作用,为创造智能化的嵌入式应用提供了有益的指导。
157 0
|
Linux
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(二十九)驱动进化之路:总线设备驱动模型
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(二十九)驱动进化之路:总线设备驱动模型
165 0
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(二十九)驱动进化之路:总线设备驱动模型
|
存储 传感器
一张图看懂嵌入式系统组成
一张图看懂嵌入式系统组成
一张图看懂嵌入式系统组成