mount -t nfs -o nolock,vers=3 192.168.1.117:/home/book/nfs_rootfs /mnt

cat /proc/sys/kernel/printk

echo 8 > /proc/sys/kernel/printk

cp led.ko  ~/nfs_rootfs/

arm-buildroot-linux-gnueabihf-gcc app.c -o app -static

一、C语言LED驱动实验

1.设置处理器模式

  设置6ULL处于SVC模式下。设置CPSR寄存器的bit4-0,也就是M[4:0]=0x13。读写状态要用到MRS指令和MSR指令。MRS指令将CPSR寄存器数据读出到通用寄存器里面,MSR指令将通用寄存器的值写入到CPSR里面去。

2.设置SP指针

  处理器栈增长方式,对于A7而言是向下增长的。设置SP指向 0x200000+0x80000000=0x80200000。

3.跳转到C语言

  使用b指令,跳转到C语言函数,比如main函数

1.怎么进入到make menuconfig图形化界面?

首先进入到内核源码的路径下,然后输入make menuconfig即可打开这个界面。

2.make menuconfig图形化界面的操作

(1)搜索功能

  输入 / ,即可弹出搜索界面,然后输入我们想要搜索的内容即可。

(2)配置驱动的状态。

  a.把驱动编译成模块

  b.把驱动编译到内核里面,用*

  c.不编译

3.和make menuconfig有关的文件

Makefile  里面是编译规则,告诉我们在make 的时候要怎么编译,相当于菜的做饭。

Kconfig   内核配置的选项,相当于吃饭的菜单。

.config     配置完内核以后生成的配置选项,相当于我们点完的菜

4.make menuconfig 会读取哪个目录下的Kconfig文件。

arch/ $ARCH /目录下的Kconfig。

/arch/arm/configs#下面有好多的配置文件。相当于饭店的特色菜。

5.为什么要复制成.config而不复制成其他文件呢?

因为内核会默认读取Linux内核根目录下的.config作为默认的配置选项,所以不能改名字。

6.复制的这个默认的配置选项不符合要求咋办?

7.怎么和Makefile文件建立的关系呢?

内核中编译驱动

Kconfig的一个例子

source “drivers/redled/Kconfig”
config LED__4412
tristate “Led Support for GPIO Led”
depends on LEDS_CLASS
help

1.source “drivers/redled/Kconfig”, 他会包含 drivers/redled/这个路径下的驱动文件,方便我们对菜单进行管理
2.config LED__4412 配置选项的名称,CONFIG_LED_4412
3.tristate 表示的驱动的状态, 三种状态是把驱动编译成模块, 把驱动编译到内核, 不编译。 与之对应的还
有 bool 分别是编译到内核, 不编译
4 “Led Support for GPIO Led”make menuconfig 显示的名字
5 A depends on B 表示只有在选择 B 的时候才可以选择 A
比如我想直接去掉 LED 相关的驱动, 我们直接改.config 文件可以吗? 可以, 但是不推荐。 如果有依赖的话,
直接修改.config 是不成功的。
6.select 反向依赖, 该选项被选中时, 后面的定义也会被选中。
7.help

我们输入“ vim Kconfig” 命令编辑 Kconfig, Kconfig 写入以下内容:

config HELLO
  tristate "helloworld"
  help
  hello hello

 config HELLO 里就是Makefile里的名字。

Linux三大设备驱动

字符设备:IO的传输过程是以字符位单位的,没有缓冲的。比如 I2C,SPI  都是字符设备

块设备:IO的传输过程是以块为单位的。跟存储相关的,都属于块设备,比如 tf 卡

网络设备:与前两个不一样,是以socket套接字来访问的。

1.杂项设备是字符设备的一种,可以自动生成设备节点。

我们的系统里面有很多杂项设备。我们可以输入 cat /proc/misc 命令来查看。

2.杂项设备除了比字符设备简单,杂项设备的主设备号是相同的,均为10次,次设备号是不同的。主设备号相同就可以节省内核的资源。

3.主设备号和次设备号的概念

设备号包含主设备号和次设备号, 设备号是计算机识别设备的一种方式, 主设备号相同的就被视为同
一类设备, 主设备号在 Linux 系统里面是唯一的, 次设备号不一定唯一。

主设备可以比作电话号码的区号。比如北京区号010。次设备号相当于电话号码。

主设备号可以通过以下命令来查看, 前面的数字就是主设备号, 如下图所示:cat /proc/devices

4.杂项设备的描述

misc 设备用 miscdevice 结构体表示, miscdevice 结构体的定义在内核源码具体定义在include/linux/miscdevice.h 中, 内容如下:

struct miscdevice  {
int minor; //次设备号
const char *name;//设备节点的名字
const struct file_operations *fops;//文件操作集
struct list_head list;
struct device *parent;
struct device *this_device;
const struct attribute_group **groups;
const char *nodename;
umode_t mode;
};

file_operations 文件操作集在定义在 include/linux/fs.h 下面

里面的一个结构体成员都对应一个调用。

extern int misc_register(struct miscdevice *misc);注册杂项设备
extern void misc_deregister(struct miscdevice *misc);注销杂项设备

5 注册杂项设备的流程。

(1)填充 miscdevice 这个结构体

(2)填充 file_operations 这个结构体

(3)注册杂项设备并生生成设备节点

