当前位置: 首页 > article >正文

笔记整理—linux驱动开发部分(4)驱动框架

        内核中,针对每种驱动都设计了一套成熟的、标准的、典型的驱动框架,实现将相同部分实现,不同部分留出接口给工程师自行发挥。具有以下特点:①简单化;②标准化;③统一管控系统资源;④特定化接口函数与数据结构。eg:led_class.c led_core.c 去实现自己的LED_xxx.c,其中xxx为SOC厂商,产品商又会以厂商代码做出产品,对soc厂商代码做移植与调试。

        模块分析方法:从下往上进行分析。驱动框架:实现了一个类的通用class。

        内核驱动开发以级别定启动顺序与段(n=1~7s,可选1~7s,但实际为0~7s)。内核启动时按段启动顺序执行。

#define __define_initcall(level,fn,id) \
	static initcall_t __initcall_##fn##id __used \
	__attribute__((__section__(".initcall" level ".init"))) = fn

   attribute,对应于/sys/class/xxx/目录中的内容,一般为文件或文件夹,是sysfs给应用层的一些接口,类似/dev/下的设备文件。

        驱动操作硬件:①file_operation;②attribute。

class_create:在/sys/class下创建一个类
device_create:创建属于一个类的一个设备,本质是注册一个设备,是驱动框架的注册接口
file_operations:用于register_chrdev注册

驱动模型分为三种
platform:平台设备
system:系统设备
virtual:虚拟设备

        echo 1>led1 可操作led1,因为在led1的ston中有方法支持(X210开发板)。

        基于LED class实现LED驱动,关键点在于led_classdev_register,这是厂商已经写好的,自己不用在写一次。

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;
}

        当注册成功在/sys/class/led/下会出现相关信息。

static int __init s5pv210_led_init(void);//注册
static void __exit s5pv210_led_exit(void);//注销

struct led_classdev mydev;//led_classdev 是内核类设备

led_classdev_register(NULL,&mydev);
led_classdev_unregister(&mydev);

mydev.name="mydev";
mydev.brightness=0;
mydev.brightness_set=s5pv210_led_set;

        store 方法对应echo写法;show方法对应cat(show)读方法。

        show方法读硬件信息,返回到应用层,但因为驱动框架提供的方法无法直接提取到信息,因为没有特殊性的去细化每一个设备,只是一个类的方法,所以在show和store之间使struct led_classdev结构体中相对应的细化方法去实现。struct led_classdev结构体中的方法要自己去写。

struct led_classdev {
	const char		*name;
	int			 brightness;
	int			 max_brightness;
	int			 flags;

	/* Lower 16 bits reflect status */
#define LED_SUSPENDED		(1 << 0)
	/* Upper 16 bits reflect control information */
#define LED_CORE_SUSPENDRESUME	(1 << 16)

	/* Set LED brightness level */
	/* Must not sleep, use a workqueue if needed */
	void		(*brightness_set)(struct led_classdev *led_cdev,
					  enum led_brightness brightness);
	/* Get LED brightness level */
	enum led_brightness (*brightness_get)(struct led_classdev *led_cdev);

	/* Activate hardware accelerated blink, delays are in
	 * miliseconds and if none is provided then a sensible default
	 * should be chosen. The call can adjust the timings if it can't
	 * match the values specified exactly. */
	int		(*blink_set)(struct led_classdev *led_cdev,
				     unsigned long *delay_on,
				     unsigned long *delay_off);

	struct device		*dev;
	struct list_head	 node;			/* LED Device list */
	const char		*default_trigger;	/* Trigger to use */

#ifdef CONFIG_LEDS_TRIGGERS
	/* Protects the trigger data below */
	struct rw_semaphore	 trigger_lock;

