最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Linux的GPIO驅(qū)動程序使用詳解

 更新時間:2025年10月17日 16:07:19   作者:大聰明-PLUS  
文章詳細(xì)解析了Linux系統(tǒng)中GPIO驅(qū)動程序的結(jié)構(gòu)及多層抽象,說明了從硬件寄存器到內(nèi)核與用戶空間API的實現(xiàn)過程,并通過具體開發(fā)板實例,展示了驅(qū)動注冊、訪問與共享機制,總結(jié):Linux GPIO驅(qū)動層次清晰,保障了平臺兼容性與安全訪問

讓我們看看 Linux 中 GPIO 驅(qū)動程序的具體結(jié)構(gòu),以及為什么要這樣做。這樣我們就能理解為什么在這個操作系統(tǒng)中,僅僅為了讓 LED 閃爍就需要經(jīng)過 N 層抽象。

上述內(nèi)容大部分也適用于其他驅(qū)動程序。GPIO 被選為最簡單的外圍設(shè)備示例。

我們的文章結(jié)構(gòu)如下:

  • 我們先來看看硬件層面的GPIO控制;
  • 我們來研究一下操作系統(tǒng)給我們增加了哪些要求;
  • 讓我們考慮一下這些要求通常是如何實現(xiàn)的;
  • 讓我們看看 Linux 中如何為不同的 SoC 實現(xiàn)真正的 GPIO 驅(qū)動程序;
  • 讓我們再次簡單總結(jié)一下研究結(jié)果。

硬件級別

在硬件層面,一切都非常簡單。處理器有一個 GPIO 引腳控制器,從程序員的角度來看,它就是映射到通用地址空間的十幾個寄存器。

要點亮 LED,我們首先需要初始化所需的引腳(將其配置為輸出),然后設(shè)置所需的值。

基本上,寄存器中有兩個條目。一個條目位于確定輸出方向的寄存器中。第二個條目位于確定輸出值的寄存器中。這可以用十幾條 CPU 指令完成。

操作系統(tǒng)限制

當(dāng)我們開始在操作系統(tǒng)中實現(xiàn)相同的操作時,我們會產(chǎn)生額外的需求。

這些需求會導(dǎo)致額外的代碼,但與此同時,這些需求的實現(xiàn)正是確保操作系統(tǒng)便捷運行的關(guān)鍵因素。

從內(nèi)核模式和用戶模式訪問外設(shè)

在用戶空間運行的應(yīng)用程序無法訪問外設(shè)寄存器和其他服務(wù)內(nèi)存區(qū)域。

這是像 Linux 這樣的操作系統(tǒng)的基本要求之一——用戶進程不應(yīng)該破壞其他進程的內(nèi)存,尤其是內(nèi)核內(nèi)存。

這意味著需要一個在內(nèi)核空間運行并為用戶模式應(yīng)用程序提供 API 的驅(qū)動程序。

GPIO 引腳必須由內(nèi)核控制(例如,來自鄰近的驅(qū)動程序),也就是說,內(nèi)核模式也需要一個 API。

訪問共享

多個用戶空間進程可能想要管理同一個輸出。必須提供一種機制來防止多個進程同時訪問該輸出時出現(xiàn)錯誤。

多個內(nèi)核空間進程可能想要使用相同的輸出。這些情況也需要處理,但需要排除錯誤。

標(biāo)準(zhǔn) API

許多 SoC 中集成了多種 GPIO 端口實現(xiàn)。無論具體實現(xiàn)如何,驅(qū)動程序都必須提供相同的一組“旋鈕”(換句話說,相同的 API)。原因如下:

  • 用戶空間應(yīng)用程序獲得了額外的平臺 獨立性——在任何 Linux 平臺上,GPIO 引腳都以相同的方式控制;
  • 對于在工作中使用 GPIO 引腳的驅(qū)動程序,也賦予了類似的自由度(例如,觸摸控制器或 Wi-Fi 模塊的驅(qū)動程序可以使用 GPIO 引腳進行電源管理)。

此外,大多數(shù) SoC 可以使用 GPIO 引腳接收外部中斷。Linux 也解決了這個問題,但 Linux 中的中斷有其自身的抽象,值得單獨研究。因此,雖然我們在評測過程中會遇到處理中斷的代碼,但本文不會討論它。

實現(xiàn)通用 GPIO 驅(qū)動程序要求

從內(nèi)核和用戶空間訪問外設(shè)

一切都很簡單。驅(qū)動程序是一個常規(guī)的 Linux 內(nèi)核模塊,因此它在內(nèi)核空間中執(zhí)行,并且可以訪問 CPU 的整個地址空間。

對于用戶級應(yīng)用程序,sysfs中提供了特殊文件。

自定義應(yīng)用程序的 API

這是 Linux 系統(tǒng)的標(biāo)準(zhǔn)機制 - 通過文件/sys/class/gpio。

當(dāng)我們找到系統(tǒng)中所需的輸出編號(它通常與物理輸出的編號不一致)時,我們將該編號寫入文件/sys/class/gpio/export

num=416
echo $num > /sys/class/gpio/export

此后,文件系統(tǒng)中會出現(xiàn)一個目錄/sys/class/gpio/gpio$num。該目錄下有文件directionvalue。

控制輸出方向:

echo out > /sys/class/gpio/gpio$num/direction
echo in > /sys/class/gpio/gpio$num/direction

控制輸出狀態(tài):

echo 1 > /sys/class/gpio/gpio$num/value
echo 0 > /sys/class/gpio/gpio$num/value

讀取輸入狀態(tài):

cat /sys/class/gpio/gpio$num/value

如何實現(xiàn)這一點,我們稍后會考慮。

用戶應(yīng)用程序的訪問分離

如果多個應(yīng)用程序訪問同一個“句柄”,系統(tǒng)將自行解決沖突。就像多個應(yīng)用程序同時訪問同一個文件時一樣。這些沖突不需要在驅(qū)動程序?qū)用孢M行處理。

內(nèi)核 API 和內(nèi)核訪問共享

本節(jié)主要介紹 Linux 內(nèi)核中與平臺無關(guān)的代碼。代碼以存儲庫https://github.com/torvalds/linux的主分支為例,即本文發(fā)布時對應(yīng)的內(nèi)核版本 6.x。具體的驅(qū)動程序?qū)崿F(xiàn)將以內(nèi)核 4.x 和 5.x 為例進行進一步討論。內(nèi)核版本之間存在差異,但總體概念沒有變化。