#include <linux/init.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/fs.h> /* for kernel specific devices */ struct file_operations misc_fops = { //文件操作集
.owner = THIS_MODULE }; struct miscdevice misc_dev = { //杂项设备结构体
.minor = MISC_DYNAMIC_MINOR, //动态申请的次设备号
.name = "hello_misc",     //杂项设备名字是hello_misc
.fops = &misc_fops,      //文件操作集 }; static int misc_init(void) //注册杂项设备
{
int ret;
ret = misc_register(&misc_dev);
if (ret < 0)
{
printk("misc registe is error\n");
return -1;
} printk("misc registe is succed\n");
return 0;
} static void misc_exit(void)
{
misc_deregister(&misc_dev);
printk("misc bye bye\n");
} module_init(misc_init);
module_exit(misc_exit); MODULE_LICENSE("GPL");

应用层和内核层数据传输

Linux一切接文件!

文件对应的操作有打开,关闭,读写。

设备节点对应的操作有打开,关闭,读写

1.如果我们在应用层使用系统IO对设备节点进行打开,关闭,读写等操作会发生啥???

struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
    //当我们在应用层read设备节点的时候,就会触发我们驱动里面read这个函数。
    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
    //当我们在应用层read设备节点的时候,就会触发我们驱动里面read这个函数
    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
    //当我们在应用层 poll/select设备节点的时候,就会触发我们驱动里面poll这个函数
    unsigned int (*poll) (struct file *, struct poll_table_struct *);
    //当我们在应用层ioctl设备节点的时候,就会触发发我们驱动里面ioctl这个函数
    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); 
    //当我们在应用层open设备节点的时候,就会触发我们驱动里面open这个函数
    int (*open) (struct inode *, struct file *);
    //当我们在应用层close设备节点的时候,就会触发我们驱动里面close这个函数
    int (*release) (struct inode *, struct file *);

通过框图我们可以知道:

上层应用        设备节点        底层驱动

设备节点就是连接上层应用和底层驱动的桥梁

2.假如我们的file_operations里面没有read,我们在应用层read设备

3.我们的应用层和内核层是不能直接进行数据传输的。

copy to

open read write close

应用层从内核读数据

从设备中读取数据, 当用户层调用函数 read 时, 对应的, 内核驱动就会调用这个函数。

应用层从内核写数据

Linux物理地址到虚拟地址映射

1.操作一个寄存器, 可以定义一个指针来操作寄存器

  unsighted int *p = 0x12345678;
  *p=0x87654321;

但是在linux上不行,在Linux上,如果想要操作硬件,需要先把物理地址转换成虚拟地址。

因为Linux使能了MMU,所以我们在Linux上不能直接操作物理地址。

2.使能了MMU的好处???

(1)让虚拟地址成了可能

(2)可以让系统更加安全,因为有了MMU,我们上层应用看到的内存都是虚拟内存,我们的应用就不能直接访问硬件,所以这样就保证了系统安全。

3.MMU非常复杂,如何完成物理地址到虚拟地址的转换呢??

内核提供了函数

ioremap: 把物理地址转换成虚拟地址
iounmap: 释放掉 ioremap 映射的地址

static inline void __iomem *ioremap(phys_addr_t offset, size_t size)

phys_addr_t offset:映射物理地址的起始地址。

size_t size:要映射多大的内存空间

返回值:成功返回虚拟地址的首地址,失败返回NULL

static inline void iounmap(void __iomem *addr)

*addr:要取消映射的虚拟地址的首地址。

注意: 物理地址只能被映射一次, 多次映射会失败。

4.如何查看哪些物理地址被映射过了呢??

可以用  cat /proc/iomem

驱动模块传参

1.什么是驱动传参

驱动传参就是传递参数给我们的驱动。

ex:

insmod beep.ko  a=1

2.驱动传参的作用??

(1)设置驱动的相关参数,比如设置缓冲区的大小

(2)设置安全校验,防止我们写的驱动被人盗用

3.怎么给驱动传参数?

(1)传递普通的参数,比如char ,int 类型的

  函数:

  module_param(name, type, perm);

  参数:

    name  要传递进去的参数的名称

    type    类型

    perm   参数读写的权限

(2)传递数组

module_param_array(name, type, nump, perm);

参数:

    name  要传递进去的参数的名称

    type    类型

    nump   实际传入进去的参数的个数

    perm   参数读写的权限

#include <linux/init.h>
#include <linux/module.h> static int a;
static int b[5];
static int count;
//传递普通的参数 名字 类型 参数读写的权限
module_param(a, int, S_IRUSR);
//传递数组 名字 类型 传入进去参数的个数 参数读写的权限
module_param_array(b, int, &count, S_IRUSR);
static int hello_init(void)
{
int i;
for(i=0; i<count; i++)
{
printk("b[%d] = %d\n", i , b[i]);
}
printk("count = %d \n", count);
printk("a = %d \n", a);
return 0;
}
static void hello_exit(void)
{
// printk("a=%d \n", a);
// printk("bye bye! \n");
} module_init(hello_init);
module_exit(hello_exit); MODULE_LICENSE("GPL");

(3)如果多传递进去参数,会发生什么?

    会报错!!!

   

1.字符设备和杂项设备的区别

1.杂项设备的主设备号是固定的,固定为10, 那么我们要学习的字符类设备号就需要自己或者系统来分配了。

杂项设备可以自动生成设备节点,字符设备需要我们自己生成设备节点。

2.注册字符类设备号的两个方法。

第一种:静态分配一个设备号,使用的是:

register_chrdev_region(dev_t,  unsigned, const char *);

需要明确知道我们系统里面哪些设备号没有用。

参数:

dev_t:设备号的起始值。类型是 dev_t 类型

unsigned:次设备号的个数。

const char *:设备的名称

返回值:成功返回0,失败返回非0

dev_t 类型:

dev_t是用来保存设备号的,是一个32位数。

高12位是用来保存设备号,低12位用来保存次设备的号

typedef __u32 __kernel_dev_t;
typedef __kernel_dev_t dev_t;

Linux提供了几个宏定义来操作设备号

#define MINORBITS    20
次设备号的位数,一共是20位
#define MINORMASK ((1U << MINORBITS) - 1)
次设备号的掩码

#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
在dev_t 里面获取我们的主设备号
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))

