Linux驱动

1.驱动课程大纲

  • 内核模块
  • 字符设备驱动(做驱动开发的基本要求)
  • 中断、内核定时器(很简单)

2、裸机开发和Linux驱动开发的区别?

裸机开发

驱动开发

相同点

都能操作硬件

不同点

单独编译单独执行

依赖内核编译、执行

裸机同时只能执行一份代码

驱动开发可以同时执行多份代码

裸机只需要一个main函数

驱动需要依赖内核的框架来操作硬件

3、Linux系统组成

硬件层:led 鼠标 键盘 lcd 触摸屏 摄像头 u盘 emmc 猫 路由器 dm9000

4.宏内核和微内核

宏内核:将进程、网络、文件、设备、内存等功能集成到一个内核里

优点:代码运行效率高

缺点:如果一个部分出错整个内核就瘫痪了

eg:Android ubuntu

微内核:只将进程、内存机制集成到内核中,文件、设备、驱动等在操作系统之外

优点:稳定性强

缺点:代码运行效率低

eg:鸿蒙 Window QNX

5.驱动移植

  1. 找驱动文件(新建个空白文件)
  2. 将驱动文件放到Linux内核中(driver->char)
  3. 修改驱动文件所在文件夹下的Makefil
  4. 修改Kconfig
  5. 去顶层目录,指定硬件平台(make fs6818_defconfig)
  6. 图形化界面配置驱动(make menuconfig)
  7. make modules 编译
  8. insmod lcd.ko 安装

6.开始驱动

  • 驱动的框架

入口(安装):资源的申请

出口(卸载):资源的释放

许可证:GPL

第一个驱动程序

#include <linux/init.h>
#include <linux/module.h>
static int __init hello_init(void)
{
	return 0;
}
static void __exit hello_exit(void)
{
}
module_init(hello_init);//入口
module_exit(hello_exit);//出口
MODULE_LICENSE("GPL");//许可证

  • 驱动Makefile编写

#KERNELDIR:=/home/y/linux6818/kernel/kernel-3.4.39/
KERNELDIR:=/lib/modules/$(shell uname -r)/build/
PWD:=$(shell pwd)
all:
        make -C $(KERNELDIR) M=$(PWD) modules
clean:
        make -C $(KERNELDIR) M=$(PWD) clean
obj-m:=hello.o

7、软件安装

找到安装包

激活码:SI3US-719473-71478

使用(配置工程)

配置信息:*.c;*.h;*.S;*.lds;*defconfig;*Makefile;*.mak;*.dts;*.dtsi

使用(查看源码 编辑)

8、打印函数

内核中消息的宏

#define KERN_EMERG "<0>" /* system is unusable */

#define KERN_ALERT "<1>" /* action must be taken immediately */

#define KERN_CRIT "<2>" /* critical conditions */

#define KERN_ERR "<3>" /* error conditions */

#define KERN_WARNING "<4>" /* warning conditions */

#define KERN_NOTICE "<5>" /* normal but significant condition */

#define KERN_INFO "<6>" /* informational */

#define KERN_DEBUG "<7>" /* debug-level messages

查看消息级别命令

cat /proc/sys/kernel/printk

4 4 1 7

终端的级别 消息的默认级别 终端的最大级别 终端的最小级别

#define console_loglevel (console_printk[0])

#define default_message_loglevel (console_printk[1])

#define minimum_console_loglevel (console_printk[2])

#define default_console_loglevel (console_printk[3])

= :赋值 需要等其他文件全部执行完,才执行调用的

:= :立即赋值

+= :附加赋值

?= :询问变量之前是否被赋值过,如果被赋值过本次赋值不成立 否则成立

ubuntu 16

ctrl+alt+[F1~F6] 进入虚拟控制台

ctrl+alt+F7 退出虚拟控制台

ubuntu 18

ctrl+alt+F2进入虚拟控制台

ctrl+alt+F1 退出虚拟控制台