Linux 內(nèi)核有一個獨立于平臺的抽象結(jié)構(gòu)體 gpio_chip,它是一個包含一組特定 GPIO 引腳描述的結(jié)構(gòu)體。沒有單獨的結(jié)構(gòu)體來描述單個引腳并包含管理該引腳的函數(shù)。

題外話,這與硬件層面的關(guān)系如何。

  • 在硬件層面,芯片(SoC - 片上系統(tǒng))通常具有多個 GPIO 塊,每個塊包含數(shù)十個引腳。通常使用諸如 GPIOA、GPIOB 等名稱。因此,引腳的指定格式為 GPIOA_31..GPIOA_0 等。
  • 根據(jù)芯片和驅(qū)動程序的具體實現(xiàn),可以實現(xiàn)不同的方法??梢詾槊總€塊啟動一個單獨的 gpio_chip 結(jié)構(gòu)。但也可以實現(xiàn)另一種方法。
  • 例如,可以按電源域劃分 GPIO 塊。通常,GPIOA..GPIOC 位于一個域中,而 GPIOD..GPIOF 位于另一個域中。
  • 在這種情況下,供應(yīng)商在提供的 SDK 中為一個域的塊形成一個 gpio_chip,為另一個域的塊形成第二個 gpio_chip。

總的來說,從技術(shù)角度來看,gpio_chip 的分區(qū)并不重要?;旧?,這里的一切都取決于特定驅(qū)動程序作者的審美。

無論如何,在 gpio_chip 結(jié)構(gòu)中,除其他內(nèi)容外,還有指向特定 GPIO 塊的驅(qū)動程序中實現(xiàn)的平臺相關(guān)函數(shù)的指針:

	int			(*request)(struct gpio_chip *chip,
						unsigned offset);
	void			(*free)(struct gpio_chip *chip,
						unsigned offset);
	int			(*get_direction)(struct gpio_chip *chip,
						unsigned offset);
	int			(*direction_input)(struct gpio_chip *chip,
						unsigned offset);
	int			(*direction_output)(struct gpio_chip *chip,
						unsigned offset, int value);
	…
	void			(*set)(struct gpio_chip *chip,
						unsigned offset, int value);

除了 gpio_chip 結(jié)構(gòu)體之外,內(nèi)核還提供了與平臺無關(guān)的 gpio 函數(shù)。它們分別位于 drivers/gpio/gliolib.c 和 drivers/gpio/gpiolib_sysfs.c 文件中。我們來看一下這兩個文件。

第一個包含內(nèi)核函數(shù)。有很多函數(shù),我們重點介紹兩個:

int gpiod_request(struct gpio_desc *desc, const char *label);
void gpiod_set_value(struct gpio_desc *desc, int value);

這是允許我們占用所需 GPIO 并設(shè)置其值(如果將其配置為輸出)的最小集合。其余函數(shù)基于相同的原則構(gòu)建,對它們的額外考慮不會給我們帶來任何根本性的新東西。

訪問共享機制是在 gpiod_request() 函數(shù)內(nèi)部實現(xiàn)的。當(dāng)然,它不是直接在函數(shù)內(nèi)部實現(xiàn)的,而是按照慣例,通過函數(shù)“下方”的幾個層級來實現(xiàn)的:

int gpiod_request(struct gpio_desc *desc, const char *label)
{
	int ret = -EPROBE_DEFER;

	VALIDATE_DESC(desc);

	if (try_module_get(desc->gdev->owner)) {
		ret = gpiod_request_commit(desc, label);
		if (ret)
			module_put(desc->gdev->owner);
		else
			gpio_device_get(desc->gdev);
	}

	if (ret)
		gpiod_dbg(desc, "%s: status %d\n", __func__, ret);

	return ret;
}
static int gpiod_request_commit(struct gpio_desc *desc, const char *label)
{
	struct gpio_chip *gc = desc->gdev->chip;
	unsigned long flags;
	unsigned int offset;
	int ret;

	if (label) {
		label = kstrdup_const(label, GFP_KERNEL);
		if (!label)
			return -ENOMEM;
	}

	spin_lock_irqsave(&gpio_lock, flags);

	/* NOTE:  gpio_request() can be called in early boot,
	 * before IRQs are enabled, for non-sleeping (SOC) GPIOs.
	 */

	if (test_and_set_bit(FLAG_REQUESTED, &desc->flags) == 0) {
		desc_set_label(desc, label ? : "?");
	} else {
		ret = -EBUSY;
		goto out_free_unlock;
	}

	if (gc->request) {
		/* gc->request may sleep */
		spin_unlock_irqrestore(&gpio_lock, flags);
		offset = gpio_chip_hwgpio(desc);
		if (gpiochip_line_is_valid(gc, offset))
			ret = gc->request(gc, offset);
		else
			ret = -EINVAL;
		spin_lock_irqsave(&gpio_lock, flags);

		if (ret) {
			desc_set_label(desc, NULL);
			clear_bit(FLAG_REQUESTED, &desc->flags);
			goto out_free_unlock;
		}
	}
	if (gc->get_direction) {
		/* gc->get_direction may sleep */
		spin_unlock_irqrestore(&gpio_lock, flags);
		gpiod_get_direction(desc);
		spin_lock_irqsave(&gpio_lock, flags);
	}
	spin_unlock_irqrestore(&gpio_lock, flags);
	return 0;

out_free_unlock:
	spin_unlock_irqrestore(&gpio_lock, flags);
	kfree_const(label);
	return ret;
}

要點如下:

1)將其包裝在自旋鎖中并查看此輸出是否已處于請求的狀態(tài):

spin_lock_irqsave(&gpio_lock, flags);

	/* NOTE:  gpio_request() can be called in early boot,
	 * before IRQs are enabled, for non-sleeping (SOC) GPIOs.
	 */

	if (test_and_set_bit(FLAG_REQUESTED, &desc->flags) == 0) {
		desc_set_label(desc, label ? : "?");
	} else {
		ret = -EBUSY;
		goto out_free_unlock;
	}