在dev_t 里面获取我们的次设备号
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
将我们主设备号和次设备号组成一个dev_t类型。第一个参数是主设备号,第二个参数是次设备号。

第二种方法:动态分配

alloc_chrdev_region(dev_t * , unsigned, unsigned,  const char *);

参数:

dev_t *:保存生成的设备号

unsigned:我们请求的第一个次设备号,通常是0

unsigned:连续申请的设备号的个数。

const char *:设备名称

返回值:成功返回0,失败返回负数

使用动态分配会优先使用255到234

3.注销设备号

unregister_chrdev_region(dev_t, unsigned)

dev_t  分配设备号的起始地址

unsigned  申请的连续设备号的个数

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/kdev_t.h> #define DEVICE_NUMBER 1 //定义次设备号的个数
#define DEVICE_SNAME "schrdev" //定义静态注册设备的名称
#define DEVICE_ANAME "achrdev" //定义动态注册设备的名称
#define DEVICE_MINOR_NUMBER 0 //定义次设备号的起始地址
static int major_num, minor_num; //定义主设备号和次设备号 //传递普通的参数 名字 类型 参数读写的权限
module_param(major_num, int, S_IRUSR);
module_param(minor_num, int, S_IRUSR); static int hello_init(void)
{
dev_t dev_num;
int ret;
/* 如果传进去了主设备号可以用静态方法,如果没有传进去用动态方法*/
if(major_num)
{
/** 静态注册设备号 */
printk("major_num = %d \n", major_num);
printk("minor_num = %d \n", minor_num); dev_num = MKDEV(major_num, minor_num); //MKDEV 将主设备号和次设备号合并为一个设备号
ret = register_chrdev_region(dev_num, DEVICE_NUMBER, DEVICE_SNAME);
if(ret < 0)
{
printk("register_chrdev_region error\n");
}
printk("register_chrdev_region OK\n");
}
else
{ //保存申请的设备号,次设备号起始地址,设备号数量,设备名字
ret = alloc_chrdev_region(&dev_num, DEVICE_MINOR_NUMBER, DEVICE_NUMBER, DEVICE_ANAME);
if(ret < 0)
{
printk("alloc_chrdev_region error\n");
}
printk("alloc_chrdev_region OK\n"); major_num = MAJOR(dev_num);
minor_num = MINOR(dev_num);
printk("major_num = %d \n", major_num);
printk("minor_num = %d \n", minor_num); }
return 0;
} static void hello_exit(void)
{
unregister_chrdev_region(MKDEV(major_num, minor_num), DEVICE_NUMBER);
printk("bye bye! \n");
} module_init(hello_init);
module_exit(hello_exit); MODULE_LICENSE("GPL");
  • 使用静态分配
  • 使用动态分配,不传递参数

建议使用动态申请。如果驱动很多人用,动态不会重复!!!

注册杂项设备:

misc_register(&misc_dev);

注销杂项设备:

misc_deregister(&misc_dev);

cdev结构体:描述字符设备的一个结构体

struct cdev{

  struct kobject kobj;

  struct module *owner;

  const struct file_operation *ops;

  struct list_head list;

  dev_t dev;

  unsigned int count;

};

步骤一:定义一个cdev结构体

步骤二:使用cdev_init函数初始化cdev结构体成员变量

void cdev_init(struct cdev *, struct file_operations *);

参数:

struct cdev *  要初始化的cdev

file_operations *  文件操作集

cdev->ops=fops;  //实际就是把文件操作集写个ops

步骤三:cdev_add函数注册到内核

cdev_add(struct cdev *, dev_t, unsigned);

第一个参数:cdev 的结构体指针

第二个:设备号

第三个:次设备号的数量

注销字符设备:

void cdev_del(struct cdev *);

字符设备注册完以后自动生成设备节点。

我们需要使用 mknod 命令创建一个设备节点

格式:

mknod 名称 类型 主设备号 次设备号

eg:

  mknod /dev/test  c  245  0

32

高12 主   低20 次设备号

手动创建

  可以通过命令mknod创建设备节点。

  mknod命令格式:

  mknod  设备节点名称   设备类型(字符设备用c,块设备用b)   主设备号   次设备号

举例: mknod /dev/test c236 0

2.在注册设备的时候自动创建。

可以通过mdev机制实现设备节点的自动创建与删除。

自动创建设备节点

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/device.h> #define DEVICE_NUMBER 1 //定义次设备号的个数
#define DEVICE_SNAME "schrdev" //定义静态注册设备的名称
#define DEVICE_ANAME "achrdev" //定义动态注册设备的名称
#define DEVICE_MINOR_NUMBER 0 //定义次设备号的起始地址
#define DEVICE_CLASS_NAME "chrdev_class" //宏定义类名
static int major_num, minor_num; //定义主设备号和次设备号 struct cdev cdev;
struct class *class;
//传递普通的参数 名字 类型 参数读写的权限
module_param(major_num, int, S_IRUSR);
module_param(minor_num, int, S_IRUSR); int chrdev_open(struct inode *inode, struct file *file)
{
printk("chrdev_open\n");
return 0;
} struct file_operations chrdev_ops = {
.owner = THIS_MODULE,
.open = chrdev_open}; static int hello_init(void)
{
dev_t dev_num;
int ret;
/* 如果传进去了主设备号可以用静态方法,如果没有传进去用动态方法*/
if (major_num)
{
/** 静态注册设备号 */
printk("major_num = %d \n", major_num);
printk("minor_num = %d \n", minor_num); dev_num = MKDEV(major_num, minor_num); // MKDEV 将主设备号和次设备号合并为一个设备号
ret = register_chrdev_region(dev_num, DEVICE_NUMBER, DEVICE_SNAME);
if (ret < 0)
{
printk("register_chrdev_region error\n");
}
printk("register_chrdev_region OK\n");
}
else
{ //保存申请的设备号,次设备号起始地址,设备号数量,设备名字
ret = alloc_chrdev_region(&dev_num, DEVICE_MINOR_NUMBER, DEVICE_NUMBER, DEVICE_ANAME);
if (ret < 0)
{
printk("alloc_chrdev_region error\n");
}
printk("alloc_chrdev_region OK\n"); major_num = MAJOR(dev_num);
minor_num = MINOR(dev_num);
printk("major_num = %d \n", major_num);
printk("minor_num = %d \n", minor_num);
} cdev.owner = THIS_MODULE;
cdev_init(&cdev, &chrdev_ops);
cdev_add(&cdev, dev_num, DEVICE_NUMBER);
class = class_create(THIS_MODULE, DEVICE_CLASS_NAME);

return 0;
} static void hello_exit(void)
{
unregister_chrdev_region(MKDEV(major_num, minor_num), DEVICE_NUMBER);
cdev_del(&cdev);
class_destroy(class);
printk("bye bye! \n");
} module_init(hello_init);
module_exit(hello_exit); MODULE_LICENSE("GPL");


