Linux下如何使用X86 CPU的GPIO
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)获得。中断vector由BIOS配置,反编译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的更多相关文章
- Linux下查看内核、CPU、内存及各组件版本的命令和方法
Linux下查看内核.CPU.内存及各组件版本的命令和方法 Linux查看内核版本: uname -a more /etc/*release ...
- Linux下分析某个进程CPU占用率高的原因
Linux下分析某个进程CPU占用率高的原因 通过top命令找出消耗资源高的线程id,利用strace命令查看该线程所有系统调用 1.top 查到占用cpu高的进程pid 2.查看该pid的线程 ...
- 性能测试分析过程(三)linux下查看最消耗CPU/内存的进程
linux下查看最消耗CPU 内存的进程 1.CPU占用最多的前10个进程: ps auxw|head -1;ps auxw|sort -rn -k3|head -10 2.内存消耗最多的前10 ...
- Linux下用命令查看CPU ID以及厂家等信息
Linux下用命令查看CPU ID // 获得CPU IDdmidecode -t 4 | grep ID |sort -u |awk -F': ' '{print $2}' // 获得磁盘IDfdi ...
- Linux下如何查看高CPU占用率线程
转于:http://www.cnblogs.com/lidabo/p/4738113.html 目录(?)[-] proc文件系统 proccpuinfo文件 procstat文件 procpidst ...
- Linux下内存占用和CPU占用的计算
->使用free命令查看内存使用情况: 1.echo 3 > /proc/sys/vm/drop_caches 2.free 或者使用cat /proc/yourpid/status 来查 ...
- linux 下查看机器是cpu是几核的(转)
几个cpu more /proc/cpuinfo |grep "physical id"|uniq|wc -l 每个cpu是几核(假设cpu配置相同) more /proc/cpu ...
- linux 下查看机器是cpu是几核的
几个cpu more /proc/cpuinfo |grep "physical id"|uniq|wc -l 每个cpu是几核(假设cpu配置相同) more /proc/cpu ...
- 方法:Linux 下用JAVA获取CPU、内存、磁盘的系统资源信息
CPU使用率: InputStream is = null; InputStreamReader isr = null; BufferedReader brStat = null; StringTok ...
随机推荐
- React 服务端渲染方案完美的解决方案
最近在开发一个服务端渲染工具,通过一篇小文大致介绍下服务端渲染,和服务端渲染的方式方法.在此文后面有两中服务端渲染方式的构思,根据你对服务端渲染的利弊权衡,你会选择哪一种服务端渲染方式呢? 什么是服务 ...
- 智能卡加密芯片SMEC90ST
深圳市中巨伟业信息科技有限公司 最新推出一款单价低,安全性高的智能卡安全芯片,产品型号为:SMEC90ST,采用32-bit ARM SC100 SecureCore Processor 安全内核处理 ...
- Hbuilder获取手机当前地理位置的天气
前言:前面一段时间,公司项目里有一个需求 是获取当前手机地理位置当天的天气情况 将实时天气信息提供给客户.在网上搜索资料时候,发现知识很零碎,自己实现以后整理出来,方便于各位的学习与使用. 一.获取 ...
- 更改MySQL 5.7的数据库的存储位置
操作系统:Windows 10 x64 MySQL安装包版本:mysql-installer-community-5.7.17.0 参考:MySQL 5.7版本的安装使用详细教程+更改数据库data的 ...
- 【题解】[国家集训队]happiness
题目戳我 \(\text{Solution:}\) 显然还是一个分组问题.对于理科和文科我们可以看出最小割模型,而处理同时选择某一学科的时候,需要我们根据套路建立虚点处理. 同 小M的作物 一题,这题 ...
- Pock 把 Touch Bar 变成系统中的 Dock 栏
Pock 把 Touch Bar 变成系统中的 Dock 栏 Pock 是一款 macOS App,你可以通过它把 Touch Bar 变成系统中的 Dock 栏,直接用来切换和启动 App,尽享全屏 ...
- 多测师讲解rf _基本使用002_高级讲师肖sir
在你安装好RF-ride之后,桌面就会生成一个RIDE图标.双击启动,界面如下:
- 编程体系结构(08):Spring.Mvc.Boot框架
本文源码:GitHub·点这里 || GitEE·点这里 一.Spring框架 1.框架概述 Spring是一个开源框架,框架的主要优势之一就是其分层架构,分层架构允许使用者选择使用哪一个组件,同时为 ...
- JSX 详解
一 jsx 的本质是什么? jsx是语法糖,需要被编译成js才能运行. jsx 看似是html 结构,实质是js结构的语法糖,在代码编译阶段被编译成js结构.所以jsx的本质可描述为看似html结构的 ...
- k8s集群,使用pvc方式实现数据持久化存储
环境: 系统 华为openEulerOS(CentOS7) k8s版本 1.17.3 master 192.168.1.244 node1 192.168.1.245 介绍: 在Kubernetes中 ...