2)如果輸出不忙,那么我們檢查它是否有自己的平臺相關(guān)函數(shù) request(),并調(diào)用它:

	if (gc->request) {
		/* gc->request may sleep */
		spin_unlock_irqrestore(&gpio_lock, flags);
		offset = gpio_chip_hwgpio(desc);
		if (gpiochip_line_is_valid(gc, offset))
			ret = gc->request(gc, offset);
		else
			ret = -EINVAL;
		spin_lock_irqsave(&gpio_lock, flags);

顯然,我們在自旋鎖解鎖的情況下調(diào)用此函數(shù)。雖然很難保證 100%,但看起來我們已經(jīng)解鎖了自旋鎖,因為這里我們已經(jīng)排除了對特定 GPIO 的同時訪問,并且建議盡可能縮短自旋鎖的鎖定時間,以最大程度地減少其他進程/線程的主動等待。

現(xiàn)在讓我們看看設(shè)置給定值是什么樣子的:

void gpiod_set_value(struct gpio_desc *desc, int value)
{
	VALIDATE_DESC_VOID(desc);
	/* Should be using gpiod_set_value_cansleep() */
	WARN_ON(desc->gdev->chip->can_sleep);
	gpiod_set_value_nocheck(desc, value);
}
EXPORT_SYMBOL_GPL(gpiod_set_value);
static void gpiod_set_value_nocheck(struct gpio_desc *desc, int value)
{
	if (test_bit(FLAG_ACTIVE_LOW, &desc->flags))
		value = !value;
	if (test_bit(FLAG_OPEN_DRAIN, &desc->flags))
		gpio_set_open_drain_value_commit(desc, value);
	else (test_bit(FLAG_OPEN_SOURCE, &desc->flags))
		gpio_set_open_source_value_commit(desc, value);
}
static void gpiod_set_raw_value_commit(struct gpio_desc *desc, bool value)
{
	struct gpio_chip *gc;

	gc = desc->gdev->chip;
	trace_gpio_value(desc_to_gpio(desc), 0, value);
	gc->set(gc, gpio_chip_hwgpio(desc), value);
}

可以看出,最終調(diào)用的是平臺特定函數(shù)set()。set 函數(shù)不再使用任何訪問共享機制。如果某個內(nèi)核代碼想要使用某個 GPIO 引腳,但在調(diào)用 時出錯gpiod_request(),則不應(yīng)再嘗試使用該 GPIO。

現(xiàn)在讓我們看看通過 sysfs 從用戶空間調(diào)用的實現(xiàn)。處理寫入文件的函數(shù)如下所示/sys/class/gpio/export

static ssize_t export_store(const struct class *class,
				const struct class_attribute *attr,
				const char *buf, size_t len)
{
	struct gpio_desc *desc;
	struct gpio_chip *gc;
	int status, offset;
	long gpio;

	status = kstrtol(buf, 0, &gpio);
	if (status < 0)
		goto done;

	desc = gpio_to_desc(gpio);
	/* reject invalid GPIOs */
	if (!desc) {
		pr_warn("%s: invalid GPIO %ld\n", __func__, gpio);
		return -EINVAL;
	}
	gc = desc->gdev->chip;
	offset = gpio_chip_hwgpio(desc);
	if (!gpiochip_line_is_valid(gc, offset)) {
		pr_warn("%s: GPIO %ld masked\n", __func__, gpio);
		return -EINVAL;
	}

	/* No extra locking here; FLAG_SYSFS just signifies that the
	 * request and export were done by on behalf of userspace, so
	 * they may be undone on its behalf too.
	 */

	status = gpiod_request_user(desc, "sysfs");
	if (status)
		goto done;

	status = gpiod_set_transitory(desc, false);
	if (status) {
		gpiod_free(desc);
		goto done;
	}

	status = gpiod_export(desc, true);
	if (status < 0)
		gpiod_free(desc);
	else
		set_bit(FLAG_SYSFS, &desc->flags);

done:
	if (status)
		pr_debug("%s: status %d\n", __func__, status);
	return status ? : len;
}
static CLASS_ATTR_WO(export);

這里最重要的是挑戰(zhàn)gpiod_request_user()。如果你搜索 master 分支,你會發(fā)現(xiàn)

static inline int gpiod_request_user(struct gpio_desc *desc, const char *label)
{
	int ret;

	ret = gpiod_request(desc, label);
	if (ret == -EPROBE_DEFER)
		ret = -ENODEV;

	return ret;
}

在內(nèi)核 4.x,5.x 中沒有gpiod_request_user(),直接使用常規(guī) . gpiod_request()。

static ssize_t value_store(struct device *dev,
		struct device_attribute *attr, const char *buf, size_t size)
{
	struct gpiod_data *data = dev_get_drvdata(dev);
	struct gpio_desc *desc = data->desc;
	ssize_t status;
	long value;

	status = kstrtol(buf, 0, &value);

	mutex_lock(&data->mutex);

	if (!test_bit(FLAG_IS_OUT, &desc->flags)) {
		status = -EPERM;
	} else if (status == 0) {
		gpiod_set_value_cansleep(desc, value);
		status = size;
	}

	mutex_unlock(&data->mutex);

	return status;
}
static DEVICE_ATTR_PREALLOC(value, S_IWUSR | S_IRUGO, value_show, value_store);void gpiod_set_value_cansleep(struct gpio_desc *desc, int value)
{
	might_sleep();
	VALIDATE_DESC_VOID(desc);
	gpiod_set_value_nocheck(desc, value);
}
EXPORT_SYMBOL_GPL(gpiod_set_value_cansleep);static void gpiod_set_value_nocheck(struct gpio_desc *desc, int value)
{
	if (test_bit(FLAG_ACTIVE_LOW, &desc->flags))
		value = !value;
	if (test_bit(FLAG_OPEN_DRAIN, &desc->flags))
		gpio_set_open_drain_value_commit(desc, value);
	else if (test_bit(FLAG_OPEN_SOURCE, &desc->flags))
		gpio_set_open_source_value_commit(desc, value);
	else
		gpiod_set_raw_value_commit(desc, value);
}

我們知道,這將導(dǎo)致已經(jīng)熟悉的依賴于平臺的 set() 函數(shù)。

具體驅(qū)動程序的實現(xiàn)

讓我們看看以上所有內(nèi)容在真實硬件上是如何實現(xiàn)的。

研究設(shè)置

我們使用了 BeagleBone Black(SoC TI AM335)和 Orange Pi Zero(SoC Allwinner H2)開發(fā)板。Buildroot 2021.02 被用作內(nèi)核的“包裝器”。這個設(shè)置沒有什么特別之處,只是手頭上的東西,以及作者或多或少習(xí)慣使用的東西。我們使用了兩塊開發(fā)板來測試 TI 和 Allwinner 兩家供應(yīng)商的 GPIO 驅(qū)動程序的實現(xiàn)。我們本來想考慮第三塊開發(fā)板,但手頭上沒有其他可用的。

BeagleBone 的 Buildroot 使用內(nèi)核版本 4.19.79。OrangePi Zero 的 Buildroot 使用內(nèi)核版本 5.10.10。如上所述,現(xiàn)代內(nèi)核版本與 4.19 和 5.10 在細(xì)節(jié)上有所不同,但總體方法沒有改變。

BeagleBone 黑色 (TI AM335)

我們啟動組裝好的 Buildroot 鏡像并保存 UART 輸出。我們以以下行作為搜索的起點:

  [    0.268291] OMAP GPIO hardware version 0.1

我們搜索產(chǎn)生此消息的代碼(該路徑是相對于內(nèi)核源的根目錄給出的):

驅(qū)動程序/gpio/gpio-omap.c

static void omap_gpio_show_rev(struct gpio_bank *bank)
{
	static bool called;
	u32 rev;

	if (called || bank->regs->revision == USHRT_MAX)
		return;

	rev = readw_relaxed(bank->base + bank->regs->revision);
	pr_info("OMAP GPIO hardware version %d.%d\n",
		(rev >> 4) & 0x0f, rev & 0x0f);

	called = true;
}

太好了,我們知道了源代碼在哪里。讓我們開始研究它。首先,讓我們了解一下這個驅(qū)動程序的 DTS 中應(yīng)該包含哪些內(nèi)容。記住,內(nèi)核在獲悉系統(tǒng)存在相應(yīng)的設(shè)備后,會運行驅(qū)動程序的probe()函數(shù)。

GPIO控制器是芯片內(nèi)部的一個模塊,通過 AXI 類型的總線連接。那里沒有即插即用機制。這意味著內(nèi)核根據(jù)硬件的靜態(tài)描述來確定這一點。設(shè)備樹(俗稱“DTS”或“DTB”——盡管從技術(shù)上講這并不完全正確。

DTS 和 DTB 是設(shè)備樹的兩種呈現(xiàn)形式。

DTS 指的是“設(shè)備樹源代碼”,DTB 指的是“設(shè)備樹二進制文件”)。

驅(qū)動程序包含必須在 DTS 中描述的兼容字段:

static const struct of_device_id omap_gpio_match[] = {
	{
		.compatible = "ti,omap4-gpio",
		.data = &omap4_pdata,
	},
	{
		.compatible = "ti,omap3-gpio",
		.data = &omap3_pdata,
	},
	{
		.compatible = "ti,omap2-gpio",
		.data = &omap2_pdata,
	},
	{ },
};
MODULE_DEVICE_TABLE(of, omap_gpio_match);

驅(qū)動程序本身注冊為平臺驅(qū)動程序:

static struct platform_driver omap_gpio_driver = {
	.probe		= omap_gpio_probe,
	.remove		= omap_gpio_remove,
	.driver		= {
		.name	= "omap_gpio",
		.pm	= &gpio_pm_ops,
		.of_match_table = of_match_ptr(omap_gpio_match),
	},
};

/*
 * gpio driver register needs to be done before
 * machine_init functions access gpio APIs.
 * Hence omap_gpio_drv_reg() is a postcore_initcall.
 */
static int __init omap_gpio_drv_reg(void)
{
	return platform_driver_register(&omap_gpio_driver);
}
postcore_initcall(omap_gpio_drv_reg);

這里我們重新回顧一下 注釋和說明——GPIO 驅(qū)動程序必須在使用這些 GPIO 的驅(qū)動程序初始化之前注冊。為了實現(xiàn)這一點,驅(qū)動程序在 postcore_initcall 階段初始化,這個階段比 device_initcall 階段要早得多。

同時,從兼容列表中我們了解到該驅(qū)動程序支持三個版本的GPIO模塊。要了解使用的是哪一個,我們?nèi)匀恍枰D(zhuǎn)到DTS:

架構(gòu)/arm/boot/dts/am33xx.dts

		gpio0: gpio@44e07000 {
			compatible = "ti,omap4-gpio";
			ti,hwmods = "gpio1";
			gpio-controller;
			#gpio-cells = <2>;
			interrupt-controller;
			#interrupt-cells = <2>;
			reg = <0x44e07000 0x1000>;
			interrupts = <96>;
		};

還有類似的塊,名稱分別為 gpio1、gpio2、gpio3。

結(jié)論:

  • gpio 塊至少有三個受支持的版本(omap2-gpio、omap3-gpio、omap4-gpio)。
  • 在DTS中我們可以看到這些塊在芯片內(nèi)部的基地址。
  • 這些 GPIO 模塊可以產(chǎn)生中斷。通常,這些信息可以從數(shù)據(jù)手冊中獲取,但是:
  • 原則上,Linux 程序員打開數(shù)據(jù)表的頻率比微控制器程序員要低得多。
  • 對于許多處理器,您可能仍然可以在公共領(lǐng)域找到數(shù)據(jù)表。因此,一般來說,這些知識并非毫無用處。不過,在這個特定情況下,它確實毫無用處。

以下是 omap_gpio_probe() 中發(fā)生的情況:

驅(qū)動程序/gpio/gpio-omap.c

static int omap_gpio_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct device_node *node = dev->of_node;
	const struct of_device_id *match;
	const struct omap_gpio_platform_data *pdata;
	struct resource *res;
	struct gpio_bank *bank;
	struct irq_chip *irqc;
...
	if (bank->is_mpuio)
		omap_mpuio_init(bank);

	omap_gpio_mod_init(bank);

	ret = omap_gpio_chip_init(bank, irqc);
	if (ret) {
		pm_runtime_put_sync(dev);
		pm_runtime_disable(dev);
		if (bank->dbck_flag)
			clk_unprepare(bank->dbck);
		return ret;
	}

	omap_gpio_show_rev(bank);