#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/device.h> #define DEVICE_NUMBER 1 //定义次设备号的个数
#define DEVICE_SNAME "schrdev" //定义静态注册设备的名称
#define DEVICE_ANAME "achrdev" //定义动态注册设备的名称
#define DEVICE_MINOR_NUMBER 0 //定义次设备号的起始地址
#define DEVICE_CLASS_NAME "chrdev_class" //宏定义类名
#define DEVICE_NODE_NAME "chrdev_test" //宏定义设备节点的名字 static int major_num, minor_num; //定义主设备号和次设备号
dev_t dev_num; //设备号 struct cdev cdev;
struct class *class;
struct device *device;
//传递普通的参数 名字 类型 参数读写的权限
module_param(major_num, int, S_IRUSR);
module_param(minor_num, int, S_IRUSR); int chrdev_open(struct inode *inode, struct file *file)
{
printk("chrdev_open\n");
return 0;
} struct file_operations chrdev_ops = {
.owner = THIS_MODULE,
.open = chrdev_open}; static int hello_init(void)
{
dev_t dev_num;
int ret;
/* 如果传进去了主设备号可以用静态方法,如果没有传进去用动态方法*/
if (major_num)
{
/** 静态注册设备号 */
printk("major_num = %d \n", major_num);
printk("minor_num = %d \n", minor_num); dev_num = MKDEV(major_num, minor_num); // MKDEV 将主设备号和次设备号合并为一个设备号
ret = register_chrdev_region(dev_num, DEVICE_NUMBER, DEVICE_SNAME);
if (ret < 0)
{
printk("register_chrdev_region error\n");
}
printk("register_chrdev_region OK\n");
}
else
{ //保存申请的设备号,次设备号起始地址,设备号数量,设备名字
ret = alloc_chrdev_region(&dev_num, DEVICE_MINOR_NUMBER, DEVICE_NUMBER, DEVICE_ANAME);
if (ret < 0)
{
printk("alloc_chrdev_region error\n");
}
printk("alloc_chrdev_region OK\n"); major_num = MAJOR(dev_num);
minor_num = MINOR(dev_num);
printk("major_num = %d \n", major_num);
printk("minor_num = %d \n", minor_num);
} cdev.owner = THIS_MODULE;
cdev_init(&cdev, &chrdev_ops);
cdev_add(&cdev, dev_num, DEVICE_NUMBER);
class = class_create(THIS_MODULE, DEVICE_CLASS_NAME);
//在class类下创建设备 设备号 设备节点的名字
device = device_create(class, NULL, dev_num, NULL, DEVICE_NODE_NAME); return 0;
} static void hello_exit(void)
{
unregister_chrdev_region(MKDEV(major_num, minor_num), DEVICE_NUMBER);
cdev_del(&cdev);
device_destroy(class, dev_num);
class_destroy(class);
printk("bye bye! \n");
} module_init(hello_init);
module_exit(hello_exit); MODULE_LICENSE("GPL");

平台总线模型介绍

1.什么是平台总线模型?

  平台总线模型也叫platform总线模型。是Linux内核虚拟出来的一条总线,不是真实的导线。

  平台总线模型就是把原来的驱动C文件分成了两个C文件,一个是

2.为什么会有平台总线模型?

(1)可以提高代码的重用性

(2)减少重复性代码。

设备            总线            驱动

device.c                        driver.c

3.平台总线的优点

4.怎么编写以平台总线模型设计的驱动?

一个是device.c,一个是driver.c,分别注册device.c和driver.c。

平台总线是以名字来匹配的,实际上就是字符串比较。

注册Platform设备

1.平台总线注册一个device

device.c里面写的是硬件资源,这里的硬件资源是指寄存器的地址,中断号,时钟等硬件资源。

 在Linux内核里面,我们是用一个结构体来描述硬件资源的。

struct platform_device
{
const char *name; //平台总线进行匹配的时候用到的name, /sys/bus......
int id; //设备id,一般写-1
bool id_auto;
struct device dev;//内嵌的device结构体
u32 num_resources; //资源的个数
struct resource *resource;//device里面的硬件资源
};

struct resource {
    resource_size_t start; //资源的起始
    resource_size_t end;   //资源的结束
    const char *name;      //资源的名字
    unsigned long flags;   //资源的类型
    struct resource *parent, *sibling, *child;
};
 

#define IORESOURCE_IO         IO 的内存
#define IORESOURCE_MEM    表述一段物理内存
#define IORESOURCE_IRQ      表示中断

注册platform驱动

编写driver.c思路

  首先定义一个platform_driver结构体变量,然后去实现结构体中的各个成员变量,那么当我们的driver和device匹配成功的时候,就会执行probe函数,所以匹配成功以后的重点就在于probe函数的编写。

