1.前言

在arm嵌入式开发中,各个外设具有固定的物理地址,我们可以直接通过芯片手册来编写驱动配置后使用。但是在x86中有所不同,所有外设控制器集成在PCH(曾经的南桥)中,每个外设都是作为一个PCI设备挂在PCH的PCI总线上,PCH再通过DMI与CPU相联。对于标压处理器H/K系列(也就是我们台式机),南桥还在主板上,对于x86移动处理器(Y/U结尾系列),已将PCH和CPU集成到同一封装中,与如今各类SOC类似,如下(详见datasheet)。

由于x86中每个外设是一个PCI设备,所以我们要使用某个外设就需要为其分配内存空间映射、IRQ和I/O基址,x86中这些资源配置是由BIOS(UEFI)完成的,因为每块主板设计和外设使用不一样,就需要不一样的配置,所以不同的主板厂商需要定制自己主板的BIOS 。

BIOS配置单板板使用外设后,一些BIOS(UEFI)通过ACPI(高级配置和电源接口)的DSDT来传递设备信息(类似arm设备树,但功能更强)给操作系统,获取这些设备信息后我们才能配置和使用这个外设,但ACPI对各个操作系统有兼容性问题,这就会出现你在Windows设备管理器能看到该设备,到linux下什么也没有,因为大部分X86硬件厂商的BIOS主要兼容Windows为主,一般桌面CPU都是用的Windows系统嘛。

本文说的GPIO就是这么个问题,linux下无法使用,由于涉及的东西有点多,所以简单介绍在如何将x86工控机引出的GPIO使用起来的(注意:是CPU的GPIO引脚,不是Super IO的GPIO)。

CPU :英特尔7代低压处理器( Kaby Lake) i5-7200U/赛扬3865U

linux:linux 4.0以上

2.linux pinctrl子系统

要使用gpio需要先看一下linux系统PINCTRL子系统,层级如下所示(图片来源蜗窝科技):

最底层是硬件控制器,其上是操作这些硬件的相关驱动(pin controller driver),不同的控制器有不同底层驱动,一般由芯片厂商BSP完成;pin controller driver初始化的时候会向pin control core模块注册pin control设备(通过pinctrl_register这个bootom level interface)。pin control core模块是一个硬件无关模块,它抽象了所有pin controller的硬件特性,仅仅从用户(各个driver就是pin control subsystem的用户)角度给出了top level的接口函数,这样,各个driver不需要关注pin controller的底层硬件相关的内容,使用时直接向pinctrl子系统申请IO资源即可。关于linux GPIO与pinctrl子系统信息,详见蜗窝科技-GPIO子系统.

pin controller driver成功注册到pin control core后,我们通过pin control core导出到sysfs的文件就可以直接操作一个GPIO,使其输入输出,而不需要专门去写一个驱动模块。

3. pin controller driver

搞嵌入式的一定对platform bus非常熟悉,pin controller driver的注册同样离不开platform bus,driver与device必须经过某种匹配后,才能进一步执行probe注册到系统中。

结合前言中对x86设备的描述,platform bus可通过以下两种方式来判断driver和device是否匹配。

  • 方式一,由BIOS通过ACPI 中DSDT传递控制器设备节点描述给linux(可类比设备树),linux内核启动过程中解析处理DSDT信息,自动构造device设备并添加到Platform bus,添加过程中匹配ACPI_ID,触发执行pin controller driver 的probe()函数。
  • 方式二,linux扫描PCI总线设备创建设备并添加,PCI驱动匹配vendor、device、class后触发执行pin controller driver 的probe()函数。

别忘了前提,启动时BIOS必须为使用的PCI设备分配好设备中断号(中断vector)、映射空间地址等我们才能用。那对于我们的GPIO设备linux系统使用的是哪种方式呢,这需要到源码中来看,首先七代系列CPU linux pinctrl driver源码文件为\drivers\pinctrl\intel\pinctrl-sunrisepoint.c,看如下代码。

static const struct acpi_device_id spt_pinctrl_acpi_match[] = {
{ "INT344B", (kernel_ulong_t)&sptlp_soc_data },
{ "INT345D", (kernel_ulong_t)&spth_soc_data },
{ }
};
MODULE_DEVICE_TABLE(acpi, spt_pinctrl_acpi_match);
.....
static struct platform_driver spt_pinctrl_driver = {
.probe = spt_pinctrl_probe,
.driver = {
.name = "sunrisepoint-pinctrl",
.acpi_match_table = spt_pinctrl_acpi_match,
.pm = &spt_pinctrl_pm_ops,
},
};

