linux内核驱动学习(八)----驱动分类 | 学习方法 | 硬件访问

驱动分类:

对于驱动,我们一般按两种方法进行分类:常规分类法和总线分类法。

按照常规分类法,可以分为以下三类:

1、字符设备:

以字节为最小访问单位的设备。一般通过字符设备文件来访问字符设备驱动程序。字符驱动程序则负责驱动字符设备,
,这样的驱动通常支持open、close、read、write系统调用,应用程序可以通过设备文件(比如/dev/ttySAC0等)来访问字符设备(串口)。例如:串口\led\按键

2、块设备:

以块(一般512字节)为最 小传输单位的设备。大多数UNIX系统中,块设备不能按字节处理数据。常见的块设备包括硬盘、Flash、sd卡。

在Linux追踪则允许块设备传送任意数目的字节。

块设备的特别之处:

a)操作硬件的接口的实现方式不一样

块设备驱动程序是先将用户发来的数据组织成块,再写入设备;或从设备中读出若干块数据,再从中挑出用户需要的

b)数据块上的数据可以有一定的格式。

通常在快设备中按一定的格式存放数据,不同的文件系统类型就是定义这些格式的。内核中,文件系统的层次位于块设备驱动程序上面的,这意味着块设备驱动程序除了向用户层提供与字符设备一           样的接口外,还要向内核其他部件提供一些接口,这些接口用户看不到,但是可以使用这些接口是的可以在块设备上存放文件系统,挂载块设备。

块设备与字符设备的区别仅仅在于驱动向内核提供的接口不一样,而向用户层提供的接口是一样的。

3、网络接口

即可以是一个硬件设备,如网卡;也可以是纯软件的设备。比如回环接口(lo)。一个网络接口负责发送和接收数据报文。

对于网络驱动程序并不同于字符设备和块设备,库、内核提供了一套和数据包传输相关的函数,而不是普通的系统调用(open\write)

按照总线分类法,也可分为以下三类:

1、USB 设备

2、PCI设备

3、平台总线设备

譬如USB无线网卡:按常规分类为网络接口,按总线分类:USB设备。

驱动学习方法:

我们知道Linux内核就是由各种驱动组成的,内核源码中大约85%都为驱动程序的代码。内核中实现的驱动程序种类齐全,我们可以在通类型驱动的基础上进行修改以符合具体的设备。

学习驱动更重要的是搞清楚现有驱动的框架,在这个框架上添加相应硬件。

对于硬件操作,可参考ARM裸机代码,将其移植到驱动框架中去。

还有一点就是在驱动学习初期最好不要过多去阅读内核代码,以免造成混乱。

硬件访问:

硬件访问的实质:驱动程序控制设备,主要是通过设备内的寄存器来达到控制的目的,因此我们讨论如何访问硬件,就成了如何访问这些寄存器了。

一、地址映射

裸机直接使用物理地址去操作寄存器;而在Linux系统中使用的为虚拟地址(无论内核程序还是应用程序)。则当操作寄存器时,需要完成物理地址到虚拟地址的映射。

地址映射又分为:

1.1动态映射:

在驱动程序中采用ioremap函数将物理地址映射为虚拟地址。

函数原型:void * ioremap(physaddr,
size)

参数:physaddr:待映射的物理地址

size:映射的区域长度

返回值:映射后的虚拟地址

1.2静态映射:是指Linux系统根据用户事先指定的映射关系,在内核启动时,自动地将物理地址映射为虚拟地址。

映射的举例:IO内存的静态映射,linux系统在建立IO内存物理地址到虚拟地址的映射时,映射关系是怎么指定的呢?这就需要map_desc这个结构数组了,映射就是在这个结构数组中添加新成员来完成的。

即在静态映射中,用户 是通过map_desc结构来指明物理地址与虚拟地址的映射关系

文件Map.h中 (linux-ok6410\arch\arm\include\asm\mach)在静态映射中,用
户 是通过map_desc结构来指明物理地址与虚拟地址的映射关系 。

struct map_desc {
unsigned long virtual; /* 映射后的虚拟地址 */
unsigned long pfn; /* 物理地址所在的页帧号 */
unsigned long length; /* 映射长度 */
unsigned int type; /* 映射的设备类型 */
};

pfn: 利用 __phys_to_pfn(物理地址)可以计算出物理地址所在的物理页帧号

对于6410处理器,关于该结构的填充如下:

Cpu.c
(linux-ok6410\arch\arm\mach-s3c64xx)