struct platform_driver {
  /*当 driver 和 device 匹配成功的时候,就会执行 probe 函数*/
  int (*probe)(struct platform_device *);
  /*当 driver 和 device 任意一个 remove 的时候,就会执行这个函数*/
  int (*remove)(struct platform_device *);
  /*当设备收到 shutdown 命令的时候,就会执行这个函数*/
  void (*shutdown)(struct platform_device *);
  /*当设备收到 suspend 命令的时候,就会执行这个函数*/
  int (*suspend)(struct platform_device *, pm_message_t state);
  /*当设备收到 resume 命令的时候,就会执行这个函数*/
  int (*resume)(struct platform_device *);
  //内置的 device_driver 结构体
  struct device_driver driver;
//该设备驱动支持的设备的列表, 他是通过这个指针去指向 platform_device_id 类型的数组
  const struct platform_device_id *id_table;
};
struct device_driver{
const char *name; //这个是我们匹配时用到的名字
struct bus_type *bus;
struct module *owner;
}

改改

id_table的优先级比driver的.name这个高。

driver.c里的.name的名字要和device.c里面的名字一样

1.driver.c和device.c里面都要定义一个platform_driver和platform_device结构体变量。

2.匹配成功,执行probe函数。。id_table的优先级比driver.name的优先级要高。

平台总线probe函数的编写

编写probe函数的思路:

(1)从 device.c 里面获得硬件资源,因为我们的平台总线将驱动拆成了俩部分,第一部分是 device.c,另一部分是 driver.c。那么匹配成功了之后,driver.c 要从 device.c 中获得硬件资源,那么 driver.c 就是在 probe 函数中获得的。

方法一: 直接获取,不推荐

int beep_probe(struct platform_device *pdev){
printk("beep_probe\n");
return 0;
}

方法二:只用函数获得

extern struct resource * platform_get_resource(struct platform_device *, unsigned int ,unsigned int)

(2)获得硬件资源之后,就可以在 probe 函数中注册杂项/字符设备,完善 file_operation 结构体,并生成设备节点。

注册之前要先登记:

  request()

ls  /sys/bus/platform/devices/

设备树中添加自定义节点

make dtbs  

cp arch/arm/boot/dts/100ask_imx6ul l_mini.dtb ~/nfs_rootfs/decp

sudo cp arch/arm/boot/dts/100ask_i mx6ull_mini.dtb   ~/nfs_rootfs/demo_hcl/

获得设备树文件节点里面资源的步骤:

步骤一:查找我们要找的节点。
步骤二:获取我们需要的属性值。

 与查找节点有关的 OF 函数有 3 个

  • inline struct device_node *of_find_node_by_path(const char *path)
  • struct device_node *of_get_parent(const struct device_node *node)
  • struct device_node *of_get_next_child(const struct device_node *node struct device_node *prev)

获取属性值的 of 函数

  • of_find_property 函数     property *of_find_property(const struct device_node *np,const char *name,int *lenp)

设备树下的platform总线

优先级顺序:

Pinctrl 子系统和 GPIO 子系统

匹配成功后,进到了probe函数。查找我们要查找的节点

注册杂项设备和字符设备,GPIO用杂项设备完成

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h> #include <linux/miscdevice.h>
#include <linux/fs.h> /* for kernel specific devices */
#include <linux/uaccess.h>
#include <linux/io.h> #include <linux/gpio.h>
#include <linux/of_gpio.h> int size;
u32 out_values[2];
struct device_node *test_device_node;
struct property *test_node_property;
unsigned int *virt_gpio_dr;
int led_gpio = 0; int misc_open(struct inode *inode, struct file *file)
{
printk("hello misc_open\n");
return 0;
} int misc_release(struct inode *inode, struct file *file)
{
printk("hello misc_release bye bye\n");
return 0;
} ssize_t misc_read(struct file *file, char __user *ubuf, size_t size, loff_t *loff_t)
{
char kbuf[64] = "hahaha";
if (copy_to_user(ubuf, kbuf, strlen(kbuf)) != 0) //成功了是0
{
printk("copy_to_user error\n");
return -1;
}
return 0;
} ssize_t misc_write(struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t)
{
char kbuf[64] = {0};
if (copy_from_user(kbuf, ubuf, size) != 0) //成功 了是0
{
printk("copy_from_user error\n");
return -1;
}
printk("kbuf is %s\n", kbuf); if (kbuf[0] == 1)
gpio_set_value(led_gpio, 1);
else if (kbuf[0] == 0)
gpio_set_value(led_gpio, 0); return 0;
} struct file_operations misc_fops = {
.owner = THIS_MODULE,
.open = misc_open,
.release = misc_release,
.read = misc_read,
.write = misc_write }; struct miscdevice misc_dev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "hello_misc", //
.fops = &misc_fops}; int led_probe(struct platform_device *pdev)
{
int ret = 0;
printk("led_probe\n"); /*查找我们要的节点*/
test_device_node = of_find_node_by_path("/test");
if (test_device_node == NULL)
{
printk("of_find_node_by_path is error~ \n");
return -1;
} led_gpio = of_get_named_gpio(test_device_node, "led-gpio", 0);
if(led_gpio <0){
printk("of_get_named_gpio is error~ \n");
return -1;
}
printk("led_gpio is %d \n", led_gpio); ret = gpio_request(led_gpio, "led");
if(ret <0){
printk("gpio_request is error~ \n");
return -1;
} gpio_direction_output(led_gpio, 1); //杂项设备
ret = misc_register(&misc_dev);
if (ret < 0)
{
printk("misc registe is error\n");
return -1;
} return 0;
} int led_remove(struct platform_device *pdev)
{
misc_deregister(&misc_dev);
gpio_free(led_gpio);
printk("led_remove \n");
return 0;
} const struct platform_device_id led_id_table = {
.name = "led_test"}; const struct of_device_id of_match_table[]={
{.compatible = "test1234"},
{}
};
struct platform_driver led_device = {
.probe = led_probe,
.remove = led_remove,
.driver = {
.owner = THIS_MODULE,
.name = "hhh",
.of_match_table = of_match_table
},
// const struct platform_device_id *id_table;
//如果id_table的优先级比driver的.name这个高,就不能匹配成功
.id_table = &led_id_table
}; static int led_driver_init(void)
{
int ret = 0;
ret = platform_driver_register(&led_device);
if (ret < 0)
{
printk("platform_driver_register error \n");
return ret;
}
printk("platform_driver_register OK~ \n");
return 0;
} static void led_driver_exit(void)
{
printk("bye bye\n");
platform_driver_unregister(&led_device);
}
module_init(led_driver_init);
module_exit(led_driver_exit); MODULE_LICENSE("GPL");