sudo dmesg 查看消息回显

sudo dmesg -C 直接清空消息不回显

sudo dmesg -c 回显后清空

修改系统默认的级别

su root

echo 4 3 1 7 > /proc/sys/kernel/printk

#include <linux/printk.h	>
static int hello_init(void)//入口
{
  printk(KERN_ERR "huan ying guang lin 24071\n");
  return 0;
}
static void hello_exit(void)//出口
{
  printk("bai bai le nin nei\n");
}

9.驱动的多文件编译

  1. 两个.c文件 hello.c add.c
  2. 修改Makefile
obj-m:=demo.o
demo-y+=hello.o add.o

  1. sudo insmod demo.ko(安装)
  2. sudo dmesg(查看回显)

10、模块传递参数

注释:

* Standard types are:

* byte, short, ushort, int, uint, long, ulong (没有找到char!!!!!!!!) //char -->byte

* charp: a character pointer //一个字符指针

* bool: a bool, values 0/1, y/n, Y/N. 布尔类型

* invbool: the above, only sense-reversed (N = true). 反布尔类型 0 为真 1为假

头文件

#include <linux/moduleparam.h>

函数原型

module_param(name, type, perm)

功能: Linux 内核模块编程中的一个宏,用于定义模块参数。当一个内核模块被加载时,这些参数可以从用户空间传递给模块

参数:

name:变量的名字

type:变量的类型

perm:权限 0664 0775 只有读和执行权限

示例:

//int类型传参
int a=10;
module_param(a,int,0664);
static int __init hello_init(void)
{
  printk("this is a: %d\n",a);
  return 0;
}
//使用
//sudo insmod hello.ko a=20

//各种类型写一遍
int a=10;
module_param(a,int,0664);
short b=123;
module_param(b,short,0664);
char c='c';
module_param(c,byte,0664);
char *d=NULL;
module_param(d,charp,0664);
static int __init hello_init(void)
{ 
    printk("sum= %d\n",a);
    printk("sum= %d\n",b);
    printk("sum= %c\n",c);
    printk("sum= %s\n",d);                                                                                                                                                                                 
return 0;
}
static void __exit hello_exit(void)
 {
  printk(KERN_ERR"bai");
}
 module_init(hello_init);
 module_exit(hello_exit);
 MODULE_LICENSE("GPL");
 //使用:
 //sudo insmod hello.ko a=1010 b=20 c=65 p="hello_world"

module_param_array(name, type, nump, perm) 
功能:接收命令行传递的数组
参数:
		@name :数组名
		@type :数组的类型
		@nump :参数的个数,变量的地址
		@perm :权限

int ww[10]={0}; 
int num;
module_param_array(ww,int,&num,0664);
MODULE_PARM_DESC(ww,"this is int array[10]");
static int __init hello_init(void)
{
    int i;
    for(i=0;i<num;i++)
    {
     printk("ww[%d]=%d\n",i,ww[i]);
    }
}
//使用
sudo insmod hello.ko a=121 b=10 c=65 p="hello" ww=1,2,3,4,5

也一样

查看驱动命令:modinfo hello.ko

MODULE_PARM_DESC(_parm, desc)
功能:对变量的功能进行描述
参数:
	@_parm:变量
	@desc :描述字段


示例:
int a=10;
module_param(a,int,0664);
MODULE_PARM_DESC(a,"light");

lsmod查看驱动

找到路径:

/sys/module/驱动模块的名字/parameters$

/sys/module/hello/parameters$

进入su修改

su root

echo 123 > a

echo 10 > b

查看: cat a

11.字符设备驱动

  • 字符设备驱动注册

int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops);

功能:

Linux 内核中用于注册字符设备的函数

参数解释:

major:主设备号。当没有指定主设备号时(大于0时),内核会自动分配一个(等于0时)。

name:设备名称,通常是一个字符串,用于在 /dev 目录下创建对应的设备文件。