/* minimal IO mapping */
static struct map_desc s3c_iodesc[] __initdata = {
	{
		.virtual	= (unsigned long)S3C_VA_SYS,
		.pfn		= __phys_to_pfn(S3C64XX_PA_SYSCON),
		.length		= SZ_4K,
		.type		= MT_DEVICE,
	}, {
		.virtual	= (unsigned long)S3C_VA_MEM,
		.pfn		= __phys_to_pfn(S3C64XX_PA_SROM),
		.length		= SZ_4K,
		.type		= MT_DEVICE,
	}, {
		.virtual	= (unsigned long)(S3C_VA_UART + UART_OFFS),
		.pfn		= __phys_to_pfn(S3C_PA_UART),
		.length		= SZ_4K,
		.type		= MT_DEVICE,
	}, {
		.virtual	= (unsigned long)VA_VIC0,
		.pfn		= __phys_to_pfn(S3C64XX_PA_VIC0),
		.length		= SZ_16K,
		.type		= MT_DEVICE,
	}, {
		.virtual	= (unsigned long)VA_VIC1,
		.pfn		= __phys_to_pfn(S3C64XX_PA_VIC1),
		.length		= SZ_16K,
		.type		= MT_DEVICE,
	}, {
		.virtual	= (unsigned long)S3C_VA_TIMER,
		.pfn		= __phys_to_pfn(S3C_PA_TIMER),
		.length		= SZ_16K,
		.type		= MT_DEVICE,
	}, {
		.virtual	= (unsigned long)S3C64XX_VA_GPIO,
		.pfn		= __phys_to_pfn(S3C64XX_PA_GPIO),
		.length		= SZ_4K,
		.type		= MT_DEVICE,
	}, {
		.virtual	= (unsigned long)S3C64XX_VA_MODEM,
		.pfn		= __phys_to_pfn(S3C64XX_PA_MODEM),
		.length		= SZ_4K,
		.type		= MT_DEVICE,
	}, {
		.virtual	= (unsigned long)S3C_VA_WATCHDOG,
		.pfn		= __phys_to_pfn(S3C64XX_PA_WATCHDOG),
		.length		= SZ_4K,
		.type		= MT_DEVICE,
	}, {
		.virtual	= (unsigned long)S3C_VA_USB_HSPHY,
		.pfn		= __phys_to_pfn(S3C64XX_PA_USB_HSPHY),
		.length		= SZ_1K,
		.type		= MT_DEVICE,
	},

};

内核启动时,在以下函数内完成自动映射

/* read cpu identification code */

void __init s3c64xx_init_io(struct map_desc *mach_desc, int size)
{
	unsigned long idcode;

	/* initialise the io descriptors we need for initialisation */
	iotable_init(s3c_iodesc, ARRAY_SIZE(s3c_iodesc)); //建立映射
	iotable_init(mach_desc, size);

	idcode = __raw_readl(S3C_VA_SYS + 0x118);
	if (!idcode) {
		/* S3C6400 has the ID register in a different place,
		 * and needs a write before it can be read. */

		__raw_writel(0x0, S3C_VA_SYS + 0xA1C);
		idcode = __raw_readl(S3C_VA_SYS + 0xA1C);
	}

	s3c_init_cpu(idcode, cpu_ids, ARRAY_SIZE(cpu_ids));
}

二、寄存器读写

完成地址映射后,就可以读写寄存器了,linux内核(3.0.1)提供了一系列函数,来读取寄存器

/* read cpu identification code */

void __init s3c64xx_init_io(struct map_desc *mach_desc, int size)
{
	unsigned long idcode;

	/* initialise the io descriptors we need for initialisation */
	iotable_init(s3c_iodesc, ARRAY_SIZE(s3c_iodesc)); //建立映射
	iotable_init(mach_desc, size);

	idcode = __raw_readl(S3C_VA_SYS + 0x118);
	if (!idcode) {
		/* S3C6400 has the ID register in a different place,
		 * and needs a write before it can be read. */

		__raw_writel(0x0, S3C_VA_SYS + 0xA1C);
		idcode = __raw_readl(S3C_VA_SYS + 0xA1C);
	}

	s3c_init_cpu(idcode, cpu_ids, ARRAY_SIZE(cpu_ids));
}

时间: 2025-01-15 12:24:24

linux内核驱动学习(八)----驱动分类 | 学习方法 | 硬件访问的相关文章

