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全部代码如下:

  1. /dts-v1/;
  2. / {
  3. #address-cells = <0x1>;
  4. #size-cells = <0x1>;
  5. compatible = "xlnx,zynq-7000";
  6. cpus {
  7. #address-cells = <0x1>;
  8. #size-cells = <0x0>;
  9. compatible = "arm,cortex-a9";
  10. device_type = "cpu";
  11. reg = <0x0>;
  12. clocks = <0x1 0x3>;
  13. clock-latency = <0x3e8>;
  14. cpu0-supply = <0x2>;
  15. operating-points = <0xa4cb8 0xf4240 0x5265c 0xf4240>;
  16. };
  17. compatible = "arm,cortex-a9";
  18. device_type = "cpu";
  19. reg = <0x1>;
  20. clocks = <0x1 0x3>;
  21. };
  22. };
  23. fpga-full {
  24. compatible = "fpga-region";
  25. fpga-mgr = <0x3>;
  26. #address-cells = <0x1>;
  27. #size-cells = <0x1>;
  28. ranges;
  29. };
  30. compatible = "arm,cortex-a9-pmu";
  31. interrupts = <0x0 0x5 0x4 0x0 0x6 0x4>;
  32. interrupt-parent = <0x4>;
  33. reg = <0xf8891000 0x1000 0xf8893000 0x1000>;
  34. };
  35. fixedregulator {
  36. compatible = "regulator-fixed";
  37. regulator-name = "VCCPINT";
  38. regulator-min-microvolt = <0xf4240>;
  39. regulator-max-microvolt = <0xf4240>;
  40. regulator-boot-on;
  41. regulator-always-on;
  42. linux,phandle = <0x2>;
  43. phandle = <0x2>;
  44. };
  45. amba {
  46. u-boot,dm-pre-reloc;
  47. compatible = "simple-bus";
  48. #address-cells = <0x1>;
  49. #size-cells = <0x1>;
  50. interrupt-parent = <0x4>;
  51. ranges;
  52. compatible = "xlnx,zynq-xadc-1.00.a";
  53. reg = <0xf8007100 0x20>;
  54. interrupts = <0x0 0x7 0x4>;
  55. interrupt-parent = <0x4>;
  56. clocks = <0x1 0xc>;
  57. };
  58. compatible = "xlnx,zynq-can-1.0";
  59. status = "disabled";
  60. clocks = <0x1 0x13 0x1 0x24>;
  61. clock-names = "can_clk", "pclk";
  62. reg = <0xe0008000 0x1000>;
  63. interrupts = <0x0 0x1c 0x4>;
  64. interrupt-parent = <0x4>;
  65. tx-fifo-depth = <0x40>;
  66. rx-fifo-depth = <0x40>;
  67. };
  68. compatible = "xlnx,zynq-can-1.0";
  69. status = "disabled";
  70. clocks = <0x1 0x14 0x1 0x25>;
  71. clock-names = "can_clk", "pclk";
  72. reg = <0xe0009000 0x1000>;
  73. interrupts = <0x0 0x33 0x4>;
  74. interrupt-parent = <0x4>;
  75. tx-fifo-depth = <0x40>;
  76. rx-fifo-depth = <0x40>;
  77. };
  78. compatible = "xlnx,zynq-gpio-1.0";
  79. #gpio-cells = <0x2>;
  80. clocks = <0x1 0x2a>;
  81. gpio-controller;
  82. interrupt-controller;
  83. #interrupt-cells = <0x2>;
  84. interrupt-parent = <0x4>;
  85. interrupts = <0x0 0x14 0x4>;
  86. reg = <0xe000a000 0x1000>;
  87. };
  88. compatible = "cdns,i2c-r1p10";
  89. status = "okay";
  90. clocks = <0x1 0x26>;
  91. interrupt-parent = <0x4>;
  92. interrupts = <0x0 0x19 0x4>;
  93. reg = <0xe0004000 0x1000>;
  94. #address-cells = <0x1>;
  95. #size-cells = <0x0>;
  96. clock-frequency = <0x61a80>;
  97. };
  98. compatible = "cdns,i2c-r1p10";
  99. status = "okay";
  100. clocks = <0x1 0x27>;
  101. interrupt-parent = <0x4>;
  102. interrupts = <0x0 0x30 0x4>;
  103. reg = <0xe0005000 0x1000>;
  104. #address-cells = <0x1>;
  105. #size-cells = <0x0>;
  106. clock-frequency = <0x61a80>;
  107. };
  108. compatible = "arm,cortex-a9-gic";
  109. #interrupt-cells = <0x3>;
  110. interrupt-controller;
  111. reg = <0xf8f01000 0x1000 0xf8f00100 0x100>;
  112. num_cpus = <0x2>;
  113. num_interrupts = <0x60>;
  114. linux,phandle = <0x4>;
  115. phandle = <0x4>;
  116. };
  117. compatible = "arm,pl310-cache";
  118. reg = <0xf8f02000 0x1000>;
  119. interrupts = <0x0 0x2 0x4>;
  120. arm,data-latency = <0x3 0x2 0x2>;
  121. arm,tag-latency = <0x2 0x2 0x2>;
  122. cache-unified;
  123. cache-level = <0x2>;
  124. };
  125. compatible = "xlnx,zynq-ddrc-a05";
  126. reg = <0xf8006000 0x1000>;
  127. };
  128. compatible = "xlnx,zynq-ocmc-1.0";
  129. interrupt-parent = <0x4>;
  130. interrupts = <0x0 0x3 0x4>;
  131. reg = <0xf800c000 0x1000>;
  132. };
  133. compatible = "xlnx,xuartps", "cdns,uart-r1p8";
  134. status = "okay";
  135. clocks = <0x1 0x17 0x1 0x28>;
  136. clock-names = "uart_clk", "pclk";
  137. reg = <0xe0000000 0x1000>;
  138. interrupts = <0x0 0x1b 0x4>;
  139. device_type = "serial";
  140. port-number = <0x0>;
  141. };
  142. compatible = "xlnx,xuartps", "cdns,uart-r1p8";
  143. status = "disabled";
  144. clocks = <0x1 0x18 0x1 0x29>;
  145. clock-names = "uart_clk", "pclk";
  146. reg = <0xe0001000 0x1000>;
  147. interrupts = <0x0 0x32 0x4>;
  148. };
  149. compatible = "xlnx,zynq-spi-r1p6";
  150. reg = <0xe0006000 0x1000>;
  151. status = "okay";
  152. interrupt-parent = <0x4>;
  153. interrupts = <0x0 0x1a 0x4>;
  154. clocks = <0x1 0x19 0x1 0x22>;
  155. clock-names = "ref_clk", "pclk";
  156. #address-cells = <0x1>;
  157. #size-cells = <0x0>;
  158. is-decoded-cs = <0x0>;
  159. num-cs = <0x3>;
  160. };
  161. compatible = "xlnx,zynq-spi-r1p6";
  162. reg = <0xe0007000 0x1000>;
  163. status = "disabled";
  164. interrupt-parent = <0x4>;
  165. interrupts = <0x0 0x31 0x4>;
  166. clocks = <0x1 0x1a 0x1 0x23>;
  167. clock-names = "ref_clk", "pclk";
  168. #address-cells = <0x1>;
  169. #size-cells = <0x0>;
  170. };
  171. clock-names = "ref_clk", "pclk";
  172. clocks = <0x1 0xa 0x1 0x2b>;
  173. compatible = "xlnx,zynq-qspi-1.0";
  174. status = "okay";
  175. interrupt-parent = <0x4>;
  176. interrupts = <0x0 0x13 0x4>;
  177. reg = <0xe000d000 0x1000>;
  178. #address-cells = <0x1>;
  179. #size-cells = <0x0>;
  180. is-dual = <0x0>;
  181. num-cs = <0x1>;
  182. };
  183. #address-cells = <0x1>;
  184. #size-cells = <0x1>;
  185. status = "disabled";
  186. clock-names = "memclk", "aclk";
  187. clocks = <0x1 0xb 0x1 0x2c>;
  188. compatible = "arm,pl353-smc-r2p1";
  189. interrupt-parent = <0x4>;
  190. interrupts = <0x0 0x12 0x4>;
  191. ranges;
  192. reg = <0xe000e000 0x1000>;
  193. status = "disabled";
  194. compatible = "arm,pl353-nand-r2p1";
  195. reg = <0xe1000000 0x1000000>;
  196. #address-cells = <0x1>;
  197. #size-cells = <0x1>;
  198. };
  199. status = "disabled";
  200. compatible = "cfi-flash";
  201. reg = <0xe2000000 0x2000000>;
  202. #address-cells = <0x1>;
  203. #size-cells = <0x1>;
  204. };
  205. };
  206. compatible = "xlnx,ps7-ethernet-1.00.a";
  207. reg = <0xe000b000 0x1000>;
  208. status = "okay";
  209. interrupts = <0x0 0x16 0x4>;
  210. clocks = <0x1 0xd 0x1 0x1e>;
  211. clock-names = "ref_clk", "aper_clk";
  212. #address-cells = <0x1>;
  213. #size-cells = <0x0>;
  214. enet-reset = <0x4 0x2f 0x0>;
  215. local-mac-address = [00 0a 35 00 00 00];
  216. phy-mode = "rgmii";
  217. phy-handle = <0x7>;
  218. xlnx,eth-mode = <0x1>;
  219. xlnx,has-mdio = <0x1>;
  220. xlnx,ptp-enet-clock = <0x69f6bcb>;
  221. mdio {
  222. #address-cells = <0x1>;
  223. #size-cells = <0x0>;
  224. compatible = "marvell,88e1111";
  225. device_type = "ethernet-phy";
  226. reg = <0x0>;
  227. linux,phandle = <0x7>;
  228. phandle = <0x7>;
  229. };
  230. };
  231. };
  232. compatible = "cdns,zynq-gem", "cdns,gem";
  233. reg = <0xe000c000 0x1000>;
  234. status = "disabled";
  235. interrupts = <0x0 0x2d 0x4>;
  236. clocks = <0x1 0x1f 0x1 0x1f 0x1 0xe>;
  237. clock-names = "pclk", "hclk", "tx_clk";
  238. #address-cells = <0x1>;
  239. #size-cells = <0x0>;
  240. };
  241. compatible = "arasan,sdhci-8.9a";
  242. status = "disabled";
  243. clock-names = "clk_xin", "clk_ahb";
  244. clocks = <0x1 0x15 0x1 0x20>;
  245. interrupt-parent = <0x4>;
  246. interrupts = <0x0 0x18 0x4>;
  247. reg = <0xe0100000 0x1000>;
  248. };
  249. compatible = "arasan,sdhci-8.9a";
  250. status = "disabled";
  251. clock-names = "clk_xin", "clk_ahb";
  252. clocks = <0x1 0x16 0x1 0x21>;
  253. interrupt-parent = <0x4>;
  254. interrupts = <0x0 0x2f 0x4>;
  255. reg = <0xe0101000 0x1000>;
  256. };
  257. #address-cells = <0x1>;
  258. #size-cells = <0x1>;
  259. compatible = "xlnx,zynq-slcr", "syscon", "simple-mfd";
  260. reg = <0xf8000000 0x1000>;
  261. ranges;
  262. linux,phandle = <0x5>;
  263. phandle = <0x5>;
  264. #clock-cells = <0x1>;
  265. compatible = "xlnx,ps7-clkc";
  266. fclk-enable = <0x1>;
  267. 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";
  268. reg = <0x100 0x100>;
  269. ps-clk-frequency = <0x2faf080>;
  270. linux,phandle = <0x1>;
  271. phandle = <0x1>;
  272. };
  273. compatible = "xlnx,zynq-reset";
  274. reg = <0x200 0x48>;
  275. #reset-cells = <0x1>;
  276. syscon = <0x5>;
  277. };
  278. compatible = "xlnx,pinctrl-zynq";
  279. reg = <0x700 0x200>;
  280. syscon = <0x5>;
  281. };
  282. };
  283. compatible = "arm,pl330", "arm,primecell";
  284. reg = <0xf8003000 0x1000>;
  285. interrupt-parent = <0x4>;
  286. interrupt-names = "abort", "dma0", "dma1", "dma2", "dma3", "dma4", "dma5", "dma6", "dma7";
  287. 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>;
  288. #dma-cells = <0x1>;
  289. #dma-channels = <0x8>;
  290. #dma-requests = <0x4>;
  291. clocks = <0x1 0x1b>;
  292. clock-names = "apb_pclk";
  293. };
  294. compatible = "xlnx,zynq-devcfg-1.0";
  295. interrupt-parent = <0x4>;
  296. interrupts = <0x0 0x8 0x4>;
  297. reg = <0xf8007000 0x100>;
  298. clocks = <0x1 0xc 0x1 0xf 0x1 0x10 0x1 0x11 0x1 0x12>;
  299. clock-names = "ref_clk", "fclk0", "fclk1", "fclk2", "fclk3";
  300. syscon = <0x5>;
  301. linux,phandle = <0x3>;
  302. phandle = <0x3>;
  303. };
  304. compatible = "xlnx,zynq-efuse";
  305. reg = <0xf800d000 0x20>;
  306. };
  307. compatible = "arm,cortex-a9-global-timer";
  308. reg = <0xf8f00200 0x20>;
  309. interrupts = <0x1 0xb 0x301>;
  310. interrupt-parent = <0x4>;
  311. clocks = <0x1 0x4>;
  312. };
  313. interrupt-parent = <0x4>;
  314. interrupts = <0x0 0xa 0x4 0x0 0xb 0x4 0x0 0xc 0x4>;
  315. compatible = "cdns,ttc";
  316. clocks = <0x1 0x6>;
  317. reg = <0xf8001000 0x1000>;
  318. };
  319. interrupt-parent = <0x4>;
  320. interrupts = <0x0 0x25 0x4 0x0 0x26 0x4 0x0 0x27 0x4>;
  321. compatible = "cdns,ttc";
  322. clocks = <0x1 0x6>;
  323. reg = <0xf8002000 0x1000>;
  324. };
  325. interrupt-parent = <0x4>;
  326. interrupts = <0x1 0xd 0x301>;
  327. compatible = "arm,cortex-a9-twd-timer";
  328. reg = <0xf8f00600 0x20>;
  329. clocks = <0x1 0x4>;
  330. };
  331. compatible = "xlnx,zynq-usb-2.20a", "chipidea,usb2";
  332. status = "disabled";
  333. clocks = <0x1 0x1c>;
  334. interrupt-parent = <0x4>;
  335. interrupts = <0x0 0x15 0x4>;
  336. reg = <0xe0002000 0x1000>;
  337. phy_type = "ulpi";
  338. };
  339. compatible = "xlnx,zynq-usb-2.20a", "chipidea,usb2";
  340. status = "disabled";
  341. clocks = <0x1 0x1d>;
  342. interrupt-parent = <0x4>;
  343. interrupts = <0x0 0x2c 0x4>;
  344. reg = <0xe0003000 0x1000>;
  345. phy_type = "ulpi";
  346. };
  347. clocks = <0x1 0x2d>;
  348. compatible = "cdns,wdt-r1p2";
  349. interrupt-parent = <0x4>;
  350. interrupts = <0x0 0x9 0x1>;
  351. reg = <0xf8005000 0x1000>;
  352. timeout-sec = <0xa>;
  353. };
  354. };
  355. amba_pl {
  356. #address-cells = <0x1>;
  357. #size-cells = <0x1>;
  358. compatible = "simple-bus";
  359. ranges;
  360. #gpio-cells = <0x2>;
  361. compatible = "xlnx,xps-gpio-1.00.a";
  362. gpio-controller;
  363. reg = <0x41200000 0x10000>;
  364. xlnx,all-inputs = <0x0>;
  365. xlnx,all-inputs-2 = <0x0>;
  366. xlnx,all-outputs = <0x1>;
  367. xlnx,all-outputs-2 = <0x0>;
  368. xlnx,dout-default = <0x0>;
  369. xlnx,dout-default-2 = <0x0>;
  370. xlnx,gpio-width = <0x8>;
  371. xlnx,gpio2-width = <0x20>;
  372. xlnx,interrupt-present = <0x0>;
  373. xlnx,is-dual = <0x0>;
  374. xlnx,tri-default = <0xffffffff>;
  375. xlnx,tri-default-2 = <0xffffffff>;
  376. };
  377. compatible="generic-uio";
  378. status="okay";
  379. interrupt-controller;
  380. interrupt-parent=<0x4>;
  381. interrupts=<0x0 0x1e 0x4>;
  382. };
  383. compatible="generic-uio";
  384. status="okay";
  385. interrupt-controller;
  386. interrupt-parent=<0x4>;
  387. interrupts=<0x0 0x1f 0x4>;
  388. };
  389. compatible="generic-uio";
  390. status="okay";
  391. interrupt-controller;
  392. interrupt-parent=<0x4>;
  393. interrupts=<0x0 0x20 0x4>;
  394. };
  395. compatible="generic-uio";
  396. status="okay";
  397. interrupt-controller;
  398. interrupt-parent=<0x4>;
  399. interrupts=<0x0 0x21 0x4>;
  400. };
  401. #address-cells = <0x3>;
  402. #interrupt-cells = <0x1>;
  403. #size-cells = <0x2>;
  404. compatible = "xlnx,axi-pcie-host-1.00.a";
  405. device_type = "pci";
  406. 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>;
  407. interrupt-map-mask = <0x0 0x0 0x0 0x7>;
  408. interrupt-parent = <0x4>;
  409. interrupts = <0x0 0x1d 0x4>;
  410. ranges = <0x2000000 0x0 0xa0000000 0xa0000000 0x0 0x20000000>;
  411. reg = <0x90000000 0x4000000>;
  412. interrupt-controller {
  413. #address-cells = <0x0>;
  414. #interrupt-cells = <0x1>;
  415. interrupt-controller;
  416. linux,phandle = <0x6>;
  417. phandle = <0x6>;
  418. };
  419. };
  420. };
  421. chosen {
  422. bootargs = "earlycon uio_pdrv_genirq.of_id=generic-uio";
  423. stdout-path = "serial0:115200n8";
  424. };
  425. aliases {
  426. ethernet0 = "/amba/[email protected]";
  427. serial0 = "/amba/[email protected]";
  428. spi0 = "/amba/[email protected]";
  429. spi1 = "/amba/[email protected]";
  430. };
  431. memory {
  432. device_type = "memory";
  433. reg = <0x0 0x40000000>;
  434. };
  435. };

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驱动代码如下:

  1. /
  2. drivers/uio/uio_pdrv_genirq.c
  3. Userspace I/O platform driver with generic IRQ handling code.
  4. Copyright (C) 2008 Magnus Damm
  5. Based on uio_pdrv.c by Uwe Kleine-Koenig,
  6. * Copyright (C) 2008 by Digi International Inc.
  7. * All rights reserved.
  8. This program is free software; you can redistribute it and/or modify it
  9. * under the terms of the GNU General Public License version 2 as published by
  10. * the Free Software Foundation.
  11. /
  12. #include <linux/platform_device.h>
  13. #include <linux/uio_driver.h>
  14. #include <linux/spinlock.h>
  15. #include <linux/bitops.h>
  16. #include <linux/module.h>
  17. #include <linux/interrupt.h>
  18. #include <linux/stringify.h>
  19. #include <linux/pm_runtime.h>
  20. #include <linux/slab.h>
  21. #include <linux/of.h>
  22. #include <linux/of_platform.h>
  23. #include <linux/of_address.h>
  24. #include "dal_kernel.h"
  25. //extern static irqreturn_t intr0_handler(int irq,void dev_id);
  26. //extern static irqreturn_t intr1_handler(int irq,void* dev_id);
  27. //extern static irqreturn_t intr2_handler(int irq,void* dev_id);
  28. //extern static irqreturn_t intr3_handler(int irq,void* dev_id);
  29. #define DRIVER_NAME "uio_pdrv_genirq"
  30. struct uio_pdrv_genirq_platdata {
  31. struct uio_info uioinfo;
  32. spinlock_t lock;
  33. unsigned long flags;
  34. struct platform_device pdev;
  35. };
  36. /* Bits in uio_pdrv_genirq_platdata.flags /
  37. enum {
  38. UIO_IRQ_DISABLED = 0,
  39. };
  40. static int uio_pdrv_genirq_open(struct uio_info *info, struct inode *inode)
  41. {
  42. struct uio_pdrv_genirq_platdata priv = info->priv;
  43. /* Wait until the Runtime PM code has woken up the device /
  44. pm_runtime_get_sync(&priv->pdev->dev);
  45. return 0;
  46. }
  47. static int uio_pdrv_genirq_release(struct uio_info *info, struct inode *inode)
  48. {
  49. struct uio_pdrv_genirq_platdata priv = info->priv;
  50. /* Tell the Runtime PM code that the device has become idle /
  51. pm_runtime_put_sync(&priv->pdev->dev);
  52. return 0;
  53. }
  54. static irqreturn_t uio_pdrv_genirq_handler(int irq, struct uio_info *dev_info)
  55. {
  56. struct uio_pdrv_genirq_platdata priv = dev_info->priv;
  57. /* Just disable the interrupt in the interrupt controller, and
  58. * remember the state so we can allow user space to enable it later.
  59. /
  60. #if 0
  61. spin_lock(&priv->lock);
  62. if (!__test_and_set_bit(UIO_IRQ_DISABLED, &priv->flags))
  63. {
  64. disable_irq_nosync(irq);
  65. }
  66. spin_unlock(&priv->lock);
  67. #endif
  68. printk(">>>>>>handle UIO Interrupt now,irq number is %d\n",irq);
  69. #if 1
  70. //get irq related handler
  71. if(irq==47)
  72. {
  73. printk("Trigger interrupts 47\n");
  74. intr0_handler(47,NULL);
  75. }
  76. if(irq==48)
  77. {
  78. printk("Trigger interrupts 48\n");
  79. intr1_handler(48,NULL);
  80. }
  81. if(irq==49)
  82. {
  83. printk("Trigger interrupts 49\n");
  84. intr2_handler(49,NULL);
  85. }
  86. if(irq==50)
  87. {
  88. printk("Trigger interrupts 50\n");
  89. intr3_handler(50,NULL);
  90. }
  91. #endif
  92. return IRQ_HANDLED;
  93. }
  94. static int uio_pdrv_genirq_irqcontrol(struct uio_info *dev_info, s32 irq_on)
  95. {
  96. struct uio_pdrv_genirq_platdata priv = dev_info->priv;
  97. unsigned long flags;
  98. /* Allow user space to enable and disable the interrupt
  99. * in the interrupt controller, but keep track of the
  100. * state to prevent per-irq depth damage.
  101. Serialize this operation to support multiple tasks and concurrency
  102. * with irq handler on SMP systems.
  103. /
  104. spin_lock_irqsave(&priv->lock, flags);
  105. if (irq_on) {
  106. if (__test_and_clear_bit(UIO_IRQ_DISABLED, &priv->flags))
  107. enable_irq(dev_info->irq);
  108. } else {
  109. if (!__test_and_set_bit(UIO_IRQ_DISABLED, &priv->flags))
  110. disable_irq_nosync(dev_info->irq);
  111. }
  112. spin_unlock_irqrestore(&priv->lock, flags);
  113. return 0;
  114. }
  115. static int uio_pdrv_genirq_probe(struct platform_device *pdev)
  116. {
  117. struct uio_info uioinfo = dev_get_platdata(&pdev->dev);
  118. struct uio_pdrv_genirq_platdata priv;
  119. struct uio_mem uiomem;
  120. int ret = -EINVAL;
  121. int i;
  122. if (pdev->dev.of_node) {
  123. /* alloc uioinfo for one device /
  124. uioinfo = devm_kzalloc(&pdev->dev, sizeof(*uioinfo),
  125. GFP_KERNEL);
  126. if (!uioinfo) {
  127. dev_err(&pdev->dev, "unable to kmalloc\n");
  128. return -ENOMEM;
  129. }
  130. uioinfo->name = pdev->dev.of_node->name;
  131. uioinfo->version = "devicetree";
  132. / Multiple IRQs are not supported /
  133. }
  134. if (!uioinfo || !uioinfo->name || !uioinfo->version) {
  135. dev_err(&pdev->dev, "missing platform_data\n");
  136. return ret;
  137. }
  138. if (uioinfo->handler || uioinfo->irqcontrol ||
  139. uioinfo->irq_flags & IRQF_SHARED) {
  140. dev_err(&pdev->dev, "interrupt configuration error\n");
  141. return ret;
  142. }
  143. priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
  144. if (!priv) {
  145. dev_err(&pdev->dev, "unable to kmalloc\n");
  146. return -ENOMEM;
  147. }
  148. priv->uioinfo = uioinfo;
  149. spin_lock_init(&priv->lock);
  150. priv->flags = 0; / interrupt is enabled to begin with /
  151. priv->pdev = pdev;
  152. if (!uioinfo->irq) {
  153. ret = platform_get_irq(pdev, 0);
  154. uioinfo->irq = ret;
  155. if (ret == -ENXIO && pdev->dev.of_node)
  156. uioinfo->irq = UIO_IRQ_NONE;
  157. else if (ret < 0) {
  158. dev_err(&pdev->dev, "failed to get IRQ\n");
  159. return ret;
  160. }
  161. }
  162. uiomem = &uioinfo->mem[0];
  163. for (i = 0; i < pdev->num_resources; ++i) {
  164. struct resource r = &pdev->resource[i];
  165. if (r->flags != IORESOURCE_MEM)
  166. continue;
  167. if (uiomem >= &uioinfo->mem[MAX_UIO_MAPS]) {
  168. dev_warn(&pdev->dev, "device has more than "
  169. __stringify(MAX_UIO_MAPS)
  170. " I/O memory resources.\n");
  171. break;
  172. }
  173. uiomem->memtype = UIO_MEM_PHYS;
  174. uiomem->addr = r->start;
  175. uiomem->size = resource_size(r);
  176. uiomem->name = r->name;
  177. ++uiomem;
  178. }
  179. while (uiomem < &uioinfo->mem[MAX_UIO_MAPS]) {
  180. uiomem->size = 0;
  181. ++uiomem;
  182. }
  183. /* This driver requires no hardware specific kernel code to handle
  184. * interrupts. Instead, the interrupt handler simply disables the
  185. * interrupt in the interrupt controller. User space is responsible
  186. * for performing hardware specific acknowledge and re-enabling of
  187. * the interrupt in the interrupt controller.
  188. Interrupt sharing is not supported.
  189. /
  190. uioinfo->handler = uio_pdrv_genirq_handler;
  191. uioinfo->irqcontrol = uio_pdrv_genirq_irqcontrol;
  192. uioinfo->open = uio_pdrv_genirq_open;
  193. uioinfo->release = uio_pdrv_genirq_release;
  194. uioinfo->priv = priv;
  195. / Enable Runtime PM for this device:
  196. * The device starts in suspended state to allow the hardware to be
  197. * turned off by default. The Runtime PM bus code should power on the
  198. * hardware and enable clocks at open().
  199. /
  200. pm_runtime_enable(&pdev->dev);
  201. ret = uio_register_device(&pdev->dev, priv->uioinfo);
  202. if (ret) {
  203. dev_err(&pdev->dev, "unable to register uio device\n");
  204. pm_runtime_disable(&pdev->dev);
  205. return ret;
  206. }
  207. platform_set_drvdata(pdev, priv);
  208. return 0;
  209. }
  210. static int uio_pdrv_genirq_remove(struct platform_device *pdev)
  211. {
  212. struct uio_pdrv_genirq_platdata priv = platform_get_drvdata(pdev);
  213. uio_unregister_device(priv->uioinfo);
  214. pm_runtime_disable(&pdev->dev);
  215. priv->uioinfo->handler = NULL;
  216. priv->uioinfo->irqcontrol = NULL;
  217. return 0;
  218. }
  219. static int uio_pdrv_genirq_runtime_nop(struct device dev)
  220. {
  221. / Runtime PM callback shared between ->runtime_suspend()
  222. * and ->runtime_resume(). Simply returns success.
  223. In this driver pm_runtime_get_sync() and pm_runtime_put_sync()
  224. * are used at open() and release() time. This allows the
  225. * Runtime PM code to turn off power to the device while the
  226. * device is unused, ie before open() and after release().
  227. This Runtime PM callback does not need to save or restore
  228. * any registers since user space is responsbile for hardware
  229. * register reinitialization after open().
  230. /
  231. return 0;
  232. }
  233. static const struct dev_pm_ops uio_pdrv_genirq_dev_pm_ops = {
  234. .runtime_suspend = uio_pdrv_genirq_runtime_nop,
  235. .runtime_resume = uio_pdrv_genirq_runtime_nop,
  236. };
  237. #ifdef CONFIG_OF
  238. static struct of_device_id uio_of_genirq_match[] = {
  239. { / This is filled with module_parm / },
  240. { / Sentinel */ },
  241. };
  242. MODULE_DEVICE_TABLE(of, uio_of_genirq_match);
  243. module_param_string(of_id, uio_of_genirq_match[0].compatible, 128, 0);
  244. MODULE_PARM_DESC(of_id, "Openfirmware id of the device to be handled by uio");
  245. #endif
  246. static struct platform_driver uio_pdrv_genirq = {
  247. .probe = uio_pdrv_genirq_probe,
  248. .remove = uio_pdrv_genirq_remove,
  249. .driver = {
  250. .name = DRIVER_NAME,
  251. .pm = &uio_pdrv_genirq_dev_pm_ops,
  252. .of_match_table = of_match_ptr(uio_of_genirq_match),
  253. },
  254. };
  255. module_platform_driver(uio_pdrv_genirq);
  256. MODULE_AUTHOR("Magnus Damm");
  257. MODULE_DESCRIPTION("Userspace I/O platform driver with generic IRQ handling");
  258. MODULE_LICENSE("GPL v2");
  259. 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号中断时会出错。