fops:一个指向 file_operations 结构体的指针,该结构体定义了一组用于设备操作的函数指针。这些操作包括打开设备、读写设备、释放设备等。

返回值:

(大于0时)成功,返回 0。等于0时,返回主设备号

如果失败,返回一个负的错误码。

void unregister_chrdev(unsigned int major, const char *name)

功能:注销一个字符设备驱动

参数:

@major:主设备号

@name:名字

返回值:无

  • 第一个字符设备驱动

驱动

#include <linux/init.h>
#include <linux/module.h>
#include <linux/printk.h>
#include <linux/fs.h>
unsigned int major=0;
#define CNAME "hello"
ssize_t mycdev_read (struct file * file, char __user * ubuf, size_t len, loff_t * loff)
{
 printk("mycdev read ok\n");
 return 0;
}
ssize_t mycdev_write (struct file *file, const char __user *ubuf, size_t len, loff_t * lo)
{
	printk("mycdev write ok\n");
	return 0;
}
int mycdev_open (struct inode * inode, struct file * file)
{
	printk("mycdev open ok\n");
	return 0;
}
int mycdev_release (struct inode *inode, struct file *file)
{
	printk("mycdev close ok\n");
	return 0;
}

const struct file_operations fops=
{
  .read=mycdev_read,
  .write=mycdev_write,
  .open=mycdev_open,
  .release=mycdev_release,
};
static int __init hello_init(void)
{
	major=register_chrdev(major,CNAME,&fops);
	if(major<0)
	{
 		printk("register chrdev error\n");
		return major;
	}
	return 0;
}
static void __exit hello_exit(void)
{
	unregister_chrdev(major,CNAME);
}
module_init(hello_init);//入口
module_exit(hello_exit);//出口
MODULE_LICENSE("GPL");//许可

应用层

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
char buf[128]={0};
int main(int argc,const char *argv[])
{
  int fd;
  fd=open("./hello",O_RDWR);
  if(fd==-1)
  {
    perror("open error");
    return -1;
  }
  write(fd,buf,sizeof(buf));
  read(fd,buf,sizeof(buf));
  close(fd);
  return 0;
}

12、手动创建设备文件

cat /proc/devices (查看主设备号) 238

sudo mknod hello c/b(c 代表字符设备 b代表块设备)主设备号 次设备号

修改hello权限最高 sudo chmod a+rwx hello

13、应用程序如何将数据传递给驱动(读写的方向是站在用户的角度来说的)

#include <linux/uaccess.h>

int copy_from_user(void *to, const void __user *from, int n)

功能:从用户空间拷贝数据到内核空间

参数:

@to :内核中内存的首地址

@from:用户空间的首地址

@n :拷贝数据的长度(字节)

返回值:成功返回0,失败返回未拷贝的字节的个数

int copy_to_user(void __user *to, const void *from, int n)

功能:从内核空间拷贝数据到用户空间

参数:

@to :用户空间内存的首地址

@from:内核空间的首地址 __user需要加作用是告诉编译器这是用户空间地址

@n :拷贝数据的长度(字节)

返回值:成功返回0,失败返回未拷贝的字节的个数

驱动层

#include <linux/init.h>
#include <linux/module.h>
#include <linux/printk.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
unsigned int major=0;
#define CNAME "hello"
char kbuf[128]={0};
int copy_size=0;
ssize_t mycdev_read (struct file * file, char __user * ubuf, size_t len, loff_t * loff)
{
  if(len>sizeof(kbuf)){
	len=sizeof(kbuf);
  }
  copy_size=copy_to_user(ubuf,kbuf,len);
  if(copy_size)
  {
	printk("copy to user error\n");
	return copy_size;
  }
 //printk("mycdev read ok\n");
 return 0;
}
ssize_t mycdev_write (struct file *file, const char __user *ubuf, size_t len, loff_t * lo)
{
	if(len>sizeof(kbuf)){
	  len=sizeof(kbuf);
	}
    copy_size=copy_from_user(kbuf,ubuf,len);
	if(copy_size)
		{
	  printk("copy from user error\n");
	  return copy_size;
	}
	//printk("mycdev write ok\n");
	return 0;
}
int mycdev_open (struct inode * inode, struct file * file)
{

	printk("mycdev open ok\n");
	return 0;
}
int mycdev_release (struct inode *inode, struct file *file)
{
	printk("mycdev close ok\n");
	return 0;
}

