هبر ، مرحباً!
تتناول هذه المقالة تطوير وحدة GPIO (الإدخال / الإخراج للأغراض العامة) من نواة Linux. كما في
المقالة السابقة
، نقوم بتطبيق البنية الأساسية لبرنامج تشغيل GPIO مع دعم المقاطعة (IRQ: طلب المقاطعة).

تشبه بيانات الإدخال المقالة السابقة: كتلة GPIO التي تم تطويرها للمعالج الجديد "السلكي" إلى FPGA وتشغيل Linux الإصدار 3.18.19.
من أجل تطوير مشغل GPIO ، نحتاج إلى تنفيذ الخطوات التالية:
- فهم مبدأ التفاعل بين مشغل GPIO وواجهة مساحة المستخدم ؛
- إضافة وحدة kernel إلى التجميع ووصف الأجهزة الموجودة في شجرة الجهاز ؛
- تنفيذ الهيكل العظمي الأساسي للسائق ، وكذلك نقاط دخوله واسترجاعه ؛
- تنفيذ الجزء الوظيفي لبرنامج تشغيل GPIO ؛
- إضافة دعم IRQ لتطبيق برنامج التشغيل.
يمكن العثور على أمثلة على برامج تشغيل GPIO
هنا .
الخطوة الأولى
أولاً ، دعنا نتعرف على مبدأ التفاعل بين برنامج تشغيل GPIO من خلال وحدة تحكم المستخدم.
باستخدام برنامج نصي صغير للباش ، قم بإنشاء عناصر التحكم لكل GPIO في / sysfs. للقيام بذلك ، اكتب البرنامج النصي التالي في سطر الأوامر:
for i in {248..255}; do echo $i > /sys/class/gpio/export; done
بعد ذلك ، دعنا نرى ما توفره الميزات / sysfs لتكوين كل GPIO:
root@zed-slave:/sys/class/gpio# ls -l gpio248/ total 0 -rw-r--r-- 1 root root 4096 Jan 7 20:50 active_low -rw-r--r-- 1 root root 4096 Jan 7 20:50 direction -rw-r--r-- 1 root root 4096 Jan 7 20:50 edge drwxr-xr-x 2 root root 0 Jan 7 20:50 power lrwxrwxrwx 1 root root 0 Jan 7 20:50 subsystem -> ../../../../class/gpio -rw-r--r-- 1 root root 4096 Jan 7 20:10 uevent -rw-r--r-- 1 root root 4096 Jan 7 20:50 value
حاليًا نحن مهتمون بالمجالات التالية:
- الاتجاه - يحدد اتجاه الخط. قد تأخذ القيم "في" أو "خارج" ؛
- القيمة - تسمح لك بتعيين إشارة عالية أو منخفضة على الخط (إذا تم تعيين الاتجاه على "out") ، وإلا (تم ضبط الاتجاه على "in") تسمح لك بقراءة حالة الخط ؛
- حافة - يسمح لك بتكوين الحدث الذي يحدث فيه المقاطعة. يمكن أن تأخذ القيم التالية: "لا شيء" أو "صاعد" أو "هبوط" أو "كليهما".
بعد التعرف السريع على واجهة برنامج التشغيل من خلال sysfs ، يمكنك التفكير في كيفية معالجة برنامج التشغيل لأوامر المستخدم. نواة لينكس لديها بنية gpio_chip تصف وظائف وحدة تحكم gpio. الحقول التالية موجودة فيه:
- direction_input : تعيين الخط للإدخال. تم استدعاء الإدخال التالي: echo "in"> / sys / class / gpio / gpio248 / direction ؛
- direction_output : تعيين الخط للخروج. تم استدعاء الإدخال التالي: echo "out"> / sys / class / gpio / gpio248 / direction ؛
- get : يقرأ القيمة المحددة على الخط. تم استدعاء الإدخال التالي: cat / sys / class / gpio / gpio248 / value؛
- set : تحدد القيمة على الخط. تم استدعاء الإدخال التالي: echo 1/0> / sys / class / gpio / gpio248 / value؛
لوصف تكوين IRQ على Linux ، توجد بنية irq_chip تحتوي على المجالات التالية:
- irq_set_type : يحدد نوع الحدث الذي ستحدث به المقاطعة. تم استدعاء الإدخال التالي: echo> "الارتفاع" / "السقوط" / "كلاهما"> / sys / class / gpio / gpio248 / edge؛
- irq_mask : يمنع المقاطعات. تم استدعاء الإدخال التالي: echo "none"> / sys / class / gpio / gpio248 / edge؛
- irq_unmask : تمكين المقاطعة على حدث تم تعيينه على irq_set_type. يتم الاتصال به فورًا بعد تنفيذ irq_set_type.
الخطوة الثانية
يمكنك الآن إضافة برنامج التشغيل إلى التجميع ووصف الأجهزة باتباع الخطوات القياسية. أولاً ، قم بإنشاء ملف المصدر:
cd drivers/gpio/ vim gpio-skel.c :wq
بعد إضافة تكوين برنامج التشغيل إلى
برامج التشغيل / gpio / Kconfig :
config GPIO_SKEL tristate "SKEL GPIO" help Say yes here to support SKEL GPIO.
إضافة برنامج التشغيل إلى التجميع في
برامج التشغيل / gpio / Makefile :
obj-$(CONFIG_GPIO_SKEL) += gpio-skel.o
وأخيرًا ، أضف وصفًا لكتلة GPIO إلى devicetree (* .dts):
gpio: gpio@f8f01d00 { compatible = "skel-gpio"; rcm,ngpio = <8>; rcm,interrupt-type = <IRQ_TYPE_EDGE_RISING>; clocks = <&clkc 42>; gpio-controller ; interrupt-parent = <&ps7_scugic_0>; interrupts = <0 29 4>; reg = <0x43c00000 0x100>; } ;
يمكنك قراءة المزيد عن devicetree
هنا .
الخطوة الثالثة
دعنا ننتقل إلى الجزء الأكثر إثارة للاهتمام بالنسبة لنا!
نبدأ في تطوير برنامج التشغيل من خلال ربط ملفات الرأس الضرورية ووصف الهيكل العظمي الكامل للسائق بدون دعم IRQ. بعد ذلك ، سنقوم بملء كل وظيفة بالتتابع برمز وسنرافق التفسيرات اللازمة.
الهيكل العظمي سائق GPIO #include <linux/of.h> #include <linux/irq.h> #include <linux/io.h> #include <linux/irqdomain.h> #include <linux/bitops.h> #include <linux/irqchip/chained_irq.h> #include <linux/interrupt.h> #include <linux/slab.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/gpio/driver.h> #include <linux/platform_device.h> #define SKEL_GPIO_VER 0x04 #define SKEL_GPIO_PAD_DIR 0x08 #define SKEL_GPIO_WR_DATA 0x0C #define SKEL_GPIO_RD_DATA 0x10 #define SKEL_GPIO_WR_DATA1 0x1C #define SKEL_GPIO_WR_DATA0 0x20 #define SKEL_GPIO_SRC 0x24 #define SKEL_GPIO_MAX_NGPIO 8 #define GPIO_OFFSET 4 struct skel_gpio_chip { struct gpio_chip gchip; spinlock_t lock; void __iomem *regs; u32 type; }; static inline void gpio_write(uint32_t value, void *base, uint32_t addr) { writel(value, base + addr); #if defined DEBUG dev_dbg(rdev->dev, "iowrite32(0x%x, base + 0x%x);\n", value, addr); #endif } static inline uint32_t gpio_read(void *base, uint32_t addr) { uint32_t reg = readl(base + addr); #if defined DEBUG dev_dbg(rdev->dev, "/* ioread32(base + 0x%x) == 0x%x */\n", addr, reg); #endif return reg; } static inline struct skel_gpio_chip *to_skel_gpio(struct gpio_chip *chip) { } /* * echo "in" > /sys/class/gpio/gpioN/direction */ static int skel_gpio_direction_input(struct gpio_chip *chip, unsigned offset) { } /* * echo "out" > /sys/class/gpio/gpioN/direction */ static int skel_gpio_direction_output(struct gpio_chip *chip, unsigned offset, int value) { } static int skel_gpio_get(struct gpio_chip *chip, unsigned offset) { } static void skel_gpio_set(struct gpio_chip *chip, unsigned offset, int value) { } static int skel_gpio_probe(struct platform_device *pdev) { } static int skel_gpio_remove(struct platform_device *pdev) { } static const struct of_device_id skel_gpio_of_match[] = { { .compatible = "skel-gpio" }, { }, }; MODULE_DEVICE_TABLE(of, skel_gpio_of_match); static struct platform_driver skel_gpio_driver = { .probe = skel_gpio_probe, .remove = skel_gpio_remove, .driver = { .name = "skel-gpio", .of_match_table = of_match_ptr(skel_gpio_of_match), }, }; module_platform_driver(skel_gpio_driver); MODULE_DESCRIPTION("GPIO driver"); MODULE_AUTHOR("Name Surname"); MODULE_LICENSE("GPL");
كما ترى من التنفيذ ، يبدو الهيكل العظمي للسائق بسيطًا جدًا ولا يحتوي على العديد من الوظائف والهياكل الضرورية.
لوصف المحرك المستقبلي ، نحتاج إلى العناصر التالية:
- platform_driver skel_gpio_driver - يصف نقطة الدخول skel_gpio_probe عند تحميل برنامج التشغيل و skel_gpio_remove عندما يتم استخراجه من النواة ؛
- هيكل of_device_id skel_gpio_of_match - يحتوي على جدول يصف أجهزة كتلة GPIO ؛
- هيكل skel_gpio_chip - يحتوي على الحقول اللازمة للتحكم في مشغل كتلة GPIO.
علاوة على ذلك ، لتحميل / استخراج برنامج التشغيل من / إلى Linux ، من الضروري تنفيذ
طريقتين .robe و
.remove المحددين في بنية
skel_gpio_driver .
تنفيذ Skel_gpio_probe static int skel_gpio_probe(struct platform_device *pdev) { struct device_node *node = pdev->dev.of_node; struct skel_gpio_chip *skel_gc; struct resource *res; int ngpio, ret; skel_gc = devm_kzalloc(&pdev->dev, sizeof(*skel_gc), GFP_KERNEL); if (!skel_gc) return -ENOMEM; spin_lock_init(&skel_gc->lock); res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { dev_err(&pdev->dev, "Failed to get MMIO resource for GPIO.\n"); return -EINVAL; } skel_gc->regs = devm_ioremap_resource(&pdev->dev, res); if (!skel_gc->regs) goto free; if (!of_property_read_u32(node, "skel,ngpio", &ngpio)) skel_gc->gchip.ngpio = ngpio; else skel_gc->gchip.ngpio = SKEL_GPIO_MAX_NGPIO; if (skel_gc->gchip.ngpio > SKEL_GPIO_MAX_NGPIO) { dev_warn(&pdev->dev, "Number of gpio is greater than MAX!\n"); skel_gc->gchip.ngpio = SKEL_GPIO_MAX_NGPIO; } skel_gc->gchip.direction_input = skel_gpio_direction_input; skel_gc->gchip.direction_output = skel_gpio_direction_output; skel_gc->gchip.get = skel_gpio_get; skel_gc->gchip.set = skel_gpio_set; skel_gc->gchip.owner = THIS_MODULE; skel_gc->gchip.base = -1; platform_set_drvdata(pdev, skel_gc); ret = gpiochip_add(&skel_gc->gchip); if (ret) { dev_err(&pdev->dev, "Failed adding memory mapped gpiochip\n"); return ret; } dev_info(&pdev->dev, "SKEL GPIO probe complete: (%d .. %d)\n", skel_gc->gchip.base, skel_gc->gchip.base + skel_gc->gchip.ngpio); return 0; }
Skel_gpio_remove التنفيذ static int skel_gpio_remove(struct platform_device *pdev) { struct skel_gpio_chip *skel_gc = platform_get_drvdata(pdev); gpiochip_remove(&skel_gc->gchip); return 0; }
تقوم الوظيفة skel_gpio_remove بإزالة برنامج تشغيل GPIO المسجل من النواة ، لذا فكر في النقاط الرئيسية في skel_gpio_probe:
- devm_kzalloc - يخصص ذاكرة للبنية skel_gpio_chip ؛
- platform_get_resource - يقرأ عنوان بداية بطاقة تسجيل GPIO من devicetree ؛
- devm_ioremap_resource - ينفذ تخطيطًا لعنوان فعلي لعنوان افتراضي ؛
- of_property_read_u32 - يقرأ عدد GPIOs المتاحة من devicetree ؛
- skel_gc-> gchip. * - يملأ حقول البنية المطلوبة ؛
- gpiochip_add - يضيف وحدة تحكم GPIO إلى برنامج التشغيل ؛
حتى الآن ، لم يتم وصف سبب استخدام الأرقام السحرية مثل 248 ... 255. الإدخال skel_gc-> gchip.base = -1؛ يطلب من النواة تخصيص الأرقام التي يستخدمها GPIO ديناميكيًا. لمعرفة هذه الأرقام في نهاية برنامج التشغيل ، تتم إضافة الإخراج:
dev_info(&pdev->dev, "SKEL GPIO probe complete: (%d .. %d)\n", skel_gc->gchip.base, skel_gc->gchip.base + skel_gc->gchip.ngpio);
بالطبع ، يوفر Linux القدرة على تعيين الأرقام يدويًا ، ولكن دعنا نلقي نظرة على التعليق في
شفرة المصدر:
@base: identifies the first GPIO number handled by this chip; * or, if negative during registration, requests dynamic ID allocation. * DEPRECATION: providing anything non-negative and nailing the base * offset of GPIO chips is deprecated. Please pass -1 as base to * let gpiolib select the chip base in all possible cases. We want to * get rid of the static GPIO number space in the long run.
الخطوة الرابعة
ضع في اعتبارك الجزء الوظيفي للسائق ، أي أننا نطبق الأساليب التالية:
.direction_output و
.direction_input و
.get و
.set . بعد ذلك ، سيتم عرض رمز يعتمد على الأجهزة ، والذي سيكون مختلفًا في معظم الحالات.
تنفيذ Skel_gpio_direction_input static int skel_gpio_direction_input(struct gpio_chip *chip, unsigned offset) { struct skel_gpio_chip *gc = to_skel_gpio(chip); unsigned long flag; u32 data_dir; spin_lock_irqsave(&gc->lock, flag); data_dir = gpio_read(gc->regs, SKEL_GPIO_PAD_DIR); data_dir &= ~BIT(offset); gpio_write(data_dir, gc->regs, SKEL_GPIO_PAD_DIR); spin_unlock_irqrestore(&gc->lock, flag); return 0; }
تقوم طريقة skel_gpio_direction_input بتنفيذ الإجراءات التالية:
- تم استدعاؤها باستخدام الأمر التالي "في"> / sys / class / gpio / gpioN / direction ؛
- يقرأ السجل SKEL_GPIO_PAD_DIR ، المسؤول عن تحديد اتجاه دبوس GPIO ؛
- يضبط القناع المطلوب ؛
- إعادة القيمة المستلمة إلى SKEL_GPIO_PAD_DIR.
Skel_gpio_direction_output التنفيذ static int skel_gpio_direction_output(struct gpio_chip *chip, unsigned offset, int value) { struct skel_gpio_chip *gc = to_skel_gpio(chip); unsigned long flag; u32 data_reg, data_dir; spin_lock_irqsave(&gc->lock, flag); data_reg = gpio_read(gc->regs, SKEL_GPIO_WR_DATA); if (value) data_reg |= BIT(offset); else data_reg &= ~BIT(offset); gpio_write(data_reg, gc->regs, SKEL_GPIO_WR_DATA); data_dir = gpio_read(gc->regs, SKEL_GPIO_PAD_DIR); data_dir |= BIT(offset); gpio_write(data_dir, gc->regs, SKEL_GPIO_PAD_DIR); spin_unlock_irqrestore(&gc->lock, flag); return 0; }
تقوم طريقة skel_gpio_direction_output بتنفيذ إجراءات مشابهة لـ skel_gpio_direction_inut فيما عدا استدعاء الأوامر التالية:
- صدى "out"> / sys / class / gpio / gpioN / direction ؛
- صدى "مرتفع"> / sys / class / gpio / gpioN / direction ، وتعيين القيمة على 1 ؛
- صدى "منخفض"> / sys / class / gpio / gpioN / direction ، مع ضبط القيمة على 0.
القيمة - القيمة التي تحدد مستوى الإشارة على خط الإخراج.
تنفيذ Skel_gpio_set static void skel_gpio_set(struct gpio_chip *chip, unsigned offset, int value) { struct skel_gpio_chip *gc = to_skel_gpio(chip); unsigned long flag; unsigned int data_reg; spin_lock_irqsave(&gc->lock, flag); data_reg = gpio_read(gc->regs, SKEL_GPIO_WR_DATA); if (value) data_reg |= BIT(offset); else data_reg &= ~BIT(offset); gpio_write(data_reg, gc->regs, SKEL_GPIO_WR_DATA); spin_unlock_irqrestore(&gc->lock, flag); }
تقوم طريقة skel_gpio_set بتنفيذ الإجراءات التالية:
- يتم استدعاؤها باستخدام الأمر التالي echo 1/0> / sys / class / gpio / gpioN / value ؛
- يقرأ تسجيل SKEL_GPIO_WR_DATA ، الذي يظهر قيمة الإشارة الحالية على الخط ؛
- تعيين أو إعادة تعيين البت المطلوب عن طريق الإزاحة ؛
- يكتب القيمة المستلمة مرة أخرى إلى SKEL_GPIO_WR_DATA.
تنفيذ Skel_gpio_get static int skel_gpio_get(struct gpio_chip *chip, unsigned offset) { struct skel_gpio_chip *gc = to_skel_gpio(chip); return !!(gpio_read(gc->regs, SKEL_GPIO_RD_DATA) & BIT(offset)); }
تقرأ طريقة skel_gpio_get قيمة الإشارة على الخط بقراءة السجل SKEL_GPIO_RD_DATA.
بعد أن وصفنا جميع الأساليب والهياكل اللازمة ، يمكنك تجميع كل شيء وإلقاء نظرة على النسخة النهائية.
تنفيذ برنامج تشغيل GPIO #include <linux/of.h> #include <linux/irq.h> #include <linux/io.h> #include <linux/irqdomain.h> #include <linux/bitops.h> #include <linux/irqchip/chained_irq.h> #include <linux/interrupt.h> #include <linux/slab.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/gpio/driver.h> #include <linux/platform_device.h> #define SKEL_GPIO_VER 0x04 #define SKEL_GPIO_PAD_DIR 0x08 #define SKEL_GPIO_WR_DATA 0x0C #define SKEL_GPIO_RD_DATA 0x10 #define SKEL_GPIO_WR_DATA1 0x1C #define SKEL_GPIO_WR_DATA0 0x20 #define SKEL_GPIO_SRC 0x24 #define SKEL_GPIO_MAX_NGPIO 8 #define GPIO_OFFSET 4 struct skel_gpio_chip { struct gpio_chip gchip; spinlock_t lock; void __iomem *regs; u32 type; }; static inline void gpio_write(uint32_t value, void *base, uint32_t addr) { writel(value, base + addr); #if defined DEBUG dev_dbg(rdev->dev, "iowrite32(0x%x, base + 0x%x);\n", value, addr); #endif } static inline uint32_t gpio_read(void *base, uint32_t addr) { uint32_t reg = readl(base + addr); #if defined DEBUG dev_dbg(rdev->dev, "/* ioread32(base + 0x%x) == 0x%x */\n", addr, reg); #endif return reg; } static inline struct skel_gpio_chip *to_skel_gpio(struct gpio_chip *chip) { return container_of(chip, struct skel_gpio_chip, gchip); } /* * echo > "out" > /sys/class/gpio/gpioN/direction */ static int skel_gpio_direction_output(struct gpio_chip *chip, unsigned offset, int value) { struct skel_gpio_chip *gc = to_skel_gpio(chip); unsigned long flag; u32 data_reg, data_dir; spin_lock_irqsave(&gc->lock, flag); data_reg = gpio_read(gc->regs, SKEL_GPIO_WR_DATA); if (value) data_reg |= BIT(offset); else data_reg &= ~BIT(offset); gpio_write(data_reg, gc->regs, SKEL_GPIO_WR_DATA); data_dir = gpio_read(gc->regs, SKEL_GPIO_PAD_DIR); data_dir |= BIT(offset); gpio_write(data_dir, gc->regs, SKEL_GPIO_PAD_DIR); spin_unlock_irqrestore(&gc->lock, flag); return 0; } /* * echo > "in" > /sys/class/gpio/gpioN/direction */ static int skel_gpio_direction_input(struct gpio_chip *chip, unsigned offset) { struct skel_gpio_chip *gc = to_skel_gpio(chip); unsigned long flag; u32 data_dir; spin_lock_irqsave(&gc->lock, flag); data_dir = gpio_read(gc->regs, SKEL_GPIO_PAD_DIR); data_dir &= ~BIT(offset); gpio_write(data_dir, gc->regs, SKEL_GPIO_PAD_DIR); spin_unlock_irqrestore(&gc->lock, flag); return 0; } static int skel_gpio_get(struct gpio_chip *chip, unsigned offset) { struct skel_gpio_chip *gc = to_skel_gpio(chip); return !!(gpio_read(gc->regs, SKEL_GPIO_RD_DATA) & BIT(offset)); } static void skel_gpio_set(struct gpio_chip *chip, unsigned offset, int value) { struct skel_gpio_chip *gc = to_skel_gpio(chip); unsigned long flag; unsigned int data_reg; spin_lock_irqsave(&gc->lock, flag); data_reg = gpio_read(gc->regs, SKEL_GPIO_WR_DATA); if (value) data_reg |= BIT(offset); else data_reg &= ~BIT(offset); gpio_write(data_reg, gc->regs, SKEL_GPIO_WR_DATA); spin_unlock_irqrestore(&gc->lock, flag); } static int skel_gpio_remove(struct platform_device *pdev) { struct skel_gpio_chip *skel_gc = platform_get_drvdata(pdev); gpiochip_remove(&skel_gc->gchip); return 0; } static int skel_gpio_probe(struct platform_device *pdev) { struct device_node *node = pdev->dev.of_node; struct skel_gpio_chip *skel_gc; struct resource *res; int ngpio, ret; skel_gc = devm_kzalloc(&pdev->dev, sizeof(*skel_gc), GFP_KERNEL); if (!skel_gc) return -ENOMEM; spin_lock_init(&skel_gc->lock); res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { dev_err(&pdev->dev, "Failed to get MMIO resource for GPIO.\n"); return -EINVAL; } skel_gc->regs = devm_ioremap_resource(&pdev->dev, res); if (!skel_gc->regs) return -ENXIO; if (!of_property_read_u32(node, "skel,ngpio", &ngpio)) skel_gc->gchip.ngpio = ngpio; else skel_gc->gchip.ngpio = SKEL_GPIO_MAX_NGPIO; if (skel_gc->gchip.ngpio > SKEL_GPIO_MAX_NGPIO) { dev_warn(&pdev->dev, "Number of gpio is greater than MAX!\n"); skel_gc->gchip.ngpio = SKEL_GPIO_MAX_NGPIO; } skel_gc->gchip.direction_input = skel_gpio_direction_input; skel_gc->gchip.direction_output = skel_gpio_direction_output; skel_gc->gchip.get = skel_gpio_get; skel_gc->gchip.set = skel_gpio_set; skel_gc->gchip.owner = THIS_MODULE; skel_gc->gchip.base = -1; platform_set_drvdata(pdev, skel_gc); ret = gpiochip_add(&skel_gc->gchip); if (ret) { dev_err(&pdev->dev, "Failed adding memory mapped gpiochip\n"); return ret; } dev_info(&pdev->dev, "SKEL GPIO probe complete: (%d .. %d)\n", skel_gc->gchip.base, skel_gc->gchip.base + skel_gc->gchip.ngpio); return 0; } static const struct of_device_id skel_gpio_of_match[] = { { .compatible = "skel-gpio" }, { }, }; MODULE_DEVICE_TABLE(of, skel_gpio_of_match); static struct platform_driver skel_gpio_driver = { .probe = skel_gpio_probe, .remove = skel_gpio_remove, .driver = { .name = "skel-gpio", .of_match_table = of_match_ptr(skel_gpio_of_match), }, }; module_platform_driver(skel_gpio_driver); MODULE_DESCRIPTION("GPIO driver"); MODULE_AUTHOR("Name Surname"); MODULE_LICENSE("GPL");
يحتوي برنامج التشغيل الذي تم تنفيذه على الوظائف اللازمة للتحكم في GPIO ، ولكن في الوقت الحالي لا يدعم برنامج التشغيل معالجة المقاطعة ، بحيث يمكنك الانتقال إلى الخطوة التالية.
الخطوة الخامسة
يمكن تقسيم إضافة IRQ إلى برنامج تشغيل GPIO إلى ثلاث خطوات:
- وصف الطرق المدعومة في هياكل بيانات النواة ؛
- تمكين دعم IRQ في الوقت الذي يتم فيه تحميل برنامج التشغيل في النظام ؛
- تنفيذ الأساليب المدعومة.
في البداية ، نصف المجموعة اللازمة من العمليات:
static struct irq_chip skel_irq_chip = { .name = "skel-gpio", .irq_mask = skel_gpio_irq_mask, .irq_unmask = skel_gpio_irq_unmask, .irq_set_type = skel_gpio_irq_set_type, };
لذلك ، يمكن للسائق تمكين المقاطعات (skel_gpio_irq_unmask) / تعطيل (skel_gpio_irq_mask) والإشارة إلى نوع الحدث الذي سيتم إنشاؤه به (skel_gpio_irq_set_type).
بعد ذلك ، نصف الطريقة الوحيدة المسؤولة عن تعيين رقم IRq افتراضي إلى جهاز.
static struct irq_domain_ops skel_gpio_irq_domain_ops = { .map = skel_gpio_irq_domain_map, };
ثم سنخبر النواة أن برنامج التشغيل المحمل يدعم العمل مع IRQ. للقيام بذلك ، أضف التعليمات البرمجية التالية إلى وظيفة التحقيق:
مضيفا دعم IRQ skel_gc->gchip.to_irq = skel_gpio_to_irq; skel_gc->domain = irq_domain_add_linear(pdev->dev.of_node, rcm_gc->gchip.ngpio, &skel_gpio_irq_domain_ops, skel_gc); if (!skel_gc->domain) return -ENODEV; skel_gc->irq = platform_get_irq(pdev, 0); if (skel_gc->irq < 0) goto free; for (i = 0; i < skel_gc->gchip.ngpio; i++) { int irq = rcm_gpio_to_irq(&skel_gc->gchip, i); irq_set_chip_and_handler(irq, &skel_irq_chip, handle_simple_irq); #ifdef CONFIG_ARM set_irq_flags(irq, IRQF_VALID); #else irq_set_noprobe(irq); #endif } irq_set_chained_handler(skel_gc->irq, skel_irq_handler); irq_set_handler_data(skel_gc->irq, skel_gc);
في الرمز أعلاه يحدث:
- تخصيص وتهيئة المنطقة الواقعة تحت irq_domain ؛
- قراءة رقم المقاطعة مع devicetree ؛
- رسم الخرائط بين مقاطعة افتراضية والأجهزة ؛
- تسجيل معالج المقاطعة وضبط البيانات لإرسالها إلى المعالج ؛
نشرع في تنفيذ بعض الأساليب الموضحة أعلاه.
skel_gpio_to_irq - لإنشاء تعيين بين الأجهزة والمقاطعات الافتراضية. إذا كان هذا التعيين قد تم إنشاؤه بالفعل ، فإنه يُرجع عدد المقاطعة الافتراضية التي تم إنشاؤها.
Skel_gpio_to_irq التنفيذ static int skel_gpio_to_irq(struct gpio_chip *chip, unsigned gpio) { struct skel_gpio_chip *rcm = to_skel_gpio(chip); return irq_create_mapping(rcm->domain, gpio); }
skel_irq_handler هو معالج المقاطعة الذي:
- يقرأ سجل حالة المقاطعة ؛
- يقرأ سجل قناع المقاطعة ؛
- رميات المقاطعات التي تنتظر المعالجة ؛
- لكل GPIO يحدث فيه انقطاع ، يتم استدعاء generic_handle_irq.
تنفيذ معالج المقاطعة static void skel_irq_handler(unsigned int irq, struct irq_desc *desc) { struct skel_gpio_chip *skel_gc = irq_get_handler_data(irq); struct irq_chip *chip = irq_desc_get_chip(desc); void __iomem *base; u32 status, mask, gpio, pending; chained_irq_enter(chip, desc); base = skel_gc->regs; status = gpio_read(base, SKEL_GPIO_STATUS); mask = gpio_read(base, SKEL_GPIO_IRQ_MASK); pending = status & mask; while (pending) { gpio = __ffs(pending); pending &= ~BIT(gpio); generic_handle_irq( irq_find_mapping(skel_gc->domain, gpio)); } chained_irq_exit(chip, desc); }
هذا كل شيء ، تعلمنا في هذه المقالة كيفية تفاعل برنامج تشغيل GPIO مع نظام الملفات الظاهري sysfs ، وتنفيذ البنية الأساسية لبرنامج تشغيل GPIO ، وفحصنا أيضًا الطرق المطلوبة لدعم IRQ.
لا تصف المقالة تنفيذ طرق skel_gpio_irq_unmask و skel_gpio_irq_mask و skel_gpio_irq_set_type لسببين. أولاً ، هذه الطرق سهلة التنفيذ. ثانيا ، تعتمد على الأجهزة. وهي مسؤولة عن السماح بالمقاطعات أو تعطيلها لأحداث معينة تدعمها وحدة تحكم GPIO.
من فضلك ، إذا وجدت أخطاء / عدم دقة ، أو لديك شيء لتضيفه ، اكتبه في PM أو في التعليقات.
شكرا لكم على اهتمامكم!