1).Linux 驱动框架
思想:写驱动的时候,只提供操作硬件设备的函数接口
文件存放磁盘: open ,read ,write ,close
ARM exynos4412 origen(母板)
裸机驱动:(硬件操作 + 功能需求)
AD/I2C/UART/PWM(Timer + OUT)/LED/KEY (阅读原理图,芯片手册,编码控制硬件)
2).Linux 硬件子系统
3).动手编写驱动(学习)
实际工作的时候,更多调试
4).学习方法
[1]内核提供的函数接口(熟悉),参照别人的写法,来编写自己
[2]对linux系统的思想认识(大牛灌输)
[3]动手(一点一点加)
[4]思考
1).系统移植(bootloader , Linux kernel , device tree , fs)
2).驱动编写
Linux 内核文件添加,“add Tree” 是添加包含该文件夹路径下的全部文件。
同步文件,类似之前linux ubuntu系统下的tags功能,方便跟踪函数定义
同步过程可能需要几分钟。。。。。。
Options ->document options 设置字体大小
代码显示风格设置
编写linux驱动模块的基本步骤:
<1>头文件
#include
<2>许可权限GPL
MODULE_LICENSE("GPL");
<3>模块入口函数
int xxx_init(void)
{
….
return 0;
}
<4>模块出口函数
void xxx_exit(void)
{
….
}
<5>模块入口
module_init(xxx_init);
<6>模块出口
module_exit(xxx_exit);
1).阅读原理图
2).查看datasheet
3).编写模块
4).编译模块
思想:
Linux 内核源码的编译系统可以编译我们编写的模块代码。
第一种(产品发布):
将自己编写的代码,拷贝到Linux内核源码树下,然后配置编译,编译进内核。
第二种(驱动调试):
自己编写Makefile,然后使用Linux内核的编译系统,编译自己的模块代码。
问1:Linux内核的编译系统在哪里?
答:
<1>Linux内核源码下的Makefile
注意:你的Linux内核源码必须已经根据自己所开发的平台进行了配置
[1]修改了Makefile,指定了开发工具链(cross compile )
[2]已经使用Linux内核默认配置文件进行了配置
<2>ubuntu系统自带的Linux内核编译系统(pc机,x86)
/lib/modules/3.13.0-32-generic/build/Makefile
问2:如何在自己编写的Makefile中使用Linux内核的编译系统?
答:make -C linux内核编译系统的路径 M=需要编译的模块代码路径 modules
ifeq ($(KERNELRELEASE),)
KERNEL_BUILD = /lib/modules/$(shell uname -r)/build
MODULE_PATH := $(shell pwd)
module:
$(MAKE) -C $(KERNEL_BUILD) M=$(MODULE_PATH) modules
clean:
$(MAKE) -C $(KERNEL_BUILD) M=$(MODULE_PATH) clean
else
obj-m = led-driver.o
endif
install:
#拷贝led-driver.ko 到共享目录
cp led-driver.ko /home/ubuntu/workdir/fs4412/fs/rootfs
#用ARM gcc 编译器test.c
arm-none-linux-gnueabi-gcc test.c -o test
#拷贝可执行文件test到共享目录
cp test /home/ubuntu/workdir/fs4412/fs/rootfs
#make install 自动执行以上三个步骤
查看设备中已经注册的设备号
sudo insmod led-driver.ko 安装模块
cat /proc/devices 查看设备中已经注册的设备号
sudo rmmod led-driver.ko 卸载模块
dmesg 查看内核空间printk打印的信息
modinfo led-driver.ko 查看模块包含的信息
lsmod 查看系统中的模块
make
make clean 清除之前编译的可执行文件及配置文件。
dmesg | tail
cd /sys/class 查看已经安装的驱动路径
cd /sys/class/led/led cat uevent 查看驱动配置信息
ubuntu@farsight:/sys/class/led/led$ cat uevent
MAJOR=250
MINOR=0
DEVNAME=led
1)应用层的open( ),close( )如何调用到底层对应的open(),release( )?
函数指针把自定义的led_open,led_release函数首地址赋值给应用层的open,release函数
字符设备或块设备,我们可以通过设备文件来找到底层驱动程序
-----------------------------------------------------------------
驱动的标识:设备号
12bit(主设备号) + 20bit(次设备号) = 32bit
主设备号:标识一类设备
次设备号:为了区分同类型设备的不同设备(个体)
------------------------------------------------------------------
#define MINORBITS 20
#define MINORMASK ((1U << MINORBITS) - 1)
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
//主要通过MKDEV(MAJOR, MINOR) 宏定义来计算设备号
问1:Linux内核有那么多驱动程序,如何才能确定自己需要访问的驱动程序?
答:通过设备文件中包含的设备号信息
问2:Linux内核中,如何描述文件?
答:<1>struct inode 描述文件属性信息(文件类型,权限,大小,修改时间,设备号[设备文件])
<2>struct file 描述一个打开的文件(打开的方式,文件偏移量,…)
[注意:只要打开一次文件,就会分配一次]
问3:应用层访问底层字符设备驱动的过程?
答:open---->设备文件
struct inode:设备号
--------->struct cdev:记录操作硬件设备的函数接口
寻找成功之后:
struct inode 结构体记录struct cdev这个结构体首地址
struct file 结构体记录struct file_operations这个结构体首地址
问4:写字符驱动,需要做什么?
答:<1>struct cdev:Linux 针对字符设备的通用描述
struct led_device
{
struct cdev cdev;//通用的字符设备描述
….
};
需要给自己设计的结构体分配空间(内核申请空间使用kmalloc函数)
kmalloc(sizeof(pdev), GFP_KERNEL);
<2>提供硬件设备的操作函数接口
struct file_operations 结构体做填充
需要将这个结构体的首地址记录在struct cdev结构体中
<3>申请一个空闲的设备号
<4>使用设备号,将struct cdev这个结构体添加到系统中去
-------------------------------------------------------------------
mknod 设备文件名 设备文件类型 主设备号 次设备号
mknod /dev/led c 250 0
-------------------------------------------------------------------
mknod命令用于创建Linux中的字符设备文件和块设备文件
copy_from_user() 从用户空间拷贝数据
copy_to_user() 拷贝数据到用户空间
这里当之前已经使用
s****udo insmod led-driver.ko 安装模块
rmmod卸载后再次insmod安装的时候,会提示1 个文件已经存在;调试代码发现如下指针类型匹配的问题:
这里当多次使用insmod和rmmod的时候,使用cat /proc/devices 查看设备号发现这里有多个led的主设备号,也即设备号删除失败。
注:为了方便调试,建议可以在每进行一个删除操作后执行一个printk()打印错误代码的操作(确认删除函数的返回值类型)。便于定位各项操作是否正确执行。
void led_exit(void) 里面有函数没有被正确执行
Led 端口数据写值;
配置led端口输出模式
ubuntu@farsight:~/workdir/fs4412/kernel/linux-3.14$ pwd
/home/ubuntu/workdir/fs4412/kernel/linux-3.14
make
然后把模块文件拷贝到共享路径下
ubuntu@farsight:~/workdir/fs4412/fs/rootfs$ pwd
/home/ubuntu/workdir/fs4412/fs/rootfs
cp led-driver.ko /home/ubuntu/workdir/fs4412/fs/rootfs
ubuntu@farsight:/mnt/hgfs/share/learn_driver/led/led_version6$ arm-none-linux-gnueabi-gcc test.c -o test
ubuntu@farsight:/mnt/hgfs/share/learn_driver/led/led_version6$ cp test /home/ubuntu/workdir/fs4412/fs/rootfs
偷懒的做法,先make ;然后make install
install:
#拷贝led-driver.ko 到共享目录
cp led-driver.ko /home/ubuntu/workdir/fs4412/fs/rootfs
#用ARM gcc 编译器test.c
arm-none-linux-gnueabi-gcc test.c -o test
#拷贝可执行文件test到共享目录
cp test /home/ubuntu/workdir/fs4412/fs/rootfs
#make install 自动执行以上三个步骤
注:照抄的时候上面的文件名要根据自己的文件名修改过来。
SecureCRT 有打印led on 和led off信息,但是开发板上的led没有闪烁,这时候有一个基本的判断,之前的version在电脑上的ubuntu测试添加设备驱动成功,****所以设备驱动函数应该是没有问题的,led灯没有闪烁,问题出在针对底层硬件的操作,也即LED 的IO端口的输出模式寄存器配置以及输出电平的寄存器配置。
I****octl()函数
M****akefile 里面文件名不匹配
调用函数写错,platform_device_register();
要注意区分device和driver
led-device.ko 驱动模块编写的有问题导致加载错误。
移除驱动的时候报错,这时需要新建目录
mkdir /lib/modules/3.14.0
这样就可以成功移除驱动了。
因为level可以是1,也可以是0,当为0时,如果前面一步没有做清零操作,则这一步|操作就没有起到作用(0与之前的状态相与还是之前的状态,无改变)。
这里注意~0 ——0取反不是1,
!0 ——0取非才是1.
ls /sys/bus/platform/devices/
led_probe()函数多次执行,class_create()重复创建类fs4412-led.
L****ed2设备名字重名,
dev_name(&pdev->dev)****;
register_led_chrdev()传参数的时候需要传struct platform_device pdev****;
platform ——>led_version3 存在的卸载bug
测试老师的代码,使用rmmod led-driver 可以正常卸载
ubuntu@farsight:~/workdir/fs4412/kernel/linux-3.14/arch/arm/boot/dts$
vi exynos4412-fs4412.dts ( X )
上面修改****exynos4412-fs4412.dts 后make dtbs 没有任何输出信息。
vi exynos4412-origen.dts ( √ )
注:这里要修改的是exynos4412-origen.dts,因为之前linux系统移植的时候Makefile 里面修改的是exynos4412-origen.dtb。
在linux-3.14路径下make dtbs 编译生成.dtb设备树文件
ubuntu@farsight:~/workdir/fs4412/kernel/linux-3.14$ make dtbs
DTC arch/arm/boot/dts/exynos4412-origen.dtb
拷贝生成的.dtb设备树文件到tftpboot 目录:
ubuntu@farsight:~/workdir/fs4412/kernel/linux-3.14/arch/arm/boot/dts$
cp exynos4412-origen.dtb /home/ubuntu/workdir/fs4412/tftpboot/
ubuntu@farsight:~/workdir/fs4412/tftpboot$ chmod 777 exynos4412-origen.dtb
修改class_create()函数参数
[root@farsight ]# ls -lh /dev/led2-gpio.2
crw-rw---- 1 0 0 253, 0 Jan 1 00:00 /dev/led2-gpio.2
[root@farsight ]# ./test /dev/led2-gpio.2
为了使pled->trigger_level 为1,则这里要对flags做取反操作。
1)、注册中断函数接口devm_request_irq( )
ubuntu@farsight:~/workdir/fs4412/kernel/linux-3.14/Documentation/devicetree/bindings/interrupt-controller$ vi interrupts.txt
多个驱动注册同一个中断号
1)、每一个devm_request_irq注册函数中需要在中断标志上加上 IRQF_SHARED****;
2)、要保证每个注册函数的dev_id 要唯一。(因为linux系统在注销一个中断的时候,是通过中断号(irq)和dev_id这两个参数来决定需要注销的中断的)。
cat /proc/interrupts 查看中断信息
这里之前interrupt-names 名字没有匹配(少了‘s’),不是linux系统通用属性就需要驱动工程师自己去解析。
1)初始化一个tasklet 结构体
2)编写tasklet处理函数(中断下半部处理函数)
3)在中断上半部处理函数结束处调度tasklet——tasklet_schedule()
//tasklet_schedule(struct tasklet_struct * t)
tasklet_schedule(&pkey->tasklet);
1)初始化一个work 结构体
struct work_device{
int irq;
struct work_struct work ;
};
struct work_device *pkey;
INIT_WORK(&pkey->work, key_work_handler);
2)编写work处理函数(中断下半部处理函数)
static void key_work_handler(struct work_struct *work)
{
/*******************************************
container_of - cast a member of a structure out to the containing structure
@ptr: the pointer to the member.
@type: the type of the container struct this is embedded in.
@member: the name of the member within the struct.
*******************************************/
struct work_device pkey = container_of****(work, struct work_device, work);
printk("key work handler pkey->irq : %d\n",pkey->irq);
return;
}
3)在中断上半部处理函数结束处调度work—— schedule****_work ()
schedule_work(&pkey->work);//调度工作队列
<1>阻塞
等待队列
<2>时钟信号的使用
注意:主机接收器在接收到最后一个数据后,
I2C控制器驱动,由Samsung提供的i2c-s3c2410.c;
i2c-dev.c实现了从设备通用驱动,则工程师只需要编写I2C从设备的的驱动即可(也即MPU6050的I2C驱动)。
cd sys/bus/i2c/drivers/
在设备树中添加I2C从设备信息后(参考i2c-s3c2410.txt 的写法)
ubuntu@farsight:~/workdir/fs4412/kernel/linux-3.14/Documentation/devicetree/bindings/i2c$
vi i2c-s3c2410.txt
手机扫一扫
移动阅读更方便
你可能感兴趣的文章