接触这么久的内核代码,还没有真正分析一个完整的驱动源码,都是零零散散写只言片字。本文就作一个尝试,写一写Linux内核源码分析层面的文章。

本文介绍基于Intel baytrail系列的e3800系列的SOC的LPC驱动。后续文章将进行该系列的WDT和GPIO驱动分析。

一、概述

ICH是I/O Controller Hub,用于Intel南桥芯片,扩展IO。目前有很多代,从ICH0到ICH11。ICH支持LPC接口(Low Pin Count interface)——本文分析的Linux内核中驱动名称就是lpc_ich。这些属于Intel架构方面的知识,非笔者强项,所以就不再扩展说明。
从e3800手册(参考文后资料)35章节中可以看到,LPC是一个PCI设备,PCI寻址为00:1f.0,ID号为8086:0f1c。它的配置空间有ACPI、PMC、GPIO、ILB、SPI等基地址,偏移地位分别是0x40、0x44、0x48、0x50、0x54。我们从这些偏移地址可以获取到对应模块的IO地址(或内存地址),从而进一步完成驱动功能。比如有一片FLASH是接到e3800的SPI控制器,就应该从LPC的0x54偏移处获取到SPI基地址,之后就能控制SPI寄存器了。

另外说一点,e3800有内部看门狗功能。驱动名称是iTCO_wdt。它隐藏于ACPI中,笔者也是无意中发现了。

下面使用3.17.1版本内核跟踪分析源码。

二、lpc_ich驱动代码

lpc_ich驱动源码文件是drivers/mfd/lpc_ich.c。它是一个PCI驱动,其驱动入口定义如下:

static struct pci_driver lpc_ich_driver = {.name        = "lpc_ich",.id_table   = lpc_ich_ids,.probe        = lpc_ich_probe,.remove     = lpc_ich_remove,
};module_pci_driver(lpc_ich_driver);

其中module_pci_driver是注册pci驱动的封装函数。而pci_driver包含了探测和删除函数,以及PCI设备ID表lpc_ich_ids。其结构体定义如下:

static const struct pci_device_id lpc_ich_ids[] = {{ PCI_VDEVICE(INTEL, 0x2410), LPC_ICH},...{ PCI_VDEVICE(INTEL, 0x0f1c), LPC_BAYTRAIL},{ 0, },            /* End of list */
};

其中的LPC_ICH和LPC_BAYTRAIL等用于区别不同的类别——因为lpc_ich适用于许多不同的设备。这些值在lpc_chipset_info数组中可以找到对应的定义。lpc_chipset_info是lpc_ich_info结构体数据。声明如下:

struct lpc_ich_info {char name[32];unsigned int iTCO_version;unsigned int gpio_version;u8 use_gpio;
};

其中iTCO_version和gpio_version分别指定了iTCO看门狗和GPIO的版本。当然,如果其值为0则表示没有此设备。lpc_chipset_info数组定义如下:

static struct lpc_ich_info lpc_chipset_info[] = {[LPC_ICH] = {.name = "ICH",.iTCO_version = 1,},...[LPC_BAYTRAIL] = {.name = "Bay Trail SoC",.iTCO_version = 3,},
};

比如,ID号为0f1c的PCI设备对应着LPC_BAYTRAIL,其名称为Bay Trail SoC,同时定义了iTCO_version(但没有定义gpio_version),版本号为3(iTCO看门狗驱动将在后续文章中介绍)。

2.1 探测函数

内核启动时会扫描PCI设备,当存在lpc_ich_ids中的ID时,就会调用lpc_ich_probe函数。其主要代码功能描述如下。

1、调用devm_kzalloc申请lpc_ich_priv空间,lpc_ich_priv声明如下:

struct lpc_ich_priv {int chipset;int abase;        /* ACPI base */int actrl_pbase;    /* ACPI control or PMC base */int gbase;        /* GPIO base */int gctrl;        /* GPIO control */int abase_save;        /* Cached ACPI base value */int actrl_pbase_save;        /* Cached ACPI control or PMC base value */int gctrl_save;        /* Cached GPIO control value */
};

从上图可以看到,lpc设备有ACPI、GPIO的基地址,——而这都在结构体中体现,当然这个结构体并没有SPI基地址,如有需要则可以自行添加。

2、根据手册定义赋值ACPI、GPIO基地址:

    priv->chipset = id->driver_data; // 根据lpc_ich_ids传递给chipsetpriv->actrl_pbase_save = -1;priv->abase_save = -1;priv->abase = ACPIBASE; //ACPI基地址priv->actrl_pbase = ACPICTRL_PMCBASE;priv->gctrl_save = -1;if (priv->chipset <= LPC_ICH5) {priv->gbase = GPIOBASE_ICH0;priv->gctrl = GPIOCTRL_ICH0;} else {priv->gbase = GPIOBASE_ICH6; // GPIO基地址priv->gctrl = GPIOCTRL_ICH6;}