const struct file_operations fops=
{
  .read=mycdev_read,
  .write=mycdev_write,
  .open=mycdev_open,
  .release=mycdev_release,
};
static int __init hello_init(void)
{
	major=register_chrdev(major,CNAME,&fops);
	if(major<0)
	{
 		printk("register chrdev error\n");
		return major;
	}
	return 0;
}
static void __exit hello_exit(void)
{
	unregister_chrdev(major,CNAME);
}
module_init(hello_init);//入口
module_exit(hello_exit);//出口
MODULE_LICENSE("GPL");//许可证

应用层

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
char buf[128]="this is mycdev";
int main(int argc, const char *argv[])
{
	int fd;
	fd=open("./hello",O_RDWR);
	if(fd==-1)
	{
		perror("open error");
		return -1;
	}
	write(fd,buf,sizeof(buf));
	memset(buf,0,sizeof(buf));
	read(fd,buf,sizeof(buf));
	printf("buf is :%s\n",buf);
	close(fd);
	return 0;
}

控制LED灯

8核

*(unsigned int *)0XC001A000 |=1<<28;

*(unsigned int *)0XC001A000&=~(1<<28);

*(unsigned int *)0XC001A004 |=1<<28;

*(unsigned int *)0XC001A024 &=~(3<<28);

  1. C语言

纯地址方式

/*
 *    地址:0xc001A024 数据:0x00000000
 *    地址:0xc001A004 数据:0x10000000
 *    地址:0xc001a000 数据:0x10000000/0x00000000
 *    地址:0xc001a000 数据:0x10000000/0x00000000
 *                */
int main()
{   
  *(unsigned int *)0xc001A024=0x00000000;
  *(unsigned int *)0xc001A004=0x10000000;
 while(1)
 {
  *(unsigned int *)0xc001A000=0x00000000;
  delay_ms(1000);
  *(unsigned int *)0xc001A000=0x10000000;
  delay_ms(1000);
 }
}                                                       

宏定义

#define GPIOAALTFN1 (*(unsigned int *)0xc001A024)
#define GPIOAOUTENB (*(unsigned int *)0xc001A004)
#define GPIOAOUT    (*(unsigned int *)0xc001A000)
int main()
{
  GPIOAALTFN1=0x00000000;
  GPIOAOUTENB=0x10000000;
 while(1)
 { 
  GPIOAOUT=0x00000000;
  delay_ms(1000);
  GPIOAOUT=0x10000000;
  delay_ms(1000);
 }
}                                                      

这时候去数据手册看地址规律,发现每一类比如A的地址是连续的

问C语言里什么的地址是联系的?

这里GPIOAPAD之后有一个没有连续 18 之后1C

typedef struct
{
 unsigned int OUT;
 unsigned int OUTENB;
 unsigned int DETMODE0;
 unsigned int DETMODE1;
 unsigned int INTENB;
 unsigned int DET;
 unsigned int PAD;
 unsigned int Gsssss;
 unsigned int ALTFN0;
 unsigned int ALTFN1;
}gpio;
#define GPIOA (*(gpio *)0xc001A000)
int main()                         
{
  GPIOA.ALTFN1=0x00000000;
  GPIOA.OUTENB=0x10000000;
 while(1)
 {
  GPIOA.OUT=0x00000000;
  delay_ms(1000);
  GPIOA.OUT=0x10000000;
  delay_ms(1000);
 }
}

  • 下载调试程序

1、拷贝.bin文件到windows中

2、开发板和电脑进行硬件连接

