偶然翻到了五年前学习linux驱动时的笔记,写一下以备不时之需。
字符设备驱动
cdev下的模板
struct leds_drv{
//1.定义cdev结构体
struct cdev cdev;
};
struct leds_drv leds_drv;
//6.创建设备节点
static struct class *ledsdrv_class;
static unsigned int major = 0;
const struct file_operations leds_drv_fops = {
.owner = THIS_MODULE,
};
static int leds_drv_init(void)
{
int ret;
//2.获取设备号
dev_t devno = MKDEV(major, 0);
//3.注册
if (major)
{
ret = register_chrdev_region(devno, 1, "leds_drv");
}
else
{
ret = alloc_chrdev_region(&devno, 4, 1, "leds_drv");
major = MAJOR(devno);
}
if (ret < 0)
{
printk("register failed\n");
goto err1;
}
//4.初始化cdev
cdev_init(&leds_drv.cdev, &leds_drv_fops);
leds_drv.cdev.owner = THIS_MODULE;
leds_drv.cdev.ops = &leds_drv_fops;
//5.添加cdev
ret = cdev_add(&leds_drv.cdev, devno, 1);
if (ret < 0)
{
goto err2;
}
//6.创建设备节点
ledsdrv_class = class_create(THIS_MODULE, "leds_drv");
if (IS_ERR(ledsdrv_class))
{
ret = PTR_ERR(ledsdrv_class);
goto err2;
}
device_create(ledsdrv_class, NULL, MKDEV(major, 4), NULL, "leds_drv");
return 0;
err2:
unregister_chrdev_region(MKDEV(major, 0), 1);
err1:
return -1;
}
static void leds_drv_exit(void)
{
unregister_chrdev_region(MKDEV(major, 0), 1);
device_destroy(ledsdrv_class, MKDEV(major, 0));
class_destroy(ledsdrv_class);
cdev_del(&leds_drv.cdev);
}
module_init(leds_drv_init);
module_exit(leds_drv_exit);
MODULE_LICENSE("GPL");
知识点:
1、获取主次设备号:
MAJOR(dev_t dev); //获取主设备号
MINOR(dev_t dev); //获取次设备号
2、能操作cdev结构体的函数:
void cdev_init(struct cdev *, struct file_operations *); //初始化
struct cdev *cdev_alloc(void); //动态申请一个 cdev 内存
void cdev_put(struct cdev *p);
int cdev_add(struct cdev *, dev_t, unsigned);
void cdev_del(struct cdev *);
次设备号的使用
使用多个次设备号时,应该注册相应个数和创建相应设备节点,否则无法打开/dev/XXX
//3.注册
if (major)
{
ret = register_chrdev_region(devno, 4, "leds_drv");
}
else
{
ret = alloc_chrdev_region(&devno, 0, 4, "leds_drv");
major = MAJOR(devno);
}
//6.创建设备节点
ledsdrv_class = class_create(THIS_MODULE, "leds_drv");
if (IS_ERR(ledsdrv_class))
{
ret = PTR_ERR(ledsdrv_class);
goto err1;
}
int i;
for (i = 0; i < 4; i++)
{
device_create(ledsdrv_class, NULL, MKDEV(major, i), NULL, "leds_drv%d", i);
}
//卸载
unregister_chrdev_region(MKDEV(major, 0), 4);
int i;
for (i = 0; i < 4; i++)
{
device_destroy(ledsdrv_class, MKDEV(major, i));
}
中断的使用
static DECLARE_WAIT_QUEUE_HEAD(buttons_waitq); //创建等待队列头
static volatile int ev_press = 0; //标志位
在open函数里注册中断:
ret = request_irq(IRQ_EINT0, buttons_irq_handle, (IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING), "s1", &key_des[0]);
注:buttons_irq_handle是中断处理函数:
static irqreturn_t buttons_irq_handle(int irq, void *dev_id)
处理函数里要有唤醒休眠进程的处理:
wake_up_interruptible(&buttons_waitq); /* 唤醒休眠的进程 */
return IRQ_RETVAL(IRQ_HANDLED);
在read函数里要有休眠等待中断时间发生函数:
wait_event_interruptible(buttons_waitq, ev_press);
在release函数里取消中断:
free_irq(IRQ_EINT0, &key_des[0]);
定时器
1、定义定时器
struct timer_list buttons_timer;
2、初始化定时器
init_timer(&buttons_timer);
3、指定定时处理函数
buttons_timer.function = buttons_timer_handle; /* timer handler */
4、添加定时器
add_timer(&buttons_timer);
5、修改定时器时间
mod_timer(&buttons_timer, jiffies + HZ/100);
poll和select机制
作用:如果read不到数据不会一直阻塞,而是等待指定时间后返回。
poll函数:
static unsigned int buttons_poll(struct file *file, poll_table *wait)
{
unsigned int ret_mask = 0;
poll_wait(file, &buttons_waitq, wait);
if (1 == ev_press)
{
ret_mask |= POLLIN | POLLRDNORM;
}
return ret_mask;
}
static const struct file_operations buttons_fops = {
.owner = THIS_MODULE,
.poll = buttons_poll,
};
应用程序调用poll:
struct pollfd pfd;
pfd.fd = fd;
pfd.events = POLLIN;
while (1)
{
ret = poll(&pfd, 1, 5000);
if (ret == 0)
{
printf("time out\n");
}
else
{
read(fd, &key_val, 1);
printf("key_val: 0x%x\n", key_val);
}
}
异步IO通知
作用:驱动发生事件后主动通知应用程序
struct fasync_struct *bt_fasync; //定义fasync_struct 结构体
在中断处理函数中:
kill_fasync (&bt_fasync, SIGIO, POLL_IN); //发送信号给应用程序
static int buttons_fasync(int fd, struct file *file, int on)
{
return fasync_helper(fd, file, on, &bt_fasync);
}
static const struct file_operations buttons_fops = {
.owner = THIS_MODULE,
.fasync = buttons_fasync,
};
应用程序端:
信号处理函数:
void button_sig_handler(int argc)
{
}
main函数:
signal(SIGIO, button_sig_handler); //自定义信号处理函数
//设置属性,事件发生后会调用信号处理函数
int flag;
fcntl(fd, F_SETOWN, getpid());
flag = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, FASYNC | flag);
原子操作
原子操作指的是在执行过程中不会被别的代码路径所中断的操作。
常用原子操作函数举例:
**整型原子操作:
1、设置原子变量的值
atomic_t v = ATOMIC_INIT(0); //定义原子变量v并初始化为0
void atomic_set(atomic_t *v, int i); //设置原子变量的值为 i
2、获取原子变量的值
atomic_read(atomic_t *v); //返回原子变量的值
3、原子变量加/减
void atomic_add(int i, atomic_t *v); //原子变量增加 i
void atomic_sub(int i, atomic_t *v); //原子变量减少 i
4、原子变量自增/自减
void atomic_inc(atomic_t *v); //原子变量增加1
void atomic_dec(atomic_t *v); //原子变量减少1
5、操作并测试
int atomic_dec_and_test(atomic_t *v); //自减操作
int atomic_inc_and_test(atomic_t *v); //自增操作
int atomic_sub_and_test(int i, atomic_t *v); //减操作
上述操作后测试其是否为0,为0则返回true,否则返回false。
**位原子操作
1、设置位
void set_bit(nr, void *addr); //设置addr地址的第nr位为1
2、清除位
void clear_bit(nr, void *addr); //设置addr地址的第nr位为0
3、改变位
void change_bit(nr, void *addr); //对 addr 地址的第 nr 位进行反置
4、测试位
test_bit(nr, void *addr); //返回 addr 地址的第 nr 位
5、测试并操作位
int test_and_set_bit(nr, void *addr);
int test_and_clear_bit(nr, void *addr);
int test_and_change_bit(nr, void *addr);
上述 test_and_xxx_bit (nr , void *addr) 操作等同于执行 test_bit (nr , void *addr) 后
再执行 xxx_bit(nr, void *addr)
自旋锁
自旋锁(spin lock)是一种对临界资源进行互斥手访问的典型手段。
1、定义自旋锁
spinlock_t spin;
2、初始化自旋锁
spin_lock_init(lock) //该宏用于动态初始化自旋锁 lock
3、获得自旋锁
spin_lock(lock) //如果能够立即获得锁,则马上返回。否则将自旋直到获得锁。
spin_trylock(lock) //尝试获得自旋锁,立即获得则马上返回真,否则马上返回假。
4、释放自旋锁
spin_unlock(lock) //与spin_lock或spin_trylock配对使用
一般自旋锁的使用方法:
//定义一个自旋锁
spinlock_t lock;
spin_lock_init(&lock);
spin_lock (&lock) ; //获取自旋锁,保护临界区
. . . //临界区
spin_unlock (&lock) ; //解锁
注:尽管用了自旋锁可以保证临界区不受别的 CPU 和本 CPU 内的抢占进程打扰,但
是得到锁的代码路径在执行临界区的时候还可能受到中断和底半部(BH)的影响。
解决方法:
spin_lock_irq() = spin_lock() + local_irq_disable()
spin_unlock_irq() = spin_unlock() + local_irq_enable()
spin_lock_irqsave() = spin_unlock() + local_irq_save()
spin_unlock_irqrestore() = spin_unlock() + local_irq_restore()
spin_lock_bh() = spin_lock() + local_bh_disable()
spin_unlock_bh() = spin_unlock() + local_bh_enable()
使用自旋锁应注意:
1、自旋锁实际上是忙等锁,只有在占用锁的时间极短的情况下使用才是合理的。
2、自旋锁可能导致死锁。
读写自旋锁
读写自旋锁是一种比自旋锁粒度更小的锁机制,它保留了“自旋”的概念,但是
在写操作方面, 只能最多有一个写进程, 在读操作方面,同时可以有多个读执行单元。
当然,读和写也不能同时进行。
1、定义和初始化读写自旋锁
rwlock_t my_rwlock = RW_LOCK_UNLOCKED; /* 静态初始化 */
rwlock_t my_rwlock;
rwlock_init(&my_rwlock); /* 动态初始化 */
2、读锁定
void read_lock(rwlock_t *lock);
void read_lock_irqsave(rwlock_t *lock, unsigned long flags);
void read_lock_irq(rwlock_t *lock);
void read_lock_bh(rwlock_t *lock);
3、读解锁
void read_unlock(rwlock_t *lock);
void read_unlock_irqrestore(rwlock_t *lock, unsigned long flags);
void read_unlock_irq(rwlock_t *lock);
void read_unlock_bh(rwlock_t *lock);
4、写锁定
void write_lock(rwlock_t *lock);
void write_lock_irqsave(rwlock_t *lock, unsigned long flags);
void write_lock_irq(rwlock_t *lock);
void write_lock_bh(rwlock_t *lock);
int write_trylock(rwlock_t *lock);
5、写解锁
void write_unlock(rwlock_t *lock);
void write_unlock_irqrestore(rwlock_t *lock, unsigned long flags);
void write_unlock_irq(rwlock_t *lock);
void write_unlock_bh(rwlock_t *lock);
一般读写自旋锁使用方法:
rwlock_t lock; //定义 rwlock
rwlock_init(&lock); //初始化 rwlock
//读时获取锁
read_lock(&lock);
... //临界资源
read_unlock(&lock);
//写时获取锁
write_lock_irqsave(&lock, flags);
... //临界资源
write_unlock_irqrestore(&lock, flags);
顺序锁
顺序锁(seqlock)是对读写锁的一种优化,若使用顺序锁,读执行单元绝不会被
写执行单元阻塞,也就是说,读执行单元可以在写执行单元对被顺序锁保护的共享资
源进行写操作时仍然可以继续读,而不必等待写执行单元完成写操作,写执行单元也
不需要等待所有读执行单元完成读操作才去进行写操作。
顺序锁有一个限制,它必须要求被保护的共享资源不含有指针,因为写执行单元
可能使得指针失效,但读执行单元如果正要访问该指针,将导致 Oops。
1、获得顺序锁
void write_seqlock(seqlock_t *sl);
int write_tryseqlock(seqlock_t *sl);
write_seqlock_irqsave(lock, flags)
write_seqlock_irq(lock)
write_seqlock_bh(lock)
其中:
write_seqlock_irqsave() = loal_irq_save() + write_seqlock()
write_seqlock_irq() = local_irq_disable() + write_seqlock()
write_seqlock_bh() = local_bh_disable() + write_seqlock()
2、释放顺序锁
void write_sequnlock(seqlock_t *sl);
write_sequnlock_irqrestore(lock, flags)
write_sequnlock_irq(lock)
write_sequnlock_bh(lock)
其中:
write_sequnlock_irqrestore() = write_sequnlock() + local_irq_restore()
write_sequnlock_irq() = write_sequnlock() + local_irq_enable()
write_sequnlock_bh() = write_sequnlock() + local_bh_enable()
写执行单元使用顺序锁的模式如下:
write_seqlock(&seqlock_a);
...//写操作代码块
write_sequnlock(&seqlock_a);
读执行单元涉及如下顺序锁操作
1读开始
unsigned read_seqbegin(const seqlock_t *sl);
read_seqbegin_irqsave(lock, flags)
2、重读
int read_seqretry(const seqlock_t *sl, unsigned iv);
read_seqretry_irqrestore(lock, iv, flags)
读执行单元使用顺序锁的模式如下:
do {
seqnum = read_seqbegin(&seqlock_a);
//读操作代码块
...
} while (read_seqretry(&seqlock_a, seqnum));
信号量
信号量(semaphore)是用于保护临界区的一种常用方法,只有得到信号量的进程才能执行临界区代码。
当获取不到信号量时,进程进入休眠等待状态。
1、定义信号量
struct semaphore sem;
2、初始化信号量
void sema_init (struct semaphore *sem, int val); //val一般为1
void init_MUTEX(struct semaphore *sem);//初始化val为1
注:3.4.2版本内核没有这个宏,直接用sema_init函数就可以了
static DECLARE_MUTEX(button_lock); //定义互斥锁
注:3.4.2版本内核没有这个宏,用上面的方法定义
3、获得信号量
void down(struct semaphore * sem);
int down_interruptible(struct semaphore * sem);
int down_trylock(struct semaphore * sem);
4、释放信号量
void up(struct semaphore * sem);
注:如果信号量被初始化为0,则用于同步,同步意味着一个执行单元的继续
执行需等待另一执行单元完成某事,保证执行的先后顺序。
同步
Linux 系统提供了一种比信号更好的同步机制, 即完成(completion)。
它用于一个执行单元等待另一个执行单元执行完某事。
1、定义completion
struct completion my_completion;
2、初始化completion
init_completion(&my_completion);
或宏
DECLARE_COMPLETION(my_completion); //包含定义和初始化
3、等待completion
void wait_for_completion(struct completion *c);
4、唤醒completion
void complete(struct completion *c);
void complete_all(struct completion *c);
互斥体
1、定义初始化
struct mutex my_mutex;
mutex_init(&my_mutex);
2、获取互斥体
void fastcall mutex_lock(struct mutex *lock);
int fastcall mutex_lock_interruptible(struct mutex *lock);
int fastcall mutex_trylock(struct mutex *lock);
3、释放互斥体
void fastcall mutex_unlock(struct mutex *lock);
一般用法:
struct mutex my_mutex; //定义 mutex
mutex_init(&my_mutex); //初始化 mutex
mutex_lock(&my_mutex); //获取 mutex
...//临界资源
mutex_unlock(&my_mutex); //释放 mutex
阻塞和非阻塞
阻塞操作
是指在执行设备操作时若不能获得资源则挂起进程,直到满足可操作的条件后再进行操作。
被挂起的进程进入休眠状态,被从调度器的运行队列移走,直到等待的条件被满足。
非阻塞操作
进程在不能进行设备操作时并不挂起,它或者放弃,或者不停地查询,直至可以进行操作为止。
open的时候加上标志位O_NONBLOCK即为非阻塞,默认为阻塞
fd = open("...", O_RDWR | O_NONBLOCK);
驱动里相应处理:
先在open函数判断是阻塞还是非阻塞
if (file->f_flags & O_NONBLOCK)
{
//非阻塞操作
}
else
{
//阻塞操作
}
在别的处理函数,如read等也要进行同样的区别处理。
等待队列
1、定义等待队列头
wait_queue_head_t my_queue;
2、初始化等待队列头
init_waitqueue_head(&my_queue);
下列宏完成定义和初始化
DECLARE_WAIT_QUEUE_HEAD (name)
3、定义等待队列
DECLARE_WAITQUEUE(name, tsk)
4、添加/移除等待队列
void fastcall add_wait_queue(wait_queue_head_t *q, wait_queue_t
*wait); //将等待对接wait添加到队列头q指向的队列
void fastcall remove_wait_queue(wait_queue_head_t *q, wait_queue_t
*wait); //将wait从q指向的队列移除
5、等待事件
wait_event(queue, condition)
wait_event_interruptible(queue, condition)
wait_event_timeout(queue, condition, timeout)
wait_event_interruptible_timeout(queue, condition, timeout)
解释:
wait_event:queue等待队列头,condition条件,不满足则阻塞。
wait_event_interruptible:和wait_event类似,区别是它能被中断信号打断。
wait_event_timeout:timeout是阻塞等待超时时间,以jiffy为单位,超时时返回。
6、唤醒队列
void wake_up(wait_queue_head_t *queue);
void wake_up_interruptible(wait_queue_head_t *queue);
双向链表
内核定义了list_head数据结构, 字段next和prev分别表示通用双向链表向前和向后的指针元素。
1、链表的创建
LIST_HEAD(list_name);
2、链表处理函数
list_add(n, p); //把n指向的元素插入p所指向的特定元素之后。即头插
list_add_tail(n, p);//把n指向的元素插入p所指向的特定元素之前。即尾插
list_del(p); //删除p指向的元素
list_empty(); //判断链表是否为空
list_entry(p, t, m);
list_for_each(p, h); //遍历链表
list_for_each_entry(p, h, m)
input输入子系统
1、分配一个input_dev结构体。
static struct input_dev *button_dev;
2、为input_dev结构体申请内存
button_dev = input_allocate_device();
3、设置input_dev结构体
unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; //哪类事件
unsigned long keybit[BITS_TO_LONGS(KEY_CNT)]; //按键值
unsigned long relbit[BITS_TO_LONGS(REL_CNT)]; //相对位移
unsigned long absbit[BITS_TO_LONGS(ABS_CNT)]; //绝对位移
unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];
unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];
unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];
unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];
unsigned long swbit[BITS_TO_LONGS(SW_CNT)];
上面的设置可以直接赋值,但推荐用函数set_bit赋值,例如:
set_bit(EV_KEY, button_dev->evbit);
4、注册
ret = input_register_device(button_dev);
5、释放
input_unregister_device(button_dev);
device端
1、注册
platform_device_register(&led_device);
2、定义platform_device结构体
static struct platform_device led_device = {
.name = "wf_led", //硬件设备资源名字,必须和driver端名字匹配
.id = -1,
.num_resources = 2, //硬件设备资源个数
.resource = led_resource, //硬件设备资源
.dev = {
.release = wf_led_release, //注:如果函数必须有,可以不做什么
//函数原型为 void (*release)(struct device *dev)
},
};
3、定义硬件设备资源
static struct resource led_resource[2] = {
{ .name = "gpf4/5/6 control",
.start = 0x56000050,
.end = 0x56000050 + 8,
.flags = IORESOURCE_IO, }, //标识用的,driver读取资源时会指定
{ .name = "pin4/5/6",
.start = 6,
.end = 6,
.flags = IORESOURCE_TYPE_BITS, },
};
4、释放
platform_device_unregister(&led_device);
driver端
1、注册
platform_driver_register(&led_driver);
2、定义platform_driver结构体
static struct platform_driver led_driver = {
.probe = led_probe,
.remove = led_remove,
.driver = {
.name = "wf_led",
.owner = THIS_MODULE,
}
};
其中,
probe对应的函数是如果有匹配的device驱动,则会调用该函数,该函数一般作类似入口函数的事。
remove对应的函数作类似出口函数的事。
3、获取device端的硬件设备资源
函数platform_get_resource实现该功能,该函数的返回值是struct resource *,
所以先定义一个struct resource *类型的指针来接收返回值,硬件设备资源即在这里面。
LCD驱动
非平台下的LCD驱动:
1、分配fb_info结构体,framebuffer_alloc
2、设置
2.1、设置固定参数
2.2、设置可变参数
2.3、其他设置
s3c_fbinfo->fbops = &s3c_lcd_fb_ops; //操作函数
s3c_fbinfo->pseudo_palette = pseudo_palette; //调色板
3、硬件相关配置
3.1、配置GPIO为LCD引脚
3.2、设置LCD控制寄存器的值
3.3、LCD时钟设置,clk_get、clk_enable
4注册,register_framebuffer
static struct fb_ops s3c_lcd_fb_ops = {
.owner = THIS_MODULE,
.fb_setcolreg = s3c_lcd_setcolreg,
.fb_fillrect = cfb_fillrect,
.fb_copyarea = cfb_copyarea,
.fb_imageblit = cfb_imageblit,
};
调色板相关函数:
static inline unsigned int chan_to_field(unsigned int chan,
struct fb_bitfield *bf)
{
chan &= 0xffff;
chan >>= 16 - bf->length;
return chan << bf->offset;
}
static int s3c_lcd_setcolreg(unsigned regno,
unsigned red, unsigned green, unsigned blue,
unsigned transp, struct fb_info *info)
{
unsigned int val;
if (regno > 16)
return 1;
/* 用red,green,blue三原色构造出val */
val = chan_to_field(red, &info->var.red);
val |= chan_to_field(green, &info->var.green);
val |= chan_to_field(blue, &info->var.blue);
//((u32 *)(info->pseudo_palette))[regno] = val;
pseudo_palette[regno] = val;
return 0;
}
内核下的LCD驱动
driver:
s3c2410fb_init
platform_driver_register(&s3c2410fb_driver);
s3c2410fb_probe
s3c24xxfb_probe(pdev, DRV_S3C2410);
s3c24xxfb_probe
struct fb_info *fbinfo; //定义fb_info结构体
fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev);分配fb_info结构体
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);//获取设备源
info->io = ioremap(res->start, size);
fbinfo->fix.type = FB_TYPE_PACKED_PIXELS; //参数设置
...
fbinfo->fbops = &s3c2410fb_ops; //操作函数
binfo->flags = FBINFO_FLAG_DEFAULT;
fbinfo->pseudo_palette = &info->pseudo_pal;
clk_get(NULL, "lcd"); //开启LCD时钟
clk_enable(info->clk);
ret = s3c2410fb_map_video_memory(fbinfo); //开辟帧缓存
dma_alloc_writecombine(fbi->dev, map_size,&map_dma, GFP_KERNEL);
s3c2410fb_init_registers(fbinfo);//初始化寄存器
modify_gpio(S3C2410_GPCUP, mach_info->gpcup, mach_info->gpcup_mask);
...
modify_gpio(S3C2410_GPDCON, mach_info->gpdcon, mach_info->gpdcon_mask);
s3c2410fb_check_var(&fbinfo->var, fbinfo); //核实可变参数
ret = register_framebuffer(fbinfo); //注册
操作函数:
static struct fb_ops s3c2410fb_ops = {
.owner = THIS_MODULE,
.fb_check_var = s3c2410fb_check_var,
.fb_set_par = s3c2410fb_set_par,
.fb_blank = s3c2410fb_blank,
.fb_setcolreg = s3c2410fb_setcolreg, //调色板,必须有
.fb_fillrect = cfb_fillrect,
.fb_copyarea = cfb_copyarea,
.fb_imageblit = cfb_imageblit,
};
device:
arch/arm/plat-samsung/devs.c:
static struct resource s3c_lcd_resource[] = {
[0] = DEFINE_RES_MEM(S3C24XX_PA_LCD, S3C24XX_SZ_LCD),
[1] = DEFINE_RES_IRQ(IRQ_LCD),
};
struct platform_device s3c_device_lcd = {
.name = "s3c2410-lcd",
.id = -1,
.num_resources = ARRAY_SIZE(s3c_lcd_resource),
.resource = s3c_lcd_resource,
.dev = {
.dma_mask = &samsung_device_dma_mask,
.coherent_dma_mask = DMA_BIT_MASK(32),
}
};
arch/arm/mach-s3c24xx/mach-s3c2440.c:
/* LCD driver info */
static struct s3c2410fb_display smdk2440_lcd_cfg __initdata = {
.lcdcon5 = S3C2410_LCDCON5_FRM565 |
S3C2410_LCDCON5_INVVLINE |
S3C2410_LCDCON5_INVVFRAME |
S3C2410_LCDCON5_PWREN |
S3C2410_LCDCON5_HWSWP,
.type = S3C2410_LCDCON1_TFT,
.width = 480,
.height = 272,
//.pixclock = 166667, /* HCLK 60 MHz, divisor 10 */
.pixclock = 112493,
/*
pixclock=1/dotclock 其中dotclock是视频硬件在显示器上绘制像素的速率
dotclock=(x向分辨率+左空边+右空边+HSYNC长度)* (y向分辨率+上空边+下空边+YSYNC长度)*整屏的刷新率
其中x向分辨率、左空边、右空边、HSYNC长度、y向分辨率、上空边、下空边和YSYNC长度可以在X35LCD说明文档中查到。
DOTCLK = 66*(480 + 2 + 2 + 40) * (272 + 2 + 2 + 9)
*/
.xres = 480,
.yres = 272,
.bpp = 16,
.left_margin = 2,
.right_margin = 2,
.hsync_len = 40,
.upper_margin = 2,
.lower_margin = 2,
.vsync_len = 9,
};
static struct s3c2410fb_mach_info smdk2440_fb_info __initdata = {
.displays = &smdk2440_lcd_cfg,
.num_displays = 1,
.default_display = 0,
/* currently setup by downloader */
.gpccon = 0xaaaaaaaa,
.gpccon_mask = 0xffffffff,
.gpcup = 0xffffffff,
.gpcup_mask = 0xffffffff,
.gpdcon = 0xaaaaaaaa,
.gpdcon_mask = 0xffffffff,
.gpdup = 0xffffffff,
.gpdup_mask = 0xffffffff,
};
//点亮背光灯,把他放在了s3c2410fb_lcd_enable函数里
#if 1
//韦访添加
writel((readl(S3C2410_GPBCON) & ~(3)) | 1, S3C2410_GPBCON);
writel((readl(S3C2410_GPBDAT) | 1), S3C2410_GPBDAT);
writel((readl(S3C2410_GPGCON) | (3<<8)), S3C2410_GPGCON);
#endif
触摸屏
无平台下的触摸屏驱动
触摸屏驱动其实是个input子系统结构。
1、分配input_dev结构体
2、设置
2.1能产生哪类事件
2.2 能产生这类事件里的哪些事件
3、注册
4、硬件相关操作
4.1 使能ADC时钟
4.2 设置ADC/TS寄存器
4.3 请求IRQ_TC和IRQ_ADC中断,中断标志为IRQF_SAMPLE_RANDOM
上报中断产生的事件:
/* 已经松开 */
input_report_abs(s3c_ts_dev, ABS_PRESSURE, 0);
input_report_key(s3c_ts_dev, BTN_TOUCH, 0);
input_sync(s3c_ts_dev);
enter_wait_pen_down_mode();
/* 测量X/Y坐标 */
enter_measure_xy_mode();
start_adc();
优化措施:
1、设置ADCDLY为最大值, 这使得电压稳定后再发出IRQ_TC中断
2、如果ADC完成时, 发现触摸笔已经松开, 则丢弃此次结果
3、多次测量求平均值
4、软件过滤
5、使用定时器处理长按,滑动的情况
内核下的触摸屏驱动
drives/input/youchscreen/s3c2410_ts.c:
static struct platform_device_id s3cts_driver_ids[] = { //设备ID
{ "s3c2410-ts", 0 },
{ "s3c2440-ts", 0 },
{ "s3c64xx-ts", FEAT_PEN_IRQ },
{ }
};
static struct platform_driver s3c_ts_driver = {
.driver = {
.name = "samsung-ts",
.owner = THIS_MODULE,
#ifdef CONFIG_PM
.pm = &s3c_ts_pmops,
#endif
},
.id_table = s3cts_driver_ids,
.probe = s3c2410ts_probe,
.remove = __devexit_p(s3c2410ts_remove),
};
入口函数:
s3c2410ts_probe
struct input_dev *input_dev; //定义input_dev结构体
clk_get(dev, "adc"); //打开ADC时钟
clk_enable(ts.clock);
platform_get_irq(pdev, 0); //获取中断和设备源
platform_get_resource(pdev, IORESOURCE_MEM, 0);
ioremap(res->start, resource_size(res)); //IO映射
ts.client = s3c_adc_register(pdev, s3c24xx_ts_select,s3c24xx_ts_conversion, 1);
s3c24xx_ts_select //当ADC转换是被调用,主要设置寄存器ADCTSC
s3c24xx_ts_conversion //转化完成后被调用
writel(info->delay & 0xffff, ts.io + S3C2410_ADCDLY); //设置ADC延迟寄存器
input_dev = input_allocate_device(); //分配input_dev
ts.input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); //产生哪类事件
ts.input->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
input_set_abs_params(ts.input, ABS_X, 0, 0x3FF, 0, 0);
input_set_abs_params(ts.input, ABS_Y, 0, 0x3FF, 0, 0);
ret = request_irq(ts.irq_tc, stylus_irq, 0,"s3c2410_ts_pen", ts.input); //申请中断
stylus_irq //触摸笔被按下以后调用该函数
down = get_down(data0, data1);//判断是按下还是松开
if (down) //按下则启动ADC转换
{
s3c_adc_start(ts.client, 0, 1 << ts.shift);
}
writel(0x0, ts.io + S3C64XX_ADCCLRINTPNDNUP); //清中断
ret = input_register_device(ts.input);//注册
static DEFINE_TIMER(touch_timer, touch_timer_fire, 0, 0); //定义超时时
touch_timer_fire //超时时间处理函数
down = get_down(data0, data1);
if (down) { //按下,上报事件
...
input_report_abs(ts.input, ABS_X, ts.xp);
input_report_abs(ts.input, ABS_Y, ts.yp);
input_report_key(ts.input, BTN_TOUCH, 1);
input_sync(ts.input);
...
s3c_adc_start(ts.client, 0, 1 << ts.shift);
}
else {
input_report_key(ts.input, BTN_TOUCH, 0);
input_sync(ts.input);
writel(WAIT4INT | INT_DOWN, ts.io + S3C2410_ADCTSC);
}
device:
static struct resource s3c_ts_resource[] = {
[0] = DEFINE_RES_MEM(S3C24XX_PA_ADC, S3C24XX_SZ_ADC),
[1] = DEFINE_RES_IRQ(IRQ_TC),
};
static struct s3c2410_ts_mach_info s3c2410_ts_platdata={
.delay = 10000,
.presc = 49,
.oversampling_shift = 2, //采样次数,2的2次方为4
};
/*
delay保存ADC的延迟时间。
presc保存ADC的预分频系数。
oversampling_shift保存采样次数log2值。
*/
struct platform_device s3c_device_ts = {
.name = "s3c2410-ts",
.id = -1,
.dev.parent = &s3c_device_adc.dev,
.num_resources = ARRAY_SIZE(s3c_ts_resource),
.resource = s3c_ts_resource,
.dev = {
.platform_data = &s3c2410_ts_platdata,
}
};
USB驱动
在 USB 设备的逻辑组织中,包含设备、配置、接口和端点 4 个层次。
在 USB 系统中每一个端点都有惟一的地址,这是由设备地址和端点号给出的。
*设备通常有一个或多个配置。
*配置通常有一个或多个接口。
*接口通常有一个或多个设置。
*接口有零或多个端点。
设备描述符:
设备描述符:关于设备的通用信息,如供应商 ID、产品 ID 和修订 ID,支持的设备类、子类和适用的协议以及默认端点的最大包大小等。
在 Linux 内核中,USB 设备用 usb_device 结构体来 描述 , USB 设备描 述符 定义为
usb_device_descriptor 结构体。
配置描述符:
配置描述符:此配置中的接口数、支持的挂起和恢复能力以及功率要求。
USB配置在内核中使用 usb_host_config 结构体描述, USB 配置描述符定义为结构体
usb_conf ig_descriptor。
接口描述符:
接口描述符:接口类、 子类和适用的协议, 接口备用配置的数目和端点数目。
USB接口在内核中使用 usb_interf ace 结构体描述,USB 接口描述符定义为结构体
usb_interface_descriptor。
端点描述符:
端点描述符:端点地址、方向和类型,支持的最大包大小,如果是中断类型的端点则还包括 轮询频率 。
USB 端点使用usb_host_endpoint结构体来描述,USB端点描述符定义为
usb_endpoint_descri ptor 结构体。
USB主机驱动
- 主机控制驱动
usb_hcd 结构体描述USB主机控制器驱动,它包含 USB 主机控制器的“家务”信息、硬件资源、状态描述和用于操作主机控制器的 hc_driver等。hc_driver成员非常重要,它包含具体的用于操作主机控制器的钩子函数。
USB设备驱动
Linux 系统实现了几类通用的 USB 设备驱动,划分为如下几个设备类。
1 音频设备类。
2 通信设备类。
3 HID(人机接口)设备类。
4 显示设备类。
5 海量存储设备类。
6 电源设备类。
7 打印设备类。
8 集线器设备类。
在 Linux 内核中,使用 usb_driver 结构体描述一个USB 设备驱动。
在编写新的 USB 设备驱动时, 主要应该完成的工作是 probe()和 disconnect()函数。
注册和注销函数:
int usb_register(struct usb_driver *new_driver)
void usb_deregister(struct usb_driver *driver);
usb_driver 结构体中的 id_table 成员描述了这个 USB 驱动所支持的 USB 设备列表。
usb_device_id 结构体用于包含 USB 设备的制造商 ID、产品 ID、产品版本、设备类、接口类等信息及其要匹配标志成员 match-Flash(标明要与哪些成员匹配)可以借助下面一组宏来生成 usb_device_id 结构体的实例:
USB_DEVICE(vendor, product) ;
USB请求块
1.urb 结构体
USB 请求块(USB requestblock,urb)是 USB 设备驱动中用来描述与 USB 设备通信所用的基本载体和核心数据结构。
2、urb处理流程
(1)被USB设备驱动创建
struct urb *usb_alloc_urb(int iso_packets, int mem_flags);
(2)初始化,被安排给一个特定USB设备的特定端点
对于中断urb,使用usb_fill_int_urb函数初始化。
函数参数中的 pipe 使用 usb_sndintpipe()或 usb_rcvintpipe()创建。
对于批量urb,使用usb_fill_bulk_urb函数初始化。
函数参数中的 pipe 使用 usb_sndbulkpipe()或者 usb_rcvbulkpipe()函数来创建。
对于控制urb,使用usb_fill_control_urb函数初始化。
函数参数中的 pipe 使用 usb_sndctrlpipe()或 usb_rcvictrlpipe()函数来创建。
(3)被USB设备驱动提交给USB核心
完成(1) 、(2)步的创建和初始化以后,urb便可以提交给USB核心,用函数
usb_submit_urb来完成。
(4)提交由 USB 核心指定的 USB 主机控制器驱动。
(5)被 USB 主机控制器理,进行一次到 USB 设备的传送。
4、5步由USB和核心和主机控制器完成,不受USB设备驱动控制。
(6)当 urb 完成,USB 主机控制器驱动通知 USB 设备驱动。
内核USB鼠标驱动
drivers/hid/usbhid/usbmouse.c:
static struct usb_device_id usb_mouse_id_table [] = { //id_table,列举支持的USB设备
{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
USB_INTERFACE_PROTOCOL_MOUSE) },
{ } /* Terminating entry */
};
static struct usb_driver usb_mouse_driver = {
.name = "usbmouse",
.probe = usb_mouse_probe,
.disconnect = usb_mouse_disconnect,
.id_table = usb_mouse_id_table,
};
usb_mouse_probe
struct usb_device *dev = interface_to_usbdev(intf); //获得设备描述符
struct usb_host_interface *interface; //定义主机接口
struct usb_endpoint_descriptor *endpoint; //定义端点描述符
struct input_dev *input_dev; //定义input_dev结构体
pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress); //源: USB设备的某个端点 maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe)); //缓冲区的最大长度
input_dev = input_allocate_device(); //分配input_dev结构体
mouse->irq = usb_alloc_urb(0, GFP_KERNEL); //创建urb
input_dev->name = mouse->name;//设置input_dev结构体
...
input_dev->close = usb_mouse_close;
usb_fill_int_urb(mouse->irq, dev, pipe, mouse->data, //初始化urb
(maxp > 8 ? 8 : maxp),
usb_mouse_irq, mouse, endpoint->bInterval);
usb_mouse_irq //中断处理函数
input_report_key(dev, BTN_LEFT, data[0] & 0x01); //上报事件
...
input_sync(dev); //同步事件
usb_submit_urb (urb, GFP_ATOMIC); //提交urb
input_register_device(mouse->dev); //注册input_dev结构体
内核USB键盘驱动
drivers/hid/usbhid/usbkbd.c:
static struct usb_device_id usb_kbd_id_table [] = {
{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
USB_INTERFACE_PROTOCOL_KEYBOARD) },
{ } /* Terminating entry */
};
static struct usb_driver usb_kbd_driver = {
.name = "usbkbd",
.probe = usb_kbd_probe,
.disconnect = usb_kbd_disconnect,
.id_table = usb_kbd_id_table,
};
usb_kbd_probe
struct usb_device *dev = interface_to_usbdev(iface); //获得设备描述符
struct usb_host_interface *interface;//定义主机接口
struct usb_endpoint_descriptor *endpoint;//定义端点描述符
struct input_dev *input_dev; //定义input_dev结构体
pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);//源: USB设备的某个端点
maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe));//缓冲区的最大长度
input_dev = input_allocate_device(); //分配input_dev结构体
usb_kbd_alloc_mem //分配内存
(kbd->irq = usb_alloc_urb(0, GFP_KERNEL))//创建urb
(kbd->led = usb_alloc_urb(0, GFP_KERNEL))
(kbd->new = usb_alloc_coherent(dev, 8, GFP_ATOMIC, &kbd->new_dma)) //分配连续的dma内存给URB_NO_xxx_DMA_MAP
(kbd->cr = kmalloc(sizeof(struct usb_ctrlrequest), GFP_KERNEL))
(kbd->leds = usb_alloc_coherent(dev, 1, GFP_ATOMIC, &kbd->leds_dma))
input_dev->name = kbd->name; //配置input_dev结构体
...
for (i = 0; i < 255; i++)
set_bit(usb_kbd_keycode[i], input_dev->keybit); //产生的哪些按键事件
usb_fill_int_urb(kbd->irq, dev, pipe,kbd->new, (maxp > 8 ? 8 : maxp),//初始化urb
usb_kbd_irq, kbd, endpoint->bInterval);
usb_kbd_irq
/*若同时只按下1个按键则在第[2]个字节,若同时有两个按键则第二个在第[3]字节,类推最多可有6个按键同时按下*/
for (i = 2; i < 8; i++) {
if (kbd->old[i] > 3 && memscan(kbd->new + 2, kbd->old[i], 6) == kbd->new + 8) {
if (usb_kbd_keycode[kbd->old[i]])
input_report_key(kbd->dev, usb_kbd_keycode[kbd->old[i]], 0);
else
hid_info(urb->dev,
"Unknown key (scancode %#x) released.\n",
kbd->old[i]);
}
块设备驱动
字符设备与块设备的区别:
(1)、块设备只能以块为单位接受输入和返回输出,而字符设备则以字节为单位。
(2)、块设备对于 I/O 请求有对应的缓冲区,因此它们可以选择以什么顺序进行响
应,字符设备无须缓冲且被直接读写。
(3)、字符设备只能被顺序读写, 而块设备可以随机问。
Linux设备驱动结构
1、block_device_operations 结构体:
类似与字符设备驱动的file_operations结构体,是对块设备操作的集合。
2、gendisk 结构体:
在 Linux 内核中, 使用 gendisk (通用磁盘) 结构体来表示 1 个独立的磁盘设备 (或
分区)。
gendisk 操作函数
(1)分配gendisk
gendisk 结构体是一个动态分配的结构体, 它需要特别的内核操作来初始化, 驱动不能自己分配这个结构体,而应该使用下列函数来分配 gendisk:
struct gendisk *alloc_disk(int minors);
(2)增加gendisk
gendisk 结构体被分配之后,系统还不能使用这个磁盘,需要调用如下函数来注册这个磁盘设备。
void add_disk(struct gendisk *gd);
注:add_disk()的调用必须发生在驱动程序的初始化工作完成并能响应磁盘的请求之后。
(3)释放gendisk
void del_gendisk(struct gendisk *gd);
(4)gendisk引用计数
gendisk 中包含一个 kobject 成员,因此,它是一个可被引用计数的结构体。通过
get_disk()和 put_disk()函数可用来操作引用计数,这个工作一般不需要驱动亲自做。
(5)设置gendisk变量
void set_capacity(struct gendisk *disk, sector_t size);
块设备中最小的可寻址单元是扇区,扇区大小一般是 2 的数倍,最常见的大小
是 512 字节。
request 与 bio 结构体
1、请求
在 Linux 块设备驱动中,使用 request 结构体来表征等待进行的 I/O 请求。
2、请求队列
一个块请求队列是一个块 I/O 请求的队列,request_queue 。
Linux 2.6 内核包含 4 个 I/O 调度器, 它们分别是 No-op I/O scheduler、 Anticipator y
I/O scheduler、Deadline I/O scheduler 与 CFQ I/O scheduler。
(1)初始化请求队列
request_queue_t *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock);
(2)清除请求队列
void blk_cleanup_queue(request_queue_t * q);
(3)分配“请求队列”
request_queue_t *blk_alloc_queue(int gfp_mask);
绑定请求队列和制造请求函数:
void blk_queue_make_request(request_queue_t * q, make_request_fn * mfn);
(4)提取请求
struct request *elv_next_request(request_queue_t *queue);
(5)去除请求
void blkdev_dequeue_request(struct request *req);
如果需要将一个已经出列的请求归还到队列中,可以进行以下调用:
void elv_requeue_request(request_queue_t *queue, struct request *req);
(6)启停请求队列
void blk_stop_queue(request_queue_t *queue);
void blk_start_queue(request_queue_t *queue);
(7)参数设置
void blk_queue_max_sectors(request_queue_t *queue, unsigned short max);
void blk_queue_max_phys_segments(request_queue_t *queue, unsigned short max);
void blk_queue_max_hw_segments(request_queue_t *queue, unsigned short max);
void blk_queue_max_segment_size(request_queue_t *queue, unsigned int max);
(8)通告内核
void blk_queue_bounce_limit(request_queue_t *queue, u64 dma_addr);
注册与注销
int register_blkdev(unsigned int major, constchar *name);
int unregister_blkdev(unsigned int major, const char *name);
在块设备驱动的模块加载函数中通常需要完成如下工作。
① 分配、初始化请求队列,绑定请求队列和请求函数。
② 分配、初始化 gendisk,给 gendisk 的 major、fops、queue 等成员赋值,最后添
加 gendisk。
③ 注册块设备驱动。
内核xd.c驱动分析
drivers/block/xd.c:
xd_init
register_blkdev(XT_DISK_MAJOR, "xd") //注册块设备驱动
xd_queue = blk_init_queue(do_xd_request, &xd_lock); //初始化请求队列, xd_lock:自旋锁
do_xd_request //这个函数很重要,主要做的是响应请求的操作
req = blk_fetch_request(q); //fetch a request from a request queue
struct gendisk *disk = alloc_disk(64); //分配gendisk结构体
... //设置gendisk结构体
disk->fops = &xd_fops; //操作结构体,见附1
disk->queue = xd_queue; //将队列给gendisk使用
set_capacity(disk, p->heads * p->cylinders * p->sectors); //设置容量
add_disk(xd_gendisk[i]);//注册
附1:
static const struct block_device_operations xd_fops = {
.owner = THIS_MODULE,
.ioctl = xd_ioctl,
.getgeo = xd_getgeo, //必须有
};
static int xd_getgeo(struct block_device *bdev, struct hd_geometry *geo)
{
XD_INFO *p = bdev->bd_disk->private_data;
geo->heads = p->heads;
geo->sectors = p->sectors;
geo->cylinders = p->cylinders;
return 0;
}
网络设备驱动
Linux 系统对网络设备驱动定义了 4 个层次,这 4 个层次为网络协议接口层、网
络设备接口层、提供实际功能的设备驱动功能层和网络设备与媒介层。
网络协议接口层
网络协议接口层最主要的功能是给上层协议提供了透明的数据包发送和接收接
口。
1、套接字缓冲区成员
在sk_buff 结构体。
2、套接字缓冲区操作
(1)分配
用于分配套接字缓冲区的函数:
struct sk_buff *alloc_skb(unsigned int len,int priority);
struct sk_buff *dev_alloc_skb(unsigned int len);
alloc_skb()函数分配一个套接字缓冲区和一个数据缓冲区, 参数 len 为数据缓冲区的空间大小,以 16 字节对齐,参数 priority 为内存分配的优先级。
dev_alloc_skb()函数只是以 GFP_A TOMIC 优先级(代表分配过程不能被中断)调用上面的 alloc_skb()函数,并保存 skb->head 和 skb->data 之间的 16 个字节。
(2)释放
用于释放套接字缓冲区的函数有:
void kfree_skb(struct sk_buff *skb);
void dev_kfree_skb(struct sk_buff *skb);
void dev_kfree_skb_irq(struct sk_buff *skb);
void dev_kfree_skb_any(struct sk_buff *skb);
Linux 内 核 内部 使用 kree_skb() 函 数 , 而网 络设 备驱 动 程序 中 则 必须 用dev_kfree_skb()、dev_kfree_skb_irq()或 dev_kfree_skb_any()函数进行套接字缓冲区的释放。
(3)指针移动
Linux 套接字缓冲区中的数据缓冲区指针移动操作包括 put(放置)、push(推)、pull(拉)、reserve(保留)等。
1).put 操作
unsigned char *skb_put(struct sk_buff *skb, unsigned int len);
unsigned char*_ _skb_put(struct sk_buff *skb,unsigned int len);
上述函数将 tail 指针下移,增加 sk_buff 的 len 值,并返回 skb->tail 的当前值。
skb_put()和__ skb_put()的区别在于前者会检测放入缓冲区的数据,而后者不会检查。
这两个函数主要用于在缓冲区尾部添加数据。
2).push 操作
unsigned char *skb_push(struct sk_buff *skb, unsigned int len);
unsigned char* _ _skb_push(struct sk_buff *skb, unsigned int len);
skb_push()和_ _skb_push()会将 data 指针上移,这两个函数主要用于在缓冲区尾部添加 数据。
3).pull 操作
unsigned char * skb_pull(struct sk_buff *skb,unsigned int len);
skb_pull()函数将 data 指针下移,并减小 skb 的 len 值。
4).reserve 操作
void skb_reserve(struct sk_buff *skb, unsigned int len);
skb_reserve()函数将 data 指针和 tail 指针同时下移, 这个操作主要用于在存储空间
的头部预留 len 长度的空隙。
网络设备接口层
(1)全局信息。
char name[IFNAMESIZ];
name 是网络设备的名称。
int (*init)(struct net_device *dev);
init 为设备初始化函数指针,如果这个指针被设置了,则网络设备被注册时将调
用该函数完成对 net_device 结构体的初始化。但是,设备驱动程序可以不实现这个函
数并将其赋值为 NULL。
(2)硬件信息。
unsigned long mem_end;
unsigned long mem_start;
mem_start 和 mem_end 分别定义了设备所使用的共享内存的起始和结束地址。
unsigned long base_addr;
unsigned char irq;
unsigned char if_port;
unsigned char dma;
base_addr 为网络设备 I/O 基地址。
irq 为设备使用的中断号。
if_port 指定多端口设备使用哪一个端口,该字段仅针对多端口设备。
dma 指定分配给设备的 DMA 通道。
(3)接口信息。
unsigned short hard_header_len;
hard_header_len 是网络设备的硬件头长度,在以太网设备的初始化函数中,该成
员被赋为 ETH_HLEN,即 14。
unsigned short type;
type 是接口的硬件类型。
unsigned mtu;
mtu 指最大传输单元(MTU)。
unsigned char dev_addr[MAX_ADDR_LEN];
unsigned char broadcast[MAX_ADDR_LEN];
(4)设备操作函数。
int (*open)(struct net_device *dev);
int (*stop)(struct net_device *dev);
open()函数的作用是打开网络接口设备,获得设备需要的 I/O 地址、IRQ、DMA
通道等。stop()函数的作用是停止网络接口设备,与 open()函数的作用相反。
设备驱动功能层
对于具体的设备 xxx,工程师应该编写设备驱动功能层的函数,这些函
数 形 如 xxx_open() 、 xxx_stop() 、 xxx_tx() 、 xxx_ hard_header() 、 xxx_get_stats() 、
xxx_tx_timeout()、xxx_poll()等。
网络设备与媒介层
网络设备与媒介层直接对应于实际的硬件设备。
网络设备驱动的注册与注销
注册与注销函数:
int register_netdev(struct net_device *dev);
void unregister_netdev(struct net_device *dev);
其中,net_device结构体是网络设备驱动的核心结构体。
可以利用下面函数填充:
struct net_device *alloc_netdev(int sizeof_priv, const char *name,
void(*setup) (struct net_device*));
struct net_device *alloc_etherdev(int sizeof_priv);
释放net_device结构体的函数:
void free_netdev(struct net_device *dev);
网络设备的初始化
网络设备的初始化主要需要完成如下几个方面的工作:
- 进行硬件上的准备工作,检查网络设备是否存在,如果存在,则检测设备所使用的硬件资源。
- 进行软件接口上的准备工作,分配 net_device 结构体并对其数据和函数指针成员赋值。
- 获得设备的私有信息指针并初始化其各成员的值。 如果私有信息中包括自旋锁或信号量等并发或同步机制,则需对其进行初始化。
网络设备的打开与释放
void netif_start_queue(struct net_device *dev);
void netif_stop_queue (struct net_device *dev);
数据发送流程
流程如下:
(1)网络设备驱动程序从上层协议传递过来的 sk_buff 参数获得数据包的有效数
据和长度,将有效数据放入临时缓冲区。
(2)对于以太网,如果有效数据的长度小于以太网冲突检测所要求数据帧的最小
长度 ETH_ZLEN,则给临时缓冲区的末尾填充 0。
- 设置硬件的寄存器,驱使网络设备进行数据发送操作。
数据接收流程
网络设备接收数据的主要方法是由中断引发设备的中断处理函数,中断处理函数判断中断类型, 如果为接收中断, 则读取接收到的数据, 分配 sk_buf fer 数据结构和数据缓冲区,将接收到的数据复制到数据缓冲区,并调用 netif_rx()函数将 sk_buffer 传递给上层协议。
网络连接状态
网络设备驱动可以通过 netif_carrier_on()和 netif_carrier_off()函数改变设备的连接 状态 , 如 果 驱动 检测 到 连 接 状态 发 生 变 化 , 也 应 该 以 netif_carrier_on() 和netif_carrier_of f()函数显式地通知内核。函数 netif_carrier_ok()可用于向调用者返回链路上的载波信号是否存在。
函数原型如下:
void netif_carrier_on(struct net_device *dev);
void netif_carrier_off(struct net_device *dev);
int netif_carrier_ok(struct net_device *dev);
DM9000厂家提供的驱动分析
DM9000移植
1、设置基地址iobase
init_module函数:
iobase = (int)ioremap(0x20000000, 1024);
2、去掉 dmfe_probe1里的
//if((db->chip_revision!=0x1A) || ((chip_info&(1<<5))!=0) || ((chip_info&(1<<2))!=1)) return -ENODEV;
3、设置中断号
init_module:
irq = IRQ_EINT7;
4、设置中断触发方式
dmfe_open函数里
if(request_irq(dev->irq,&dmfe_interrupt,IRQF_TRIGGER_RISING,dev->name,dev))
return -EAGAIN;
5、加上缺省的头文件
#include <asm/delay.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch-s3c2410/regs-mem.h>
6、设置位宽,时序
init_module:
/* 设置S3C2440的memory controller */
bwscon = ioremap(0x48000000, 4);
bankcon4 = ioremap(0x48000014, 4);
/* DW4[17:16]: 01-16bit
* WS4[18] : 0-WAIT disable
* ST4[19] : 0 = Not using UB/LB (The pins are dedicated nWBE[3:0])
*/
val = *bwscon;
val &= ~(0xf<<16);
val |= (1<<16);
*bwscon = val;
/*
* Tacs[14:13]: 发出片选信号之前,多长时间内要先发出地址信号
* DM9000C的片选信号和CMD信号可以同时发出,
* 所以它设为0
* Tcos[12:11]: 发出片选信号之后,多长时间才能发出读信号nOE
* DM9000C的T1>=0ns,
* 所以它设为0
* Tacc[10:8] : 读写信号的脉冲长度,
* DM9000C的T2>=10ns,
* 所以它设为1, 表示2个hclk周期,hclk=100MHz,就是20ns
* Tcoh[7:6] : 当读信号nOE变为高电平后,片选信号还要维持多长时间
* DM9000C进行写操作时, nWE变为高电平之后, 数据线上的数据还要维持最少3ns
* DM9000C进行读操作时, nOE变为高电平之后, 数据线上的数据在6ns之内会消失
* 我们取一个宽松值: 让片选信号在nOE放为高电平后,再维持10ns,
* 所以设为01
* Tcah[5:4] : 当片选信号变为高电平后, 地址信号还要维持多长时间
* DM9000C的片选信号和CMD信号可以同时出现,同时消失
* 所以设为0
* PMC[1:0] : 00-正常模式
*
*/
*bankcon4 = (1<<8)|(1<<6); /* 对于DM9000C可以设Tacc为1, 对于DM9000E,Tacc要设大一点,比如最大值7 */
//*bankcon4 = (7<<8)|(1<<6); /* MINI2440使用DM9000E,Tacc要设大一点 */
iounmap(bwscon);
iounmap(bankcon4);
如果您感觉本篇博客对您有帮助,请打开支付宝,领个红包支持一下,祝您扫到99元,谢谢~~