IOCTL接口

1.什么是unlocked_ioctl接口?

 unlocked_ioctl就是ioctl接口,但是功能和对应的系统调用均没有发生变化。

2.unlocked_ioctl 和 read/write 函数有什么异同呢?

相同点:都可以往内核写数据。

不同点:read 函数只能完成读的功能,write 只能完成写的功能。读取大数据的时候效率高。ioctl 既可以读也可以写,读取大数据的时候效率不高。

3.unlocked_ioctl 接口命令规则

第一个分区:0-7,  命令的编号,范围是 0-255.
第二个分区:8-15,命令的幻数。
第一个分区和第二个分区主要作用是用来区分命令的。
第三个分区:16-29  表示传递的数据大小。

第四个分区:30-31 代表读写的方向。
00:表示用户程序和驱动程序没有数据传递
10:表示用户程序从驱动里面读数据
01:表示用户程序向驱动里面写数据
11:先写数据到驱动里面然后在从驱动里面把数据读出来。

中断基础概念

在设备树里面配置中断的时候只需要两个步骤即可:

1.把管脚设置为gpio功能。

2.使用 interrupt-parent 和 interrupts 属性来描述中断。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h> struct device_node *test_device_node;
struct property *test_node_property;
int gpio_nu;//GPIO编号
int irq; //中断号 irqreturn_t test_key(int irq, void *args)
{
printk("test_key hhh\n");
return IRQ_HANDLED;
} int led_probe(struct platform_device *pdev)
{
int ret = 0; printk("led_probe\n"); //printk("node name is %s\n", pdev->dev.of_node->name); /*查找我们要的节点*/
test_device_node = of_find_node_by_path("/test_key");
// if (test_device_node == NULL)
// {
// printk("of_find_node_by_path is error~ \n");
// return -1;
// }
gpio_nu = of_get_named_gpio(test_device_node, "gpios", 0);
if(gpio_nu < 0){
printk("of_get_named_gpio is error~ \n");
return -1;
} gpio_direction_input(gpio_nu);
irq = gpio_to_irq(gpio_nu); ret = request_irq(irq, test_key, IRQF_TRIGGER_RISING, "test_key", NULL);
if(ret < 0){
printk("request_irq is error~ \n");
return -1;
} printk("irq is %d \n", irq); return 0;
} int led_remove(struct platform_device *pdev)
{
printk("led_remove \n");
return 0;
} const struct platform_device_id led_id_table = {
.name = "led_test"}; const struct of_device_id of_match_table[]={
{.compatible = "keys"},
{}
}; struct platform_driver led_device = {
.probe = led_probe,
.remove = led_remove,
.driver = {
.owner = THIS_MODULE,
.name = "hhh",
.of_match_table = of_match_table
},
// const struct platform_device_id *id_table;
//如果id_table的优先级比driver的.name这个高,就不能匹配成功
.id_table = &led_id_table }; static int led_driver_init(void)
{
int ret = 0;
ret = platform_driver_register(&led_device);
if (ret < 0)
{
printk("platform_driver_register error \n");
return ret;
}
printk("platform_driver_register OK~ \n");
return 0;
} static void led_driver_exit(void)
{
printk("bye bye\n");
free_irq(irq, NULL);
platform_driver_unregister(&led_device);
}
module_init(led_driver_init);
module_exit(led_driver_exit); MODULE_LICENSE("GPL");

按键中断

中断下文之tasklet

步骤一:定义一个 tasklet 结构体
步骤二:动态初始化 tasklet
步骤三:编写 tasklet 绑定的函数
步骤四:在中断上文调用 tasklet
步骤五:卸载模块的时候删除 tasklet

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
#include <linux/of_irq.h> struct device_node *test_device_node;
struct property *test_node_property;
struct tasklet_struct key_test;
int gpio_nu;//GPIO编号
int irq; //中断号 void test(unsigned long data)
{
int i = data;
printk("i is %d\n", i);
while(i--)
printk("test_key is %d\n"
, i);
}
irqreturn_t test_key(int irq, void *args)
{
printk("start~\n");
tasklet_schedule(&key_test);
printk("end~\n");
return IRQ_HANDLED;
} int led_probe(struct platform_device *pdev)
{
int ret = 0; printk("led_probe\n"); //printk("node name is %s\n", pdev->dev.of_node->name); /*查找我们要的节点*/
test_device_node = of_find_node_by_path("/test_key");
if (test_device_node == NULL)
{
printk("of_find_node_by_path is error~ \n");
return -1;
} /** 获得GPIO的编号*/
gpio_nu = of_get_named_gpio(test_device_node, "gpios", 0);
if(gpio_nu < 0){
printk("of_get_named_gpio is error~ \n");
return -1;
} /*设置GPIO的方向*/
gpio_direction_input(gpio_nu); /*获得中断号*/
irq = gpio_to_irq(gpio_nu);
//irq = irq_of_parse_and_map(test_device_node, 0);
printk("irq is %d \n", irq); /*申请中断*/
ret = request_irq(irq, test_key, IRQF_TRIGGER_RISING, "test_key", NULL);
if(ret < 0){
printk("request_irq is error~ \n");
return -1;
} tasklet_init(&key_test, test, 100); return 0;
} int led_remove(struct platform_device *pdev)
{
printk("led_remove \n");
return 0;
} const struct platform_device_id led_id_table = {
.name = "led_test"}; const struct of_device_id of_match_table[]={
{.compatible = "keys"},
{}
}; struct platform_driver led_device = {
.probe = led_probe,
.remove = led_remove,
.driver = {
.owner = THIS_MODULE,
.name = "hhh",
.of_match_table = of_match_table
},
// const struct platform_device_id *id_table;
//如果id_table的优先级比driver的.name这个高,就不能匹配成功
.id_table = &led_id_table
}; static int led_driver_init(void)
{
int ret = 0;
ret = platform_driver_register(&led_device);
if (ret < 0)
{
printk("platform_driver_register error \n");
return ret;
}
printk("platform_driver_register OK~ \n");
return 0;
} static void led_driver_exit(void)
{
printk("bye bye\n");
free_irq(irq, NULL);
tasklet_kill(&key_test);
platform_driver_unregister(&led_device);
}
module_init(led_driver_init);
module_exit(led_driver_exit); MODULE_LICENSE("GPL");

