Linux下如何使用X86 CPU的GPIO

2020-11-05 閃念基因

作者:wsg1100

出處:http://www.cnblogs.com/wsg1100/

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/賽揚3865Ulinux: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是否匹配。

probe()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 0x00010000static 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信息可能不同!文中提供的只是一種方法

作者:wsg1100

出處:http://www.cnblogs.com/wsg1100/

相關焦點

  • ARM Linux下訪問GPIO埠
    Input/Output的縮寫)就是晶片的引腳,引腳是可編程的可對引腳的工作模式進行設置:輸入模式(檢測輸入信號),輸出模式(輸出0或1),高阻狀態(常用於AD轉換),還有禁止或允許上內部下拉電阻(上拉:管腳通過電阻接高電平,下拉:管腳通過電阻接地,也可以外部接上拉或下拉電阻),還有管腳復用等功能,即通過對內部寄存器的設置使引腳既可以工作在一般模式,作為普通的GPIO口使用
  • linux下如何獲取cpu的利用率
    這裡將介紹如何從/proc文件系統中獲取與防火牆相關的一些性能參數,以及如何通過/proc文件系統修改內核的相關配置。cpu的使用情況2、等上一個時間段3、再記錄此刻的cpu使用情況4、計算總的時間片 把第一次的所有cpu使用情況求和,得到j1把第二次的所有cpu使用情況求和,得到j2 j2-j1得到這個時間段的所有時間片 即total=j2-j1=第二次的所有列的和-第一次的所有列的和5、計算idle時間idle對應第五列的數據,用第二次的減去第一次的即可
  • Linux下如何獲取cpu的利用率
    這裡將介紹如何從/proc文件系統中獲取與防火牆相關的一些性能參數,以及如何通過/proc文件系統修改內核的相關配置。> 2、等上一個時間段 3、再記錄此刻的cpu使用情況 4、計算總的時間片 把第一次的所有cpu使用情況求和,得到j1 把第二次的所有cpu使用情況求和,得到j2 j2-j1得到這個時間段的所有時間片 即total=j2-j1=第二次的所有列的和-第一次的所有列的和 5、計算idle
  • Linux CommonClock Framework分析之四 gpio clk gate驅動實現
    前面我們已經完成了CCF子系統的分析,也說明了如何實現CCF驅動,本章為該專欄的最後一篇文章, 本章我們將實現一個虛擬的gpio clk gate驅動。),而clk provider的map存在兩種方式:若linux不支持設備樹機制,則通過調用接口clk_register_clkdev,完成這種映射操作(即完成下圖中「非設備樹模式下clk_core的map」)。
  • 基於Linux系統實現DragonBoard 410c GPIO的控制
    通過晶片的GPIO口輸出電平去控制外部設備,如繼電器、LED或者觸發某些模塊進行工作是嵌入式控制系統常用的功能,也是實現許多複雜控制系統的基礎,本文將教大家基於DragonBoard 410c GPIO開發板在linux的環境下完成對DragonBoard 410c GPIO的控制,並且通過控制按鍵和點亮LED等進行實例測試以熟悉整個GPIO操作過程。
  • 「正點原子FPGA連載」第二十七章gpio子系統下的驅動實驗
    所以本章就帶大家完成一個gpio子系統下的led驅動程序,本章我們在第二十五章實驗基礎上完成,本章實驗重點內容如下:①修改第二十五章創建的led節點,在節點中指定led使用的GPIO。②去掉led節點中的reg屬性,因為不需要用到了。③在第二十三章驅動實驗的基礎上修改驅動代碼,獲取設備樹中傳入的gpio,調用gpio子系統提供的接口函數對gpio進行操控。
  • Android Linux 內核介紹
    從下一篇開始將詳細介紹每一個Android內核驅動程序及其作用。原始碼位於drivers/staging/android/timed_output.c(timed_gpio.c)。Yaffs2文件系統 ,Android 採用Yaffs2作為MTD nand flash文件系統,原始碼位於fs/yaffs2/目錄下。
  • 嵌入式Linux設備驅動開發之:GPIO驅動程序實例
    圖11.4LED(左)和蜂鳴器(右)的驅動電路原理圖在圖11.4中,可知使用S3C2410處理器的通用I/O口GPF4、GPF5、GPF6和GPF7分別直接驅動LEDD12、D11、D10以及D9,而使用GPB0埠驅動蜂鳴器。4個LED分別在對應埠(GPF4~GPF7)為低電平時發亮,而蜂鳴器在GPB0為高電平時發聲。
  • dm6441的gpio驅動
    由於硬體資源的原因,DM6441並不是GPIO管腳就是純粹的GPIO腳,GPIO管腳和其他一些標準接口復用相同的引腳,比如SPI和GPIO復用,I2C和GPIO復用等,到底是使用GPIO還是其他接口,在初始化的時候,都需要對PINMUX0和PINMUX1兩個寄存器進行設置(見DM6441的晶片p72),而軟體設置則在Montavista linux-2.6.18_pro500
  • Linux Lab 推出十大精彩使用案例
    學習彙編語言和處理器指令集Linux Lab 不只準備了 x86 的 gcc 編譯器,而且準備了其他幾大主流處理器架構的編譯器,學習各種架構編譯器不在話下,而且可以立即使用 qemu 來執行。powerpc powerpc64 README.md riscv32 riscv64 x86 x86_64ubuntu@linux-lab:/labs/linux-lab/examples/assembly/x86$ makeas --32 -o x86-hello.o x86-hello.sld -m elf_i386 -o x86-hello x86-hello.o/labs/
  • 如何在 Linux 上安裝/更新 Intel 微碼固件 | Linux 中國
    這些來自 Intel/AMD 的微碼的更新可以去修復 bug 或者使用補丁來防範 bug。這篇文章演示了如何使用包管理器或由 lntel 提供的 Linux 處理器微碼更新來安裝 AMD 或 Intel 的微碼更新。
  • Linux下如何獲取CPU內存等硬體信息?
    前言在linux環境下,我們有時候需要寫一些有關伺服器配置信息的文檔,這時候,如果我們本身沒有這些這些伺服器的購置信息,就需要藉助命令查詢出來,然後匯總到一個表格裡,主要用於一些文檔需要。Linux下獲取CPU內存信息的命令1.
  • 「正點原子Linux連載」第四十五章 pinctrl和gpio子系統實驗一
    Linux內核提供了pinctrl和gpio子系統用於GPIO驅動,本章我們就來學習一下如何藉助pinctrl和gpio子系統來簡化GPIO驅動開發。如果使用過STM32的話應該都記得,STM32也是要先設置某個PIN的復用功能、速度、上下拉等,然後再設置PIN所對應的GPIO。其實對於大多數的32位SOC而言,引腳的設置基本都是這兩方面,因此Linux內核針對PIN的配置推出了pinctrl子系統,對於GPIO的配置推出了gpio子系統。本節我們來學習pinctrl子系統,下一節再學習gpio子系統。
  • 教你如何在 Ubuntu Linux 中獲取CPU溫度
    VMware Virtual Platform [None]# Board: Intel Corporation 440BX Desktop Reference Platform# Kernel: 4.15.0-30Ubuntu-generic x86_64# Processor: Intel(R) Core(TM) i7-8700 CPU @ 3.20GHz (6/158/
  • 控制IO埠 s3c2410_gpio_setpin()的使用
    本文基於FL2440 ARM開發板Linux內核版本 2.6.28.7本文引用地址:http://www.eepw.com.cn/article/201611/318858.htmarm-linux-gcc
  • GPIO子系統重要概念
    ;gpio-cells = <2>」表示這個控制器下每一個引腳要用2個32位的數(cell)來描述。1.3 在驅動代碼中調用GPIO子系統在設備樹中指定了GPIO引腳,在驅動代碼中如何使用?也就是GPIO子系統的接口函數是什麼?
  • linux下SCP指令的使用
    通常在Linux下執行遠程拷貝文件使用,他和cp指令類似,只不過cp是本機使用,而SCP則是跨機器使用。SCP傳輸是基於SSH的加密傳輸,也就是說知道ssh的帳密就可以上下載文件了,因此比較安全。SCP常用實例(在linux的centos環境下為例,從192.168.200.10現在一個文件到本地)1、從遠程伺服器下載文件到本地伺服器。
  • Kubernetes 資源配額使用指南 | Linux 中國|linux|metadata|cpu|...
    本文字數:4695,閱讀時長大約: 5分鐘  https://linux.cn/article-13006-1.html 作者:Mike Calizo 譯者:zwb  當 Kubernetes 集群運行過一段時間或者在被開發者大量使用後, 資源(例如 CPU 和內存)的控制的問題就會顯現出來。
  • IO埠映射和IO內存映射(詳解S3C24XX_GPIO驅動)
    剛剛我們實現了linux系統內存的分配,讀寫,釋放功能,下面,我們一鼓作氣將IO埠映射及IO內存映射搞定,加油!外設使用的是總線地址,CPU使用的是物理地址。物理地址與總線地址之間的關係由系統的設計決定的。在x86平臺上,物理地址就是總線地址,這是因為它們共享相同的地址空間——這句話有點難理解,詳見下面的"獨立編址"。在其他平臺上,可能需要轉換/映射。比如:CPU需要訪問物理地址是0xfa的單元,那麼在x86平臺上,會產生一個PCI總線上對0xfa地址的訪問。
  • 講解Linux 中的 32位與64位
    隨著技術的發展,32位CPU已經無法滿足需求,intel與AMD採用不同的方法開發64位架構的CPU,intel使用與x86完全不同的IA64架構,由於與原來的X86完全不兼容,因此在PC領域沒有得到應用,只在伺服器領域有一些使用。