Linux内核为2.6.32.2
源码分析工具source insight
前言:在裸机中操作几个gpio口很简单,对控制寄存器和数据寄存器进行配置即可,但要在linux系统中实现同样的功能还是得费上一番周折的。
以下是驱动的源码。
#include <……>
#define DEVICE_NAME 'leds' //设备名
static unsigned long led_table [] = {
S3C2410_GPB(5),
S3C2410_GPB(6),
S3C2410_GPB(7),
S3C2410_GPB(8),
};
static unsigned int led_cfg_table [] = {
S3C2410_GPIO_OUTPUT,
S3C2410_GPIO_OUTPUT,
S3C2410_GPIO_OUTPUT,
S3C2410_GPIO_OUTPUT,
};
static int sbc2440_leds_ioctl(
struct inode *inode,
struct file *file,
unsigned int cmd,
unsigned long arg)
{
switch(cmd) {
case 0:
case 1:
if (arg > 4) {
return -EINVAL;
}
s3c2410_gpio_setpin(led_table[arg], !cmd);
return 0;
default:
return -EINVAL;
}
}
static struct file_operations dev_fops = {
.owner = THIS_MODULE,
.ioctl = sbc2440_leds_ioctl,
};
static struct miscdevice misc = { //杂项设备结构体
.minor = MISC_DYNAMIC_MINOR, //次设备号
.name = DEVICE_NAME,
.fops = &dev_fops,
};
static int __init dev_init(void)
{
int ret;
int i;
for (i = 0; i < 4; i++) {
s3c2410_gpio_cfgpin(led_table[i], led_cfg_table[i]);
s3c2410_gpio_setpin(led_table[i], 0);
}
ret = misc_register(&misc); //混杂设备注册
printk (DEVICE_NAME'tinitializedn');
return ret;
}
static void __exit dev_exit(void)
{
misc_deregister(&misc); //混杂设备注销
}
module_init(dev_init); //模块加载函数
module_exit(dev_exit); //模块卸载函数
MODULE_LICENSE('GPL'); //证书
MODULE_AUTHOR('FriendlyARM Inc.'); //作者
以上列出了加入简单注释的MINI2440led驱动程序的源码,头文件已被省去。对于上述的驱动,我们主要分析这个驱动的整体框架以及里面涉及到的一些重要的函数和宏定义内容。
1、misc设备
misc设备设备被译成杂项设备或者是混杂设备,关于杂项设备在我的一篇文章里面已经专门介绍,这里根据本驱动在详细说明下。Linux设备驱动主要分为字符设备,块设备和网络设备。但是上面的驱动程序并不属于上面三大类中的常见形式,我们把上述驱动程序中的称为杂项设备。什么是杂项设备呢?linux包含了很多的设备类型,但不管怎么分类,总不能完全概括所有设备,我们把这些不属于上述三大形式的归为一类叫做杂项设备。杂项设备共用相同的主设备号(MISC_MAJOR,也就是10),但次设备号不同,所有的杂项设备形成一个链表,对设备访问的时候,内核根据此设备号找到其对应的设备,然后调用其相应的file_operations结构体中注册的各个函数。对于杂项设备,linux内核专门提供了这样的一个结构体miscdevice,其有很强的包容性。结构如下:
以下文件定义在/linux2.6.32.2/include/linux/Miscdivice.h
struct miscdevice {
int minor;
const char *name;
const struct file_operations *fops;
struct list_head list;
struct device *parent;
struct device *this_device;
const char *nodename;
mode_t mode;
};
同时提供的miscdevice注册和注销函数如下所示。
int misc_register(struct miscdevice * misc);
int misc_deregister(struct miscdevice *misc);
其实,杂项设备的本质仍然是字符设备,只是将这种设备驱动增加了一层封装而已,杂项设备中的主体还是file_operations结构的实现,所以,其并不神秘。但要注意是是杂项设备在嵌入式系统中常被用到。其使用的基本形式如本驱动程序所示,不再敖述。
2.LED对应的GPIO端口列表
static unsigned long led_table [] = {
S3C2410_GPB(5),
S3C2410_GPB(6),
S3C2410_GPB(7),
S3C2410_GPB(8),
};
led设备驱动程序中主要是对上述的几个端口进行s3c2410_gpio_cfgpin(led_table[i], led_cfg_table[i]);(配置管脚功能)
s3c2410_gpio_setpin(led_table[i], 0);(设置管脚电平状态)
操作。先来弄清楚这几个端口的定义。
以下文件定义在/arch/arm/mach-s3c2410/include/mach/gpio-nrs.h
/* S3C2410 GPIO number definitions. */
#define S3C2410_GPA(_nr) (S3C2410_GPIO_A_START + (_nr))
#define S3C2410_GPB(_nr) (S3C2410_GPIO_B_START + (_nr))
#define S3C2410_GPC(_nr) (S3C2410_GPIO_C_START + (_nr))
#define S3C2410_GPD(_nr) (S3C2410_GPIO_D_START + (_nr))
#define S3C2410_GPE(_nr) (S3C2410_GPIO_E_START + (_nr))
#define S3C2410_GPF(_nr) (S3C2410_GPIO_F_START + (_nr))
#define S3C2410_GPG(_nr) (S3C2410_GPIO_G_START + (_nr))
#define S3C2410_GPH(_nr) (S3C2410_GPIO_H_START + (_nr))
enum s3c_gpio_number {
S3C2410_GPIO_A_START = 0,
S3C2410_GPIO_B_START = S3C2410_GPIO_NEXT(S3C2410_GPIO_A),
S3C2410_GPIO_C_START = S3C2410_GPIO_NEXT(S3C2410_GPIO_B),
S3C2410_GPIO_D_START = S3C2410_GPIO_NEXT(S3C2410_GPIO_C),
S3C2410_GPIO_E_START = S3C2410_GPIO_NEXT(S3C2410_GPIO_D),
S3C2410_GPIO_F_START = S3C2410_GPIO_NEXT(S3C2410_GPIO_E),
S3C2410_GPIO_G_START = S3C2410_GPIO_NEXT(S3C2410_GPIO_F),
S3C2410_GPIO_H_START = S3C2410_GPIO_NEXT(S3C2410_GPIO_G),
};
#define S3C2410_GPIO_NEXT(__gpio)
((__gpio##_START) + (__gpio##_NR) + CONFIG_S3C_GPIO_SPACE + 0)
CONFIG_S3C_GPIO_SPAC是内核配置选项,在.config中可以找到,我的配置为:
CONFIG_S3C_GPIO_SPACE = 0
因此,以S3C2410_GPB(5)为例,其宏展开为:
S3C2410_GPIO_NEXT(S3C2410_GPIO_A) +5 =>
(S3C2410_GPIO_A_START + S3C2410_GPIO_A_NR + CONFIG_S3C_GPIO_SPACE + 0) + 5 =>
很显然,S3C2410_GPB(5)就是从GPA的首地址+GPA个数+GPB的offset就是当前GPB的IO偏移量,即
0+32+5=37, 同理
S3C2410_GPB(0) 相当于 32
S3C2410_GPB(5) 相当于 37
S3C2410_GPB(6) 相当于 38
S3C2410_GPB(7) 相当于 39
S3C2410_GPB(8) 相当于 40
到这里我们应该明白,这个宏的作用就是对端口进行编号,对于GPA其端口编号的范围是0~31,GPB端口编号范围是32~63,以此类推,当然这里所有的编号不一定都被使用。因为每组的端口的个数不一样,所以给每组都定义32个,以保证每组都够用。在得到端口号后,除以32得到的结果就可以确定这个端口是哪组的了。比如得到端口编号38,除以32后得到1,就知道是属于GPB里面的I/O口了。这在后面进一步分析中会看到。
3. LED 对应端口将要输出的状态列表分析
static unsigned int led_cfg_table [] = {
S3C2410_GPIO_OUTPUT,
S3C2410_GPIO_OUTPUT,
S3C2410_GPIO_OUTPUT,
S3C2410_GPIO_OUTPUT,
};
S3C2410_GPIO_OUTPUT定义在mach/regs-gpio.h
这里主要看最后的两位,表示了端口的状态。
00代表输入,01代表输出,10代表功能2,,1代表功能3.注意提示,GPA是没有输入功能的。
#define S3C2410_GPIO_LEAVE (0xFFFFFFFF)
#define S3C2410_GPIO_INPUT (0xFFFFFFF0) /* not available on A */
#define S3C2410_GPIO_OUTPUT (0xFFFFFFF1)
#define S3C2410_GPIO_IRQ (0xFFFFFFF2) /* not available for all */
#define S3C2410_GPIO_SFN2 (0xFFFFFFF2) /* bank A => addr/cs/nand */
#define S3C2410_GPIO_SFN3 (0xFFFFFFF3) /* not available on A */
4、s3c2410_gpio_cfgpin(led_table[i], led_cfg_table[i])分析
函数源码定义在linux/arch/arm/plat-s3c24xx/gpio.c
函数原型:
void s3c2410_gpio_cfgpin(unsigned int pin, unsigned int function)
{
void __iomem *base = S3C24XX_GPIO_BASE(pin);
unsigned long mask;
unsigned long con;
unsigned long flags;
if (pin < S3C2410_GPIO_BANKB) { //判断I/O口是不是属于GPA,
mask = 1 << S3C2410_GPIO_OFFSET(pin);
} else {
mask = 3 << S3C2410_GPIO_OFFSET(pin)*2;
}
switch (function) { //根据要设置的管脚的功能进行相应的操作
case S3C2410_GPIO_LEAVE:
mask = 0;
function = 0;
break;
case S3C2410_GPIO_INPUT:
case S3C2410_GPIO_OUTPUT:
case S3C2410_GPIO_SFN2:
case S3C2410_GPIO_SFN3:
if (pin < S3C2410_GPIO_BANKB) {
function -= 1;
function &= 1;
function <<= S3C2410_GPIO_OFFSET(pin);
} else {
function &= 3;
function <<= S3C2410_GPIO_OFFSET(pin)*2;
}
}
/* modify the specified register wwith IRQs off */
local_irq_save(flags);
con = __raw_readl(base + 0x00);
con &= ~mask;
con |= function;
__raw_writel(con, base + 0x00);
local_irq_restore(flags);
}
先看一下主体框架,主体通过switch(function)找到要设置的相应的功能进行对应的操作。这个估计很容易看懂。下面将里面几个不好搞懂的地方具体说一下。
对于void __iomem *base = S3C24XX_GPIO_BASE(pin);先来看它的实现
以下内容定义在/linux-2.6.32.2/arch/arm/mach-s3c2410includemachRegs-gpio.h
#define S3C24XX_GPIO_BASE(x) S3C2410_GPIO_BASE(x)
#define S3C2410_GPIO_BASE(pin) ((((pin) & ~31) >> 1) + S3C24XX_VA_GPIO)
以下内容定义在/linux-2.6.32.2/arch/arm/plat-s3c24xx/include/plat/map.h
#define S3C24XX_VA_GPIO ((S3C24XX_PA_GPIO - S3C24XX_PA_UART) + S3C24XX_VA_UART)
以下内容定义在/linux-2.6.32.2/arch/arm/mach-s3c2410/include/mach/map.h
#define S3C24XX_PA_GPIO S3C2410_PA_GPIO
#define S3C24XX_PA_UART S3C2410_PA_UART
以下内容定义在/linux-2.6.32.2/arch/arm/plat-s3c24xx/include/plat/map.h
#define S3C2410_PA_GPIO (0x56000000)
#define S3C2410_PA_UART (0x50000000)
以下内容定义在linux-2.6.32.2/arch/arm/plat-s3c24xx/include/plat/map.h
#define S3C24XX_VA_UART S3C_VA_UART
以下内容定义在linux-2.6.32.2/arch/arm/plat-s3c/include/plat/map.h
#define S3C_VA_UART S3C_ADDR(0x01000000) /* UART */
以下内容定义在linux-2.6.32.2/arch/arm/plat-s3c/include/plat/Map-base.h
#ifndef __ASSEMBLY__
#define S3C_ADDR(x) ((void __iomem __force *)S3C_ADDR_BASE + (x))
#else
#define S3C_ADDR(x) (S3C_ADDR_BASE + (x))
#endif
#define S3C_ADDR_BASE (0xF4000000)
到这找出了定义S3C24XX_GPIO_BASE(x)全部的宏,从此处可以发现,linux中文件的定义分布是比较散乱的,这也是让很多初学者头疼的地方。接着分析
S3C24XX_VA_GPIO=((S3C24XX_PA_GPIO - S3C24XX_PA_UART) + S3C24XX_VA_UART)
=((0x56000000 - 0x50000000) + (0xF4000000 + 0x01000000))
= (0x06000000 + 0xF5000000)
= (0xFB000000)
#define S3C_VA_UART S3C_ADDR(0x01000000) /* UART */
这句话看出在虚拟地址的基地址上偏移0x01000000
对下面两个进行解释:
#define S3C_ADDR_BASE (0xF4000000) 所有寄存器虚拟地址首地址
#S3C24XX_VA_GPIO GPIO的虚拟地址首地址
S3C2410_GPB(5)通过上面的计算其数值为37,
S3C24XX_GPIO_BASE(S3C2410_GPB(5))= S3C24XX_GPIO_BASE(37)
=((((37) & ~31) >> 1) + S3C24XX_VA_GPIO)
=((((37) & ~31) >> 1) + (0xFB000000))= 0xFB000010
所以最终*base =0xFB000010,这个就是GPBCON的虚拟地址,查看其手册我们知道GPBCON物理地址为0X56000010, GPACON的虚拟地址0xFB000000,查看其手册我们知道GPACON物理地址为0X56000000,下面的程序通过访问这个虚拟地址,来访问控制寄存器,实现对I/O端口的配置,此时有人就会问,我访问这个虚拟地址,为什么就能实现了对该物理地址的访问?这是MMU的虚拟地址和物理地址的映射问题,关于这个的问题比较复杂,我专门找了一篇文章来介绍这个虚实映射关系,看本站的《2410下寄存器地址虚实映射的实现》http://www.linuxidc.com/Linux/2013-01/77977.htm 的这一文章,因为内容较多,我不在这里介绍。
还一个问题
((((pin) & ~31) >> 1)到底是神马意思?这个主要靠理解,刚才上面说了每组端口定义为32个,((pin) & ~31)相当于就是把低五位全部清零,而第五位所能代表的范围正好是32,有点以大小32进行对其的意思。如果将得到的数值右移5位的话,得到的数值(设为ppvalue)能正好代表是哪组I/O口。这里为什么右移1位呢,我们看下
设计资源 培训 开发板 精华推荐
- 基于STM8S手持非接触红外测温枪MLX90614 额温枪设计(原理图、PCB、源程序)
- 具有 0 至 4.095 V 输出摆幅的 OP295GSZ-REEL7 5V、12 位 DAC 运算放大器的典型应用
- LTC3630AIDHC 5V 至 76V 输入至 5V 输出、高效率、500mA 稳压器的典型应用电路
- 具有基本抑制功能的 LF50ACP 5V 极低压降稳压器的典型应用
- MP2315电源模块
- LTC3429、2 节电池至 3.3V 同步升压转换器
- 在 AD9279 的 I/Q 输出之后使用 ADA4896-2ACPZ-R2 作为滤波器、I/V 转换器、电流加法器和 ADC 驱动器的典型应用电路
- 使用 Analog Devices 的 LTC7851IUHH-1 的参考设计
- LT1085CM-3.3、3.3V/7.5A LDO稳压器的典型应用电路
- AD8601WARTZ-RL 高端运算放大器电流监控器的典型应用
- 秋风送爽,你来答题我送礼!看Maxim 深入浅出低功耗处理器视频精彩为您呈现!
- 有奖直播 | 瑞萨新一代视觉 AI MPU 处理器 RZ/V2H:高算力、低功耗、实时控制
- 预约有礼:以光代电,硅光芯片了解一下~ 走进工程师网络学堂直播,赢好礼
- 有奖问答|ADI应用之旅——工业大机器健康篇
- 有奖直播|Nexperia针对车联网应用的高效ESD解决方案
- 答题赢好礼|ADI技术直通车第1期
- 温故知新:回顾 ST 2017 Roadshow,洞悉电子界技术新潮流!
- 了解 PI 全新 PowiGaN 开关电源 IC ,答题赢好礼!
- 有奖活动|Mentor PCB 手册:《利用自动验证消除原理图设计错误》
- 下载《基于巨磁(GMR)的Allegro IC》白皮书,赢50元京东卡