先copy一份图过来:
上述图中基本包含了kernel中gpio驱动的架构,只是部分代码位置跟图中不一样。
一. 什么是gpiolib
1.1. linux中从2.6.35以后就开始有gpiolib库了,gpiolib的作用是对所有的gpio实行统一管理,因为驱动在工作的时候,会出现好几个驱动共同使用同一个gpio的情况;这会造成混乱。所以内核提供了一些方法来管理gpio资源;
二. gpiolib在内核中实现流程
2.1. gpiolib初始化哪里
iTop4412_Kernel_3.0/drivers/gpio/gpio-exynos4.c
static __init int exynos4_gpiolib_init(void)
{
struct s3c_gpio_chip *chip;
int i;
int nr_chips;
/* GPIO common part */
chip = exynos4_gpio_common_4bit;
nr_chips = ARRAY_SIZE(exynos4_gpio_common_4bit);
for (i = 0; i < nr_chips; i++, chip++) {
if (chip->config == NULL)
chip->config = &gpio_cfg;
if (chip->base == NULL)
pr_err("No allocation of base address for [common gpio]");
}
samsung_gpiolib_add_4bit_chips(exynos4_gpio_common_4bit, nr_chips);
/* Only 4210 GPIO part */
if (soc_is_exynos4210()) {
chip = exynos4210_gpio_4bit;
nr_chips = ARRAY_SIZE(exynos4210_gpio_4bit);
for (i = 0; i < nr_chips; i++, chip++) {
if (chip->config == NULL)
chip->config = &gpio_cfg;
if (chip->base == NULL)
pr_err("No allocation of base address [4210 gpio]");
}
samsung_gpiolib_add_4bit_chips(exynos4210_gpio_4bit, nr_chips);
} else {
/* Only 4212/4412 GPIO part */
chip = exynos4212_gpio_4bit;
nr_chips = ARRAY_SIZE(exynos4212_gpio_4bit);
for (i = 0; i < nr_chips; i++, chip++) {
if (chip->config == NULL)
chip->config = &gpio_cfg;
if (chip->base == NULL)
pr_err("No allocation of base address [4212 gpio]");
}
samsung_gpiolib_add_4bit_chips(exynos4212_gpio_4bit, nr_chips);
}
samsung_gpiolib_add_4bit_chips这个函数用于挂载硬件数据到内核中:
void __init samsung_gpiolib_add_4bit2_chips(struct s3c_gpio_chip *chip,
int nr_chips)
{
for (; nr_chips > 0; nr_chips--, chip++) {
samsung_gpiolib_add_4bit2(chip);
s3c_gpiolib_add(chip);
}
}
void __init samsung_gpiolib_add_4bit2(struct s3c_gpio_chip *chip)
{
chip->chip.direction_input = samsung_gpiolib_4bit2_input;
chip->chip.direction_output = samsung_gpiolib_4bit2_output;
chip->pm = __gpio_pm(&s3c_gpio_pm_4bit);
}
s3c_gpiolib_add 这个函数把底层gpio chip信息加入到s3c_gpios数组中:
后面调用的时候直接指向我们操作的gpio硬件:
主要是s3c_gpio_chip 这个结构体找对了我们就找到了我们的硬件
extern struct s3c_gpio_chip *s3c_gpios[S3C_GPIO_END];
static inline struct s3c_gpio_chip *s3c_gpiolib_getchip(unsigned int chip)
{
return (chip < S3C_GPIO_END) ? s3c_gpios[chip] : NULL;
}
__init void s3c_gpiolib_add(struct s3c_gpio_chip *chip)
{
struct gpio_chip *gc = &chip->chip;
/* gpiochip_add() prints own failure message on error. */
ret = gpiochip_add(gc);
if (ret >= 0)
s3c_gpiolib_track(chip);
}
2.2. 与硬件相关函数
samsung_gpiolib_4bit_input&samsung_gpiolib_4bit_output函数
这个函数是真正进行寄存器操作的函数,最终驱动工程师进行io设置时会间接指向该函数指针
简单的介绍下调用流程
从最开始的地方跟踪下:
MACHINE_START(SMDK4212, "SMDK4X12")
//MACHINE_START(SMDK4212, "SC1_DVT1")
.boot_params = S5P_PA_SDRAM + 0x100,
.init_irq = exynos4_init_irq,
.map_io = smdk4x12_map_io,
.init_machine = smdk4x12_machine_init,------>s3c_gpio_cfgpin(EXYNOS4_GPC1(0),S3C_GPIO_OUTPUT);
.timer = &exynos4_timer,
#if defined(CONFIG_KERNEL_PANIC_DUMP) //mj for panic-dump
.reserve = reserve_panic_dump_area,
#endif
#ifdef CONFIG_EXYNOS_C2C
.reserve = &exynos_c2c_reserve,
#endif
MACHINE_END
s3c_gpio_cfgpin------------>
struct s3c_gpio_chip *chip = s3c_gpiolib_getchip(pin);--------------->
#ifdef CONFIG_S3C_GPIO_TRACK
extern struct s3c_gpio_chip *s3c_gpios[S3C_GPIO_END];
static inline struct s3c_gpio_chip *s3c_gpiolib_getchip(unsigned int chip)
{
return (chip < S3C_GPIO_END) ? s3c_gpios[chip] : NULL;
}
下面看下gpio的分配情况:
static struct s3c_gpio_chip exynos4_gpio_common_4bit[] = {
#endif
{
.base = S5P_VA_GPIO1, //io映射的地址
.eint_offset = 0x0,
.group = 0,
.chip = {
.base = EXYNOS4_GPA0(0), 这部分是一个规定模式对于4412来说是一个迭代的方法
具体的方式请看上一篇博客
.ngpio = EXYNOS4_GPIO_A0_NR,
.label = "GPA0",
},
}, {
.base = (S5P_VA_GPIO1 + 0x20), //每次只是根据地址进行偏移,偏移量跟spec一一对应
.eint_offset = 0x4,
.group = 1,
.chip = {
.base = EXYNOS4_GPA1(0),
.ngpio = EXYNOS4_GPIO_A1_NR,
.label = "GPA1",
},
}, {
.base = (S5P_VA_GPIO1 + 0x40),
.eint_offset = 0x8,
.group = 2,
.chip = {
.base = EXYNOS4_GPB(0),
.ngpio = EXYNOS4_GPIO_B_NR,
.label = "GPB",
},
.................................
地址映射:
#define S5P_VA_GPIO1 S5P_VA_GPIO
#define S5P_VA_GPIO S3C_ADDR(0x02200000)
#define S3C_ADDR_BASE 0xF6000000
#ifndef __ASSEMBLY__
#define S3C_ADDR(x) ((void __iomem __force *)S3C_ADDR_BASE + (x))
#else
#define S3C_ADDR(x) (S3C_ADDR_BASE + (x))
#endif
下一个问题:
gpio号 与 EXYNOS4_GPA0(0) 这种方式得到的区别
EXYNOS4_GPA0(0):是通过映射地址与偏移地址得到我们gpio地址然后进行操作
gpio号是如何得到了呢?
在初始化的过程
map_io = smdk4x12_map_io.
--->
iotable_init(s5p_iodesc, ARRAY_SIZE(s5p_iodesc));
if (mach_desc)
iotable_init(mach_desc, size);
linux iotable_init 静态映射与内核页表的建立
void __init iotable_init(struct map_desc *io_desc, int nr)
struct map_desc {
unsigned long virtual;
unsigned long pfn;
unsigned long length;
unsigned int type;
};
如map_desc所示通过实例化这个结构体我们可以创建对某个物理地址的固定的虚拟地址映射
iotable_init 一般是在machine desc 的map_io的call函数里具体的call stack如下
start_kernel-->setup_arch-->paging_init-->devicemaps_init-->mdesc->map_io()
iotable_init会调用函数create_mapping来创建内核页表和映射关系,不只是iotable_init 包括内核页表的创建都是通过该函数
盗一张3+1的32bit linux 的memeory layout 对比kernel启动过程中的LOG帮助后面理解
log 出自 start_kernel–>mm_init–>mem_init
/*
* Create the page directory entries and any necessary
* page tables for the mapping specified by `md'. We
* are able to cope here with varying sizes and address
* offsets, and we take full advantage of sections and
* supersections.
*/
static void __init create_mapping(struct map_desc *md)
{
if (md->virtual != vectors_base() && md->virtual < TASK_SIZE) {
pr_warn("BUG: not creating mapping for 0x%08llx at 0x%08lx in user region\n",
(long long)__pfn_to_phys((u64)md->pfn), md->virtual);
return;
}
//vectors_base是中断向量表的位置0xffff0000 TASK_SIZE是userspace的空间大小为0x7f000000
//iotable_init 是从vmalloc区域拿地址空间,或者说low memory区,所以先判断所申请的虚拟地址是否在此范围内
if ((md->type == MT_DEVICE || md->type == MT_ROM) &&
md->virtual >= PAGE_OFFSET && md->virtual < FIXADDR_START &&
(md->virtual < VMALLOC_START || md->virtual >= VMALLOC_END)) {
pr_warn("BUG: mapping for 0x%08llx at 0x%08lx out of vmalloc space\n",
(long long)__pfn_to_phys((u64)md->pfn), md->virtual);
}
//PAGE_OFFSET是kernel space虚拟地址的起始0x80000000 是2+2的memory分布,跟TASK_SIZE gap 了16M
//FIXADDR_START 是kernel space的永久映射区 0xffc00000
//vmalloc_start 0xa0800000 vmaloc_end 0xff000000 大概1.5G
//vmalloc区域大小也会有boot cmdline vmalloc= 决定
---------------------------------------------------------------------
type = &mem_types[md->type];
addr = md->virtual & PAGE_MASK;
//分配的地址以page为offset
phys = __pfn_to_phys(md->pfn);
//从页框号获得要映射的物理地址
length = PAGE_ALIGN(md->length + (md->virtual & ~PAGE_MASK));
//分配的size也是以page为offset
if (type->prot_l1 == 0 && ((addr | phys | length) & ~SECTION_MASK)) {
printk(KERN_WARNING "BUG: map for 0x%08llx at 0x%08lx can not "
"be mapped using pages, ignoring.\n",
(long long)__pfn_to_phys(md->pfn), addr);
return;
}
pgd = pgd_offset_k(addr);
//根据虚拟地址去查询kernel页表swapper_pg_dir找到对应的pgd(虚拟地址在text段的前0x4000,16k)
end = addr + length;
do {
unsigned long next = pgd_addr_end(addr, end);
alloc_init_pud(pgd, addr, next, phys, type);
phys += next - addr;
addr = next;
} while (pgd++, addr != end);
}
* to find an entry in a kernel page-table-directory */
#define pgd_offset_k(addr) pgd_offset(&init_mm, addr)
#define pgd_offset(mm, addr) ((mm)->pgd + pgd_index(addr))
/* to find an entry in a page-table-directory */
#define pgd_index(addr) ((addr) >> PGDIR_SHIFT)