讓我們仔細(xì)看看:

	if (bank->is_mpuio)
		omap_mpuio_init(bank);

從代碼上看不清楚它是什么。如果你在搜索引擎中輸入“mpuio”,你可以在TI數(shù)據(jù)表中找到它:

互聯(lián)網(wǎng)上的TI處理器數(shù)據(jù)表

也許這足以得出以下暫時的結(jié)論:

  • 這是來自 TI 的一個具體事物。
  • 這是 GPIO 端口的一些特殊操作模式,為此有一個驅(qū)動程序。
  • 并非所有 TI GPIO 端口都支持此模式。
  • 在這種情況下,這種模式的存在與否并不會對文章主題的考慮產(chǎn)生很大的影響。

接下來我們有這個函數(shù):

static void omap_gpio_mod_init(struct gpio_bank *bank)
{
	void __iomem *base = bank->base;
	u32 l = 0xffffffff;

	if (bank->width == 16)
		l = 0xffff;

	if (bank->is_mpuio) {
		writel_relaxed(l, bank->base + bank->regs->irqenable);
		return;
	}

	omap_gpio_rmw(base, bank->regs->irqenable, l,
		      bank->regs->irqenable_inv);
	omap_gpio_rmw(base, bank->regs->irqstatus, l,
		      !bank->regs->irqenable_inv);
	if (bank->regs->debounce_en)
		writel_relaxed(0, base + bank->regs->debounce_en);

	/* Save OE default value (0xffffffff) in the context */
	bank->context.oe = readl_relaxed(bank->base + bank->regs->direction);
	 /* Initialize interface clk ungated, module enabled */
	if (bank->regs->ctrl)
		writel_relaxed(0, base + bank->regs->ctrl);
}

這些是對 GPIO 塊寄存器的可靠寫入。這里沒有特定于 Linux 內(nèi)核的操作??偟膩碚f,對于我們討論的主題來說,這也不是一個非常重要的功能。

但下一個函數(shù)正好符合目標(biāo):