可以看到使用的是ACPI模式,那么驱动的注册逻辑应该如下,

其中driver把系统中所有的pin描述出来,并将driver注册到platform bus。driver需要对应的device才能工作,但是linux因为ACPI的兼容性问题,linux并没有解析DSDT并创建出GPIO 相关的device,所以没有触发执行probe来将pin controller driver注册到pin control core中,pinctrl子系统没有工作当然无法使用。到这里我们去解决内核对ACPI的解析(或者说兼容性问题)显然是不太现实的(自己太菜(╯﹏╰)),有没有其他办法呢?

先阅读源码看看,probe()执行过程中需要用到device的哪些resource,只要我们能获取到这些resource,自己手动构造一个device注册到platform bus不就行了,O(∩_∩)O哈哈~。

int intel_pinctrl_probe(struct platform_device *pdev,
const struct intel_pinctrl_soc_data *soc_data)
{
......
for (i = 0; i < pctrl->ncommunities; i++) {
......
res = platform_get_resource(pdev, IORESOURCE_MEM,
community->barno);//0
regs = devm_ioremap_resource(&pdev->dev, res);
......
}
......
irq = platform_get_irq(pdev, 0);
......
}

可以看到pin controller driver需要pincontrler 的地址空间和使用的中断号两部分资源,其中地址空间是三个,因为所有GPIO由三个GPIO控制器组成,三个GPIO控制器共享相同的中断线,三个GPIO控制器作为一个PCI设备。如何获取这两个信息呢?

4.手动构造device

上面通过阅读源代码得知,intel-pinctrl需要pincontrler 地址空间、和使用的中断号两部分资源。

地址空间起始地址可通过PCI 设备P2SB Bridge (D31:F1)获得。中断vectorBIOS配置,反编译BIOS给linux传递的ACPI信息,看是否有中断vector相关信息:

在板子上进入/sys/firmware/acpi/tables,将目录下所有文件考出,使用acpi工具iasl对DSDT文件进行反编译:

iasl -d DSDT.dat

得到AML文件 DSDT.dsl,里面包含BIOS开发的各设备节点信息。