串口线的USB端插到电脑的USB口

串口线的串口端插到开发板的UART0端口上

开发板插上电源

3、配置windows超级终端

可以查看配置超级终端的使用说明文档资料中有, 如果串口线第一次使用需要安装串口驱动,串口驱动文件在资料中

4、配置超级终端:

在设备管理器中,查看串口线使用的那个端口号

配置端口属性:

波特率:115200

数据位:8

停止位:1

校验位:无

流控:无

5、开发板上电,超级终端会有打印信息

在倒计时减到0之前按任意键,进入到FS6818#界面

执行命令 loadb 0x43c00000 --》下载二进制文件到内存的0x43c00000

传送--》发送文件--》选择要下载.bin文件,选择Kermit协议 --》 确定下载

执行命令:go 0x43c00000 --》到0x43c00000位置运行代码

经过对寄存器的分析,我们找到了三个寄存器需要配置并且知道了需要的地址和写入的数据,如果是裸机开发这里直接,创建个文件+启动文件,向地址内写值就能够实现功能了,但是有一个问题,这里需要使用Linux所以物理地址无法直接使用,因为Linux使用的是虚拟地址

所以,下面的问题就是考虑如何将物理地址转换为虚拟地址

  • 虚拟地址物理地址转换

驱动如何操作寄存器:

void * ioremap(phys_addr_t offset, unsigned long size)

功能:将物理地址映射成虚拟地址·1

参数:

@offset :要映射的物理地址

@size :大小(字节)

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

void iounmap(void *addr)

功能:取消映射

参数: 1

@addr :虚拟地址

返回值:无

C001A000 | 1<<28 // 配置输出数据1 高电平

C001A000+4 | 1<<28 // 配置为输出模式

C001A000+24 & ~(3<<24) //配置为GPIOA28功能

总结思路

  1. 找地址 找数据
  2. 在入口函数写物理地址转虚拟地址函数
  3. 使用转换后的 地址,向地址中写值
  4. 卸载取消地址映射
  5. 使用

              Makefile->改为使用开发板的内核编译

              make编译

              得到的ko文件放到NFS目录里

              开发板上电-》insmod 驱动

控制红灯

#include <linux/init.h>
#include <linux/module.h>
#include <linux/printk.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
unsigned int major=0;
#define CNAME "hello"
#define rea_base 0xC001A000
char kbuf[128]={0};
int copy_size=0;
unsigned  int *RED_BASE=NULL;
ssize_t mycdev_read (struct file * file, char __user * ubuf, size_t len, loff_t * loff)
{
  if(len>sizeof(kbuf)){
	len=sizeof(kbuf);
  }
  copy_size=copy_to_user(ubuf,kbuf,len);
  if(copy_size)
  {
	printk("copy to user error\n");
	return copy_size;
  }
 //printk("mycdev read ok\n");
 return 0;
}
ssize_t mycdev_write (struct file *file, const char __user *ubuf, size_t len, loff_t * lo)
{
	if(len>sizeof(kbuf)){
	  len=sizeof(kbuf);
	}
    copy_size=copy_from_user(kbuf,ubuf,len);
	if(copy_size)
		{
	  printk("copy from user error\n");
	  return copy_size;
	}
	//printk("mycdev write ok\n");
	return 0;
}
int mycdev_open (struct inode * inode, struct file * file)
{

	printk("mycdev open ok\n");
	return 0;
}
int mycdev_release (struct inode *inode, struct file *file)
{
	printk("mycdev close ok\n");
	return 0;
}

