linux 设备驱动(一)——字符设备驱动

linux 设备驱动(一)——字符设备驱动

  • 一、概述
    • 1.1 设备驱动介绍
    • 1.2. 设备结构
      • 1.2.1 设备号
      • 1.2.2 驱动层次
    • 1.3 设备驱动对外接口
    • 1.4 设备驱动特点
  • 二、字符设备驱动
    • 2.1. 字符设备驱动简介
    • 2.2. 数据结构
    • 2.3. 字符设备驱动程序主要组成
      • 2.3.1 设备注册(register_chrdev)
      • 2.3.2 设备卸载(unregister_chrdev)
      • 2.3.3 打开设备(open)
      • 2.3.4 释放设备(release)
      • 2.3.5 读、写设备(read/write)
      • 2.3.6 获取内存
      • 2.3.7 打印信息
    • 2.4. proc文件系统
  • 三、LCD设备驱动
    • 3.1 LCD工作原理
    • 3.2 LCD详细
      • 3.2.1 LCD驱动设计
      • 3.2.1 LCD驱动代码

一、概述

1.1 设备驱动介绍

操作系统是通过各种驱动程序驾驭硬件设备的,驱动程序是操作系统级别组成部分,占比60%。linux内核采用可加载模块化设计,核心代码编译进内核,其他代码可选择动态加载。

lsmod 	#可列出当前系统已加载的所有模块;
rmmod  	#卸载当前模块;
insmod  #加载模块;
modprobe#用于加载当前模块;
mknod  	#创建相关模块;

linux的一个杜特地方是把所有设备当作文件进行处理,这类特殊的文件叫做设备文件。设备文件通常挂在在/dev 下面的逻辑设备节点上,这个节点以文件形式存在。

**linux设备文件分为三类:**
字符设备文件	:没有缓冲,可直接读写的设备文件;
块设备文件	:以块的形式写入设备文件;
网络设备文件	:他平面广告BSD socket接口访问,如网卡。

1.2. 设备结构

1.2.1 设备号

mkmod创建设备时,指定了主设备号和次设备号,是设备的标志。

1.2.2 驱动层次

linux的驱动程序是内核的一部分,运行在内核模式。
linux 设备驱动(一)——字符设备驱动_第1张图片

1.3 设备驱动对外接口

linux为不同种类的设备驱动程序提供相应的数据结构,统一接口是驱动呈现出的可装载性和动态性,其中驱动与外界的接口分三部分:

驱动与内核的接口 :fileoperations
驱动与系统改用到的接口 :驱动程序对设备进行初始化
驱动程序与设备的接口 :与具体设备相关。

1.4 设备驱动特点

1. 设备驱动程序是内核的一部分,如果驱动出错,可能导致系统崩溃;
2. 驱动程序与内核之间的接口是标准接口;
3. 使用标准的内核机制与服务;
4. 可装载;
5. 可设置,系统编译强进行相关设置;
6. 动态性;

二、字符设备驱动

2.1. 字符设备驱动简介

设备驱动程序可以使用模块的方式动态加载到内核中去。
驱动开发与应用开发有很大区别,应用开发的入口是main函数,驱动却没有main函数,模块在调用insmod命令时候被加载,此时入口点是init_module函数,通常在该函数中完成设备注册。
同样,调用rmmod函数时候被卸载,此时入口点是cleanup_module函数,在该函数中完成设备的卸载。
在设备驱动被加载之后,应用程序可以对该设备进行一定操作,如读、写。而驱动程序就是用于这些操作的。

模块相关接口:
insmod:设备注册
rmmod:设备卸载
——————————
用户调用:
read:读
write:写
ioctl:控制
——————————

2.2. 数据结构

用户应用程序调用设备的一些功能在设备驱动程序中定义,在中,定义的内核结构体,常见的文件I/O函数入口:

llseek read write readdir poll ioctl mmap open flush release fsync fasync check_media_change revalidate lock

#include 


struct file_operations {
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *filp, char *buff, size_t count, loff_t *offp);
ssize_t (*write) (struct file *filp, const char *buff, size_t count, loff_t *offp);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned
long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, struct dentry *);
int (*fasync) (int, struct file *, int);
int (*check_media_change) (kdev_t dev);
int (*revalidate) (kdev_t dev);
int (*lock) (struct file *, int, struct file_lock *);
};

struct inode 提供了关于设备文件/dev/driver (假设此设备名为 driver)的信息。

