转自:
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/jj12345jj198999/article/details/80285150
1、 背景介绍最近项目中使用了盛科的交换芯片8086,该交换芯片除了使用PCIE连接到zynq外,还提供了四根GPIO引脚连入zynq。盛科技术人员的说法是该芯片支持GPIO管脚中断和PCIE MSI中断,使用过程中二选一即可。目前PCIE MSI中断已经解决,需要调试GPIO管脚中断方式,ZYNQ连接示意图如下。
如上图所示,四根线之间连入一个concat,再加上PCIE的引脚,组成一个向量连入zynq的IRQ管脚。Zynq中启用了PL-PS的中断,分配的中断号为61-65.
2、 UIO机制引入
通常来说,zynq上挂接的中断都需要与一个控制器或IP核相对应,比如i2c,网络等,可以在devicetree中看到中断号,如下图
该中断号与UG585中中断描述的章节相一致,下表中的IRQ ID为对应设备的中断号+32的值(0x19+32=57,正好是i2c0的IRQ ID)
对于这种四根线直接接入的,devicetree中没有对应的设备,导致在操作系统中看不到中断。幸运的是Linux内核中提供了UIO机制,详细介绍见:https://01.org/linuxgraphics/gfx-docs/drm/driver-api/uio-howto.html
对我而言,UIO就是处理没有具体设备只有引脚的一种机制。
3、 devicetree设置
利用UIO,可以在devicetree中为四根GPIO线设置对应的设备,如下图所示。
四根线对应的中断号为0x1e-0x21,正好是62-65号中断。同时,需要修改devicetree启动项。
让操作系统在加载过程中执行uio驱动程序。
devicetree.dts全部代码如下:
/dts-v1/;
/ { #address-cells = <0x1>; #size-cells = <0x1>; compatible = "xlnx,zynq-7000"; cpus { #address-cells = <0x1>; #size-cells = <0x0>; cpu@0 { compatible = "arm,cortex-a9"; device_type = "cpu"; reg = <0x0>; clocks = <0x1 0x3>; clock-latency = <0x3e8>; cpu0-supply = <0x2>; operating-points = <0xa4cb8 0xf4240 0x5265c 0xf4240>; }; cpu@1 { compatible = "arm,cortex-a9"; device_type = "cpu"; reg = <0x1>; clocks = <0x1 0x3>; }; }; fpga-full { compatible = "fpga-region"; fpga-mgr = <0x3>; #address-cells = <0x1>; #size-cells = <0x1>; ranges; }; pmu@f8891000 { compatible = "arm,cortex-a9-pmu"; interrupts = <0x0 0x5 0x4 0x0 0x6 0x4>; interrupt-parent = <0x4>; reg = <0xf8891000 0x1000 0xf8893000 0x1000>; }; fixedregulator { compatible = "regulator-fixed"; regulator-name = "VCCPINT"; regulator-min-microvolt = <0xf4240>; regulator-max-microvolt = <0xf4240>; regulator-boot-on; regulator-always-on; linux,phandle = <0x2>; phandle = <0x2>; }; amba { u-boot,dm-pre-reloc; compatible = "simple-bus"; #address-cells = <0x1>; #size-cells = <0x1>; interrupt-parent = <0x4>; ranges; adc@f8007100 { compatible = "xlnx,zynq-xadc-1.00.a"; reg = <0xf8007100 0x20>; interrupts = <0x0 0x7 0x4>; interrupt-parent = <0x4>; clocks = <0x1 0xc>; }; can@e0008000 { compatible = "xlnx,zynq-can-1.0"; status = "disabled"; clocks = <0x1 0x13 0x1 0x24>; clock-names = "can_clk", "pclk"; reg = <0xe0008000 0x1000>; interrupts = <0x0 0x1c 0x4>; interrupt-parent = <0x4>; tx-fifo-depth = <0x40>; rx-fifo-depth = <0x40>; }; can@e0009000 { compatible = "xlnx,zynq-can-1.0"; status = "disabled"; clocks = <0x1 0x14 0x1 0x25>; clock-names = "can_clk", "pclk"; reg = <0xe0009000 0x1000>; interrupts = <0x0 0x33 0x4>; interrupt-parent = <0x4>; tx-fifo-depth = <0x40>; rx-fifo-depth = <0x40>; }; gpio@e000a000 { compatible = "xlnx,zynq-gpio-1.0"; #gpio-cells = <0x2>; clocks = <0x1 0x2a>; gpio-controller; interrupt-controller; #interrupt-cells = <0x2>; interrupt-parent = <0x4>; interrupts = <0x0 0x14 0x4>; reg = <0xe000a000 0x1000>; }; i2c@e0004000 { compatible = "cdns,i2c-r1p10"; status = "okay"; clocks = <0x1 0x26>; interrupt-parent = <0x4>; interrupts = <0x0 0x19 0x4>; reg = <0xe0004000 0x1000>; #address-cells = <0x1>; #size-cells = <0x0>; clock-frequency = <0x61a80>; }; i2c@e0005000 { compatible = "cdns,i2c-r1p10"; status = "okay"; clocks = <0x1 0x27>; interrupt-parent = <0x4>; interrupts = <0x0 0x30 0x4>; reg = <0xe0005000 0x1000>; #address-cells = <0x1>; #size-cells = <0x0>; clock-frequency = <0x61a80>; }; interrupt-controller@f8f01000 { compatible = "arm,cortex-a9-gic"; #interrupt-cells = <0x3>; interrupt-controller; reg = <0xf8f01000 0x1000 0xf8f00100 0x100>; num_cpus = <0x2>; num_interrupts = <0x60>; linux,phandle = <0x4>; phandle = <0x4>; }; cache-controller@f8f02000 { compatible = "arm,pl310-cache"; reg = <0xf8f02000 0x1000>; interrupts = <0x0 0x2 0x4>; arm,data-latency = <0x3 0x2 0x2>; arm,tag-latency = <0x2 0x2 0x2>; cache-unified; cache-level = <0x2>; }; memory-controller@f8006000 { compatible = "xlnx,zynq-ddrc-a05"; reg = <0xf8006000 0x1000>; }; ocmc@f800c000 { compatible = "xlnx,zynq-ocmc-1.0"; interrupt-parent = <0x4>; interrupts = <0x0 0x3 0x4>; reg = <0xf800c000 0x1000>; }; serial@e0000000 { compatible = "xlnx,xuartps", "cdns,uart-r1p8"; status = "okay"; clocks = <0x1 0x17 0x1 0x28>; clock-names = "uart_clk", "pclk"; reg = <0xe0000000 0x1000>; interrupts = <0x0 0x1b 0x4>; device_type = "serial"; port-number = <0x0>; }; serial@e0001000 { compatible = "xlnx,xuartps", "cdns,uart-r1p8"; status = "disabled"; clocks = <0x1 0x18 0x1 0x29>; clock-names = "uart_clk", "pclk"; reg = <0xe0001000 0x1000>; interrupts = <0x0 0x32 0x4>; }; spi@e0006000 { compatible = "xlnx,zynq-spi-r1p6"; reg = <0xe0006000 0x1000>; status = "okay"; interrupt-parent = <0x4>; interrupts = <0x0 0x1a 0x4>; clocks = <0x1 0x19 0x1 0x22>; clock-names = "ref_clk", "pclk"; #address-cells = <0x1>; #size-cells = <0x0>; is-decoded-cs = <0x0>; num-cs = <0x3>; }; spi@e0007000 { compatible = "xlnx,zynq-spi-r1p6"; reg = <0xe0007000 0x1000>; status = "disabled"; interrupt-parent = <0x4>; interrupts = <0x0 0x31 0x4>; clocks = <0x1 0x1a 0x1 0x23>; clock-names = "ref_clk", "pclk"; #address-cells = <0x1>; #size-cells = <0x0>; }; spi@e000d000 { clock-names = "ref_clk", "pclk"; clocks = <0x1 0xa 0x1 0x2b>; compatible = "xlnx,zynq-qspi-1.0"; status = "okay"; interrupt-parent = <0x4>; interrupts = <0x0 0x13 0x4>; reg = <0xe000d000 0x1000>; #address-cells = <0x1>; #size-cells = <0x0>; is-dual = <0x0>; num-cs = <0x1>; }; memory-controller@e000e000 { #address-cells = <0x1>; #size-cells = <0x1>; status = "disabled"; clock-names = "memclk", "aclk"; clocks = <0x1 0xb 0x1 0x2c>; compatible = "arm,pl353-smc-r2p1"; interrupt-parent = <0x4>; interrupts = <0x0 0x12 0x4>; ranges; reg = <0xe000e000 0x1000>; flash@e1000000 { status = "disabled"; compatible = "arm,pl353-nand-r2p1"; reg = <0xe1000000 0x1000000>; #address-cells = <0x1>; #size-cells = <0x1>; }; flash@e2000000 { status = "disabled"; compatible = "cfi-flash"; reg = <0xe2000000 0x2000000>; #address-cells = <0x1>; #size-cells = <0x1>; }; }; ethernet@e000b000 { compatible = "xlnx,ps7-ethernet-1.00.a"; reg = <0xe000b000 0x1000>; status = "okay"; interrupts = <0x0 0x16 0x4>; clocks = <0x1 0xd 0x1 0x1e>; clock-names = "ref_clk", "aper_clk"; #address-cells = <0x1>; #size-cells = <0x0>; enet-reset = <0x4 0x2f 0x0>; local-mac-address = [00 0a 35 00 00 00]; phy-mode = "rgmii"; phy-handle = <0x7>; xlnx,eth-mode = <0x1>; xlnx,has-mdio = <0x1>; xlnx,ptp-enet-clock = <0x69f6bcb>; mdio { #address-cells = <0x1>; #size-cells = <0x0>; phy@0 { compatible = "marvell,88e1111"; device_type = "ethernet-phy"; reg = <0x0>; linux,phandle = <0x7>; phandle = <0x7>; }; }; }; ethernet@e000c000 { compatible = "cdns,zynq-gem", "cdns,gem"; reg = <0xe000c000 0x1000>; status = "disabled"; interrupts = <0x0 0x2d 0x4>; clocks = <0x1 0x1f 0x1 0x1f 0x1 0xe>; clock-names = "pclk", "hclk", "tx_clk"; #address-cells = <0x1>; #size-cells = <0x0>; }; sdhci@e0100000 { compatible = "arasan,sdhci-8.9a"; status = "disabled"; clock-names = "clk_xin", "clk_ahb"; clocks = <0x1 0x15 0x1 0x20>; interrupt-parent = <0x4>; interrupts = <0x0 0x18 0x4>; reg = <0xe0100000 0x1000>; }; sdhci@e0101000 { compatible = "arasan,sdhci-8.9a"; status = "disabled"; clock-names = "clk_xin", "clk_ahb"; clocks = <0x1 0x16 0x1 0x21>; interrupt-parent = <0x4>; interrupts = <0x0 0x2f 0x4>; reg = <0xe0101000 0x1000>; }; slcr@f8000000 { #address-cells = <0x1>; #size-cells = <0x1>; compatible = "xlnx,zynq-slcr", "syscon", "simple-mfd"; reg = <0xf8000000 0x1000>; ranges; linux,phandle = <0x5>; phandle = <0x5>; clkc@100 { #clock-cells = <0x1>; compatible = "xlnx,ps7-clkc"; fclk-enable = <0x1>; clock-output-names = "armpll", "ddrpll", "iopll", "cpu_6or4x", "cpu_3or2x", "cpu_2x", "cpu_1x", "ddr2x", "ddr3x", "dci", "lqspi", "smc", "pcap", "gem0", "gem1", "fclk0", "fclk1", "fclk2", "fclk3", "can0", "can1", "sdio0", "sdio1", "uart0", "uart1", "spi0", "spi1", "dma", "usb0_aper", "usb1_aper", "gem0_aper", "gem1_aper", "sdio0_aper", "sdio1_aper", "spi0_aper", "spi1_aper", "can0_aper", "can1_aper", "i2c0_aper", "i2c1_aper", "uart0_aper", "uart1_aper", "gpio_aper", "lqspi_aper", "smc_aper", "swdt", "dbg_trc", "dbg_apb"; reg = <0x100 0x100>; ps-clk-frequency = <0x2faf080>; linux,phandle = <0x1>; phandle = <0x1>; }; rstc@200 { compatible = "xlnx,zynq-reset"; reg = <0x200 0x48>; #reset-cells = <0x1>; syscon = <0x5>; }; pinctrl@700 { compatible = "xlnx,pinctrl-zynq"; reg = <0x700 0x200>; syscon = <0x5>; }; }; dmac@f8003000 { compatible = "arm,pl330", "arm,primecell"; reg = <0xf8003000 0x1000>; interrupt-parent = <0x4>; interrupt-names = "abort", "dma0", "dma1", "dma2", "dma3", "dma4", "dma5", "dma6", "dma7"; interrupts = <0x0 0xd 0x4 0x0 0xe 0x4 0x0 0xf 0x4 0x0 0x10 0x4 0x0 0x11 0x4 0x0 0x28 0x4 0x0 0x29 0x4 0x0 0x2a 0x4 0x0 0x2b 0x4>; #dma-cells = <0x1>; #dma-channels = <0x8>; #dma-requests = <0x4>; clocks = <0x1 0x1b>; clock-names = "apb_pclk"; }; devcfg@f8007000 { compatible = "xlnx,zynq-devcfg-1.0"; interrupt-parent = <0x4>; interrupts = <0x0 0x8 0x4>; reg = <0xf8007000 0x100>; clocks = <0x1 0xc 0x1 0xf 0x1 0x10 0x1 0x11 0x1 0x12>; clock-names = "ref_clk", "fclk0", "fclk1", "fclk2", "fclk3"; syscon = <0x5>; linux,phandle = <0x3>; phandle = <0x3>; }; efuse@f800d000 { compatible = "xlnx,zynq-efuse"; reg = <0xf800d000 0x20>; }; timer@f8f00200 { compatible = "arm,cortex-a9-global-timer"; reg = <0xf8f00200 0x20>; interrupts = <0x1 0xb 0x301>; interrupt-parent = <0x4>; clocks = <0x1 0x4>; }; timer@f8001000 { interrupt-parent = <0x4>; interrupts = <0x0 0xa 0x4 0x0 0xb 0x4 0x0 0xc 0x4>; compatible = "cdns,ttc"; clocks = <0x1 0x6>; reg = <0xf8001000 0x1000>; }; timer@f8002000 { interrupt-parent = <0x4>; interrupts = <0x0 0x25 0x4 0x0 0x26 0x4 0x0 0x27 0x4>; compatible = "cdns,ttc"; clocks = <0x1 0x6>; reg = <0xf8002000 0x1000>; }; timer@f8f00600 { interrupt-parent = <0x4>; interrupts = <0x1 0xd 0x301>; compatible = "arm,cortex-a9-twd-timer"; reg = <0xf8f00600 0x20>; clocks = <0x1 0x4>; }; usb@e0002000 { compatible = "xlnx,zynq-usb-2.20a", "chipidea,usb2"; status = "disabled"; clocks = <0x1 0x1c>; interrupt-parent = <0x4>; interrupts = <0x0 0x15 0x4>; reg = <0xe0002000 0x1000>; phy_type = "ulpi"; }; usb@e0003000 { compatible = "xlnx,zynq-usb-2.20a", "chipidea,usb2"; status = "disabled"; clocks = <0x1 0x1d>; interrupt-parent = <0x4>; interrupts = <0x0 0x2c 0x4>; reg = <0xe0003000 0x1000>; phy_type = "ulpi"; }; watchdog@f8005000 { clocks = <0x1 0x2d>; compatible = "cdns,wdt-r1p2"; interrupt-parent = <0x4>; interrupts = <0x0 0x9 0x1>; reg = <0xf8005000 0x1000>; timeout-sec = <0xa>; }; }; amba_pl { #address-cells = <0x1>; #size-cells = <0x1>; compatible = "simple-bus"; ranges; gpio@41200000 { #gpio-cells = <0x2>; compatible = "xlnx,xps-gpio-1.00.a"; gpio-controller; reg = <0x41200000 0x10000>; xlnx,all-inputs = <0x0>; xlnx,all-inputs-2 = <0x0>; xlnx,all-outputs = <0x1>; xlnx,all-outputs-2 = <0x0>; xlnx,dout-default = <0x0>; xlnx,dout-default-2 = <0x0>; xlnx,gpio-width = <0x8>; xlnx,gpio2-width = <0x20>; xlnx,interrupt-present = <0x0>; xlnx,is-dual = <0x0>; xlnx,tri-default = <0xffffffff>; xlnx,tri-default-2 = <0xffffffff>; }; uio@0{ compatible="generic-uio"; status="okay"; interrupt-controller; interrupt-parent=<0x4>; interrupts=<0x0 0x1e 0x4>; }; uio@1{ compatible="generic-uio"; status="okay"; interrupt-controller; interrupt-parent=<0x4>; interrupts=<0x0 0x1f 0x4>; }; uio@2{ compatible="generic-uio"; status="okay"; interrupt-controller; interrupt-parent=<0x4>; interrupts=<0x0 0x20 0x4>; }; uio@3{ compatible="generic-uio"; status="okay"; interrupt-controller; interrupt-parent=<0x4>; interrupts=<0x0 0x21 0x4>; }; axi-pcie@90000000 { #address-cells = <0x3>; #interrupt-cells = <0x1>; #size-cells = <0x2>; compatible = "xlnx,axi-pcie-host-1.00.a"; device_type = "pci"; interrupt-map = <0x0 0x0 0x0 0x1 0x6 0x1 0x0 0x0 0x0 0x2 0x6 0x2 0x0 0x0 0x0 0x3 0x6 0x3 0x0 0x0 0x0 0x4 0x6 0x4>; interrupt-map-mask = <0x0 0x0 0x0 0x7>; interrupt-parent = <0x4>; interrupts = <0x0 0x1d 0x4>; ranges = <0x2000000 0x0 0xa0000000 0xa0000000 0x0 0x20000000>; reg = <0x90000000 0x4000000>; interrupt-controller { #address-cells = <0x0>; #interrupt-cells = <0x1>; interrupt-controller; linux,phandle = <0x6>; phandle = <0x6>; }; }; }; chosen { bootargs = "earlycon uio_pdrv_genirq.of_id=generic-uio"; stdout-path = "serial0:115200n8"; }; aliases { ethernet0 = "/amba/ethernet@e000b000"; serial0 = "/amba/serial@e0000000"; spi0 = "/amba/spi@e000d000"; spi1 = "/amba/spi@e0006000"; }; memory { device_type = "memory"; reg = <0x0 0x40000000>; };};4、 kernel配置
在kernel中需要把UIO驱动编译进内核,这里使用的版本是XILINX 2017.4(内核版本4.9)。
5、 UIO测试
修改完devicetree和kernel,就可以启动linux对UIO进行测试了。这里通过cat /proc/interrupts看到的信息如下。
为了测试中断,需要在vivado中引入VIO机制,模拟四根线电平拉高拉低。示例代码如下:
使用vio修改value值即可模拟
此时能发现确实收到了中断
不过UIO存在一个问题,当中断到来时,驱动处理函数中将该中断disable,导致每次只能响应一次。需要用户启用该中断shell中输入echo 0x1 > /dev/uioX 或者调用write()函数。
再次启用中断后,计数值增加了。
6、 用户自定义中断
UIO机制中提供了中断响应函数,不过该处理函数只是禁中断,比较简单,用户完全可以对该函数进行修改,增加自己的处理过程,比如像下面这样
其中intrX_handler函数定义如下
这样在中断来临时,就能触发自定义函数的执行。
注意,在执行完自定义中断处理函数结束后enable对应的irq,否则就要像上面那样手动打开了。
uio驱动代码如下:
/*
* drivers/uio/uio_pdrv_genirq.c * * Userspace I/O platform driver with generic IRQ handling code. * * Copyright (C) 2008 Magnus Damm * * Based on uio_pdrv.c by Uwe Kleine-Koenig, * Copyright (C) 2008 by Digi International Inc. * All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 as published by * the Free Software Foundation. */#include <linux/platform_device.h>#include <linux/uio_driver.h>#include <linux/spinlock.h>#include <linux/bitops.h>#include <linux/module.h>#include <linux/interrupt.h>#include <linux/stringify.h>#include <linux/pm_runtime.h>#include <linux/slab.h>#include <linux/of.h>#include <linux/of_platform.h>#include <linux/of_address.h>#include "dal_kernel.h"//extern static irqreturn_t intr0_handler(int irq,void* dev_id);//extern static irqreturn_t intr1_handler(int irq,void* dev_id);//extern static irqreturn_t intr2_handler(int irq,void* dev_id);//extern static irqreturn_t intr3_handler(int irq,void* dev_id);#define DRIVER_NAME "uio_pdrv_genirq"struct uio_pdrv_genirq_platdata { struct uio_info *uioinfo; spinlock_t lock; unsigned long flags; struct platform_device *pdev;};/* Bits in uio_pdrv_genirq_platdata.flags */enum { UIO_IRQ_DISABLED = 0,};static int uio_pdrv_genirq_open(struct uio_info *info, struct inode *inode){ struct uio_pdrv_genirq_platdata *priv = info->priv; /* Wait until the Runtime PM code has woken up the device */ pm_runtime_get_sync(&priv->pdev->dev); return 0;}static int uio_pdrv_genirq_release(struct uio_info *info, struct inode *inode){ struct uio_pdrv_genirq_platdata *priv = info->priv; /* Tell the Runtime PM code that the device has become idle */ pm_runtime_put_sync(&priv->pdev->dev); return 0;}static irqreturn_t uio_pdrv_genirq_handler(int irq, struct uio_info *dev_info){ struct uio_pdrv_genirq_platdata *priv = dev_info->priv; /* Just disable the interrupt in the interrupt controller, and * remember the state so we can allow user space to enable it later. */#if 0 spin_lock(&priv->lock); if (!__test_and_set_bit(UIO_IRQ_DISABLED, &priv->flags)) { disable_irq_nosync(irq); } spin_unlock(&priv->lock);#endif printk(">>>>>>handle UIO Interrupt now,irq number is %d\n",irq);#if 1 //get irq related handler if(irq==47) { printk("Trigger interrupts 47\n"); intr0_handler(47,NULL); } if(irq==48) { printk("Trigger interrupts 48\n"); intr1_handler(48,NULL); } if(irq==49) { printk("Trigger interrupts 49\n"); intr2_handler(49,NULL); } if(irq==50) { printk("Trigger interrupts 50\n"); intr3_handler(50,NULL); }#endif return IRQ_HANDLED;}static int uio_pdrv_genirq_irqcontrol(struct uio_info *dev_info, s32 irq_on){ struct uio_pdrv_genirq_platdata *priv = dev_info->priv; unsigned long flags; /* Allow user space to enable and disable the interrupt * in the interrupt controller, but keep track of the * state to prevent per-irq depth damage. * * Serialize this operation to support multiple tasks and concurrency * with irq handler on SMP systems. */ spin_lock_irqsave(&priv->lock, flags); if (irq_on) { if (__test_and_clear_bit(UIO_IRQ_DISABLED, &priv->flags)) enable_irq(dev_info->irq); } else { if (!__test_and_set_bit(UIO_IRQ_DISABLED, &priv->flags)) disable_irq_nosync(dev_info->irq); } spin_unlock_irqrestore(&priv->lock, flags); return 0;}static int uio_pdrv_genirq_probe(struct platform_device *pdev){ struct uio_info *uioinfo = dev_get_platdata(&pdev->dev); struct uio_pdrv_genirq_platdata *priv; struct uio_mem *uiomem; int ret = -EINVAL; int i; if (pdev->dev.of_node) { /* alloc uioinfo for one device */ uioinfo = devm_kzalloc(&pdev->dev, sizeof(*uioinfo), GFP_KERNEL); if (!uioinfo) { dev_err(&pdev->dev, "unable to kmalloc\n"); return -ENOMEM; } uioinfo->name = pdev->dev.of_node->name; uioinfo->version = "devicetree"; /* Multiple IRQs are not supported */ } if (!uioinfo || !uioinfo->name || !uioinfo->version) { dev_err(&pdev->dev, "missing platform_data\n"); return ret; } if (uioinfo->handler || uioinfo->irqcontrol || uioinfo->irq_flags & IRQF_SHARED) { dev_err(&pdev->dev, "interrupt configuration error\n"); return ret; } priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); if (!priv) { dev_err(&pdev->dev, "unable to kmalloc\n"); return -ENOMEM; } priv->uioinfo = uioinfo; spin_lock_init(&priv->lock); priv->flags = 0; /* interrupt is enabled to begin with */ priv->pdev = pdev; if (!uioinfo->irq) { ret = platform_get_irq(pdev, 0); uioinfo->irq = ret; if (ret == -ENXIO && pdev->dev.of_node) uioinfo->irq = UIO_IRQ_NONE; else if (ret < 0) { dev_err(&pdev->dev, "failed to get IRQ\n"); return ret; } } uiomem = &uioinfo->mem[0]; for (i = 0; i < pdev->num_resources; ++i) { struct resource *r = &pdev->resource[i]; if (r->flags != IORESOURCE_MEM) continue; if (uiomem >= &uioinfo->mem[MAX_UIO_MAPS]) { dev_warn(&pdev->dev, "device has more than " __stringify(MAX_UIO_MAPS) " I/O memory resources.\n"); break; } uiomem->memtype = UIO_MEM_PHYS; uiomem->addr = r->start; uiomem->size = resource_size(r); uiomem->name = r->name; ++uiomem; } while (uiomem < &uioinfo->mem[MAX_UIO_MAPS]) { uiomem->size = 0; ++uiomem; } /* This driver requires no hardware specific kernel code to handle * interrupts. Instead, the interrupt handler simply disables the * interrupt in the interrupt controller. User space is responsible * for performing hardware specific acknowledge and re-enabling of * the interrupt in the interrupt controller. * * Interrupt sharing is not supported. */ uioinfo->handler = uio_pdrv_genirq_handler; uioinfo->irqcontrol = uio_pdrv_genirq_irqcontrol; uioinfo->open = uio_pdrv_genirq_open; uioinfo->release = uio_pdrv_genirq_release; uioinfo->priv = priv; /* Enable Runtime PM for this device: * The device starts in suspended state to allow the hardware to be * turned off by default. The Runtime PM bus code should power on the * hardware and enable clocks at open(). */ pm_runtime_enable(&pdev->dev); ret = uio_register_device(&pdev->dev, priv->uioinfo); if (ret) { dev_err(&pdev->dev, "unable to register uio device\n"); pm_runtime_disable(&pdev->dev); return ret; } platform_set_drvdata(pdev, priv); return 0;}static int uio_pdrv_genirq_remove(struct platform_device *pdev){ struct uio_pdrv_genirq_platdata *priv = platform_get_drvdata(pdev); uio_unregister_device(priv->uioinfo); pm_runtime_disable(&pdev->dev); priv->uioinfo->handler = NULL; priv->uioinfo->irqcontrol = NULL; return 0;}static int uio_pdrv_genirq_runtime_nop(struct device *dev){ /* Runtime PM callback shared between ->runtime_suspend() * and ->runtime_resume(). Simply returns success. * * In this driver pm_runtime_get_sync() and pm_runtime_put_sync() * are used at open() and release() time. This allows the * Runtime PM code to turn off power to the device while the * device is unused, ie before open() and after release(). * * This Runtime PM callback does not need to save or restore * any registers since user space is responsbile for hardware * register reinitialization after open(). */ return 0;}static const struct dev_pm_ops uio_pdrv_genirq_dev_pm_ops = { .runtime_suspend = uio_pdrv_genirq_runtime_nop, .runtime_resume = uio_pdrv_genirq_runtime_nop,};#ifdef CONFIG_OFstatic struct of_device_id uio_of_genirq_match[] = { { /* This is filled with module_parm */ }, { /* Sentinel */ },};MODULE_DEVICE_TABLE(of, uio_of_genirq_match);module_param_string(of_id, uio_of_genirq_match[0].compatible, 128, 0);MODULE_PARM_DESC(of_id, "Openfirmware id of the device to be handled by uio");#endifstatic struct platform_driver uio_pdrv_genirq = { .probe = uio_pdrv_genirq_probe, .remove = uio_pdrv_genirq_remove, .driver = { .name = DRIVER_NAME, .pm = &uio_pdrv_genirq_dev_pm_ops, .of_match_table = of_match_ptr(uio_of_genirq_match), },};module_platform_driver(uio_pdrv_genirq);MODULE_AUTHOR("Magnus Damm");MODULE_DESCRIPTION("Userspace I/O platform driver with generic IRQ handling");MODULE_LICENSE("GPL v2");MODULE_ALIAS("platform:" DRIVER_NAME);
7、 一点补充
a)UIO机制中虽然包含四种中断方式(高电平、低电平、上升沿、下降沿),但在zynq中只支持2种(高电平、上升沿),中断方式的配置位于devicetree中,示例如下
Devicetree中大多数设备的interrupts最后参数都是0x4,从cat/proc/interrupts中能看到只有watchdog几个为上升沿触发。
b)使用request_irq申请中断的时候irq的值不是devicetree中设定的值,而是操作系统启动后分配的值,所以在UIO中打印出来irq值不是62-65,而是47-50。通过在irq-gic.c中和uio_pdrv_genirq.c中增加打印信息,发现中断触发时过程如下。
需要注意的是,在gic中该值为62,这和devicetree中设置的一样。而从打印信息能够看到,中断是先送到gic,然后再送给内核,内核再调用uio的中断处理函数。所以如果需要在uio中增加自己的代码处理相应的中断,需要先启动操作系统,确认好每根线对应的中断号才行。
注意:在这个例子中,由于UIO已经注册了47-50号中断,且UIO不支持中断共享,所以其他程序再次注册47-50号中断时会出错。
--------------------- 作者:Felven 来源:CSDN 原文:https://blog.csdn.net/zhaoxinfan/article/details/80285150 版权声明:本文为博主原创文章,转载请附上博文链接!