LINUX内核分析第八周学习总结——进程的切换和系统的一般执行过程

LINUX内核分析第八周学习总结——进程的切换和系统的一般执行过程 黄韧(原创作品转载请注明出处) <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.知识概要 Linux中进程调度的基本概念与相关知识 schedule函数如何实现进程调度 Linux进程的执行过程(一般情况与特殊情况) 宏观描述Linux系统执行 二.学习笔记 (一)进程切换的关键代码switch_to分析 进程进度与进程调度的时机分析 1.

Linux内核设计基础(八)之内核数据结构

我个人比较喜欢学习数据结构,而Linux内核中实现的数据结构会是我们去学习.理解和应用数据结构的一个很好途径.这里介绍内核中广泛应用的四种数据结构:链表.队列.映射和二叉树. 链表: Linux内核讲求高效精简,所以有时需要我们动态去创建和分配内存,这时就要借助链表,我们根据实际情况分配内存后,只需修改链表的指针,仍能索引到刚分配的内存区.链表分单向链表.双向链表和循环链表. 单向链表 struct list_element { void *data; struct list_element *

Linux内核设计第八周 ——进程的切换和系统的一般执行过程

Linux内核设计第八周 ——进程的切换和系统的一般执行过程 第一部分 知识点总结

linux内核数据结构学习总结(undone)

本文旨在整理内核和应用层分别涉及到的数据结构,从基础数据结构的角度来为内核研究作准备,会在今后的研究中不断补充 目录 1. 进程相关数据结构 1) struct task_struct 2. 内核中的队列/链表对象 3. 内核模块相关数据结构 2) struct module 1. 进程相关数据结构 0x1: task_struct 我们知道,在windows中使用PCB(进程控制块)来对进程的运行状态进行描述,对应的,在linux中使用task_struct结构体存储相关的进程信息,task_

Linux 内核list_head 学习

Linux 内核list_head 学习(一) http://www.cnblogs.com/zhuyp1015/archive/2012/06/02/2532240.html 在Linux内核中,提供了一个用来创建双向循环链表的结构 list_head.虽然linux内核是用C语言写的,但是list_head的引入,使得内核数据结构也可以拥有面向对象的特性,通过使用操作list_head 的通用接口很容易实现代码的重用,有点类似于C++的继承机制(希望有机会写篇文章研究一下C语言的面向对象机制

浅谈 Linux 内核开发之网络设备驱动

网络设备介绍 网络设备是计算机体系结构中必不可少的一部分,处理器如果想与外界通信,通常都会选择网络设备作为通信接口.众所周知,在 OSI(Open Systems Interconnection,开放网际互连)中,网络被划分为七个层次,从下到上分别是物理层.数据链路层.网络层.传输层.会话层.表示层和应用层.我们所讲的网络设备也包括两个层次,一层叫做 MAC(Media Access Control)层,对应于 OSI 的数据链路层:另一层叫做 PHY(Physical Layer)层,对应于物

Linux内核部件分析 设备驱动模型之device

来源:Linux社区 -- http://www.linuxidc.com/Linux/2011-10/44627p6.htm 作者 : qb_2008 linux的设备驱动模型,是建立在sysfs和kobject之上的,由总线.设备.驱动.类所组成的关系结构.从本节开始,我们将对linux这一设备驱动模型进行深入分析. 头文件是include/linux/device.h,实现在drivers/base目录中.本节要分析的,是其中的设备,主要在core.c中. struct device {

LINUX内核升级-更新网卡驱动

因项目需要,将当前内核(2.6.32-220.el6.x86_64)升级到目标内核(2.6.33-110.el6.x86_64),但是编译的目标 内核(2.6.33-110.el6.x86_64)的对应驱动程序并不一定支持当前系统的对应设备.本篇将详细讲述目标内核驱动无法支持当前网卡设备的 处理步骤. 01.编译目标内核 请参考内核编译相关文档(省略) 02.显示所有网卡 由于一个系统可以支持多张网卡,可通过如下命显示当前系统的所有网卡及其基本信息: #ifconfig -a em1 Link

Linux内核设计第八周学习总结 理解进程调度时机跟踪分析进程调度与进程切换的过程

陈巧然 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.视频内容 Linux系统的一般执行过程 最一般的情况:正在运行的用户态进程X切换到运行用户态进程Y的过程 1. 正在运行的用户态进程X 2. 发生中断——save cs:eip/esp/eflags(current) to kernel stack, then load cs:eip(entry of a specific IS