3、根据lpc_chipset_info定义初始化WDT和GPIO

    if (lpc_chipset_info[priv->chipset].iTCO_version) {ret = lpc_ich_init_wdt(dev);if (!ret)cell_added = true;}if (lpc_chipset_info[priv->chipset].gpio_version) {ret = lpc_ich_init_gpio(dev);if (!ret)cell_added = true;}

在lpc_ich_init_wdt和lpc_ich_init_gpio中,会初始化相关的IO资源(或memory资源),最后调用mfd_add_devices函数注册mfd设备。该函数带有mfd_cell结构体,定义如下:

static struct mfd_cell lpc_ich_cells[] = {[LPC_WDT] = {.name = "iTCO_wdt",.num_resources = ARRAY_SIZE(wdt_ich_res),.resources = wdt_ich_res,.ignore_resource_conflicts = true,},[LPC_GPIO] = {.name = "gpio_ich",.num_resources = ARRAY_SIZE(gpio_ich_res),.resources = gpio_ich_res,.ignore_resource_conflicts = true,},
};

从结构体中可以清晰看到,LPC含有WDT设备、GPIO设备。名称分别为iTCO_wdt和gpio_ich。而在drivers/watchdog/和drivers/gpio目录中,我们看到有这2个设备的驱动。

2.2 GPIO设备初始化

本节介绍一下LPC驱动中GPIO设备添加的过程。
在LPC探测函数lpc_ich_probe对GPIO基地址进行赋值,代码如下:
        priv->gbase = GPIOBASE_ICH6; // GPIO基地址priv->gctrl = GPIOCTRL_ICH6;

其定义是:

#define GPIOBASE_ICH6       0x48
#define GPIOCTRL_ICH6       0x4C

前文也提及有0x48地址,所有地址都可以在手册对应章节中找到。

直接初始化GPIO在函数lpc_ich_init_gpio中。这个函数对ACPI(GPE0)和GPIO都进行初始化。使用的resource结构体gpio_ich_res是一个数组,包含了GPE0和GPIO。这里只看GPIO的部分,主要代码功能描述如下。

1、读取GPIO基地址值,即通过LPC这个PCI设备的配置空间偏移值GPIOBASE_ICH6。

    /* Setup GPIO base register */pci_read_config_dword(dev, priv->gbase, &base_addr_cfg);base_addr = base_addr_cfg & 0x0000ff80;

另外提一点,对于Linux的PCI驱动,一般使用pci_read_config_dword、pci_write_config_dword等函数进行读写,该类函数形式相近。dword表示读写32字节,word读写16字节,byte读写8字节。

2、设置resource,即把前面获取到的base_addr赋值给resource的start成员变量。

    res = &gpio_ich_res[ICH_RES_GPIO];res->start = base_addr;switch (lpc_chipset_info[priv->chipset].gpio_version) {case ICH_V5_GPIO:case ICH_V10CORP_GPIO:res->end = res->start + 128 - 1;break;default:res->end = res->start + 64 - 1;break;}

3、检查GPIO冲突。

ret = lpc_ich_check_conflict_gpio(res);

4、使能GPIO:

lpc_ich_enable_gpio_space(dev);

即使能GPIO地址解码,

e3800手册中对GPIO使能解释如下:

bit 1 Enable (EN): When set, decode of the IO range pointed to by the GBASE is enabled.

函数代码如下:

static void lpc_ich_enable_gpio_space(struct pci_dev *dev)
{struct lpc_ich_priv *priv = pci_get_drvdata(dev);u8 reg_save;pci_read_config_byte(dev, priv->gctrl, &reg_save);pci_write_config_byte(dev, priv->gctrl, reg_save | 0x10);priv->gctrl_save = reg_save;
}

对比发现,其使能并不是0x10,而是0x02。所以在实际驱动开发中,是需要修改的。——这或许就是公司嵌入式工程师价值所在吧,他们并不是外人所认知的那么简单。并非一句“Linux内核都是开源”就能概括的。

5、添加mfd设备。

    lpc_ich_finalize_cell(dev, &lpc_ich_cells[LPC_GPIO]);ret = mfd_add_devices(&dev->dev, -1, &lpc_ich_cells[LPC_GPIO],1, NULL, 0, NULL);

其中lpc_ich_finalize_cell是设置mfd_cell结构体的成员platform_data。从上文知道,lpc_chipset_info保存着名称和一些模块的版本号。这样,在对应的驱动中就能获取到这个些值从而进行不同的处理。最后调用mfd_add_devices添加lpc_ich_cells[LPC_GPIO]——即GPIO的platform设备。

三、总结

mfd是多功能设备,它将一些类似的模块结合到一起,其本质上还是platform设备。由platform设备模型原理知道,platform设备和platform驱动通过name来匹配,匹配时会调用到驱动的probe函数。关于mfd,将会后续文章进行分析。

对于lpc_ich驱动而言。当系统启动时扫描到LPC设备时,就会注册WDT和GPIO设备。从而调用到对应驱动的probe函数,进而使之正常工作。——当然,前提是把WDT和GPIO驱动添加到内核中。

资源参考:

1、baytrail手册:http://www.intel.com/content/www/us/en/embedded/products/bay-trail/atom-e3800-family-datasheet.html

