字符设备驱动程序的编写_点亮LED灯

自学或者看别人已经写好的代码有时会有疑惑,几百行几千行的代码是怎么敲出来的,对于初学者时常有这样的疑惑。看过别人敲代码才知道,程序不是一句一句写的,而是一个函数一个函数写,这个道理我学了好久才懂,看这边的文章的人如果你是初学者,希望你少走弯路。

-----------------------------------------------------------------------------------------------

应用程序、库、内核 、驱动程序之间的关系


1) 应用程序调用应用程序函数库完成功能
2) 应用程序以文件形式访问各种资源
3) 应用程序函数库
4) 部分函数直接完成功能
    部分函数通过系统调用由内核完成
5) 内核处理系统调用,调用设备驱动程序
6) 设备驱动直接与硬件通信

注意:

1. 系统调用是内核与应用程序之间的接口

2. 设备驱动程序是内核与硬件之间的接口

-----------------------------------------------------------------------------------------------

这个程序包含的文件包括:

test.c // 驱动程序

app.c //应用程序

io_cmd.h //头文件

Makefile //这个你懂的....

install.sh //脚本文件

-----------------------------------------------------------------------------------------------

test.c 的编写如下

1. 最基本的框架:

#include <linux/module.h>
#include <linux/init.h>

module_init();
module_exit();
MODULE_LICENSE("GPL");

2. 添加两个 module 里面的函数:

#include <linux/module.h>
#include <linux/init.h>

int test_init(void)
{
	return 0;
}

void test_exit(void)
{

}

module_init(test_init);
module_exit(test_exit);
MODULE_LICENSE("GPL");
3. 定义一些要用到的变量:

#include <linux/module.h>
#include <linux/init.h>

unsigned long virt, phys;	//定义两个变量

//宏定义两个寄存器的地址
#define GPJ2CON		*((volatile unsigned long *) virt)
#define GPJ2DAT		*((volatile unsigned long *) (virt + 4))

int test_init(void)
{
	phys = 0xE0200280;	//LED灯的控制地址

	return 0;
}

void test_exit(void)
{

}

module_init(test_init);
module_exit(test_exit);
MODULE_LICENSE("GPL");

4. 填充两个 test 函数:

#include <linux/module.h>
#include <linux/init.h>

unsigned long virt, phys;	//定义两个变量

//宏定义两个寄存器的地址
#define GPJ2CON		*((volatile unsigned long *) virt)
#define GPJ2DAT		*((volatile unsigned long *) (virt + 4))

int test_init(void)
{
	int ret;
	phys = 0xE0200280;	// LED灯的控制地址
	alloc_io_address();	// I/O内存映射申请函数
	init_led();		//初始化LED硬件寄存器用

	ret = register_chrdev(241, "led-driver", test_fops);	//向内核注册字符设备
	if(ret)
	{
		printk("register char failed\n");
	}

	return 0;
}

void test_exit(void)
{
	destroy_io_address();	// I/O内存映射释放函数
	unregister_chrdev(241, "test_driver");	//从内核注销字符设备
}

module_init(test_init);
module_exit(test_exit);
MODULE_LICENSE("GPL");
5. 第四步中两个 test 函数增加了不少函数, 写的时候注意要对应来写,申请VS释放 / 注册VS注销,下面一个个实例化 test 里面的内容。

#include <linux/module.h>
#include <linux/init.h>

unsigned long virt, phys;	//定义两个变量 phys 为硬件地址, virt 映射的虚拟地址

//宏定义两个寄存器的地址
#define GPJ2CON		*((volatile unsigned long *) virt)
#define GPJ2DAT		*((volatile unsigned long *) (virt + 4))

int alloc_io_address(void)
{
	request_mem_region(phys, 8, "led-driver"); // 申请虚拟内存
	virt = ioremap(phys, 8); // 将一个IO地址空间映射到内核的虚拟地址空间上去
}

void destroy_io_address(void)
{
	iounmap(virt);	// 释放映射出啦的虚拟地址空间
	release_mem_region(phys, 8); // 释放虚拟内存
}

//初始化硬件寄存器
void init_led(void)
{
	GPJ2CON &= ~(0xffff << 0);
	GPJ2CON |= (0x1111 << 0);
}

//这个字符设备驱动的函数集合的结构体,用来连接驱动程序与系统调用
struct file_operations test_fops = {
	.ioctl = test_inctl,
};

int test_init(void)
{
	int ret;
	phys = 0xE0200280;	// LED灯的控制地址
	alloc_io_address();	// I/O内存映射申请函数
	init_led();		//初始化LED硬件寄存器用

	ret = register_chrdev(241, "led-driver", test_fops);	//向内核注册字符设备
	if(ret)
	{
		printk("register char failed\n");
	}

	return 0;
}

void test_exit(void)
{
	destroy_io_address();	// I/O内存映射释放函数
	unregister_chrdev(241, "test_driver");	//从内核注销字符设备
}

