Zynq-Linux移植学习笔记之27UIO机制响应外部中断实现

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_OF
static 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");
#endif

static 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号中断时会出错。


猜你喜欢

转载自blog.csdn.net/jj12345jj198999/article/details/80285150