2、ICH的wifi介绍:https://en.wikipedia.org/wiki/I/O_Controller_Hub

3、内核源码官网:https://www.kernel.org

4、内核源码查询:http://lxr.free-electrons.com/source/?v=3.17

李迟 2016.12.5 周一 晚

我的内核学习笔记7:Intel LPC驱动lpc_ich分析相关推荐

  1. 我的内核学习笔记6:PCI驱动probe的一点认知

    对于PCI的学习,在文章<初识PCI>和<再识PCI:一个PCI驱动实例>中有介绍,文中使用大量代码进行演示.但总觉得有些认知不到位.于是就再写一文. 一.PCI驱动一般框架 ...

  2. ETHREAD APC 《寒江独钓》内核学习笔记(4)

    继续学习windows 中和线程有关系的数据结构: ETHREAD.KTHREAD.TEB 1. 相关阅读材料 <windows 内核原理与实现> --- 潘爱民 2. 数据结构分析 我们 ...

  3. THREAD APC 《寒江独钓》内核学习笔记(4)

    继续学习windows 中和线程有关系的数据结构: ETHREAD.KTHREAD.TEB 1. 相关阅读材料 <windows 内核原理与实现> --- 潘爱民 2. 数据结构分析 我们 ...

  4. Windows x64内核学习笔记(四)—— 9-9-9-9-12分页

    Windows x64内核学习笔记(四)-- 9-9-9-9-12分页 前言 9-9-9-9-12分页 实验一:线性地址转物理地址 页表基址 定位基址 PTE to PXE 实验二:通过页表基址定位各 ...

  5. Windows x64内核学习笔记(三)—— SMEP SMAP

    Windows x64内核学习笔记(三)-- SMEP & SMAP SMEP & SMAP 实验:构造IDT后门 第一步:编译以下代码 第二步:构造IDT后门 第三步:运行程序 第四 ...

  6. Windows x64内核学习笔记(二)—— IA-32e模式

    Windows x64内核学习笔记(二)-- IA-32e模式 IA-32e模式 模式检测 强制平坦段 任务切换 中断门描述符 FS / GS 模式切换 32位程序进内核 64位程序进内核 实验:模式 ...

  7. Windows x64内核学习笔记(五)—— KPTI(未完待续)

    Windows x64内核学习笔记(五)-- KPTI(未完待续) KPTI 实验一:构造IDT后门并读取Cr3 参考资料 KPTI 描述:KPTI(Kernel page-table isolati ...

  8. Windows x64内核学习笔记(一)—— 环境与配置

    Windows x64内核学习笔记(一)-- 环境与配置 前言 新特性 基础要求 实验环境 Guest Win10配置 问题解决 参考资料 前言 之前,跟着海哥学习了windows内核的一些机制,包括 ...

  9. Windows驱动开发学习笔记(二)—— 驱动调试内核编程基础

    Windows驱动开发学习笔记(二)-- 驱动调试&内核编程基础 基础知识 驱动调试 PDB(Program Debug Database) WinDbg 加载 PDB 实验:调试 .sys ...

最新文章

  1. SDK开发日积月累(二)
  2. 修复mysql数据库供应商_修复MYSQL数据库
  3. 线程池设计中的惊群问题
  4. OO第一单元总结博客
  5. delegate、Lambda表达式、Func委托和Expression(TDelegate)表达式目录树
  6. 【记录】python多线程的使用 线程同步(LOCK和RLOCK) python与mysql数据库交互实现增加和查找 python的格式化输出
  7. 无法载入增效工具_山东省 智能工具箱 智能工具管理 工具管理企业数字化管理...
  8. mysql分表方法实现
  9. win10你的电脑设备需要修复_cf挑战辅助w10蓝屏后若何用命令提示符修复
  10. HTML如何引入外部JS文件
  11. 一个局域网联机小游戏
  12. 游戏安全02:手游外挂简单分类和实现原理介绍
  13. 区块链入门导航-磨链社区
  14. Refresh PDB
  15. Android设备图标显示模糊问题
  16. 《哪来的天才》读书笔记
  17. aardio - 范例搜索工具
  18. C++——输入、输出和文件
  19. 云计算时代的进阶者,专访景安董事长杨小龙
  20. POI批量导出Excel ZIP打包下载

热门文章

  1. c语言电子地图程序,C语言 电子地图信息
  2. 用指针交换两个数_LeetCode双指针系列
  3. python自动翻译excel某一列_【python excel实例教程】怎样用Python将excel的某一列生成一个列表?...
  4. 如何用JS实现泛玉米解析
  5. Model 3车主对FSD套件不满意 德国法院下令特斯拉回购汽车
  6. 网信办:2021年全国受理网络违法和不良信息举报1.66亿件
  7. 不玩了?王思聪退出香蕉娱乐董事长职务,麻闻多接任
  8. “万物互联”的时代来了!鸿蒙系统OS 2.0重磅发布:“朋友圈”逐渐扩大
  9. 疑似vivo X60t Pro在工信部入网:搭载天玑1200 主打线下渠道
  10. 力压腾讯!《原神》连续5个月成中国手游海外收入冠军