首页 > 其他分享 >设备驱动畅想

设备驱动畅想

时间:2023-10-26 11:01:29浏览次数:26  
标签:led s5pv210 misc platform device 驱动 root 畅想 设备


设备驱动畅想_linux


文章目录

  • 一、设备驱动程序
  • 二、字符设备驱动示例
  • 1、自动创建设备节点
  • Makefile
  • hello.c
  • 2、misc
  • 驱动
  • 加载模块
  • 原理
  • 3、platform
  • (1)platform_driver_register
  • 驱动
  • 加载模块
  • 原理
  • (2)platform_device_register
  • 驱动
  • 加载模块
  • 原理
  • (3)驱动和设备都加载
  • (4)改生成 /dev 下节点
  • 三、内核函数


一、设备驱动程序

1、介绍

设备驱动程序的简介


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设备驱动层代码分析


二、字符设备驱动示例

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.04linux 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 <[email protected]>");   // 描述模块的作者
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 <[email protected]>");   // 描述模块的作者
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 <[email protected]>");   // 描述模块的作者
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

   

标签:led,s5pv210,misc,platform,device,驱动,root,畅想,设备
From: https://blog.51cto.com/u_15930680/8031033

相关文章

  • 揭秘计算机奇迹:探索I/O设备的神秘世界!
    引言在之前的章节中,我们详细讲解了计算机系统中一些核心组成部分,如中央处理器(CPU)、内存、硬盘等。这些组件负责处理和存储数据,使得计算机能够有效地运行。然而,除了这些核心组件,计算机系统还包含许多其他重要的部分,其中之一就是输入输出设备。它们使得计算机能够与用户进行有效的......
  • 转个文件过滤驱动的东东,总结的比较全面
    1> IFS 流程图a.生成一个控制设备.当然此前你必须给控制设置指定名称.b.设置Dispatch Functions. c.设置Fast Io Functions. d.编写一个my_fs_notify回调函数,在其中绑定刚激活的FS CDO. e.使用wdff_reg_notify调用注册这个回调函数。f.编写默认的dispatch functions. g......
  • 接口自动化--postman(7)批量运行测试用例,Postman参数化和数据驱动
    批量运行测试用例Postman参数化和数据驱动参数化:把数据用参数来代替,从而进行测试的过程。参数化是实现数据驱动测试的前置技术数据驱动:把测试数据和测试脚本分离,用数据来驱动测试用例的执行。简单的说,就是一条数据对应一条测试用例。  Postman实现数据驱动--支持的......
  • 防静电/防干扰面包机LCD段码驱动芯片 VK1C21A/B/C/D/DA/E/EA显示效果好,静电耐压高等优
    产品型号:VK1C21A/B产品品牌:永嘉微电/VINKA封装形式:SSOP48/LQFP48可定制裸片:DICE(COB邦定片);COG(邦定玻璃用)产品年份:新年份原厂,工程服务,技术支持! 概述:VK1C21A/B是一个点阵式存储映射的LCD驱动器,可支持最大128点(32SEGx4COM)的LCD屏,也支持2COM和3COM的LCD屏。单片机可通过......
  • 智慧矿山:带式运输机空载识别与防止设备空转的惊人策略!
    带式运输机作为一种常见的物料输送设备,广泛应用于矿山、建筑、化工等行业。但在使用过程中,经常会出现空载运行的情况,即带式运输机无物料传送时仍不停工作,导致能源和设备的浪费。因此,对带式运输机进行空载识别并采取相应措施防止设备空转,具有重要的意义。带式运输机空载识别是指通过......
  • 全志V3S嵌入式驱动开发(基于usb otg的spi-nor镜像烧入)
    全志V3S嵌入式驱动开发(基于usbotg的spi-nor镜像烧入)2023/10/2418:27:22【声明:版权所有,欢迎转载,请勿用于商业用途。联系信箱:[email protected]】     说到了用usbotg来实现spinandflash的烧入,这中间主要用到了PhoenixSuit软件。那么怎么用usbotg来实现......
  • 关于Windows打印机驱动相关问题-如何利用Java(或其他)调用打印机驱动程序完成原始文件翻
    前面这些都是问题描述,问题在偏下面场景:用户电脑上安装了PCL驱动,可通过驱动完成打印。需求:现在需要提供一种脱离PC端完成文件上传并打印的功能。让用户使用手机或pc未安装驱动时都能打印文件。目前思路:首先上传文件这一步没有任何问题,开发了相应的H5界面,通过浏览器进行原始文件......
  • 数据飞轮拆解车企数据驱动三板斧:数据分析、市场画像、A/B 实验
    更多技术交流、求职机会,欢迎关注字节跳动数据平台微信公众号,回复【1】进入官方交流群 近日,火山引擎数智平台(VeDI)2023数据飞轮汽车行业研讨会在上海举办,活动聚焦汽车行业数字化转型痛点,从字节跳动自身数据驱动经验、数据飞轮模式落地、企业侧场景实践三方面,诠释数据飞轮如......
  • 自动加药设备如何进行数据采集和远程维护上下载
    行业背景自动加药设备是一种用于水处理、化工、医药等领域的加药设备。自动加药设备主要由药液箱、计量泵、管道、阀门、控制系统等组成,它通过自动化PLC控制系统,根据设定的剂量和比例,将药液自动加入到水中,以达到所需的水质要求,具有操作简便、加药精度高、维护方便等优点。设备的自......
  • CH32X035 模拟IIC驱动EEPROM
    在CH32X035的GPIO模式配置选项中,并没有开漏输出的配置模式,如下图。在使用GPIO模拟IIC时,可在初始化时将其配置成推挽输出模式,在需要时切换对应的输入输出模式,以下是CH32X035GPIO模拟IIC的实现例程。 具体程序代码如下:iic.h文件:#ifndef__IIC_H#define__IIC_H#include"c......