	struct led_trigger	*trigger;
	struct list_head	 trig_list;
	void			*trigger_data;
#endif
};
static void s5pv210_led_set(struct led_classdev *led_cdev,emum led_brightness value)
{
    if(LED_OFF==value)
    {
        write(0xxxxxxxxx,GPJ0CON);
        //方法
        write(0xxxxxxxxx,GPJ0DAT);
    }else if(LED_ON==value)
    {
        //方法
    }
}

        硬件驱动的机制与策略/机制——怎么实现操作方法,也即是驱动;策略——如何使硬件按照自己的要求工作,也就是应用。

        机制不应该去提供策略方法,操作LED的方法就是想灭就灭,想关就关,机制只应该提供开关的接口,不应该去管要亮几个LED,等应用层要LED亮就亮就行了。

        读改写保证开关一个LED:

writel(((readl(GPJ0DAT)|(1<<3),GPJ0DAT);//灭
writel(((readl(GPJ0DAT)&~(1<<3),GPJ0DAT);//亮

        GPIOLIB是先申请,再使用的。大部分硬件都使用GPIO工作以及复用(同GPIO工作不同硬件);同一个GPIO被两个驱动同时控制会出现BUG;内核提供gpiolib进行统一管理系统中所有gpio(只要一方不释放,别人就别想用);gpiolib本质属于驱动框架的一部分(/kernel/drivers/gpio)。

        GPIO的使用方法:申请——>使用——>释放。

        gpio_chip结构体,gpio操作方法框架。

struct gpio_chip {
	const char		*label;
	struct device		*dev;
	struct module		*owner;

	int			(*request)(struct gpio_chip *chip,
						unsigned offset);
	void			(*free)(struct gpio_chip *chip,
						unsigned offset);

	int			(*direction_input)(struct gpio_chip *chip,
						unsigned offset);
	int			(*get)(struct gpio_chip *chip,
						unsigned offset);
	int			(*direction_output)(struct gpio_chip *chip,
						unsigned offset, int value);
	int			(*set_debounce)(struct gpio_chip *chip,
						unsigned offset, unsigned debounce);

	void			(*set)(struct gpio_chip *chip,
						unsigned offset, int value);

	int			(*to_irq)(struct gpio_chip *chip,
						unsigned offset);

	void			(*dbg_show)(struct seq_file *s,
						struct gpio_chip *chip);
	int			base;
	u16			ngpio;
	const char		*const *names;
	unsigned		can_sleep:1;
	unsigned		exported:1;
};

        xxx_gpio_cfg:xxxgpio配置。xxx_gpio_pm,xxx电源管理;__iomem是虚拟地址基地址。

struct s3c_gpio_chip {
	struct gpio_chip	chip;
	struct s3c_gpio_cfg	*config;
	struct s3c_gpio_pm	*pm;
	void __iomem		*base;
	int			eint_offset;
	spinlock_t		 lock;
#ifdef CONFIG_PM
	u32			pm_save[7];
#endif
};

struct s3c_gpio_cfg {
	unsigned int	cfg_eint;

	s3c_gpio_pull_t	(*get_pull)(struct s3c_gpio_chip *chip, unsigned offs);
	int		(*set_pull)(struct s3c_gpio_chip *chip, unsigned offs,
				    s3c_gpio_pull_t pull);

	int		(*set_pin)(struct s3c_gpio_chip *chip, unsigned offs,
				    s3c_gpio_pull_t level);

	unsigned (*get_config)(struct s3c_gpio_chip *chip, unsigned offs);
	int	 (*set_config)(struct s3c_gpio_chip *chip, unsigned offs,
			       unsigned config);
};

struct s3c_gpio_pm {
	void (*save)(struct s3c_gpio_chip *chip);
	void (*resume)(struct s3c_gpio_chip *chip);
};

        端口与IO口,一个端口包含多个IO口,如GPA0是一个端口,GPA0_0是其中的一个IO口,每一个端口与相接端口相差0x20。        

 

        内核中为每个IO分配唯一一个连续编号,编号可用让程序识别,每一个GPIO label(是给人看的端口),base是每个GPIO的起始编码。

        内核中也对终端号进行了统一管控。

static struct s3c_gpio_chip s5pv210_gpio_4bit[]

        实现了对s5pv210开发板上的端口信息概括:

ARRAY_SIZE(s5pv210_gpio_4bit);//实现端口数计算
void __init samsung_gpiolib_add_4bit_chips(struct s3c_gpio_chip *chip,
					   int nr_chips)
{
	for (; nr_chips > 0; nr_chips--, chip++) {
		samsung_gpiolib_add_4bit(chip);
		s3c_gpiolib_add(chip);
	}
}
//这是三星注册gpio的方法。

int gpiochip_add(struct gpio_chip *chip)
{
	unsigned long	flags;
	int		status = 0;
	unsigned	id;
	int		base = chip->base;

	if ((!gpio_is_valid(base) || !gpio_is_valid(base + chip->ngpio - 1))
			&& base >= 0) {
		status = -EINVAL;
		goto fail;
	}

	spin_lock_irqsave(&gpio_lock, flags);

	if (base < 0) {
		base = gpiochip_find_base(chip->ngpio);
		if (base < 0) {
			status = base;
			goto unlock;
		}
		chip->base = base;
	}

	/* these GPIO numbers must not be managed by another gpio_chip */
	for (id = base; id < base + chip->ngpio; id++) {
		if (gpio_desc[id].chip != NULL) {
			status = -EBUSY;
			break;
		}
	}
	if (status == 0) {
		for (id = base; id < base + chip->ngpio; id++) {
			gpio_desc[id].chip = chip;

			/* REVISIT:  most hardware initializes GPIOs as
			 * inputs (often with pullups enabled) so power
			 * usage is minimized.  Linux code should set the
			 * gpio direction first thing; but until it does,
			 * we may expose the wrong direction in sysfs.
			 */
			gpio_desc[id].flags = !chip->direction_input
				? (1 << FLAG_IS_OUT)
				: 0;
		}
	}

unlock:
	spin_unlock_irqrestore(&gpio_lock, flags);
	if (status == 0)
		status = gpiochip_export(chip);
fail:
	/* failures here can mean systems won't boot... */
	if (status)
		pr_err("gpiochip_add: gpios %d..%d (%s) failed to register\n",
			chip->base, chip->base + chip->ngpio - 1,
			chip->label ? : "generic");
	return status;
}
//进行gpiochip注册,这是通用的

__init void s3c_gpiolib_add(struct s3c_gpio_chip *chip)
{
	struct gpio_chip *gc = &chip->chip;
	int ret;

	BUG_ON(!chip->base);
	BUG_ON(!gc->label);
	BUG_ON(!gc->ngpio);

	spin_lock_init(&chip->lock);

	if (!gc->direction_input)
		gc->direction_input = s3c_gpiolib_input;
	if (!gc->direction_output)
		gc->direction_output = s3c_gpiolib_output;
	if (!gc->set)
		gc->set = s3c_gpiolib_set;
	if (!gc->get)
		gc->get = s3c_gpiolib_get;

#ifdef CONFIG_PM
	if (chip->pm != NULL) {
		if (!chip->pm->save || !chip->pm->resume)
			printk(KERN_ERR "gpio: %s has missing PM functions\n",
			       gc->label);
	} else
		printk(KERN_ERR "gpio: %s has no PM function\n", gc->label);
#endif

	/* gpiochip_add() prints own failure message on error. */
	ret = gpiochip_add(gc);
	if (ret >= 0)
		s3c_gpiolib_track(chip);
}
//其中提供了chip的input、output、set、get四种方法

        同一个虚拟地址不可关联多个chip。

        gpiochip_add是将封装的一个GPIO端口所有信息变量挂载到内核gpiolib模块定义的一个gpio_desc数组中。gpiochip_add是给厂商用来注册gpio接口的,在正常开发过程中是用不上的。gpio_request给驱动工程师使用gpiolib的接口,也是正常开发使用的接口。

        驱动过程中,使用gpio接口,应先调用gpio_request去申请gpio接口,gpio_free用于释放申请的gpio接口。

int gpio_request_array(struct gpio *array, size_t num);可以一次申请多个gpio
void gpio_free_array(struct gpio *array, size_t num);一次释放多个gpio
int gpio_request_one(unsigned gpio, unsigned long flags, const char *label);申请一个gpio,支持flag调试模式
const char *gpiochip_is_requested(struct gpio_chip *chip, unsigned offset);查看gpio是否被申请
int gpio_direction_input/output(unsigned gpio);设置gpio输入/输出模式(只是在soc方法做了封装)本质上指向了samsung_gpiolib_4bit_output/input.

        框架就是给不同soc留有接口去定制专项方法。 

        在gpiochip n文件夹中有base、lable、ngpio方法。

        echo n>export可导出相关gpio信息。

        gpiolib使用方法:①申请gpio_request;②设置输入输出模式gpio_direction_input/output;③设置输入输入的值gpio_get_value、gpio_set_value。

        在mach/gpio中可看到用gpio与申请方法:

#defile GPIO_LED1    s5pv210_GPJ0(3);
#defile GPIO_LED2    s5pv210_GPJ0(4);
#defile GPIO_LED3    s5pv210_GPJ0(5);

if(gpio_request(GPIO_LED1,"gpio_3"))
{
    //err
}else{
    gpio_direction_output(GPIO_LED1,1);
}

//释放
gpio_free(GPIO_LED1);

//gpio设置高低电平
gpio_set_value(GPIO_LED1,0);


//申请多个gpio口
gpio_request_array(struct gpio,size);

        查看某个gpio释放被使用 debugfs虚拟文件系统:①mount -t dugfs debugfs /tmp;②cat /tmp/gpio 可查看gpio信息;③umount /tmp卸载debugfd。

        自己写的驱动应该放在文件夹中的/eds/下,后修改MAKEFILE、修改kconfig文件。在make menuconfig后可进行配置,在.config中可查看配置结果,make就可进行内核编译。编译成为模块的文件在同文件夹下可见.ko文件。


http://www.kler.cn/a/374918.html

相关文章:

  • 『 Linux 』网络传输层 - TCP(二)
  • HT8787B 可任意限幅、内置自适应升压的2x9.0W立体声音频功放
  • 回溯2:深入探讨C语言中的操作符 —— 从基础到进阶
  • 【如何获取股票数据31】Python、Java等多种主流语言实例演示获取股票行情api接口之沪深A股融资融券标的股数据获取实例演示及接口API说明文档
  • redis详细教程(6.主从复制)
  • TDengine 数据订阅 vs. InfluxDB 数据订阅:谁更胜一筹?
  • 【Nginx】编译安装(Centos)
  • Windows下Jenkins自动启动jar包
  • 技术总结(十九)
  • unity后端kbengine用DOTween让 移动同步丝滑
  • HJ106 字符逆序
  • 发布 NPM 包时,终端显示发布成功但实际上版本并没有更新,可能是由于以下原因
  • 基于 Postman 和 Elasticsearch 测试乐观锁的操作流程
  • Java的多态
  • LEADTOOLS 版本 23 现已发布,引入了 Excel API等众多新功能!
  • 就业市场变革:AI时代,我们将如何评估人才?
  • Python之groupby()及aggregate()方法
  • 手机实时提取SIM卡打电话的信令声音-新的篇章(三、Android虚拟声卡探索)
  • 每日互动基于 Apache DolphinScheduler 从容应对ClickHouse 大数据入库瓶颈
  • 巨好看的登录注册界面源码
  • 【 纷享销客-注册安全分析报告-无验证方式导致安全隐患】
  • C++:二叉搜索树进阶
  • flink 自定义kudu connector中使用Metrics计数平均吞吐量,并推送到自定义kafkaReporter
  • DDIM扩散模型的加速采样(去噪)算法 Denoising Diffusion Implicit Models
  • windows 11 配置 kafka 使用SASL SCRAM-SHA-256 认证
  • 操作符详解