const struct file_operations fops=
{
  .read=mycdev_read,
  .write=mycdev_write,
  .open=mycdev_open,
  .release=mycdev_release,
};
static int __init hello_init(void)
{
	major=register_chrdev(major,CNAME,&fops);
	if(major<0)
	{
 		printk("register chrdev error\n");
		return major;
	}
	RED_BASE=ioremap(rea_base,36);
	if(RED_BASE==NULL)
		{
     printk("rea_base ioremap error\n");
	 return -ENOMEM;
	}
	*RED_BASE &=~(1<<28);
	
*(RED_BASE+1)|=1<<28;
	*(RED_BASE+9) &=~(3<<24);
	return 0;
}
static void __exit hello_exit(void)
{
	iounmap(RED_BASE);
	unregister_chrdev(major,CNAME);
}
module_init(hello_init);//入口
module_exit(hello_exit);//出口
MODULE_LICENSE("GPL");//许可证

14.自动创建设备节点

#include <linux/device.h>

class_create(owner, name)    
功能:
 创建一个class类型的对象
参数:
  @owner  THIS_MODULE·
  @name   类名字
返回值
  可以定义一个struct class的指针变量cls接受返回值,然后通过IS_ERR(cls)判断`
  是否失败,如果成功这个宏返回0,失败返回非9值(可以通过PTR_ERR(cls)来获得
  失败返回的错误码)
 void class_destroy(struct class *cls)
功能:注销一个class类型的对象

struct device *device_create(struct class *class, struct device *parent,dev_t devt, void *drvdata, const char *fmt, ...)
功能:向用户空间提交文件信息
参数:
		@class :录名字
		@parent:NULL
		@devt  :设备号    MKDEV(major,0)
		@drvdata :NULL
		@fmt   :文件的名字
void device_destroy(struct class *class, dev_t devt)
功能:注销文件信息


struct class *class: 指向设备所属的类的指针。

struct device *parent: 指向父设备的指针。在设备树中,一个设备可以有多个父设备。

dev_t devt: 设备的标识符。它是一个在 /dev 目录下用于访问设备的文件名的一部分。

void *drvdata: 一个指向任意数据的指针,该数据将与新创建的设备关联。这个指针可以用于存储与设备相关的私有数据。

const char *fmt, ...: 一个格式字符串,类似于 printf 函数的第一个参数,用于指定如何格式化后续的参数列表。

返回值:成功返回struct device *指针

失败返回错误码指针

static struct class *cls;
static struct device *test_device;

  devno = MKDEV(major,minor);
  cls = class_create(THIS_MODULE,"helloclass");
  if(IS_ERR(cls))
  {
    return PTR_ERR(cls);
  }
  test_device = device_create(cls,NULL,devno,NULL,"hellodevice");
  if(IS_ERR(test_device ))
  {
    return PTR_ERR(test_device);
  }

自动创建设备节点+应用层控制闪灯

#include <linux/init.h>
#include <linux/module.h>
#include <linux/printk.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/device.h>
unsigned int major=0;
#define CNAME "hello"
#define rea_base 0xC001A000
char kbuf[128]={0};
int copy_size=0;
static struct class *cls;
static struct device *test_device;

unsigned  int *RED_BASE=NULL;
ssize_t mycdev_read (struct file * file, char __user * ubuf, size_t len, loff_t * loff)
{
  if(len>sizeof(kbuf)){
	len=sizeof(kbuf);
  }
  copy_size=copy_to_user(ubuf,kbuf,len);
  if(copy_size)
  {
	printk("copy to user error\n");
	return copy_size;
  }
 //printk("mycdev read ok\n");
 return 0;
}
ssize_t mycdev_write (struct file *file, const char __user *ubuf, size_t len, loff_t * lo)
{
	if(len>sizeof(kbuf)){
	  len=sizeof(kbuf);
	}
    copy_size=copy_from_user(kbuf,ubuf,len);
	if(copy_size)
		{
	  printk("copy from user error\n");
	  return copy_size;
	}
	if(kbuf[0]==1)
	{
	 *RED_BASE |=1<<28;
	}
	else
	{
	 *RED_BASE &=~(1<<28);
	}
	//printk("mycdev write ok\n");
	return 0;
}
int mycdev_open (struct inode * inode, struct file * file)
{

	printk("mycdev open ok\n");
	return 0;
}
int mycdev_release (struct inode *inode, struct file *file)
{
	printk("mycdev close ok\n");
	return 0;
}

const struct file_operations fops=
{
  .read=mycdev_read,
  .write=mycdev_write,
  .open=mycdev_open,
  .release=mycdev_release,
};
static int __init hello_init(void)
{
	major=register_chrdev(major,CNAME,&fops);
	if(major<0)
	{
 		printk("register chrdev error\n");
		return major;
	}
	RED_BASE=ioremap(rea_base,36);
	if(RED_BASE==NULL)
		{
     printk("rea_base ioremap error\n");
	 return -ENOMEM;
	}
	*RED_BASE &=~(1<<28);
	
*(RED_BASE+1)
|=1<<28;
	*(RED_BASE+9) &=~(3<<24);
    cls = class_create(THIS_MODULE,"hello");  
	if(IS_ERR(cls))  
	{    
	  return PTR_ERR(cls);  
	}
	test_device =           device_create(cls,NULL,MKDEV(major,0),NULL,"hello");
	if(IS_ERR(test_device ))  
	{    
	  return PTR_ERR(test_device);  
	 }
	return 0;
}
static void __exit hello_exit(void)
{
	device_destroy(cls,MKDEV(major,0));
	class_destroy(cls);
	iounmap(RED_BASE);
	unregister_chrdev(major,CNAME);
}
module_init(hello_init);//入口
module_exit(hello_exit);//出口
MODULE_LICENSE("GPL");//许可证

应用层

char buf[3]={0};
int main()
{
	int fd;
	fd=open("./hello",O_RDWR);
 while(1)  
	 {
    	write(fd,buf,sizeof(buf));
		sleep(1);
		buf[0]=buf[0]?0:1;
	 }
}

15.ioctl函数

注:用户程序所作的只是通过命令码告诉驱动程序它想做什么,至于怎么解释这些命令和怎么实现这些命令,这都是驱动程序要做的事情。驱动程序提供了对ioctl的支持,用户就可以在用户程序中使用ioctl函数控制设备的I/O通道(功能:input output 的控制)

用户:

#include <sys/ioctl.h>

int ioctl(int fd, int request, ...);(RED_ON)

(让点灯的代码变得简洁)

参数:

@fd : 打开文件产生的文件描述符

@request: 请求码(读写|第三个参数传递的字节的个数),

:在sys/ioctl.h中有这个请求码的定义方式。

@... :可写、可不写,如果要写,写一个内存的地址

内核:

long (*unlocked_ioctl) (struct file *, unsigned int,unsigned long)

作用:此函数指针原型位于struct file_operations结构体当中,配合应用层ioctl函数实现指令传递的功能

__IOWR(type,nr,size)类型 序号 大小

#define _IO(type,nr)

_IOC(_IOC_NONE,(type),(nr),0)

#define _IOR(type,nr,size)

_IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))

#define _IOW(type,nr,size)

_IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))

_IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))

#define RDE_LED _IO(type,nr)

(dir,type,nr,size) \

(((dir) << _IOC_DIRSHIFT) | \

((type) << _IOC_TYPESHIFT) | \

((nr) << _IOC_NRSHIFT) | \

((size) << _IOC_SIZESHIFT))

__IOWR(type,nr,size)类型 序号 大小

16、Linux内核中断

原理:

start .s 里有异常向量表

外部中断找IRQ,根据IRQ的中断地址。找到我们使用的函数,比如叫do_irq

然后判断我们的中断号,看我们触发了哪一个中断。

处理中断

清中断

内核: 找到IRQ的标签,然后跳转,跳转时这个名字是写死的(handle_irq),在handle_irq里定义一个数组,irq_desc[],数组的每一个成员变量里存了结构体,irq_xxx。在结构体里面有个函数指针,这个指针指向了我们函数的名字。数组的下标和中断号有关系,这里是中断号但是是软中断号。软中断号是linux内核给分配的中断号。

软中断号,是内核为了兼容各种芯片,而设计的。这里兼容是通过映射实现的,我们不同的板子根据映射关系,使用中断号得到软中断号。

int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev)

功能:注册中断

参数:

@irq : 软中断号 gpio的软中断号 软中断号 = gpio_to_irq(gpio号);A28 27 26 B27

gpio = m*32+n m:那一组 A B C D E 0 1 2 3 4

n:组内的序号

gpioa28 = 0*32+28 (按键)gpiob8 = 1*32+8

gpiob8 =1*32+8 (按键) gpiob16 = 1*32+16

@handler: 中断的处理函数

irqreturn_t (*irq_handler_t)(int irqno, void *dev);

IRQ_NONE //中断没有处理完成

IRQ_HANDLED //中断正常处理完成

@flags :中断的触发方式

#define IRQF_DISABLED 0x00000020 //快速中断

#define IRQF_SHARED 0x00000080 //共享中断

#define IRQF_TRIGGER_RISING 0x00000001

#define IRQF_TRIGGER_FALLING 0x00000002

#define IRQF_TRIGGER_HIGH 0x00000004

#define IRQF_TRIGGER_LOW 0x00000008

@name :名字 cat /proc/interrupts

@dev :向中断处理函数中传递参数 ,如果不想传写个NULL就行

返回值:成功0,失败返回错误码

void free_irq(unsigned int irq, void *dev_id)

功能:注销中断

参数:

@irq :软中断号

@dev_id:向中断处理函数中传递的参数 ,如果上面写的NULL,这里就写NULL

17、问题解决方法

[root@farsight]#insmod farsight_irq.ko

[ 21.262000] request irq146 error

insmod: can't insert 'farsight_irq.ko': Device or resource busy

通过 cat /proc/interrupts

146: GPIO nxp-keypad

154: GPIO nxp-keypad

说明中断号已经被占用了

解决办法:在内核中将这个驱动删掉

如果确定驱动文件的名字是谁?

grep "nxp-keypad" * -nR

arch/arm/mach-s5p6818/include/mach/devices.h:48:

#define DEV_NAME_KEYPAD "nxp-keypad"

grep "DEV_NAME_KEYPAD" * -nR

drivers/input/keyboard/nxp_io_key.c:324: .name = DEV_NAME_KEYPAD,

驱动文件的名字是nxp_io_key.c

如何从内核中将他去掉?

选项菜单的名字?Kconfig

config KEYBOARD_NXP_KEY

tristate "SLsiAP push Keypad support"

make menuconfig

<>SLsiAP push Keypad support

make uImage 重新编译内核

cp arch/arm/boot/uImage ~/tftpboot

18、Linux内核定时器

1.定时器的当前时间如何获取?

jiffies:内核时钟节拍数

jiffies是在板子上电这一刻开始计数,只要

板子不断电,这个值一直在增加(64位)。在

驱动代码中直接使用即可。

2.定时器加1代表走了多长时间?

在内核顶层目录下有.config

CONFIG_HZ=1000

周期 = 1/CONFIG_HZ

周期是1ms;

1.分配的对象

struct timer_list mytimer;

2.对象的初始化

struct timer_list

{

unsigned long expires; //定时的时间

void (*function)(unsigned long); //定时器的处理函数

unsigned long data; //向定时器处理函数中填写的值

};

void timer_function(unsigned long data) //定时器的处理函数

{

}

mytimer.expries = jiffies + 1000; //1s

mytimer.function = timer_function;

mytimer.data = 0;

init_timer(&mytimer); //内核帮你填充你未填充的对象

3.对象的添加定时器

void add_timer(struct timer_list *timer);

//同一个定时器只能被添加一次,

//在你添加定时器的时候定时器就启动了,只会执行一次

int mod_timer(struct timer_list *timer, unsigned long expires)

//再次启动定时器 jiffies+1000

4.对象的删除

int del_timer(struct timer_list *timer)//删除定时器

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值