struct file {
mode_t f_mode;/*标识文件是否可读或可写,FMODE_READ 或 FMODE_WRITE*/
dev_t f_rdev; /* 用于/dev/tty */
off_t f_pos; /* 当前文件位移 */
unsigned short f_flags; /* 文件标志,如 O_RDONLY、O_NONBLOCK 和 O_SYNC */
unsigned short f_count; /* 打开的文件数目 */
unsigned short f_reada;
struct inode *f_inode; /*指向 inode 的结构指针 */
struct file_operations *f_op;/* 文件索引指针 */
};

2.3. 字符设备驱动程序主要组成

2.3.1 设备注册(register_chrdev)

#include 


// major 主设备号
// name 设备名
// fops 各调用入口点
int register_chrdev(unsigned int major, const char *name,struct file_operations *fops)

调用该函数后就可以向系统申请主设备号,如果register_chrdev 操作成功,设备名就会出现在/proc/devices 文件里;

2.3.2 设备卸载(unregister_chrdev)

#include 


// major 主设备号
// name 设备名
int unregister_chrdev(unsigned int major, const char *name)

2.3.3 打开设备(open)

open函数完成以下功能:
递增计数器 检查特定设备的特殊情况 初始化设备 识别次设备号.

2.3.4 释放设备(release)

注意释放设备和关闭设备是完全不同的。
当一个设备释放设备时候,其他进程还能继续使用该设备,只是该进程暂停对该设备使用;
当一个进程关闭设备后,其他进程要程序打开此设备才能够使用。

2.3.5 读、写设备(read/write)

// filp 文件指针
// buff 指向用户缓冲区
// count 传入数据长度
// offp 用户在文件中的位置
ssize_t (*read) (struct file *filp, char *buff, size_t count, loff_t *offp)
ssize_t (*write) (struct file *filp, const char *buff, size_t count, loff_t *offp)

实现用户空间和内核空间的数据交换的,如下:

#include 

// to 数据目的缓冲区
// from 数据源缓冲区
// count 数据长度
unsigned long copy_to_user(void *to, const void *from, unsigned long count)
unsigned long copy_from_user(void *to, const void *from, unsigned long count)

2.3.6 获取内存

在应用程序中通过malloc函数获取内存,而在设备驱动中动态开辟内存可以有基于内存地址和基于页面为单位的两类。

基于内存地址的动态内存获取:
kmalloc函数,返回的是物理地址,申请大小有限制,必须是2的整次方,且不回对所获取的内存空间清零;

#include 

// Len 希望申请的字节数
// flags 内存控制字
void *kmalloc(unsigned int len,int flags)

// obj 要释放的内存指针
void kfree(void * obj)

基于页为单位的动态内存获取:

get_zeroed_page:获得一个已清零页面
get_free_page:获得一个或几个连续页面
get_dma_pages:获得用于 DMA 传输的页面

#include 


//flags 内存控制字
// order 要求的页面数,以2为底的对数
unsigned long get_zeroed_page(int flags)
unsigned long __get_free_page(int flags)
unsigned long __get_free_page(int flags,unsigned long order)
unsigned long __get_dma_page(int flags,unsigned long order)
unsigned long free_page(unsigned long addr)

2.3.7 打印信息

打印信息是很好的调试手段,与用户空间不通,内核空间要用printk函数打印。

#include 


// fmt 日志级别
int printk(const char * fmt,)

日志级别分类:

KERN_EMERG:紧急时间消息
KERN_ALERT:需要立即采取动作的情况
KERN_CRIT:临界状态,通常涉及严重的硬件或软件操作失败
KERN_ERR:错误报告
KERN_WARNING:对可能出现的问题提出警告 KERN_NOTICE:有必要进行提示的正常情况
KERN_INFO:提示性信息
KERN_DEBUG:调试信息

2.4. proc文件系统

proc 文件系统是一个伪文件系统,它是一种内核和内核模块用来向进程发送信息的机制。
proc文件系统可以让用户和内核内部数据结构进行交互,获得相关进程的有用信息,在运行时通过改变内核参数改变设置。/proc存储在内存而不是硬盘,通过ls查看/proc文件系统内容。
linux 设备驱动(一)——字符设备驱动_第2张图片
也可通过cat /proc 命令查看其中内容。

查看加载模块状况:

root@ubuntu:/media/root$ cat /proc/devices 
Character devices:
  1 mem
  4 /dev/vc/0
  4 tty
  4 ttyS
  5 /dev/tty
  5 /dev/console
  5 /dev/ptmx
  5 ttyprintk
  6 lp
  7 vcs
 10 misc
 13 input
 14 sound/midi
 14 sound/dmmidi
 21 sg
 29 fb
 89 i2c
 99 ppdev