module_init(test_init);
module_exit(test_exit);
MODULE_LICENSE("GPL");
6. 第五步中除了 实例化 两个 test 里面的函数,还加了一个 struct file_operations test_fops 的结构体。然后,要实例化这个结构体里面的 I/O 控制函数。

#include <linux/module.h>
#include <linux/init.h>
#include <liunx/fs.h>
#include <linux/ioport.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include "io_cmd.h"

unsigned long virt, phys;	//定义两个变量 phys 为硬件地址, virt 映射的虚拟地址

//宏定义两个寄存器的地址
#define GPJ2CON		*((volatile unsigned long *) virt)
#define GPJ2DAT		*((volatile unsigned long *) (virt + 4))

int alloc_io_address(void)
{
	request_mem_region(phys, 8, "led-driver"); // 申请虚拟内存
	virt = ioremap(phys, 8); // 将一个IO地址空间映射到内核的虚拟地址空间上去
}

void destroy_io_address(void)
{
	iounmap(virt);	// 释放映射出啦的虚拟地址空间
	release_mem_region(phys, 8); // 释放虚拟内存
}

//初始化硬件寄存器
void init_led(void)
{
	GPJ2CON &= ~(0xffff << 0);
	GPJ2CON |= (0x1111 << 0);
}

//四个LED灯亮
void led_on(void)
{
	GPJ2DAT &= ~(0xf << 0);
}

//四个LED灯灭了
void led_off(void)
{
	GPJ2DAT |= (0xf << 0);
}

//延时函数
void delay(volatile int time)
{
	volatile int i, j;
	for(; time > 0; time--)
	{
		for(i = 0; i < 1000; i++)
			for(j = 0; i < 1000; j++);
	}
}

//流水灯
void led_water(void)
{
	char buf[4] = {0xe, 0xd, 0xb, 0x7};
	int time = 20;
	volatile int i;

	for(; time > 0; time--)
	{
		for(i = 0; i < 4; i++)
		{
			GPJ2DAT = buf[i];
			delay(10);
		}
	}
}

int test_ioctl(struct inode *node, struct file *filp, unsigned int cmd, unsigned long data)
{
	switch(cmd)
	{
		case LED_ON: //这里用了三个宏定义,参考 io_cmd.h 文件
			led_on();
			break;
		case LED_OFF:
			led_off();
			break;
		case LED_WATER:
			led_water();
			break;
		default:
			printk("unknow io cmd\n");
			return -1;
	}

	return 0;
}

//这个字符设备驱动的函数集合的结构体,用来连接驱动程序与系统调用
struct file_operations test_fops = {
	.ioctl = test_inctl,
};

int test_init(void)
{
	int ret;
	phys = 0xE0200280;	// LED灯的控制地址
	alloc_io_address();	// I/O内存映射申请函数
	init_led();		//初始化LED硬件寄存器用

	ret = register_chrdev(241, "led-driver", test_fops);	//向内核注册字符设备
	if(ret)
	{
		printk("register char failed\n");
	}

	return 0;
}

void test_exit(void)
{
	destroy_io_address();	// I/O内存映射释放函数
	unregister_chrdev(241, "test_driver");	//从内核注销字符设备
}

module_init(test_init);
module_exit(test_exit);
MODULE_LICENSE("GPL");

补充 led 函数的实现,test.c 已经编写完成。

-----------------------------------------------------------------------------------

app.c  的代码如下:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <strings.h>
#include "io_cmd.h"

int main(int argc, char *argv[])
{
	 int fd, ret;
	 char buf[40];

	 fd = open("/dev/test", O_RDWR);
	 if(fd < 0)
	 {
		 perror("<app> open failed\n");
	 }
	

	if(!strncasecmp("on", argv[1], 2))
		ioctl(fd, LED_ON);

	if(!strncasecmp("off", argv[1], 3))
		ioctl(fd, LED_OFF);

	if(!strncasecmp("water", argv[1], 5))
		ioctl(fd, LED_WATER);
		
	 return 0;
}
------------------------------------------------------------------------------------------

io_cmd.h 的代码如下:

#ifndef _IO_CMD_H
#define _IO_CMD_H

#define LED_ON 		0
#define LED_OFF		1
#define LED_WATER 	4

#endif /* _IO_CMD_H*

---------------------------------------------------------------------------------------------

Makefile 的代码如下:

obj-m +=test.o

KERN = /home/android-kernel-samsung-dev/
ROOTFS = /nfs/mini_rootfs
all:
	make -C $(KERN) M=`pwd` modules

clean:
	make -C $(KERN) M=`pwd` modules clean

install:
	make -C $(KERN) M=`pwd` modules_install INSTALL_MOD_PATH=$(ROOTFS

----------------------------------------------------------------------------------------------

install.sh 脚本文件的代码如下

#!/bin/sh
make
make install
arm-linux-gcc -o app app.c
cp app /nfs/mini_rootfs
-------------------------------------------------------------------------------------------------------------------
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值