打开 DSDT.dsl并找到pin controler设备节点描述,只需要搜索驱动里的"INT344B"或"INT345D"就能定位到。到这里我们也明白了,为什么驱动里的spt_pinctrl_acpi_match[]有两像,原来是一个代表标压处理器(H),一个代表低压处理器(U)。

 Device (GPI0)
{
Method (_HID, 0, NotSerialized) // _HID: Hardware ID
{
If ((PCHV () == SPTH))
{
If ((PCHG == 0x02))
{
Return ("INT3451")
}
Return ("INT345D") //表示7代标压处理器
}
Return ("INT344B") //表示7代低压处理器
Name (LINK, "\\_SB.PCI0.GPI0")
Method (_CRS, 0, NotSerialized) // _CRS: Current Resource Settings
{
Name (RBUF, ResourceTemplate ()
{
Memory32Fixed (ReadWrite,
0x00000000, // Address Base
0x00010000, // Address Length 地址空间大小
_Y2E)
Memory32Fixed (ReadWrite,
0x00000000, // Address Base
0x00010000, // Address Length 地址空间大小
_Y2F)
Memory32Fixed (ReadWrite,
0x00000000, // Address Base
0x00010000, // Address Length 地址空间大小
_Y31)
Interrupt (ResourceConsumer, Level, ActiveLow, Shared, ,, _Y30)
{
0x0000000E, //中断号
}
})
CreateDWordField (RBUF, \_SB.PCI0.GPI0._CRS._Y2E._BAS, COM0) // _BAS: Base Address
CreateDWordField (RBUF, \_SB.PCI0.GPI0._CRS._Y2F._BAS, COM1) // _BAS: Base Address
CreateDWordField (RBUF, \_SB.PCI0.GPI0._CRS._Y30._INT, IRQN) // _INT: Interrupts
COM0 = (SBRG + 0x00AF0000)
COM1 = (SBRG + 0x00AE0000)
CreateDWordField (RBUF, \_SB.PCI0.GPI0._CRS._Y31._BAS, COM3) // _BAS: Base Address
COM3 = (SBRG + 0x00AC0000)
IRQN = SGIR /* \SGIR */
Return (RBUF) /* \_SB_.PCI0.GPI0._CRS.RBUF */ }

你可能看不懂上面面的信息,到底哪个是标压哪个是低压?没关系,我们去pin controller driver中,里面有注释,反推一下就知道INT345D代表的是标压,INT344B代表的是低压。

/* Sunrisepoint-LP */
static const struct pinctrl_pin_desc sptlp_pins[] = {
....
}
static const struct intel_pinctrl_soc_data sptlp_soc_data = {
.pins = sptlp_pins,
...
}
.....
/* Sunrisepoint-H */
static const struct pinctrl_pin_desc spth_pins[] = {
....
}
static const struct intel_pinctrl_soc_data spth_soc_data = {
.pins = spth_pins,
...
}
static const struct acpi_device_id spt_pinctrl_acpi_match[] = {
{ "INT344B", (kernel_ulong_t)&sptlp_soc_data },
{ "INT345D", (kernel_ulong_t)&spth_soc_data },
{ }
};

回到正题,我们从 DSDT.dsl获取得到中断号: 0xE,三个地址空间起始地址及大小。构建一个platform_device 如下:

#include <linux/debugfs.h>
#include <linux/ioport.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h> #define P2SB_PORTID_SHIFT 16
#define P2SB_PORT_GPIO3 0xAC
#define P2SB_PORT_GPIO2 0xAD /*未使用*/
#define P2SB_PORT_GPIO1 0xAE
#define P2SB_PORT_GPIO0 0xAF #define sbreg_addr 0xfd000000 /*Address Base*/ /*Community 0*/
#define SPT_PINCTRL_COMMUNITY0_OFFSET sbreg_addr + (P2SB_PORT_GPIO0 << P2SB_PORTID_SHIFT)
#define SPT_PINCTRL_COMMUNITY0_SIZE 0x00010000
/*Community 1*/
#define SPT_PINCTRL_COMMUNITY1_OFFSET sbreg_addr + (P2SB_PORT_GPIO1 << P2SB_PORTID_SHIFT)
#define SPT_PINCTRL_COMMUNITY1_SIZE 0x00010000
/*Community 2*/
#define SPT_PINCTRL_COMMUNITY2_OFFSET sbreg_addr + (P2SB_PORT_GPIO2 << P2SB_PORTID_SHIFT)
#define SPT_PINCTRL_COMMUNITY2_SIZE 0x00010000
/*Community 3*/
#define SPT_PINCTRL_COMMUNITY3_OFFSET sbreg_addr + (P2SB_PORT_GPIO3 << P2SB_PORTID_SHIFT)
#define SPT_PINCTRL_COMMUNITY3_SIZE 0x00010000 static struct resource intel_pinctrl_dev_resources[] = {
/* iomem resource */
DEFINE_RES_MEM_NAMED(SPT_PINCTRL_COMMUNITY0_OFFSET, SPT_PINCTRL_COMMUNITY0_SIZE, NULL),
DEFINE_RES_MEM_NAMED(SPT_PINCTRL_COMMUNITY1_OFFSET, SPT_PINCTRL_COMMUNITY1_SIZE, NULL),
// DEFINE_RES_MEM_NAMED(SPT_PINCTRL_COMMUNITY2_OFFSET, SPT_PINCTRL_COMMUNITY2_SIZE, NULL),/*未使用*/
DEFINE_RES_MEM_NAMED(SPT_PINCTRL_COMMUNITY3_OFFSET, SPT_PINCTRL_COMMUNITY3_SIZE, NULL),
/* irq resource */
DEFINE_RES_IRQ(0x0E), /*反编译BIOS DSDT获取*/
}; static struct platform_device intel_pinctrl_device = {
.name = "sunrisepoint-pinctrl",
.id = -1,
.resource = intel_pinctrl_dev_resources,
.num_resources = ARRAY_SIZE(intel_pinctrl_dev_resources),
}; static int __init intel_spt_device_init(void)
{
return platform_device_register(&intel_pinctrl_device);
}
module_init(intel_spt_device_init); static void __exit intel_spt_device_exit(void)
{
platform_device_unregister(&intel_pinctrl_device);
}
module_exit(intel_spt_device_exit); MODULE_AUTHOR("wsg1100");
MODULE_DESCRIPTION("Intel sunrisepoint pinctrl device");
MODULE_LICENSE("GPL v2");

随内核编译后,加载模块,intel pinctrl子系统正常工作,(^o^)/。

注意,相同平台,不同BIOS PCI信息可能不同!文中提供的只是一种方法

版权声明:本文为本文为博主原创文章,转载请注明出处,博客地址:https://www.cnblogs.com/wsg1100/。如有错误,欢迎指正。

Linux下如何使用X86 CPU的GPIO的更多相关文章

  1. Linux下查看内核、CPU、内存及各组件版本的命令和方法

    Linux下查看内核.CPU.内存及各组件版本的命令和方法 Linux查看内核版本: uname -a                        more /etc/*release       ...

  2. Linux下分析某个进程CPU占用率高的原因

      Linux下分析某个进程CPU占用率高的原因 通过top命令找出消耗资源高的线程id,利用strace命令查看该线程所有系统调用  1.top 查到占用cpu高的进程pid 2.查看该pid的线程 ...

  3. 性能测试分析过程(三)linux下查看最消耗CPU/内存的进程

    linux下查看最消耗CPU  内存的进程 1.CPU占用最多的前10个进程:  ps auxw|head -1;ps auxw|sort -rn -k3|head -10  2.内存消耗最多的前10 ...

  4. Linux下用命令查看CPU ID以及厂家等信息

    Linux下用命令查看CPU ID // 获得CPU IDdmidecode -t 4 | grep ID |sort -u |awk -F': ' '{print $2}' // 获得磁盘IDfdi ...

  5. Linux下如何查看高CPU占用率线程

    转于:http://www.cnblogs.com/lidabo/p/4738113.html 目录(?)[-] proc文件系统 proccpuinfo文件 procstat文件 procpidst ...

  6. Linux下内存占用和CPU占用的计算

    ->使用free命令查看内存使用情况: 1.echo 3 > /proc/sys/vm/drop_caches 2.free 或者使用cat /proc/yourpid/status 来查 ...

  7. linux 下查看机器是cpu是几核的(转)

    几个cpu more /proc/cpuinfo |grep "physical id"|uniq|wc -l 每个cpu是几核(假设cpu配置相同) more /proc/cpu ...

  8. linux 下查看机器是cpu是几核的

    几个cpu more /proc/cpuinfo |grep "physical id"|uniq|wc -l 每个cpu是几核(假设cpu配置相同) more /proc/cpu ...

  9. 方法:Linux 下用JAVA获取CPU、内存、磁盘的系统资源信息

    CPU使用率: InputStream is = null; InputStreamReader isr = null; BufferedReader brStat = null; StringTok ...

随机推荐

  1. 干货满满!关于Pycharm远程开发

    可以在Windows中使用Pycharm编写代码,而代码的调试运行可以使用远程服务器中的python解释器. 在本地创建好工程项目(或从git上clone下代码)后,用Pycharm打开: 打开「To ...

  2. 学习Maven有感

    1.maven的由来 maven是一款服务于java平台的自动化构建工具 构建定义:把动态的Web工程经过编译得到的编译结果部署到服务器上的整个过程. 编译:java源文件[.java]->编译 ...

  3. Python-全局解释器锁GIL原理和多线程产生原因与原理-多线程通信机制

    GIL 全局解释器锁,这个锁是个粗粒度的锁,解释器层面上的锁,为了保证线程安全,同一时刻只允许一个线程执行,但这个锁并不能保存线程安全,因为GIL会释放掉的并且切换到另外一个线程上,不会完全占用,依据 ...

  4. Web Storage API的介绍和使用

    目录 简介 浏览器的本地存储技术 Web Storage相关接口 浏览器兼容性 隐身模式 使用Web Storage API 总结 简介 Web Storage为浏览器提供了方便的key value存 ...

  5. 084 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 02 构造方法介绍 03 构造方法-this关键字

    084 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 02 构造方法介绍 03 构造方法-this关键字 本文知识点:构造方法-this关键字 说明:因为时间紧 ...

  6. C++ CComboBox控件详解

    转载:http://blog.sina.com.cn/s/blog_46d93f190100m395.html C++ CComboBox控件详解 (2010-09-14 14:03:44) 转载▼ ...

  7. Arduino control Eeprom by IIC method of using device address in Arduino

    参考: 1.https://www.arduino.cc/ 2.https://www.arduino.cc/reference/en/ 3.https://www.arduino.cc/en/Ref ...

  8. 【优化】单调队列与dp

    笔者大概看了一下单调队列对于DP的优化,故撰此文,望有帮助. (dp还是推式子难啊qwq) 例题1. 题目大意:在n个数的序列中,选择数字,使得其连续不超过k个数,且和最大. 本题的方程相对好推:设d ...

  9. ubuntu20 使用命令安装 mysql

    命令安装 mysql sudo apt-get update sudo apt-get install -y mysql-server mysql-client 查看 mysql 安装情况 servi ...

  10. Solr单机安装

    Solr单机安装 一.下载 自行到官网下载所需版本即可,我这里使用的是solr-7.1.0solr官网:http://archive.apache.org/dist/lucene/solr/ 二.安装 ...