108 ppp
116 alsa
128 ptm
136 pts
180 usb
189 usb_device
204 ttyMAX
226 drm
244 hidraw
245 aux
246 bsg
247 hmm_device
248 watchdog
249 rtc
250 dax
251 dimmctl
252 ndctl
253 tpm
254 gpiochip

Block devices:
  7 loop
  8 sd
  9 md
 11 sr
 65 sd
 66 sd
 67 sd
 68 sd
 69 sd
 70 sd
 71 sd
128 sd
129 sd
130 sd
131 sd
132 sd
133 sd
134 sd
135 sd
253 device-mapper
254 mdp
259 blkext

三、LCD设备驱动

3.1 LCD工作原理

S3C2410LCD 控制器用于传输视频数据和产生必要的控制信号,如 VFRAME、VLINE、VCLK、VM 等。
linux 设备驱动(一)——字符设备驱动_第3张图片
LCD 的寄存器主要有:LCDCON1 寄存器、LCDCON2 寄存器、LCDCON3 寄存器、LCDC-
ON4 寄存器和 LCDCON5 寄存器。

多数 LCD 驱动器都需要与显示器相匹配的帧频率,帧频率计算公式如下:
FrameRate=1/{[(VSPW+1)+(VBPD+1)+(LINEVAL+1)+(VFPD+1)][(HSPW+1)+(HBPD+1)
+(HFPD+l)+(HOZVAL+1)
[2*(CLKVAL+1)/(HCLK)]}

3.2 LCD详细

3.2.1 LCD驱动设计

/* LCD 配置函数 */

static void setup_lcd(void)

/在 LCD 中画一个点/

static void lcd__pixel_set(int x, int y, COLOR color)

/把所有 LCD 图片清零/

void clear_lcd(void)

/显示一个 ascii 符号/

static void write_en(int x, int y, unsigned char* codes, COLOR color)

/显示一个中文字符/

static void write_cn(int x, iht y, unsigned char* codes, COLOR color)
static int lcdexp_open(struct inode *node, struct file *file)

static int lcdexp_read(struct file *file, char *buff, size_t count, Ioff_t *offp)

static int lcdexp_write(struct file *file, const char *buff, size_t count,
Ioff_t *offp)

/lcd ioctl 驱动函数,分类处理 lcd 的各项动作,在每种情况下都会调用前述的关键函数/

static int lcdex_ioctl(struct inode *inode, struct file *file, unsigned int
cmd, unsigned long
arg)
int lcdexp_init(void)
static void _exit lcdexp_exit(void)
module_init(lcdexp_init);
module_exit(lcclexp_exit);
static struct file_operations lcdexp_fops = {
open: lcdexp_open,
read: lcdexp_read,
ioctl: lcdexp_joctl,
write: lcdexp_write,
release: lcdexp_release,
};

3.2.1 LCD驱动代码

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "lcdexp.h"
static unsigned char*, lcd base;
/* LCD 配置函数 */
static void setup_lcd(void)
{
/*在设置 LCD 寄存器之前关闭 LCD*/
LCDEN[12] = 0;
SYSCON1 &= ~0x00001000;
/* 设置 LCD 控制寄存器
* Video Buffer Size[0:12]: 320'240'12 / 128 = 0xlclf
* Line Length[13:18]: 320 / 16 -1 = 0x13
* Pixel Prescale[19:24]: 0x01
* ACPmscale[25:29]: 0x13
* GSEN[30]: =1,Enables gray scale output to LCD
* GSMD[31]: =1,4 bpp ( 16-gray scale )
*/
LCDCON = 0xe60f7clf;
/* 设置 LCD Palette 寄存器 */
PALLSW = 0x76543210;
PALMSW = 0xfedcba98;
/*
* 设置 LCD frame buffer Sets 的起始位置
* 这样, frame buffer 就从 0xc0000000 起始
*/
FBADDR = 0xc;
/*使能 LCD 使之改变 LCD 的配置*/
LCDEN[12] = 1;
SYSCON1 = 0x00001000;
return;
}
/*在 LCD 中画一个点
* x,y: 亮点的坐标
* color:点的颜色
*/
static void lcd__pixel_set(int x, int y, COLOR color)
{
unsigned char* fb_ptr;
COLOR pure_color = OxO000;
/* if the dot is out of the LCD, return */
If (x<0 II x>=32011 y<0 II y>=240){
/*计算点的地址 */
fb_.ptr = lcd base + (x+y*320)*12/8;
/*把版面上的点映射到帧缓冲中(frame buffer)*/
if (x & 0xl ) (
pure_color = ( color & 0x000f ) < < 4;
*fb_ptr &= 0x0f;
*fb_ptr I= pure_color;
pure_color = ( color & 0x0ff0 ) >> 4;
*(fb_ptr+l) = 0xff & pure_color;
} else {
pure_color = color & 0x00ff;
*fb_ptr = 0xff & pure_color;
pure_color = (color & 0x0f00 ) >> 8;
*(fb_ptr+l) &= OxfO;
*(fb_ptr+l) |= pure_color;
}
return;
}
/*
把所有 LCD 图片清零
*/
void clear_lcd(void)
{
int x;
int y;
for (y=0;y<240; y++) {
for (x=0; x<320; x++) {
//lcd_disp.x = x;
//lcd_disp.y = y;
Lcd_plxel_set(x,y,0x0000);
}
}
Return;
}
/* (start x, start_y): 矩形最左边的坐标
* (end_x, end_y): 矩形最右边的坐标
*/
static void draw_rectangle(int start_x,int start_y,int end_x,int end_y,COLOR
color)
{
draw__vline(start_x, start_y, end_y, color);
draw_vline(end_x, start_y, end_y, color);
draw_hline(start_x, end_x, start_y, color),
draw_hline(start_x, end_x, end_y, color);
return;
}
/*
* (start x, start_y):矩形最上边的坐标
* (end_x, end_y): 矩形最下边的坐标
*/
static void draw_full_rectangle(int start_x, int start_y,int end_x,int
end_y,COLOR color)
{
int i = 0;
int tmp= 0;
tmp= end_x - start_x;
for ( i=0;i<tmp;++i ) {
draw_vline(start_x+i, start_y,end_y, color);
}
return;
}
/*显示一个 ascii 符号
* x,y: 符号起始坐标
* codes: 要显示的字节数
*/
static void write_en(int x, int y, unsigned char* codes, COLOR color)
{
inti = 0;
/* total 16 bytes codes*/
for (i=0;i<16;++i) {
intj = 0;
x += 8;
for ( j=0;j<8;++j ){
--x;
if ((codes[i]>>j) 8, 0x1 ) {
lcd_pixel_set(x, y, color);
}
}
/*移到下一行,x 轴不变,y 轴加一*/
++y;
}
return;
}
/*显示一个中文字符
* x,y: 字符起始点
* codes: 要显示的字节数
* color: 要显示的字符颜色
*/
static void write_cn(int x, iht y, unsigned char* codes, COLOR color)
{
int i;
/* total 2'16 bytes codes */
for(i=0;i< 16;i++) {
int j = 0;
for (j=0;j<2;++j) {
int k = 0;
x += 8(j+1);
for ( k=O;k<8;++k ){
--x;
if ( ( codes[2*i+j] >> k) &0xl ) {
Icd_pixel_set(x,y,color);
}
}
}
x-= 8;
++y;
}
return;
}
static int lcdexp_open(struct inode *node, struct file *file)
{
return 0;
}
static int lcdexp_read(struct file *file, char *buff, size_t count, Ioff_t *offp)
{
return 0;
}
static int lcdexp_write(struct file *file, const char *buff, size_t count,
Ioff_t *offp)
{
return 0;
}
/*lcd ioctl 驱动函数,分类处理 lcd 的各项动作,在每种情况下都会调用前述的关键函数*/
static int lcdex_ioctl(struct inode *inode, struct file *file, unsigned int
cmd, unsigned long
arg)
{
switch ( cmd ) {
case LCD_Clear:/*lcd 清屏*/
{
clear_lcd();
break;
}
case LCD_Pixel_Set /*lcd 象素设置*/
{
struct lcd_display pixel_display;
if(copy_from_user(&pixel_display,(struct Icd_display*)arg,sizeof
(struct lcd_display))) {
printk("copy_frorn_user error!\n"),
return -1;
}
lcd_pixel_set(pixeLdisplay.xl, pixel_display.yl, pixel_display.
color);
break;
}
case LCD_Big_Pixel_Set:/*lcd 高级象素设置*/
{
struct lcd_display b_pixel_display;
if(copy_from_user(&b_pixel_display,(struct Icd_display*)arg,sizeof
(struct lcd_display))) {
printk("copy_from_user error!\n");
return -1;
}
lcd_big_pixel_set(b_pixel_display.xl, b_pixel display.y1, b_pixel_display.
color);
break;
}
case LCD_Draw_Vline:/*lcd 中显示水平线*/
{
struct lcd_display vline_display;
if(copy_from_user(&vline_display,(struct Icd_display*)arg,sizeof
(struct lcd_display)))
{
printk("copy_from_user error!\n");
return -1;
}
draw_vline(vline_display.xl, vline_display.yl, vline_display.y2,
vline_display.color);
}
case LCD_Draw_HLine:/*lcd 中显示垂直线*/
{
struct lcddisplay hline_display;
if ( copy_from_user(Ehline_display,(struct Icd_display*)arg,sizeof
(struct lcd_display))) {
printk("copy_from_user error!\n");
return -1;
}
draw_hline(hline_display.xl,  hline  display.x2,  hline_display.yl,
hline_display.color);
break;
}
Case LCD_Draw_Vdashed:/*lcd 中显示水平随意线*/
{
struct lcd-_display vdashed display;
if(copy_from_user(&vdashed_display,(structlcd_display*)arg,sizeof
(struct lcd_display))) {
printk("copy_from_user error!\n");
return -1;
}
draw hdashed(hdashed-display.xl, hdashed_display.x2, hdashed_
display.yl,
vdashed_display.color);
break;
}
Case LCD_Draw_HDdashed:/*lcd 中显示垂直随意线*/
{
struct lcd_display hdashed display;
if(copy_from_user(&hdashed_display,(structlcd_display*)arg,sizeof
(struct lcd_display))) {
printk("copy_from_user error!\n");
return -1;
}
draw hdashed(hdashed-display.xl, hdashed_display.x2, hdashed_
display.yl,
vdashed_display.color);
break;
}
case LCD_Draw_Rectangle:/*lcd 中显示矩阵*/
{
struct/cd-display rect display;
if ( copy_from_user(&rect_display,(struct Icd_display*)arg,sizeof
(struct lcd_display))) {
printk("copy_from_user error!\n");
return -1;
}
draw_rectangle(rect_display.xl,rect_display.yl,rect_display.x2,
rect_display.y2,rect_display.color);
break;
}
case LCD_Draw_Full_Rectangle:/*lcd 中显示填充矩阵*/
{
Struct xlcd_display frect_display;
if ( copy_from_user(&frect_display,(struct Icd_display*)arg,sizeof
(struct lcd_display))) {
printk("copy_from_user error!\n");
}
draw_full_rectangle(frect_display.xl,
frect_display.yl,frect_display.x2,frect_display.y2, rect_display.color);
break;
}
case LCD Write_EN:/*lcd 英文显示*/
{
Struct lcd_display en_display;
if ( copy_from_user(&en_display,(struct Icd_display*)arg,sizeof
(struct lcd_display))) {
printk("copy_from_user error!\n");
}
return -1;
write_en(en_display.xl, en_.display.yl, en_display.buf, en_display.
color);
break;
}
case LCD Write_CN:/*lcd 中文显示*/
{
struct lcd_display cn_display;
if ( copy_from_user(&cn_display,(struct Icd_display*)arg,sizeof
(struct lcd_display))) {
printk("copy_ffom_user errod\n");
return -1;
}
write_cn(cn_display.xl, cn_display.yl, cn_display.buf, cn_display.
color);
break;
}
default:
printk("unknown cmd\n");
break;
return 0;
}
static struct file_operations lcdexp_fops = {
open: lcdexp_open,
read: lcdexp_read,
ioctl: lcdexp_joctl,
write: lcdexp_write,
release: lcdexp_release,
};
int lcdexp_init(void)
{
int result;
lcd base = (unsigned char*)0xc0000000;
result = register_chrdev(DEV_MA)OR,"lcdexp",&lcdexp_fops);
if ( result < 0 ) {
printk( KERN_INFO "lcdexp:register Icdexp failed !\n'
return result;
setup_lcd();
for ( i=0;i<320*240*12/8;i++ )
lcd base++ = 0x77;
_lcd_base = (unsigned char*)0xc0000000;
printk("LCD ............ support.\n");
return 0;
}
static void _exit lcdexp_exit(void)
{
/* clear LCD */
unregister_chrdev(DEV_MA.lOR,"lcdexp");
}
module_init(lcdexp_init);
module_exit(lcclexp_exit);

感谢阅读,祝君成功!
-by aiziyou

你可能感兴趣的:(linux驱动,驱动程序)