嵌⼊式Linux开发笔记(韦东⼭2)
嵌⼊式Linux驱动开发基础知识
1. 具体单板的LED驱动程序
1.1 编写LED驱动程序的详细步骤
1. 看原理图确定引脚,确定引脚输出什么电平才能点亮/熄灭LED
2. 看主芯⽚⼿册,确定寄存器的操作⽅法:哪些寄存器?哪些位?地址是?
3. 编写驱动:先写框架,再写硬件操作的代码
(上次部分写出了框架,这次在其中补充具体硬件操作的代码)
注意:在芯⽚⼿册中确定的寄存器地址被称为物理地址,在linux内核中⽆法直接使⽤。需要使⽤内核提供的ioremap把物理地址映射为虚拟地址,使⽤虚拟地址。
ioremap函数的使⽤:
(1)函数原型:
void __iomem *ioremap(resource_size_t res_cookie, size_t size)
//使⽤时要包含头⽂件
//#include <asm/io.h>
(2)作⽤:
把物理地址phys_addr开始的⼀段空间(⼤⼩为size),映射为虚拟地址;返回值是该段虚拟地址的⾸地址。
virt_addr =ioremap(phys_addr,size);
实际上,它是按页(4096字节)进⾏映射的,是整页整页地映射的。
假设phys_addr = 0x10002,size = 4,ioremap的内部实现是:
a. phys_addr按页取整,得到地址0x10000
b. size按页取整,得到4096
c. 把起始地址0x10000,⼤⼩为4096的这⼀块物理地址空间,映射到虚拟地址空间,假设得到的虚拟空间起始地址为0xf0010000
d. 那么phys_addr = 0x10002对应的virt_addr = 0xf0010002
(3)不再使⽤该段虚拟地址时,要iounmap(virt_addr):
void iounmap(volatile void __iomem *cookie)
1.2 AM335X的LED驱动程序
//AM335X的LED驱动程序
//LED驱动程序 leddrv.c⽂件
//1.驱动程序
//(1)包含头⽂件
#include<linux/module.h>
#include<linux/fs.h>
#include<linux/errno.h>
#include<linux/miscdevice.h>
#include<linux/kernel.h>
#include<linux/major.h>
#include<linux/mutex.h>
#include<linux/proc_fs.h>
#include<linux/seq_file>
#include<linux/stat.h>
#include<linux/init.h>
#include<linux/device.h>
#include<linux/tty.h>
#include<linux/kmod.h>
#include<linux/gfp.h>
#include"led_operation.h"
//(2) 确定主设备号
static int major =0;//让内核⾃动分配
static int major =0;//让内核⾃动分配
static struct class*led_class;
struct led_operations *p_led_opr;
#define MIN(a,b) (a<b?a:b)
//(4) 实现对应的open/read/write等函数,填⼊file_operations结构体
static ssize_t led_drv_read (struct file *file,const char __user *buf, size_t size, loff_t *offset)
{
//举例,放⼊⼀些打印信息
printk("%s %s line %d\n",__FILE__,__FUNCTION__,__LINE__);
return0;
}
static ssize_t led_drv_write (struct file *file,char __user *buf, size_t size, loff_t *offset)
{
char status;
int err;
struct inode *inode =file_inode(file);
int minor =iminor(node);
printk("%s %s line %d\n",__FILE__,__FUNCTION__,__LINE__);
//从buffer⾥⾯拿到应⽤程序下发过来的数据,拷贝到kernel——buf(驱动中的buffer)去
err=copy_from_user(&status,const buf,1);
//根据次设备号和status控制LED
p_led_opr->ctl(minor,status);
return1;
}
static int led_drv_open (struct inode *node,struct file *file)
{
int minor =iminor(inode);
printk("%s %s line %d\n",__FILE__,__FUNCTION__,__LINE__);
/
/根据次设备号初始化LED
p_led_opr->init(minor);
return0;
}
static int led_drv_close (struct inode *node,struct file *file)
{
printk("%s %s line %d\n",__FILE__,__FUNCTION__,__LINE__);
return0;
}
//(3) 定义⾃⼰的file_operation结构体
static struct file_operation led_drv ={
.
owner = THIS_MOUDLE;
.open = led_drv_open;
.read = led_drv_read;
.write = led_drv_write;
.release= led_drv_close;
};
//(5) 把file_operations结构体告诉内核:注册驱动程序
//(6) 谁来注册驱动程序?需要⼀个⼊⼝函数:安装驱动程序时,就会去调⽤这个⼊⼝函数(⼊⼝函数中会去调⽤注册函数)static int __init led_init(void)
{
int err;
//注册函数
major =register_chrdev(0,"led",&led_drv);
//创建了class
led_class =class_create(THIS_MOUDLE,"led_class");
err =PTR_ERR(led_class);
if(IS_ERR(led_class)){
unregister_chrdev(major,"led");
return-1;
return-1;
}
//还需要创建⼀个device,多创建⼏个LED
device_create(led_class,NULL,MKDEV(major,0),NULL,"led");
device_create(led_class,NULL,MKDEV(major,1),NULL,"led0");
p_led_opr =get_board_led_opr();
return0;
}
//(7) 有⼊⼝函数就应该有出⼝函数:卸载驱动程序时,就会去调⽤这个出⼝函数
static void __exit led_exit(void)
{
//销毁device
device_destroy(led_class,MKDEV(major,0));
device_destroy(led_class,MKDEV(major,1));
//类销毁
class_destroy(led_class);
//取消注册函数
unregister_chrdev(major,"led");
}
//(8) 其他完善:提供设备信息,⾃动创建设备节点
//将led_init修饰为⼊⼝函数
module_init(led_init);
//将led_exit修饰为出⼝函数
module_exit(led_exit);
MODULE_LICENSE("GPL");//说明驱动程序遵守GPL协议
************************************************************************************************************ //led——operation.h⽂件
#ifndef _LED_OPR
#define _LED_OPR
struct led_operations {
int num;
//初始化LED,which-哪个LED
int(*init)(int which);
//控制LED,which-哪个led,status:1-亮,0-灭
int(*ctl)(int which,char status);
};
struct led_operations *get_board_led_opr(void);
#endif
************************************************************************************************************** //单板上需要实现的程序 board_am335x.c,这⾥是针对AM335X的具体程序
#include<linux/seq_file.h>
#include<linux/stat.h>
#include<linux/init.h>
#include<linux/device.h>
#include<linux/tty.h>
#include<linux/kmod.h>
#include<linux/gfp.h>
#include<asm/io.h>
#include"led_operation.h"
static volatile unsigned int*CM_PER_GPIO1_CLKCTRL;
static volatile unsigned int*conf_gpmc_ad0;
static volatile unsigned int*GPIO1_OE;
static volatile unsigned int*GPIO1_CLEARDATAOUT;
static volatile unsigned int*GPIO1_SETDATAOUT;
static int board_demo_led_init (int which)
{
if(which ==0)
{
//不需要每次都对寄存器指针进⾏初始化,⽽是事先判断⼀下
if(!CM_PER_GPIO1_CLKCTRL)
{
CM_PER_GPIO1_CLKCTRL =ioremap(0x44E00000+0xAC,4);
conf_gpmc_ad0 =ioremap(0x44E10000+0x800,4);
GPIO1_OE =ioremap(0x4804C000+0x134,4);
GPIO1_CLEARDATAOUT =ioremap(0x4804C000+0x190,4);
GPIO1_SETDATAOUT =ioremap(0x4804C000+0x194,4);
}
//printk("%s %s line %d, led %d\n",__FILE__,__FUNCTION__,__LINE__, which);
//a. 使能GPIO1
//set PRCM to enalbe GPIO1
//set CM_PER_GPIO1_CLKCTRL(0x44E00000 + 0xAC)
//val:(1<<18)|0x2
*CM_PER_GPIO1_CLKCTRL =(1<<18)|0x2;
/*b. 设置GPIO1_16的功能,让它⼯作于GPIO模式
*set Control Module to set GPIO1_16(R13) used as GPIO
* conf_gpmc_ad0 as mode7
*addr: 0x44E10000+0x800
*val: 7
*/
*conf_gpmc_ad0 =7;
/*c.设置GPIO1_16的⽅向,让它作为输出引脚
*set GPIO1's registers, to set GPIO1_16's dir(output)
*GPIO1_OE
*addr : 0x4804C000 + 0x134
*clear bit 16
*/
*GPIO1_OE &=~(1<<16);
}
return0;
}
static int board_demo_led_ctl(int which,char status)
{
//printk("%s %s line %d, led %d,%s\n",__FILE__,__FUNCTION__,__LINE__,which,status?"on":"off");
if(which ==0)
{
//on 的情况,观察原理图可知需要点亮的话让引脚输出低电平
if(status)
{
/*e. 清除GPIO1_16的数据,让它输出低电平
*AM335X芯⽚⽀持set-and-clear protocol,设置GPIO_CLEARDATAOUT的bit 16为1即可让引脚输出0; *set GPIO1_16's registers, to output 0
*GPIO_CLEARDATAOUT
*addr: 0x4804C000 + 0x190
*/
*GPIO1_CLEARDATAOUT =(1<<16);
}
else//灭的情况
{
/* d. 设置GPIO1_16的数据,让它输出⾼电平
*AM335X芯⽚⽀持set-and-clear protocol,设置GPIO_SETDATAOUT的bit 16 为1即可让引脚输出1
* set GPIO1_16's registers, to output 1
* GPIO_SETDATAOUT
* addr : 0x4804C000 + 0x194
*/
*GPIO1_SETDATAOUT =(1<<16);
}
}
return0;
}
static struct led_operatioms board_deemo_led_opr ={
.num =1,
.init = board_demo_led_init,
.ctl = board_demo_led_ctl,
.ctl = board_demo_led_ctl,
};
struct led_operations *get_board_led_opr(void)
{
return&board_demo_led_opr;
}
********************************************************************************************************
//ledtest.c⽂件
int main(int argc,char**argv)
{
int fd;
char status;
//1.判断参数
if(argc !=3)
{
printf("Usage: %s <dev> <on | off>\n",argv[0]);
return-1;
}
//2.打开⽂件
fd =open(argv[1],O_RDWR);
if(fd ==-1)
{
printf("can not open file %s\n",argv[1]);
return-1;
}
//3. 写⽂件
if(0==strcmp(argv[2],"on"))
{
status =1;
write(fd,&status,1);
}
else
{
status =0;
write(fd,&status,1);
}
close(fd);
return0;
嵌入式linux开发书籍}
2. 驱动设计的思想—⾯向对象/分层/分离
linux驱动 = 驱动框架 + 硬件操作
= 驱动框架 + 单⽚机
2.1 ⾯向对象
在Linux当中,可以认为⾯向对象就是⽤某⼀个结构体来表⽰对象。
字符设备驱动程序抽象出⼀个file_operations结构体;
程序针对硬件部分抽象出led_operations结构体。
2.2 分层
上下分层,例如前⾯写的LED驱动程序就分为2层:
1. 上层实现硬件⽆关的操作,⽐如注册字符设备驱动:leddrv.c
2. 下层实现硬件相关的操作,⽐如board_A.c实现单板A的LED操作
leddrv.c:实现file_operations,注册驱动
board_A.c或者board_B.c等等:实现硬件操作,构造各⾃的led_operations
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论