等待队列

阻塞:  操作是指在执行设备操作时,若不能获得资源,则挂起进程直到满足可操作的条件后再进行操作。被挂起的进程进入睡眠状态,被从调度器的运行队列移走,直到等待的条件被满足。

非阻塞:  操作的进程在不能进行设备操作时,并不挂起,它要么放弃,要么不停地查询,直至可以进行操作为止。

irqreturn_t test_key(int irq, void *args)
{
value = !value;
wq_flags = 1;
wake_up(&key_wq);
return IRQ_RETVAL(IRQ_HANDLED);
}

唤醒的时候不会立马解除阻塞,先去判断wq_flags是否达成,如果还是0,不会解除,如果标志位为1 解除。

工作队列

工作队列(workqueue)是实现中断下文的机制之一,是一种将工作推后执行的形式。那工作队列和我们之前学的 tasklet 机制有什么不同呢?tasklet 也是实现中断下文的机制。他们俩个最主要的区别是 tasklet不能休眠,而工作队列是可以休眠的。所以,tasklet 可以用来处理比较耗时间的事情,而工作队列可以处理非常复杂并且更耗时间的事情。

Linux 系统在启动期间会创建内核线程,该线程创建以后就处于 sleep 状态,然后这个线程会一直去队列里面读,看看有没有任务,如果有就执行,如果没有就休眠。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
#include <linux/of_irq.h>
#include <linux/workqueue.h> struct device_node *test_device_node;
struct property *test_node_property;
//struct tasklet_struct key_test;
struct work_struct key_test;
int gpio_nu;//GPIO编号
int irq; //中断号 void test(struct work_struct *data)
{
int i = 100;
printk("i is %d\n", i);
while(i--)
printk("test_key is %d\n"
, i);
}
irqreturn_t test_key(int irq, void *args)
{
printk("start~\n");
//tasklet_schedule(&key_test);
//调度
schedule_work(&key_test);
printk("end~\n");
return IRQ_HANDLED;
} int led_probe(struct platform_device *pdev)
{
int ret = 0; printk("led_probe\n"); //printk("node name is %s\n", pdev->dev.of_node->name); /*查找我们要的节点*/
test_device_node = of_find_node_by_path("/test_key");
if (test_device_node == NULL)
{
printk("of_find_node_by_path is error~ \n");
return -1;
} /** 获得GPIO的编号*/
gpio_nu = of_get_named_gpio(test_device_node, "gpios", 0);
if(gpio_nu < 0){
printk("of_get_named_gpio is error~ \n");
return -1;
} /*设置GPIO的方向*/
gpio_direction_input(gpio_nu); /*获得中断号*/
//irq = gpio_to_irq(gpio_nu);
irq = irq_of_parse_and_map(test_device_node, 0);
printk("irq is %d \n", irq); /*申请中断*/
ret = request_irq(irq, test_key, IRQF_TRIGGER_RISING, "test_key", NULL);
if(ret < 0){
printk("request_irq is error~ \n");
return -1;
} //tasklet_init(&key_test, test, 100);
INIT_WORK(&key_test, test); return 0;
} int led_remove(struct platform_device *pdev)
{
printk("led_remove \n");
return 0;
} const struct platform_device_id led_id_table = {
.name = "led_test"}; const struct of_device_id of_match_table[]={
{.compatible = "keys"},
{}
}; struct platform_driver led_device = {
.probe = led_probe,
.remove = led_remove,
.driver = {
.owner = THIS_MODULE,
.name = "hhh",
.of_match_table = of_match_table
},
// const struct platform_device_id *id_table;
//如果id_table的优先级比driver的.name这个高,就不能匹配成功
.id_table = &led_id_table
}; static int led_driver_init(void)
{
int ret = 0;
ret = platform_driver_register(&led_device);
if (ret < 0)
{
printk("platform_driver_register error \n");
return ret;
}
printk("platform_driver_register OK~ \n");
return 0;
} static void led_driver_exit(void)
{
printk("bye bye\n");
free_irq(irq, NULL);
//tasklet_kill(&key_test);
platform_driver_unregister(&led_device);
}
module_init(led_driver_init);
module_exit(led_driver_exit); MODULE_LICENSE("GPL");

内核定时器

expires=jiffies+ 1*HZ

#include <linux/init.h>
#include <linux/module.h>
#include <linux/timer.h> static void timer_function(unsigned long data);
DEFINE_TIMER(test_timer, timer_function, 0, 0
);

/*现在是10:00,定时了一分钟,到了10:01进入了
超时处理函数,超时处理函数里面又使用了mod_timer()
把它设置了10:02,10:02进来设置成10:03...*/