static int omap_gpio_chip_init(struct gpio_bank *bank, struct irq_chip *irqc)
{
	struct gpio_irq_chip *irq;
	static int gpio;
	const char *label;
	int irq_base = 0;
	int ret;

	/*
	 * REVISIT eventually switch from OMAP-specific gpio structs
	 * over to the generic ones
	 */
	bank->chip.request = omap_gpio_request;
	bank->chip.free = omap_gpio_free;
	bank->chip.get_direction = omap_gpio_get_direction;
	bank->chip.direction_input = omap_gpio_input;
	bank->chip.get = omap_gpio_get;
	bank->chip.direction_output = omap_gpio_output;
	bank->chip.set_config = omap_gpio_set_config;
	bank->chip.set = omap_gpio_set;
	if (bank->is_mpuio) {
		bank->chip.label = "mpuio";
		if (bank->regs->wkup_en)
			bank->chip.parent = &omap_mpuio_device.dev;
		bank->chip.base = OMAP_MPUIO(0);
	} else {
		label = devm_kasprintf(bank->chip.parent, GFP_KERNEL, "gpio-%d-%d",
				       gpio, gpio + bank->width - 1);
		if (!label)
			return -ENOMEM;
		bank->chip.label = label;
		bank->chip.base = gpio;
	}
	bank->chip.ngpio = bank->width;

#ifdef CONFIG_ARCH_OMAP1
	/*
	 * REVISIT: Once we have OMAP1 supporting SPARSE_IRQ, we can drop
	 * irq_alloc_descs() since a base IRQ offset will no longer be needed.
	 */
	irq_base = devm_irq_alloc_descs(bank->chip.parent,
					-1, 0, bank->width, 0);
	if (irq_base < 0) {
		dev_err(bank->chip.parent, "Couldn't allocate IRQ numbers\n");
		return -ENODEV;
	}
#endif

	/* MPUIO is a bit different, reading IRQ status clears it */
	if (bank->is_mpuio) {
		irqc->irq_ack = dummy_irq_chip.irq_ack;
		if (!bank->regs->wkup_en)
			irqc->irq_set_wake = NULL;
	}

	irq = &bank->chip.irq;
	irq->chip = irqc;
	irq->handler = handle_bad_irq;
	irq->default_type = IRQ_TYPE_NONE;
	irq->num_parents = 1;
	irq->parents = &bank->irq;
	irq->first = irq_base;

	ret = gpiochip_add_data(&bank->chip, bank);
	if (ret) {
		dev_err(bank->chip.parent,
			"Could not register gpio chip %d\n", ret);
		return ret;
	}

	ret = devm_request_irq(bank->chip.parent, bank->irq,
			       omap_gpio_irq_handler,
			       0, dev_name(bank->chip.parent), bank);
	if (ret)
		gpiochip_remove(&bank->chip);

	if (!bank->is_mpuio)
		gpio += bank->width;

原則上,一條注釋就足以理解——這里我們從“OMAP 專用”結(jié)構(gòu)體(struct gpio_bank)過渡到“系統(tǒng)級”結(jié)構(gòu)體(struct gpiochip)。實際上,我們將系統(tǒng)級結(jié)構(gòu)體作為 OMAP 專用結(jié)構(gòu)體中的一個實例。這里有一些與中斷相關(guān)的操作,但我們暫時不討論這個話題。

對于我們來說現(xiàn)在最重要的部分是:

	bank->chip.request = omap_gpio_request;
	bank->chip.free = omap_gpio_free;
	bank->chip.get_direction = omap_gpio_get_direction;
	bank->chip.direction_input = omap_gpio_input;
	bank->chip.get = omap_gpio_get;
	bank->chip.direction_output = omap_gpio_output;
	bank->chip.set_config = omap_gpio_set_config;
	bank->chip.set = omap_gpio_set;

這里,我們實現(xiàn)了從通用 API 到其平臺相關(guān)代碼的轉(zhuǎn)換。當(dāng)某些代碼調(diào)用平臺無關(guān)函數(shù)(例如 gpiod_request())時,系統(tǒng)會調(diào)用 gpio_chip.request()。如果這種情況發(fā)生在 AM335x 處理器上,我們就會調(diào)用 omap_gpio_request()。

好了,填充 gpio_chip 結(jié)構(gòu)體之后,最重要的事情就是在系統(tǒng)中注冊這個結(jié)構(gòu)體。從這一刻起,系統(tǒng)就知道了這些 GPIO 線的存在。

讓我們來看看兩個函數(shù)的實現(xiàn)——GPIO 請求和 GPIO 值設(shè)置:

static int omap_gpio_request(struct gpio_chip *chip, unsigned offset)
{
	struct gpio_bank *bank = gpiochip_get_data(chip);
	unsigned long flags;

	/*
	 * If this is the first gpio_request for the bank,
	 * enable the bank module.
	 */
	if (!BANK_USED(bank))
		pm_runtime_get_sync(chip->parent);

	raw_spin_lock_irqsave(&bank->lock, flags);
	omap_enable_gpio_module(bank, offset);
	bank->mod_usage |= BIT(offset);
	raw_spin_unlock_irqrestore(&bank->lock, flags);

	return 0;
}

這里最重要的是自旋鎖 (spinlock) 封裝的內(nèi)容:啟用 GPIO 模塊并寫入內(nèi)部 bank.mod_usage 結(jié)構(gòu)體,這實際上復(fù)制了關(guān)于引腳“忙”狀態(tài)的條目。以下是其中最重要的部分:

  • 事實上,使用自旋鎖可以消除碰撞。
  • 雖然這里存在一個函數(shù) omap_enable_gpio_module(),但通常不需要對硬件進行任何特殊操作。GPIO 請求只是在系統(tǒng)中記錄“此 GPIO 已被占用”的一種方式。嚴(yán)格來說,平臺相關(guān)的 request() 函數(shù)根本沒有必要存在。

設(shè)置GPIO輸出值的函數(shù):

static void omap_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
{
	struct gpio_bank *bank;
	unsigned long flags;

	bank = gpiochip_get_data(chip);
	raw_spin_lock_irqsave(&bank->lock, flags);
	bank->set_dataout(bank, offset, value);
	raw_spin_unlock_irqrestore(&bank->lock, flags);
}

set_dataout() 函數(shù)是在驅(qū)動程序初始化期間在probe()函數(shù)中指定的:

	if (bank->regs->set_dataout && bank->regs->clr_dataout) {
		bank->set_dataout = omap_set_gpio_dataout_reg;
		bank->set_dataout_multiple = omap_set_gpio_dataout_reg_multiple;
	} else {
		bank->set_dataout = omap_set_gpio_dataout_mask;
		bank->set_dataout_multiple =
				omap_set_gpio_dataout_mask_multiple;
	}

顯然,這為驅(qū)動程序開發(fā)人員提供了額外的自由度。無論如何,如果我們查看 omap_set_gpio_dataout_reg(),我們會發(fā)現(xiàn)它已經(jīng)直接與 GPIO 塊寄存器進行了交互:

/* set data out value using dedicate set/clear register */
static void omap_set_gpio_dataout_reg(struct gpio_bank *bank, unsigned offset,
				      int enable)
{
	void __iomem *reg = bank->base;
	u32 l = BIT(offset);

	if (enable) {
		reg += bank->regs->set_dataout;
		bank->context.dataout |= l;
	} else {
		reg += bank->regs->clr_dataout;
		bank->context.dataout &= ~l;
	}

	writel_relaxed(l, reg);
}

本質(zhì)上,我們在這里看到的是一個簡單的注冊表條目,與我們在文章開頭討論的相同。

現(xiàn)在,讓我們嘗試驗證之前通過閱讀源代碼獲得的理論計算。確保在通過內(nèi)核空間和用戶空間訪問時,我們確實會經(jīng)歷這一系列調(diào)用。讓我們用最原始的方式做到這一點——在 

printk(“%s: entered\n”, __func__);

我們討論的函數(shù)中添加一行代碼。然后,我們將刪除日志。

首先,讓我們看一下UART啟動日志,我們會立即看到我們干預(yù)的痕跡:

[    0.571306] pinctrl-single 44e10800.pinmux: 142 pins, size 568
[    0.576824] gpiod_request_commit: entered
[    0.576852] omap_gpio_request: entered
[    0.576992] gpiod_direction_output_raw_commit: entered
[    0.577051] omap_set_gpio_direction: entered
[    0.580063] Serial: 8250/16550 driver, 6 ports, IRQ sharing enabled
...
[    1.535627] sdhci: Secure Digital Host Controller Interface driver
[    1.541871] sdhci: Copyright(c) Pierre Ossman
[    1.547696] gpiod_request_commit: entered
[    1.551745] omap_gpio_request: entered
[    1.555787] omap_set_gpio_direction: entered
[    1.560134] omap_gpio 44e07000.gpio: Could not set line 6 debounce to 200000)
[    1.568968] omap_hsmmc 48060000.mmc: Got CD GPIO

很明顯,驅(qū)動程序在加載過程中使用了兩個 GPIO。而且,它們的執(zhí)行路徑與之前提到的完全一致——首先調(diào)用與平臺無關(guān)的 gpiod_request() 函數(shù),然后調(diào)用與平臺相關(guān)的 omap_gpio_request() 函數(shù)。

現(xiàn)在讓我們看看如果我們從內(nèi)核空間控制 GPIO 會發(fā)生什么:

# cd /sys/class/gpio
# ls
export      gpiochip0   gpiochip32  gpiochip64  gpiochip96  unexport
# echo 15 > export
[  803.092438] gpiod_request_commit: entered
[  803.097022] omap_gpio_request: entered
# ls
export      gpiochip0   gpiochip64  unexport
gpio15      gpiochip32  gpiochip96
# cd gpio15
# echo out > direction
[  815.830522] gpiod_direction_output_raw_commit: entered
[  815.836210] omap_set_gpio_direction: entered
# echo 1 > value
[  820.621108] value_store: entered
[  820.624881] gpiod_set_value_cansleep: entered
[  820.629404] gpiod_set_raw_value_commit: entered
[  820.634088] omap_gpio_set: entered

顯然,一切確實都朝著我們所討論的相同功能發(fā)展。

OrangePi Zero(全志 H2)

這里的啟動日志并沒有用GPIO相關(guān)的字眼來破壞我們的印象。這些行看起來像是我們要找的內(nèi)容:

[    0.089512] sun8i-h3-pinctrl 1c20800.pinctrl: initialized sunXi PIO driver
[    0.091141] sun8i-h3-r-pinctrl 1f02c00.pinctrl: initialized sunXi PIO driver

“sun8i-h3”這個名字很容易讓人混淆,因為我們主板上的處理器上有一個大大的“H2”字樣。搜索可用的DTS,找不到任何類似“sun8i-h2”的版本。不過,匯編代碼可以運行,這意味著H2和H3之間的區(qū)別并不大。

這意味著一切都有效,我們可以進一步研究代碼。

讓我們看一下顯示這些行的代碼。它位于此處:

驅(qū)動程序/pinctrl/sunxi/pinctrl-sunxi.c

int sunxi_pinctrl_init_with_variant(struct platform_device *pdev,
				    const struct sunxi_pinctrl_desc *desc,
				    unsigned long variant)
{
...
	pctl->chip->owner = THIS_MODULE;
	pctl->chip->request = gpiochip_generic_request;
	pctl->chip->free = gpiochip_generic_free;
	pctl->chip->set_config = gpiochip_generic_config;
	pctl->chip->direction_input = sunxi_pinctrl_gpio_direction_input;
	pctl->chip->direction_output = sunxi_pinctrl_gpio_direction_output;
	pctl->chip->get = sunxi_pinctrl_gpio_get;
	pctl->chip->set = sunxi_pinctrl_gpio_set;
	pctl->chip->of_xlate = sunxi_pinctrl_gpio_of_xlate;
	pctl->chip->to_irq = sunxi_pinctrl_gpio_to_irq;
	pctl->chip->of_gpio_n_cells = 3;
	pctl->chip->can_sleep = false;
	pctl->chip->ngpio = round_up(last_pin, PINS_PER_BANK) -
			    pctl->desc->pin_base;
	pctl->chip->label = dev_name(&pdev->dev);
	pctl->chip->parent = &pdev->dev;
	pctl->chip->base = pctl->desc->pin_base;
...

他們這樣稱呼它:

驅(qū)動程序/pinctrl/sunxi/pinctrl-sun8i-v3s.c

static int sun8i_v3s_pinctrl_probe(struct platform_device *pdev)
{
	unsigned long variant = (unsigned long)of_device_get_match_data(&pdev->dev);

	return sunxi_pinctrl_init_with_variant(pdev, &sun8i_v3s_pinctrl_data,
					       variant);
}

static const struct of_device_id sun8i_v3s_pinctrl_match[] = {
	{
		.compatible = "allwinner,sun8i-v3-pinctrl",
		.data = (void *)PINCTRL_SUN8I_V3
	},
	{
		.compatible = "allwinner,sun8i-v3s-pinctrl",
		.data = (void *)PINCTRL_SUN8I_V3S
	},
	{ },
};

static struct platform_driver sun8i_v3s_pinctrl_driver = {
	.probe	= sun8i_v3s_pinctrl_probe,
	.driver	= {
		.name		= "sun8i-v3s-pinctrl",
		.of_match_table	= sun8i_v3s_pinctrl_match,
	},
};
builtin_platform_driver(sun8i_v3s_pinctrl_driver);

與 TI 類似,這是一個平臺驅(qū)動程序。實際上,很難用其他方式實現(xiàn)。 

但明顯的區(qū)別是,驅(qū)動程序不在 GPIO 目錄中,而是在 pinctrl 目錄中。

pinctrl 驅(qū)動程序還控制每個特定引腳的分配。通常,現(xiàn)代 SoC 上的 GPIO 引腳與其他 I2C/SPI/UART 級接口復(fù)用。pinctrl 驅(qū)動程序包含控制這些復(fù)用器的功能。例如,TI AM335x 上也存在 pinctrl 驅(qū)動程序,但該實體與 GPIO 驅(qū)動程序是分開的。在這里,他們決定這樣做。

讓我們回到 GPIO 上。無論如何,此驅(qū)動程序中的關(guān)鍵操作已經(jīng)完成——創(chuàng)建、填充并在系統(tǒng)中注冊 gpio_chip 結(jié)構(gòu)的實例。讓我們仔細(xì)看看這一點。

首先,你可以看到函數(shù) request() 用于實現(xiàn)gpiochip_generic_request()

驅(qū)動程序/gpio/gpiolib.c

int gpiochip_generic_request(struct gpio_chip *gc, unsigned offset)
{
#ifdef CONFIG_PINCTRL
	if (list_empty(&gc->gpiodev->pin_ranges))
		return 0;
#endif

	return pinctrl_gpio_request(gc->gpiodev->base + offset);
}
EXPORT_SYMBOL_GPL(gpiochip_generic_request);

她在引擎蓋下喊道:

驅(qū)動程序/pinctrl/core.c

/**
 * pinctrl_gpio_request() - request a single pin to be used as GPIO
 * @gpio: the GPIO pin number from the GPIO subsystem number space
 *
 * This function should *ONLY* be used from gpiolib-based GPIO drivers,
 * as part of their gpio_request() semantics, platforms and individual drivers
 * shall *NOT* request GPIO pins to be muxed in.
 */
int pinctrl_gpio_request(unsigned gpio)
{
	struct pinctrl_dev *pctldev;
	struct pinctrl_gpio_range *range;
	int ret;
	int pin;

	ret = pinctrl_get_device_gpio_range(gpio, &pctldev, &range);
	if (ret) {
		if (pinctrl_ready_for_gpio_range(gpio))
			ret = 0;
		return ret;
	}

	mutex_lock(&pctldev->mutex);

	/* Convert to the pin controllers number space */
	pin = gpio_to_pin(range, gpio);

	ret = pinmux_request_gpio(pctldev, range, pin, gpio);

	mutex_unlock(&pctldev->mutex);

	return ret;
}
EXPORT_SYMBOL_GPL(pinctrl_gpio_request);

無需詳細(xì)了解 pinctrl 和 pinmux 的實現(xiàn),我們嘗試說明:此功能不僅保留了 GPIO,而且將其固定為 GPIO,而不是 I2C/SPI/UART/等輸出。

我們來看看設(shè)置GPIO輸出值的函數(shù)是什么樣的:

驅(qū)動程序/sunxi/pinctrl-sunxi.c

static void sunxi_pinctrl_gpio_set(struct gpio_chip *chip,
				unsigned offset, int value)
{
	struct sunxi_pinctrl *pctl = gpiochip_get_data(chip);
	u32 reg = sunxi_data_reg(offset);
	u8 index = sunxi_data_offset(offset);
	unsigned long flags;
	u32 regval;

	raw_spin_lock_irqsave(&pctl->lock, flags);

	regval = readl(pctl->membase + reg);

	if (value)
		regval |= BIT(index);
	else
		regval &= ~(BIT(index));

	writel(regval, pctl->membase + reg);

	raw_spin_unlock_irqrestore(&pctl->lock, flags);

正如預(yù)期的那樣,這里所有操作也都?xì)w結(jié)為寫入外設(shè)寄存器。有趣的是,這里對寄存器的操作被封裝在自旋鎖中。這可能是因為寄存器操作沒有原子操作,操作分為三個階段:讀取寄存器、更改指定位以及將更改后的值寫入寄存器。引入自旋鎖可以消除潛在的沖突。

為了確保我們理解正確,讓我們通過添加調(diào)試輸出來實現(xiàn)類似的路線。

確實,當(dāng)添加此輸出并使用此固件運行時,我們會陷入無限循環(huán)的消息中:

[   20.884895] gpiod_set_value_nocheck: entered
[   20.889190] gpiod_set_raw_value_commit: entered
[   20.893720] sunxi_pinctrl_gpio_set: entered 

但在日志的開頭我們看到:

[    1.623498] gpiod_request: entered
[    1.630993] gpiod_request_commit: entered
[    1.635007] gpiochip_generic_request: entered
[    1.639378] pinctrl_gpio_request: entered
[    1.643401] pin_request: entered

這基本上證實了上面的結(jié)論,但在某種程度上干擾了檢查通過 sysfs 訪問的過程?;旧?,這是一個非常典型的 Linux 調(diào)試故事——你只需要稍微深入挖掘一下,就會發(fā)現(xiàn)一些完全無法理解的行為,雖然這并不那么嚴(yán)重(因為一直以來都是這樣),但忽略它似乎也不妥。我們將把此行為的調(diào)試放在稍后的劇透部分,在這里我們將完成確保 sysfs 正常工作的工作。讓我們刪除 gpiod_set_...() 的額外輸出,重建、重啟,然后查看:

# cd /sys/class/gpio
# ls
export       gpiochip0    gpiochip352  unexport
# echo 15 > export
[ 1035.505141] export_store: entered
[ 1035.508561] gpiod_request: entered
[ 1035.511968] gpiod_request_commit: entered
[ 1035.515979] gpiochip_generic_request: entered
[ 1035.520397] pinctrl_gpio_request: entered
[ 1035.524426] pin_request: entered
# cd gpio15
# echo out > direction
# echo 1 > value
[ 1057.037344] value_store: entered

顯然,這正是我們所期望的。

不定期調(diào)試

誰在拉動 GPIO?調(diào)試過程大大縮短了,搜索和嘗試次數(shù)也顯著增加。最終,結(jié)果如下:

1) 值設(shè)置功能增加調(diào)試輸出:

printk("%s: gpiochip->base=%d, offset=%d\n", __func__, chip->base, offset);

我們收到了以下日志:

[   59.314332] gpiod_set_value_cansleep: entered
[   59.318718] gpiod_set_value_nocheck: entered
[   59.322989] sunxi_pinctrl_gpio_set: entered
[   59.327174] sunxi_pinctrl_gpio_set: gpiochip->base=352, offset=6

2)關(guān)閉日志并查看:

# cat /sys/class/gpio/gpiochip352/label
1f02c00.pinctrl 

3) 我們查看了處理器的數(shù)據(jù)手冊。我們發(fā)現(xiàn) R_PIO 塊位于地址 0x1F0_2C00。實際上,我們當(dāng)然可以通過 DTS 找到這一點。第二個引腳塊稱為 PIO。目前還不清楚它們之間有什么區(qū)別。

4) 好的,讓我們進入開發(fā)板上的DTS。搜索“r_pio”并找到:

架構(gòu)/arm/boot/dts/sun8i-h2-plus-orangepi-zero.dts

	reg_vdd_cpux: vdd-cpux-regulator {
		compatible = "regulator-gpio";
		regulator-name = "vdd-cpux";
		regulator-type = "voltage";
		regulator-boot-on;
		regulator-always-on;
		regulator-min-microvolt = <1100000>;
		regulator-max-microvolt = <1300000>;
		regulator-ramp-delay = <50>; /* 4ms */

		gpios = <&r_pio 0 6 GPIO_ACTIVE_HIGH>; /* PL6 */
		enable-active-high;
		gpios-states = <1>;
		states = <1100000 0>, <1300000 1>;
	};

注釋里寫著PL6??雌饋砗芟裎锢硪_標(biāo)識。

5)打開電路板圖 

我們看到 PL6 引腳確實連接到名為 CPUX-VSET 的鏈:

這個鏈實際上控制著某種電源:

這對我們來說并沒有什么用,但至少讓我們清楚了它到底是什么。看起來 Linux 在運行時正在積極地管理 CPU 功耗。

6)對DTS節(jié)點進行注釋:

 &cpu0 {
	cpu-supply = <&reg_vdd_cpux>;
};

7) 重建鏡像,刷入——確保垃圾信息消失。案件告破。這種引腳抖動現(xiàn)象的原因很有趣,但這超出了本文的討論范圍。

總結(jié)

總而言之,我們可以將所說的一切簡化為一個簡單的結(jié)構(gòu)圖:

千言萬語值得,但是,借助流程圖并研究源代碼,您可以實現(xiàn)比僅借助流程圖更多的東西,所以我們不要急于拋棄前面的所有文字。

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

最新評論

安溪县| 乐山市| 阳新县| 巩留县| 永春县| 通城县| 马关县| 马关县| 泸水县| 河北省| 武定县| 靖安县| 海淀区| 旌德县| 垦利县| 民勤县| 中牟县| 中超| 永昌县| 太仓市| 丹寨县| 泉州市| 渝中区| 富裕县| 新蔡县| 尉氏县| 无为县| 铜山县| 铁岭县| 宜春市| 阿克| 外汇| 滦平县| 梅河口市| 大连市| 平陆县| 古田县| 清涧县| 兴海县| 醴陵市| 喜德县|