static void timer_function(unsigned long data)
{
printk("This is time_function!\n");
mod_timer(&test_timer, jiffies+ 3*HZ);
}
static int hello_init(void)
{
printk("hello world!\n");
test_timer.expires=jiffies+ 3*HZ;
add_timer(&test_timer);//启动定时器
return 0;
} static void hello_exit(void)
{
printk("bye bye\n");
del_timer(&test_timer);
}
module_init(hello_init);
module_exit(hello_exit); MODULE_LICENSE("GPL");

按键消抖实验

按键按下,第一次进入中断,会定时20ms,20ms超时后,就会在超时处理函数判断当前电容是否低电平,如果是低电平,就执行想要执行的

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
#include <linux/of_irq.h>
#include <linux/timer.h> static void timer_function(unsigned long data);
DEFINE_TIMER(test_timer, timer_function, 0, 0); struct device_node *test_device_node;
struct property *test_node_property;
int gpio_nu; // GPIO编号
int irq; //中断号 static void timer_function(unsigned long data)
{
printk("This is tim e_function!\n");
//mod_timer(&test_timer, jiffies + 1 * HZ); //周期性定时 } irqreturn_t test_key(int irq, void *args)
{
printk("test_key hhh\n");
//20ms
test_timer.expires = jiffies + msecs_to_jiffies(20); //定义时间点
add_timer(&test_timer);//添加到内核里面

return IRQ_HANDLED;
} int led_probe(struct platform_device *pdev)
{
int ret = 0; printk("led_probe\n"); // printk("node name is %s\n", pdev->dev.of_node->name); /*查找我们要的节点*/
test_device_node = of_find_node_by_path("/test_key");
if (test_device_node == NULL)
{
printk("of_find_node_by_path is error~ \n");
return -1;
} /** 获得GPIO的编号*/
gpio_nu = of_get_named_gpio(test_device_node, "gpios", 0);
if (gpio_nu < 0)
{
printk("of_get_named_gpio is error~ \n");
return -1;
} /*设置GPIO的方向*/
gpio_direction_input(gpio_nu); /*获得中断号*/
// irq = gpio_to_irq(gpio_nu);
irq = irq_of_parse_and_map(test_device_node, 0);
printk("irq is %d \n", irq); /*申请中断*/
ret = request_irq(irq, test_key, IRQF_TRIGGER_RISING, "test_key", NULL);
if (ret < 0)
{
printk("request_irq is error~ \n");
return -1;
} // printk("irq is %d \n", irq); return 0;
} int led_remove(struct platform_device *pdev)
{ printk("led_remove \n");
return 0;
} const struct platform_device_id led_id_table = {
.name = "led_test"}; const struct of_device_id of_match_table[] = {
{.compatible = "keys"},
{}}; struct platform_driver led_device = {
.probe = led_probe,
.remove = led_remove,
.driver = {
.owner = THIS_MODULE,
.name = "hhh",
.of_match_table = of_match_table},
// const struct platform_device_id *id_table;
//如果id_table的优先级比driver的.name这个高,就不能匹配成功
.id_table = &led_id_table}; static int led_driver_init(void)
{
int ret = 0;
ret = platform_driver_register(&led_device);
if (ret < 0)
{
printk("platform_driver_register error \n");
return ret;
}
printk("platform_driver_register OK~ \n");
return 0;
} static void led_driver_exit(void)
{
printk("bye bye\n");
free_irq(irq, NULL);
platform_driver_unregister(&led_device);
}
module_init(led_driver_init);
module_exit(led_driver_exit); MODULE_LICENSE("GPL");

输入子系统

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/input.h>
int main(int argc, char *argv[])
{
int fd;
struct input_event test_event; fd = open("/dev/input/event1", O_RDWR); if (fd < 0)
{
perror("open error");
return fd;
} while(1)
{
read(fd, &test_event, sizeof(test_event));
if(test_event.type == EV_KEY){
printf("type is %#x\n", test_event.type);
printf("value is %#x\n", test_event.value);
}
}
return 0;
}

(二)

应用层实现I2C通信

IO 模型

A钓鱼不干其他的事情,等着

B钓鱼看书,画画...

C拿了几个鱼竿钓鱼

D在鱼竿上系了一个铃铛

A请助手B钓鱼

最新文章

  1. webSocket实现web及时聊天的例子
  2. Monkey学习(1)环境搭建
  3. Azkaban遇到的坑-installation Failed.Error chunking
  4. IndexedDB 增删改查 简单的库
  5. requirejs 打包参数
  6. C# DbHelperSQLP,操作不同的数据库帮助类 (转载)
  7. Express4 Route笔记
  8. matlab-----均值滤波函数的实现
  9. BZOJ 1266: [AHOI2006]上学路线route
  10. javascript的八张图
  11. 小白——java基础之数据类型
  12. mininet中加载ECN
  13. java aes CBC的填充方式发现
  14. Ubuntu如何使用Vscode写C++代码
  15. Mysql对用户的操作
  16. [BZOJ2252]矩阵距离(BFS)
  17. AD9361寄存器配置顺序,循环模式,自收自发
  18. 第三个Sprint冲刺第3天
  19. Linux高级文本处理命令
  20. nginx 限流配置

热门文章

  1. RocketMQ - 消费者启动机制
  2. Balanced Team
  3. GPT接入飞书
  4. Python常见面试题006 类方法、类实例方法、静态方法有何区别?
  5. 开源分布式任务调度系统就选:DolphinScheduler
  6. mogdb的一主两备
  7. ubuntu18.04开机后NVIDIA显卡驱动加载失败
  8. [Unity]利用Mesh绘制简单的可被遮挡,可以探测的攻击指示器
  9. Java面向对象之什么是继承?
  10. RestTemplate 远程服务调用