From cdb84bd0430aa3220ce2791377724b1324c2fea9 Mon Sep 17 00:00:00 2001 From: Atmaram Date: Wed, 2 Aug 2017 17:00:51 +0530 Subject: [PATCH] VFIO and MMU support LS2088ARDB Signed-off-by: Atmaram --- .../configs/arm64-all/arm64-all.config | 7 +- ...-set-for-booting-ls2088rdb-with-vfio.patch | 101360 +++++++++++++++ .../3.18.25/patches/ls2_mc_console.patch | 56 +- .../any/kernels/3.18.25/patches/series.arm64 | 7 +- 4 files changed, 101403 insertions(+), 27 deletions(-) create mode 100644 packages/base/any/kernels/3.18.25/patches/0001-Patch-set-for-booting-ls2088rdb-with-vfio.patch diff --git a/packages/base/any/kernels/3.18.25/configs/arm64-all/arm64-all.config b/packages/base/any/kernels/3.18.25/configs/arm64-all/arm64-all.config index 4c80980e..a023c63c 100644 --- a/packages/base/any/kernels/3.18.25/configs/arm64-all/arm64-all.config +++ b/packages/base/any/kernels/3.18.25/configs/arm64-all/arm64-all.config @@ -2302,8 +2302,13 @@ CONFIG_DMA_OF=y # CONFIG_DMATEST is not set # CONFIG_AUXDISPLAY is not set # CONFIG_UIO is not set -# CONFIG_VFIO is not set # CONFIG_VIRT_DRIVERS is not set + +CONFIG_VFIO_IOMMU_TYPE1=y +CONFIG_VFIO=y +CONFIG_VFIO_PCI=y +CONFIG_VFIO_FSL_MC=y + CONFIG_VIRTIO=y # diff --git a/packages/base/any/kernels/3.18.25/patches/0001-Patch-set-for-booting-ls2088rdb-with-vfio.patch b/packages/base/any/kernels/3.18.25/patches/0001-Patch-set-for-booting-ls2088rdb-with-vfio.patch new file mode 100644 index 00000000..ad0e4a06 --- /dev/null +++ b/packages/base/any/kernels/3.18.25/patches/0001-Patch-set-for-booting-ls2088rdb-with-vfio.patch @@ -0,0 +1,101360 @@ +From 7ab86f28bfb4d36d4d741a41941a0aa971124d88 Mon Sep 17 00:00:00 2001 +From: "Chenyin.Ha" +Date: Fri, 19 May 2017 13:44:09 +0800 +Subject: [PATCH] Patch set for booting ls2088rdb with vfio + +--- + Documentation/IRQ-domain.txt | 71 + + Documentation/devicetree/bindings/arm/fsl.txt | 15 + + Documentation/devicetree/bindings/arm/gic.txt | 8 +- + .../devicetree/bindings/clock/qoriq-clock.txt | 64 +- + Documentation/devicetree/bindings/i2c/i2c-imx.txt | 11 + + .../devicetree/bindings/i2c/i2c-mux-pca954x.txt | 3 + + .../bindings/memory-controllers/fsl/ifc.txt | 3 + + .../devicetree/bindings/pci/designware-pcie.txt | 3 +- + .../devicetree/bindings/powerpc/fsl/board.txt | 14 +- + Documentation/devicetree/bindings/usb/dwc3.txt | 3 +- + Documentation/devicetree/of_selftest.txt | 20 +- + Documentation/devicetree/todo.txt | 1 - + MAINTAINERS | 60 + + arch/arm/Kconfig | 3 + + arch/arm/Makefile | 8 +- + arch/arm/boot/dts/Makefile | 12 +- + arch/arm/include/asm/dma-mapping.h | 10 +- + arch/arm/include/asm/mach/pci.h | 12 +- + arch/arm/include/asm/pci.h | 7 - + arch/arm/kernel/bios32.c | 39 +- + arch/arm/mach-iop13xx/msi.c | 10 +- + arch/arm64/Kconfig | 8 +- + arch/arm64/Makefile | 11 +- + arch/arm64/boot/dts/Makefile | 2 +- + arch/arm64/boot/dts/Makefile.rej | 10 + + arch/arm64/boot/dts/arm64-nxp-ls2080ardb-r0.dts | 249 ++ + arch/arm64/boot/dts/arm64-nxp-ls2088ardb-r1.dts | 256 ++ + arch/arm64/boot/dts/fsl-ls2080a.dtsi | 729 +++++ + arch/arm64/boot/dts/fsl-ls2088a.dtsi | 833 ++++++ + arch/arm64/boot/dts/include/dt-bindings | 1 + + arch/arm64/boot/dts/thermal.h | 17 + + arch/arm64/configs/defconfig | 1 + + arch/arm64/configs/nxp_ls2088rdb_config | 3034 ++++++++++++++++++++ + arch/arm64/include/asm/device.h | 1 + + arch/arm64/include/asm/dma-mapping.h | 16 +- + arch/arm64/include/asm/io.h | 1 + + arch/arm64/include/asm/mmu_context.h | 43 + + arch/arm64/include/asm/page.h | 6 +- + arch/arm64/include/asm/pgtable-hwdef.h | 7 +- + arch/arm64/include/asm/pgtable.h | 8 + + arch/arm64/kernel/head.S | 37 + + arch/arm64/kernel/smp.c | 1 + + arch/arm64/mm/mmu.c | 7 +- + arch/arm64/mm/proc-macros.S | 10 + + arch/arm64/mm/proc.S | 3 + + arch/ia64/kernel/msi_ia64.c | 8 +- + arch/ia64/sn/kernel/msi_sn.c | 8 +- + arch/mips/pci/msi-octeon.c | 2 +- + arch/mips/pci/msi-xlp.c | 12 +- + arch/mips/pci/pci-xlr.c | 2 +- + arch/powerpc/include/asm/mpc85xx.h | 94 - + arch/powerpc/platforms/512x/mpc5121_ads_cpld.c | 3 +- + arch/powerpc/platforms/85xx/mpc85xx_mds.c | 2 +- + arch/powerpc/platforms/85xx/mpc85xx_rdb.c | 2 +- + arch/powerpc/platforms/85xx/p1022_ds.c | 2 +- + arch/powerpc/platforms/85xx/p1022_rdk.c | 2 +- + arch/powerpc/platforms/85xx/smp.c | 2 +- + arch/powerpc/platforms/85xx/twr_p102x.c | 2 +- + arch/powerpc/platforms/86xx/mpc8610_hpcd.c | 2 +- + arch/powerpc/platforms/cell/axon_msi.c | 8 +- + arch/powerpc/platforms/cell/interrupt.c | 3 +- + arch/powerpc/platforms/embedded6xx/flipper-pic.c | 3 +- + arch/powerpc/platforms/powermac/pic.c | 3 +- + arch/powerpc/platforms/powernv/pci.c | 2 +- + arch/powerpc/platforms/ps3/interrupt.c | 3 +- + arch/powerpc/platforms/pseries/msi.c | 2 +- + arch/powerpc/sysdev/ehv_pic.c | 3 +- + arch/powerpc/sysdev/fsl_msi.c | 6 +- + arch/powerpc/sysdev/i8259.c | 3 +- + arch/powerpc/sysdev/ipic.c | 3 +- + arch/powerpc/sysdev/mpic.c | 3 +- + arch/powerpc/sysdev/mpic_pasemi_msi.c | 6 +- + arch/powerpc/sysdev/mpic_u3msi.c | 6 +- + arch/powerpc/sysdev/ppc4xx_hsta_msi.c | 2 +- + arch/powerpc/sysdev/ppc4xx_msi.c | 2 +- + arch/powerpc/sysdev/qe_lib/qe_ic.c | 3 +- + arch/powerpc/sysdev/xics/ics-opal.c | 2 +- + arch/powerpc/sysdev/xics/ics-rtas.c | 2 +- + arch/powerpc/sysdev/xics/xics-common.c | 3 +- + arch/s390/pci/pci.c | 10 +- + arch/sparc/kernel/pci_msi.c | 10 +- + arch/tile/kernel/pci_gx.c | 8 +- + arch/x86/include/asm/x86_init.h | 3 - + arch/x86/kernel/apic/io_apic.c | 8 +- + arch/x86/kernel/x86_init.c | 10 - + arch/x86/pci/bus_numa.c | 4 +- + arch/x86/pci/xen.c | 23 +- + drivers/acpi/acpi_lpss.c | 8 +- + drivers/acpi/acpi_platform.c | 4 +- + drivers/acpi/resource.c | 17 +- + drivers/base/core.c | 3 + + drivers/base/platform.c | 1 + + drivers/block/loop.c | 18 + + drivers/clk/Kconfig | 10 +- + drivers/clk/Makefile | 2 +- + drivers/clk/clk-qoriq.c | 1256 ++++++++ + drivers/cpufreq/Kconfig.powerpc | 2 +- + drivers/dma/acpi-dma.c | 10 +- + drivers/i2c/busses/Kconfig | 4 +- + drivers/i2c/busses/i2c-imx.c | 373 ++- + drivers/i2c/muxes/i2c-mux-pca9541.c | 4 +- + drivers/i2c/muxes/i2c-mux-pca954x.c | 57 +- + drivers/iommu/Kconfig | 34 +- + drivers/iommu/Makefile | 2 + + drivers/iommu/amd_iommu.c | 6 +- + drivers/iommu/arm-smmu.c | 1382 +++++---- + drivers/iommu/exynos-iommu.c | 2 +- + drivers/iommu/fsl_pamu.c | 3 +- + drivers/iommu/intel-iommu.c | 1 + + drivers/iommu/io-pgtable-arm.c | 997 +++++++ + drivers/iommu/io-pgtable.c | 82 + + drivers/iommu/io-pgtable.h | 143 + + drivers/iommu/iommu.c | 111 +- + drivers/iommu/ipmmu-vmsa.c | 2 +- + drivers/iommu/irq_remapping.c | 8 - + drivers/iommu/msm_iommu.c | 1 + + drivers/iommu/of_iommu.c | 95 + + drivers/iommu/omap-iommu.c | 1 + + drivers/iommu/shmobile-iommu.c | 1 + + drivers/iommu/shmobile-ipmmu.c | 1 - + drivers/iommu/tegra-gart.c | 1 - + drivers/iommu/tegra-smmu.c | 2 +- + drivers/irqchip/Kconfig | 12 + + drivers/irqchip/Makefile | 2 + + drivers/irqchip/irq-armada-370-xp.c | 16 +- + drivers/irqchip/irq-atmel-aic.c | 40 +- + drivers/irqchip/irq-atmel-aic5.c | 65 +- + drivers/irqchip/irq-gic-common.c | 18 +- + drivers/irqchip/irq-gic-common.h | 2 +- + drivers/irqchip/irq-gic-v2m.c | 333 +++ + drivers/irqchip/irq-gic-v3-its.c | 1630 +++++++++++ + drivers/irqchip/irq-gic-v3.c | 180 +- + drivers/irqchip/irq-gic.c | 90 +- + drivers/irqchip/irq-hip04.c | 9 +- + drivers/irqchip/irq-sunxi-nmi.c | 4 +- + drivers/irqchip/irq-tb10x.c | 4 +- + drivers/memory/Kconfig | 2 +- + drivers/memory/fsl_ifc.c | 77 +- + drivers/mfd/vexpress-sysreg.c | 2 +- + drivers/mmc/card/block.c | 4 + + drivers/mmc/host/Kconfig | 10 +- + drivers/mmc/host/sdhci-esdhc.h | 9 +- + drivers/mmc/host/sdhci-of-esdhc.c | 680 ++++- + drivers/mmc/host/sdhci.c | 250 +- + drivers/mmc/host/sdhci.h | 42 + + drivers/mtd/nand/Kconfig | 2 +- + drivers/mtd/nand/fsl_ifc_nand.c | 301 +- + drivers/net/ethernet/freescale/Kconfig | 8 +- + drivers/net/ethernet/freescale/fec_mpc52xx.c | 2 +- + drivers/net/ethernet/freescale/fec_mpc52xx_phy.c | 2 +- + .../net/ethernet/freescale/fs_enet/fs_enet-main.c | 4 +- + .../net/ethernet/freescale/fs_enet/mii-bitbang.c | 2 +- + drivers/net/ethernet/freescale/fs_enet/mii-fec.c | 4 +- + drivers/net/ethernet/freescale/fsl_pq_mdio.c | 2 +- + drivers/net/ethernet/freescale/gianfar.c | 8 +- + drivers/net/ethernet/freescale/gianfar_ptp.c | 2 +- + drivers/net/ethernet/freescale/ucc_geth.c | 2 +- + drivers/net/ethernet/freescale/xgmac_mdio.c | 194 +- + drivers/net/ethernet/intel/igb/e1000_82575.c | 6 + + drivers/net/ethernet/intel/igb/e1000_defines.h | 1 + + drivers/net/ethernet/intel/igb/e1000_hw.h | 1 + + drivers/net/ethernet/intel/igb/igb_main.c | 1 + + drivers/net/phy/Kconfig | 19 +- + drivers/net/phy/Makefile | 5 +- + drivers/net/phy/aquantia.c | 201 ++ + drivers/net/phy/at803x.c | 4 + + drivers/net/phy/fixed.c | 336 --- + drivers/net/phy/fixed_phy.c | 370 +++ + drivers/net/phy/fsl_10gkr.c | 1467 ++++++++++ + drivers/net/phy/marvell.c | 11 + + drivers/net/phy/mdio_bus.c | 34 +- + drivers/net/phy/phy.c | 19 +- + drivers/net/phy/phy_device.c | 90 +- + drivers/net/phy/realtek.c | 82 +- + drivers/net/phy/teranetics.c | 135 + + drivers/of/base.c | 53 +- + drivers/of/device.c | 84 + + drivers/of/dynamic.c | 13 - + drivers/of/fdt.c | 30 +- + drivers/of/irq.c | 21 + + drivers/of/of_pci.c | 34 +- + drivers/of/pdt.c | 27 +- + drivers/of/platform.c | 139 +- + drivers/of/selftest.c | 71 +- + drivers/pci/Kconfig | 6 + + drivers/pci/Makefile | 1 + + drivers/pci/access.c | 87 + + drivers/pci/bus.c | 18 +- + drivers/pci/host-bridge.c | 22 +- + drivers/pci/host/Kconfig | 19 +- + drivers/pci/host/Makefile | 3 + + drivers/pci/host/pci-dra7xx.c | 8 +- + drivers/pci/host/pci-exynos.c | 5 +- + drivers/pci/host/pci-host-generic.c | 229 +- + drivers/pci/host/pci-keystone-dw.c | 37 +- + drivers/pci/host/pci-keystone.h | 4 +- + drivers/pci/host/pci-layerscape.c | 729 +++++ + drivers/pci/host/pci-layerscape.h | 13 + + drivers/pci/host/pci-mvebu.c | 17 +- + drivers/pci/host/pci-tegra.c | 22 +- + drivers/pci/host/pci-xgene-msi.c | 595 ++++ + drivers/pci/host/pci-xgene.c | 25 +- + drivers/pci/host/pcie-designware.c | 665 ++--- + drivers/pci/host/pcie-designware.h | 24 +- + drivers/pci/host/pcie-rcar.c | 22 +- + drivers/pci/host/pcie-xilinx.c | 64 +- + drivers/pci/msi.c | 533 ++-- + drivers/pci/pci.c | 1 + + drivers/pci/pci.h | 21 + + drivers/pci/pcie/portdrv_core.c | 31 +- + drivers/pci/probe.c | 29 +- + drivers/pci/quirks.c | 10 +- + drivers/pci/remove.c | 2 + + drivers/pci/search.c | 5 +- + drivers/pci/setup-bus.c | 1 + + drivers/pci/setup-irq.c | 1 + + drivers/pci/xen-pcifront.c | 2 +- + drivers/power/reset/Kconfig | 6 + + drivers/power/reset/Makefile | 1 + + drivers/power/reset/ls-reboot.c | 93 + + drivers/soc/Kconfig | 13 + + drivers/soc/Makefile | 1 + + drivers/soc/fsl/Kconfig | 6 + + drivers/soc/fsl/Kconfig.arm | 25 + + drivers/soc/fsl/Makefile | 6 + + drivers/soc/fsl/guts.c | 123 + + drivers/soc/fsl/ls1/Kconfig | 11 + + drivers/soc/fsl/ls1/Makefile | 1 + + drivers/soc/fsl/ls1/ftm_alarm.c | 274 ++ + drivers/staging/Kconfig | 4 + + drivers/staging/Makefile | 2 + + drivers/staging/fsl-dpaa2/Kconfig | 12 + + drivers/staging/fsl-dpaa2/Makefile | 6 + + drivers/staging/fsl-dpaa2/ethernet/Kconfig | 36 + + drivers/staging/fsl-dpaa2/ethernet/Makefile | 21 + + .../staging/fsl-dpaa2/ethernet/dpaa2-eth-debugfs.c | 317 ++ + .../staging/fsl-dpaa2/ethernet/dpaa2-eth-debugfs.h | 61 + + .../staging/fsl-dpaa2/ethernet/dpaa2-eth-trace.h | 185 ++ + drivers/staging/fsl-dpaa2/ethernet/dpaa2-eth.c | 2957 +++++++++++++++++++ + drivers/staging/fsl-dpaa2/ethernet/dpaa2-eth.h | 397 +++ + drivers/staging/fsl-dpaa2/ethernet/dpaa2-ethtool.c | 732 +++++ + drivers/staging/fsl-dpaa2/ethernet/dpkg.h | 175 ++ + drivers/staging/fsl-dpaa2/ethernet/dpni-cmd.h | 1058 +++++++ + drivers/staging/fsl-dpaa2/ethernet/dpni.c | 1907 ++++++++++++ + drivers/staging/fsl-dpaa2/ethernet/dpni.h | 2581 +++++++++++++++++ + drivers/staging/fsl-dpaa2/mac/Kconfig | 24 + + drivers/staging/fsl-dpaa2/mac/Makefile | 10 + + drivers/staging/fsl-dpaa2/mac/dpmac-cmd.h | 195 ++ + drivers/staging/fsl-dpaa2/mac/dpmac.c | 422 +++ + drivers/staging/fsl-dpaa2/mac/dpmac.h | 593 ++++ + drivers/staging/fsl-dpaa2/mac/mac.c | 694 +++++ + drivers/staging/fsl-mc/Kconfig | 1 + + drivers/staging/fsl-mc/Makefile | 2 + + drivers/staging/fsl-mc/TODO | 13 + + drivers/staging/fsl-mc/bus/Kconfig | 45 + + drivers/staging/fsl-mc/bus/Makefile | 24 + + drivers/staging/fsl-mc/bus/dpbp.c | 459 +++ + drivers/staging/fsl-mc/bus/dpcon.c | 407 +++ + drivers/staging/fsl-mc/bus/dpio/Makefile | 9 + + drivers/staging/fsl-mc/bus/dpio/dpio-drv.c | 401 +++ + drivers/staging/fsl-mc/bus/dpio/dpio-drv.h | 33 + + drivers/staging/fsl-mc/bus/dpio/dpio.c | 468 +++ + drivers/staging/fsl-mc/bus/dpio/dpio_service.c | 801 ++++++ + drivers/staging/fsl-mc/bus/dpio/fsl_dpio.h | 460 +++ + drivers/staging/fsl-mc/bus/dpio/fsl_dpio_cmd.h | 184 ++ + drivers/staging/fsl-mc/bus/dpio/fsl_qbman_base.h | 123 + + drivers/staging/fsl-mc/bus/dpio/fsl_qbman_portal.h | 753 +++++ + drivers/staging/fsl-mc/bus/dpio/qbman_debug.c | 846 ++++++ + drivers/staging/fsl-mc/bus/dpio/qbman_debug.h | 136 + + drivers/staging/fsl-mc/bus/dpio/qbman_portal.c | 1212 ++++++++ + drivers/staging/fsl-mc/bus/dpio/qbman_portal.h | 261 ++ + drivers/staging/fsl-mc/bus/dpio/qbman_private.h | 173 ++ + drivers/staging/fsl-mc/bus/dpio/qbman_sys.h | 307 ++ + drivers/staging/fsl-mc/bus/dpio/qbman_sys_decl.h | 86 + + drivers/staging/fsl-mc/bus/dpio/qbman_test.c | 664 +++++ + drivers/staging/fsl-mc/bus/dpmcp-cmd.h | 56 + + drivers/staging/fsl-mc/bus/dpmcp.c | 318 ++ + drivers/staging/fsl-mc/bus/dpmcp.h | 323 +++ + drivers/staging/fsl-mc/bus/dpmng-cmd.h | 47 + + drivers/staging/fsl-mc/bus/dpmng.c | 85 + + drivers/staging/fsl-mc/bus/dprc-cmd.h | 87 + + drivers/staging/fsl-mc/bus/dprc-driver.c | 1084 +++++++ + drivers/staging/fsl-mc/bus/dprc.c | 1218 ++++++++ + drivers/staging/fsl-mc/bus/mc-allocator.c | 716 +++++ + drivers/staging/fsl-mc/bus/mc-bus.c | 1347 +++++++++ + drivers/staging/fsl-mc/bus/mc-ioctl.h | 25 + + drivers/staging/fsl-mc/bus/mc-restool.c | 312 ++ + drivers/staging/fsl-mc/bus/mc-sys.c | 677 +++++ + drivers/staging/fsl-mc/include/dpbp-cmd.h | 62 + + drivers/staging/fsl-mc/include/dpbp.h | 438 +++ + drivers/staging/fsl-mc/include/dpcon-cmd.h | 162 ++ + drivers/staging/fsl-mc/include/dpcon.h | 407 +++ + drivers/staging/fsl-mc/include/dpmac-cmd.h | 192 ++ + drivers/staging/fsl-mc/include/dpmac.h | 528 ++++ + drivers/staging/fsl-mc/include/dpmng.h | 80 + + drivers/staging/fsl-mc/include/dprc.h | 990 +++++++ + drivers/staging/fsl-mc/include/fsl_dpaa2_fd.h | 774 +++++ + drivers/staging/fsl-mc/include/fsl_dpaa2_io.h | 619 ++++ + drivers/staging/fsl-mc/include/mc-cmd.h | 133 + + drivers/staging/fsl-mc/include/mc-private.h | 168 ++ + drivers/staging/fsl-mc/include/mc-sys.h | 128 + + drivers/staging/fsl-mc/include/mc.h | 244 ++ + drivers/staging/fsl-mc/include/net.h | 481 ++++ + drivers/usb/core/config.c | 3 +- + drivers/usb/core/driver.c | 6 +- + drivers/usb/core/hcd-pci.c | 9 + + drivers/usb/core/hub.c | 66 +- + drivers/usb/core/quirks.c | 6 + + drivers/usb/dwc3/core.c | 76 +- + drivers/usb/dwc3/core.h | 8 + + drivers/usb/dwc3/host.c | 6 + + drivers/usb/host/xhci-pci.c | 114 +- + drivers/usb/host/xhci-ring.c | 6 +- + drivers/usb/host/xhci.c | 34 +- + drivers/usb/host/xhci.h | 3 + + drivers/vfio/Kconfig | 5 +- + drivers/vfio/Makefile | 1 + + drivers/vfio/fsl-mc/Kconfig | 9 + + drivers/vfio/fsl-mc/Makefile | 2 + + drivers/vfio/fsl-mc/vfio_fsl_mc.c | 603 ++++ + drivers/vfio/fsl-mc/vfio_fsl_mc_intr.c | 273 ++ + drivers/vfio/fsl-mc/vfio_fsl_mc_private.h | 43 + + drivers/vfio/pci/vfio_pci_intrs.c | 2 +- + drivers/vfio/vfio_iommu_type1.c | 5 +- + fs/Kconfig | 1 + + fs/Makefile | 1 + + fs/aufs/Kconfig | 185 ++ + fs/aufs/Makefile | 44 + + fs/aufs/aufs.h | 59 + + fs/aufs/branch.c | 1402 +++++++++ + fs/aufs/branch.h | 279 ++ + fs/aufs/conf.mk | 38 + + fs/aufs/cpup.c | 1368 +++++++++ + fs/aufs/cpup.h | 94 + + fs/aufs/dbgaufs.c | 432 +++ + fs/aufs/dbgaufs.h | 48 + + fs/aufs/dcsub.c | 224 ++ + fs/aufs/dcsub.h | 123 + + fs/aufs/debug.c | 436 +++ + fs/aufs/debug.h | 228 ++ + fs/aufs/dentry.c | 1129 ++++++++ + fs/aufs/dentry.h | 234 ++ + fs/aufs/dinfo.c | 544 ++++ + fs/aufs/dir.c | 756 +++++ + fs/aufs/dir.h | 131 + + fs/aufs/dynop.c | 379 +++ + fs/aufs/dynop.h | 76 + + fs/aufs/export.c | 831 ++++++ + fs/aufs/f_op.c | 781 +++++ + fs/aufs/fhsm.c | 426 +++ + fs/aufs/file.c | 857 ++++++ + fs/aufs/file.h | 291 ++ + fs/aufs/finfo.c | 156 + + fs/aufs/fstype.h | 400 +++ + fs/aufs/hfsnotify.c | 288 ++ + fs/aufs/hfsplus.c | 56 + + fs/aufs/hnotify.c | 714 +++++ + fs/aufs/i_op.c | 1460 ++++++++++ + fs/aufs/i_op_add.c | 930 ++++++ + fs/aufs/i_op_del.c | 506 ++++ + fs/aufs/i_op_ren.c | 1013 +++++++ + fs/aufs/iinfo.c | 277 ++ + fs/aufs/inode.c | 522 ++++ + fs/aufs/inode.h | 686 +++++ + fs/aufs/ioctl.c | 219 ++ + fs/aufs/loop.c | 146 + + fs/aufs/loop.h | 52 + + fs/aufs/magic.mk | 30 + + fs/aufs/module.c | 222 ++ + fs/aufs/module.h | 105 + + fs/aufs/mvdown.c | 703 +++++ + fs/aufs/opts.c | 1878 ++++++++++++ + fs/aufs/opts.h | 212 ++ + fs/aufs/plink.c | 506 ++++ + fs/aufs/poll.c | 52 + + fs/aufs/posix_acl.c | 98 + + fs/aufs/procfs.c | 169 ++ + fs/aufs/rdu.c | 388 +++ + fs/aufs/rwsem.h | 191 ++ + fs/aufs/sbinfo.c | 348 +++ + fs/aufs/spl.h | 111 + + fs/aufs/super.c | 1041 +++++++ + fs/aufs/super.h | 626 ++++ + fs/aufs/sysaufs.c | 104 + + fs/aufs/sysaufs.h | 101 + + fs/aufs/sysfs.c | 376 +++ + fs/aufs/sysrq.c | 157 + + fs/aufs/vdir.c | 888 ++++++ + fs/aufs/vfsub.c | 864 ++++++ + fs/aufs/vfsub.h | 315 ++ + fs/aufs/wbr_policy.c | 765 +++++ + fs/aufs/whout.c | 1061 +++++++ + fs/aufs/whout.h | 85 + + fs/aufs/wkq.c | 213 ++ + fs/aufs/wkq.h | 91 + + fs/aufs/xattr.c | 344 +++ + fs/aufs/xino.c | 1343 +++++++++ + fs/buffer.c | 2 +- + fs/dcache.c | 2 +- + fs/fcntl.c | 4 +- + fs/inode.c | 2 +- + fs/proc/base.c | 2 +- + fs/proc/nommu.c | 5 +- + fs/proc/task_mmu.c | 7 +- + fs/proc/task_nommu.c | 5 +- + fs/splice.c | 10 +- + include/asm-generic/msi.h | 32 + + include/asm-generic/vmlinux.lds.h | 2 + + include/linux/acpi.h | 6 +- + include/linux/device.h | 24 + + include/linux/dma-mapping.h | 13 +- + include/linux/file.h | 1 + + include/linux/fs.h | 3 + + include/linux/fsl/guts.h | 195 ++ + include/linux/fsl/svr.h | 95 + + include/linux/fsl_ifc.h | 116 +- + include/linux/interrupt.h | 14 + + include/linux/iommu.h | 76 +- + include/linux/iopoll.h | 144 + + include/linux/irq.h | 75 +- + include/linux/irqchip/arm-gic-v3.h | 165 ++ + include/linux/irqchip/arm-gic.h | 2 + + include/linux/irqdomain.h | 127 +- + include/linux/irqhandler.h | 14 + + include/linux/mm.h | 22 + + include/linux/mm_types.h | 2 + + include/linux/mmc/sdhci.h | 16 +- + include/linux/msi.h | 199 +- + include/linux/of.h | 11 +- + include/linux/of_device.h | 3 + + include/linux/of_iommu.h | 25 + + include/linux/of_irq.h | 1 + + include/linux/of_pci.h | 15 +- + include/linux/of_pdt.h | 3 +- + include/linux/of_platform.h | 6 + + include/linux/pci.h | 32 +- + include/linux/phy.h | 1 + + include/linux/phy_fixed.h | 11 +- + include/linux/resource_ext.h | 77 + + include/linux/splice.h | 6 + + include/linux/usb/quirks.h | 3 + + include/trace/events/iommu.h | 31 +- + include/uapi/linux/Kbuild | 1 + + include/uapi/linux/aufs_type.h | 419 +++ + include/uapi/linux/vfio.h | 5 + + kernel/fork.c | 2 +- + kernel/irq/Kconfig | 15 + + kernel/irq/Makefile | 1 + + kernel/irq/chip.c | 163 +- + kernel/irq/generic-chip.c | 36 +- + kernel/irq/irqdomain.c | 585 +++- + kernel/irq/manage.c | 93 + + kernel/irq/msi.c | 356 +++ + kernel/resource.c | 25 + + mm/Makefile | 2 +- + mm/filemap.c | 2 +- + mm/fremap.c | 16 +- + mm/memory.c | 2 +- + mm/mmap.c | 12 +- + mm/nommu.c | 10 +- + mm/prfile.c | 86 + + scripts/Kbuild.include | 6 + + scripts/Makefile.dtbinst | 51 + + scripts/Makefile.lib | 12 - + sound/soc/fsl/mpc8610_hpcd.c | 2 +- + sound/soc/fsl/p1022_ds.c | 2 +- + sound/soc/fsl/p1022_rdk.c | 2 +- + 467 files changed, 87181 insertions(+), 3457 deletions(-) + create mode 100644 arch/arm64/boot/dts/Makefile.rej + create mode 100644 arch/arm64/boot/dts/arm64-nxp-ls2080ardb-r0.dts + create mode 100644 arch/arm64/boot/dts/arm64-nxp-ls2088ardb-r1.dts + create mode 100644 arch/arm64/boot/dts/fsl-ls2080a.dtsi + create mode 100644 arch/arm64/boot/dts/fsl-ls2088a.dtsi + create mode 120000 arch/arm64/boot/dts/include/dt-bindings + create mode 100644 arch/arm64/boot/dts/thermal.h + create mode 100644 arch/arm64/configs/nxp_ls2088rdb_config + delete mode 100644 arch/powerpc/include/asm/mpc85xx.h + create mode 100644 drivers/clk/clk-qoriq.c + create mode 100644 drivers/iommu/io-pgtable-arm.c + create mode 100644 drivers/iommu/io-pgtable.c + create mode 100644 drivers/iommu/io-pgtable.h + create mode 100644 drivers/irqchip/irq-gic-v2m.c + create mode 100644 drivers/irqchip/irq-gic-v3-its.c + create mode 100644 drivers/net/phy/aquantia.c + delete mode 100644 drivers/net/phy/fixed.c + create mode 100644 drivers/net/phy/fixed_phy.c + create mode 100644 drivers/net/phy/fsl_10gkr.c + create mode 100644 drivers/net/phy/teranetics.c + create mode 100644 drivers/pci/host/pci-layerscape.c + create mode 100644 drivers/pci/host/pci-layerscape.h + create mode 100644 drivers/pci/host/pci-xgene-msi.c + create mode 100644 drivers/power/reset/ls-reboot.c + create mode 100644 drivers/soc/fsl/Kconfig + create mode 100644 drivers/soc/fsl/Kconfig.arm + create mode 100644 drivers/soc/fsl/Makefile + create mode 100644 drivers/soc/fsl/guts.c + create mode 100644 drivers/soc/fsl/ls1/Kconfig + create mode 100644 drivers/soc/fsl/ls1/Makefile + create mode 100644 drivers/soc/fsl/ls1/ftm_alarm.c + create mode 100644 drivers/staging/fsl-dpaa2/Kconfig + create mode 100644 drivers/staging/fsl-dpaa2/Makefile + create mode 100644 drivers/staging/fsl-dpaa2/ethernet/Kconfig + create mode 100644 drivers/staging/fsl-dpaa2/ethernet/Makefile + create mode 100644 drivers/staging/fsl-dpaa2/ethernet/dpaa2-eth-debugfs.c + create mode 100644 drivers/staging/fsl-dpaa2/ethernet/dpaa2-eth-debugfs.h + create mode 100644 drivers/staging/fsl-dpaa2/ethernet/dpaa2-eth-trace.h + create mode 100644 drivers/staging/fsl-dpaa2/ethernet/dpaa2-eth.c + create mode 100644 drivers/staging/fsl-dpaa2/ethernet/dpaa2-eth.h + create mode 100644 drivers/staging/fsl-dpaa2/ethernet/dpaa2-ethtool.c + create mode 100644 drivers/staging/fsl-dpaa2/ethernet/dpkg.h + create mode 100644 drivers/staging/fsl-dpaa2/ethernet/dpni-cmd.h + create mode 100644 drivers/staging/fsl-dpaa2/ethernet/dpni.c + create mode 100644 drivers/staging/fsl-dpaa2/ethernet/dpni.h + create mode 100644 drivers/staging/fsl-dpaa2/mac/Kconfig + create mode 100644 drivers/staging/fsl-dpaa2/mac/Makefile + create mode 100644 drivers/staging/fsl-dpaa2/mac/dpmac-cmd.h + create mode 100644 drivers/staging/fsl-dpaa2/mac/dpmac.c + create mode 100644 drivers/staging/fsl-dpaa2/mac/dpmac.h + create mode 100644 drivers/staging/fsl-dpaa2/mac/mac.c + create mode 100644 drivers/staging/fsl-mc/Kconfig + create mode 100644 drivers/staging/fsl-mc/Makefile + create mode 100644 drivers/staging/fsl-mc/TODO + create mode 100644 drivers/staging/fsl-mc/bus/Kconfig + create mode 100644 drivers/staging/fsl-mc/bus/Makefile + create mode 100644 drivers/staging/fsl-mc/bus/dpbp.c + create mode 100644 drivers/staging/fsl-mc/bus/dpcon.c + create mode 100644 drivers/staging/fsl-mc/bus/dpio/Makefile + create mode 100644 drivers/staging/fsl-mc/bus/dpio/dpio-drv.c + create mode 100644 drivers/staging/fsl-mc/bus/dpio/dpio-drv.h + create mode 100644 drivers/staging/fsl-mc/bus/dpio/dpio.c + create mode 100644 drivers/staging/fsl-mc/bus/dpio/dpio_service.c + create mode 100644 drivers/staging/fsl-mc/bus/dpio/fsl_dpio.h + create mode 100644 drivers/staging/fsl-mc/bus/dpio/fsl_dpio_cmd.h + create mode 100644 drivers/staging/fsl-mc/bus/dpio/fsl_qbman_base.h + create mode 100644 drivers/staging/fsl-mc/bus/dpio/fsl_qbman_portal.h + create mode 100644 drivers/staging/fsl-mc/bus/dpio/qbman_debug.c + create mode 100644 drivers/staging/fsl-mc/bus/dpio/qbman_debug.h + create mode 100644 drivers/staging/fsl-mc/bus/dpio/qbman_portal.c + create mode 100644 drivers/staging/fsl-mc/bus/dpio/qbman_portal.h + create mode 100644 drivers/staging/fsl-mc/bus/dpio/qbman_private.h + create mode 100644 drivers/staging/fsl-mc/bus/dpio/qbman_sys.h + create mode 100644 drivers/staging/fsl-mc/bus/dpio/qbman_sys_decl.h + create mode 100644 drivers/staging/fsl-mc/bus/dpio/qbman_test.c + create mode 100644 drivers/staging/fsl-mc/bus/dpmcp-cmd.h + create mode 100644 drivers/staging/fsl-mc/bus/dpmcp.c + create mode 100644 drivers/staging/fsl-mc/bus/dpmcp.h + create mode 100644 drivers/staging/fsl-mc/bus/dpmng-cmd.h + create mode 100644 drivers/staging/fsl-mc/bus/dpmng.c + create mode 100644 drivers/staging/fsl-mc/bus/dprc-cmd.h + create mode 100644 drivers/staging/fsl-mc/bus/dprc-driver.c + create mode 100644 drivers/staging/fsl-mc/bus/dprc.c + create mode 100644 drivers/staging/fsl-mc/bus/mc-allocator.c + create mode 100644 drivers/staging/fsl-mc/bus/mc-bus.c + create mode 100644 drivers/staging/fsl-mc/bus/mc-ioctl.h + create mode 100644 drivers/staging/fsl-mc/bus/mc-restool.c + create mode 100644 drivers/staging/fsl-mc/bus/mc-sys.c + create mode 100644 drivers/staging/fsl-mc/include/dpbp-cmd.h + create mode 100644 drivers/staging/fsl-mc/include/dpbp.h + create mode 100644 drivers/staging/fsl-mc/include/dpcon-cmd.h + create mode 100644 drivers/staging/fsl-mc/include/dpcon.h + create mode 100644 drivers/staging/fsl-mc/include/dpmac-cmd.h + create mode 100644 drivers/staging/fsl-mc/include/dpmac.h + create mode 100644 drivers/staging/fsl-mc/include/dpmng.h + create mode 100644 drivers/staging/fsl-mc/include/dprc.h + create mode 100644 drivers/staging/fsl-mc/include/fsl_dpaa2_fd.h + create mode 100644 drivers/staging/fsl-mc/include/fsl_dpaa2_io.h + create mode 100644 drivers/staging/fsl-mc/include/mc-cmd.h + create mode 100644 drivers/staging/fsl-mc/include/mc-private.h + create mode 100644 drivers/staging/fsl-mc/include/mc-sys.h + create mode 100644 drivers/staging/fsl-mc/include/mc.h + create mode 100644 drivers/staging/fsl-mc/include/net.h + create mode 100644 drivers/vfio/fsl-mc/Kconfig + create mode 100644 drivers/vfio/fsl-mc/Makefile + create mode 100644 drivers/vfio/fsl-mc/vfio_fsl_mc.c + create mode 100644 drivers/vfio/fsl-mc/vfio_fsl_mc_intr.c + create mode 100644 drivers/vfio/fsl-mc/vfio_fsl_mc_private.h + create mode 100644 fs/aufs/Kconfig + create mode 100644 fs/aufs/Makefile + create mode 100644 fs/aufs/aufs.h + create mode 100644 fs/aufs/branch.c + create mode 100644 fs/aufs/branch.h + create mode 100644 fs/aufs/conf.mk + create mode 100644 fs/aufs/cpup.c + create mode 100644 fs/aufs/cpup.h + create mode 100644 fs/aufs/dbgaufs.c + create mode 100644 fs/aufs/dbgaufs.h + create mode 100644 fs/aufs/dcsub.c + create mode 100644 fs/aufs/dcsub.h + create mode 100644 fs/aufs/debug.c + create mode 100644 fs/aufs/debug.h + create mode 100644 fs/aufs/dentry.c + create mode 100644 fs/aufs/dentry.h + create mode 100644 fs/aufs/dinfo.c + create mode 100644 fs/aufs/dir.c + create mode 100644 fs/aufs/dir.h + create mode 100644 fs/aufs/dynop.c + create mode 100644 fs/aufs/dynop.h + create mode 100644 fs/aufs/export.c + create mode 100644 fs/aufs/f_op.c + create mode 100644 fs/aufs/fhsm.c + create mode 100644 fs/aufs/file.c + create mode 100644 fs/aufs/file.h + create mode 100644 fs/aufs/finfo.c + create mode 100644 fs/aufs/fstype.h + create mode 100644 fs/aufs/hfsnotify.c + create mode 100644 fs/aufs/hfsplus.c + create mode 100644 fs/aufs/hnotify.c + create mode 100644 fs/aufs/i_op.c + create mode 100644 fs/aufs/i_op_add.c + create mode 100644 fs/aufs/i_op_del.c + create mode 100644 fs/aufs/i_op_ren.c + create mode 100644 fs/aufs/iinfo.c + create mode 100644 fs/aufs/inode.c + create mode 100644 fs/aufs/inode.h + create mode 100644 fs/aufs/ioctl.c + create mode 100644 fs/aufs/loop.c + create mode 100644 fs/aufs/loop.h + create mode 100644 fs/aufs/magic.mk + create mode 100644 fs/aufs/module.c + create mode 100644 fs/aufs/module.h + create mode 100644 fs/aufs/mvdown.c + create mode 100644 fs/aufs/opts.c + create mode 100644 fs/aufs/opts.h + create mode 100644 fs/aufs/plink.c + create mode 100644 fs/aufs/poll.c + create mode 100644 fs/aufs/posix_acl.c + create mode 100644 fs/aufs/procfs.c + create mode 100644 fs/aufs/rdu.c + create mode 100644 fs/aufs/rwsem.h + create mode 100644 fs/aufs/sbinfo.c + create mode 100644 fs/aufs/spl.h + create mode 100644 fs/aufs/super.c + create mode 100644 fs/aufs/super.h + create mode 100644 fs/aufs/sysaufs.c + create mode 100644 fs/aufs/sysaufs.h + create mode 100644 fs/aufs/sysfs.c + create mode 100644 fs/aufs/sysrq.c + create mode 100644 fs/aufs/vdir.c + create mode 100644 fs/aufs/vfsub.c + create mode 100644 fs/aufs/vfsub.h + create mode 100644 fs/aufs/wbr_policy.c + create mode 100644 fs/aufs/whout.c + create mode 100644 fs/aufs/whout.h + create mode 100644 fs/aufs/wkq.c + create mode 100644 fs/aufs/wkq.h + create mode 100644 fs/aufs/xattr.c + create mode 100644 fs/aufs/xino.c + create mode 100644 include/asm-generic/msi.h + create mode 100644 include/linux/fsl/guts.h + create mode 100644 include/linux/fsl/svr.h + create mode 100644 include/linux/iopoll.h + create mode 100644 include/linux/irqhandler.h + create mode 100644 include/linux/resource_ext.h + create mode 100644 include/uapi/linux/aufs_type.h + create mode 100644 kernel/irq/msi.c + create mode 100644 mm/prfile.c + create mode 100644 scripts/Makefile.dtbinst + +diff --git a/Documentation/IRQ-domain.txt b/Documentation/IRQ-domain.txt +index 8a8b82c..39cfa72 100644 +--- a/Documentation/IRQ-domain.txt ++++ b/Documentation/IRQ-domain.txt +@@ -151,3 +151,74 @@ used and no descriptor gets allocated it is very important to make sure + that the driver using the simple domain call irq_create_mapping() + before any irq_find_mapping() since the latter will actually work + for the static IRQ assignment case. ++ ++==== Hierarchy IRQ domain ==== ++On some architectures, there may be multiple interrupt controllers ++involved in delivering an interrupt from the device to the target CPU. ++Let's look at a typical interrupt delivering path on x86 platforms: ++ ++Device --> IOAPIC -> Interrupt remapping Controller -> Local APIC -> CPU ++ ++There are three interrupt controllers involved: ++1) IOAPIC controller ++2) Interrupt remapping controller ++3) Local APIC controller ++ ++To support such a hardware topology and make software architecture match ++hardware architecture, an irq_domain data structure is built for each ++interrupt controller and those irq_domains are organized into hierarchy. ++When building irq_domain hierarchy, the irq_domain near to the device is ++child and the irq_domain near to CPU is parent. So a hierarchy structure ++as below will be built for the example above. ++ CPU Vector irq_domain (root irq_domain to manage CPU vectors) ++ ^ ++ | ++ Interrupt Remapping irq_domain (manage irq_remapping entries) ++ ^ ++ | ++ IOAPIC irq_domain (manage IOAPIC delivery entries/pins) ++ ++There are four major interfaces to use hierarchy irq_domain: ++1) irq_domain_alloc_irqs(): allocate IRQ descriptors and interrupt ++ controller related resources to deliver these interrupts. ++2) irq_domain_free_irqs(): free IRQ descriptors and interrupt controller ++ related resources associated with these interrupts. ++3) irq_domain_activate_irq(): activate interrupt controller hardware to ++ deliver the interrupt. ++3) irq_domain_deactivate_irq(): deactivate interrupt controller hardware ++ to stop delivering the interrupt. ++ ++Following changes are needed to support hierarchy irq_domain. ++1) a new field 'parent' is added to struct irq_domain; it's used to ++ maintain irq_domain hierarchy information. ++2) a new field 'parent_data' is added to struct irq_data; it's used to ++ build hierarchy irq_data to match hierarchy irq_domains. The irq_data ++ is used to store irq_domain pointer and hardware irq number. ++3) new callbacks are added to struct irq_domain_ops to support hierarchy ++ irq_domain operations. ++ ++With support of hierarchy irq_domain and hierarchy irq_data ready, an ++irq_domain structure is built for each interrupt controller, and an ++irq_data structure is allocated for each irq_domain associated with an ++IRQ. Now we could go one step further to support stacked(hierarchy) ++irq_chip. That is, an irq_chip is associated with each irq_data along ++the hierarchy. A child irq_chip may implement a required action by ++itself or by cooperating with its parent irq_chip. ++ ++With stacked irq_chip, interrupt controller driver only needs to deal ++with the hardware managed by itself and may ask for services from its ++parent irq_chip when needed. So we could achieve a much cleaner ++software architecture. ++ ++For an interrupt controller driver to support hierarchy irq_domain, it ++needs to: ++1) Implement irq_domain_ops.alloc and irq_domain_ops.free ++2) Optionally implement irq_domain_ops.activate and ++ irq_domain_ops.deactivate. ++3) Optionally implement an irq_chip to manage the interrupt controller ++ hardware. ++4) No need to implement irq_domain_ops.map and irq_domain_ops.unmap, ++ they are unused with hierarchy irq_domain. ++ ++Hierarchy irq_domain may also be used to support other architectures, ++such as ARM, ARM64 etc. +diff --git a/Documentation/devicetree/bindings/arm/fsl.txt b/Documentation/devicetree/bindings/arm/fsl.txt +index e935d7d..5c9f338 100644 +--- a/Documentation/devicetree/bindings/arm/fsl.txt ++++ b/Documentation/devicetree/bindings/arm/fsl.txt +@@ -74,3 +74,18 @@ Required root node properties: + i.MX6q generic board + Required root node properties: + - compatible = "fsl,imx6q"; ++ +++Freescale ARMv8 based Layerscape SoC family Device Tree Bindings +++---------------------------------------------------------------- ++ ++LS2080A ARMv8 based Simulator model ++Required root node properties: ++ - compatible = "fsl,ls2080a-simu", "fsl,ls2080a"; ++ ++LS2080A ARMv8 based QDS Board ++Required root node properties: ++ - compatible = "fsl,ls2080a-qds", "fsl,ls2080a"; ++ ++LS2080A ARMv8 based RDB Board ++Required root node properties: ++ - compatible = "fsl,ls2080a-rdb", "fsl,ls2080a"; +diff --git a/Documentation/devicetree/bindings/arm/gic.txt b/Documentation/devicetree/bindings/arm/gic.txt +index c7d2fa1..e87d3d7 100644 +--- a/Documentation/devicetree/bindings/arm/gic.txt ++++ b/Documentation/devicetree/bindings/arm/gic.txt +@@ -31,12 +31,16 @@ Main node required properties: + The 3rd cell is the flags, encoded as follows: + bits[3:0] trigger type and level flags. + 1 = low-to-high edge triggered +- 2 = high-to-low edge triggered ++ 2 = high-to-low edge triggered (invalid for SPIs) + 4 = active high level-sensitive +- 8 = active low level-sensitive ++ 8 = active low level-sensitive (invalid for SPIs). + bits[15:8] PPI interrupt cpu mask. Each bit corresponds to each of + the 8 possible cpus attached to the GIC. A bit set to '1' indicated + the interrupt is wired to that CPU. Only valid for PPI interrupts. ++ Also note that the configurability of PPI interrupts is IMPLEMENTATION ++ DEFINED and as such not guaranteed to be present (most SoC available ++ in 2014 seem to ignore the setting of this flag and use the hardware ++ default value). + + - reg : Specifies base physical address(s) and size of the GIC registers. The + first region is the GIC distributor register base and size. The 2nd region is +diff --git a/Documentation/devicetree/bindings/clock/qoriq-clock.txt b/Documentation/devicetree/bindings/clock/qoriq-clock.txt +index 5666812..128fc72 100644 +--- a/Documentation/devicetree/bindings/clock/qoriq-clock.txt ++++ b/Documentation/devicetree/bindings/clock/qoriq-clock.txt +@@ -1,6 +1,6 @@ +-* Clock Block on Freescale CoreNet Platforms ++* Clock Block on Freescale QorIQ Platforms + +-Freescale CoreNet chips take primary clocking input from the external ++Freescale QorIQ chips take primary clocking input from the external + SYSCLK signal. The SYSCLK input (frequency) is multiplied using + multiple phase locked loops (PLL) to create a variety of frequencies + which can then be passed to a variety of internal logic, including +@@ -13,14 +13,16 @@ which the chip complies. + Chassis Version Example Chips + --------------- ------------- + 1.0 p4080, p5020, p5040 +-2.0 t4240, b4860, t1040 ++2.0 t4240, b4860 + + 1. Clock Block Binding + + Required properties: +-- compatible: Should contain a specific clock block compatible string +- and a single chassis clock compatible string. +- Clock block strings include, but not limited to, one of the: ++- compatible: Should contain a chip-specific clock block compatible ++ string and (if applicable) may contain a chassis-version clock ++ compatible string. ++ ++ Chip-specific strings are of the form "fsl,-clockgen", such as: + * "fsl,p2041-clockgen" + * "fsl,p3041-clockgen" + * "fsl,p4080-clockgen" +@@ -29,15 +31,15 @@ Required properties: + * "fsl,t4240-clockgen" + * "fsl,b4420-clockgen" + * "fsl,b4860-clockgen" +- Chassis clock strings include: ++ * "fsl,ls1021a-clockgen" ++ Chassis-version clock strings include: + * "fsl,qoriq-clockgen-1.0": for chassis 1.0 clocks + * "fsl,qoriq-clockgen-2.0": for chassis 2.0 clocks + - reg: Describes the address of the device's resources within the + address space defined by its parent bus, and resource zero + represents the clock register set +-- clock-frequency: Input system clock frequency + +-Recommended properties: ++Optional properties: + - ranges: Allows valid translation between child's address space and + parent's. Must be present if the device has sub-nodes. + - #address-cells: Specifies the number of cells used to represent +@@ -46,8 +48,46 @@ Recommended properties: + - #size-cells: Specifies the number of cells used to represent + the size of an address. Must be present if the device has + sub-nodes and set to 1 if present ++- clock-frequency: Input system clock frequency (SYSCLK) ++- clocks: If clock-frequency is not specified, sysclk may be provided ++ as an input clock. Either clock-frequency or clocks must be ++ provided. ++ ++2. Clock Provider ++ ++The clockgen node should act as a clock provider, though in older device ++trees the children of the clockgen node are the clock providers. ++ ++When the clockgen node is a clock provider, #clock-cells = <2>. ++The first cell of the clock specifier is the clock type, and the ++second cell is the clock index for the specified type. ++ ++ Type# Name Index Cell ++ 0 sysclk must be 0 ++ 1 cmux index (n in CLKCnCSR) ++ 2 hwaccel index (n in CLKCGnHWACSR) ++ 3 fman 0 for fm1, 1 for fm2 ++ 4 platform pll 0=pll, 1=pll/2, 2=pll/3, 3=pll/4 ++ ++3. Example ++ ++ clockgen: global-utilities@e1000 { ++ compatible = "fsl,p5020-clockgen", "fsl,qoriq-clockgen-1.0"; ++ clock-frequency = <133333333>; ++ reg = <0xe1000 0x1000>; ++ #clock-cells = <2>; ++ }; ++ ++ fman@400000 { ++ ... ++ clocks = <&clockgen 3 0>; ++ ... ++ }; ++} ++4. Legacy Child Nodes + +-2. Clock Provider/Consumer Binding ++NOTE: These nodes are deprecated. Kernels should continue to support ++device trees with these nodes, but new device trees should not use them. + + Most of the bindings are from the common clock binding[1]. + [1] Documentation/devicetree/bindings/clock/clock-bindings.txt +@@ -79,7 +119,7 @@ Recommended properties: + - reg: Should be the offset and length of clock block base address. + The length should be 4. + +-Example for clock block and clock provider: ++Legacy Example: + / { + clockgen: global-utilities@e1000 { + compatible = "fsl,p5020-clockgen", "fsl,qoriq-clockgen-1.0"; +@@ -131,7 +171,7 @@ Example for clock block and clock provider: + }; + } + +-Example for clock consumer: ++Example for legacy clock consumer: + + / { + cpu0: PowerPC,e5500@0 { +diff --git a/Documentation/devicetree/bindings/i2c/i2c-imx.txt b/Documentation/devicetree/bindings/i2c/i2c-imx.txt +index 4a8513e..52d37fd 100644 +--- a/Documentation/devicetree/bindings/i2c/i2c-imx.txt ++++ b/Documentation/devicetree/bindings/i2c/i2c-imx.txt +@@ -11,6 +11,8 @@ Required properties: + Optional properties: + - clock-frequency : Constains desired I2C/HS-I2C bus clock frequency in Hz. + The absence of the propoerty indicates the default frequency 100 kHz. ++- dmas: A list of two dma specifiers, one for each entry in dma-names. ++- dma-names: should contain "tx" and "rx". + + Examples: + +@@ -26,3 +28,12 @@ i2c@70038000 { /* HS-I2C on i.MX51 */ + interrupts = <64>; + clock-frequency = <400000>; + }; ++ ++i2c0: i2c@40066000 { /* i2c0 on vf610 */ ++ compatible = "fsl,vf610-i2c"; ++ reg = <0x40066000 0x1000>; ++ interrupts =<0 71 0x04>; ++ dmas = <&edma0 0 50>, ++ <&edma0 0 51>; ++ dma-names = "rx","tx"; ++}; +diff --git a/Documentation/devicetree/bindings/i2c/i2c-mux-pca954x.txt b/Documentation/devicetree/bindings/i2c/i2c-mux-pca954x.txt +index 34a3fb6..cf53d5f 100644 +--- a/Documentation/devicetree/bindings/i2c/i2c-mux-pca954x.txt ++++ b/Documentation/devicetree/bindings/i2c/i2c-mux-pca954x.txt +@@ -16,6 +16,9 @@ Required Properties: + Optional Properties: + + - reset-gpios: Reference to the GPIO connected to the reset input. ++ - i2c-mux-idle-disconnect: Boolean; if defined, forces mux to disconnect all ++ children in idle state. This is necessary for example, if there are several ++ multiplexers on the bus and the devices behind them use same I2C addresses. + + + Example: +diff --git a/Documentation/devicetree/bindings/memory-controllers/fsl/ifc.txt b/Documentation/devicetree/bindings/memory-controllers/fsl/ifc.txt +index d5e3704..89427b0 100644 +--- a/Documentation/devicetree/bindings/memory-controllers/fsl/ifc.txt ++++ b/Documentation/devicetree/bindings/memory-controllers/fsl/ifc.txt +@@ -18,6 +18,8 @@ Properties: + interrupt (NAND_EVTER_STAT). If there is only one, + that interrupt reports both types of event. + ++- little-endian : If this property is absent, the big-endian mode will ++ be in use as default for registers. + + - ranges : Each range corresponds to a single chipselect, and covers + the entire access window as configured. +@@ -34,6 +36,7 @@ Example: + #size-cells = <1>; + reg = <0x0 0xffe1e000 0 0x2000>; + interrupts = <16 2 19 2>; ++ little-endian; + + /* NOR, NAND Flashes and CPLD on board */ + ranges = <0x0 0x0 0x0 0xee000000 0x02000000 +diff --git a/Documentation/devicetree/bindings/pci/designware-pcie.txt b/Documentation/devicetree/bindings/pci/designware-pcie.txt +index 9f4faa8..0036ab3 100644 +--- a/Documentation/devicetree/bindings/pci/designware-pcie.txt ++++ b/Documentation/devicetree/bindings/pci/designware-pcie.txt +@@ -14,7 +14,6 @@ Required properties: + - interrupt-map-mask and interrupt-map: standard PCI properties + to define the mapping of the PCIe interface to interrupt + numbers. +-- num-lanes: number of lanes to use + - clocks: Must contain an entry for each entry in clock-names. + See ../clocks/clock-bindings.txt for details. + - clock-names: Must include the following entries: +@@ -22,6 +21,8 @@ Required properties: + - "pcie_bus" + + Optional properties: ++- num-lanes: number of lanes to use (this property should be specified unless ++ the link is brought already up in BIOS) + - reset-gpio: gpio pin number of power good signal + - bus-range: PCI bus numbers covered (it is recommended for new devicetrees to + specify this property, to keep backwards compatibility a range of 0x00-0xff +diff --git a/Documentation/devicetree/bindings/powerpc/fsl/board.txt b/Documentation/devicetree/bindings/powerpc/fsl/board.txt +index cff38bd..89c90f4 100644 +--- a/Documentation/devicetree/bindings/powerpc/fsl/board.txt ++++ b/Documentation/devicetree/bindings/powerpc/fsl/board.txt +@@ -21,11 +21,14 @@ Example: + + This is the memory-mapped registers for on board FPGA. + +-Required properities: ++Required properties: + - compatible: should be a board-specific string followed by a string + indicating the type of FPGA. Example: +- "fsl,-fpga", "fsl,fpga-pixis" ++ "fsl,-fpga", "fsl,fpga-pixis" or ++ "fsl,-fpga", "fsl,fpga-qixis" + - reg: should contain the address and the length of the FPGA register set. ++ ++Optional properties: + - interrupt-parent: should specify phandle for the interrupt controller. + - interrupts: should specify event (wakeup) IRQ. + +@@ -38,6 +41,13 @@ Example (P1022DS): + interrupts = <8 8 0 0>; + }; + ++Example (LS2080A-RDB): ++ ++ cpld@3,0 { ++ compatible = "fsl,ls2080ardb-fpga", "fsl,fpga-qixis"; ++ reg = <0x3 0 0x10000>; ++ }; ++ + * Freescale BCSR GPIO banks + + Some BCSR registers act as simple GPIO controllers, each such +diff --git a/Documentation/devicetree/bindings/usb/dwc3.txt b/Documentation/devicetree/bindings/usb/dwc3.txt +index 471366d..1f9900c 100644 +--- a/Documentation/devicetree/bindings/usb/dwc3.txt ++++ b/Documentation/devicetree/bindings/usb/dwc3.txt +@@ -1,6 +1,7 @@ + synopsys DWC3 CORE + +-DWC3- USB3 CONTROLLER ++DWC3- USB3 CONTROLLER. Complies to the generic USB binding properties ++ as described in 'usb/generic.txt' + + Required properties: + - compatible: must be "snps,dwc3" +diff --git a/Documentation/devicetree/of_selftest.txt b/Documentation/devicetree/of_selftest.txt +index 1e3d5c9..57a808b 100644 +--- a/Documentation/devicetree/of_selftest.txt ++++ b/Documentation/devicetree/of_selftest.txt +@@ -63,7 +63,6 @@ struct device_node { + struct device_node *parent; + struct device_node *child; + struct device_node *sibling; +- struct device_node *allnext; /* next in list of all nodes */ + ... + }; + +@@ -99,12 +98,6 @@ child11 -> sibling12 -> sibling13 -> sibling14 -> null + Figure 1: Generic structure of un-flattened device tree + + +-*allnext: it is used to link all the nodes of DT into a list. So, for the +- above tree the list would be as follows: +- +-root->child1->child11->sibling12->sibling13->child131->sibling14->sibling2-> +-child21->sibling22->sibling23->sibling3->child31->sibling32->sibling4->null +- + Before executing OF selftest, it is required to attach the test data to + machine's device tree (if present). So, when selftest_data_add() is called, + at first it reads the flattened device tree data linked into the kernel image +@@ -131,11 +124,6 @@ root ('/') + test-child01 null null null + + +-allnext list: +- +-root->testcase-data->test-child0->test-child01->test-sibling1->test-sibling2 +-->test-sibling3->null +- + Figure 2: Example test data tree to be attached to live tree. + + According to the scenario above, the live tree is already present so it isn't +@@ -204,8 +192,6 @@ detached and then moving up the parent nodes are removed, and eventually the + whole tree). selftest_data_remove() calls detach_node_and_children() that uses + of_detach_node() to detach the nodes from the live device tree. + +-To detach a node, of_detach_node() first updates all_next linked list, by +-attaching the previous node's allnext to current node's allnext pointer. And +-then, it either updates the child pointer of given node's parent to its +-sibling or attaches the previous sibling to the given node's sibling, as +-appropriate. That is it :) ++To detach a node, of_detach_node() either updates the child pointer of given ++node's parent to its sibling or attaches the previous sibling to the given ++node's sibling, as appropriate. That is it :) +diff --git a/Documentation/devicetree/todo.txt b/Documentation/devicetree/todo.txt +index c3cf065..b5139d1 100644 +--- a/Documentation/devicetree/todo.txt ++++ b/Documentation/devicetree/todo.txt +@@ -2,7 +2,6 @@ Todo list for devicetree: + + === General structure === + - Switch from custom lists to (h)list_head for nodes and properties structure +-- Remove of_allnodes list and iterate using list of child nodes alone + + === CONFIG_OF_DYNAMIC === + - Switch to RCU for tree updates and get rid of global spinlock +diff --git a/MAINTAINERS b/MAINTAINERS +index c721042..cb2296a 100644 +--- a/MAINTAINERS ++++ b/MAINTAINERS +@@ -1562,6 +1562,7 @@ M: Will Deacon + L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers) + S: Maintained + F: drivers/iommu/arm-smmu.c ++F: drivers/iommu/io-pgtable-arm.c + + ARM64 PORT (AARCH64 ARCHITECTURE) + M: Catalin Marinas +@@ -1795,6 +1796,20 @@ F: include/linux/audit.h + F: include/uapi/linux/audit.h + F: kernel/audit* + ++AUFS (advanced multi layered unification filesystem) FILESYSTEM ++M: "J. R. Okajima" ++L: linux-unionfs@vger.kernel.org ++L: aufs-users@lists.sourceforge.net (members only) ++W: http://aufs.sourceforge.net ++T: git://git.code.sf.net/p/aufs/aufs3-linux ++T: git://github.com/sfjro/aufs3-linux.git ++S: Supported ++F: Documentation/filesystems/aufs/ ++F: Documentation/ABI/testing/debugfs-aufs ++F: Documentation/ABI/testing/sysfs-aufs ++F: fs/aufs/ ++F: include/uapi/linux/aufs_type.h ++ + AUXILIARY DISPLAY DRIVERS + M: Miguel Ojeda Sandonis + W: http://miguelojeda.es/auxdisplay.htm +@@ -3972,6 +3987,33 @@ F: sound/soc/fsl/fsl* + F: sound/soc/fsl/imx* + F: sound/soc/fsl/mpc8610_hpcd.c + ++FREESCALE QORIQ MANAGEMENT COMPLEX DRIVER ++M: J. German Rivera ++L: linux-kernel@vger.kernel.org ++S: Maintained ++F: drivers/staging/fsl-mc/ ++ ++FREESCALE DPAA2 ETH DRIVER ++M: Ioana Radulescu ++M: Bogdan Hamciuc ++M: Cristian Sovaiala ++L: linux-kernel@vger.kernel.org ++S: Maintained ++F: drivers/staging/fsl-dpaa2/ethernet/ ++ ++FREESCALE QORIQ MANAGEMENT COMPLEX RESTOOL DRIVER ++M: Lijun Pan ++L: linux-kernel@vger.kernel.org ++S: Maintained ++F: drivers/staging/fsl-mc/bus/mc-ioctl.h ++F: drivers/staging/fsl-mc/bus/mc-restool.c ++ ++FREESCALE DPAA2 MAC/PHY INTERFACE DRIVER ++M: Alex Marginean ++L: linux-kernel@vger.kernel.org ++S: Maintained ++F: drivers/staging/fsl-dpaa2/mac/ ++ + FREEVXFS FILESYSTEM + M: Christoph Hellwig + W: ftp://ftp.openlinux.org/pub/people/hch/vxfs +@@ -7047,6 +7089,16 @@ S: Maintained + F: Documentation/devicetree/bindings/pci/xgene-pci.txt + F: drivers/pci/host/pci-xgene.c + ++PCI DRIVER FOR FREESCALE LAYERSCAPE ++M: Minghuan Lian ++M: Mingkai Hu ++M: Roy Zang ++L: linuxppc-dev@lists.ozlabs.org ++L: linux-pci@vger.kernel.org ++L: linux-arm-kernel@lists.infradead.org ++S: Maintained ++F: drivers/pci/host/*layerscape* ++ + PCI DRIVER FOR IMX6 + M: Richard Zhu + M: Lucas Stach +@@ -7122,6 +7174,14 @@ L: linux-pci@vger.kernel.org + S: Maintained + F: drivers/pci/host/*spear* + ++PCI MSI DRIVER FOR APPLIEDMICRO XGENE ++M: Duc Dang ++L: linux-pci@vger.kernel.org ++L: linux-arm-kernel@lists.infradead.org ++S: Maintained ++F: Documentation/devicetree/bindings/pci/xgene-pci-msi.txt ++F: drivers/pci/host/pci-xgene-msi.c ++ + PCMCIA SUBSYSTEM + P: Linux PCMCIA Team + L: linux-pcmcia@lists.infradead.org +diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig +index 89c4b5c..29544f0 100644 +--- a/arch/arm/Kconfig ++++ b/arch/arm/Kconfig +@@ -1292,6 +1292,9 @@ config PCI_DOMAINS + bool + depends on PCI + ++config PCI_DOMAINS_GENERIC ++ def_bool PCI_DOMAINS ++ + config PCI_NANOENGINE + bool "BSE nanoEngine PCI support" + depends on SA1100_NANOENGINE +diff --git a/arch/arm/Makefile b/arch/arm/Makefile +index b5d7988..93a30a2 100644 +--- a/arch/arm/Makefile ++++ b/arch/arm/Makefile +@@ -320,8 +320,12 @@ $(INSTALL_TARGETS): + $(Q)$(MAKE) $(build)=$(boot)/dts MACHINE=$(MACHINE) $(boot)/dts/$@ + + PHONY += dtbs dtbs_install +-dtbs dtbs_install: prepare scripts +- $(Q)$(MAKE) $(build)=$(boot)/dts MACHINE=$(MACHINE) $@ ++ ++dtbs: prepare scripts ++ $(Q)$(MAKE) $(build)=$(boot)/dts MACHINE=$(MACHINE) ++ ++dtbs_install: ++ $(Q)$(MAKE) $(dtbinst)=$(boot)/dts MACHINE=$(MACHINE) + + # We use MRPROPER_FILES and CLEAN_FILES now + archclean: +diff --git a/arch/arm/boot/dts/Makefile b/arch/arm/boot/dts/Makefile +index 38c89ca..6e784fa 100644 +--- a/arch/arm/boot/dts/Makefile ++++ b/arch/arm/boot/dts/Makefile +@@ -517,15 +517,7 @@ dtb-$(CONFIG_MACH_DOVE) += dove-cm-a510.dtb \ + dove-dove-db.dtb + dtb-$(CONFIG_ARCH_MEDIATEK) += mt6589-aquaris5.dtb + +-targets += dtbs dtbs_install +-targets += $(dtb-y) + endif + +-# *.dtb used to be generated in the directory above. Clean out the +-# old build results so people don't accidentally use them. +-dtbs: $(addprefix $(obj)/, $(dtb-y)) +- $(Q)rm -f $(obj)/../*.dtb +- +-clean-files := *.dtb +- +-dtbs_install: $(addsuffix _dtbinst_, $(dtb-y)) ++always := $(dtb-y) ++clean-files := *.dtb +diff --git a/arch/arm/include/asm/dma-mapping.h b/arch/arm/include/asm/dma-mapping.h +index 85738b2..f3c0d95 100644 +--- a/arch/arm/include/asm/dma-mapping.h ++++ b/arch/arm/include/asm/dma-mapping.h +@@ -121,12 +121,14 @@ static inline unsigned long dma_max_pfn(struct device *dev) + } + #define dma_max_pfn(dev) dma_max_pfn(dev) + +-static inline int set_arch_dma_coherent_ops(struct device *dev) ++static inline void arch_setup_dma_ops(struct device *dev, u64 dma_base, ++ u64 size, struct iommu_ops *iommu, ++ bool coherent) + { +- set_dma_ops(dev, &arm_coherent_dma_ops); +- return 0; ++ if (coherent) ++ set_dma_ops(dev, &arm_coherent_dma_ops); + } +-#define set_arch_dma_coherent_ops(dev) set_arch_dma_coherent_ops(dev) ++#define arch_setup_dma_ops arch_setup_dma_ops + + static inline dma_addr_t phys_to_dma(struct device *dev, phys_addr_t paddr) + { +diff --git a/arch/arm/include/asm/mach/pci.h b/arch/arm/include/asm/mach/pci.h +index 7fc4278..c074e7a 100644 +--- a/arch/arm/include/asm/mach/pci.h ++++ b/arch/arm/include/asm/mach/pci.h +@@ -19,9 +19,7 @@ struct pci_bus; + struct device; + + struct hw_pci { +-#ifdef CONFIG_PCI_DOMAINS +- int domain; +-#endif ++ struct msi_controller *msi_ctrl; + struct pci_ops *ops; + int nr_controllers; + void **private_data; +@@ -36,16 +34,14 @@ struct hw_pci { + resource_size_t start, + resource_size_t size, + resource_size_t align); +- void (*add_bus)(struct pci_bus *bus); +- void (*remove_bus)(struct pci_bus *bus); + }; + + /* + * Per-controller structure + */ + struct pci_sys_data { +-#ifdef CONFIG_PCI_DOMAINS +- int domain; ++#ifdef CONFIG_PCI_MSI ++ struct msi_controller *msi_ctrl; + #endif + struct list_head node; + int busnr; /* primary bus number */ +@@ -65,8 +61,6 @@ struct pci_sys_data { + resource_size_t start, + resource_size_t size, + resource_size_t align); +- void (*add_bus)(struct pci_bus *bus); +- void (*remove_bus)(struct pci_bus *bus); + void *private_data; /* platform controller private data */ + }; + +diff --git a/arch/arm/include/asm/pci.h b/arch/arm/include/asm/pci.h +index 7e95d85..585dc33 100644 +--- a/arch/arm/include/asm/pci.h ++++ b/arch/arm/include/asm/pci.h +@@ -18,13 +18,6 @@ static inline int pcibios_assign_all_busses(void) + } + + #ifdef CONFIG_PCI_DOMAINS +-static inline int pci_domain_nr(struct pci_bus *bus) +-{ +- struct pci_sys_data *root = bus->sysdata; +- +- return root->domain; +-} +- + static inline int pci_proc_domain(struct pci_bus *bus) + { + return pci_domain_nr(bus); +diff --git a/arch/arm/kernel/bios32.c b/arch/arm/kernel/bios32.c +index 17a26c1..a5cd259 100644 +--- a/arch/arm/kernel/bios32.c ++++ b/arch/arm/kernel/bios32.c +@@ -18,6 +18,15 @@ + + static int debug_pci; + ++#ifdef CONFIG_PCI_MSI ++struct msi_controller *pcibios_msi_controller(struct pci_dev *dev) ++{ ++ struct pci_sys_data *sysdata = dev->bus->sysdata; ++ ++ return sysdata->msi_ctrl; ++} ++#endif ++ + /* + * We can't use pci_get_device() here since we are + * called from interrupt context. +@@ -360,20 +369,6 @@ void pcibios_fixup_bus(struct pci_bus *bus) + } + EXPORT_SYMBOL(pcibios_fixup_bus); + +-void pcibios_add_bus(struct pci_bus *bus) +-{ +- struct pci_sys_data *sys = bus->sysdata; +- if (sys->add_bus) +- sys->add_bus(bus); +-} +- +-void pcibios_remove_bus(struct pci_bus *bus) +-{ +- struct pci_sys_data *sys = bus->sysdata; +- if (sys->remove_bus) +- sys->remove_bus(bus); +-} +- + /* + * Swizzle the device pin each time we cross a bridge. If a platform does + * not provide a swizzle function, we perform the standard PCI swizzling. +@@ -427,17 +422,16 @@ static int pcibios_map_irq(const struct pci_dev *dev, u8 slot, u8 pin) + static int pcibios_init_resources(int busnr, struct pci_sys_data *sys) + { + int ret; +- struct pci_host_bridge_window *window; ++ struct resource_entry *window; + + if (list_empty(&sys->resources)) { + pci_add_resource_offset(&sys->resources, + &iomem_resource, sys->mem_offset); + } + +- list_for_each_entry(window, &sys->resources, list) { ++ resource_list_for_each_entry(window, &sys->resources) + if (resource_type(window->res) == IORESOURCE_IO) + return 0; +- } + + sys->io_res.start = (busnr * SZ_64K) ? : pcibios_min_io; + sys->io_res.end = (busnr + 1) * SZ_64K - 1; +@@ -468,15 +462,13 @@ static void pcibios_init_hw(struct device *parent, struct hw_pci *hw, + if (!sys) + panic("PCI: unable to allocate sys data!"); + +-#ifdef CONFIG_PCI_DOMAINS +- sys->domain = hw->domain; ++#ifdef CONFIG_PCI_MSI ++ sys->msi_ctrl = hw->msi_ctrl; + #endif + sys->busnr = busnr; + sys->swizzle = hw->swizzle; + sys->map_irq = hw->map_irq; + sys->align_resource = hw->align_resource; +- sys->add_bus = hw->add_bus; +- sys->remove_bus = hw->remove_bus; + INIT_LIST_HEAD(&sys->resources); + + if (hw->private_data) +@@ -494,8 +486,9 @@ static void pcibios_init_hw(struct device *parent, struct hw_pci *hw, + if (hw->scan) + sys->bus = hw->scan(nr, sys); + else +- sys->bus = pci_scan_root_bus(parent, sys->busnr, +- hw->ops, sys, &sys->resources); ++ sys->bus = pci_scan_root_bus_msi(parent, ++ sys->busnr, hw->ops, sys, ++ &sys->resources, hw->msi_ctrl); + + if (!sys->bus) + panic("PCI: unable to scan bus!"); +diff --git a/arch/arm/mach-iop13xx/msi.c b/arch/arm/mach-iop13xx/msi.c +index e7730cf..9f89e76 100644 +--- a/arch/arm/mach-iop13xx/msi.c ++++ b/arch/arm/mach-iop13xx/msi.c +@@ -126,10 +126,10 @@ static void iop13xx_msi_nop(struct irq_data *d) + static struct irq_chip iop13xx_msi_chip = { + .name = "PCI-MSI", + .irq_ack = iop13xx_msi_nop, +- .irq_enable = unmask_msi_irq, +- .irq_disable = mask_msi_irq, +- .irq_mask = mask_msi_irq, +- .irq_unmask = unmask_msi_irq, ++ .irq_enable = pci_msi_unmask_irq, ++ .irq_disable = pci_msi_mask_irq, ++ .irq_mask = pci_msi_mask_irq, ++ .irq_unmask = pci_msi_unmask_irq, + }; + + int arch_setup_msi_irq(struct pci_dev *pdev, struct msi_desc *desc) +@@ -153,7 +153,7 @@ int arch_setup_msi_irq(struct pci_dev *pdev, struct msi_desc *desc) + id = iop13xx_cpu_id(); + msg.data = (id << IOP13XX_MU_MIMR_CORE_SELECT) | (irq & 0x7f); + +- write_msi_msg(irq, &msg); ++ pci_write_msi_msg(irq, &msg); + irq_set_chip_and_handler(irq, &iop13xx_msi_chip, handle_simple_irq); + + return 0; +diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig +index 00b9c48..329f5f4 100644 +--- a/arch/arm64/Kconfig ++++ b/arch/arm64/Kconfig +@@ -13,7 +13,9 @@ config ARM64 + select ARM_ARCH_TIMER + select ARM_GIC + select AUDIT_ARCH_COMPAT_GENERIC ++ select ARM_GIC_V2M if PCI_MSI + select ARM_GIC_V3 ++ select ARM_GIC_V3_ITS if PCI_MSI + select BUILDTIME_EXTABLE_SORT + select CLONE_BACKWARDS + select COMMON_CLK +@@ -166,6 +168,11 @@ config ARCH_XGENE + help + This enables support for AppliedMicro X-Gene SOC Family + ++config ARCH_LAYERSCAPE ++ bool "ARMv8 based Freescale Layerscape SoC family" ++ help ++ This enables support for the Freescale Layerscape SoC family. ++ + endmenu + + menu "Bus support" +@@ -366,7 +373,6 @@ config ARM64_VA_BITS_42 + + config ARM64_VA_BITS_48 + bool "48-bit" +- depends on !ARM_SMMU + + endchoice + +diff --git a/arch/arm64/Makefile b/arch/arm64/Makefile +index 2d54c55..7cf8a29 100644 +--- a/arch/arm64/Makefile ++++ b/arch/arm64/Makefile +@@ -74,8 +74,13 @@ zinstall install: vmlinux + %.dtb: scripts + $(Q)$(MAKE) $(build)=$(boot)/dts $(boot)/dts/$@ + +-dtbs: scripts +- $(Q)$(MAKE) $(build)=$(boot)/dts dtbs ++PHONY += dtbs dtbs_install ++ ++dtbs: prepare scripts ++ $(Q)$(MAKE) $(build)=$(boot)/dts ++ ++dtbs_install: ++ $(Q)$(MAKE) $(dtbinst)=$(boot)/dts + + PHONY += vdso_install + vdso_install: +@@ -84,11 +89,13 @@ vdso_install: + # We use MRPROPER_FILES and CLEAN_FILES now + archclean: + $(Q)$(MAKE) $(clean)=$(boot) ++ $(Q)$(MAKE) $(clean)=$(boot)/dts + + define archhelp + echo '* Image.gz - Compressed kernel image (arch/$(ARCH)/boot/Image.gz)' + echo ' Image - Uncompressed kernel image (arch/$(ARCH)/boot/Image)' + echo '* dtbs - Build device tree blobs for enabled boards' ++ echo ' dtbs_install - Install dtbs to $(INSTALL_DTBS_PATH)' + echo ' install - Install uncompressed kernel' + echo ' zinstall - Install compressed kernel' + echo ' Install using (your) ~/bin/installkernel or' +diff --git a/arch/arm64/boot/dts/Makefile b/arch/arm64/boot/dts/Makefile +index f8001a6..2644389 100644 +--- a/arch/arm64/boot/dts/Makefile ++++ b/arch/arm64/boot/dts/Makefile +@@ -1,6 +1,6 @@ + dtb-$(CONFIG_ARCH_THUNDER) += thunder-88xx.dtb + dtb-$(CONFIG_ARCH_VEXPRESS) += rtsm_ve-aemv8a.dtb foundation-v8.dtb +-dtb-$(CONFIG_ARCH_XGENE) += apm-mustang.dtb ++dtb-$(CONFIG_ARCH_XGENE) += apm-mustang.dtb arm64-nxp-ls2088ardb-r1.dtb + + targets += dtbs + targets += $(dtb-y) +diff --git a/arch/arm64/boot/dts/Makefile.rej b/arch/arm64/boot/dts/Makefile.rej +new file mode 100644 +index 0000000..3610e7d +--- /dev/null ++++ b/arch/arm64/boot/dts/Makefile.rej +@@ -0,0 +1,10 @@ ++--- arch/arm64/boot/dts/Makefile +++++ arch/arm64/boot/dts/Makefile ++@@ -1,6 +1,7 @@ ++ dtb-$(CONFIG_ARCH_THUNDER) += thunder-88xx.dtb ++ dtb-$(CONFIG_ARCH_VEXPRESS) += rtsm_ve-aemv8a.dtb foundation-v8.dtb ++ dtb-$(CONFIG_ARCH_XGENE) += apm-mustang.dtb +++dtb-$(CONFIG_ARCH_LAYERSCAPE) += arm64-nxp-ls2080ardb-r0.dtb ++ ++ targets += dtbs ++ targets += $(dtb-y) +diff --git a/arch/arm64/boot/dts/arm64-nxp-ls2080ardb-r0.dts b/arch/arm64/boot/dts/arm64-nxp-ls2080ardb-r0.dts +new file mode 100644 +index 0000000..5da2834 +--- /dev/null ++++ b/arch/arm64/boot/dts/arm64-nxp-ls2080ardb-r0.dts +@@ -0,0 +1,249 @@ ++/* ++ * Device Tree file for NXP LS2080a RDB board ++ * ++ */ ++ ++/dts-v1/; ++ ++#include "fsl-ls2080a.dtsi" ++ ++/ { ++ model = "arm64-nxp-ls2080ardb-r0"; ++ compatible = "fsl,ls2080a-rdb", "fsl,ls2080a"; ++}; ++ ++&esdhc { ++ status = "okay"; ++}; ++ ++&ifc { ++ status = "okay"; ++ #address-cells = <2>; ++ #size-cells = <1>; ++ ranges = <0x0 0x0 0x5 0x80000000 0x08000000 ++ 0x2 0x0 0x5 0x30000000 0x00010000 ++ 0x3 0x0 0x5 0x20000000 0x00010000>; ++ ++ nor@0,0 { ++ #address-cells = <1>; ++ #size-cells = <1>; ++ compatible = "cfi-flash"; ++ reg = <0x0 0x0 0x8000000>; ++ bank-width = <2>; ++ device-width = <1>; ++ ++ partition@0 { ++ /* SoC RCW, this location must not be altered */ ++ reg = <0x0 0x100000>; ++ label = "rcw (RO)"; ++ read-only; ++ }; ++ ++ partition@1 { ++ /* U-Boot image */ ++ reg = <0x100000 0x100000>; ++ label = "uboot"; ++ }; ++ ++ partition@2 { ++ /* U-Boot environment varialbes, 1MB */ ++ reg = <0x200000 0x100000>; ++ label = "uboot-env"; ++ env_size = <0x20000>; ++ }; ++ ++ partition@3 { ++ /* MC firmware, 4MB*/ ++ reg = <0x300000 0x400000>; ++ label = "mc_firmware"; ++ }; ++ ++ partition@4 { ++ /* MC DPL Blob, 1MB */ ++ reg = <0x700000 0x100000>; ++ label = "mc_dpl_blob"; ++ }; ++ ++ partition@5 { ++ /* MC DPC Blob, 1MB */ ++ reg = <0x800000 0x100000>; ++ label = "mc_dpc_blob"; ++ }; ++ ++ partition@6 { ++ /* AIOP FW, 4MB */ ++ reg = <0x900000 0x400000>; ++ label = "aiop_fw"; ++ }; ++ ++ partition@7 { ++ /* DebugServerFW, 2MB */ ++ reg = <0xd00000 0x200000>; ++ label = "DebugServer_fw"; ++ }; ++ }; ++ ++ nand@2,0 { ++ #address-cells = <1>; ++ #size-cells = <1>; ++ compatible = "fsl,ifc-nand"; ++ reg = <0x2 0x0 0x10000>; ++ }; ++ ++ cpld@3,0 { ++ reg = <0x3 0x0 0x10000>; ++ compatible = "fsl,ls2080a-rdb-qixis", "fsl,fpga-qixis"; ++ }; ++ ++}; ++ ++&i2c0 { ++ status = "okay"; ++ pca9547@75 { ++ compatible = "nxp,pca9547"; ++ reg = <0x75>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ i2c-mux-never-disable; ++ i2c@1 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x01>; ++ rtc@68 { ++ compatible = "dallas,ds3232"; ++ reg = <0x68>; ++ }; ++ }; ++ ++ i2c@3 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x3>; ++ ++ adt7481@4c { ++ compatible = "adi,adt7461"; ++ reg = <0x4c>; ++ }; ++ }; ++ }; ++}; ++ ++&i2c1 { ++ status = "disabled"; ++}; ++ ++&i2c2 { ++ status = "disabled"; ++}; ++ ++&i2c3 { ++ status = "disabled"; ++}; ++ ++&dspi { ++ status = "okay"; ++ dflash0: n25q512a { ++ #address-cells = <1>; ++ #size-cells = <1>; ++ compatible = "st,m25p80"; ++ spi-max-frequency = <3000000>; ++ reg = <0>; ++ }; ++}; ++ ++&qspi { ++ status = "disabled"; ++}; ++ ++&sata0 { ++ status = "okay"; ++}; ++ ++&sata1 { ++ status = "okay"; ++}; ++ ++&usb0 { ++ status = "okay"; ++}; ++ ++&usb1 { ++ status = "okay"; ++}; ++ ++&emdio1 { ++ status = "disabled"; ++ /* CS4340 PHYs */ ++ mdio1_phy1: emdio1_phy@1 { ++ reg = <0x10>; ++ phy-connection-type = "xfi"; ++ }; ++ mdio1_phy2: emdio1_phy@2 { ++ reg = <0x11>; ++ phy-connection-type = "xfi"; ++ }; ++ mdio1_phy3: emdio1_phy@3 { ++ reg = <0x12>; ++ phy-connection-type = "xfi"; ++ }; ++ mdio1_phy4: emdio1_phy@4 { ++ reg = <0x13>; ++ phy-connection-type = "xfi"; ++ }; ++}; ++ ++&emdio2 { ++ /* AQR405 PHYs */ ++ mdio2_phy1: emdio2_phy@1 { ++ compatible = "ethernet-phy-ieee802.3-c45"; ++ interrupts = <0 1 0x4>; /* Level high type */ ++ reg = <0x0>; ++ phy-connection-type = "xfi"; ++ }; ++ mdio2_phy2: emdio2_phy@2 { ++ compatible = "ethernet-phy-ieee802.3-c45"; ++ interrupts = <0 2 0x4>; /* Level high type */ ++ reg = <0x1>; ++ phy-connection-type = "xfi"; ++ }; ++ mdio2_phy3: emdio2_phy@3 { ++ compatible = "ethernet-phy-ieee802.3-c45"; ++ interrupts = <0 4 0x4>; /* Level high type */ ++ reg = <0x2>; ++ phy-connection-type = "xfi"; ++ }; ++ mdio2_phy4: emdio2_phy@4 { ++ compatible = "ethernet-phy-ieee802.3-c45"; ++ interrupts = <0 5 0x4>; /* Level high type */ ++ reg = <0x3>; ++ phy-connection-type = "xfi"; ++ }; ++}; ++ ++/* Update DPMAC connections to external PHYs, under the assumption of ++ * SerDes 0x2a_0x41. This is currently the only SerDes supported on the board. ++ */ ++&dpmac1 { ++ phy-handle = <&mdio1_phy1>; ++}; ++&dpmac2 { ++ phy-handle = <&mdio1_phy2>; ++}; ++&dpmac3 { ++ phy-handle = <&mdio1_phy3>; ++}; ++&dpmac4 { ++ phy-handle = <&mdio1_phy4>; ++}; ++&dpmac5 { ++ phy-handle = <&mdio2_phy1>; ++}; ++&dpmac6 { ++ phy-handle = <&mdio2_phy2>; ++}; ++&dpmac7 { ++ phy-handle = <&mdio2_phy3>; ++}; ++&dpmac8 { ++ phy-handle = <&mdio2_phy4>; ++}; +diff --git a/arch/arm64/boot/dts/arm64-nxp-ls2088ardb-r1.dts b/arch/arm64/boot/dts/arm64-nxp-ls2088ardb-r1.dts +new file mode 100644 +index 0000000..0433cf2 +--- /dev/null ++++ b/arch/arm64/boot/dts/arm64-nxp-ls2088ardb-r1.dts +@@ -0,0 +1,256 @@ ++/* ++ * Device Tree file for NXP LS2088a RDB board ++ * ++ * Copyright (C) 2016, Freescale Semiconductor ++ * ++ * This file is licensed under the terms of the GNU General Public ++ * License version 2. This program is licensed "as is" without any ++ * warranty of any kind, whether express or implied. ++ */ ++ ++/dts-v1/; ++ ++#include "fsl-ls2088a.dtsi" ++ ++/ { ++ model = "arm64-nxp-ls2088ardb-r1"; ++ compatible = "fsl,ls2088a-rdb", "fsl,ls2088a"; ++}; ++ ++&esdhc { ++ status = "okay"; ++}; ++ ++&ifc { ++ status = "okay"; ++ #address-cells = <2>; ++ #size-cells = <1>; ++ ranges = <0x0 0x0 0x5 0x80000000 0x08000000 ++ 0x2 0x0 0x5 0x30000000 0x00010000 ++ 0x3 0x0 0x5 0x20000000 0x00010000>; ++ ++ nor@0,0 { ++ #address-cells = <1>; ++ #size-cells = <1>; ++ compatible = "cfi-flash"; ++ reg = <0x0 0x0 0x8000000>; ++ bank-width = <2>; ++ device-width = <1>; ++ ++ partition@0 { ++ /* SoC RCW, this location must not be altered */ ++ reg = <0x0 0x100000>; ++ label = "rcw (RO)"; ++ read-only; ++ }; ++ ++ partition@1 { ++ /* U-Boot image */ ++ reg = <0x100000 0x100000>; ++ label = "uboot"; ++ }; ++ ++ partition@2 { ++ /* U-Boot environment varialbes, 1MB */ ++ reg = <0x200000 0x100000>; ++ label = "uboot-env"; ++ env_size = <0x20000>; ++ }; ++ ++ partition@3 { ++ /* MC firmware, 4MB*/ ++ reg = <0x300000 0x400000>; ++ label = "mc_firmware"; ++ }; ++ ++ partition@4 { ++ /* MC DPL Blob, 1MB */ ++ reg = <0x700000 0x100000>; ++ label = "mc_dpl_blob"; ++ }; ++ ++ partition@5 { ++ /* MC DPC Blob, 1MB */ ++ reg = <0x800000 0x100000>; ++ label = "mc_dpc_blob"; ++ }; ++ ++ partition@6 { ++ /* AIOP FW, 4MB */ ++ reg = <0x900000 0x400000>; ++ label = "aiop_fw"; ++ }; ++ ++ partition@7 { ++ /* DebugServerFW, 2MB */ ++ reg = <0xd00000 0x200000>; ++ label = "DebugServer_fw"; ++ }; ++ }; ++ ++ nand@2,0 { ++ #address-cells = <1>; ++ #size-cells = <1>; ++ compatible = "fsl,ifc-nand"; ++ reg = <0x2 0x0 0x10000>; ++ }; ++ ++ cpld@3,0 { ++ reg = <0x3 0x0 0x10000>; ++ compatible = "fsl,ls2088a-rdb-qixis", "fsl,fpga-qixis"; ++ }; ++}; ++ ++&ftm0 { ++ status = "okay"; ++}; ++ ++&i2c0 { ++ status = "okay"; ++ pca9547@75 { ++ compatible = "nxp,pca9547"; ++ reg = <0x75>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ i2c-mux-never-disable; ++ i2c@1 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x01>; ++ rtc@68 { ++ compatible = "dallas,ds3232"; ++ reg = <0x68>; ++ }; ++ }; ++ ++ i2c@3 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x3>; ++ ++ adt7481@4c { ++ compatible = "adi,adt7461"; ++ reg = <0x4c>; ++ }; ++ }; ++ }; ++}; ++ ++&i2c1 { ++ status = "disabled"; ++}; ++ ++&i2c2 { ++ status = "disabled"; ++}; ++ ++&i2c3 { ++ status = "disabled"; ++}; ++ ++&dspi { ++ status = "okay"; ++ dflash0: n25q512a { ++ #address-cells = <1>; ++ #size-cells = <1>; ++ compatible = "st,m25p80"; ++ spi-max-frequency = <3000000>; ++ reg = <0>; ++ }; ++}; ++ ++&qspi { ++ status = "disabled"; ++}; ++ ++&sata0 { ++ status = "okay"; ++}; ++ ++&sata1 { ++ status = "okay"; ++}; ++ ++&usb0 { ++ status = "okay"; ++}; ++ ++&usb1 { ++ status = "okay"; ++}; ++ ++&emdio1 { ++ /* CS4340 PHYs */ ++ mdio1_phy1: emdio1_phy@1 { ++ reg = <0x10>; ++ phy-connection-type = "xfi"; ++ }; ++ mdio1_phy2: emdio1_phy@2 { ++ reg = <0x11>; ++ phy-connection-type = "xfi"; ++ }; ++ mdio1_phy3: emdio1_phy@3 { ++ reg = <0x12>; ++ phy-connection-type = "xfi"; ++ }; ++ mdio1_phy4: emdio1_phy@4 { ++ reg = <0x13>; ++ phy-connection-type = "xfi"; ++ }; ++}; ++ ++&emdio2 { ++ /* AQR405 PHYs */ ++ mdio2_phy1: emdio2_phy@1 { ++ compatible = "ethernet-phy-ieee802.3-c45"; ++ interrupts = <0 1 0x4>; /* Level high type */ ++ reg = <0x0>; ++ phy-connection-type = "xfi"; ++ }; ++ mdio2_phy2: emdio2_phy@2 { ++ compatible = "ethernet-phy-ieee802.3-c45"; ++ interrupts = <0 2 0x4>; /* Level high type */ ++ reg = <0x1>; ++ phy-connection-type = "xfi"; ++ }; ++ mdio2_phy3: emdio2_phy@3 { ++ compatible = "ethernet-phy-ieee802.3-c45"; ++ interrupts = <0 4 0x4>; /* Level high type */ ++ reg = <0x2>; ++ phy-connection-type = "xfi"; ++ }; ++ mdio2_phy4: emdio2_phy@4 { ++ compatible = "ethernet-phy-ieee802.3-c45"; ++ interrupts = <0 5 0x4>; /* Level high type */ ++ reg = <0x3>; ++ phy-connection-type = "xfi"; ++ }; ++}; ++ ++/* Update DPMAC connections to external PHYs, under the assumption of ++ * SerDes 0x2a_0x41. This is currently the only SerDes supported on the board. ++ */ ++&dpmac1 { ++ phy-handle = <&mdio1_phy1>; ++}; ++&dpmac2 { ++ phy-handle = <&mdio1_phy2>; ++}; ++&dpmac3 { ++ phy-handle = <&mdio1_phy3>; ++}; ++&dpmac4 { ++ phy-handle = <&mdio1_phy4>; ++}; ++&dpmac5 { ++ phy-handle = <&mdio2_phy1>; ++}; ++&dpmac6 { ++ phy-handle = <&mdio2_phy2>; ++}; ++&dpmac7 { ++ phy-handle = <&mdio2_phy3>; ++}; ++&dpmac8 { ++ phy-handle = <&mdio2_phy4>; ++}; +diff --git a/arch/arm64/boot/dts/fsl-ls2080a.dtsi b/arch/arm64/boot/dts/fsl-ls2080a.dtsi +new file mode 100644 +index 0000000..5e53b04 +--- /dev/null ++++ b/arch/arm64/boot/dts/fsl-ls2080a.dtsi +@@ -0,0 +1,729 @@ ++/* ++ * Device Tree Include file for Freescale Layerscape-2080A family SoC. ++ * ++ * Copyright (C) 2014-2015, Freescale Semiconductor ++ * ++ * Bhupesh Sharma ++ * Harninder Rai ++ * ++ * This file is licensed under the terms of the GNU General Public ++ * License version 2. This program is licensed "as is" without any ++ * warranty of any kind, whether express or implied. ++ */ ++ ++#include ++ ++/memreserve/ 0x80000000 0x00010000; ++ ++/ { ++ compatible = "fsl,ls2080a"; ++ interrupt-parent = <&gic>; ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ++ cpus { ++ #address-cells = <2>; ++ #size-cells = <0>; ++ ++ /* We have 4 clusters having 2 Cortex-A57 cores each */ ++ cpu0: cpu@0 { ++ device_type = "cpu"; ++ compatible = "arm,cortex-a57"; ++ reg = <0x0 0x0>; ++ clocks = <&clockgen 1 0>; ++ #cooling-cells = <2>; ++ }; ++ ++ cpu1: cpu@1 { ++ device_type = "cpu"; ++ compatible = "arm,cortex-a57"; ++ reg = <0x0 0x1>; ++ clocks = <&clockgen 1 0>; ++ }; ++ ++ cpu2: cpu@100 { ++ device_type = "cpu"; ++ compatible = "arm,cortex-a57"; ++ reg = <0x0 0x100>; ++ clocks = <&clockgen 1 1>; ++ #cooling-cells = <2>; ++ }; ++ ++ cpu3: cpu@101 { ++ device_type = "cpu"; ++ compatible = "arm,cortex-a57"; ++ reg = <0x0 0x101>; ++ clocks = <&clockgen 1 1>; ++ }; ++ ++ cpu4: cpu@200 { ++ device_type = "cpu"; ++ compatible = "arm,cortex-a57"; ++ reg = <0x0 0x200>; ++ clocks = <&clockgen 1 2>; ++ #cooling-cells = <2>; ++ }; ++ ++ cpu5: cpu@201 { ++ device_type = "cpu"; ++ compatible = "arm,cortex-a57"; ++ reg = <0x0 0x201>; ++ clocks = <&clockgen 1 2>; ++ }; ++ ++ cpu6: cpu@300 { ++ device_type = "cpu"; ++ compatible = "arm,cortex-a57"; ++ reg = <0x0 0x300>; ++ clocks = <&clockgen 1 3>; ++ #cooling-cells = <2>; ++ }; ++ ++ cpu7: cpu@301 { ++ device_type = "cpu"; ++ compatible = "arm,cortex-a57"; ++ reg = <0x0 0x301>; ++ clocks = <&clockgen 1 3>; ++ }; ++ }; ++ ++ pmu { ++ compatible = "arm,armv8-pmuv3"; ++ interrupts = <1 7 0x8>; /* PMU PPI, Level low type */ ++ }; ++ ++ gic: interrupt-controller@6000000 { ++ compatible = "arm,gic-v3"; ++ reg = <0x0 0x06000000 0 0x10000>, /* GIC Dist */ ++ <0x0 0x06100000 0 0x100000>, /* GICR (RD_base + SGI_base) */ ++ <0x0 0x0c0c0000 0 0x2000>, /* GICC */ ++ <0x0 0x0c0d0000 0 0x1000>, /* GICH */ ++ <0x0 0x0c0e0000 0 0x20000>; /* GICV */ ++ #interrupt-cells = <3>; ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ranges; ++ interrupt-controller; ++ interrupts = <1 9 0x4>; ++ ++ its: gic-its@6020000 { ++ compatible = "arm,gic-v3-its"; ++ msi-controller; ++ reg = <0x0 0x6020000 0 0x20000>; ++ }; ++ }; ++ ++ sysclk: sysclk { ++ compatible = "fixed-clock"; ++ #clock-cells = <0>; ++ clock-frequency = <100000000>; ++ clock-output-names = "sysclk"; ++ }; ++ ++ clockgen: clocking@1300000 { ++ compatible = "fsl,ls2080a-clockgen"; ++ reg = <0 0x1300000 0 0xa0000>; ++ #clock-cells = <2>; ++ clocks = <&sysclk>; ++ }; ++ ++ tmu: tmu@1f80000 { ++ compatible = "fsl,qoriq-tmu", "fsl,ls2080a-tmu"; ++ reg = <0x0 0x1f80000 0x0 0x10000>; ++ interrupts = <0 23 0x4>; ++ fsl,tmu-range = <0xb0000 0x9002a 0x6004c 0x30062>; ++ fsl,tmu-calibration = <0x00000000 0x00000026 ++ 0x00000001 0x0000002d ++ 0x00000002 0x00000032 ++ 0x00000003 0x00000039 ++ 0x00000004 0x0000003f ++ 0x00000005 0x00000046 ++ 0x00000006 0x0000004d ++ 0x00000007 0x00000054 ++ 0x00000008 0x0000005a ++ 0x00000009 0x00000061 ++ 0x0000000a 0x0000006a ++ 0x0000000b 0x00000071 ++ ++ 0x00010000 0x00000025 ++ 0x00010001 0x0000002c ++ 0x00010002 0x00000035 ++ 0x00010003 0x0000003d ++ 0x00010004 0x00000045 ++ 0x00010005 0x0000004e ++ 0x00010006 0x00000057 ++ 0x00010007 0x00000061 ++ 0x00010008 0x0000006b ++ 0x00010009 0x00000076 ++ ++ 0x00020000 0x00000029 ++ 0x00020001 0x00000033 ++ 0x00020002 0x0000003d ++ 0x00020003 0x00000049 ++ 0x00020004 0x00000056 ++ 0x00020005 0x00000061 ++ 0x00020006 0x0000006d ++ ++ 0x00030000 0x00000021 ++ 0x00030001 0x0000002a ++ 0x00030002 0x0000003c ++ 0x00030003 0x0000004e>; ++ little-endian; ++ #thermal-sensor-cells = <1>; ++ }; ++ ++ thermal-zones { ++ cpu_thermal: cpu-thermal { ++ polling-delay-passive = <1000>; ++ polling-delay = <5000>; ++ ++ thermal-sensors = <&tmu 4>; ++ ++ trips { ++ cpu_alert: cpu-alert { ++ temperature = <75000>; ++ hysteresis = <2000>; ++ type = "passive"; ++ }; ++ cpu_crit: cpu-crit { ++ temperature = <85000>; ++ hysteresis = <2000>; ++ type = "critical"; ++ }; ++ }; ++ ++ cooling-maps { ++ map0 { ++ trip = <&cpu_alert>; ++ cooling-device = ++ <&cpu0 THERMAL_NO_LIMIT ++ THERMAL_NO_LIMIT>; ++ }; ++ map1 { ++ trip = <&cpu_alert>; ++ cooling-device = ++ <&cpu2 THERMAL_NO_LIMIT ++ THERMAL_NO_LIMIT>; ++ }; ++ map2 { ++ trip = <&cpu_alert>; ++ cooling-device = ++ <&cpu4 THERMAL_NO_LIMIT ++ THERMAL_NO_LIMIT>; ++ }; ++ map3 { ++ trip = <&cpu_alert>; ++ cooling-device = ++ <&cpu6 THERMAL_NO_LIMIT ++ THERMAL_NO_LIMIT>; ++ }; ++ }; ++ }; ++ }; ++ ++ serial0: serial@21c0500 { ++ device_type = "serial"; ++ compatible = "fsl,ns16550", "ns16550a"; ++ reg = <0x0 0x21c0500 0x0 0x100>; ++ clocks = <&clockgen 4 3>; ++ interrupts = <0 32 0x4>; /* Level high type */ ++ }; ++ ++ serial1: serial@21c0600 { ++ device_type = "serial"; ++ compatible = "fsl,ns16550", "ns16550a"; ++ reg = <0x0 0x21c0600 0x0 0x100>; ++ clocks = <&clockgen 4 3>; ++ interrupts = <0 32 0x4>; /* Level high type */ ++ }; ++ ++ gpio0: gpio@2300000 { ++ compatible = "fsl,qoriq-gpio"; ++ reg = <0x0 0x2300000 0x0 0x10000>; ++ interrupts = <0 36 0x4>; /* Level high type */ ++ gpio-controller; ++ little-endian; ++ #gpio-cells = <2>; ++ interrupt-controller; ++ #interrupt-cells = <2>; ++ }; ++ ++ gpio1: gpio@2310000 { ++ compatible = "fsl,qoriq-gpio"; ++ reg = <0x0 0x2310000 0x0 0x10000>; ++ interrupts = <0 36 0x4>; /* Level high type */ ++ gpio-controller; ++ little-endian; ++ #gpio-cells = <2>; ++ interrupt-controller; ++ #interrupt-cells = <2>; ++ }; ++ ++ gpio2: gpio@2320000 { ++ compatible = "fsl,qoriq-gpio"; ++ reg = <0x0 0x2320000 0x0 0x10000>; ++ interrupts = <0 37 0x4>; /* Level high type */ ++ gpio-controller; ++ little-endian; ++ #gpio-cells = <2>; ++ interrupt-controller; ++ #interrupt-cells = <2>; ++ }; ++ ++ gpio3: gpio@2330000 { ++ compatible = "fsl,qoriq-gpio"; ++ reg = <0x0 0x2330000 0x0 0x10000>; ++ interrupts = <0 37 0x4>; /* Level high type */ ++ gpio-controller; ++ little-endian; ++ #gpio-cells = <2>; ++ interrupt-controller; ++ #interrupt-cells = <2>; ++ }; ++ ++ /* TODO: WRIOP (CCSR?) */ ++ emdio1: mdio@0x8B96000 { /* WRIOP0: 0x8B8_0000, E-MDIO1: 0x1_6000 */ ++ compatible = "fsl,fman-memac-mdio"; ++ reg = <0x0 0x8B96000 0x0 0x1000>; ++ device_type = "mdio"; /* TODO: is this necessary? */ ++ little-endian; /* force the driver in LE mode */ ++ ++ /* Not necessary on the QDS, but needed on the RDB */ ++ #address-cells = <1>; ++ #size-cells = <0>; ++ }; ++ ++ emdio2: mdio@0x8B97000 { /* WRIOP0: 0x8B8_0000, E-MDIO2: 0x1_7000 */ ++ compatible = "fsl,fman-memac-mdio"; ++ reg = <0x0 0x8B97000 0x0 0x1000>; ++ device_type = "mdio"; /* TODO: is this necessary? */ ++ little-endian; /* force the driver in LE mode */ ++ ++ #address-cells = <1>; ++ #size-cells = <0>; ++ }; ++ ++ ifc: ifc@2240000 { ++ compatible = "fsl,ifc", "simple-bus"; ++ reg = <0x0 0x2240000 0x0 0x20000>; ++ interrupts = <0 21 0x4>; /* Level high type */ ++ little-endian; ++ #address-cells = <2>; ++ #size-cells = <1>; ++ ++ ranges = <0 0 0x5 0x80000000 0x08000000 ++ 2 0 0x5 0x30000000 0x00010000 ++ 3 0 0x5 0x20000000 0x00010000>; ++ }; ++ ++ esdhc: esdhc@2140000 { ++ compatible = "fsl,ls2080a-esdhc", "fsl,esdhc"; ++ reg = <0x0 0x2140000 0x0 0x10000>; ++ interrupts = <0 28 0x4>; /* Level high type */ ++ clock-frequency = <0>; ++ voltage-ranges = <1800 1800 3300 3300>; ++ sdhci,auto-cmd12; ++ little-endian; ++ bus-width = <4>; ++ }; ++ ++ ftm0: ftm0@2800000 { ++ compatible = "fsl,ftm-alarm"; ++ reg = <0x0 0x2800000 0x0 0x10000>; ++ interrupts = <0 44 4>; ++ }; ++ ++ reset: reset@1E60000 { ++ compatible = "fsl,ls-reset"; ++ reg = <0x0 0x1E60000 0x0 0x10000>; ++ }; ++ ++ dspi: dspi@2100000 { ++ compatible = "fsl,ls2085a-dspi", "fsl,ls2080a-dspi"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x2100000 0x0 0x10000>; ++ interrupts = <0 26 0x4>; /* Level high type */ ++ clocks = <&clockgen 4 3>; ++ clock-names = "dspi"; ++ spi-num-chipselects = <5>; ++ bus-num = <0>; ++ }; ++ ++ i2c0: i2c@2000000 { ++ compatible = "fsl,vf610-i2c"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x2000000 0x0 0x10000>; ++ interrupts = <0 34 0x4>; /* Level high type */ ++ clock-names = "i2c"; ++ clocks = <&clockgen 4 3>; ++ }; ++ ++ i2c1: i2c@2010000 { ++ compatible = "fsl,vf610-i2c"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x2010000 0x0 0x10000>; ++ interrupts = <0 34 0x4>; /* Level high type */ ++ clock-names = "i2c"; ++ clocks = <&clockgen 4 3>; ++ }; ++ ++ i2c2: i2c@2020000 { ++ compatible = "fsl,vf610-i2c"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x2020000 0x0 0x10000>; ++ interrupts = <0 35 0x4>; /* Level high type */ ++ clock-names = "i2c"; ++ clocks = <&clockgen 4 3>; ++ }; ++ ++ i2c3: i2c@2030000 { ++ compatible = "fsl,vf610-i2c"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x2030000 0x0 0x10000>; ++ interrupts = <0 35 0x4>; /* Level high type */ ++ clock-names = "i2c"; ++ clocks = <&clockgen 4 3>; ++ }; ++ ++ qspi: quadspi@20c0000 { ++ compatible = "fsl,ls2080a-qspi"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x20c0000 0x0 0x10000>, ++ <0x0 0x20000000 0x0 0x10000000>; ++ reg-names = "QuadSPI", "QuadSPI-memory"; ++ interrupts = <0 25 0x4>; /* Level high type */ ++ clocks = <&clockgen 4 3>, <&clockgen 4 3>; ++ clock-names = "qspi_en", "qspi"; ++ }; ++ ++ pcie@3400000 { ++ compatible = "fsl,ls2080a-pcie", "fsl,ls2085a-pcie", ++ "snps,dw-pcie"; ++ reg = <0x00 0x03400000 0x0 0x00100000 /* controller registers */ ++ 0x10 0x00000000 0x0 0x00001000>; /* configuration space */ ++ reg-names = "regs", "config"; ++ interrupts = <0 108 0x4>; /* Level high type */ ++ interrupt-names = "intr"; ++ #address-cells = <3>; ++ #size-cells = <2>; ++ device_type = "pci"; ++ num-lanes = <4>; ++ bus-range = <0x0 0xff>; ++ ranges = <0x81000000 0x0 0x00000000 0x10 0x00010000 0x0 0x00010000 /* downstream I/O */ ++ 0x82000000 0x0 0x40000000 0x10 0x40000000 0x0 0x40000000>; /* non-prefetchable memory */ ++ msi-parent = <&its>; ++ #interrupt-cells = <1>; ++ interrupt-map-mask = <0 0 0 7>; ++ interrupt-map = <0000 0 0 1 &gic 0 0 0 109 4>, ++ <0000 0 0 2 &gic 0 0 0 110 4>, ++ <0000 0 0 3 &gic 0 0 0 111 4>, ++ <0000 0 0 4 &gic 0 0 0 112 4>; ++ }; ++ ++ pcie@3500000 { ++ compatible = "fsl,ls2080a-pcie", "fsl,ls2085a-pcie", ++ "snps,dw-pcie"; ++ reg = <0x00 0x03500000 0x0 0x00100000 /* controller registers */ ++ 0x12 0x00000000 0x0 0x00001000>; /* configuration space */ ++ reg-names = "regs", "config"; ++ interrupts = <0 113 0x4>; /* Level high type */ ++ interrupt-names = "intr"; ++ #address-cells = <3>; ++ #size-cells = <2>; ++ device_type = "pci"; ++ num-lanes = <4>; ++ bus-range = <0x0 0xff>; ++ ranges = <0x81000000 0x0 0x00000000 0x12 0x00010000 0x0 0x00010000 /* downstream I/O */ ++ 0x82000000 0x0 0x40000000 0x12 0x40000000 0x0 0x40000000>; /* non-prefetchable memory */ ++ msi-parent = <&its>; ++ #interrupt-cells = <1>; ++ interrupt-map-mask = <0 0 0 7>; ++ interrupt-map = <0000 0 0 1 &gic 0 0 0 114 4>, ++ <0000 0 0 2 &gic 0 0 0 115 4>, ++ <0000 0 0 3 &gic 0 0 0 116 4>, ++ <0000 0 0 4 &gic 0 0 0 117 4>; ++ }; ++ ++ pcie@3600000 { ++ compatible = "fsl,ls2080a-pcie", "fsl,ls2085a-pcie", ++ "snps,dw-pcie"; ++ reg = <0x00 0x03600000 0x0 0x00100000 /* controller registers */ ++ 0x14 0x00000000 0x0 0x00001000>; /* configuration space */ ++ reg-names = "regs", "config"; ++ interrupts = <0 118 0x4>; /* Level high type */ ++ interrupt-names = "intr"; ++ #address-cells = <3>; ++ #size-cells = <2>; ++ device_type = "pci"; ++ num-lanes = <8>; ++ bus-range = <0x0 0xff>; ++ ranges = <0x81000000 0x0 0x00000000 0x14 0x00010000 0x0 0x00010000 /* downstream I/O */ ++ 0x82000000 0x0 0x40000000 0x14 0x40000000 0x0 0x40000000>; /* non-prefetchable memory */ ++ msi-parent = <&its>; ++ #interrupt-cells = <1>; ++ interrupt-map-mask = <0 0 0 7>; ++ interrupt-map = <0000 0 0 1 &gic 0 0 0 119 4>, ++ <0000 0 0 2 &gic 0 0 0 120 4>, ++ <0000 0 0 3 &gic 0 0 0 121 4>, ++ <0000 0 0 4 &gic 0 0 0 122 4>; ++ }; ++ ++ pcie@3700000 { ++ compatible = "fsl,ls2080a-pcie", "fsl,ls2085a-pcie", ++ "snps,dw-pcie"; ++ reg = <0x00 0x03700000 0x0 0x00100000 /* controller registers */ ++ 0x16 0x00000000 0x0 0x00001000>; /* configuration space */ ++ reg-names = "regs", "config"; ++ interrupts = <0 123 0x4>; /* Level high type */ ++ interrupt-names = "intr"; ++ #address-cells = <3>; ++ #size-cells = <2>; ++ device_type = "pci"; ++ num-lanes = <4>; ++ bus-range = <0x0 0xff>; ++ ranges = <0x81000000 0x0 0x00000000 0x16 0x00010000 0x0 0x00010000 /* downstream I/O */ ++ 0x82000000 0x0 0x40000000 0x16 0x40000000 0x0 0x40000000>; /* non-prefetchable memory */ ++ msi-parent = <&its>; ++ #interrupt-cells = <1>; ++ interrupt-map-mask = <0 0 0 7>; ++ interrupt-map = <0000 0 0 1 &gic 0 0 0 124 4>, ++ <0000 0 0 2 &gic 0 0 0 125 4>, ++ <0000 0 0 3 &gic 0 0 0 126 4>, ++ <0000 0 0 4 &gic 0 0 0 127 4>; ++ }; ++ ++ sata0: sata@3200000 { ++ compatible = "fsl,ls2080a-ahci", "fsl,ls1021a-ahci"; ++ reg = <0x0 0x3200000 0x0 0x10000>; ++ interrupts = <0 133 0x4>; /* Level high type */ ++ clocks = <&clockgen 4 3>; ++ }; ++ ++ sata1: sata@3210000 { ++ compatible = "fsl,ls2080a-ahci", "fsl,ls1021a-ahci"; ++ reg = <0x0 0x3210000 0x0 0x10000>; ++ interrupts = <0 136 0x4>; /* Level high type */ ++ clocks = <&clockgen 4 3>; ++ }; ++ ++ usb0: usb3@3100000 { ++ compatible = "snps,dwc3"; ++ reg = <0x0 0x3100000 0x0 0x10000>; ++ interrupts = <0 80 0x4>; /* Level high type */ ++ dr_mode = "host"; ++ configure-gfladj; ++ }; ++ ++ usb1: usb3@3110000 { ++ compatible = "snps,dwc3"; ++ reg = <0x0 0x3110000 0x0 0x10000>; ++ interrupts = <0 81 0x4>; /* Level high type */ ++ dr_mode = "host"; ++ configure-gfladj; ++ }; ++ ++ smmu: iommu@5000000 { ++ compatible = "arm,mmu-500"; ++ reg = <0 0x5000000 0 0x800000>; ++ #global-interrupts = <12>; ++ interrupts = <0 13 4>, /* global secure fault */ ++ <0 14 4>, /* combined secure interrupt */ ++ <0 15 4>, /* global non-secure fault */ ++ <0 16 4>, /* combined non-secure interrupt */ ++ /* performance counter interrupts 0-7 */ ++ <0 211 4>, ++ <0 212 4>, ++ <0 213 4>, ++ <0 214 4>, ++ <0 215 4>, ++ <0 216 4>, ++ <0 217 4>, ++ <0 218 4>, ++ /* per context interrupt, 64 interrupts */ ++ <0 146 4>, ++ <0 147 4>, ++ <0 148 4>, ++ <0 149 4>, ++ <0 150 4>, ++ <0 151 4>, ++ <0 152 4>, ++ <0 153 4>, ++ <0 154 4>, ++ <0 155 4>, ++ <0 156 4>, ++ <0 157 4>, ++ <0 158 4>, ++ <0 159 4>, ++ <0 160 4>, ++ <0 161 4>, ++ <0 162 4>, ++ <0 163 4>, ++ <0 164 4>, ++ <0 165 4>, ++ <0 166 4>, ++ <0 167 4>, ++ <0 168 4>, ++ <0 169 4>, ++ <0 170 4>, ++ <0 171 4>, ++ <0 172 4>, ++ <0 173 4>, ++ <0 174 4>, ++ <0 175 4>, ++ <0 176 4>, ++ <0 177 4>, ++ <0 178 4>, ++ <0 179 4>, ++ <0 180 4>, ++ <0 181 4>, ++ <0 182 4>, ++ <0 183 4>, ++ <0 184 4>, ++ <0 185 4>, ++ <0 186 4>, ++ <0 187 4>, ++ <0 188 4>, ++ <0 189 4>, ++ <0 190 4>, ++ <0 191 4>, ++ <0 192 4>, ++ <0 193 4>, ++ <0 194 4>, ++ <0 195 4>, ++ <0 196 4>, ++ <0 197 4>, ++ <0 198 4>, ++ <0 199 4>, ++ <0 200 4>, ++ <0 201 4>, ++ <0 202 4>, ++ <0 203 4>, ++ <0 204 4>, ++ <0 205 4>, ++ <0 206 4>, ++ <0 207 4>, ++ <0 208 4>, ++ <0 209 4>; ++ mmu-masters = <&fsl_mc 0x300 0>; ++ }; ++ ++ timer { ++ compatible = "arm,armv8-timer"; ++ interrupts = <1 13 0x1>, /* Physical Secure PPI, edge triggered */ ++ <1 14 0x1>, /* Physical Non-Secure PPI, edge triggered */ ++ <1 11 0x1>, /* Virtual PPI, edge triggered */ ++ <1 10 0x1>; /* Hypervisor PPI, edge triggered */ ++ arm,reread-timer; ++ }; ++ ++ fsl_mc: fsl-mc@80c000000 { ++ compatible = "fsl,qoriq-mc"; ++ #stream-id-cells = <2>; ++ reg = <0x00000008 0x0c000000 0 0x40>, /* MC portal base */ ++ <0x00000000 0x08340000 0 0x40000>; /* MC control reg */ ++ msi-parent = <&its>; ++ #address-cells = <3>; ++ #size-cells = <1>; ++ ++ /* ++ * Region type 0x0 - MC portals ++ * Region type 0x1 - QBMAN portals ++ */ ++ ranges = <0x0 0x0 0x0 0x8 0x0c000000 0x4000000 ++ 0x1 0x0 0x0 0x8 0x18000000 0x8000000>; ++ ++ /* ++ * Define the maximum number of MACs present on the SoC. ++ * They won't necessarily be all probed, since the ++ * Data Path Layout file and the MC firmware can put fewer ++ * actual DPMAC objects on the MC bus. ++ */ ++ dpmacs { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ dpmac1: dpmac@1 { ++ compatible = "fsl,qoriq-mc-dpmac"; ++ reg = <1>; ++ }; ++ dpmac2: dpmac@2 { ++ compatible = "fsl,qoriq-mc-dpmac"; ++ reg = <2>; ++ }; ++ dpmac3: dpmac@3 { ++ compatible = "fsl,qoriq-mc-dpmac"; ++ reg = <3>; ++ }; ++ dpmac4: dpmac@4 { ++ compatible = "fsl,qoriq-mc-dpmac"; ++ reg = <4>; ++ }; ++ dpmac5: dpmac@5 { ++ compatible = "fsl,qoriq-mc-dpmac"; ++ reg = <5>; ++ }; ++ dpmac6: dpmac@6 { ++ compatible = "fsl,qoriq-mc-dpmac"; ++ reg = <6>; ++ }; ++ dpmac7: dpmac@7 { ++ compatible = "fsl,qoriq-mc-dpmac"; ++ reg = <7>; ++ }; ++ dpmac8: dpmac@8 { ++ compatible = "fsl,qoriq-mc-dpmac"; ++ reg = <8>; ++ }; ++ dpmac9: dpmac@9 { ++ compatible = "fsl,qoriq-mc-dpmac"; ++ reg = <9>; ++ }; ++ dpmac10: dpmac@10 { ++ compatible = "fsl,qoriq-mc-dpmac"; ++ reg = <0xa>; ++ }; ++ dpmac11: dpmac@11 { ++ compatible = "fsl,qoriq-mc-dpmac"; ++ reg = <0xb>; ++ }; ++ dpmac12: dpmac@12 { ++ compatible = "fsl,qoriq-mc-dpmac"; ++ reg = <0xc>; ++ }; ++ dpmac13: dpmac@13 { ++ compatible = "fsl,qoriq-mc-dpmac"; ++ reg = <0xd>; ++ }; ++ dpmac14: dpmac@14 { ++ compatible = "fsl,qoriq-mc-dpmac"; ++ reg = <0xe>; ++ }; ++ dpmac15: dpmac@15 { ++ compatible = "fsl,qoriq-mc-dpmac"; ++ reg = <0xf>; ++ }; ++ dpmac16: dpmac@16 { ++ compatible = "fsl,qoriq-mc-dpmac"; ++ reg = <0x10>; ++ }; ++ }; ++ }; ++ ++ ccn@4000000 { ++ compatible = "arm,ccn-504"; ++ reg = <0x0 0x04000000 0x0 0x01000000>; ++ interrupts = <0 12 4>; ++ }; ++ ++ memory@80000000 { ++ device_type = "memory"; ++ reg = <0x00000000 0x80000000 0 0x80000000>; ++ /* DRAM space 1 - 2 GB DRAM */ ++ }; ++}; +diff --git a/arch/arm64/boot/dts/fsl-ls2088a.dtsi b/arch/arm64/boot/dts/fsl-ls2088a.dtsi +new file mode 100644 +index 0000000..2e3529a +--- /dev/null ++++ b/arch/arm64/boot/dts/fsl-ls2088a.dtsi +@@ -0,0 +1,833 @@ ++/* ++ * Device Tree Include file for Freescale Layerscape-2088A family SoC. ++ * ++ * Copyright (C) 2016, Freescale Semiconductor ++ * ++ * Abhimanyu Saini ++ * ++ * This file is dual-licensed: you can use it either under the terms ++ * of the GPLv2 or the X11 license, at your option. Note that this dual ++ * licensing only applies to this file, and not this project as a ++ * whole. ++ * ++ * a) This library is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License as ++ * published by the Free Software Foundation; either version 2 of the ++ * License, or (at your option) any later version. ++ * ++ * This library is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * Or, alternatively, ++ * ++ * b) Permission is hereby granted, free of charge, to any person ++ * obtaining a copy of this software and associated documentation ++ * files (the "Software"), to deal in the Software without ++ * restriction, including without limitation the rights to use, ++ * copy, modify, merge, publish, distribute, sublicense, and/or ++ * sell copies of the Software, and to permit persons to whom the ++ * Software is furnished to do so, subject to the following ++ * conditions: ++ * ++ * The above copyright notice and this permission notice shall be ++ * included in all copies or substantial portions of the Software. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, ++ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES ++ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND ++ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT ++ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, ++ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING ++ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR ++ * OTHER DEALINGS IN THE SOFTWARE. ++ */ ++ ++#include "thermal.h" ++ ++/memreserve/ 0x80000000 0x00010000; ++ ++/ { ++ compatible = "fsl,ls2088a"; ++ interrupt-parent = <&gic>; ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ++ cpus { ++ #address-cells = <2>; ++ #size-cells = <0>; ++ ++ cpu0: cpu@0 { ++ device_type = "cpu"; ++ compatible = "arm,cortex-a72"; ++ reg = <0x0 0x0>; ++ clocks = <&clockgen 1 0>; ++ #cooling-cells = <2>; ++ }; ++ ++ cpu1: cpu@1 { ++ device_type = "cpu"; ++ compatible = "arm,cortex-a72"; ++ reg = <0x0 0x1>; ++ clocks = <&clockgen 1 0>; ++ }; ++ ++ cpu2: cpu@100 { ++ device_type = "cpu"; ++ compatible = "arm,cortex-a72"; ++ reg = <0x0 0x100>; ++ clocks = <&clockgen 1 1>; ++ #cooling-cells = <2>; ++ }; ++ ++ cpu3: cpu@101 { ++ device_type = "cpu"; ++ compatible = "arm,cortex-a72"; ++ reg = <0x0 0x101>; ++ clocks = <&clockgen 1 1>; ++ }; ++ ++ cpu4: cpu@200 { ++ device_type = "cpu"; ++ compatible = "arm,cortex-a72"; ++ reg = <0x0 0x200>; ++ clocks = <&clockgen 1 2>; ++ #cooling-cells = <2>; ++ }; ++ ++ cpu5: cpu@201 { ++ device_type = "cpu"; ++ compatible = "arm,cortex-a72"; ++ reg = <0x0 0x201>; ++ clocks = <&clockgen 1 2>; ++ }; ++ ++ cpu6: cpu@300 { ++ device_type = "cpu"; ++ compatible = "arm,cortex-a72"; ++ reg = <0x0 0x300>; ++ clocks = <&clockgen 1 3>; ++ #cooling-cells = <2>; ++ }; ++ ++ cpu7: cpu@301 { ++ device_type = "cpu"; ++ compatible = "arm,cortex-a72"; ++ reg = <0x0 0x301>; ++ clocks = <&clockgen 1 3>; ++ }; ++ }; ++ ++ pmu { ++ compatible = "arm,armv8-pmuv3"; ++ interrupts = <1 7 0x8>; /* PMU PPI, Level low type */ ++ }; ++ ++ gic: interrupt-controller@6000000 { ++ compatible = "arm,gic-v3"; ++ reg = <0x0 0x06000000 0 0x10000>, /* GIC Dist */ ++ <0x0 0x06100000 0 0x100000>, /* GICR (RD_base + SGI_base) */ ++ <0x0 0x0c0c0000 0 0x2000>, /* GICC */ ++ <0x0 0x0c0d0000 0 0x1000>, /* GICH */ ++ <0x0 0x0c0e0000 0 0x20000>; /* GICV */ ++ #interrupt-cells = <3>; ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ranges; ++ interrupt-controller; ++ interrupts = <1 9 0x4>; ++ ++ its: gic-its@6020000 { ++ compatible = "arm,gic-v3-its"; ++ msi-controller; ++ reg = <0x0 0x6020000 0 0x20000>; ++ }; ++ }; ++ ++ sysclk: sysclk { ++ compatible = "fixed-clock"; ++ #clock-cells = <0>; ++ clock-frequency = <100000000>; ++ clock-output-names = "sysclk"; ++ }; ++ ++ clockgen: clocking@1300000 { ++ compatible = "fsl,ls2088a-clockgen"; ++ reg = <0 0x1300000 0 0xa0000>; ++ #clock-cells = <2>; ++ clocks = <&sysclk>; ++ }; ++ ++ tmu: tmu@1f80000 { ++ compatible = "fsl,qoriq-tmu", "fsl,ls2080a-tmu", "fsl,ls2088a-tmu"; ++ reg = <0x0 0x1f80000 0x0 0x10000>; ++ interrupts = <0 23 0x4>; ++ fsl,tmu-range = <0xb0000 0x9002a 0x6004c 0x30062>; ++ fsl,tmu-calibration = <0x00000000 0x00000026 ++ 0x00000001 0x0000002d ++ 0x00000002 0x00000032 ++ 0x00000003 0x00000039 ++ 0x00000004 0x0000003f ++ 0x00000005 0x00000046 ++ 0x00000006 0x0000004d ++ 0x00000007 0x00000054 ++ 0x00000008 0x0000005a ++ 0x00000009 0x00000061 ++ 0x0000000a 0x0000006a ++ 0x0000000b 0x00000071 ++ ++ 0x00010000 0x00000025 ++ 0x00010001 0x0000002c ++ 0x00010002 0x00000035 ++ 0x00010003 0x0000003d ++ 0x00010004 0x00000045 ++ 0x00010005 0x0000004e ++ 0x00010006 0x00000057 ++ 0x00010007 0x00000061 ++ 0x00010008 0x0000006b ++ 0x00010009 0x00000076 ++ ++ 0x00020000 0x00000029 ++ 0x00020001 0x00000033 ++ 0x00020002 0x0000003d ++ 0x00020003 0x00000049 ++ 0x00020004 0x00000056 ++ 0x00020005 0x00000061 ++ 0x00020006 0x0000006d ++ ++ 0x00030000 0x00000021 ++ 0x00030001 0x0000002a ++ 0x00030002 0x0000003c ++ 0x00030003 0x0000004e>; ++ little-endian; ++ #thermal-sensor-cells = <1>; ++ }; ++ ++ thermal-zones { ++ cpu_thermal: cpu-thermal { ++ polling-delay-passive = <1000>; ++ polling-delay = <5000>; ++ ++ thermal-sensors = <&tmu 4>; ++ ++ trips { ++ cpu_alert: cpu-alert { ++ temperature = <75000>; ++ hysteresis = <2000>; ++ type = "passive"; ++ }; ++ cpu_crit: cpu-crit { ++ temperature = <85000>; ++ hysteresis = <2000>; ++ type = "critical"; ++ }; ++ }; ++ ++ cooling-maps { ++ map0 { ++ trip = <&cpu_alert>; ++ cooling-device = ++ <&cpu0 THERMAL_NO_LIMIT ++ THERMAL_NO_LIMIT>; ++ }; ++ map1 { ++ trip = <&cpu_alert>; ++ cooling-device = ++ <&cpu2 THERMAL_NO_LIMIT ++ THERMAL_NO_LIMIT>; ++ }; ++ map2 { ++ trip = <&cpu_alert>; ++ cooling-device = ++ <&cpu4 THERMAL_NO_LIMIT ++ THERMAL_NO_LIMIT>; ++ }; ++ map3 { ++ trip = <&cpu_alert>; ++ cooling-device = ++ <&cpu6 THERMAL_NO_LIMIT ++ THERMAL_NO_LIMIT>; ++ }; ++ }; ++ }; ++ }; ++ ++ serial0: serial@21c0500 { ++ device_type = "serial"; ++ compatible = "fsl,ns16550", "ns16550a"; ++ reg = <0x0 0x21c0500 0x0 0x100>; ++ clocks = <&clockgen 4 3>; ++ interrupts = <0 32 0x4>; /* Level high type */ ++ }; ++ ++ serial1: serial@21c0600 { ++ device_type = "serial"; ++ compatible = "fsl,ns16550", "ns16550a"; ++ reg = <0x0 0x21c0600 0x0 0x100>; ++ clocks = <&clockgen 4 3>; ++ interrupts = <0 32 0x4>; /* Level high type */ ++ }; ++ cluster1_core0_watchdog: wdt@c000000 { ++ compatible = "arm,sp805-wdt", "arm,primecell"; ++ reg = <0x0 0xc000000 0x0 0x1000>; ++ clocks = <&clockgen 4 3>, <&clockgen 4 3>; ++ clock-names = "apb_pclk", "wdog_clk"; ++ }; ++ ++ cluster1_core1_watchdog: wdt@c010000 { ++ compatible = "arm,sp805-wdt", "arm,primecell"; ++ reg = <0x0 0xc010000 0x0 0x1000>; ++ clocks = <&clockgen 4 3>, <&clockgen 4 3>; ++ clock-names = "apb_pclk", "wdog_clk"; ++ }; ++ ++ cluster2_core0_watchdog: wdt@c100000 { ++ compatible = "arm,sp805-wdt", "arm,primecell"; ++ reg = <0x0 0xc100000 0x0 0x1000>; ++ clocks = <&clockgen 4 3>, <&clockgen 4 3>; ++ clock-names = "apb_pclk", "wdog_clk"; ++ }; ++ ++ cluster2_core1_watchdog: wdt@c110000 { ++ compatible = "arm,sp805-wdt", "arm,primecell"; ++ reg = <0x0 0xc110000 0x0 0x1000>; ++ clocks = <&clockgen 4 3>, <&clockgen 4 3>; ++ clock-names = "apb_pclk", "wdog_clk"; ++ }; ++ ++ cluster3_core0_watchdog: wdt@c200000 { ++ compatible = "arm,sp805-wdt", "arm,primecell"; ++ reg = <0x0 0xc200000 0x0 0x1000>; ++ clocks = <&clockgen 4 3>, <&clockgen 4 3>; ++ clock-names = "apb_pclk", "wdog_clk"; ++ }; ++ ++ cluster3_core1_watchdog: wdt@c210000 { ++ compatible = "arm,sp805-wdt", "arm,primecell"; ++ reg = <0x0 0xc210000 0x0 0x1000>; ++ clocks = <&clockgen 4 3>, <&clockgen 4 3>; ++ clock-names = "apb_pclk", "wdog_clk"; ++ }; ++ ++ cluster4_core0_watchdog: wdt@c300000 { ++ compatible = "arm,sp805-wdt", "arm,primecell"; ++ reg = <0x0 0xc300000 0x0 0x1000>; ++ clocks = <&clockgen 4 3>, <&clockgen 4 3>; ++ clock-names = "apb_pclk", "wdog_clk"; ++ }; ++ ++ cluster4_core1_watchdog: wdt@c310000 { ++ compatible = "arm,sp805-wdt", "arm,primecell"; ++ reg = <0x0 0xc310000 0x0 0x1000>; ++ clocks = <&clockgen 4 3>, <&clockgen 4 3>; ++ clock-names = "apb_pclk", "wdog_clk"; ++ }; ++ ++ gpio0: gpio@2300000 { ++ compatible = "fsl,qoriq-gpio"; ++ reg = <0x0 0x2300000 0x0 0x10000>; ++ interrupts = <0 36 0x4>; /* Level high type */ ++ gpio-controller; ++ little-endian; ++ #gpio-cells = <2>; ++ interrupt-controller; ++ #interrupt-cells = <2>; ++ }; ++ ++ gpio1: gpio@2310000 { ++ compatible = "fsl,qoriq-gpio"; ++ reg = <0x0 0x2310000 0x0 0x10000>; ++ interrupts = <0 36 0x4>; /* Level high type */ ++ gpio-controller; ++ little-endian; ++ #gpio-cells = <2>; ++ interrupt-controller; ++ #interrupt-cells = <2>; ++ }; ++ ++ gpio2: gpio@2320000 { ++ compatible = "fsl,qoriq-gpio"; ++ reg = <0x0 0x2320000 0x0 0x10000>; ++ interrupts = <0 37 0x4>; /* Level high type */ ++ gpio-controller; ++ little-endian; ++ #gpio-cells = <2>; ++ interrupt-controller; ++ #interrupt-cells = <2>; ++ }; ++ ++ gpio3: gpio@2330000 { ++ compatible = "fsl,qoriq-gpio"; ++ reg = <0x0 0x2330000 0x0 0x10000>; ++ interrupts = <0 37 0x4>; /* Level high type */ ++ gpio-controller; ++ little-endian; ++ #gpio-cells = <2>; ++ interrupt-controller; ++ #interrupt-cells = <2>; ++ }; ++ ++ /* TODO: WRIOP (CCSR?) */ ++ emdio1: mdio@0x8B96000 { /* WRIOP0: 0x8B8_0000, E-MDIO1: 0x1_6000 */ ++ compatible = "fsl,fman-memac-mdio"; ++ reg = <0x0 0x8B96000 0x0 0x1000>; ++ device_type = "mdio"; /* TODO: is this necessary? */ ++ little-endian; /* force the driver in LE mode */ ++ ++ /* Not necessary on the QDS, but needed on the RDB */ ++ #address-cells = <1>; ++ #size-cells = <0>; ++ }; ++ ++ emdio2: mdio@0x8B97000 { /* WRIOP0: 0x8B8_0000, E-MDIO2: 0x1_7000 */ ++ compatible = "fsl,fman-memac-mdio"; ++ reg = <0x0 0x8B97000 0x0 0x1000>; ++ device_type = "mdio"; /* TODO: is this necessary? */ ++ little-endian; /* force the driver in LE mode */ ++ ++ #address-cells = <1>; ++ #size-cells = <0>; ++ }; ++ ++ ifc: ifc@2240000 { ++ compatible = "fsl,ifc", "simple-bus"; ++ reg = <0x0 0x2240000 0x0 0x20000>; ++ interrupts = <0 21 0x4>; /* Level high type */ ++ little-endian; ++ #address-cells = <2>; ++ #size-cells = <1>; ++ ++ ranges = <0 0 0x5 0x80000000 0x08000000 ++ 2 0 0x5 0x30000000 0x00010000 ++ 3 0 0x5 0x20000000 0x00010000>; ++ }; ++ ++ esdhc: esdhc@2140000 { ++ compatible = "fsl,ls2088a-esdhc", "fsl,ls2080a-esdhc", ++ "fsl,esdhc"; ++ reg = <0x0 0x2140000 0x0 0x10000>; ++ interrupts = <0 28 0x4>; /* Level high type */ ++ clock-frequency = <0>; ++ voltage-ranges = <1800 1800 3300 3300>; ++ sdhci,auto-cmd12; ++ little-endian; ++ bus-width = <4>; ++ }; ++ ++ ftm0: ftm0@2800000 { ++ compatible = "fsl,ftm-alarm"; ++ reg = <0x0 0x2800000 0x0 0x10000>; ++ interrupts = <0 44 4>; ++ }; ++ ++ reset: reset@1E60000 { ++ compatible = "fsl,ls-reset"; ++ reg = <0x0 0x1E60000 0x0 0x10000>; ++ }; ++ ++ dspi: dspi@2100000 { ++ compatible = "fsl,ls2088a-dspi", "fsl,ls2085a-dspi", ++ "fsl,ls2080a-dspi"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x2100000 0x0 0x10000>; ++ interrupts = <0 26 0x4>; /* Level high type */ ++ clocks = <&clockgen 4 3>; ++ clock-names = "dspi"; ++ spi-num-chipselects = <5>; ++ bus-num = <0>; ++ }; ++ ++ i2c0: i2c@2000000 { ++ compatible = "fsl,vf610-i2c"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x2000000 0x0 0x10000>; ++ interrupts = <0 34 0x4>; /* Level high type */ ++ clock-names = "i2c"; ++ clocks = <&clockgen 4 3>; ++ }; ++ ++ i2c1: i2c@2010000 { ++ compatible = "fsl,vf610-i2c"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x2010000 0x0 0x10000>; ++ interrupts = <0 34 0x4>; /* Level high type */ ++ clock-names = "i2c"; ++ clocks = <&clockgen 4 3>; ++ }; ++ ++ i2c2: i2c@2020000 { ++ compatible = "fsl,vf610-i2c"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x2020000 0x0 0x10000>; ++ interrupts = <0 35 0x4>; /* Level high type */ ++ clock-names = "i2c"; ++ clocks = <&clockgen 4 3>; ++ }; ++ ++ i2c3: i2c@2030000 { ++ compatible = "fsl,vf610-i2c"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x2030000 0x0 0x10000>; ++ interrupts = <0 35 0x4>; /* Level high type */ ++ clock-names = "i2c"; ++ clocks = <&clockgen 4 3>; ++ }; ++ ++ qspi: quadspi@20c0000 { ++ compatible = "fsl,ls2088a-qspi", "fsl,ls2080a-qspi"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x20c0000 0x0 0x10000>, ++ <0x0 0x20000000 0x0 0x10000000>; ++ reg-names = "QuadSPI", "QuadSPI-memory"; ++ interrupts = <0 25 0x4>; /* Level high type */ ++ clocks = <&clockgen 4 3>, <&clockgen 4 3>; ++ clock-names = "qspi_en", "qspi"; ++ }; ++ ++ pcie1: pcie@3400000 { ++ compatible = "fsl,ls2088a-pcie", "fsl,ls2080a-pcie", ++ "fsl,ls2085a-pcie", "snps,dw-pcie"; ++ reg = <0x00 0x03400000 0x0 0x00100000 /* controller registers */ ++ 0x20 0x00000000 0x0 0x00001000>; /* configuration space */ ++ reg-names = "regs", "config"; ++ interrupts = <0 108 0x4>; /* Level high type */ ++ interrupt-names = "aer"; ++ #address-cells = <3>; ++ #size-cells = <2>; ++ device_type = "pci"; ++ dma-coherent; ++ fsl,lut_diff; ++ num-lanes = <4>; ++ bus-range = <0x0 0xff>; ++ ranges = <0x81000000 0x0 0x00000000 0x20 0x00010000 0x0 0x00010000 /* downstream I/O */ ++ 0x82000000 0x0 0x40000000 0x20 0x40000000 0x0 0x40000000>; /* non-prefetchable memory */ ++ msi-parent = <&its>; ++ #interrupt-cells = <1>; ++ interrupt-map-mask = <0 0 0 7>; ++ interrupt-map = <0000 0 0 1 &gic 0 0 0 109 4>, ++ <0000 0 0 2 &gic 0 0 0 110 4>, ++ <0000 0 0 3 &gic 0 0 0 111 4>, ++ <0000 0 0 4 &gic 0 0 0 112 4>; ++ }; ++ ++ pcie2: pcie@3500000 { ++ compatible = "fsl,ls2080a-pcie", "fsl,ls2080a-pcie", ++ "fsl,ls2085a-pcie", "snps,dw-pcie"; ++ reg = <0x00 0x03500000 0x0 0x00100000 /* controller registers */ ++ 0x28 0x00000000 0x0 0x00001000>; /* configuration space */ ++ reg-names = "regs", "config"; ++ interrupts = <0 113 0x4>; /* Level high type */ ++ interrupt-names = "aer"; ++ #address-cells = <3>; ++ #size-cells = <2>; ++ device_type = "pci"; ++ dma-coherent; ++ fsl,lut_diff; ++ num-lanes = <4>; ++ bus-range = <0x0 0xff>; ++ ranges = <0x81000000 0x0 0x00000000 0x28 0x00010000 0x0 0x00010000 /* downstream I/O */ ++ 0x82000000 0x0 0x40000000 0x28 0x40000000 0x0 0x40000000>; /* non-prefetchable memory */ ++ msi-parent = <&its>; ++ #interrupt-cells = <1>; ++ interrupt-map-mask = <0 0 0 7>; ++ interrupt-map = <0000 0 0 1 &gic 0 0 0 114 4>, ++ <0000 0 0 2 &gic 0 0 0 115 4>, ++ <0000 0 0 3 &gic 0 0 0 116 4>, ++ <0000 0 0 4 &gic 0 0 0 117 4>; ++ }; ++ ++ pcie3: pcie@3600000 { ++ compatible = "fsl,ls2088a-pcie", "fsl,ls2080a-pcie", ++ "fsl,ls2085a-pcie", "snps,dw-pcie"; ++ reg = <0x00 0x03600000 0x0 0x00100000 /* controller registers */ ++ 0x30 0x00000000 0x0 0x00001000>; /* configuration space */ ++ reg-names = "regs", "config"; ++ interrupts = <0 118 0x4>; /* Level high type */ ++ interrupt-names = "aer"; ++ #address-cells = <3>; ++ #size-cells = <2>; ++ device_type = "pci"; ++ dma-coherent; ++ fsl,lut_diff; ++ num-lanes = <8>; ++ bus-range = <0x0 0xff>; ++ ranges = <0x81000000 0x0 0x00000000 0x30 0x00010000 0x0 0x00010000 /* downstream I/O */ ++ 0x82000000 0x0 0x40000000 0x30 0x40000000 0x0 0x40000000>; /* non-prefetchable memory */ ++ msi-parent = <&its>; ++ #interrupt-cells = <1>; ++ interrupt-map-mask = <0 0 0 7>; ++ interrupt-map = <0000 0 0 1 &gic 0 0 0 119 4>, ++ <0000 0 0 2 &gic 0 0 0 120 4>, ++ <0000 0 0 3 &gic 0 0 0 121 4>, ++ <0000 0 0 4 &gic 0 0 0 122 4>; ++ }; ++ ++ pcie4: pcie@3700000 { ++ compatible = "fsl,ls2080a-pcie", "fsl,ls2080a-pcie", ++ "fsl,ls2085a-pcie", "snps,dw-pcie"; ++ reg = <0x00 0x03700000 0x0 0x00100000 /* controller registers */ ++ 0x38 0x00000000 0x0 0x00001000>; /* configuration space */ ++ reg-names = "regs", "config"; ++ interrupts = <0 123 0x4>; /* Level high type */ ++ interrupt-names = "aer"; ++ #address-cells = <3>; ++ #size-cells = <2>; ++ device_type = "pci"; ++ dma-coherent; ++ fsl,lut_diff; ++ num-lanes = <4>; ++ bus-range = <0x0 0xff>; ++ ranges = <0x81000000 0x0 0x00000000 0x38 0x00010000 0x0 0x00010000 /* downstream I/O */ ++ 0x82000000 0x0 0x40000000 0x38 0x40000000 0x0 0x40000000>; /* non-prefetchable memory */ ++ msi-parent = <&its>; ++ #interrupt-cells = <1>; ++ interrupt-map-mask = <0 0 0 7>; ++ interrupt-map = <0000 0 0 1 &gic 0 0 0 124 4>, ++ <0000 0 0 2 &gic 0 0 0 125 4>, ++ <0000 0 0 3 &gic 0 0 0 126 4>, ++ <0000 0 0 4 &gic 0 0 0 127 4>; ++ }; ++ ++ sata0: sata@3200000 { ++ status = "disabled"; ++ compatible = "fsl,ls2088a-ahci", "fsl,ls2080a-ahci"; ++ reg = <0x0 0x3200000 0x0 0x10000>; ++ interrupts = <0 133 0x4>; /* Level high type */ ++ clocks = <&clockgen 4 3>; ++ }; ++ ++ sata1: sata@3210000 { ++ status = "disabled"; ++ compatible = "fsl,ls2088a-ahci", "fsl,ls2080a-ahci"; ++ reg = <0x0 0x3210000 0x0 0x10000>; ++ interrupts = <0 136 0x4>; /* Level high type */ ++ clocks = <&clockgen 4 3>; ++ }; ++ ++ usb0: usb3@3100000 { ++ status = "disabled"; ++ compatible = "snps,dwc3"; ++ reg = <0x0 0x3100000 0x0 0x10000>; ++ interrupts = <0 80 0x4>; /* Level high type */ ++ dr_mode = "host"; ++ configure-gfladj; ++ snps,dis_rxdet_inp3_quirk; ++ }; ++ ++ usb1: usb3@3110000 { ++ status = "disabled"; ++ compatible = "snps,dwc3"; ++ reg = <0x0 0x3110000 0x0 0x10000>; ++ interrupts = <0 81 0x4>; /* Level high type */ ++ dr_mode = "host"; ++ configure-gfladj; ++ snps,dis_rxdet_inp3_quirk; ++ }; ++ ++ smmu: iommu@5000000 { ++ compatible = "arm,mmu-500"; ++ reg = <0 0x5000000 0 0x800000>; ++ #global-interrupts = <12>; ++ interrupts = <0 13 4>, /* global secure fault */ ++ <0 14 4>, /* combined secure interrupt */ ++ <0 15 4>, /* global non-secure fault */ ++ <0 16 4>, /* combined non-secure interrupt */ ++ /* performance counter interrupts 0-7 */ ++ <0 211 4>, ++ <0 212 4>, ++ <0 213 4>, ++ <0 214 4>, ++ <0 215 4>, ++ <0 216 4>, ++ <0 217 4>, ++ <0 218 4>, ++ /* per context interrupt, 64 interrupts */ ++ <0 146 4>, ++ <0 147 4>, ++ <0 148 4>, ++ <0 149 4>, ++ <0 150 4>, ++ <0 151 4>, ++ <0 152 4>, ++ <0 153 4>, ++ <0 154 4>, ++ <0 155 4>, ++ <0 156 4>, ++ <0 157 4>, ++ <0 158 4>, ++ <0 159 4>, ++ <0 160 4>, ++ <0 161 4>, ++ <0 162 4>, ++ <0 163 4>, ++ <0 164 4>, ++ <0 165 4>, ++ <0 166 4>, ++ <0 167 4>, ++ <0 168 4>, ++ <0 169 4>, ++ <0 170 4>, ++ <0 171 4>, ++ <0 172 4>, ++ <0 173 4>, ++ <0 174 4>, ++ <0 175 4>, ++ <0 176 4>, ++ <0 177 4>, ++ <0 178 4>, ++ <0 179 4>, ++ <0 180 4>, ++ <0 181 4>, ++ <0 182 4>, ++ <0 183 4>, ++ <0 184 4>, ++ <0 185 4>, ++ <0 186 4>, ++ <0 187 4>, ++ <0 188 4>, ++ <0 189 4>, ++ <0 190 4>, ++ <0 191 4>, ++ <0 192 4>, ++ <0 193 4>, ++ <0 194 4>, ++ <0 195 4>, ++ <0 196 4>, ++ <0 197 4>, ++ <0 198 4>, ++ <0 199 4>, ++ <0 200 4>, ++ <0 201 4>, ++ <0 202 4>, ++ <0 203 4>, ++ <0 204 4>, ++ <0 205 4>, ++ <0 206 4>, ++ <0 207 4>, ++ <0 208 4>, ++ <0 209 4>; ++ mmu-masters = <&fsl_mc 0x300 0>; ++ }; ++ ++ timer { ++ compatible = "arm,armv8-timer"; ++ interrupts = <1 13 0x1>, /* Physical Secure PPI, edge triggered */ ++ <1 14 0x1>, /* Physical Non-Secure PPI, edge triggered */ ++ <1 11 0x1>, /* Virtual PPI, edge triggered */ ++ <1 10 0x1>; /* Hypervisor PPI, edge triggered */ ++ arm,reread-timer; ++ fsl,erratum-a008585; ++ }; ++ ++ fsl_mc: fsl-mc@80c000000 { ++ compatible = "fsl,qoriq-mc"; ++ #stream-id-cells = <2>; ++ reg = <0x00000008 0x0c000000 0 0x40>, /* MC portal base */ ++ <0x00000000 0x08340000 0 0x40000>; /* MC control reg */ ++ msi-parent = <&its>; ++ #address-cells = <3>; ++ #size-cells = <1>; ++ ++ /* ++ * Region type 0x0 - MC portals ++ * Region type 0x1 - QBMAN portals ++ */ ++ ranges = <0x0 0x0 0x0 0x8 0x0c000000 0x4000000 ++ 0x1 0x0 0x0 0x8 0x18000000 0x8000000>; ++ ++ /* ++ * Define the maximum number of MACs present on the SoC. ++ * They won't necessarily be all probed, since the ++ * Data Path Layout file and the MC firmware can put fewer ++ * actual DPMAC objects on the MC bus. ++ */ ++ dpmacs { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ dpmac1: dpmac@1 { ++ compatible = "fsl,qoriq-mc-dpmac"; ++ reg = <1>; ++ }; ++ dpmac2: dpmac@2 { ++ compatible = "fsl,qoriq-mc-dpmac"; ++ reg = <2>; ++ }; ++ dpmac3: dpmac@3 { ++ compatible = "fsl,qoriq-mc-dpmac"; ++ reg = <3>; ++ }; ++ dpmac4: dpmac@4 { ++ compatible = "fsl,qoriq-mc-dpmac"; ++ reg = <4>; ++ }; ++ dpmac5: dpmac@5 { ++ compatible = "fsl,qoriq-mc-dpmac"; ++ reg = <5>; ++ }; ++ dpmac6: dpmac@6 { ++ compatible = "fsl,qoriq-mc-dpmac"; ++ reg = <6>; ++ }; ++ dpmac7: dpmac@7 { ++ compatible = "fsl,qoriq-mc-dpmac"; ++ reg = <7>; ++ }; ++ dpmac8: dpmac@8 { ++ compatible = "fsl,qoriq-mc-dpmac"; ++ reg = <8>; ++ }; ++ dpmac9: dpmac@9 { ++ compatible = "fsl,qoriq-mc-dpmac"; ++ reg = <9>; ++ }; ++ dpmac10: dpmac@10 { ++ compatible = "fsl,qoriq-mc-dpmac"; ++ reg = <0xa>; ++ }; ++ dpmac11: dpmac@11 { ++ compatible = "fsl,qoriq-mc-dpmac"; ++ reg = <0xb>; ++ }; ++ dpmac12: dpmac@12 { ++ compatible = "fsl,qoriq-mc-dpmac"; ++ reg = <0xc>; ++ }; ++ dpmac13: dpmac@13 { ++ compatible = "fsl,qoriq-mc-dpmac"; ++ reg = <0xd>; ++ }; ++ dpmac14: dpmac@14 { ++ compatible = "fsl,qoriq-mc-dpmac"; ++ reg = <0xe>; ++ }; ++ dpmac15: dpmac@15 { ++ compatible = "fsl,qoriq-mc-dpmac"; ++ reg = <0xf>; ++ }; ++ dpmac16: dpmac@16 { ++ compatible = "fsl,qoriq-mc-dpmac"; ++ reg = <0x10>; ++ }; ++ }; ++ }; ++ ++ ccn@4000000 { ++ compatible = "arm,ccn-504"; ++ reg = <0x0 0x04000000 0x0 0x01000000>; ++ interrupts = <0 12 4>; ++ }; ++ ++ memory@80000000 { ++ device_type = "memory"; ++ reg = <0x00000000 0x80000000 0 0x80000000>; ++ /* DRAM space 1 - 2 GB DRAM */ ++ }; ++}; +diff --git a/arch/arm64/boot/dts/include/dt-bindings b/arch/arm64/boot/dts/include/dt-bindings +new file mode 120000 +index 0000000..08c00e4 +--- /dev/null ++++ b/arch/arm64/boot/dts/include/dt-bindings +@@ -0,0 +1 @@ ++../../../../../include/dt-bindings +\ No newline at end of file +diff --git a/arch/arm64/boot/dts/thermal.h b/arch/arm64/boot/dts/thermal.h +new file mode 100644 +index 0000000..59822a9 +--- /dev/null ++++ b/arch/arm64/boot/dts/thermal.h +@@ -0,0 +1,17 @@ ++/* ++ * This header provides constants for most thermal bindings. ++ * ++ * Copyright (C) 2013 Texas Instruments ++ * Eduardo Valentin ++ * ++ * GPLv2 only ++ */ ++ ++#ifndef _DT_BINDINGS_THERMAL_THERMAL_H ++#define _DT_BINDINGS_THERMAL_THERMAL_H ++ ++/* On cooling devices upper and lower limits */ ++#define THERMAL_NO_LIMIT (-1UL) ++ ++#endif ++ +diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig +index dd301be..3852a77 100644 +--- a/arch/arm64/configs/defconfig ++++ b/arch/arm64/configs/defconfig +@@ -32,6 +32,7 @@ CONFIG_MODULES=y + CONFIG_MODULE_UNLOAD=y + # CONFIG_BLK_DEV_BSG is not set + # CONFIG_IOSCHED_DEADLINE is not set ++CONFIG_ARCH_LAYERSCAPE=y + CONFIG_ARCH_THUNDER=y + CONFIG_ARCH_VEXPRESS=y + CONFIG_ARCH_XGENE=y +diff --git a/arch/arm64/configs/nxp_ls2088rdb_config b/arch/arm64/configs/nxp_ls2088rdb_config +new file mode 100644 +index 0000000..f1127f9 +--- /dev/null ++++ b/arch/arm64/configs/nxp_ls2088rdb_config +@@ -0,0 +1,3034 @@ ++# ++# Automatically generated file; DO NOT EDIT. ++# Linux/arm64 3.18.25 Kernel Configuration ++# ++CONFIG_ARM64=y ++CONFIG_64BIT=y ++CONFIG_ARCH_PHYS_ADDR_T_64BIT=y ++CONFIG_MMU=y ++CONFIG_STACKTRACE_SUPPORT=y ++CONFIG_ILLEGAL_POINTER_VALUE=0xdead000000000000 ++CONFIG_LOCKDEP_SUPPORT=y ++CONFIG_TRACE_IRQFLAGS_SUPPORT=y ++CONFIG_RWSEM_XCHGADD_ALGORITHM=y ++CONFIG_GENERIC_HWEIGHT=y ++CONFIG_GENERIC_CSUM=y ++CONFIG_GENERIC_CALIBRATE_DELAY=y ++CONFIG_ZONE_DMA=y ++CONFIG_HAVE_GENERIC_RCU_GUP=y ++CONFIG_ARCH_DMA_ADDR_T_64BIT=y ++CONFIG_NEED_DMA_MAP_STATE=y ++CONFIG_NEED_SG_DMA_LENGTH=y ++CONFIG_SWIOTLB=y ++CONFIG_IOMMU_HELPER=y ++CONFIG_KERNEL_MODE_NEON=y ++CONFIG_FIX_EARLYCON_MEM=y ++CONFIG_DEFCONFIG_LIST="/lib/modules/$UNAME_RELEASE/.config" ++CONFIG_IRQ_WORK=y ++CONFIG_BUILDTIME_EXTABLE_SORT=y ++ ++# ++# General setup ++# ++CONFIG_INIT_ENV_ARG_LIMIT=32 ++CONFIG_CROSS_COMPILE="aarch64-linux-gnu-" ++# CONFIG_COMPILE_TEST is not set ++CONFIG_LOCALVERSION="" ++CONFIG_LOCALVERSION_AUTO=y ++CONFIG_DEFAULT_HOSTNAME="(none)" ++CONFIG_SWAP=y ++CONFIG_SYSVIPC=y ++CONFIG_SYSVIPC_SYSCTL=y ++CONFIG_POSIX_MQUEUE=y ++CONFIG_POSIX_MQUEUE_SYSCTL=y ++CONFIG_CROSS_MEMORY_ATTACH=y ++# CONFIG_FHANDLE is not set ++CONFIG_USELIB=y ++CONFIG_AUDIT=y ++CONFIG_HAVE_ARCH_AUDITSYSCALL=y ++# CONFIG_AUDITSYSCALL is not set ++ ++# ++# IRQ subsystem ++# ++CONFIG_GENERIC_IRQ_PROBE=y ++CONFIG_GENERIC_IRQ_SHOW=y ++CONFIG_HARDIRQS_SW_RESEND=y ++CONFIG_IRQ_DOMAIN=y ++CONFIG_IRQ_DOMAIN_HIERARCHY=y ++CONFIG_GENERIC_MSI_IRQ=y ++CONFIG_GENERIC_MSI_IRQ_DOMAIN=y ++CONFIG_HANDLE_DOMAIN_IRQ=y ++# CONFIG_IRQ_DOMAIN_DEBUG is not set ++CONFIG_SPARSE_IRQ=y ++CONFIG_GENERIC_TIME_VSYSCALL=y ++CONFIG_GENERIC_CLOCKEVENTS=y ++CONFIG_GENERIC_CLOCKEVENTS_BUILD=y ++CONFIG_ARCH_HAS_TICK_BROADCAST=y ++CONFIG_GENERIC_CLOCKEVENTS_BROADCAST=y ++ ++# ++# Timers subsystem ++# ++CONFIG_TICK_ONESHOT=y ++CONFIG_NO_HZ_COMMON=y ++# CONFIG_HZ_PERIODIC is not set ++CONFIG_NO_HZ_IDLE=y ++# CONFIG_NO_HZ_FULL is not set ++# CONFIG_NO_HZ is not set ++CONFIG_HIGH_RES_TIMERS=y ++ ++# ++# CPU/Task time and stats accounting ++# ++CONFIG_TICK_CPU_ACCOUNTING=y ++# CONFIG_VIRT_CPU_ACCOUNTING_GEN is not set ++CONFIG_BSD_PROCESS_ACCT=y ++CONFIG_BSD_PROCESS_ACCT_V3=y ++CONFIG_TASKSTATS=y ++CONFIG_TASK_DELAY_ACCT=y ++CONFIG_TASK_XACCT=y ++CONFIG_TASK_IO_ACCOUNTING=y ++ ++# ++# RCU Subsystem ++# ++CONFIG_TREE_PREEMPT_RCU=y ++CONFIG_PREEMPT_RCU=y ++# CONFIG_TASKS_RCU is not set ++CONFIG_RCU_STALL_COMMON=y ++# CONFIG_RCU_USER_QS is not set ++CONFIG_RCU_FANOUT=64 ++CONFIG_RCU_FANOUT_LEAF=16 ++# CONFIG_RCU_FANOUT_EXACT is not set ++# CONFIG_RCU_FAST_NO_HZ is not set ++# CONFIG_TREE_RCU_TRACE is not set ++# CONFIG_RCU_BOOST is not set ++# CONFIG_RCU_NOCB_CPU is not set ++CONFIG_BUILD_BIN2C=y ++CONFIG_IKCONFIG=y ++CONFIG_IKCONFIG_PROC=y ++CONFIG_LOG_BUF_SHIFT=14 ++CONFIG_LOG_CPU_MAX_BUF_SHIFT=12 ++CONFIG_GENERIC_SCHED_CLOCK=y ++CONFIG_CGROUPS=y ++# CONFIG_CGROUP_DEBUG is not set ++# CONFIG_CGROUP_FREEZER is not set ++# CONFIG_CGROUP_DEVICE is not set ++# CONFIG_CPUSETS is not set ++# CONFIG_CGROUP_CPUACCT is not set ++CONFIG_RESOURCE_COUNTERS=y ++CONFIG_MEMCG=y ++CONFIG_MEMCG_SWAP=y ++CONFIG_MEMCG_SWAP_ENABLED=y ++CONFIG_MEMCG_KMEM=y ++CONFIG_CGROUP_HUGETLB=y ++# CONFIG_CGROUP_PERF is not set ++CONFIG_CGROUP_SCHED=y ++CONFIG_FAIR_GROUP_SCHED=y ++# CONFIG_CFS_BANDWIDTH is not set ++# CONFIG_RT_GROUP_SCHED is not set ++# CONFIG_BLK_CGROUP is not set ++# CONFIG_CHECKPOINT_RESTORE is not set ++CONFIG_NAMESPACES=y ++# CONFIG_UTS_NS is not set ++# CONFIG_IPC_NS is not set ++# CONFIG_USER_NS is not set ++# CONFIG_PID_NS is not set ++CONFIG_NET_NS=y ++CONFIG_SCHED_AUTOGROUP=y ++# CONFIG_SYSFS_DEPRECATED is not set ++# CONFIG_RELAY is not set ++CONFIG_BLK_DEV_INITRD=y ++CONFIG_INITRAMFS_SOURCE="" ++CONFIG_RD_GZIP=y ++CONFIG_RD_BZIP2=y ++CONFIG_RD_LZMA=y ++CONFIG_RD_XZ=y ++CONFIG_RD_LZO=y ++CONFIG_RD_LZ4=y ++# CONFIG_CC_OPTIMIZE_FOR_SIZE is not set ++CONFIG_SYSCTL=y ++CONFIG_ANON_INODES=y ++CONFIG_HAVE_UID16=y ++CONFIG_SYSCTL_EXCEPTION_TRACE=y ++CONFIG_BPF=y ++# CONFIG_EXPERT is not set ++CONFIG_UID16=y ++# CONFIG_SGETMASK_SYSCALL is not set ++CONFIG_SYSFS_SYSCALL=y ++# CONFIG_SYSCTL_SYSCALL is not set ++CONFIG_KALLSYMS=y ++CONFIG_KALLSYMS_ALL=y ++CONFIG_PRINTK=y ++CONFIG_BUG=y ++CONFIG_ELF_CORE=y ++CONFIG_BASE_FULL=y ++CONFIG_FUTEX=y ++CONFIG_EPOLL=y ++CONFIG_SIGNALFD=y ++CONFIG_TIMERFD=y ++CONFIG_EVENTFD=y ++# CONFIG_BPF_SYSCALL is not set ++CONFIG_SHMEM=y ++CONFIG_AIO=y ++CONFIG_ADVISE_SYSCALLS=y ++CONFIG_PCI_QUIRKS=y ++# CONFIG_EMBEDDED is not set ++CONFIG_HAVE_PERF_EVENTS=y ++CONFIG_PERF_USE_VMALLOC=y ++ ++# ++# Kernel Performance Events And Counters ++# ++CONFIG_PERF_EVENTS=y ++# CONFIG_DEBUG_PERF_USE_VMALLOC is not set ++CONFIG_VM_EVENT_COUNTERS=y ++# CONFIG_COMPAT_BRK is not set ++CONFIG_SLAB=y ++# CONFIG_SLUB is not set ++# CONFIG_SYSTEM_TRUSTED_KEYRING is not set ++CONFIG_PROFILING=y ++CONFIG_JUMP_LABEL=y ++# CONFIG_UPROBES is not set ++# CONFIG_HAVE_64BIT_ALIGNED_ACCESS is not set ++CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS=y ++CONFIG_HAVE_ARCH_TRACEHOOK=y ++CONFIG_HAVE_DMA_ATTRS=y ++CONFIG_HAVE_DMA_CONTIGUOUS=y ++CONFIG_GENERIC_SMP_IDLE_THREAD=y ++CONFIG_HAVE_CLK=y ++CONFIG_HAVE_DMA_API_DEBUG=y ++CONFIG_HAVE_HW_BREAKPOINT=y ++CONFIG_HAVE_PERF_REGS=y ++CONFIG_HAVE_PERF_USER_STACK_DUMP=y ++CONFIG_HAVE_ARCH_JUMP_LABEL=y ++CONFIG_HAVE_RCU_TABLE_FREE=y ++CONFIG_ARCH_WANT_COMPAT_IPC_PARSE_VERSION=y ++CONFIG_HAVE_CC_STACKPROTECTOR=y ++# CONFIG_CC_STACKPROTECTOR is not set ++CONFIG_CC_STACKPROTECTOR_NONE=y ++# CONFIG_CC_STACKPROTECTOR_REGULAR is not set ++# CONFIG_CC_STACKPROTECTOR_STRONG is not set ++CONFIG_HAVE_CONTEXT_TRACKING=y ++CONFIG_HAVE_VIRT_CPU_ACCOUNTING_GEN=y ++CONFIG_HAVE_ARCH_TRANSPARENT_HUGEPAGE=y ++CONFIG_MODULES_USE_ELF_RELA=y ++CONFIG_CLONE_BACKWARDS=y ++CONFIG_OLD_SIGSUSPEND3=y ++CONFIG_COMPAT_OLD_SIGACTION=y ++ ++# ++# GCOV-based kernel profiling ++# ++# CONFIG_GCOV_KERNEL is not set ++CONFIG_HAVE_GENERIC_DMA_COHERENT=y ++CONFIG_SLABINFO=y ++CONFIG_RT_MUTEXES=y ++CONFIG_BASE_SMALL=0 ++CONFIG_MODULES=y ++CONFIG_MODULE_FORCE_LOAD=y ++CONFIG_MODULE_UNLOAD=y ++# CONFIG_MODULE_FORCE_UNLOAD is not set ++CONFIG_MODVERSIONS=y ++# CONFIG_MODULE_SRCVERSION_ALL is not set ++# CONFIG_MODULE_SIG is not set ++# CONFIG_MODULE_COMPRESS is not set ++CONFIG_STOP_MACHINE=y ++CONFIG_BLOCK=y ++# CONFIG_BLK_DEV_BSG is not set ++# CONFIG_BLK_DEV_BSGLIB is not set ++# CONFIG_BLK_DEV_INTEGRITY is not set ++# CONFIG_BLK_CMDLINE_PARSER is not set ++ ++# ++# Partition Types ++# ++CONFIG_PARTITION_ADVANCED=y ++# CONFIG_ACORN_PARTITION is not set ++# CONFIG_AIX_PARTITION is not set ++# CONFIG_OSF_PARTITION is not set ++# CONFIG_AMIGA_PARTITION is not set ++# CONFIG_ATARI_PARTITION is not set ++# CONFIG_MAC_PARTITION is not set ++CONFIG_MSDOS_PARTITION=y ++# CONFIG_BSD_DISKLABEL is not set ++# CONFIG_MINIX_SUBPARTITION is not set ++# CONFIG_SOLARIS_X86_PARTITION is not set ++# CONFIG_UNIXWARE_DISKLABEL is not set ++# CONFIG_LDM_PARTITION is not set ++# CONFIG_SGI_PARTITION is not set ++# CONFIG_ULTRIX_PARTITION is not set ++# CONFIG_SUN_PARTITION is not set ++# CONFIG_KARMA_PARTITION is not set ++CONFIG_EFI_PARTITION=y ++# CONFIG_SYSV68_PARTITION is not set ++# CONFIG_CMDLINE_PARTITION is not set ++CONFIG_BLOCK_COMPAT=y ++ ++# ++# IO Schedulers ++# ++CONFIG_IOSCHED_NOOP=y ++# CONFIG_IOSCHED_DEADLINE is not set ++CONFIG_IOSCHED_CFQ=y ++CONFIG_DEFAULT_CFQ=y ++# CONFIG_DEFAULT_NOOP is not set ++CONFIG_DEFAULT_IOSCHED="cfq" ++CONFIG_PREEMPT_NOTIFIERS=y ++CONFIG_UNINLINE_SPIN_UNLOCK=y ++CONFIG_ARCH_SUPPORTS_ATOMIC_RMW=y ++CONFIG_MUTEX_SPIN_ON_OWNER=y ++CONFIG_RWSEM_SPIN_ON_OWNER=y ++CONFIG_FREEZER=y ++ ++# ++# Platform selection ++# ++CONFIG_ARCH_THUNDER=y ++CONFIG_ARCH_VEXPRESS=y ++CONFIG_ARCH_XGENE=y ++CONFIG_ARCH_LAYERSCAPE=y ++ ++# ++# Bus support ++# ++CONFIG_ARM_AMBA=y ++CONFIG_PCI=y ++CONFIG_PCI_DOMAINS=y ++CONFIG_PCI_DOMAINS_GENERIC=y ++CONFIG_PCI_SYSCALL=y ++CONFIG_PCI_MSI=y ++CONFIG_PCI_MSI_IRQ_DOMAIN=y ++# CONFIG_PCI_DEBUG is not set ++# CONFIG_PCI_REALLOC_ENABLE_AUTO is not set ++# CONFIG_PCI_STUB is not set ++# CONFIG_PCI_IOV is not set ++# CONFIG_PCI_PRI is not set ++# CONFIG_PCI_PASID is not set ++ ++# ++# PCI host controller drivers ++# ++CONFIG_PCIE_DW=y ++# CONFIG_PCI_HOST_GENERIC is not set ++CONFIG_PCI_XGENE=y ++CONFIG_PCI_XGENE_MSI=y ++CONFIG_PCI_LAYERSCAPE=y ++CONFIG_PCIEPORTBUS=y ++CONFIG_PCIEAER=y ++# CONFIG_PCIE_ECRC is not set ++# CONFIG_PCIEAER_INJECT is not set ++CONFIG_PCIEASPM=y ++# CONFIG_PCIEASPM_DEBUG is not set ++CONFIG_PCIEASPM_DEFAULT=y ++# CONFIG_PCIEASPM_POWERSAVE is not set ++# CONFIG_PCIEASPM_PERFORMANCE is not set ++# CONFIG_HOTPLUG_PCI is not set ++ ++# ++# Kernel Features ++# ++ ++# ++# ARM errata workarounds via the alternatives framework ++# ++CONFIG_ARM64_ERRATUM_826319=y ++CONFIG_ARM64_ERRATUM_827319=y ++CONFIG_ARM64_ERRATUM_824069=y ++CONFIG_ARM64_ERRATUM_819472=y ++CONFIG_ARM64_ERRATUM_832075=y ++CONFIG_ARM64_ERRATUM_845719=y ++CONFIG_ARM64_4K_PAGES=y ++# CONFIG_ARM64_64K_PAGES is not set ++# CONFIG_ARM64_VA_BITS_39 is not set ++CONFIG_ARM64_VA_BITS_48=y ++CONFIG_ARM64_VA_BITS=48 ++CONFIG_ARM64_PGTABLE_LEVELS=4 ++# CONFIG_CPU_BIG_ENDIAN is not set ++CONFIG_SMP=y ++# CONFIG_SCHED_MC is not set ++# CONFIG_SCHED_SMT is not set ++CONFIG_NR_CPUS=64 ++CONFIG_HOTPLUG_CPU=y ++# CONFIG_PREEMPT_NONE is not set ++# CONFIG_PREEMPT_VOLUNTARY is not set ++CONFIG_PREEMPT=y ++CONFIG_PREEMPT_COUNT=y ++CONFIG_HZ=100 ++CONFIG_ARCH_HAS_HOLES_MEMORYMODEL=y ++CONFIG_ARCH_SPARSEMEM_ENABLE=y ++CONFIG_ARCH_SPARSEMEM_DEFAULT=y ++CONFIG_ARCH_SELECT_MEMORY_MODEL=y ++CONFIG_HAVE_ARCH_PFN_VALID=y ++CONFIG_HW_PERF_EVENTS=y ++CONFIG_SYS_SUPPORTS_HUGETLBFS=y ++CONFIG_ARCH_WANT_GENERAL_HUGETLB=y ++CONFIG_ARCH_WANT_HUGE_PMD_SHARE=y ++CONFIG_ARCH_HAS_CACHE_LINE_SIZE=y ++CONFIG_SELECT_MEMORY_MODEL=y ++CONFIG_SPARSEMEM_MANUAL=y ++CONFIG_SPARSEMEM=y ++CONFIG_HAVE_MEMORY_PRESENT=y ++CONFIG_SPARSEMEM_EXTREME=y ++CONFIG_SPARSEMEM_VMEMMAP_ENABLE=y ++CONFIG_SPARSEMEM_VMEMMAP=y ++CONFIG_HAVE_MEMBLOCK=y ++CONFIG_NO_BOOTMEM=y ++CONFIG_MEMORY_ISOLATION=y ++# CONFIG_HAVE_BOOTMEM_INFO_NODE is not set ++CONFIG_PAGEFLAGS_EXTENDED=y ++CONFIG_SPLIT_PTLOCK_CPUS=4 ++CONFIG_MEMORY_BALLOON=y ++CONFIG_BALLOON_COMPACTION=y ++CONFIG_COMPACTION=y ++CONFIG_MIGRATION=y ++CONFIG_PHYS_ADDR_T_64BIT=y ++CONFIG_ZONE_DMA_FLAG=1 ++CONFIG_BOUNCE=y ++CONFIG_MMU_NOTIFIER=y ++CONFIG_KSM=y ++CONFIG_DEFAULT_MMAP_MIN_ADDR=4096 ++CONFIG_TRANSPARENT_HUGEPAGE=y ++CONFIG_TRANSPARENT_HUGEPAGE_ALWAYS=y ++# CONFIG_TRANSPARENT_HUGEPAGE_MADVISE is not set ++# CONFIG_CLEANCACHE is not set ++# CONFIG_FRONTSWAP is not set ++CONFIG_CMA=y ++# CONFIG_CMA_DEBUG is not set ++CONFIG_CMA_AREAS=7 ++# CONFIG_ZPOOL is not set ++# CONFIG_ZBUD is not set ++# CONFIG_ZSMALLOC is not set ++CONFIG_GENERIC_EARLY_IOREMAP=y ++# CONFIG_XEN is not set ++CONFIG_FORCE_MAX_ZONEORDER=11 ++ ++# ++# Boot options ++# ++CONFIG_CMDLINE="console=ttyAMA0" ++# CONFIG_CMDLINE_FORCE is not set ++CONFIG_EFI_STUB=y ++CONFIG_EFI=y ++ ++# ++# Userspace binary formats ++# ++CONFIG_BINFMT_ELF=y ++CONFIG_COMPAT_BINFMT_ELF=y ++CONFIG_ARCH_BINFMT_ELF_RANDOMIZE_PIE=y ++# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set ++CONFIG_BINFMT_SCRIPT=y ++# CONFIG_HAVE_AOUT is not set ++# CONFIG_BINFMT_MISC is not set ++CONFIG_COREDUMP=y ++CONFIG_COMPAT=y ++CONFIG_SYSVIPC_COMPAT=y ++ ++# ++# Power management options ++# ++CONFIG_SUSPEND=y ++CONFIG_SUSPEND_FREEZER=y ++CONFIG_PM_SLEEP=y ++CONFIG_PM_SLEEP_SMP=y ++# CONFIG_PM_AUTOSLEEP is not set ++# CONFIG_PM_WAKELOCKS is not set ++# CONFIG_PM_RUNTIME is not set ++CONFIG_PM=y ++# CONFIG_PM_DEBUG is not set ++CONFIG_PM_CLK=y ++# CONFIG_WQ_POWER_EFFICIENT_DEFAULT is not set ++CONFIG_CPU_PM=y ++CONFIG_ARCH_SUSPEND_POSSIBLE=y ++CONFIG_ARM64_CPU_SUSPEND=y ++ ++# ++# CPU Power Management ++# ++ ++# ++# CPU Idle ++# ++# CONFIG_CPU_IDLE is not set ++# CONFIG_ARCH_NEEDS_CPU_IDLE_COUPLED is not set ++ ++# ++# CPU Frequency scaling ++# ++CONFIG_CPU_FREQ=y ++CONFIG_CPU_FREQ_GOV_COMMON=y ++CONFIG_CPU_FREQ_STAT=y ++# CONFIG_CPU_FREQ_STAT_DETAILS is not set ++CONFIG_CPU_FREQ_DEFAULT_GOV_PERFORMANCE=y ++# CONFIG_CPU_FREQ_DEFAULT_GOV_USERSPACE is not set ++# CONFIG_CPU_FREQ_DEFAULT_GOV_ONDEMAND is not set ++# CONFIG_CPU_FREQ_DEFAULT_GOV_CONSERVATIVE is not set ++CONFIG_CPU_FREQ_GOV_PERFORMANCE=y ++CONFIG_CPU_FREQ_GOV_POWERSAVE=y ++CONFIG_CPU_FREQ_GOV_USERSPACE=y ++CONFIG_CPU_FREQ_GOV_ONDEMAND=y ++CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y ++# CONFIG_CPUFREQ_DT is not set ++ ++# ++# ARM CPU frequency scaling drivers ++# ++# CONFIG_ARM_KIRKWOOD_CPUFREQ is not set ++CONFIG_ARM64_ERRATUM_843419=y ++CONFIG_NET=y ++ ++# ++# Networking options ++# ++CONFIG_PACKET=y ++# CONFIG_PACKET_DIAG is not set ++CONFIG_UNIX=y ++# CONFIG_UNIX_DIAG is not set ++CONFIG_XFRM=y ++CONFIG_XFRM_ALGO=y ++# CONFIG_XFRM_USER is not set ++# CONFIG_XFRM_SUB_POLICY is not set ++# CONFIG_XFRM_MIGRATE is not set ++# CONFIG_XFRM_STATISTICS is not set ++CONFIG_XFRM_IPCOMP=y ++# CONFIG_NET_KEY is not set ++CONFIG_INET=y ++CONFIG_IP_MULTICAST=y ++CONFIG_IP_ADVANCED_ROUTER=y ++CONFIG_IP_FIB_TRIE_STATS=y ++CONFIG_IP_MULTIPLE_TABLES=y ++CONFIG_IP_ROUTE_MULTIPATH=y ++# CONFIG_IP_ROUTE_VERBOSE is not set ++CONFIG_IP_PNP=y ++CONFIG_IP_PNP_DHCP=y ++CONFIG_IP_PNP_BOOTP=y ++# CONFIG_IP_PNP_RARP is not set ++# CONFIG_NET_IPIP is not set ++# CONFIG_NET_IPGRE_DEMUX is not set ++CONFIG_NET_IP_TUNNEL=y ++CONFIG_IP_MROUTE=y ++# CONFIG_IP_MROUTE_MULTIPLE_TABLES is not set ++# CONFIG_IP_PIMSM_V1 is not set ++CONFIG_IP_PIMSM_V2=y ++# CONFIG_SYN_COOKIES is not set ++# CONFIG_NET_IPVTI is not set ++# CONFIG_NET_UDP_TUNNEL is not set ++# CONFIG_NET_FOU is not set ++# CONFIG_GENEVE is not set ++# CONFIG_INET_AH is not set ++# CONFIG_INET_ESP is not set ++# CONFIG_INET_IPCOMP is not set ++# CONFIG_INET_XFRM_TUNNEL is not set ++CONFIG_INET_TUNNEL=y ++CONFIG_INET_XFRM_MODE_TRANSPORT=y ++CONFIG_INET_XFRM_MODE_TUNNEL=y ++CONFIG_INET_XFRM_MODE_BEET=y ++# CONFIG_INET_LRO is not set ++CONFIG_INET_DIAG=y ++CONFIG_INET_TCP_DIAG=y ++# CONFIG_INET_UDP_DIAG is not set ++CONFIG_TCP_CONG_ADVANCED=y ++CONFIG_TCP_CONG_BIC=y ++CONFIG_TCP_CONG_CUBIC=y ++CONFIG_TCP_CONG_WESTWOOD=y ++CONFIG_TCP_CONG_HTCP=y ++# CONFIG_TCP_CONG_HSTCP is not set ++# CONFIG_TCP_CONG_HYBLA is not set ++# CONFIG_TCP_CONG_VEGAS is not set ++# CONFIG_TCP_CONG_SCALABLE is not set ++# CONFIG_TCP_CONG_LP is not set ++# CONFIG_TCP_CONG_VENO is not set ++# CONFIG_TCP_CONG_YEAH is not set ++# CONFIG_TCP_CONG_ILLINOIS is not set ++# CONFIG_TCP_CONG_DCTCP is not set ++# CONFIG_DEFAULT_BIC is not set ++CONFIG_DEFAULT_CUBIC=y ++# CONFIG_DEFAULT_HTCP is not set ++# CONFIG_DEFAULT_WESTWOOD is not set ++# CONFIG_DEFAULT_RENO is not set ++CONFIG_DEFAULT_TCP_CONG="cubic" ++# CONFIG_TCP_MD5SIG is not set ++CONFIG_IPV6=y ++CONFIG_IPV6_ROUTER_PREF=y ++CONFIG_IPV6_ROUTE_INFO=y ++CONFIG_IPV6_OPTIMISTIC_DAD=y ++CONFIG_INET6_AH=y ++CONFIG_INET6_ESP=y ++CONFIG_INET6_IPCOMP=y ++CONFIG_IPV6_MIP6=y ++CONFIG_INET6_XFRM_TUNNEL=y ++CONFIG_INET6_TUNNEL=y ++CONFIG_INET6_XFRM_MODE_TRANSPORT=y ++CONFIG_INET6_XFRM_MODE_TUNNEL=y ++CONFIG_INET6_XFRM_MODE_BEET=y ++CONFIG_INET6_XFRM_MODE_ROUTEOPTIMIZATION=y ++# CONFIG_IPV6_VTI is not set ++CONFIG_IPV6_SIT=y ++# CONFIG_IPV6_SIT_6RD is not set ++CONFIG_IPV6_NDISC_NODETYPE=y ++CONFIG_IPV6_TUNNEL=y ++# CONFIG_IPV6_GRE is not set ++CONFIG_IPV6_MULTIPLE_TABLES=y ++CONFIG_IPV6_SUBTREES=y ++# CONFIG_IPV6_MROUTE is not set ++# CONFIG_NETLABEL is not set ++# CONFIG_NETWORK_SECMARK is not set ++CONFIG_NET_PTP_CLASSIFY=y ++# CONFIG_NETWORK_PHY_TIMESTAMPING is not set ++# CONFIG_NETFILTER is not set ++# CONFIG_IP_DCCP is not set ++# CONFIG_IP_SCTP is not set ++# CONFIG_RDS is not set ++# CONFIG_TIPC is not set ++# CONFIG_ATM is not set ++# CONFIG_L2TP is not set ++CONFIG_STP=m ++CONFIG_BRIDGE=m ++CONFIG_BRIDGE_IGMP_SNOOPING=y ++# CONFIG_BRIDGE_VLAN_FILTERING is not set ++CONFIG_HAVE_NET_DSA=y ++CONFIG_VLAN_8021Q=y ++# CONFIG_VLAN_8021Q_GVRP is not set ++# CONFIG_VLAN_8021Q_MVRP is not set ++# CONFIG_DECNET is not set ++CONFIG_LLC=m ++# CONFIG_LLC2 is not set ++# CONFIG_IPX is not set ++# CONFIG_ATALK is not set ++# CONFIG_X25 is not set ++# CONFIG_LAPB is not set ++# CONFIG_PHONET is not set ++# CONFIG_6LOWPAN is not set ++# CONFIG_IEEE802154 is not set ++CONFIG_NET_SCHED=y ++ ++# ++# Queueing/Scheduling ++# ++# CONFIG_NET_SCH_CBQ is not set ++# CONFIG_NET_SCH_HTB is not set ++# CONFIG_NET_SCH_HFSC is not set ++# CONFIG_NET_SCH_PRIO is not set ++# CONFIG_NET_SCH_MULTIQ is not set ++# CONFIG_NET_SCH_RED is not set ++# CONFIG_NET_SCH_SFB is not set ++# CONFIG_NET_SCH_SFQ is not set ++# CONFIG_NET_SCH_TEQL is not set ++# CONFIG_NET_SCH_TBF is not set ++# CONFIG_NET_SCH_GRED is not set ++# CONFIG_NET_SCH_DSMARK is not set ++# CONFIG_NET_SCH_NETEM is not set ++# CONFIG_NET_SCH_DRR is not set ++# CONFIG_NET_SCH_MQPRIO is not set ++# CONFIG_NET_SCH_CHOKE is not set ++# CONFIG_NET_SCH_QFQ is not set ++# CONFIG_NET_SCH_CODEL is not set ++# CONFIG_NET_SCH_FQ_CODEL is not set ++# CONFIG_NET_SCH_FQ is not set ++# CONFIG_NET_SCH_HHF is not set ++# CONFIG_NET_SCH_PIE is not set ++# CONFIG_NET_SCH_PLUG is not set ++ ++# ++# Classification ++# ++# CONFIG_NET_CLS_BASIC is not set ++# CONFIG_NET_CLS_TCINDEX is not set ++# CONFIG_NET_CLS_ROUTE4 is not set ++# CONFIG_NET_CLS_FW is not set ++# CONFIG_NET_CLS_U32 is not set ++# CONFIG_NET_CLS_RSVP is not set ++# CONFIG_NET_CLS_RSVP6 is not set ++# CONFIG_NET_CLS_FLOW is not set ++# CONFIG_NET_CLS_CGROUP is not set ++# CONFIG_NET_CLS_BPF is not set ++# CONFIG_NET_EMATCH is not set ++# CONFIG_NET_CLS_ACT is not set ++CONFIG_NET_SCH_FIFO=y ++CONFIG_DCB=y ++CONFIG_DNS_RESOLVER=y ++# CONFIG_BATMAN_ADV is not set ++# CONFIG_OPENVSWITCH is not set ++# CONFIG_VSOCKETS is not set ++# CONFIG_NETLINK_MMAP is not set ++# CONFIG_NETLINK_DIAG is not set ++# CONFIG_NET_MPLS_GSO is not set ++# CONFIG_HSR is not set ++CONFIG_RPS=y ++CONFIG_RFS_ACCEL=y ++CONFIG_XPS=y ++# CONFIG_CGROUP_NET_PRIO is not set ++# CONFIG_CGROUP_NET_CLASSID is not set ++CONFIG_NET_RX_BUSY_POLL=y ++CONFIG_BQL=y ++CONFIG_BPF_JIT=y ++CONFIG_NET_FLOW_LIMIT=y ++ ++# ++# Network testing ++# ++# CONFIG_NET_PKTGEN is not set ++# CONFIG_HAMRADIO is not set ++# CONFIG_CAN is not set ++# CONFIG_IRDA is not set ++# CONFIG_BT is not set ++# CONFIG_AF_RXRPC is not set ++CONFIG_FIB_RULES=y ++# CONFIG_WIRELESS is not set ++# CONFIG_WIMAX is not set ++# CONFIG_RFKILL is not set ++# CONFIG_RFKILL_REGULATOR is not set ++CONFIG_NET_9P=y ++CONFIG_NET_9P_VIRTIO=y ++# CONFIG_NET_9P_DEBUG is not set ++# CONFIG_CAIF is not set ++# CONFIG_CEPH_LIB is not set ++# CONFIG_NFC is not set ++CONFIG_HAVE_BPF_JIT=y ++ ++# ++# Device Drivers ++# ++ ++# ++# Generic Driver Options ++# ++CONFIG_UEVENT_HELPER=y ++CONFIG_UEVENT_HELPER_PATH="/sbin/hotplug" ++CONFIG_DEVTMPFS=y ++CONFIG_DEVTMPFS_MOUNT=y ++CONFIG_STANDALONE=y ++CONFIG_PREVENT_FIRMWARE_BUILD=y ++CONFIG_FW_LOADER=y ++CONFIG_FIRMWARE_IN_KERNEL=y ++CONFIG_EXTRA_FIRMWARE="" ++# CONFIG_FW_LOADER_USER_HELPER_FALLBACK is not set ++CONFIG_ALLOW_DEV_COREDUMP=y ++# CONFIG_DEBUG_DRIVER is not set ++# CONFIG_DEBUG_DEVRES is not set ++# CONFIG_SYS_HYPERVISOR is not set ++# CONFIG_GENERIC_CPU_DEVICES is not set ++CONFIG_GENERIC_CPU_AUTOPROBE=y ++CONFIG_REGMAP=y ++CONFIG_REGMAP_MMIO=y ++# CONFIG_DMA_SHARED_BUFFER is not set ++CONFIG_DMA_CMA=y ++ ++# ++# Default contiguous memory area size: ++# ++CONFIG_CMA_SIZE_MBYTES=16 ++CONFIG_CMA_SIZE_SEL_MBYTES=y ++# CONFIG_CMA_SIZE_SEL_PERCENTAGE is not set ++# CONFIG_CMA_SIZE_SEL_MIN is not set ++# CONFIG_CMA_SIZE_SEL_MAX is not set ++CONFIG_CMA_ALIGNMENT=8 ++ ++# ++# Bus devices ++# ++# CONFIG_ARM_CCN is not set ++CONFIG_VEXPRESS_CONFIG=y ++# CONFIG_CONNECTOR is not set ++CONFIG_MTD=y ++# CONFIG_MTD_TESTS is not set ++# CONFIG_MTD_REDBOOT_PARTS is not set ++CONFIG_MTD_CMDLINE_PARTS=y ++CONFIG_MTD_OF_PARTS=y ++# CONFIG_MTD_AR7_PARTS is not set ++ ++# ++# User Modules And Translation Layers ++# ++CONFIG_MTD_BLKDEVS=y ++CONFIG_MTD_BLOCK=y ++CONFIG_FTL=y ++# CONFIG_NFTL is not set ++# CONFIG_INFTL is not set ++# CONFIG_RFD_FTL is not set ++# CONFIG_SSFDC is not set ++# CONFIG_SM_FTL is not set ++# CONFIG_MTD_OOPS is not set ++# CONFIG_MTD_SWAP is not set ++ ++# ++# RAM/ROM/Flash chip drivers ++# ++CONFIG_MTD_CFI=y ++# CONFIG_MTD_JEDECPROBE is not set ++CONFIG_MTD_GEN_PROBE=y ++CONFIG_MTD_CFI_ADV_OPTIONS=y ++CONFIG_MTD_CFI_NOSWAP=y ++# CONFIG_MTD_CFI_BE_BYTE_SWAP is not set ++# CONFIG_MTD_CFI_LE_BYTE_SWAP is not set ++# CONFIG_MTD_CFI_GEOMETRY is not set ++CONFIG_MTD_MAP_BANK_WIDTH_1=y ++CONFIG_MTD_MAP_BANK_WIDTH_2=y ++CONFIG_MTD_MAP_BANK_WIDTH_4=y ++# CONFIG_MTD_MAP_BANK_WIDTH_8 is not set ++# CONFIG_MTD_MAP_BANK_WIDTH_16 is not set ++# CONFIG_MTD_MAP_BANK_WIDTH_32 is not set ++CONFIG_MTD_CFI_I1=y ++CONFIG_MTD_CFI_I2=y ++# CONFIG_MTD_CFI_I4 is not set ++# CONFIG_MTD_CFI_I8 is not set ++# CONFIG_MTD_OTP is not set ++CONFIG_MTD_CFI_INTELEXT=y ++CONFIG_MTD_CFI_AMDSTD=y ++CONFIG_MTD_CFI_STAA=y ++CONFIG_MTD_CFI_UTIL=y ++CONFIG_MTD_RAM=y ++# CONFIG_MTD_ROM is not set ++# CONFIG_MTD_ABSENT is not set ++ ++# ++# Mapping drivers for chip access ++# ++# CONFIG_MTD_COMPLEX_MAPPINGS is not set ++CONFIG_MTD_PHYSMAP=y ++# CONFIG_MTD_PHYSMAP_COMPAT is not set ++CONFIG_MTD_PHYSMAP_OF=y ++# CONFIG_MTD_INTEL_VR_NOR is not set ++CONFIG_MTD_PLATRAM=y ++ ++# ++# Self-contained MTD device drivers ++# ++# CONFIG_MTD_PMC551 is not set ++# CONFIG_MTD_DATAFLASH is not set ++CONFIG_MTD_M25P80=y ++# CONFIG_MTD_SST25L is not set ++# CONFIG_MTD_SLRAM is not set ++# CONFIG_MTD_PHRAM is not set ++# CONFIG_MTD_MTDRAM is not set ++# CONFIG_MTD_BLOCK2MTD is not set ++ ++# ++# Disk-On-Chip Device Drivers ++# ++# CONFIG_MTD_DOCG3 is not set ++CONFIG_MTD_NAND_ECC=y ++# CONFIG_MTD_NAND_ECC_SMC is not set ++CONFIG_MTD_NAND=y ++# CONFIG_MTD_NAND_ECC_BCH is not set ++# CONFIG_MTD_SM_COMMON is not set ++# CONFIG_MTD_NAND_DENALI is not set ++CONFIG_MTD_NAND_GPIO=y ++# CONFIG_MTD_NAND_OMAP_BCH_BUILD is not set ++CONFIG_MTD_NAND_IDS=y ++# CONFIG_MTD_NAND_RICOH is not set ++# CONFIG_MTD_NAND_DISKONCHIP is not set ++# CONFIG_MTD_NAND_DOCG4 is not set ++# CONFIG_MTD_NAND_CAFE is not set ++# CONFIG_MTD_NAND_NANDSIM is not set ++# CONFIG_MTD_NAND_PLATFORM is not set ++CONFIG_MTD_NAND_FSL_IFC=y ++# CONFIG_MTD_ONENAND is not set ++ ++# ++# LPDDR & LPDDR2 PCM memory drivers ++# ++# CONFIG_MTD_LPDDR is not set ++CONFIG_MTD_SPI_NOR=y ++CONFIG_MTD_SPI_NOR_USE_4K_SECTORS=y ++# CONFIG_MTD_UBI is not set ++CONFIG_DTC=y ++CONFIG_OF=y ++ ++# ++# Device Tree and Open Firmware support ++# ++# CONFIG_OF_SELFTEST is not set ++CONFIG_OF_FLATTREE=y ++CONFIG_OF_EARLY_FLATTREE=y ++CONFIG_OF_ADDRESS=y ++CONFIG_OF_ADDRESS_PCI=y ++CONFIG_OF_IRQ=y ++CONFIG_OF_NET=y ++CONFIG_OF_MDIO=y ++CONFIG_OF_PCI=y ++CONFIG_OF_PCI_IRQ=y ++CONFIG_OF_MTD=y ++CONFIG_OF_RESERVED_MEM=y ++# CONFIG_PARPORT is not set ++CONFIG_BLK_DEV=y ++# CONFIG_BLK_DEV_NULL_BLK is not set ++# CONFIG_BLK_DEV_PCIESSD_MTIP32XX is not set ++# CONFIG_BLK_CPQ_CISS_DA is not set ++# CONFIG_BLK_DEV_DAC960 is not set ++# CONFIG_BLK_DEV_UMEM is not set ++# CONFIG_BLK_DEV_COW_COMMON is not set ++CONFIG_BLK_DEV_LOOP=y ++CONFIG_BLK_DEV_LOOP_MIN_COUNT=8 ++# CONFIG_BLK_DEV_CRYPTOLOOP is not set ++# CONFIG_BLK_DEV_DRBD is not set ++# CONFIG_BLK_DEV_NBD is not set ++# CONFIG_BLK_DEV_NVME is not set ++# CONFIG_BLK_DEV_SKD is not set ++# CONFIG_BLK_DEV_SX8 is not set ++CONFIG_BLK_DEV_RAM=y ++CONFIG_BLK_DEV_RAM_COUNT=16 ++CONFIG_BLK_DEV_RAM_SIZE=262144 ++# CONFIG_BLK_DEV_XIP is not set ++# CONFIG_CDROM_PKTCDVD is not set ++# CONFIG_ATA_OVER_ETH is not set ++CONFIG_VIRTIO_BLK=y ++# CONFIG_BLK_DEV_RBD is not set ++# CONFIG_BLK_DEV_RSXX is not set ++ ++# ++# Misc devices ++# ++# CONFIG_SENSORS_LIS3LV02D is not set ++# CONFIG_AD525X_DPOT is not set ++# CONFIG_DUMMY_IRQ is not set ++# CONFIG_PHANTOM is not set ++# CONFIG_SGI_IOC4 is not set ++# CONFIG_TIFM_CORE is not set ++# CONFIG_ICS932S401 is not set ++# CONFIG_ENCLOSURE_SERVICES is not set ++# CONFIG_HP_ILO is not set ++# CONFIG_APDS9802ALS is not set ++# CONFIG_ISL29003 is not set ++# CONFIG_ISL29020 is not set ++# CONFIG_SENSORS_TSL2550 is not set ++# CONFIG_SENSORS_BH1780 is not set ++# CONFIG_SENSORS_BH1770 is not set ++# CONFIG_SENSORS_APDS990X is not set ++# CONFIG_HMC6352 is not set ++# CONFIG_DS1682 is not set ++# CONFIG_TI_DAC7512 is not set ++# CONFIG_BMP085_I2C is not set ++# CONFIG_BMP085_SPI is not set ++# CONFIG_USB_SWITCH_FSA9480 is not set ++# CONFIG_LATTICE_ECP3_CONFIG is not set ++# CONFIG_SRAM is not set ++CONFIG_VEXPRESS_SYSCFG=y ++# CONFIG_C2PORT is not set ++ ++# ++# EEPROM support ++# ++CONFIG_EEPROM_AT24=y ++CONFIG_EEPROM_AT25=y ++# CONFIG_EEPROM_LEGACY is not set ++# CONFIG_EEPROM_MAX6875 is not set ++# CONFIG_EEPROM_93CX6 is not set ++# CONFIG_EEPROM_93XX46 is not set ++# CONFIG_CB710_CORE is not set ++ ++# ++# Texas Instruments shared transport line discipline ++# ++# CONFIG_TI_ST is not set ++# CONFIG_SENSORS_LIS3_SPI is not set ++# CONFIG_SENSORS_LIS3_I2C is not set ++ ++# ++# Altera FPGA firmware download module ++# ++# CONFIG_ALTERA_STAPL is not set ++ ++# ++# Intel MIC Bus Driver ++# ++ ++# ++# Intel MIC Host Driver ++# ++ ++# ++# Intel MIC Card Driver ++# ++# CONFIG_GENWQE is not set ++# CONFIG_ECHO is not set ++# CONFIG_CXL_BASE is not set ++ ++# ++# SCSI device support ++# ++CONFIG_SCSI_MOD=y ++# CONFIG_RAID_ATTRS is not set ++CONFIG_SCSI=y ++CONFIG_SCSI_DMA=y ++# CONFIG_SCSI_NETLINK is not set ++# CONFIG_SCSI_MQ_DEFAULT is not set ++CONFIG_SCSI_PROC_FS=y ++ ++# ++# SCSI support type (disk, tape, CD-ROM) ++# ++CONFIG_BLK_DEV_SD=y ++# CONFIG_CHR_DEV_ST is not set ++# CONFIG_CHR_DEV_OSST is not set ++# CONFIG_BLK_DEV_SR is not set ++# CONFIG_CHR_DEV_SG is not set ++# CONFIG_CHR_DEV_SCH is not set ++# CONFIG_SCSI_CONSTANTS is not set ++# CONFIG_SCSI_LOGGING is not set ++# CONFIG_SCSI_SCAN_ASYNC is not set ++ ++# ++# SCSI Transports ++# ++# CONFIG_SCSI_SPI_ATTRS is not set ++# CONFIG_SCSI_FC_ATTRS is not set ++# CONFIG_SCSI_ISCSI_ATTRS is not set ++# CONFIG_SCSI_SAS_ATTRS is not set ++# CONFIG_SCSI_SAS_LIBSAS is not set ++# CONFIG_SCSI_SRP_ATTRS is not set ++# CONFIG_SCSI_LOWLEVEL is not set ++# CONFIG_SCSI_LOWLEVEL_PCMCIA is not set ++# CONFIG_SCSI_DH is not set ++# CONFIG_SCSI_OSD_INITIATOR is not set ++CONFIG_HAVE_PATA_PLATFORM=y ++CONFIG_ATA=y ++# CONFIG_ATA_NONSTANDARD is not set ++CONFIG_ATA_VERBOSE_ERROR=y ++CONFIG_SATA_PMP=y ++ ++# ++# Controllers with non-SFF native interface ++# ++CONFIG_SATA_AHCI=y ++CONFIG_SATA_AHCI_PLATFORM=y ++CONFIG_AHCI_XGENE=y ++# CONFIG_SATA_INIC162X is not set ++# CONFIG_SATA_ACARD_AHCI is not set ++# CONFIG_SATA_SIL24 is not set ++CONFIG_ATA_SFF=y ++ ++# ++# SFF controllers with custom DMA interface ++# ++# CONFIG_PDC_ADMA is not set ++# CONFIG_SATA_QSTOR is not set ++# CONFIG_SATA_SX4 is not set ++CONFIG_ATA_BMDMA=y ++ ++# ++# SATA SFF controllers with BMDMA ++# ++# CONFIG_ATA_PIIX is not set ++# CONFIG_SATA_MV is not set ++# CONFIG_SATA_NV is not set ++# CONFIG_SATA_PROMISE is not set ++# CONFIG_SATA_SIL is not set ++# CONFIG_SATA_SIS is not set ++# CONFIG_SATA_SVW is not set ++# CONFIG_SATA_ULI is not set ++# CONFIG_SATA_VIA is not set ++# CONFIG_SATA_VITESSE is not set ++ ++# ++# PATA SFF controllers with BMDMA ++# ++# CONFIG_PATA_ALI is not set ++# CONFIG_PATA_AMD is not set ++# CONFIG_PATA_ARTOP is not set ++# CONFIG_PATA_ATIIXP is not set ++# CONFIG_PATA_ATP867X is not set ++# CONFIG_PATA_CMD64X is not set ++# CONFIG_PATA_CYPRESS is not set ++# CONFIG_PATA_EFAR is not set ++# CONFIG_PATA_HPT366 is not set ++# CONFIG_PATA_HPT37X is not set ++# CONFIG_PATA_HPT3X2N is not set ++# CONFIG_PATA_HPT3X3 is not set ++# CONFIG_PATA_IT8213 is not set ++# CONFIG_PATA_IT821X is not set ++# CONFIG_PATA_JMICRON is not set ++# CONFIG_PATA_MARVELL is not set ++# CONFIG_PATA_NETCELL is not set ++# CONFIG_PATA_NINJA32 is not set ++# CONFIG_PATA_NS87415 is not set ++# CONFIG_PATA_OLDPIIX is not set ++# CONFIG_PATA_OPTIDMA is not set ++# CONFIG_PATA_PDC2027X is not set ++# CONFIG_PATA_PDC_OLD is not set ++# CONFIG_PATA_RADISYS is not set ++# CONFIG_PATA_RDC is not set ++# CONFIG_PATA_SCH is not set ++# CONFIG_PATA_SERVERWORKS is not set ++# CONFIG_PATA_SIL680 is not set ++# CONFIG_PATA_SIS is not set ++# CONFIG_PATA_TOSHIBA is not set ++# CONFIG_PATA_TRIFLEX is not set ++# CONFIG_PATA_VIA is not set ++# CONFIG_PATA_WINBOND is not set ++ ++# ++# PIO-only SFF controllers ++# ++# CONFIG_PATA_CMD640_PCI is not set ++# CONFIG_PATA_MPIIX is not set ++# CONFIG_PATA_NS87410 is not set ++# CONFIG_PATA_OPTI is not set ++# CONFIG_PATA_PLATFORM is not set ++# CONFIG_PATA_RZ1000 is not set ++ ++# ++# Generic fallback / legacy drivers ++# ++# CONFIG_ATA_GENERIC is not set ++# CONFIG_PATA_LEGACY is not set ++# CONFIG_MD is not set ++# CONFIG_TARGET_CORE is not set ++# CONFIG_FUSION is not set ++ ++# ++# IEEE 1394 (FireWire) support ++# ++# CONFIG_FIREWIRE is not set ++# CONFIG_FIREWIRE_NOSY is not set ++# CONFIG_I2O is not set ++CONFIG_NETDEVICES=y ++CONFIG_MII=y ++CONFIG_NET_CORE=y ++# CONFIG_BONDING is not set ++# CONFIG_DUMMY is not set ++# CONFIG_EQUALIZER is not set ++# CONFIG_NET_FC is not set ++# CONFIG_NET_TEAM is not set ++CONFIG_MACVLAN=y ++# CONFIG_MACVTAP is not set ++# CONFIG_VXLAN is not set ++# CONFIG_NETCONSOLE is not set ++# CONFIG_NETPOLL is not set ++# CONFIG_NET_POLL_CONTROLLER is not set ++CONFIG_TUN=y ++# CONFIG_VETH is not set ++CONFIG_VIRTIO_NET=y ++# CONFIG_NLMON is not set ++# CONFIG_ARCNET is not set ++ ++# ++# CAIF transport drivers ++# ++ ++# ++# Distributed Switch Architecture drivers ++# ++# CONFIG_NET_DSA_MV88E6XXX is not set ++# CONFIG_NET_DSA_MV88E6060 is not set ++# CONFIG_NET_DSA_MV88E6XXX_NEED_PPU is not set ++# CONFIG_NET_DSA_MV88E6131 is not set ++# CONFIG_NET_DSA_MV88E6123_61_65 is not set ++# CONFIG_NET_DSA_MV88E6171 is not set ++# CONFIG_NET_DSA_BCM_SF2 is not set ++CONFIG_ETHERNET=y ++CONFIG_NET_VENDOR_3COM=y ++# CONFIG_VORTEX is not set ++# CONFIG_TYPHOON is not set ++CONFIG_NET_VENDOR_ADAPTEC=y ++# CONFIG_ADAPTEC_STARFIRE is not set ++CONFIG_NET_VENDOR_AGERE=y ++# CONFIG_ET131X is not set ++CONFIG_NET_VENDOR_ALTEON=y ++# CONFIG_ACENIC is not set ++# CONFIG_ALTERA_TSE is not set ++CONFIG_NET_VENDOR_AMD=y ++# CONFIG_AMD8111_ETH is not set ++# CONFIG_PCNET32 is not set ++# CONFIG_AMD_XGBE is not set ++CONFIG_NET_XGENE=y ++CONFIG_NET_VENDOR_ARC=y ++# CONFIG_ARC_EMAC is not set ++# CONFIG_EMAC_ROCKCHIP is not set ++CONFIG_NET_VENDOR_ATHEROS=y ++# CONFIG_ATL2 is not set ++# CONFIG_ATL1 is not set ++# CONFIG_ATL1E is not set ++# CONFIG_ATL1C is not set ++# CONFIG_ALX is not set ++CONFIG_NET_VENDOR_BROADCOM=y ++# CONFIG_B44 is not set ++# CONFIG_BCMGENET is not set ++# CONFIG_BNX2 is not set ++# CONFIG_CNIC is not set ++# CONFIG_TIGON3 is not set ++# CONFIG_BNX2X is not set ++# CONFIG_SYSTEMPORT is not set ++CONFIG_NET_VENDOR_BROCADE=y ++# CONFIG_BNA is not set ++CONFIG_NET_VENDOR_CHELSIO=y ++# CONFIG_CHELSIO_T1 is not set ++# CONFIG_CHELSIO_T3 is not set ++# CONFIG_CHELSIO_T4 is not set ++# CONFIG_CHELSIO_T4VF is not set ++CONFIG_NET_VENDOR_CISCO=y ++# CONFIG_ENIC is not set ++# CONFIG_DNET is not set ++CONFIG_NET_VENDOR_DEC=y ++# CONFIG_NET_TULIP is not set ++CONFIG_NET_VENDOR_DLINK=y ++# CONFIG_DL2K is not set ++# CONFIG_SUNDANCE is not set ++CONFIG_NET_VENDOR_EMULEX=y ++# CONFIG_BE2NET is not set ++CONFIG_NET_VENDOR_EXAR=y ++# CONFIG_S2IO is not set ++# CONFIG_VXGE is not set ++CONFIG_NET_VENDOR_FREESCALE=y ++# CONFIG_FSL_PQ_MDIO is not set ++CONFIG_FSL_XGMAC_MDIO=y ++CONFIG_NET_VENDOR_HP=y ++# CONFIG_HP100 is not set ++CONFIG_NET_VENDOR_INTEL=y ++# CONFIG_E100 is not set ++CONFIG_E1000=y ++CONFIG_E1000E=y ++# CONFIG_IGB is not set ++# CONFIG_IGBVF is not set ++# CONFIG_IXGB is not set ++# CONFIG_IXGBE is not set ++# CONFIG_IXGBEVF is not set ++# CONFIG_I40E is not set ++# CONFIG_I40EVF is not set ++# CONFIG_FM10K is not set ++CONFIG_NET_VENDOR_I825XX=y ++# CONFIG_IP1000 is not set ++# CONFIG_JME is not set ++CONFIG_NET_VENDOR_MARVELL=y ++# CONFIG_MVMDIO is not set ++# CONFIG_SKGE is not set ++# CONFIG_SKY2 is not set ++CONFIG_NET_VENDOR_MELLANOX=y ++# CONFIG_MLX4_EN is not set ++# CONFIG_MLX4_CORE is not set ++# CONFIG_MLX5_CORE is not set ++CONFIG_NET_VENDOR_MICREL=y ++# CONFIG_KS8842 is not set ++# CONFIG_KS8851 is not set ++# CONFIG_KS8851_MLL is not set ++# CONFIG_KSZ884X_PCI is not set ++CONFIG_NET_VENDOR_MICROCHIP=y ++# CONFIG_ENC28J60 is not set ++CONFIG_NET_VENDOR_MYRI=y ++# CONFIG_MYRI10GE is not set ++# CONFIG_FEALNX is not set ++CONFIG_NET_VENDOR_NATSEMI=y ++# CONFIG_NATSEMI is not set ++# CONFIG_NS83820 is not set ++CONFIG_NET_VENDOR_8390=y ++# CONFIG_NE2K_PCI is not set ++CONFIG_NET_VENDOR_NVIDIA=y ++# CONFIG_FORCEDETH is not set ++CONFIG_NET_VENDOR_OKI=y ++# CONFIG_ETHOC is not set ++CONFIG_NET_PACKET_ENGINE=y ++# CONFIG_HAMACHI is not set ++# CONFIG_YELLOWFIN is not set ++CONFIG_NET_VENDOR_QLOGIC=y ++# CONFIG_QLA3XXX is not set ++# CONFIG_QLCNIC is not set ++# CONFIG_QLGE is not set ++# CONFIG_NETXEN_NIC is not set ++CONFIG_NET_VENDOR_QUALCOMM=y ++# CONFIG_QCA7000 is not set ++CONFIG_NET_VENDOR_REALTEK=y ++# CONFIG_8139CP is not set ++# CONFIG_8139TOO is not set ++# CONFIG_R8169 is not set ++CONFIG_NET_VENDOR_RDC=y ++# CONFIG_R6040 is not set ++CONFIG_NET_VENDOR_SAMSUNG=y ++# CONFIG_SXGBE_ETH is not set ++CONFIG_NET_VENDOR_SEEQ=y ++CONFIG_NET_VENDOR_SILAN=y ++# CONFIG_SC92031 is not set ++CONFIG_NET_VENDOR_SIS=y ++# CONFIG_SIS900 is not set ++# CONFIG_SIS190 is not set ++# CONFIG_SFC is not set ++CONFIG_NET_VENDOR_SMSC=y ++CONFIG_SMC91X=y ++# CONFIG_EPIC100 is not set ++CONFIG_SMSC911X=y ++# CONFIG_SMSC911X_ARCH_HOOKS is not set ++# CONFIG_SMSC9420 is not set ++CONFIG_NET_VENDOR_STMICRO=y ++# CONFIG_STMMAC_ETH is not set ++CONFIG_NET_VENDOR_SUN=y ++# CONFIG_HAPPYMEAL is not set ++# CONFIG_SUNGEM is not set ++# CONFIG_CASSINI is not set ++# CONFIG_NIU is not set ++CONFIG_NET_VENDOR_TEHUTI=y ++# CONFIG_TEHUTI is not set ++CONFIG_NET_VENDOR_TI=y ++# CONFIG_TLAN is not set ++CONFIG_NET_VENDOR_VIA=y ++# CONFIG_VIA_RHINE is not set ++# CONFIG_VIA_VELOCITY is not set ++CONFIG_NET_VENDOR_WIZNET=y ++# CONFIG_WIZNET_W5100 is not set ++# CONFIG_WIZNET_W5300 is not set ++# CONFIG_FDDI is not set ++# CONFIG_HIPPI is not set ++CONFIG_PHYLIB=y ++ ++# ++# MII PHY device drivers ++# ++CONFIG_AQUANTIA_PHY=y ++# CONFIG_AT803X_PHY is not set ++# CONFIG_AMD_PHY is not set ++# CONFIG_AMD_XGBE_PHY is not set ++# CONFIG_MARVELL_PHY is not set ++# CONFIG_DAVICOM_PHY is not set ++# CONFIG_QSEMI_PHY is not set ++# CONFIG_LXT_PHY is not set ++# CONFIG_CICADA_PHY is not set ++CONFIG_VITESSE_PHY=y ++# CONFIG_TERANETICS_PHY is not set ++CONFIG_SMSC_PHY=y ++CONFIG_BROADCOM_PHY=y ++# CONFIG_BCM7XXX_PHY is not set ++# CONFIG_BCM87XX_PHY is not set ++# CONFIG_ICPLUS_PHY is not set ++CONFIG_REALTEK_PHY=y ++# CONFIG_NATIONAL_PHY is not set ++# CONFIG_STE10XP is not set ++# CONFIG_LSI_ET1011C_PHY is not set ++# CONFIG_MICREL_PHY is not set ++CONFIG_FIXED_PHY=y ++# CONFIG_MDIO_BITBANG is not set ++CONFIG_MDIO_BUS_MUX=y ++# CONFIG_MDIO_BUS_MUX_GPIO is not set ++CONFIG_MDIO_BUS_MUX_MMIOREG=y ++# CONFIG_FSL_10GBASE_KR is not set ++# CONFIG_MDIO_BCM_UNIMAC is not set ++# CONFIG_MICREL_KS8995MA is not set ++# CONFIG_PPP is not set ++# CONFIG_SLIP is not set ++CONFIG_USB_NET_DRIVERS=y ++# CONFIG_USB_CATC is not set ++# CONFIG_USB_KAWETH is not set ++# CONFIG_USB_PEGASUS is not set ++# CONFIG_USB_RTL8150 is not set ++# CONFIG_USB_RTL8152 is not set ++# CONFIG_USB_USBNET is not set ++# CONFIG_USB_IPHETH is not set ++# CONFIG_WLAN is not set ++ ++# ++# Enable WiMAX (Networking options) to see the WiMAX drivers ++# ++# CONFIG_WAN is not set ++# CONFIG_VMXNET3 is not set ++# CONFIG_ISDN is not set ++ ++# ++# Input device support ++# ++CONFIG_INPUT=y ++# CONFIG_INPUT_FF_MEMLESS is not set ++# CONFIG_INPUT_POLLDEV is not set ++# CONFIG_INPUT_SPARSEKMAP is not set ++# CONFIG_INPUT_MATRIXKMAP is not set ++ ++# ++# Userland interfaces ++# ++CONFIG_INPUT_MOUSEDEV=y ++CONFIG_INPUT_MOUSEDEV_PSAUX=y ++CONFIG_INPUT_MOUSEDEV_SCREEN_X=1024 ++CONFIG_INPUT_MOUSEDEV_SCREEN_Y=768 ++# CONFIG_INPUT_JOYDEV is not set ++CONFIG_INPUT_EVDEV=y ++# CONFIG_INPUT_EVBUG is not set ++ ++# ++# Input Device Drivers ++# ++CONFIG_INPUT_KEYBOARD=y ++# CONFIG_KEYBOARD_ADP5588 is not set ++# CONFIG_KEYBOARD_ADP5589 is not set ++CONFIG_KEYBOARD_ATKBD=y ++# CONFIG_KEYBOARD_QT1070 is not set ++# CONFIG_KEYBOARD_QT2160 is not set ++# CONFIG_KEYBOARD_LKKBD is not set ++# CONFIG_KEYBOARD_GPIO is not set ++# CONFIG_KEYBOARD_GPIO_POLLED is not set ++# CONFIG_KEYBOARD_TCA6416 is not set ++# CONFIG_KEYBOARD_TCA8418 is not set ++# CONFIG_KEYBOARD_MATRIX is not set ++# CONFIG_KEYBOARD_LM8333 is not set ++# CONFIG_KEYBOARD_MAX7359 is not set ++# CONFIG_KEYBOARD_MCS is not set ++# CONFIG_KEYBOARD_MPR121 is not set ++# CONFIG_KEYBOARD_NEWTON is not set ++# CONFIG_KEYBOARD_OPENCORES is not set ++# CONFIG_KEYBOARD_SAMSUNG is not set ++# CONFIG_KEYBOARD_STOWAWAY is not set ++# CONFIG_KEYBOARD_SUNKBD is not set ++# CONFIG_KEYBOARD_OMAP4 is not set ++# CONFIG_KEYBOARD_XTKBD is not set ++# CONFIG_KEYBOARD_CAP1106 is not set ++CONFIG_INPUT_MOUSE=y ++CONFIG_MOUSE_PS2=y ++CONFIG_MOUSE_PS2_ALPS=y ++CONFIG_MOUSE_PS2_LOGIPS2PP=y ++CONFIG_MOUSE_PS2_SYNAPTICS=y ++CONFIG_MOUSE_PS2_CYPRESS=y ++CONFIG_MOUSE_PS2_TRACKPOINT=y ++# CONFIG_MOUSE_PS2_ELANTECH is not set ++# CONFIG_MOUSE_PS2_SENTELIC is not set ++# CONFIG_MOUSE_PS2_TOUCHKIT is not set ++# CONFIG_MOUSE_SERIAL is not set ++# CONFIG_MOUSE_APPLETOUCH is not set ++# CONFIG_MOUSE_BCM5974 is not set ++# CONFIG_MOUSE_CYAPA is not set ++# CONFIG_MOUSE_VSXXXAA is not set ++# CONFIG_MOUSE_GPIO is not set ++# CONFIG_MOUSE_SYNAPTICS_I2C is not set ++# CONFIG_MOUSE_SYNAPTICS_USB is not set ++# CONFIG_INPUT_JOYSTICK is not set ++# CONFIG_INPUT_TABLET is not set ++# CONFIG_INPUT_TOUCHSCREEN is not set ++# CONFIG_INPUT_MISC is not set ++ ++# ++# Hardware I/O ports ++# ++CONFIG_SERIO=y ++# CONFIG_SERIO_SERPORT is not set ++CONFIG_SERIO_AMBAKMI=y ++# CONFIG_SERIO_PCIPS2 is not set ++CONFIG_SERIO_LIBPS2=y ++# CONFIG_SERIO_RAW is not set ++# CONFIG_SERIO_ALTERA_PS2 is not set ++# CONFIG_SERIO_PS2MULT is not set ++# CONFIG_SERIO_ARC_PS2 is not set ++# CONFIG_SERIO_APBPS2 is not set ++# CONFIG_GAMEPORT is not set ++ ++# ++# Character devices ++# ++CONFIG_TTY=y ++CONFIG_VT=y ++CONFIG_CONSOLE_TRANSLATIONS=y ++CONFIG_VT_CONSOLE=y ++CONFIG_VT_CONSOLE_SLEEP=y ++CONFIG_HW_CONSOLE=y ++CONFIG_VT_HW_CONSOLE_BINDING=y ++CONFIG_UNIX98_PTYS=y ++# CONFIG_DEVPTS_MULTIPLE_INSTANCES is not set ++CONFIG_LEGACY_PTYS=y ++CONFIG_LEGACY_PTY_COUNT=16 ++# CONFIG_SERIAL_NONSTANDARD is not set ++# CONFIG_NOZOMI is not set ++# CONFIG_N_GSM is not set ++# CONFIG_TRACE_SINK is not set ++CONFIG_DEVKMEM=y ++ ++# ++# Serial drivers ++# ++CONFIG_SERIAL_EARLYCON=y ++CONFIG_SERIAL_8250=y ++CONFIG_SERIAL_8250_DEPRECATED_OPTIONS=y ++CONFIG_SERIAL_8250_CONSOLE=y ++CONFIG_SERIAL_8250_DMA=y ++CONFIG_SERIAL_8250_PCI=y ++CONFIG_SERIAL_8250_NR_UARTS=4 ++CONFIG_SERIAL_8250_RUNTIME_UARTS=4 ++# CONFIG_SERIAL_8250_EXTENDED is not set ++# CONFIG_SERIAL_8250_DW is not set ++ ++# ++# Non-8250 serial port support ++# ++# CONFIG_SERIAL_AMBA_PL010 is not set ++CONFIG_SERIAL_AMBA_PL011=y ++CONFIG_SERIAL_AMBA_PL011_CONSOLE=y ++# CONFIG_SERIAL_EARLYCON_ARM_SEMIHOST is not set ++# CONFIG_SERIAL_MAX3100 is not set ++# CONFIG_SERIAL_MAX310X is not set ++# CONFIG_SERIAL_MFD_HSU is not set ++CONFIG_SERIAL_CORE=y ++CONFIG_SERIAL_CORE_CONSOLE=y ++# CONFIG_SERIAL_JSM is not set ++CONFIG_SERIAL_OF_PLATFORM=y ++# CONFIG_SERIAL_SCCNXP is not set ++# CONFIG_SERIAL_SC16IS7XX is not set ++# CONFIG_SERIAL_ALTERA_JTAGUART is not set ++# CONFIG_SERIAL_ALTERA_UART is not set ++# CONFIG_SERIAL_IFX6X60 is not set ++# CONFIG_SERIAL_XILINX_PS_UART is not set ++# CONFIG_SERIAL_ARC is not set ++# CONFIG_SERIAL_RP2 is not set ++# CONFIG_SERIAL_FSL_LPUART is not set ++CONFIG_HVC_DRIVER=y ++CONFIG_VIRTIO_CONSOLE=y ++# CONFIG_IPMI_HANDLER is not set ++CONFIG_HW_RANDOM=y ++# CONFIG_HW_RANDOM_TIMERIOMEM is not set ++# CONFIG_HW_RANDOM_VIRTIO is not set ++CONFIG_HW_RANDOM_XGENE=y ++# CONFIG_R3964 is not set ++# CONFIG_APPLICOM is not set ++ ++# ++# PCMCIA character devices ++# ++# CONFIG_RAW_DRIVER is not set ++# CONFIG_TCG_TPM is not set ++CONFIG_DEVPORT=y ++# CONFIG_XILLYBUS is not set ++ ++# ++# I2C support ++# ++CONFIG_I2C=y ++CONFIG_I2C_BOARDINFO=y ++CONFIG_I2C_COMPAT=y ++CONFIG_I2C_CHARDEV=y ++CONFIG_I2C_MUX=y ++ ++# ++# Multiplexer I2C Chip support ++# ++# CONFIG_I2C_ARB_GPIO_CHALLENGE is not set ++# CONFIG_I2C_MUX_GPIO is not set ++# CONFIG_I2C_MUX_PCA9541 is not set ++CONFIG_I2C_MUX_PCA954x=y ++CONFIG_I2C_HELPER_AUTO=y ++ ++# ++# I2C Hardware Bus support ++# ++ ++# ++# PC SMBus host controller drivers ++# ++# CONFIG_I2C_ALI1535 is not set ++# CONFIG_I2C_ALI1563 is not set ++# CONFIG_I2C_ALI15X3 is not set ++# CONFIG_I2C_AMD756 is not set ++# CONFIG_I2C_AMD8111 is not set ++# CONFIG_I2C_I801 is not set ++# CONFIG_I2C_ISCH is not set ++# CONFIG_I2C_PIIX4 is not set ++# CONFIG_I2C_NFORCE2 is not set ++# CONFIG_I2C_SIS5595 is not set ++# CONFIG_I2C_SIS630 is not set ++# CONFIG_I2C_SIS96X is not set ++# CONFIG_I2C_VIA is not set ++# CONFIG_I2C_VIAPRO is not set ++ ++# ++# I2C system bus drivers (mostly embedded / system-on-chip) ++# ++# CONFIG_I2C_CBUS_GPIO is not set ++# CONFIG_I2C_DESIGNWARE_PLATFORM is not set ++# CONFIG_I2C_DESIGNWARE_PCI is not set ++# CONFIG_I2C_GPIO is not set ++CONFIG_I2C_IMX=y ++# CONFIG_I2C_NOMADIK is not set ++# CONFIG_I2C_OCORES is not set ++# CONFIG_I2C_PCA_PLATFORM is not set ++# CONFIG_I2C_PXA_PCI is not set ++# CONFIG_I2C_RK3X is not set ++# CONFIG_I2C_SIMTEC is not set ++# CONFIG_I2C_VERSATILE is not set ++# CONFIG_I2C_XILINX is not set ++ ++# ++# External I2C/SMBus adapter drivers ++# ++# CONFIG_I2C_DIOLAN_U2C is not set ++# CONFIG_I2C_PARPORT_LIGHT is not set ++# CONFIG_I2C_ROBOTFUZZ_OSIF is not set ++# CONFIG_I2C_TAOS_EVM is not set ++# CONFIG_I2C_TINY_USB is not set ++ ++# ++# Other I2C/SMBus bus drivers ++# ++# CONFIG_I2C_STUB is not set ++# CONFIG_I2C_DEBUG_CORE is not set ++# CONFIG_I2C_DEBUG_ALGO is not set ++# CONFIG_I2C_DEBUG_BUS is not set ++CONFIG_SPI=y ++# CONFIG_SPI_DEBUG is not set ++CONFIG_SPI_MASTER=y ++ ++# ++# SPI Master Controller Drivers ++# ++# CONFIG_SPI_ALTERA is not set ++# CONFIG_SPI_BITBANG is not set ++# CONFIG_SPI_GPIO is not set ++# CONFIG_SPI_FSL_SPI is not set ++# CONFIG_SPI_OC_TINY is not set ++CONFIG_SPI_PL022=y ++# CONFIG_SPI_PXA2XX is not set ++# CONFIG_SPI_PXA2XX_PCI is not set ++# CONFIG_SPI_ROCKCHIP is not set ++# CONFIG_SPI_SC18IS602 is not set ++# CONFIG_SPI_XCOMM is not set ++# CONFIG_SPI_XILINX is not set ++# CONFIG_SPI_DESIGNWARE is not set ++ ++# ++# SPI Protocol Masters ++# ++# CONFIG_SPI_SPIDEV is not set ++# CONFIG_SPI_TLE62X0 is not set ++# CONFIG_SPMI is not set ++# CONFIG_HSI is not set ++ ++# ++# PPS support ++# ++CONFIG_PPS=y ++# CONFIG_PPS_DEBUG is not set ++# CONFIG_NTP_PPS is not set ++ ++# ++# PPS clients support ++# ++# CONFIG_PPS_CLIENT_KTIMER is not set ++# CONFIG_PPS_CLIENT_LDISC is not set ++# CONFIG_PPS_CLIENT_GPIO is not set ++ ++# ++# PPS generators support ++# ++ ++# ++# PTP clock support ++# ++CONFIG_PTP_1588_CLOCK=y ++ ++# ++# Enable PHYLIB and NETWORK_PHY_TIMESTAMPING to see the additional clocks. ++# ++CONFIG_ARCH_WANT_OPTIONAL_GPIOLIB=y ++CONFIG_ARCH_REQUIRE_GPIOLIB=y ++CONFIG_GPIOLIB=y ++CONFIG_GPIO_DEVRES=y ++CONFIG_OF_GPIO=y ++CONFIG_GPIOLIB_IRQCHIP=y ++# CONFIG_DEBUG_GPIO is not set ++# CONFIG_GPIO_SYSFS is not set ++CONFIG_GPIO_GENERIC=y ++ ++# ++# Memory mapped GPIO drivers: ++# ++CONFIG_GPIO_GENERIC_PLATFORM=y ++# CONFIG_GPIO_DWAPB is not set ++CONFIG_GPIO_PL061=y ++# CONFIG_GPIO_SCH311X is not set ++# CONFIG_GPIO_SYSCON is not set ++CONFIG_GPIO_XGENE=y ++# CONFIG_GPIO_VX855 is not set ++# CONFIG_GPIO_GRGPIO is not set ++ ++# ++# I2C GPIO expanders: ++# ++# CONFIG_GPIO_MAX7300 is not set ++# CONFIG_GPIO_MAX732X is not set ++# CONFIG_GPIO_PCA953X is not set ++# CONFIG_GPIO_PCF857X is not set ++# CONFIG_GPIO_SX150X is not set ++# CONFIG_GPIO_ADP5588 is not set ++# CONFIG_GPIO_ADNP is not set ++ ++# ++# PCI GPIO expanders: ++# ++# CONFIG_GPIO_BT8XX is not set ++# CONFIG_GPIO_AMD8111 is not set ++# CONFIG_GPIO_ML_IOH is not set ++# CONFIG_GPIO_RDC321X is not set ++ ++# ++# SPI GPIO expanders: ++# ++# CONFIG_GPIO_MAX7301 is not set ++# CONFIG_GPIO_MCP23S08 is not set ++# CONFIG_GPIO_MC33880 is not set ++# CONFIG_GPIO_74X164 is not set ++ ++# ++# AC97 GPIO expanders: ++# ++ ++# ++# LPC GPIO expanders: ++# ++ ++# ++# MODULbus GPIO expanders: ++# ++ ++# ++# USB GPIO expanders: ++# ++# CONFIG_W1 is not set ++CONFIG_POWER_SUPPLY=y ++# CONFIG_POWER_SUPPLY_DEBUG is not set ++# CONFIG_PDA_POWER is not set ++# CONFIG_TEST_POWER is not set ++# CONFIG_BATTERY_DS2780 is not set ++# CONFIG_BATTERY_DS2781 is not set ++# CONFIG_BATTERY_DS2782 is not set ++# CONFIG_BATTERY_SBS is not set ++# CONFIG_BATTERY_BQ27x00 is not set ++# CONFIG_BATTERY_MAX17040 is not set ++# CONFIG_BATTERY_MAX17042 is not set ++# CONFIG_CHARGER_MAX8903 is not set ++# CONFIG_CHARGER_LP8727 is not set ++# CONFIG_CHARGER_GPIO is not set ++# CONFIG_CHARGER_MANAGER is not set ++# CONFIG_CHARGER_BQ2415X is not set ++# CONFIG_CHARGER_BQ24190 is not set ++# CONFIG_CHARGER_BQ24735 is not set ++# CONFIG_CHARGER_SMB347 is not set ++CONFIG_POWER_RESET=y ++# CONFIG_POWER_RESET_GPIO is not set ++# CONFIG_POWER_RESET_GPIO_RESTART is not set ++# CONFIG_POWER_RESET_LTC2952 is not set ++CONFIG_POWER_RESET_VEXPRESS=y ++# CONFIG_POWER_RESET_XGENE is not set ++# CONFIG_POWER_RESET_SYSCON is not set ++CONFIG_POWER_RESET_LAYERSCAPE=y ++# CONFIG_POWER_AVS is not set ++# CONFIG_HWMON is not set ++# CONFIG_THERMAL is not set ++# CONFIG_WATCHDOG is not set ++CONFIG_SSB_POSSIBLE=y ++ ++# ++# Sonics Silicon Backplane ++# ++# CONFIG_SSB is not set ++CONFIG_BCMA_POSSIBLE=y ++ ++# ++# Broadcom specific AMBA ++# ++# CONFIG_BCMA is not set ++ ++# ++# Multifunction device drivers ++# ++CONFIG_MFD_CORE=y ++# CONFIG_MFD_AS3711 is not set ++# CONFIG_MFD_AS3722 is not set ++# CONFIG_PMIC_ADP5520 is not set ++# CONFIG_MFD_AAT2870_CORE is not set ++# CONFIG_MFD_BCM590XX is not set ++# CONFIG_MFD_AXP20X is not set ++# CONFIG_MFD_CROS_EC is not set ++# CONFIG_PMIC_DA903X is not set ++# CONFIG_MFD_DA9052_SPI is not set ++# CONFIG_MFD_DA9052_I2C is not set ++# CONFIG_MFD_DA9055 is not set ++# CONFIG_MFD_DA9063 is not set ++# CONFIG_MFD_MC13XXX_SPI is not set ++# CONFIG_MFD_MC13XXX_I2C is not set ++# CONFIG_MFD_HI6421_PMIC is not set ++# CONFIG_HTC_PASIC3 is not set ++# CONFIG_HTC_I2CPLD is not set ++# CONFIG_LPC_ICH is not set ++# CONFIG_LPC_SCH is not set ++# CONFIG_INTEL_SOC_PMIC is not set ++# CONFIG_MFD_JANZ_CMODIO is not set ++# CONFIG_MFD_KEMPLD is not set ++# CONFIG_MFD_88PM800 is not set ++# CONFIG_MFD_88PM805 is not set ++# CONFIG_MFD_88PM860X is not set ++# CONFIG_MFD_MAX14577 is not set ++# CONFIG_MFD_MAX77686 is not set ++# CONFIG_MFD_MAX77693 is not set ++# CONFIG_MFD_MAX8907 is not set ++# CONFIG_MFD_MAX8925 is not set ++# CONFIG_MFD_MAX8997 is not set ++# CONFIG_MFD_MAX8998 is not set ++# CONFIG_MFD_MENF21BMC is not set ++# CONFIG_EZX_PCAP is not set ++# CONFIG_MFD_VIPERBOARD is not set ++# CONFIG_MFD_RETU is not set ++# CONFIG_MFD_PCF50633 is not set ++# CONFIG_MFD_RDC321X is not set ++# CONFIG_MFD_RTSX_PCI is not set ++# CONFIG_MFD_RTSX_USB is not set ++# CONFIG_MFD_RC5T583 is not set ++# CONFIG_MFD_RK808 is not set ++# CONFIG_MFD_RN5T618 is not set ++# CONFIG_MFD_SEC_CORE is not set ++# CONFIG_MFD_SI476X_CORE is not set ++# CONFIG_MFD_SM501 is not set ++# CONFIG_MFD_SMSC is not set ++# CONFIG_ABX500_CORE is not set ++# CONFIG_MFD_STMPE is not set ++CONFIG_MFD_SYSCON=y ++# CONFIG_MFD_TI_AM335X_TSCADC is not set ++# CONFIG_MFD_LP3943 is not set ++# CONFIG_MFD_LP8788 is not set ++# CONFIG_MFD_PALMAS is not set ++# CONFIG_TPS6105X is not set ++# CONFIG_TPS65010 is not set ++# CONFIG_TPS6507X is not set ++# CONFIG_MFD_TPS65090 is not set ++# CONFIG_MFD_TPS65217 is not set ++# CONFIG_MFD_TPS65218 is not set ++# CONFIG_MFD_TPS6586X is not set ++# CONFIG_MFD_TPS65910 is not set ++# CONFIG_MFD_TPS65912 is not set ++# CONFIG_MFD_TPS65912_I2C is not set ++# CONFIG_MFD_TPS65912_SPI is not set ++# CONFIG_MFD_TPS80031 is not set ++# CONFIG_TWL4030_CORE is not set ++# CONFIG_TWL6040_CORE is not set ++# CONFIG_MFD_WL1273_CORE is not set ++# CONFIG_MFD_LM3533 is not set ++# CONFIG_MFD_TC3589X is not set ++# CONFIG_MFD_TMIO is not set ++# CONFIG_MFD_VX855 is not set ++# CONFIG_MFD_ARIZONA_I2C is not set ++# CONFIG_MFD_ARIZONA_SPI is not set ++# CONFIG_MFD_WM8400 is not set ++# CONFIG_MFD_WM831X_I2C is not set ++# CONFIG_MFD_WM831X_SPI is not set ++# CONFIG_MFD_WM8350_I2C is not set ++# CONFIG_MFD_WM8994 is not set ++CONFIG_MFD_VEXPRESS_SYSREG=y ++CONFIG_REGULATOR=y ++# CONFIG_REGULATOR_DEBUG is not set ++CONFIG_REGULATOR_FIXED_VOLTAGE=y ++# CONFIG_REGULATOR_VIRTUAL_CONSUMER is not set ++# CONFIG_REGULATOR_USERSPACE_CONSUMER is not set ++# CONFIG_REGULATOR_ACT8865 is not set ++# CONFIG_REGULATOR_AD5398 is not set ++# CONFIG_REGULATOR_ANATOP is not set ++# CONFIG_REGULATOR_DA9210 is not set ++# CONFIG_REGULATOR_DA9211 is not set ++# CONFIG_REGULATOR_FAN53555 is not set ++# CONFIG_REGULATOR_GPIO is not set ++# CONFIG_REGULATOR_ISL9305 is not set ++# CONFIG_REGULATOR_ISL6271A is not set ++# CONFIG_REGULATOR_LP3971 is not set ++# CONFIG_REGULATOR_LP3972 is not set ++# CONFIG_REGULATOR_LP872X is not set ++# CONFIG_REGULATOR_LP8755 is not set ++# CONFIG_REGULATOR_LTC3589 is not set ++# CONFIG_REGULATOR_MAX1586 is not set ++# CONFIG_REGULATOR_MAX8649 is not set ++# CONFIG_REGULATOR_MAX8660 is not set ++# CONFIG_REGULATOR_MAX8952 is not set ++# CONFIG_REGULATOR_MAX8973 is not set ++# CONFIG_REGULATOR_PFUZE100 is not set ++# CONFIG_REGULATOR_TPS51632 is not set ++# CONFIG_REGULATOR_TPS62360 is not set ++# CONFIG_REGULATOR_TPS65023 is not set ++# CONFIG_REGULATOR_TPS6507X is not set ++# CONFIG_REGULATOR_TPS6524X is not set ++# CONFIG_REGULATOR_VEXPRESS is not set ++# CONFIG_MEDIA_SUPPORT is not set ++ ++# ++# Graphics support ++# ++CONFIG_VGA_ARB=y ++CONFIG_VGA_ARB_MAX_GPUS=16 ++ ++# ++# Direct Rendering Manager ++# ++# CONFIG_DRM is not set ++ ++# ++# Frame buffer Devices ++# ++CONFIG_FB=y ++# CONFIG_FIRMWARE_EDID is not set ++CONFIG_FB_CMDLINE=y ++# CONFIG_FB_DDC is not set ++# CONFIG_FB_BOOT_VESA_SUPPORT is not set ++CONFIG_FB_CFB_FILLRECT=y ++CONFIG_FB_CFB_COPYAREA=y ++CONFIG_FB_CFB_IMAGEBLIT=y ++# CONFIG_FB_CFB_REV_PIXELS_IN_BYTE is not set ++# CONFIG_FB_SYS_FILLRECT is not set ++# CONFIG_FB_SYS_COPYAREA is not set ++# CONFIG_FB_SYS_IMAGEBLIT is not set ++# CONFIG_FB_FOREIGN_ENDIAN is not set ++# CONFIG_FB_SYS_FOPS is not set ++# CONFIG_FB_SVGALIB is not set ++# CONFIG_FB_MACMODES is not set ++# CONFIG_FB_BACKLIGHT is not set ++CONFIG_FB_MODE_HELPERS=y ++# CONFIG_FB_TILEBLITTING is not set ++ ++# ++# Frame buffer hardware drivers ++# ++# CONFIG_FB_CIRRUS is not set ++# CONFIG_FB_PM2 is not set ++CONFIG_FB_ARMCLCD=y ++# CONFIG_FB_CYBER2000 is not set ++# CONFIG_FB_ASILIANT is not set ++# CONFIG_FB_IMSTT is not set ++# CONFIG_FB_OPENCORES is not set ++# CONFIG_FB_S1D13XXX is not set ++# CONFIG_FB_NVIDIA is not set ++# CONFIG_FB_RIVA is not set ++# CONFIG_FB_I740 is not set ++# CONFIG_FB_MATROX is not set ++# CONFIG_FB_RADEON is not set ++# CONFIG_FB_ATY128 is not set ++# CONFIG_FB_ATY is not set ++# CONFIG_FB_S3 is not set ++# CONFIG_FB_SAVAGE is not set ++# CONFIG_FB_SIS is not set ++# CONFIG_FB_NEOMAGIC is not set ++# CONFIG_FB_KYRO is not set ++# CONFIG_FB_3DFX is not set ++# CONFIG_FB_VOODOO1 is not set ++# CONFIG_FB_VT8623 is not set ++# CONFIG_FB_TRIDENT is not set ++# CONFIG_FB_ARK is not set ++# CONFIG_FB_PM3 is not set ++# CONFIG_FB_CARMINE is not set ++# CONFIG_FB_SMSCUFX is not set ++# CONFIG_FB_UDL is not set ++# CONFIG_FB_VIRTUAL is not set ++# CONFIG_FB_METRONOME is not set ++# CONFIG_FB_MB862XX is not set ++# CONFIG_FB_BROADSHEET is not set ++# CONFIG_FB_AUO_K190X is not set ++# CONFIG_FB_SIMPLE is not set ++# CONFIG_FB_SSD1307 is not set ++# CONFIG_BACKLIGHT_LCD_SUPPORT is not set ++# CONFIG_VGASTATE is not set ++CONFIG_VIDEOMODE_HELPERS=y ++ ++# ++# Console display driver support ++# ++CONFIG_DUMMY_CONSOLE=y ++CONFIG_FRAMEBUFFER_CONSOLE=y ++# CONFIG_FRAMEBUFFER_CONSOLE_DETECT_PRIMARY is not set ++# CONFIG_FRAMEBUFFER_CONSOLE_ROTATION is not set ++CONFIG_LOGO=y ++# CONFIG_LOGO_LINUX_MONO is not set ++# CONFIG_LOGO_LINUX_VGA16 is not set ++CONFIG_LOGO_LINUX_CLUT224=y ++# CONFIG_SOUND is not set ++ ++# ++# HID support ++# ++CONFIG_HID=y ++# CONFIG_HID_BATTERY_STRENGTH is not set ++# CONFIG_HIDRAW is not set ++# CONFIG_UHID is not set ++CONFIG_HID_GENERIC=y ++ ++# ++# Special HID drivers ++# ++CONFIG_HID_A4TECH=y ++# CONFIG_HID_ACRUX is not set ++CONFIG_HID_APPLE=y ++# CONFIG_HID_APPLEIR is not set ++# CONFIG_HID_AUREAL is not set ++CONFIG_HID_BELKIN=y ++CONFIG_HID_CHERRY=y ++CONFIG_HID_CHICONY=y ++# CONFIG_HID_CP2112 is not set ++CONFIG_HID_CYPRESS=y ++# CONFIG_HID_DRAGONRISE is not set ++# CONFIG_HID_EMS_FF is not set ++# CONFIG_HID_ELECOM is not set ++# CONFIG_HID_ELO is not set ++CONFIG_HID_EZKEY=y ++# CONFIG_HID_HOLTEK is not set ++# CONFIG_HID_HUION is not set ++# CONFIG_HID_KEYTOUCH is not set ++# CONFIG_HID_KYE is not set ++# CONFIG_HID_UCLOGIC is not set ++# CONFIG_HID_WALTOP is not set ++# CONFIG_HID_GYRATION is not set ++# CONFIG_HID_ICADE is not set ++# CONFIG_HID_TWINHAN is not set ++CONFIG_HID_KENSINGTON=y ++# CONFIG_HID_LCPOWER is not set ++# CONFIG_HID_LENOVO is not set ++CONFIG_HID_LOGITECH=y ++# CONFIG_HID_LOGITECH_HIDPP is not set ++# CONFIG_LOGITECH_FF is not set ++# CONFIG_LOGIRUMBLEPAD2_FF is not set ++# CONFIG_LOGIG940_FF is not set ++# CONFIG_LOGIWHEELS_FF is not set ++# CONFIG_HID_MAGICMOUSE is not set ++CONFIG_HID_MICROSOFT=y ++CONFIG_HID_MONTEREY=y ++# CONFIG_HID_MULTITOUCH is not set ++# CONFIG_HID_NTRIG is not set ++# CONFIG_HID_ORTEK is not set ++# CONFIG_HID_PANTHERLORD is not set ++# CONFIG_HID_PENMOUNT is not set ++# CONFIG_HID_PETALYNX is not set ++# CONFIG_HID_PICOLCD is not set ++# CONFIG_HID_PRIMAX is not set ++# CONFIG_HID_ROCCAT is not set ++# CONFIG_HID_SAITEK is not set ++# CONFIG_HID_SAMSUNG is not set ++# CONFIG_HID_SPEEDLINK is not set ++# CONFIG_HID_STEELSERIES is not set ++# CONFIG_HID_SUNPLUS is not set ++# CONFIG_HID_RMI is not set ++# CONFIG_HID_GREENASIA is not set ++# CONFIG_HID_SMARTJOYPLUS is not set ++# CONFIG_HID_TIVO is not set ++# CONFIG_HID_TOPSEED is not set ++# CONFIG_HID_THRUSTMASTER is not set ++# CONFIG_HID_WACOM is not set ++# CONFIG_HID_XINMO is not set ++# CONFIG_HID_ZEROPLUS is not set ++# CONFIG_HID_ZYDACRON is not set ++# CONFIG_HID_SENSOR_HUB is not set ++ ++# ++# USB HID support ++# ++CONFIG_USB_HID=y ++# CONFIG_HID_PID is not set ++# CONFIG_USB_HIDDEV is not set ++ ++# ++# I2C HID support ++# ++# CONFIG_I2C_HID is not set ++CONFIG_USB_OHCI_LITTLE_ENDIAN=y ++CONFIG_USB_SUPPORT=y ++CONFIG_USB_COMMON=y ++CONFIG_USB_ARCH_HAS_HCD=y ++CONFIG_USB=y ++# CONFIG_USB_ANNOUNCE_NEW_DEVICES is not set ++ ++# ++# Miscellaneous USB options ++# ++CONFIG_USB_DEFAULT_PERSIST=y ++# CONFIG_USB_DYNAMIC_MINORS is not set ++# CONFIG_USB_OTG_WHITELIST is not set ++# CONFIG_USB_OTG_FSM is not set ++# CONFIG_USB_MON is not set ++# CONFIG_USB_WUSB_CBAF is not set ++ ++# ++# USB Host Controller Drivers ++# ++# CONFIG_USB_C67X00_HCD is not set ++CONFIG_USB_XHCI_HCD=y ++CONFIG_USB_XHCI_PCI=y ++CONFIG_USB_XHCI_PLATFORM=y ++CONFIG_USB_EHCI_HCD=y ++# CONFIG_USB_EHCI_ROOT_HUB_TT is not set ++CONFIG_USB_EHCI_TT_NEWSCHED=y ++CONFIG_USB_EHCI_PCI=y ++CONFIG_USB_EHCI_HCD_PLATFORM=y ++# CONFIG_USB_OXU210HP_HCD is not set ++# CONFIG_USB_ISP116X_HCD is not set ++CONFIG_USB_ISP1760_HCD=y ++# CONFIG_USB_ISP1362_HCD is not set ++# CONFIG_USB_FUSBH200_HCD is not set ++# CONFIG_USB_FOTG210_HCD is not set ++# CONFIG_USB_MAX3421_HCD is not set ++CONFIG_USB_OHCI_HCD=y ++CONFIG_USB_OHCI_HCD_PCI=y ++CONFIG_USB_OHCI_HCD_PLATFORM=y ++# CONFIG_USB_UHCI_HCD is not set ++# CONFIG_USB_SL811_HCD is not set ++# CONFIG_USB_R8A66597_HCD is not set ++# CONFIG_USB_HCD_TEST_MODE is not set ++ ++# ++# USB Device Class drivers ++# ++# CONFIG_USB_ACM is not set ++# CONFIG_USB_PRINTER is not set ++# CONFIG_USB_WDM is not set ++# CONFIG_USB_TMC is not set ++ ++# ++# NOTE: USB_STORAGE depends on SCSI but BLK_DEV_SD may ++# ++ ++# ++# also be needed; see USB_STORAGE Help for more info ++# ++CONFIG_USB_STORAGE=y ++# CONFIG_USB_STORAGE_DEBUG is not set ++# CONFIG_USB_STORAGE_REALTEK is not set ++# CONFIG_USB_STORAGE_DATAFAB is not set ++# CONFIG_USB_STORAGE_FREECOM is not set ++# CONFIG_USB_STORAGE_ISD200 is not set ++# CONFIG_USB_STORAGE_USBAT is not set ++# CONFIG_USB_STORAGE_SDDR09 is not set ++# CONFIG_USB_STORAGE_SDDR55 is not set ++# CONFIG_USB_STORAGE_JUMPSHOT is not set ++# CONFIG_USB_STORAGE_ALAUDA is not set ++# CONFIG_USB_STORAGE_ONETOUCH is not set ++# CONFIG_USB_STORAGE_KARMA is not set ++# CONFIG_USB_STORAGE_CYPRESS_ATACB is not set ++# CONFIG_USB_STORAGE_ENE_UB6250 is not set ++# CONFIG_USB_UAS is not set ++ ++# ++# USB Imaging devices ++# ++# CONFIG_USB_MDC800 is not set ++# CONFIG_USB_MICROTEK is not set ++# CONFIG_USBIP_CORE is not set ++# CONFIG_USB_MUSB_HDRC is not set ++CONFIG_USB_DWC3=y ++CONFIG_USB_DWC3_HOST=y ++ ++# ++# Platform Glue Driver Support ++# ++CONFIG_USB_DWC3_PCI=y ++ ++# ++# Debugging features ++# ++# CONFIG_USB_DWC3_DEBUG is not set ++# CONFIG_DWC3_HOST_USB3_LPM_ENABLE is not set ++# CONFIG_USB_DWC2 is not set ++# CONFIG_USB_CHIPIDEA is not set ++ ++# ++# USB port drivers ++# ++# CONFIG_USB_SERIAL is not set ++ ++# ++# USB Miscellaneous drivers ++# ++# CONFIG_USB_EMI62 is not set ++# CONFIG_USB_EMI26 is not set ++# CONFIG_USB_ADUTUX is not set ++# CONFIG_USB_SEVSEG is not set ++# CONFIG_USB_RIO500 is not set ++# CONFIG_USB_LEGOTOWER is not set ++# CONFIG_USB_LCD is not set ++# CONFIG_USB_LED is not set ++# CONFIG_USB_CYPRESS_CY7C63 is not set ++# CONFIG_USB_CYTHERM is not set ++# CONFIG_USB_IDMOUSE is not set ++# CONFIG_USB_FTDI_ELAN is not set ++# CONFIG_USB_APPLEDISPLAY is not set ++# CONFIG_USB_SISUSBVGA is not set ++# CONFIG_USB_LD is not set ++# CONFIG_USB_TRANCEVIBRATOR is not set ++# CONFIG_USB_IOWARRIOR is not set ++# CONFIG_USB_TEST is not set ++# CONFIG_USB_EHSET_TEST_FIXTURE is not set ++# CONFIG_USB_ISIGHTFW is not set ++# CONFIG_USB_YUREX is not set ++# CONFIG_USB_EZUSB_FX2 is not set ++# CONFIG_USB_HSIC_USB3503 is not set ++# CONFIG_USB_LINK_LAYER_TEST is not set ++ ++# ++# USB Physical Layer drivers ++# ++# CONFIG_USB_PHY is not set ++# CONFIG_NOP_USB_XCEIV is not set ++# CONFIG_USB_GPIO_VBUS is not set ++# CONFIG_USB_ISP1301 is not set ++CONFIG_USB_ULPI=y ++# CONFIG_USB_GADGET is not set ++# CONFIG_UWB is not set ++CONFIG_MMC=y ++# CONFIG_MMC_DEBUG is not set ++# CONFIG_MMC_CLKGATE is not set ++ ++# ++# MMC/SD/SDIO Card Drivers ++# ++CONFIG_MMC_BLOCK=y ++CONFIG_MMC_BLOCK_MINORS=8 ++CONFIG_MMC_BLOCK_BOUNCE=y ++# CONFIG_SDIO_UART is not set ++# CONFIG_MMC_TEST is not set ++ ++# ++# MMC/SD/SDIO Host Controller Drivers ++# ++CONFIG_MMC_ARMMMCI=y ++CONFIG_MMC_SDHCI=y ++CONFIG_MMC_SDHCI_IO_ACCESSORS=y ++# CONFIG_MMC_SDHCI_PCI is not set ++CONFIG_MMC_SDHCI_PLTFM=y ++# CONFIG_MMC_SDHCI_OF_ARASAN is not set ++CONFIG_MMC_SDHCI_OF_ESDHC=y ++# CONFIG_MMC_SDHCI_PXAV3 is not set ++# CONFIG_MMC_SDHCI_PXAV2 is not set ++# CONFIG_MMC_TIFM_SD is not set ++CONFIG_MMC_SPI=y ++# CONFIG_MMC_CB710 is not set ++# CONFIG_MMC_VIA_SDMMC is not set ++# CONFIG_MMC_VUB300 is not set ++# CONFIG_MMC_USHC is not set ++# CONFIG_MMC_USDHI6ROL0 is not set ++# CONFIG_MEMSTICK is not set ++# CONFIG_NEW_LEDS is not set ++# CONFIG_ACCESSIBILITY is not set ++# CONFIG_INFINIBAND is not set ++CONFIG_RTC_LIB=y ++CONFIG_RTC_CLASS=y ++CONFIG_RTC_HCTOSYS=y ++CONFIG_RTC_SYSTOHC=y ++CONFIG_RTC_HCTOSYS_DEVICE="rtc0" ++# CONFIG_RTC_DEBUG is not set ++ ++# ++# RTC interfaces ++# ++CONFIG_RTC_INTF_SYSFS=y ++CONFIG_RTC_INTF_PROC=y ++CONFIG_RTC_INTF_DEV=y ++# CONFIG_RTC_INTF_DEV_UIE_EMUL is not set ++# CONFIG_RTC_DRV_TEST is not set ++ ++# ++# I2C RTC drivers ++# ++# CONFIG_RTC_DRV_DS1307 is not set ++# CONFIG_RTC_DRV_DS1374 is not set ++# CONFIG_RTC_DRV_DS1672 is not set ++CONFIG_RTC_DRV_DS3232=y ++# CONFIG_RTC_DRV_HYM8563 is not set ++# CONFIG_RTC_DRV_MAX6900 is not set ++# CONFIG_RTC_DRV_RS5C372 is not set ++# CONFIG_RTC_DRV_ISL1208 is not set ++# CONFIG_RTC_DRV_ISL12022 is not set ++# CONFIG_RTC_DRV_ISL12057 is not set ++# CONFIG_RTC_DRV_X1205 is not set ++# CONFIG_RTC_DRV_PCF2127 is not set ++# CONFIG_RTC_DRV_PCF8523 is not set ++# CONFIG_RTC_DRV_PCF8563 is not set ++# CONFIG_RTC_DRV_PCF85063 is not set ++# CONFIG_RTC_DRV_PCF8583 is not set ++# CONFIG_RTC_DRV_M41T80 is not set ++# CONFIG_RTC_DRV_BQ32K is not set ++# CONFIG_RTC_DRV_S35390A is not set ++# CONFIG_RTC_DRV_FM3130 is not set ++# CONFIG_RTC_DRV_RX8581 is not set ++# CONFIG_RTC_DRV_RX8025 is not set ++# CONFIG_RTC_DRV_EM3027 is not set ++# CONFIG_RTC_DRV_RV3029C2 is not set ++ ++# ++# SPI RTC drivers ++# ++# CONFIG_RTC_DRV_M41T93 is not set ++# CONFIG_RTC_DRV_M41T94 is not set ++# CONFIG_RTC_DRV_DS1305 is not set ++# CONFIG_RTC_DRV_DS1343 is not set ++# CONFIG_RTC_DRV_DS1347 is not set ++# CONFIG_RTC_DRV_DS1390 is not set ++# CONFIG_RTC_DRV_MAX6902 is not set ++# CONFIG_RTC_DRV_R9701 is not set ++# CONFIG_RTC_DRV_RS5C348 is not set ++# CONFIG_RTC_DRV_DS3234 is not set ++# CONFIG_RTC_DRV_PCF2123 is not set ++# CONFIG_RTC_DRV_RX4581 is not set ++# CONFIG_RTC_DRV_MCP795 is not set ++ ++# ++# Platform RTC drivers ++# ++# CONFIG_RTC_DRV_DS1286 is not set ++# CONFIG_RTC_DRV_DS1511 is not set ++# CONFIG_RTC_DRV_DS1553 is not set ++# CONFIG_RTC_DRV_DS1742 is not set ++# CONFIG_RTC_DRV_DS2404 is not set ++CONFIG_RTC_DRV_EFI=y ++# CONFIG_RTC_DRV_STK17TA8 is not set ++# CONFIG_RTC_DRV_M48T86 is not set ++# CONFIG_RTC_DRV_M48T35 is not set ++# CONFIG_RTC_DRV_M48T59 is not set ++# CONFIG_RTC_DRV_MSM6242 is not set ++# CONFIG_RTC_DRV_BQ4802 is not set ++# CONFIG_RTC_DRV_RP5C01 is not set ++# CONFIG_RTC_DRV_V3020 is not set ++ ++# ++# on-CPU RTC drivers ++# ++# CONFIG_RTC_DRV_PL030 is not set ++# CONFIG_RTC_DRV_PL031 is not set ++# CONFIG_RTC_DRV_SNVS is not set ++CONFIG_RTC_DRV_XGENE=y ++ ++# ++# HID Sensor RTC drivers ++# ++# CONFIG_RTC_DRV_HID_SENSOR_TIME is not set ++CONFIG_DMADEVICES=y ++# CONFIG_DMADEVICES_DEBUG is not set ++ ++# ++# DMA Devices ++# ++# CONFIG_AMBA_PL08X is not set ++# CONFIG_DW_DMAC_CORE is not set ++# CONFIG_DW_DMAC is not set ++# CONFIG_DW_DMAC_PCI is not set ++# CONFIG_PL330_DMA is not set ++# CONFIG_FSL_EDMA is not set ++CONFIG_DMA_ENGINE=y ++CONFIG_DMA_OF=y ++ ++# ++# DMA Clients ++# ++# CONFIG_ASYNC_TX_DMA is not set ++# CONFIG_DMATEST is not set ++# CONFIG_AUXDISPLAY is not set ++# CONFIG_UIO is not set ++# CONFIG_VFIO_IOMMU_TYPE1 is not set ++CONFIG_VFIO=y ++CONFIG_VFIO_PCI=y ++CONFIG_VFIO_FSL_MC=y ++# CONFIG_VIRT_DRIVERS is not set ++CONFIG_VIRTIO=y ++ ++# ++# Virtio drivers ++# ++CONFIG_VIRTIO_PCI=y ++CONFIG_VIRTIO_BALLOON=y ++CONFIG_VIRTIO_MMIO=y ++# CONFIG_VIRTIO_MMIO_CMDLINE_DEVICES is not set ++ ++# ++# Microsoft Hyper-V guest support ++# ++CONFIG_STAGING=y ++# CONFIG_COMEDI is not set ++# CONFIG_RTS5208 is not set ++# CONFIG_FB_XGI is not set ++# CONFIG_BCM_WIMAX is not set ++# CONFIG_FT1000 is not set ++ ++# ++# Speakup console speech ++# ++# CONFIG_SPEAKUP is not set ++# CONFIG_TOUCHSCREEN_CLEARPAD_TM1217 is not set ++# CONFIG_TOUCHSCREEN_SYNAPTICS_I2C_RMI4 is not set ++# CONFIG_STAGING_MEDIA is not set ++ ++# ++# Android ++# ++# CONFIG_ANDROID is not set ++# CONFIG_USB_WPAN_HCD is not set ++# CONFIG_WIMAX_GDM72XX is not set ++# CONFIG_LTE_GDM724X is not set ++# CONFIG_MTD_SPINAND_MT29F is not set ++# CONFIG_LUSTRE_FS is not set ++# CONFIG_DGNC is not set ++# CONFIG_DGAP is not set ++# CONFIG_GS_FPGABOOT is not set ++CONFIG_FSL_MC_BUS=y ++CONFIG_FSL_MC_RESTOOL=y ++CONFIG_FSL_MC_DPIO=y ++# CONFIG_FSL_QBMAN_DEBUG is not set ++CONFIG_FSL_DPAA2=y ++CONFIG_FSL_DPAA2_ETH=y ++# CONFIG_FSL_DPAA2_ETH_USE_ERR_QUEUE is not set ++CONFIG_FSL_DPAA2_MAC=y ++# CONFIG_FSL_DPAA2_MAC_NETDEVS is not set ++ ++# ++# SOC (System On Chip) specific Drivers ++# ++# CONFIG_SOC_TI is not set ++CONFIG_FSL_SOC_DRIVERS=y ++CONFIG_FSL_GUTS=y ++CONFIG_LS_SOC_DRIVERS=y ++CONFIG_CLKDEV_LOOKUP=y ++CONFIG_HAVE_CLK_PREPARE=y ++CONFIG_COMMON_CLK=y ++ ++# ++# Common Clock Framework ++# ++CONFIG_COMMON_CLK_VERSATILE=y ++CONFIG_CLK_SP810=y ++CONFIG_CLK_VEXPRESS_OSC=y ++# CONFIG_COMMON_CLK_SI5351 is not set ++# CONFIG_COMMON_CLK_SI570 is not set ++CONFIG_CLK_QORIQ=y ++CONFIG_COMMON_CLK_XGENE=y ++# CONFIG_COMMON_CLK_PXA is not set ++# CONFIG_COMMON_CLK_QCOM is not set ++ ++# ++# Hardware Spinlock drivers ++# ++ ++# ++# Clock Source drivers ++# ++CONFIG_CLKSRC_OF=y ++CONFIG_CLKSRC_MMIO=y ++CONFIG_ARM_ARCH_TIMER=y ++CONFIG_ARM_ARCH_TIMER_EVTSTREAM=y ++# CONFIG_ATMEL_PIT is not set ++# CONFIG_SH_TIMER_CMT is not set ++# CONFIG_SH_TIMER_MTU2 is not set ++# CONFIG_SH_TIMER_TMU is not set ++# CONFIG_EM_TIMER_STI is not set ++CONFIG_CLKSRC_VERSATILE=y ++# CONFIG_MAILBOX is not set ++CONFIG_IOMMU_API=y ++CONFIG_IOMMU_SUPPORT=y ++ ++# ++# Generic IOMMU Pagetable Support ++# ++CONFIG_IOMMU_IO_PGTABLE=y ++CONFIG_IOMMU_IO_PGTABLE_LPAE=y ++# CONFIG_IOMMU_IO_PGTABLE_LPAE_SELFTEST is not set ++CONFIG_OF_IOMMU=y ++CONFIG_ARM_SMMU=y ++ ++# ++# Remoteproc drivers ++# ++# CONFIG_STE_MODEM_RPROC is not set ++ ++# ++# Rpmsg drivers ++# ++ ++# ++# SOC (System On Chip) specific Drivers ++# ++# CONFIG_PM_DEVFREQ is not set ++# CONFIG_EXTCON is not set ++CONFIG_MEMORY=y ++CONFIG_FSL_IFC=y ++# CONFIG_IIO is not set ++# CONFIG_VME_BUS is not set ++# CONFIG_PWM is not set ++CONFIG_IRQCHIP=y ++CONFIG_ARM_GIC=y ++CONFIG_ARM_GIC_V2M=y ++CONFIG_ARM_GIC_V3=y ++CONFIG_ARM_GIC_V3_ITS=y ++# CONFIG_IPACK_BUS is not set ++CONFIG_RESET_CONTROLLER=y ++# CONFIG_FMC is not set ++ ++# ++# PHY Subsystem ++# ++CONFIG_GENERIC_PHY=y ++# CONFIG_BCM_KONA_USB2_PHY is not set ++CONFIG_PHY_XGENE=y ++# CONFIG_POWERCAP is not set ++# CONFIG_MCB is not set ++CONFIG_RAS=y ++# CONFIG_THUNDERBOLT is not set ++ ++# ++# Firmware Drivers ++# ++# CONFIG_FIRMWARE_MEMMAP is not set ++ ++# ++# EFI (Extensible Firmware Interface) Support ++# ++# CONFIG_EFI_VARS is not set ++CONFIG_EFI_PARAMS_FROM_FDT=y ++CONFIG_EFI_RUNTIME_WRAPPERS=y ++CONFIG_EFI_ARMSTUB=y ++ ++# ++# File systems ++# ++CONFIG_DCACHE_WORD_ACCESS=y ++CONFIG_EXT2_FS=y ++# CONFIG_EXT2_FS_XATTR is not set ++# CONFIG_EXT2_FS_XIP is not set ++CONFIG_EXT3_FS=y ++# CONFIG_EXT3_DEFAULTS_TO_ORDERED is not set ++# CONFIG_EXT3_FS_XATTR is not set ++CONFIG_EXT4_FS=y ++# CONFIG_EXT4_FS_POSIX_ACL is not set ++# CONFIG_EXT4_FS_SECURITY is not set ++# CONFIG_EXT4_DEBUG is not set ++CONFIG_JBD=y ++# CONFIG_JBD_DEBUG is not set ++CONFIG_JBD2=y ++# CONFIG_JBD2_DEBUG is not set ++CONFIG_FS_MBCACHE=y ++# CONFIG_REISERFS_FS is not set ++# CONFIG_JFS_FS is not set ++# CONFIG_XFS_FS is not set ++# CONFIG_GFS2_FS is not set ++# CONFIG_BTRFS_FS is not set ++# CONFIG_NILFS2_FS is not set ++# CONFIG_FS_POSIX_ACL is not set ++CONFIG_FILE_LOCKING=y ++CONFIG_FSNOTIFY=y ++CONFIG_DNOTIFY=y ++CONFIG_INOTIFY_USER=y ++CONFIG_FANOTIFY=y ++CONFIG_FANOTIFY_ACCESS_PERMISSIONS=y ++# CONFIG_QUOTA is not set ++# CONFIG_QUOTACTL is not set ++# CONFIG_AUTOFS4_FS is not set ++CONFIG_FUSE_FS=y ++CONFIG_CUSE=y ++CONFIG_OVERLAY_FS=y ++ ++# ++# Caches ++# ++# CONFIG_FSCACHE is not set ++ ++# ++# CD-ROM/DVD Filesystems ++# ++# CONFIG_ISO9660_FS is not set ++# CONFIG_UDF_FS is not set ++ ++# ++# DOS/FAT/NT Filesystems ++# ++CONFIG_FAT_FS=y ++CONFIG_MSDOS_FS=y ++CONFIG_VFAT_FS=y ++CONFIG_FAT_DEFAULT_CODEPAGE=437 ++CONFIG_FAT_DEFAULT_IOCHARSET="iso8859-1" ++# CONFIG_NTFS_FS is not set ++ ++# ++# Pseudo filesystems ++# ++CONFIG_PROC_FS=y ++# CONFIG_PROC_KCORE is not set ++CONFIG_PROC_SYSCTL=y ++CONFIG_PROC_PAGE_MONITOR=y ++CONFIG_KERNFS=y ++CONFIG_SYSFS=y ++CONFIG_TMPFS=y ++# CONFIG_TMPFS_POSIX_ACL is not set ++CONFIG_TMPFS_XATTR=y ++CONFIG_HUGETLBFS=y ++CONFIG_HUGETLB_PAGE=y ++# CONFIG_CONFIGFS_FS is not set ++CONFIG_MISC_FILESYSTEMS=y ++# CONFIG_ADFS_FS is not set ++# CONFIG_AFFS_FS is not set ++# CONFIG_ECRYPT_FS is not set ++# CONFIG_HFS_FS is not set ++# CONFIG_HFSPLUS_FS is not set ++# CONFIG_BEFS_FS is not set ++# CONFIG_BFS_FS is not set ++# CONFIG_EFS_FS is not set ++CONFIG_JFFS2_FS=y ++CONFIG_JFFS2_FS_DEBUG=0 ++CONFIG_JFFS2_FS_WRITEBUFFER=y ++# CONFIG_JFFS2_FS_WBUF_VERIFY is not set ++CONFIG_JFFS2_SUMMARY=y ++# CONFIG_JFFS2_FS_XATTR is not set ++# CONFIG_JFFS2_COMPRESSION_OPTIONS is not set ++CONFIG_JFFS2_ZLIB=y ++# CONFIG_JFFS2_LZO is not set ++CONFIG_JFFS2_RTIME=y ++# CONFIG_JFFS2_RUBIN is not set ++# CONFIG_LOGFS is not set ++# CONFIG_CRAMFS is not set ++CONFIG_SQUASHFS=y ++CONFIG_SQUASHFS_FILE_CACHE=y ++# CONFIG_SQUASHFS_FILE_DIRECT is not set ++CONFIG_SQUASHFS_DECOMP_SINGLE=y ++# CONFIG_SQUASHFS_DECOMP_MULTI is not set ++# CONFIG_SQUASHFS_DECOMP_MULTI_PERCPU is not set ++CONFIG_SQUASHFS_XATTR=y ++CONFIG_SQUASHFS_ZLIB=y ++CONFIG_SQUASHFS_LZO=y ++CONFIG_SQUASHFS_XZ=y ++# CONFIG_SQUASHFS_4K_DEVBLK_SIZE is not set ++# CONFIG_SQUASHFS_EMBEDDED is not set ++CONFIG_SQUASHFS_FRAGMENT_CACHE_SIZE=3 ++# CONFIG_VXFS_FS is not set ++# CONFIG_MINIX_FS is not set ++# CONFIG_OMFS_FS is not set ++# CONFIG_HPFS_FS is not set ++# CONFIG_QNX4FS_FS is not set ++# CONFIG_QNX6FS_FS is not set ++# CONFIG_ROMFS_FS is not set ++# CONFIG_PSTORE is not set ++# CONFIG_SYSV_FS is not set ++# CONFIG_UFS_FS is not set ++# CONFIG_F2FS_FS is not set ++# CONFIG_EFIVAR_FS is not set ++# CONFIG_AUFS_FS is not set ++CONFIG_NETWORK_FILESYSTEMS=y ++CONFIG_NFS_FS=y ++CONFIG_NFS_V2=y ++CONFIG_NFS_V3=y ++# CONFIG_NFS_V3_ACL is not set ++CONFIG_NFS_V4=y ++# CONFIG_NFS_SWAP is not set ++# CONFIG_NFS_V4_1 is not set ++CONFIG_ROOT_NFS=y ++# CONFIG_NFS_USE_LEGACY_DNS is not set ++CONFIG_NFS_USE_KERNEL_DNS=y ++# CONFIG_NFSD is not set ++CONFIG_GRACE_PERIOD=y ++CONFIG_LOCKD=y ++CONFIG_LOCKD_V4=y ++CONFIG_NFS_COMMON=y ++CONFIG_SUNRPC=y ++CONFIG_SUNRPC_GSS=y ++# CONFIG_SUNRPC_DEBUG is not set ++# CONFIG_CEPH_FS is not set ++# CONFIG_CIFS is not set ++# CONFIG_NCP_FS is not set ++# CONFIG_CODA_FS is not set ++# CONFIG_AFS_FS is not set ++CONFIG_9P_FS=y ++# CONFIG_9P_FS_POSIX_ACL is not set ++# CONFIG_9P_FS_SECURITY is not set ++CONFIG_NLS=y ++CONFIG_NLS_DEFAULT="iso8859-1" ++CONFIG_NLS_CODEPAGE_437=y ++# CONFIG_NLS_CODEPAGE_737 is not set ++# CONFIG_NLS_CODEPAGE_775 is not set ++# CONFIG_NLS_CODEPAGE_850 is not set ++# CONFIG_NLS_CODEPAGE_852 is not set ++# CONFIG_NLS_CODEPAGE_855 is not set ++# CONFIG_NLS_CODEPAGE_857 is not set ++# CONFIG_NLS_CODEPAGE_860 is not set ++# CONFIG_NLS_CODEPAGE_861 is not set ++# CONFIG_NLS_CODEPAGE_862 is not set ++# CONFIG_NLS_CODEPAGE_863 is not set ++# CONFIG_NLS_CODEPAGE_864 is not set ++# CONFIG_NLS_CODEPAGE_865 is not set ++# CONFIG_NLS_CODEPAGE_866 is not set ++# CONFIG_NLS_CODEPAGE_869 is not set ++# CONFIG_NLS_CODEPAGE_936 is not set ++# CONFIG_NLS_CODEPAGE_950 is not set ++# CONFIG_NLS_CODEPAGE_932 is not set ++# CONFIG_NLS_CODEPAGE_949 is not set ++# CONFIG_NLS_CODEPAGE_874 is not set ++# CONFIG_NLS_ISO8859_8 is not set ++# CONFIG_NLS_CODEPAGE_1250 is not set ++# CONFIG_NLS_CODEPAGE_1251 is not set ++CONFIG_NLS_ASCII=y ++CONFIG_NLS_ISO8859_1=y ++# CONFIG_NLS_ISO8859_2 is not set ++# CONFIG_NLS_ISO8859_3 is not set ++# CONFIG_NLS_ISO8859_4 is not set ++# CONFIG_NLS_ISO8859_5 is not set ++# CONFIG_NLS_ISO8859_6 is not set ++# CONFIG_NLS_ISO8859_7 is not set ++# CONFIG_NLS_ISO8859_9 is not set ++# CONFIG_NLS_ISO8859_13 is not set ++# CONFIG_NLS_ISO8859_14 is not set ++# CONFIG_NLS_ISO8859_15 is not set ++# CONFIG_NLS_KOI8_R is not set ++# CONFIG_NLS_KOI8_U is not set ++# CONFIG_NLS_MAC_ROMAN is not set ++# CONFIG_NLS_MAC_CELTIC is not set ++# CONFIG_NLS_MAC_CENTEURO is not set ++# CONFIG_NLS_MAC_CROATIAN is not set ++# CONFIG_NLS_MAC_CYRILLIC is not set ++# CONFIG_NLS_MAC_GAELIC is not set ++# CONFIG_NLS_MAC_GREEK is not set ++# CONFIG_NLS_MAC_ICELAND is not set ++# CONFIG_NLS_MAC_INUIT is not set ++# CONFIG_NLS_MAC_ROMANIAN is not set ++# CONFIG_NLS_MAC_TURKISH is not set ++CONFIG_NLS_UTF8=y ++CONFIG_HAVE_KVM_IRQCHIP=y ++CONFIG_KVM_MMIO=y ++CONFIG_HAVE_KVM_CPU_RELAX_INTERCEPT=y ++CONFIG_VIRTUALIZATION=y ++CONFIG_KVM=y ++CONFIG_KVM_ARM_HOST=y ++CONFIG_KVM_ARM_MAX_VCPUS=8 ++CONFIG_KVM_ARM_VGIC=y ++CONFIG_KVM_ARM_TIMER=y ++ ++# ++# Kernel hacking ++# ++ ++# ++# printk and dmesg options ++# ++CONFIG_PRINTK_TIME=y ++CONFIG_MESSAGE_LOGLEVEL_DEFAULT=4 ++# CONFIG_BOOT_PRINTK_DELAY is not set ++# CONFIG_DYNAMIC_DEBUG is not set ++ ++# ++# Compile-time checks and compiler options ++# ++CONFIG_DEBUG_INFO=y ++# CONFIG_DEBUG_INFO_REDUCED is not set ++# CONFIG_DEBUG_INFO_SPLIT is not set ++# CONFIG_DEBUG_INFO_DWARF4 is not set ++CONFIG_ENABLE_WARN_DEPRECATED=y ++CONFIG_ENABLE_MUST_CHECK=y ++CONFIG_FRAME_WARN=2048 ++# CONFIG_STRIP_ASM_SYMS is not set ++# CONFIG_READABLE_ASM is not set ++# CONFIG_UNUSED_SYMBOLS is not set ++CONFIG_DEBUG_FS=y ++# CONFIG_HEADERS_CHECK is not set ++# CONFIG_DEBUG_SECTION_MISMATCH is not set ++CONFIG_ARCH_WANT_FRAME_POINTERS=y ++CONFIG_FRAME_POINTER=y ++# CONFIG_DEBUG_FORCE_WEAK_PER_CPU is not set ++CONFIG_MAGIC_SYSRQ=y ++CONFIG_MAGIC_SYSRQ_DEFAULT_ENABLE=0x1 ++CONFIG_DEBUG_KERNEL=y ++ ++# ++# Memory Debugging ++# ++# CONFIG_DEBUG_PAGEALLOC is not set ++# CONFIG_DEBUG_OBJECTS is not set ++# CONFIG_DEBUG_SLAB is not set ++CONFIG_HAVE_DEBUG_KMEMLEAK=y ++# CONFIG_DEBUG_KMEMLEAK is not set ++# CONFIG_DEBUG_STACK_USAGE is not set ++# CONFIG_DEBUG_VM is not set ++CONFIG_DEBUG_MEMORY_INIT=y ++# CONFIG_DEBUG_PER_CPU_MAPS is not set ++# CONFIG_DEBUG_SHIRQ is not set ++ ++# ++# Debug Lockups and Hangs ++# ++CONFIG_LOCKUP_DETECTOR=y ++# CONFIG_BOOTPARAM_SOFTLOCKUP_PANIC is not set ++CONFIG_BOOTPARAM_SOFTLOCKUP_PANIC_VALUE=0 ++CONFIG_DETECT_HUNG_TASK=y ++CONFIG_DEFAULT_HUNG_TASK_TIMEOUT=120 ++# CONFIG_BOOTPARAM_HUNG_TASK_PANIC is not set ++CONFIG_BOOTPARAM_HUNG_TASK_PANIC_VALUE=0 ++# CONFIG_PANIC_ON_OOPS is not set ++CONFIG_PANIC_ON_OOPS_VALUE=0 ++CONFIG_PANIC_TIMEOUT=0 ++CONFIG_SCHED_DEBUG=y ++# CONFIG_SCHEDSTATS is not set ++# CONFIG_SCHED_STACK_END_CHECK is not set ++# CONFIG_TIMER_STATS is not set ++CONFIG_DEBUG_PREEMPT=y ++ ++# ++# Lock Debugging (spinlocks, mutexes, etc...) ++# ++# CONFIG_DEBUG_RT_MUTEXES is not set ++# CONFIG_DEBUG_SPINLOCK is not set ++# CONFIG_DEBUG_MUTEXES is not set ++# CONFIG_DEBUG_WW_MUTEX_SLOWPATH is not set ++# CONFIG_DEBUG_LOCK_ALLOC is not set ++# CONFIG_PROVE_LOCKING is not set ++# CONFIG_LOCK_STAT is not set ++# CONFIG_DEBUG_ATOMIC_SLEEP is not set ++# CONFIG_DEBUG_LOCKING_API_SELFTESTS is not set ++# CONFIG_LOCK_TORTURE_TEST is not set ++# CONFIG_STACKTRACE is not set ++# CONFIG_DEBUG_KOBJECT is not set ++CONFIG_HAVE_DEBUG_BUGVERBOSE=y ++CONFIG_DEBUG_BUGVERBOSE=y ++# CONFIG_DEBUG_LIST is not set ++# CONFIG_DEBUG_PI_LIST is not set ++# CONFIG_DEBUG_SG is not set ++# CONFIG_DEBUG_NOTIFIERS is not set ++# CONFIG_DEBUG_CREDENTIALS is not set ++ ++# ++# RCU Debugging ++# ++# CONFIG_SPARSE_RCU_POINTER is not set ++# CONFIG_TORTURE_TEST is not set ++# CONFIG_RCU_TORTURE_TEST is not set ++CONFIG_RCU_CPU_STALL_TIMEOUT=21 ++CONFIG_RCU_CPU_STALL_VERBOSE=y ++# CONFIG_RCU_CPU_STALL_INFO is not set ++# CONFIG_RCU_TRACE is not set ++# CONFIG_DEBUG_BLOCK_EXT_DEVT is not set ++# CONFIG_NOTIFIER_ERROR_INJECTION is not set ++# CONFIG_FAULT_INJECTION is not set ++CONFIG_HAVE_FUNCTION_TRACER=y ++CONFIG_HAVE_FUNCTION_GRAPH_TRACER=y ++CONFIG_HAVE_DYNAMIC_FTRACE=y ++CONFIG_HAVE_FTRACE_MCOUNT_RECORD=y ++CONFIG_HAVE_SYSCALL_TRACEPOINTS=y ++CONFIG_HAVE_C_RECORDMCOUNT=y ++CONFIG_TRACING_SUPPORT=y ++# CONFIG_FTRACE is not set ++ ++# ++# Runtime Testing ++# ++# CONFIG_LKDTM is not set ++# CONFIG_TEST_LIST_SORT is not set ++# CONFIG_BACKTRACE_SELF_TEST is not set ++# CONFIG_RBTREE_TEST is not set ++# CONFIG_INTERVAL_TREE_TEST is not set ++# CONFIG_PERCPU_TEST is not set ++# CONFIG_ATOMIC64_SELFTEST is not set ++# CONFIG_TEST_STRING_HELPERS is not set ++# CONFIG_TEST_KSTRTOX is not set ++# CONFIG_TEST_RHASHTABLE is not set ++# CONFIG_DMA_API_DEBUG is not set ++# CONFIG_TEST_LKM is not set ++# CONFIG_TEST_USER_COPY is not set ++# CONFIG_TEST_BPF is not set ++# CONFIG_TEST_FIRMWARE is not set ++# CONFIG_TEST_UDELAY is not set ++# CONFIG_SAMPLES is not set ++CONFIG_HAVE_ARCH_KGDB=y ++# CONFIG_KGDB is not set ++# CONFIG_STRICT_DEVMEM is not set ++CONFIG_PID_IN_CONTEXTIDR=y ++# CONFIG_ARM64_RANDOMIZE_TEXT_OFFSET is not set ++# CONFIG_DEBUG_SET_MODULE_RONX is not set ++ ++# ++# Security options ++# ++CONFIG_KEYS=y ++# CONFIG_PERSISTENT_KEYRINGS is not set ++# CONFIG_BIG_KEYS is not set ++# CONFIG_ENCRYPTED_KEYS is not set ++# CONFIG_KEYS_DEBUG_PROC_KEYS is not set ++# CONFIG_SECURITY_DMESG_RESTRICT is not set ++CONFIG_SECURITY=y ++# CONFIG_SECURITYFS is not set ++# CONFIG_SECURITY_NETWORK is not set ++# CONFIG_SECURITY_PATH is not set ++# CONFIG_SECURITY_SMACK is not set ++# CONFIG_SECURITY_TOMOYO is not set ++# CONFIG_SECURITY_APPARMOR is not set ++# CONFIG_SECURITY_YAMA is not set ++CONFIG_INTEGRITY=y ++# CONFIG_INTEGRITY_SIGNATURE is not set ++CONFIG_INTEGRITY_AUDIT=y ++# CONFIG_IMA is not set ++# CONFIG_EVM is not set ++CONFIG_DEFAULT_SECURITY_DAC=y ++CONFIG_DEFAULT_SECURITY="" ++CONFIG_CRYPTO=y ++ ++# ++# Crypto core or helper ++# ++CONFIG_CRYPTO_ALGAPI=y ++CONFIG_CRYPTO_ALGAPI2=y ++CONFIG_CRYPTO_AEAD=y ++CONFIG_CRYPTO_AEAD2=y ++CONFIG_CRYPTO_BLKCIPHER=y ++CONFIG_CRYPTO_BLKCIPHER2=y ++CONFIG_CRYPTO_HASH=y ++CONFIG_CRYPTO_HASH2=y ++CONFIG_CRYPTO_RNG=y ++CONFIG_CRYPTO_RNG2=y ++CONFIG_CRYPTO_PCOMP2=y ++CONFIG_CRYPTO_MANAGER=y ++CONFIG_CRYPTO_MANAGER2=y ++# CONFIG_CRYPTO_USER is not set ++CONFIG_CRYPTO_MANAGER_DISABLE_TESTS=y ++# CONFIG_CRYPTO_GF128MUL is not set ++# CONFIG_CRYPTO_NULL is not set ++# CONFIG_CRYPTO_PCRYPT is not set ++CONFIG_CRYPTO_WORKQUEUE=y ++CONFIG_CRYPTO_CRYPTD=y ++# CONFIG_CRYPTO_MCRYPTD is not set ++CONFIG_CRYPTO_AUTHENC=y ++# CONFIG_CRYPTO_TEST is not set ++CONFIG_CRYPTO_ABLK_HELPER=y ++ ++# ++# Authenticated Encryption with Associated Data ++# ++# CONFIG_CRYPTO_CCM is not set ++# CONFIG_CRYPTO_GCM is not set ++# CONFIG_CRYPTO_SEQIV is not set ++ ++# ++# Block modes ++# ++CONFIG_CRYPTO_CBC=y ++# CONFIG_CRYPTO_CTR is not set ++# CONFIG_CRYPTO_CTS is not set ++# CONFIG_CRYPTO_ECB is not set ++# CONFIG_CRYPTO_LRW is not set ++# CONFIG_CRYPTO_PCBC is not set ++# CONFIG_CRYPTO_XTS is not set ++ ++# ++# Hash modes ++# ++# CONFIG_CRYPTO_CMAC is not set ++CONFIG_CRYPTO_HMAC=y ++# CONFIG_CRYPTO_XCBC is not set ++# CONFIG_CRYPTO_VMAC is not set ++ ++# ++# Digest ++# ++CONFIG_CRYPTO_CRC32C=y ++# CONFIG_CRYPTO_CRC32 is not set ++# CONFIG_CRYPTO_CRCT10DIF is not set ++# CONFIG_CRYPTO_GHASH is not set ++# CONFIG_CRYPTO_MD4 is not set ++CONFIG_CRYPTO_MD5=y ++# CONFIG_CRYPTO_MICHAEL_MIC is not set ++# CONFIG_CRYPTO_RMD128 is not set ++# CONFIG_CRYPTO_RMD160 is not set ++# CONFIG_CRYPTO_RMD256 is not set ++# CONFIG_CRYPTO_RMD320 is not set ++CONFIG_CRYPTO_SHA1=y ++# CONFIG_CRYPTO_SHA256 is not set ++# CONFIG_CRYPTO_SHA512 is not set ++# CONFIG_CRYPTO_TGR192 is not set ++# CONFIG_CRYPTO_WP512 is not set ++ ++# ++# Ciphers ++# ++CONFIG_CRYPTO_AES=y ++# CONFIG_CRYPTO_ANUBIS is not set ++# CONFIG_CRYPTO_ARC4 is not set ++# CONFIG_CRYPTO_BLOWFISH is not set ++# CONFIG_CRYPTO_CAMELLIA is not set ++# CONFIG_CRYPTO_CAST5 is not set ++# CONFIG_CRYPTO_CAST6 is not set ++CONFIG_CRYPTO_DES=y ++# CONFIG_CRYPTO_FCRYPT is not set ++# CONFIG_CRYPTO_KHAZAD is not set ++# CONFIG_CRYPTO_SALSA20 is not set ++# CONFIG_CRYPTO_SEED is not set ++# CONFIG_CRYPTO_SERPENT is not set ++# CONFIG_CRYPTO_TEA is not set ++# CONFIG_CRYPTO_TWOFISH is not set ++ ++# ++# Compression ++# ++CONFIG_CRYPTO_DEFLATE=y ++# CONFIG_CRYPTO_ZLIB is not set ++# CONFIG_CRYPTO_LZO is not set ++# CONFIG_CRYPTO_LZ4 is not set ++# CONFIG_CRYPTO_LZ4HC is not set ++ ++# ++# Random Number Generation ++# ++CONFIG_CRYPTO_ANSI_CPRNG=y ++# CONFIG_CRYPTO_DRBG_MENU is not set ++# CONFIG_CRYPTO_USER_API_HASH is not set ++# CONFIG_CRYPTO_USER_API_SKCIPHER is not set ++CONFIG_CRYPTO_HW=y ++# CONFIG_CRYPTO_DEV_CCP is not set ++# CONFIG_ASYMMETRIC_KEY_TYPE is not set ++CONFIG_ARM64_CRYPTO=y ++CONFIG_CRYPTO_SHA1_ARM64_CE=y ++CONFIG_CRYPTO_SHA2_ARM64_CE=y ++CONFIG_CRYPTO_GHASH_ARM64_CE=y ++CONFIG_CRYPTO_AES_ARM64_CE=y ++CONFIG_CRYPTO_AES_ARM64_CE_CCM=y ++CONFIG_CRYPTO_AES_ARM64_CE_BLK=y ++CONFIG_CRYPTO_AES_ARM64_NEON_BLK=y ++# CONFIG_BINARY_PRINTF is not set ++ ++# ++# Library routines ++# ++CONFIG_BITREVERSE=y ++CONFIG_GENERIC_STRNCPY_FROM_USER=y ++CONFIG_GENERIC_STRNLEN_USER=y ++CONFIG_GENERIC_NET_UTILS=y ++CONFIG_GENERIC_PCI_IOMAP=y ++CONFIG_GENERIC_IOMAP=y ++CONFIG_GENERIC_IO=y ++CONFIG_ARCH_USE_CMPXCHG_LOCKREF=y ++# CONFIG_CRC_CCITT is not set ++CONFIG_CRC16=y ++# CONFIG_CRC_T10DIF is not set ++CONFIG_CRC_ITU_T=y ++CONFIG_CRC32=y ++# CONFIG_CRC32_SELFTEST is not set ++CONFIG_CRC32_SLICEBY8=y ++# CONFIG_CRC32_SLICEBY4 is not set ++# CONFIG_CRC32_SARWATE is not set ++# CONFIG_CRC32_BIT is not set ++CONFIG_CRC7=y ++# CONFIG_LIBCRC32C is not set ++# CONFIG_CRC8 is not set ++CONFIG_AUDIT_GENERIC=y ++CONFIG_AUDIT_ARCH_COMPAT_GENERIC=y ++CONFIG_AUDIT_COMPAT_GENERIC=y ++# CONFIG_RANDOM32_SELFTEST is not set ++CONFIG_ZLIB_INFLATE=y ++CONFIG_ZLIB_DEFLATE=y ++CONFIG_LZO_COMPRESS=y ++CONFIG_LZO_DECOMPRESS=y ++CONFIG_LZ4_DECOMPRESS=y ++CONFIG_XZ_DEC=y ++CONFIG_XZ_DEC_X86=y ++CONFIG_XZ_DEC_POWERPC=y ++CONFIG_XZ_DEC_IA64=y ++CONFIG_XZ_DEC_ARM=y ++CONFIG_XZ_DEC_ARMTHUMB=y ++CONFIG_XZ_DEC_SPARC=y ++CONFIG_XZ_DEC_BCJ=y ++# CONFIG_XZ_DEC_TEST is not set ++CONFIG_DECOMPRESS_GZIP=y ++CONFIG_DECOMPRESS_BZIP2=y ++CONFIG_DECOMPRESS_LZMA=y ++CONFIG_DECOMPRESS_XZ=y ++CONFIG_DECOMPRESS_LZO=y ++CONFIG_DECOMPRESS_LZ4=y ++CONFIG_GENERIC_ALLOCATOR=y ++CONFIG_ASSOCIATIVE_ARRAY=y ++CONFIG_HAS_IOMEM=y ++CONFIG_HAS_IOPORT_MAP=y ++CONFIG_HAS_DMA=y ++CONFIG_CPU_RMAP=y ++CONFIG_DQL=y ++CONFIG_GLOB=y ++# CONFIG_GLOB_SELFTEST is not set ++CONFIG_NLATTR=y ++CONFIG_ARCH_HAS_ATOMIC64_DEC_IF_POSITIVE=y ++CONFIG_AVERAGE=y ++# CONFIG_CORDIC is not set ++# CONFIG_DDR is not set ++CONFIG_LIBFDT=y ++CONFIG_OID_REGISTRY=y ++CONFIG_UCS2_STRING=y ++CONFIG_FONT_SUPPORT=y ++# CONFIG_FONTS is not set ++CONFIG_FONT_8x8=y ++CONFIG_FONT_8x16=y ++CONFIG_ARCH_HAS_SG_CHAIN=y +diff --git a/arch/arm64/include/asm/device.h b/arch/arm64/include/asm/device.h +index cf98b36..243ef25 100644 +--- a/arch/arm64/include/asm/device.h ++++ b/arch/arm64/include/asm/device.h +@@ -21,6 +21,7 @@ struct dev_archdata { + #ifdef CONFIG_IOMMU_API + void *iommu; /* private IOMMU data */ + #endif ++ bool dma_coherent; + }; + + struct pdev_archdata { +diff --git a/arch/arm64/include/asm/dma-mapping.h b/arch/arm64/include/asm/dma-mapping.h +index adeae3f..9ce3e68 100644 +--- a/arch/arm64/include/asm/dma-mapping.h ++++ b/arch/arm64/include/asm/dma-mapping.h +@@ -52,12 +52,20 @@ static inline void set_dma_ops(struct device *dev, struct dma_map_ops *ops) + dev->archdata.dma_ops = ops; + } + +-static inline int set_arch_dma_coherent_ops(struct device *dev) ++static inline void arch_setup_dma_ops(struct device *dev, u64 dma_base, u64 size, ++ struct iommu_ops *iommu, bool coherent) + { +- set_dma_ops(dev, &coherent_swiotlb_dma_ops); +- return 0; ++ dev->archdata.dma_coherent = coherent; ++ if (coherent) ++ set_dma_ops(dev, &coherent_swiotlb_dma_ops); ++} ++#define arch_setup_dma_ops arch_setup_dma_ops ++ ++/* do not use this function in a driver */ ++static inline bool is_device_dma_coherent(struct device *dev) ++{ ++ return dev->archdata.dma_coherent; + } +-#define set_arch_dma_coherent_ops set_arch_dma_coherent_ops + + #include + +diff --git a/arch/arm64/include/asm/io.h b/arch/arm64/include/asm/io.h +index 75825b6..f58e31a 100644 +--- a/arch/arm64/include/asm/io.h ++++ b/arch/arm64/include/asm/io.h +@@ -249,6 +249,7 @@ extern void __iomem *ioremap_cache(phys_addr_t phys_addr, size_t size); + #define ioremap(addr, size) __ioremap((addr), (size), __pgprot(PROT_DEVICE_nGnRE)) + #define ioremap_nocache(addr, size) __ioremap((addr), (size), __pgprot(PROT_DEVICE_nGnRE)) + #define ioremap_wc(addr, size) __ioremap((addr), (size), __pgprot(PROT_NORMAL_NC)) ++#define ioremap_cache_ns(addr, size) __ioremap((addr), (size), __pgprot(PROT_NORMAL_NS)) + #define iounmap __iounmap + + #define ARCH_HAS_IOREMAP_WC +diff --git a/arch/arm64/include/asm/mmu_context.h b/arch/arm64/include/asm/mmu_context.h +index 101a42b..8ec41e5 100644 +--- a/arch/arm64/include/asm/mmu_context.h ++++ b/arch/arm64/include/asm/mmu_context.h +@@ -64,6 +64,49 @@ static inline void cpu_set_reserved_ttbr0(void) + : "r" (ttbr)); + } + ++/* ++ * TCR.T0SZ value to use when the ID map is active. Usually equals ++ * TCR_T0SZ(VA_BITS), unless system RAM is positioned very high in ++ * physical memory, in which case it will be smaller. ++ */ ++extern u64 idmap_t0sz; ++ ++static inline bool __cpu_uses_extended_idmap(void) ++{ ++ return (!IS_ENABLED(CONFIG_ARM64_VA_BITS_48) && ++ unlikely(idmap_t0sz != TCR_T0SZ(VA_BITS))); ++} ++ ++static inline void __cpu_set_tcr_t0sz(u64 t0sz) ++{ ++ unsigned long tcr; ++ ++ if (__cpu_uses_extended_idmap()) ++ asm volatile ( ++ " mrs %0, tcr_el1 ;" ++ " bfi %0, %1, %2, %3 ;" ++ " msr tcr_el1, %0 ;" ++ " isb" ++ : "=&r" (tcr) ++ : "r"(t0sz), "I"(TCR_T0SZ_OFFSET), "I"(TCR_TxSZ_WIDTH)); ++} ++ ++/* ++ * Set TCR.T0SZ to the value appropriate for activating the identity map. ++ */ ++static inline void cpu_set_idmap_tcr_t0sz(void) ++{ ++ __cpu_set_tcr_t0sz(idmap_t0sz); ++} ++ ++/* ++ * Set TCR.T0SZ to its default value (based on VA_BITS) ++ */ ++static inline void cpu_set_default_tcr_t0sz(void) ++{ ++ __cpu_set_tcr_t0sz(TCR_T0SZ(VA_BITS)); ++} ++ + static inline void switch_new_context(struct mm_struct *mm) + { + unsigned long flags; +diff --git a/arch/arm64/include/asm/page.h b/arch/arm64/include/asm/page.h +index 22b1623..3d02b18 100644 +--- a/arch/arm64/include/asm/page.h ++++ b/arch/arm64/include/asm/page.h +@@ -33,7 +33,9 @@ + * image. Both require pgd, pud (4 levels only) and pmd tables to (section) + * map the kernel. With the 64K page configuration, swapper and idmap need to + * map to pte level. The swapper also maps the FDT (see __create_page_tables +- * for more information). ++ * for more information). Note that the number of ID map translation levels ++ * could be increased on the fly if system RAM is out of reach for the default ++ * VA range, so 3 pages are reserved in all cases. + */ + #ifdef CONFIG_ARM64_64K_PAGES + #define SWAPPER_PGTABLE_LEVELS (CONFIG_ARM64_PGTABLE_LEVELS) +@@ -42,7 +44,7 @@ + #endif + + #define SWAPPER_DIR_SIZE (SWAPPER_PGTABLE_LEVELS * PAGE_SIZE) +-#define IDMAP_DIR_SIZE (SWAPPER_DIR_SIZE) ++#define IDMAP_DIR_SIZE (3 * PAGE_SIZE) + + #ifndef __ASSEMBLY__ + +diff --git a/arch/arm64/include/asm/pgtable-hwdef.h b/arch/arm64/include/asm/pgtable-hwdef.h +index 88174e0..500b74e 100644 +--- a/arch/arm64/include/asm/pgtable-hwdef.h ++++ b/arch/arm64/include/asm/pgtable-hwdef.h +@@ -142,7 +142,12 @@ + /* + * TCR flags. + */ +-#define TCR_TxSZ(x) (((UL(64) - (x)) << 16) | ((UL(64) - (x)) << 0)) ++#define TCR_T0SZ_OFFSET 0 ++#define TCR_T1SZ_OFFSET 16 ++#define TCR_T0SZ(x) ((UL(64) - (x)) << TCR_T0SZ_OFFSET) ++#define TCR_T1SZ(x) ((UL(64) - (x)) << TCR_T1SZ_OFFSET) ++#define TCR_TxSZ(x) (TCR_T0SZ(x) | TCR_T1SZ(x)) ++#define TCR_TxSZ_WIDTH 6 + #define TCR_IRGN_NC ((UL(0) << 8) | (UL(0) << 24)) + #define TCR_IRGN_WBWA ((UL(1) << 8) | (UL(1) << 24)) + #define TCR_IRGN_WT ((UL(2) << 8) | (UL(2) << 24)) +diff --git a/arch/arm64/include/asm/pgtable.h b/arch/arm64/include/asm/pgtable.h +index 41a43bf..9b417b8 100644 +--- a/arch/arm64/include/asm/pgtable.h ++++ b/arch/arm64/include/asm/pgtable.h +@@ -65,6 +65,7 @@ extern void __pgd_error(const char *file, int line, unsigned long val); + #define PROT_DEVICE_nGnRE (PROT_DEFAULT | PTE_PXN | PTE_UXN | PTE_ATTRINDX(MT_DEVICE_nGnRE)) + #define PROT_NORMAL_NC (PROT_DEFAULT | PTE_PXN | PTE_UXN | PTE_ATTRINDX(MT_NORMAL_NC)) + #define PROT_NORMAL (PROT_DEFAULT | PTE_PXN | PTE_UXN | PTE_ATTRINDX(MT_NORMAL)) ++#define PROT_NORMAL_NS (PTE_TYPE_PAGE | PTE_AF | PTE_PXN | PTE_UXN | PTE_ATTRINDX(MT_NORMAL)) + + #define PROT_SECT_DEVICE_nGnRE (PROT_SECT_DEFAULT | PMD_SECT_PXN | PMD_SECT_UXN | PMD_ATTRINDX(MT_DEVICE_nGnRE)) + #define PROT_SECT_NORMAL (PROT_SECT_DEFAULT | PMD_SECT_PXN | PMD_SECT_UXN | PMD_ATTRINDX(MT_NORMAL)) +@@ -321,6 +322,13 @@ static inline int has_transparent_hugepage(void) + #define pgprot_device(prot) \ + __pgprot_modify(prot, PTE_ATTRINDX_MASK, PTE_ATTRINDX(MT_DEVICE_nGnRE) | PTE_PXN | PTE_UXN) + #define __HAVE_PHYS_MEM_ACCESS_PROT ++#define pgprot_cached_ns(prot) \ ++ __pgprot(pgprot_val(pgprot_cached(prot)) & ~PTE_SHARED) ++#define pgprot_cached(prot) \ ++ __pgprot_modify(prot, PTE_ATTRINDX_MASK, PTE_ATTRINDX(MT_NORMAL) | \ ++ PTE_PXN | PTE_UXN) ++ ++ + struct file; + extern pgprot_t phys_mem_access_prot(struct file *file, unsigned long pfn, + unsigned long size, pgprot_t vma_prot); +diff --git a/arch/arm64/kernel/head.S b/arch/arm64/kernel/head.S +index 2877dd8..ca02239 100644 +--- a/arch/arm64/kernel/head.S ++++ b/arch/arm64/kernel/head.S +@@ -592,6 +592,43 @@ __create_page_tables: + mov x0, x25 // idmap_pg_dir + ldr x3, =KERNEL_START + add x3, x3, x28 // __pa(KERNEL_START) ++ ++#ifndef CONFIG_ARM64_VA_BITS_48 ++#define EXTRA_SHIFT (PGDIR_SHIFT + PAGE_SHIFT - 3) ++#define EXTRA_PTRS (1 << (48 - EXTRA_SHIFT)) ++ ++ /* ++ * If VA_BITS < 48, it may be too small to allow for an ID mapping to be ++ * created that covers system RAM if that is located sufficiently high ++ * in the physical address space. So for the ID map, use an extended ++ * virtual range in that case, by configuring an additional translation ++ * level. ++ * First, we have to verify our assumption that the current value of ++ * VA_BITS was chosen such that all translation levels are fully ++ * utilised, and that lowering T0SZ will always result in an additional ++ * translation level to be configured. ++ */ ++#if VA_BITS != EXTRA_SHIFT ++#error "Mismatch between VA_BITS and page size/number of translation levels" ++#endif ++ ++ /* ++ * Calculate the maximum allowed value for TCR_EL1.T0SZ so that the ++ * entire kernel image can be ID mapped. As T0SZ == (64 - #bits used), ++ * this number conveniently equals the number of leading zeroes in ++ * the physical address of KERNEL_END. ++ */ ++ adrp x5, KERNEL_END ++ clz x5, x5 ++ cmp x5, TCR_T0SZ(VA_BITS) // default T0SZ small enough? ++ b.ge 1f // .. then skip additional level ++ ++ str_l x5, idmap_t0sz, x6 ++ ++ create_table_entry x0, x3, EXTRA_SHIFT, EXTRA_PTRS, x5, x6 ++1: ++#endif ++ + create_pgd_entry x0, x3, x5, x6 + ldr x6, =KERNEL_END + mov x5, x3 // __pa(KERNEL_START) +diff --git a/arch/arm64/kernel/smp.c b/arch/arm64/kernel/smp.c +index 0ef8789..5657692 100644 +--- a/arch/arm64/kernel/smp.c ++++ b/arch/arm64/kernel/smp.c +@@ -152,6 +152,7 @@ asmlinkage void secondary_start_kernel(void) + */ + cpu_set_reserved_ttbr0(); + flush_tlb_all(); ++ cpu_set_default_tcr_t0sz(); + + preempt_disable(); + trace_hardirqs_off(); +diff --git a/arch/arm64/mm/mmu.c b/arch/arm64/mm/mmu.c +index f4f8b50..53bbff9 100644 +--- a/arch/arm64/mm/mmu.c ++++ b/arch/arm64/mm/mmu.c +@@ -37,6 +37,8 @@ + + #include "mm.h" + ++u64 idmap_t0sz = TCR_T0SZ(VA_BITS); ++ + /* + * Empty_zero_page is a special page that is used for zero-initialized data + * and COW. +@@ -369,6 +371,7 @@ void __init paging_init(void) + */ + cpu_set_reserved_ttbr0(); + flush_tlb_all(); ++ cpu_set_default_tcr_t0sz(); + } + + /* +@@ -376,8 +379,10 @@ void __init paging_init(void) + */ + void setup_mm_for_reboot(void) + { +- cpu_switch_mm(idmap_pg_dir, &init_mm); ++ cpu_set_reserved_ttbr0(); + flush_tlb_all(); ++ cpu_set_idmap_tcr_t0sz(); ++ cpu_switch_mm(idmap_pg_dir, &init_mm); + } + + /* +diff --git a/arch/arm64/mm/proc-macros.S b/arch/arm64/mm/proc-macros.S +index 005d29e..4c4d93c 100644 +--- a/arch/arm64/mm/proc-macros.S ++++ b/arch/arm64/mm/proc-macros.S +@@ -52,3 +52,13 @@ + mov \reg, #4 // bytes per word + lsl \reg, \reg, \tmp // actual cache line size + .endm ++ ++/* ++ * tcr_set_idmap_t0sz - update TCR.T0SZ so that we can load the ID map ++ */ ++ .macro tcr_set_idmap_t0sz, valreg, tmpreg ++#ifndef CONFIG_ARM64_VA_BITS_48 ++ ldr_l \tmpreg, idmap_t0sz ++ bfi \valreg, \tmpreg, #TCR_T0SZ_OFFSET, #TCR_TxSZ_WIDTH ++#endif ++ .endm +diff --git a/arch/arm64/mm/proc.S b/arch/arm64/mm/proc.S +index 4e778b1..cbea872 100644 +--- a/arch/arm64/mm/proc.S ++++ b/arch/arm64/mm/proc.S +@@ -156,6 +156,7 @@ ENTRY(cpu_do_resume) + msr cpacr_el1, x6 + msr ttbr0_el1, x1 + msr ttbr1_el1, x7 ++ tcr_set_idmap_t0sz x8, x7 + msr tcr_el1, x8 + msr vbar_el1, x9 + msr mdscr_el1, x10 +@@ -233,6 +234,8 @@ ENTRY(__cpu_setup) + */ + ldr x10, =TCR_TxSZ(VA_BITS) | TCR_CACHE_FLAGS | TCR_SMP_FLAGS | \ + TCR_TG_FLAGS | TCR_ASID16 | TCR_TBI0 ++ tcr_set_idmap_t0sz x10, x9 ++ + /* + * Read the PARange bits from ID_AA64MMFR0_EL1 and set the IPS bits in + * TCR_EL1. +diff --git a/arch/ia64/kernel/msi_ia64.c b/arch/ia64/kernel/msi_ia64.c +index 8c3730c..8ae36ea 100644 +--- a/arch/ia64/kernel/msi_ia64.c ++++ b/arch/ia64/kernel/msi_ia64.c +@@ -35,7 +35,7 @@ static int ia64_set_msi_irq_affinity(struct irq_data *idata, + data |= MSI_DATA_VECTOR(irq_to_vector(irq)); + msg.data = data; + +- write_msi_msg(irq, &msg); ++ pci_write_msi_msg(irq, &msg); + cpumask_copy(idata->affinity, cpumask_of(cpu)); + + return 0; +@@ -71,7 +71,7 @@ int ia64_setup_msi_irq(struct pci_dev *pdev, struct msi_desc *desc) + MSI_DATA_DELIVERY_FIXED | + MSI_DATA_VECTOR(vector); + +- write_msi_msg(irq, &msg); ++ pci_write_msi_msg(irq, &msg); + irq_set_chip_and_handler(irq, &ia64_msi_chip, handle_edge_irq); + + return 0; +@@ -102,8 +102,8 @@ static int ia64_msi_retrigger_irq(struct irq_data *data) + */ + static struct irq_chip ia64_msi_chip = { + .name = "PCI-MSI", +- .irq_mask = mask_msi_irq, +- .irq_unmask = unmask_msi_irq, ++ .irq_mask = pci_msi_mask_irq, ++ .irq_unmask = pci_msi_unmask_irq, + .irq_ack = ia64_ack_msi_irq, + #ifdef CONFIG_SMP + .irq_set_affinity = ia64_set_msi_irq_affinity, +diff --git a/arch/ia64/sn/kernel/msi_sn.c b/arch/ia64/sn/kernel/msi_sn.c +index 446e779..a0eb27b 100644 +--- a/arch/ia64/sn/kernel/msi_sn.c ++++ b/arch/ia64/sn/kernel/msi_sn.c +@@ -145,7 +145,7 @@ int sn_setup_msi_irq(struct pci_dev *pdev, struct msi_desc *entry) + msg.data = 0x100 + irq; + + irq_set_msi_desc(irq, entry); +- write_msi_msg(irq, &msg); ++ pci_write_msi_msg(irq, &msg); + irq_set_chip_and_handler(irq, &sn_msi_chip, handle_edge_irq); + + return 0; +@@ -205,7 +205,7 @@ static int sn_set_msi_irq_affinity(struct irq_data *data, + msg.address_hi = (u32)(bus_addr >> 32); + msg.address_lo = (u32)(bus_addr & 0x00000000ffffffff); + +- write_msi_msg(irq, &msg); ++ pci_write_msi_msg(irq, &msg); + cpumask_copy(data->affinity, cpu_mask); + + return 0; +@@ -228,8 +228,8 @@ static int sn_msi_retrigger_irq(struct irq_data *data) + + static struct irq_chip sn_msi_chip = { + .name = "PCI-MSI", +- .irq_mask = mask_msi_irq, +- .irq_unmask = unmask_msi_irq, ++ .irq_mask = pci_msi_mask_irq, ++ .irq_unmask = pci_msi_unmask_irq, + .irq_ack = sn_ack_msi_irq, + #ifdef CONFIG_SMP + .irq_set_affinity = sn_set_msi_irq_affinity, +diff --git a/arch/mips/pci/msi-octeon.c b/arch/mips/pci/msi-octeon.c +index 63bbe07..cffaaf4 100644 +--- a/arch/mips/pci/msi-octeon.c ++++ b/arch/mips/pci/msi-octeon.c +@@ -178,7 +178,7 @@ msi_irq_allocated: + pci_write_config_word(dev, dev->msi_cap + PCI_MSI_FLAGS, control); + + irq_set_msi_desc(irq, desc); +- write_msi_msg(irq, &msg); ++ pci_write_msi_msg(irq, &msg); + return 0; + } + +diff --git a/arch/mips/pci/msi-xlp.c b/arch/mips/pci/msi-xlp.c +index f7ac3ed..6a40f24 100644 +--- a/arch/mips/pci/msi-xlp.c ++++ b/arch/mips/pci/msi-xlp.c +@@ -217,7 +217,7 @@ static void xlp_msix_mask_ack(struct irq_data *d) + + msixvec = nlm_irq_msixvec(d->irq); + link = nlm_irq_msixlink(msixvec); +- mask_msi_irq(d); ++ pci_msi_mask_irq(d); + md = irq_data_get_irq_handler_data(d); + + /* Ack MSI on bridge */ +@@ -239,10 +239,10 @@ static void xlp_msix_mask_ack(struct irq_data *d) + + static struct irq_chip xlp_msix_chip = { + .name = "XLP-MSIX", +- .irq_enable = unmask_msi_irq, +- .irq_disable = mask_msi_irq, ++ .irq_enable = pci_msi_unmask_irq, ++ .irq_disable = pci_msi_mask_irq, + .irq_mask_ack = xlp_msix_mask_ack, +- .irq_unmask = unmask_msi_irq, ++ .irq_unmask = pci_msi_unmask_irq, + }; + + void arch_teardown_msi_irq(unsigned int irq) +@@ -345,7 +345,7 @@ static int xlp_setup_msi(uint64_t lnkbase, int node, int link, + if (ret < 0) + return ret; + +- write_msi_msg(xirq, &msg); ++ pci_write_msi_msg(xirq, &msg); + return 0; + } + +@@ -446,7 +446,7 @@ static int xlp_setup_msix(uint64_t lnkbase, int node, int link, + if (ret < 0) + return ret; + +- write_msi_msg(xirq, &msg); ++ pci_write_msi_msg(xirq, &msg); + return 0; + } + +diff --git a/arch/mips/pci/pci-xlr.c b/arch/mips/pci/pci-xlr.c +index 0dde803..26d2dab 100644 +--- a/arch/mips/pci/pci-xlr.c ++++ b/arch/mips/pci/pci-xlr.c +@@ -260,7 +260,7 @@ int arch_setup_msi_irq(struct pci_dev *dev, struct msi_desc *desc) + if (ret < 0) + return ret; + +- write_msi_msg(irq, &msg); ++ pci_write_msi_msg(irq, &msg); + return 0; + } + #endif +diff --git a/arch/powerpc/include/asm/mpc85xx.h b/arch/powerpc/include/asm/mpc85xx.h +deleted file mode 100644 +index 3bef74a..0000000 +--- a/arch/powerpc/include/asm/mpc85xx.h ++++ /dev/null +@@ -1,94 +0,0 @@ +-/* +- * MPC85xx cpu type detection +- * +- * Copyright 2011-2012 Freescale Semiconductor, Inc. +- * +- * This is free software; you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation; either version 2 of the License, or +- * (at your option) any later version. +- */ +- +-#ifndef __ASM_PPC_MPC85XX_H +-#define __ASM_PPC_MPC85XX_H +- +-#define SVR_REV(svr) ((svr) & 0xFF) /* SOC design resision */ +-#define SVR_MAJ(svr) (((svr) >> 4) & 0xF) /* Major revision field*/ +-#define SVR_MIN(svr) (((svr) >> 0) & 0xF) /* Minor revision field*/ +- +-/* Some parts define SVR[0:23] as the SOC version */ +-#define SVR_SOC_VER(svr) (((svr) >> 8) & 0xFFF7FF) /* SOC Version fields */ +- +-#define SVR_8533 0x803400 +-#define SVR_8535 0x803701 +-#define SVR_8536 0x803700 +-#define SVR_8540 0x803000 +-#define SVR_8541 0x807200 +-#define SVR_8543 0x803200 +-#define SVR_8544 0x803401 +-#define SVR_8545 0x803102 +-#define SVR_8547 0x803101 +-#define SVR_8548 0x803100 +-#define SVR_8555 0x807100 +-#define SVR_8560 0x807000 +-#define SVR_8567 0x807501 +-#define SVR_8568 0x807500 +-#define SVR_8569 0x808000 +-#define SVR_8572 0x80E000 +-#define SVR_P1010 0x80F100 +-#define SVR_P1011 0x80E500 +-#define SVR_P1012 0x80E501 +-#define SVR_P1013 0x80E700 +-#define SVR_P1014 0x80F101 +-#define SVR_P1017 0x80F700 +-#define SVR_P1020 0x80E400 +-#define SVR_P1021 0x80E401 +-#define SVR_P1022 0x80E600 +-#define SVR_P1023 0x80F600 +-#define SVR_P1024 0x80E402 +-#define SVR_P1025 0x80E403 +-#define SVR_P2010 0x80E300 +-#define SVR_P2020 0x80E200 +-#define SVR_P2040 0x821000 +-#define SVR_P2041 0x821001 +-#define SVR_P3041 0x821103 +-#define SVR_P4040 0x820100 +-#define SVR_P4080 0x820000 +-#define SVR_P5010 0x822100 +-#define SVR_P5020 0x822000 +-#define SVR_P5021 0X820500 +-#define SVR_P5040 0x820400 +-#define SVR_T4240 0x824000 +-#define SVR_T4120 0x824001 +-#define SVR_T4160 0x824100 +-#define SVR_C291 0x850000 +-#define SVR_C292 0x850020 +-#define SVR_C293 0x850030 +-#define SVR_B4860 0X868000 +-#define SVR_G4860 0x868001 +-#define SVR_G4060 0x868003 +-#define SVR_B4440 0x868100 +-#define SVR_G4440 0x868101 +-#define SVR_B4420 0x868102 +-#define SVR_B4220 0x868103 +-#define SVR_T1040 0x852000 +-#define SVR_T1041 0x852001 +-#define SVR_T1042 0x852002 +-#define SVR_T1020 0x852100 +-#define SVR_T1021 0x852101 +-#define SVR_T1022 0x852102 +-#define SVR_T2080 0x853000 +-#define SVR_T2081 0x853100 +- +-#define SVR_8610 0x80A000 +-#define SVR_8641 0x809000 +-#define SVR_8641D 0x809001 +- +-#define SVR_9130 0x860001 +-#define SVR_9131 0x860000 +-#define SVR_9132 0x861000 +-#define SVR_9232 0x861400 +- +-#define SVR_Unknown 0xFFFFFF +- +-#endif +diff --git a/arch/powerpc/platforms/512x/mpc5121_ads_cpld.c b/arch/powerpc/platforms/512x/mpc5121_ads_cpld.c +index ca3a062..11090ab 100644 +--- a/arch/powerpc/platforms/512x/mpc5121_ads_cpld.c ++++ b/arch/powerpc/platforms/512x/mpc5121_ads_cpld.c +@@ -123,7 +123,8 @@ cpld_pic_cascade(unsigned int irq, struct irq_desc *desc) + } + + static int +-cpld_pic_host_match(struct irq_domain *h, struct device_node *node) ++cpld_pic_host_match(struct irq_domain *h, struct device_node *node, ++ enum irq_domain_bus_token bus_token) + { + return cpld_pic_node == node; + } +diff --git a/arch/powerpc/platforms/85xx/mpc85xx_mds.c b/arch/powerpc/platforms/85xx/mpc85xx_mds.c +index a392e94..f0be439 100644 +--- a/arch/powerpc/platforms/85xx/mpc85xx_mds.c ++++ b/arch/powerpc/platforms/85xx/mpc85xx_mds.c +@@ -34,6 +34,7 @@ + #include + #include + #include ++#include + + #include + #include +@@ -51,7 +52,6 @@ + #include + #include + #include +-#include + #include "smp.h" + + #include "mpc85xx.h" +diff --git a/arch/powerpc/platforms/85xx/mpc85xx_rdb.c b/arch/powerpc/platforms/85xx/mpc85xx_rdb.c +index e358bed..50dcc00 100644 +--- a/arch/powerpc/platforms/85xx/mpc85xx_rdb.c ++++ b/arch/powerpc/platforms/85xx/mpc85xx_rdb.c +@@ -17,6 +17,7 @@ + #include + #include + #include ++#include + + #include + #include +@@ -27,7 +28,6 @@ + #include + #include + #include +-#include + + #include + #include +diff --git a/arch/powerpc/platforms/85xx/p1022_ds.c b/arch/powerpc/platforms/85xx/p1022_ds.c +index 6ac986d..371df82 100644 +--- a/arch/powerpc/platforms/85xx/p1022_ds.c ++++ b/arch/powerpc/platforms/85xx/p1022_ds.c +@@ -16,6 +16,7 @@ + * kind, whether express or implied. + */ + ++#include + #include + #include + #include +@@ -25,7 +26,6 @@ + #include + #include + #include +-#include + #include + #include "smp.h" + +diff --git a/arch/powerpc/platforms/85xx/p1022_rdk.c b/arch/powerpc/platforms/85xx/p1022_rdk.c +index 7a180f0..4f8fc5f 100644 +--- a/arch/powerpc/platforms/85xx/p1022_rdk.c ++++ b/arch/powerpc/platforms/85xx/p1022_rdk.c +@@ -12,6 +12,7 @@ + * kind, whether express or implied. + */ + ++#include + #include + #include + #include +@@ -21,7 +22,6 @@ + #include + #include + #include +-#include + #include "smp.h" + + #include "mpc85xx.h" +diff --git a/arch/powerpc/platforms/85xx/smp.c b/arch/powerpc/platforms/85xx/smp.c +index d7c1e69..3956455 100644 +--- a/arch/powerpc/platforms/85xx/smp.c ++++ b/arch/powerpc/platforms/85xx/smp.c +@@ -19,6 +19,7 @@ + #include + #include + #include ++#include + + #include + #include +@@ -26,7 +27,6 @@ + #include + #include + #include +-#include + #include + #include + +diff --git a/arch/powerpc/platforms/85xx/twr_p102x.c b/arch/powerpc/platforms/85xx/twr_p102x.c +index 1eadb6d..2799120 100644 +--- a/arch/powerpc/platforms/85xx/twr_p102x.c ++++ b/arch/powerpc/platforms/85xx/twr_p102x.c +@@ -15,6 +15,7 @@ + #include + #include + #include ++#include + #include + #include + +@@ -23,7 +24,6 @@ + #include + #include + #include +-#include + + #include + #include +diff --git a/arch/powerpc/platforms/86xx/mpc8610_hpcd.c b/arch/powerpc/platforms/86xx/mpc8610_hpcd.c +index 55413a5..437a9c3 100644 +--- a/arch/powerpc/platforms/86xx/mpc8610_hpcd.c ++++ b/arch/powerpc/platforms/86xx/mpc8610_hpcd.c +@@ -24,6 +24,7 @@ + #include + #include + #include ++#include + + #include + #include +@@ -38,7 +39,6 @@ + #include + #include + #include +-#include + + #include "mpc86xx.h" + +diff --git a/arch/powerpc/platforms/cell/axon_msi.c b/arch/powerpc/platforms/cell/axon_msi.c +index 862b327..0883994 100644 +--- a/arch/powerpc/platforms/cell/axon_msi.c ++++ b/arch/powerpc/platforms/cell/axon_msi.c +@@ -279,7 +279,7 @@ static int axon_msi_setup_msi_irqs(struct pci_dev *dev, int nvec, int type) + + irq_set_msi_desc(virq, entry); + msg.data = virq; +- write_msi_msg(virq, &msg); ++ pci_write_msi_msg(virq, &msg); + } + + return 0; +@@ -301,9 +301,9 @@ static void axon_msi_teardown_msi_irqs(struct pci_dev *dev) + } + + static struct irq_chip msic_irq_chip = { +- .irq_mask = mask_msi_irq, +- .irq_unmask = unmask_msi_irq, +- .irq_shutdown = mask_msi_irq, ++ .irq_mask = pci_msi_mask_irq, ++ .irq_unmask = pci_msi_unmask_irq, ++ .irq_shutdown = pci_msi_mask_irq, + .name = "AXON-MSI", + }; + +diff --git a/arch/powerpc/platforms/cell/interrupt.c b/arch/powerpc/platforms/cell/interrupt.c +index 28e558d..109d236 100644 +--- a/arch/powerpc/platforms/cell/interrupt.c ++++ b/arch/powerpc/platforms/cell/interrupt.c +@@ -222,7 +222,8 @@ void iic_request_IPIs(void) + #endif /* CONFIG_SMP */ + + +-static int iic_host_match(struct irq_domain *h, struct device_node *node) ++static int iic_host_match(struct irq_domain *h, struct device_node *node, ++ enum irq_domain_bus_token bus_token) + { + return of_device_is_compatible(node, + "IBM,CBEA-Internal-Interrupt-Controller"); +diff --git a/arch/powerpc/platforms/embedded6xx/flipper-pic.c b/arch/powerpc/platforms/embedded6xx/flipper-pic.c +index 4cde8e7..b7866e0 100644 +--- a/arch/powerpc/platforms/embedded6xx/flipper-pic.c ++++ b/arch/powerpc/platforms/embedded6xx/flipper-pic.c +@@ -108,7 +108,8 @@ static int flipper_pic_map(struct irq_domain *h, unsigned int virq, + return 0; + } + +-static int flipper_pic_match(struct irq_domain *h, struct device_node *np) ++static int flipper_pic_match(struct irq_domain *h, struct device_node *np, ++ enum irq_domain_bus_token bus_token) + { + return 1; + } +diff --git a/arch/powerpc/platforms/powermac/pic.c b/arch/powerpc/platforms/powermac/pic.c +index 4c24bf6..246cab4 100644 +--- a/arch/powerpc/platforms/powermac/pic.c ++++ b/arch/powerpc/platforms/powermac/pic.c +@@ -268,7 +268,8 @@ static struct irqaction gatwick_cascade_action = { + .name = "cascade", + }; + +-static int pmac_pic_host_match(struct irq_domain *h, struct device_node *node) ++static int pmac_pic_host_match(struct irq_domain *h, struct device_node *node, ++ enum irq_domain_bus_token bus_token) + { + /* We match all, we don't always have a node anyway */ + return 1; +diff --git a/arch/powerpc/platforms/powernv/pci.c b/arch/powerpc/platforms/powernv/pci.c +index 9ff55d5..019991d 100644 +--- a/arch/powerpc/platforms/powernv/pci.c ++++ b/arch/powerpc/platforms/powernv/pci.c +@@ -90,7 +90,7 @@ static int pnv_setup_msi_irqs(struct pci_dev *pdev, int nvec, int type) + return rc; + } + irq_set_msi_desc(virq, entry); +- write_msi_msg(virq, &msg); ++ pci_write_msi_msg(virq, &msg); + } + return 0; + } +diff --git a/arch/powerpc/platforms/ps3/interrupt.c b/arch/powerpc/platforms/ps3/interrupt.c +index 5f3b232..df0c086 100644 +--- a/arch/powerpc/platforms/ps3/interrupt.c ++++ b/arch/powerpc/platforms/ps3/interrupt.c +@@ -678,7 +678,8 @@ static int ps3_host_map(struct irq_domain *h, unsigned int virq, + return 0; + } + +-static int ps3_host_match(struct irq_domain *h, struct device_node *np) ++static int ps3_host_match(struct irq_domain *h, struct device_node *np, ++ enum irq_domain_bus_token bus_token) + { + /* Match all */ + return 1; +diff --git a/arch/powerpc/platforms/pseries/msi.c b/arch/powerpc/platforms/pseries/msi.c +index 8b909e9..691a154 100644 +--- a/arch/powerpc/platforms/pseries/msi.c ++++ b/arch/powerpc/platforms/pseries/msi.c +@@ -476,7 +476,7 @@ again: + irq_set_msi_desc(virq, entry); + + /* Read config space back so we can restore after reset */ +- __read_msi_msg(entry, &msg); ++ __pci_read_msi_msg(entry, &msg); + entry->msg = msg; + } + +diff --git a/arch/powerpc/sysdev/ehv_pic.c b/arch/powerpc/sysdev/ehv_pic.c +index 2d20f10..eca0b00 100644 +--- a/arch/powerpc/sysdev/ehv_pic.c ++++ b/arch/powerpc/sysdev/ehv_pic.c +@@ -177,7 +177,8 @@ unsigned int ehv_pic_get_irq(void) + return irq_linear_revmap(global_ehv_pic->irqhost, irq); + } + +-static int ehv_pic_host_match(struct irq_domain *h, struct device_node *node) ++static int ehv_pic_host_match(struct irq_domain *h, struct device_node *node, ++ enum irq_domain_bus_token bus_token) + { + /* Exact match, unless ehv_pic node is NULL */ + return h->of_node == NULL || h->of_node == node; +diff --git a/arch/powerpc/sysdev/fsl_msi.c b/arch/powerpc/sysdev/fsl_msi.c +index ea6b3a1..f13282c 100644 +--- a/arch/powerpc/sysdev/fsl_msi.c ++++ b/arch/powerpc/sysdev/fsl_msi.c +@@ -82,8 +82,8 @@ static void fsl_msi_print_chip(struct irq_data *irqd, struct seq_file *p) + + + static struct irq_chip fsl_msi_chip = { +- .irq_mask = mask_msi_irq, +- .irq_unmask = unmask_msi_irq, ++ .irq_mask = pci_msi_mask_irq, ++ .irq_unmask = pci_msi_unmask_irq, + .irq_ack = fsl_msi_end_irq, + .irq_print_chip = fsl_msi_print_chip, + }; +@@ -243,7 +243,7 @@ static int fsl_setup_msi_irqs(struct pci_dev *pdev, int nvec, int type) + irq_set_msi_desc(virq, entry); + + fsl_compose_msi_msg(pdev, hwirq, &msg, msi_data); +- write_msi_msg(virq, &msg); ++ pci_write_msi_msg(virq, &msg); + } + return 0; + +diff --git a/arch/powerpc/sysdev/i8259.c b/arch/powerpc/sysdev/i8259.c +index 45598da..8c3756c 100644 +--- a/arch/powerpc/sysdev/i8259.c ++++ b/arch/powerpc/sysdev/i8259.c +@@ -162,7 +162,8 @@ static struct resource pic_edgectrl_iores = { + .flags = IORESOURCE_BUSY, + }; + +-static int i8259_host_match(struct irq_domain *h, struct device_node *node) ++static int i8259_host_match(struct irq_domain *h, struct device_node *node, ++ enum irq_domain_bus_token bus_token) + { + return h->of_node == NULL || h->of_node == node; + } +diff --git a/arch/powerpc/sysdev/ipic.c b/arch/powerpc/sysdev/ipic.c +index b50f978..1b9b00f 100644 +--- a/arch/powerpc/sysdev/ipic.c ++++ b/arch/powerpc/sysdev/ipic.c +@@ -672,7 +672,8 @@ static struct irq_chip ipic_edge_irq_chip = { + .irq_set_type = ipic_set_irq_type, + }; + +-static int ipic_host_match(struct irq_domain *h, struct device_node *node) ++static int ipic_host_match(struct irq_domain *h, struct device_node *node, ++ enum irq_domain_bus_token bus_token) + { + /* Exact match, unless ipic node is NULL */ + return h->of_node == NULL || h->of_node == node; +diff --git a/arch/powerpc/sysdev/mpic.c b/arch/powerpc/sysdev/mpic.c +index 89cec0e..bf6f77e 100644 +--- a/arch/powerpc/sysdev/mpic.c ++++ b/arch/powerpc/sysdev/mpic.c +@@ -1009,7 +1009,8 @@ static struct irq_chip mpic_irq_ht_chip = { + #endif /* CONFIG_MPIC_U3_HT_IRQS */ + + +-static int mpic_host_match(struct irq_domain *h, struct device_node *node) ++static int mpic_host_match(struct irq_domain *h, struct device_node *node, ++ enum irq_domain_bus_token bus_token) + { + /* Exact match, unless mpic node is NULL */ + return h->of_node == NULL || h->of_node == node; +diff --git a/arch/powerpc/sysdev/mpic_pasemi_msi.c b/arch/powerpc/sysdev/mpic_pasemi_msi.c +index a6add4a..5a4c474 100644 +--- a/arch/powerpc/sysdev/mpic_pasemi_msi.c ++++ b/arch/powerpc/sysdev/mpic_pasemi_msi.c +@@ -42,7 +42,7 @@ static struct mpic *msi_mpic; + static void mpic_pasemi_msi_mask_irq(struct irq_data *data) + { + pr_debug("mpic_pasemi_msi_mask_irq %d\n", data->irq); +- mask_msi_irq(data); ++ pci_msi_mask_irq(data); + mpic_mask_irq(data); + } + +@@ -50,7 +50,7 @@ static void mpic_pasemi_msi_unmask_irq(struct irq_data *data) + { + pr_debug("mpic_pasemi_msi_unmask_irq %d\n", data->irq); + mpic_unmask_irq(data); +- unmask_msi_irq(data); ++ pci_msi_unmask_irq(data); + } + + static struct irq_chip mpic_pasemi_msi_chip = { +@@ -138,7 +138,7 @@ static int pasemi_msi_setup_msi_irqs(struct pci_dev *pdev, int nvec, int type) + * register to generate MSI [512...1023] + */ + msg.data = hwirq-0x200; +- write_msi_msg(virq, &msg); ++ pci_write_msi_msg(virq, &msg); + } + + return 0; +diff --git a/arch/powerpc/sysdev/mpic_u3msi.c b/arch/powerpc/sysdev/mpic_u3msi.c +index db35a40..65880cc 100644 +--- a/arch/powerpc/sysdev/mpic_u3msi.c ++++ b/arch/powerpc/sysdev/mpic_u3msi.c +@@ -25,14 +25,14 @@ static struct mpic *msi_mpic; + + static void mpic_u3msi_mask_irq(struct irq_data *data) + { +- mask_msi_irq(data); ++ pci_msi_mask_irq(data); + mpic_mask_irq(data); + } + + static void mpic_u3msi_unmask_irq(struct irq_data *data) + { + mpic_unmask_irq(data); +- unmask_msi_irq(data); ++ pci_msi_unmask_irq(data); + } + + static struct irq_chip mpic_u3msi_chip = { +@@ -172,7 +172,7 @@ static int u3msi_setup_msi_irqs(struct pci_dev *pdev, int nvec, int type) + printk("u3msi: allocated virq 0x%x (hw 0x%x) addr 0x%lx\n", + virq, hwirq, (unsigned long)addr); + msg.data = hwirq; +- write_msi_msg(virq, &msg); ++ pci_write_msi_msg(virq, &msg); + + hwirq++; + } +diff --git a/arch/powerpc/sysdev/ppc4xx_hsta_msi.c b/arch/powerpc/sysdev/ppc4xx_hsta_msi.c +index a6a4dbd..908105f 100644 +--- a/arch/powerpc/sysdev/ppc4xx_hsta_msi.c ++++ b/arch/powerpc/sysdev/ppc4xx_hsta_msi.c +@@ -85,7 +85,7 @@ static int hsta_setup_msi_irqs(struct pci_dev *dev, int nvec, int type) + msi_bitmap_free_hwirqs(&ppc4xx_hsta_msi.bmp, irq, 1); + return -EINVAL; + } +- write_msi_msg(hwirq, &msg); ++ pci_write_msi_msg(hwirq, &msg); + } + + return 0; +diff --git a/arch/powerpc/sysdev/ppc4xx_msi.c b/arch/powerpc/sysdev/ppc4xx_msi.c +index 85d9c18..c6df3e2 100644 +--- a/arch/powerpc/sysdev/ppc4xx_msi.c ++++ b/arch/powerpc/sysdev/ppc4xx_msi.c +@@ -116,7 +116,7 @@ static int ppc4xx_setup_msi_irqs(struct pci_dev *dev, int nvec, int type) + + irq_set_msi_desc(virq, entry); + msg.data = int_no; +- write_msi_msg(virq, &msg); ++ pci_write_msi_msg(virq, &msg); + } + return 0; + } +diff --git a/arch/powerpc/sysdev/qe_lib/qe_ic.c b/arch/powerpc/sysdev/qe_lib/qe_ic.c +index b2b87c3..a433b3d 100644 +--- a/arch/powerpc/sysdev/qe_lib/qe_ic.c ++++ b/arch/powerpc/sysdev/qe_lib/qe_ic.c +@@ -245,7 +245,8 @@ static struct irq_chip qe_ic_irq_chip = { + .irq_mask_ack = qe_ic_mask_irq, + }; + +-static int qe_ic_host_match(struct irq_domain *h, struct device_node *node) ++static int qe_ic_host_match(struct irq_domain *h, struct device_node *node, ++ enum irq_domain_bus_token bus_token) + { + /* Exact match, unless qe_ic node is NULL */ + return h->of_node == NULL || h->of_node == node; +diff --git a/arch/powerpc/sysdev/xics/ics-opal.c b/arch/powerpc/sysdev/xics/ics-opal.c +index 3c6ee1b..4ba554e 100644 +--- a/arch/powerpc/sysdev/xics/ics-opal.c ++++ b/arch/powerpc/sysdev/xics/ics-opal.c +@@ -73,7 +73,7 @@ static unsigned int ics_opal_startup(struct irq_data *d) + * at that level, so we do it here by hand. + */ + if (d->msi_desc) +- unmask_msi_irq(d); ++ pci_msi_unmask_irq(d); + #endif + + /* unmask it */ +diff --git a/arch/powerpc/sysdev/xics/ics-rtas.c b/arch/powerpc/sysdev/xics/ics-rtas.c +index 936575d..bc81335 100644 +--- a/arch/powerpc/sysdev/xics/ics-rtas.c ++++ b/arch/powerpc/sysdev/xics/ics-rtas.c +@@ -76,7 +76,7 @@ static unsigned int ics_rtas_startup(struct irq_data *d) + * at that level, so we do it here by hand. + */ + if (d->msi_desc) +- unmask_msi_irq(d); ++ pci_msi_unmask_irq(d); + #endif + /* unmask it */ + ics_rtas_unmask_irq(d); +diff --git a/arch/powerpc/sysdev/xics/xics-common.c b/arch/powerpc/sysdev/xics/xics-common.c +index fe0cca4..13ab716 100644 +--- a/arch/powerpc/sysdev/xics/xics-common.c ++++ b/arch/powerpc/sysdev/xics/xics-common.c +@@ -300,7 +300,8 @@ int xics_get_irq_server(unsigned int virq, const struct cpumask *cpumask, + } + #endif /* CONFIG_SMP */ + +-static int xics_host_match(struct irq_domain *h, struct device_node *node) ++static int xics_host_match(struct irq_domain *h, struct device_node *node, ++ enum irq_domain_bus_token bus_token) + { + struct ics *ics; + +diff --git a/arch/s390/pci/pci.c b/arch/s390/pci/pci.c +index 2fa7b14..d59c825 100644 +--- a/arch/s390/pci/pci.c ++++ b/arch/s390/pci/pci.c +@@ -50,8 +50,8 @@ static DEFINE_SPINLOCK(zpci_list_lock); + + static struct irq_chip zpci_irq_chip = { + .name = "zPCI", +- .irq_unmask = unmask_msi_irq, +- .irq_mask = mask_msi_irq, ++ .irq_unmask = pci_msi_unmask_irq, ++ .irq_mask = pci_msi_mask_irq, + }; + + static DECLARE_BITMAP(zpci_domain, ZPCI_NR_DEVICES); +@@ -403,7 +403,7 @@ int arch_setup_msi_irqs(struct pci_dev *pdev, int nvec, int type) + msg.data = hwirq; + msg.address_lo = zdev->msi_addr & 0xffffffff; + msg.address_hi = zdev->msi_addr >> 32; +- write_msi_msg(irq, &msg); ++ pci_write_msi_msg(irq, &msg); + airq_iv_set_data(zdev->aibv, hwirq, irq); + hwirq++; + } +@@ -448,9 +448,9 @@ void arch_teardown_msi_irqs(struct pci_dev *pdev) + /* Release MSI interrupts */ + list_for_each_entry(msi, &pdev->msi_list, list) { + if (msi->msi_attrib.is_msix) +- default_msix_mask_irq(msi, 1); ++ __pci_msix_desc_mask_irq(msi, 1); + else +- default_msi_mask_irq(msi, 1, 1); ++ __pci_msi_desc_mask_irq(msi, 1, 1); + irq_set_msi_desc(msi->irq, NULL); + irq_free_desc(msi->irq); + msi->msg.address_lo = 0; +diff --git a/arch/sparc/kernel/pci_msi.c b/arch/sparc/kernel/pci_msi.c +index 580651a..84e16d8 100644 +--- a/arch/sparc/kernel/pci_msi.c ++++ b/arch/sparc/kernel/pci_msi.c +@@ -111,10 +111,10 @@ static void free_msi(struct pci_pbm_info *pbm, int msi_num) + + static struct irq_chip msi_irq = { + .name = "PCI-MSI", +- .irq_mask = mask_msi_irq, +- .irq_unmask = unmask_msi_irq, +- .irq_enable = unmask_msi_irq, +- .irq_disable = mask_msi_irq, ++ .irq_mask = pci_msi_mask_irq, ++ .irq_unmask = pci_msi_unmask_irq, ++ .irq_enable = pci_msi_unmask_irq, ++ .irq_disable = pci_msi_mask_irq, + /* XXX affinity XXX */ + }; + +@@ -161,7 +161,7 @@ static int sparc64_setup_msi_irq(unsigned int *irq_p, + msg.data = msi; + + irq_set_msi_desc(*irq_p, entry); +- write_msi_msg(*irq_p, &msg); ++ pci_write_msi_msg(*irq_p, &msg); + + return 0; + +diff --git a/arch/tile/kernel/pci_gx.c b/arch/tile/kernel/pci_gx.c +index e39f9c5..e717af2 100644 +--- a/arch/tile/kernel/pci_gx.c ++++ b/arch/tile/kernel/pci_gx.c +@@ -1453,7 +1453,7 @@ static struct pci_ops tile_cfg_ops = { + static unsigned int tilegx_msi_startup(struct irq_data *d) + { + if (d->msi_desc) +- unmask_msi_irq(d); ++ pci_msi_unmask_irq(d); + + return 0; + } +@@ -1465,14 +1465,14 @@ static void tilegx_msi_ack(struct irq_data *d) + + static void tilegx_msi_mask(struct irq_data *d) + { +- mask_msi_irq(d); ++ pci_msi_mask_irq(d); + __insn_mtspr(SPR_IPI_MASK_SET_K, 1UL << d->irq); + } + + static void tilegx_msi_unmask(struct irq_data *d) + { + __insn_mtspr(SPR_IPI_MASK_RESET_K, 1UL << d->irq); +- unmask_msi_irq(d); ++ pci_msi_unmask_irq(d); + } + + static struct irq_chip tilegx_msi_chip = { +@@ -1590,7 +1590,7 @@ int arch_setup_msi_irq(struct pci_dev *pdev, struct msi_desc *desc) + msg.address_hi = msi_addr >> 32; + msg.address_lo = msi_addr & 0xffffffff; + +- write_msi_msg(irq, &msg); ++ pci_write_msi_msg(irq, &msg); + irq_set_chip_and_handler(irq, &tilegx_msi_chip, handle_level_irq); + irq_set_handler_data(irq, controller); + +diff --git a/arch/x86/include/asm/x86_init.h b/arch/x86/include/asm/x86_init.h +index e45e4da..f58a9c7 100644 +--- a/arch/x86/include/asm/x86_init.h ++++ b/arch/x86/include/asm/x86_init.h +@@ -172,7 +172,6 @@ struct x86_platform_ops { + + struct pci_dev; + struct msi_msg; +-struct msi_desc; + + struct x86_msi_ops { + int (*setup_msi_irqs)(struct pci_dev *dev, int nvec, int type); +@@ -183,8 +182,6 @@ struct x86_msi_ops { + void (*teardown_msi_irqs)(struct pci_dev *dev); + void (*restore_msi_irqs)(struct pci_dev *dev); + int (*setup_hpet_msi)(unsigned int irq, unsigned int id); +- u32 (*msi_mask_irq)(struct msi_desc *desc, u32 mask, u32 flag); +- u32 (*msix_mask_irq)(struct msi_desc *desc, u32 flag); + }; + + struct IO_APIC_route_entry; +diff --git a/arch/x86/kernel/apic/io_apic.c b/arch/x86/kernel/apic/io_apic.c +index 1183d54..7ffe0a2 100644 +--- a/arch/x86/kernel/apic/io_apic.c ++++ b/arch/x86/kernel/apic/io_apic.c +@@ -3158,7 +3158,7 @@ msi_set_affinity(struct irq_data *data, const struct cpumask *mask, bool force) + msg.address_lo &= ~MSI_ADDR_DEST_ID_MASK; + msg.address_lo |= MSI_ADDR_DEST_ID(dest); + +- __write_msi_msg(data->msi_desc, &msg); ++ __pci_write_msi_msg(data->msi_desc, &msg); + + return IRQ_SET_MASK_OK_NOCOPY; + } +@@ -3169,8 +3169,8 @@ msi_set_affinity(struct irq_data *data, const struct cpumask *mask, bool force) + */ + static struct irq_chip msi_chip = { + .name = "PCI-MSI", +- .irq_unmask = unmask_msi_irq, +- .irq_mask = mask_msi_irq, ++ .irq_unmask = pci_msi_unmask_irq, ++ .irq_mask = pci_msi_mask_irq, + .irq_ack = ack_apic_edge, + .irq_set_affinity = msi_set_affinity, + .irq_retrigger = ioapic_retrigger_irq, +@@ -3196,7 +3196,7 @@ int setup_msi_irq(struct pci_dev *dev, struct msi_desc *msidesc, + * MSI message denotes a contiguous group of IRQs, written for 0th IRQ. + */ + if (!irq_offset) +- write_msi_msg(irq, &msg); ++ pci_write_msi_msg(irq, &msg); + + setup_remapped_irq(irq, irq_cfg(irq), chip); + +diff --git a/arch/x86/kernel/x86_init.c b/arch/x86/kernel/x86_init.c +index e48b674..234b072 100644 +--- a/arch/x86/kernel/x86_init.c ++++ b/arch/x86/kernel/x86_init.c +@@ -116,8 +116,6 @@ struct x86_msi_ops x86_msi = { + .teardown_msi_irqs = default_teardown_msi_irqs, + .restore_msi_irqs = default_restore_msi_irqs, + .setup_hpet_msi = default_setup_hpet_msi, +- .msi_mask_irq = default_msi_mask_irq, +- .msix_mask_irq = default_msix_mask_irq, + }; + + /* MSI arch specific hooks */ +@@ -140,14 +138,6 @@ void arch_restore_msi_irqs(struct pci_dev *dev) + { + x86_msi.restore_msi_irqs(dev); + } +-u32 arch_msi_mask_irq(struct msi_desc *desc, u32 mask, u32 flag) +-{ +- return x86_msi.msi_mask_irq(desc, mask, flag); +-} +-u32 arch_msix_mask_irq(struct msi_desc *desc, u32 flag) +-{ +- return x86_msi.msix_mask_irq(desc, flag); +-} + #endif + + struct x86_io_apic_ops x86_io_apic_ops = { +diff --git a/arch/x86/pci/bus_numa.c b/arch/x86/pci/bus_numa.c +index f3a2cfc..7bcf06a 100644 +--- a/arch/x86/pci/bus_numa.c ++++ b/arch/x86/pci/bus_numa.c +@@ -31,7 +31,7 @@ void x86_pci_root_bus_resources(int bus, struct list_head *resources) + { + struct pci_root_info *info = x86_find_pci_root_info(bus); + struct pci_root_res *root_res; +- struct pci_host_bridge_window *window; ++ struct resource_entry *window; + bool found = false; + + if (!info) +@@ -41,7 +41,7 @@ void x86_pci_root_bus_resources(int bus, struct list_head *resources) + bus); + + /* already added by acpi ? */ +- list_for_each_entry(window, resources, list) ++ resource_list_for_each_entry(window, resources) + if (window->res->flags & IORESOURCE_BUS) { + found = true; + break; +diff --git a/arch/x86/pci/xen.c b/arch/x86/pci/xen.c +index 6b3cf7c..878fb8e 100644 +--- a/arch/x86/pci/xen.c ++++ b/arch/x86/pci/xen.c +@@ -229,7 +229,7 @@ static int xen_hvm_setup_msi_irqs(struct pci_dev *dev, int nvec, int type) + return 1; + + list_for_each_entry(msidesc, &dev->msi_list, list) { +- __read_msi_msg(msidesc, &msg); ++ __pci_read_msi_msg(msidesc, &msg); + pirq = MSI_ADDR_EXT_DEST_ID(msg.address_hi) | + ((msg.address_lo >> MSI_ADDR_DEST_ID_SHIFT) & 0xff); + if (msg.data != XEN_PIRQ_MSI_DATA || +@@ -240,7 +240,7 @@ static int xen_hvm_setup_msi_irqs(struct pci_dev *dev, int nvec, int type) + goto error; + } + xen_msi_compose_msg(dev, pirq, &msg); +- __write_msi_msg(msidesc, &msg); ++ __pci_write_msi_msg(msidesc, &msg); + dev_dbg(&dev->dev, "xen: msi bound to pirq=%d\n", pirq); + } else { + dev_dbg(&dev->dev, +@@ -296,12 +296,16 @@ static int xen_initdom_setup_msi_irqs(struct pci_dev *dev, int nvec, int type) + map_irq.entry_nr = nvec; + } else if (type == PCI_CAP_ID_MSIX) { + int pos; ++ unsigned long flags; + u32 table_offset, bir; + + pos = dev->msix_cap; + pci_read_config_dword(dev, pos + PCI_MSIX_TABLE, + &table_offset); + bir = (u8)(table_offset & PCI_MSIX_TABLE_BIR); ++ flags = pci_resource_flags(dev, bir); ++ if (!flags || (flags & IORESOURCE_UNSET)) ++ return -EINVAL; + + map_irq.table_base = pci_resource_start(dev, bir); + map_irq.entry_nr = msidesc->msi_attrib.entry_nr; +@@ -394,14 +398,7 @@ static void xen_teardown_msi_irq(unsigned int irq) + { + xen_destroy_irq(irq); + } +-static u32 xen_nop_msi_mask_irq(struct msi_desc *desc, u32 mask, u32 flag) +-{ +- return 0; +-} +-static u32 xen_nop_msix_mask_irq(struct msi_desc *desc, u32 flag) +-{ +- return 0; +-} ++ + #endif + + int __init pci_xen_init(void) +@@ -425,8 +422,7 @@ int __init pci_xen_init(void) + x86_msi.setup_msi_irqs = xen_setup_msi_irqs; + x86_msi.teardown_msi_irq = xen_teardown_msi_irq; + x86_msi.teardown_msi_irqs = xen_teardown_msi_irqs; +- x86_msi.msi_mask_irq = xen_nop_msi_mask_irq; +- x86_msi.msix_mask_irq = xen_nop_msix_mask_irq; ++ pci_msi_ignore_mask = 1; + #endif + return 0; + } +@@ -460,8 +456,7 @@ int __init pci_xen_initial_domain(void) + x86_msi.setup_msi_irqs = xen_initdom_setup_msi_irqs; + x86_msi.teardown_msi_irq = xen_teardown_msi_irq; + x86_msi.restore_msi_irqs = xen_initdom_restore_msi_irqs; +- x86_msi.msi_mask_irq = xen_nop_msi_mask_irq; +- x86_msi.msix_mask_irq = xen_nop_msix_mask_irq; ++ pci_msi_ignore_mask = 1; + #endif + __acpi_register_gsi = acpi_register_gsi_xen; + /* Pre-allocate legacy irqs */ +diff --git a/drivers/acpi/acpi_lpss.c b/drivers/acpi/acpi_lpss.c +index fdb5701..0ad0ce6 100644 +--- a/drivers/acpi/acpi_lpss.c ++++ b/drivers/acpi/acpi_lpss.c +@@ -325,7 +325,7 @@ static int acpi_lpss_create_device(struct acpi_device *adev, + { + struct lpss_device_desc *dev_desc; + struct lpss_private_data *pdata; +- struct resource_list_entry *rentry; ++ struct resource_entry *rentry; + struct list_head resource_list; + struct platform_device *pdev; + int ret; +@@ -345,12 +345,12 @@ static int acpi_lpss_create_device(struct acpi_device *adev, + goto err_out; + + list_for_each_entry(rentry, &resource_list, node) +- if (resource_type(&rentry->res) == IORESOURCE_MEM) { ++ if (resource_type(rentry->res) == IORESOURCE_MEM) { + if (dev_desc->prv_size_override) + pdata->mmio_size = dev_desc->prv_size_override; + else +- pdata->mmio_size = resource_size(&rentry->res); +- pdata->mmio_base = ioremap(rentry->res.start, ++ pdata->mmio_size = resource_size(rentry->res); ++ pdata->mmio_base = ioremap(rentry->res->start, + pdata->mmio_size); + break; + } +diff --git a/drivers/acpi/acpi_platform.c b/drivers/acpi/acpi_platform.c +index 6ba8beb..1284138 100644 +--- a/drivers/acpi/acpi_platform.c ++++ b/drivers/acpi/acpi_platform.c +@@ -45,7 +45,7 @@ struct platform_device *acpi_create_platform_device(struct acpi_device *adev) + struct platform_device *pdev = NULL; + struct acpi_device *acpi_parent; + struct platform_device_info pdevinfo; +- struct resource_list_entry *rentry; ++ struct resource_entry *rentry; + struct list_head resource_list; + struct resource *resources = NULL; + int count; +@@ -71,7 +71,7 @@ struct platform_device *acpi_create_platform_device(struct acpi_device *adev) + } + count = 0; + list_for_each_entry(rentry, &resource_list, node) +- resources[count++] = rentry->res; ++ resources[count++] = *rentry->res; + + acpi_dev_free_resource_list(&resource_list); + } +diff --git a/drivers/acpi/resource.c b/drivers/acpi/resource.c +index 2ba8f02..e7f4aa0 100644 +--- a/drivers/acpi/resource.c ++++ b/drivers/acpi/resource.c +@@ -415,12 +415,7 @@ EXPORT_SYMBOL_GPL(acpi_dev_resource_interrupt); + */ + void acpi_dev_free_resource_list(struct list_head *list) + { +- struct resource_list_entry *rentry, *re; +- +- list_for_each_entry_safe(rentry, re, list, node) { +- list_del(&rentry->node); +- kfree(rentry); +- } ++ resource_list_free(list); + } + EXPORT_SYMBOL_GPL(acpi_dev_free_resource_list); + +@@ -435,15 +430,15 @@ struct res_proc_context { + static acpi_status acpi_dev_new_resource_entry(struct resource *r, + struct res_proc_context *c) + { +- struct resource_list_entry *rentry; ++ struct resource_entry *rentry; + +- rentry = kmalloc(sizeof(*rentry), GFP_KERNEL); ++ rentry = resource_list_create_entry(NULL, 0); + if (!rentry) { + c->error = -ENOMEM; + return AE_NO_MEMORY; + } +- rentry->res = *r; +- list_add_tail(&rentry->node, c->list); ++ *rentry->res = *r; ++ resource_list_add_tail(rentry, c->list); + c->count++; + return AE_OK; + } +@@ -503,7 +498,7 @@ static acpi_status acpi_dev_process_resource(struct acpi_resource *ares, + * returned as the final error code. + * + * The resultant struct resource objects are put on the list pointed to by +- * @list, that must be empty initially, as members of struct resource_list_entry ++ * @list, that must be empty initially, as members of struct resource_entry + * objects. Callers of this routine should use %acpi_dev_free_resource_list() to + * free that list. + * +diff --git a/drivers/base/core.c b/drivers/base/core.c +index 842d047..4c7a18f 100644 +--- a/drivers/base/core.c ++++ b/drivers/base/core.c +@@ -661,6 +661,9 @@ void device_initialize(struct device *dev) + INIT_LIST_HEAD(&dev->devres_head); + device_pm_init(dev); + set_dev_node(dev, -1); ++#ifdef CONFIG_GENERIC_MSI_IRQ ++ INIT_LIST_HEAD(&dev->msi_list); ++#endif + } + EXPORT_SYMBOL_GPL(device_initialize); + +diff --git a/drivers/base/platform.c b/drivers/base/platform.c +index 317e0e4..b387fb9 100644 +--- a/drivers/base/platform.c ++++ b/drivers/base/platform.c +@@ -1011,6 +1011,7 @@ int __init platform_bus_init(void) + error = bus_register(&platform_bus_type); + if (error) + device_unregister(&platform_bus); ++ of_platform_register_reconfig_notifier(); + return error; + } + +diff --git a/drivers/block/loop.c b/drivers/block/loop.c +index 6cb1beb..12678be 100644 +--- a/drivers/block/loop.c ++++ b/drivers/block/loop.c +@@ -692,6 +692,24 @@ static inline int is_loop_device(struct file *file) + return i && S_ISBLK(i->i_mode) && MAJOR(i->i_rdev) == LOOP_MAJOR; + } + ++/* ++ * for AUFS ++ * no get/put for file. ++ */ ++struct file *loop_backing_file(struct super_block *sb) ++{ ++ struct file *ret; ++ struct loop_device *l; ++ ++ ret = NULL; ++ if (MAJOR(sb->s_dev) == LOOP_MAJOR) { ++ l = sb->s_bdev->bd_disk->private_data; ++ ret = l->lo_backing_file; ++ } ++ return ret; ++} ++EXPORT_SYMBOL_GPL(loop_backing_file); ++ + /* loop sysfs attributes */ + + static ssize_t loop_attr_show(struct device *dev, char *page, +diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig +index 455fd17..38c8814 100644 +--- a/drivers/clk/Kconfig ++++ b/drivers/clk/Kconfig +@@ -101,12 +101,12 @@ config COMMON_CLK_AXI_CLKGEN + Support for the Analog Devices axi-clkgen pcore clock generator for Xilinx + FPGAs. It is commonly used in Analog Devices' reference designs. + +-config CLK_PPC_CORENET +- bool "Clock driver for PowerPC corenet platforms" +- depends on PPC_E500MC && OF ++config CLK_QORIQ ++ bool "Clock driver for Freescale QorIQ platforms" ++ depends on (PPC_E500MC || ARM || ARM64) && OF + ---help--- +- This adds the clock driver support for Freescale PowerPC corenet +- platforms using common clock framework. ++ This adds the clock driver support for Freescale QorIQ platforms ++ using common clock framework. + + config COMMON_CLK_XGENE + bool "Clock driver for APM XGene SoC" +diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile +index d5fba5b..4ff94cd 100644 +--- a/drivers/clk/Makefile ++++ b/drivers/clk/Makefile +@@ -30,7 +30,7 @@ obj-$(CONFIG_ARCH_MOXART) += clk-moxart.o + obj-$(CONFIG_ARCH_NOMADIK) += clk-nomadik.o + obj-$(CONFIG_ARCH_NSPIRE) += clk-nspire.o + obj-$(CONFIG_COMMON_CLK_PALMAS) += clk-palmas.o +-obj-$(CONFIG_CLK_PPC_CORENET) += clk-ppc-corenet.o ++obj-$(CONFIG_CLK_QORIQ) += clk-qoriq.o + obj-$(CONFIG_COMMON_CLK_RK808) += clk-rk808.o + obj-$(CONFIG_COMMON_CLK_S2MPS11) += clk-s2mps11.o + obj-$(CONFIG_COMMON_CLK_SI5351) += clk-si5351.o +diff --git a/drivers/clk/clk-qoriq.c b/drivers/clk/clk-qoriq.c +new file mode 100644 +index 0000000..74051c9 +--- /dev/null ++++ b/drivers/clk/clk-qoriq.c +@@ -0,0 +1,1256 @@ ++/* ++ * Copyright 2013 Freescale Semiconductor, Inc. ++ * ++ * 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. ++ * ++ * clock driver for Freescale QorIQ SoCs. ++ */ ++ ++#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define PLL_DIV1 0 ++#define PLL_DIV2 1 ++#define PLL_DIV3 2 ++#define PLL_DIV4 3 ++ ++#define PLATFORM_PLL 0 ++#define CGA_PLL1 1 ++#define CGA_PLL2 2 ++#define CGA_PLL3 3 ++#define CGA_PLL4 4 /* only on clockgen-1.0, which lacks CGB */ ++#define CGB_PLL1 4 ++#define CGB_PLL2 5 ++ ++struct clockgen_pll_div { ++ struct clk *clk; ++ char name[32]; ++}; ++ ++struct clockgen_pll { ++ struct clockgen_pll_div div[4]; ++}; ++ ++#define CLKSEL_VALID 1 ++#define CLKSEL_80PCT 2 /* Only allowed if PLL <= 80% of max cpu freq */ ++ ++struct clockgen_sourceinfo { ++ u32 flags; /* CLKSEL_xxx */ ++ int pll; /* CGx_PLLn */ ++ int div; /* PLL_DIVn */ ++}; ++ ++#define NUM_MUX_PARENTS 16 ++ ++struct clockgen_muxinfo { ++ struct clockgen_sourceinfo clksel[NUM_MUX_PARENTS]; ++}; ++ ++#define NUM_HWACCEL 5 ++#define NUM_CMUX 8 ++ ++struct clockgen; ++ ++/* ++ * cmux freq must be >= platform pll. ++ * If not set, cmux freq must be >= platform pll/2 ++ */ ++#define CG_CMUX_GE_PLAT 1 ++ ++#define CG_PLL_8BIT 2 /* PLLCnGSR[CFG] is 8 bits, not 6 */ ++#define CG_VER3 4 /* version 3 cg: reg layout different */ ++#define CG_LITTLE_ENDIAN 8 ++ ++struct clockgen_chipinfo { ++ const char *compat, *guts_compat; ++ const struct clockgen_muxinfo *cmux_groups[2]; ++ const struct clockgen_muxinfo *hwaccel[NUM_HWACCEL]; ++ void (*init_periph)(struct clockgen *cg); ++ int cmux_to_group[NUM_CMUX]; /* -1 terminates if fewer than NUM_CMUX */ ++ u32 pll_mask; /* 1 << n bit set if PLL n is valid */ ++ u32 flags; /* CG_xxx */ ++}; ++ ++struct clockgen { ++ struct device_node *node; ++ void __iomem *regs; ++ struct clockgen_chipinfo info; /* mutable copy */ ++ struct clk *sysclk; ++ struct clockgen_pll pll[6]; ++ struct clk *cmux[NUM_CMUX]; ++ struct clk *hwaccel[NUM_HWACCEL]; ++ struct clk *fman[2]; ++ struct ccsr_guts __iomem *guts; ++}; ++ ++static struct clockgen clockgen; ++ ++static void cg_out(struct clockgen *cg, u32 val, u32 __iomem *reg) ++{ ++ if (cg->info.flags & CG_LITTLE_ENDIAN) ++ iowrite32(val, reg); ++ else ++ iowrite32be(val, reg); ++} ++ ++static u32 cg_in(struct clockgen *cg, u32 __iomem *reg) ++{ ++ u32 val; ++ ++ if (cg->info.flags & CG_LITTLE_ENDIAN) ++ val = ioread32(reg); ++ else ++ val = ioread32be(reg); ++ ++ return val; ++} ++ ++static const struct clockgen_muxinfo p2041_cmux_grp1 = { ++ { ++ [0] = { CLKSEL_VALID, CGA_PLL1, PLL_DIV1 }, ++ [1] = { CLKSEL_VALID, CGA_PLL1, PLL_DIV2 }, ++ [4] = { CLKSEL_VALID, CGA_PLL2, PLL_DIV1 }, ++ } ++}; ++ ++static const struct clockgen_muxinfo p2041_cmux_grp2 = { ++ { ++ [0] = { CLKSEL_VALID, CGA_PLL1, PLL_DIV1 }, ++ [4] = { CLKSEL_VALID, CGA_PLL1, PLL_DIV2 }, ++ [5] = { CLKSEL_VALID, CGA_PLL2, PLL_DIV1 }, ++ } ++}; ++ ++static const struct clockgen_muxinfo p5020_cmux_grp1 = { ++ { ++ [0] = { CLKSEL_VALID, CGA_PLL1, PLL_DIV1 }, ++ [1] = { CLKSEL_VALID, CGA_PLL1, PLL_DIV2 }, ++ [4] = { CLKSEL_VALID | CLKSEL_80PCT, CGA_PLL2, PLL_DIV1 }, ++ } ++}; ++ ++static const struct clockgen_muxinfo p5020_cmux_grp2 = { ++ { ++ [0] = { CLKSEL_VALID | CLKSEL_80PCT, CGA_PLL1, PLL_DIV1 }, ++ [4] = { CLKSEL_VALID, CGA_PLL2, PLL_DIV1 }, ++ [5] = { CLKSEL_VALID, CGA_PLL2, PLL_DIV2 }, ++ } ++}; ++ ++static const struct clockgen_muxinfo p5040_cmux_grp1 = { ++ { ++ [0] = { CLKSEL_VALID, CGA_PLL1, PLL_DIV1 }, ++ [1] = { CLKSEL_VALID, CGA_PLL1, PLL_DIV2 }, ++ [4] = { CLKSEL_VALID | CLKSEL_80PCT, CGA_PLL2, PLL_DIV1 }, ++ [5] = { CLKSEL_VALID | CLKSEL_80PCT, CGA_PLL2, PLL_DIV2 }, ++ } ++}; ++ ++static const struct clockgen_muxinfo p5040_cmux_grp2 = { ++ { ++ [0] = { CLKSEL_VALID | CLKSEL_80PCT, CGA_PLL1, PLL_DIV1 }, ++ [1] = { CLKSEL_VALID | CLKSEL_80PCT, CGA_PLL1, PLL_DIV2 }, ++ [4] = { CLKSEL_VALID, CGA_PLL2, PLL_DIV1 }, ++ [5] = { CLKSEL_VALID, CGA_PLL2, PLL_DIV2 }, ++ } ++}; ++ ++static const struct clockgen_muxinfo p4080_cmux_grp1 = { ++ { ++ [0] = { CLKSEL_VALID, CGA_PLL1, PLL_DIV1 }, ++ [1] = { CLKSEL_VALID, CGA_PLL1, PLL_DIV2 }, ++ [4] = { CLKSEL_VALID, CGA_PLL2, PLL_DIV1 }, ++ [5] = { CLKSEL_VALID, CGA_PLL2, PLL_DIV2 }, ++ [8] = { CLKSEL_VALID | CLKSEL_80PCT, CGA_PLL3, PLL_DIV1 }, ++ } ++}; ++ ++static const struct clockgen_muxinfo p4080_cmux_grp2 = { ++ { ++ [0] = { CLKSEL_VALID | CLKSEL_80PCT, CGA_PLL1, PLL_DIV1 }, ++ [8] = { CLKSEL_VALID, CGA_PLL3, PLL_DIV1 }, ++ [9] = { CLKSEL_VALID, CGA_PLL3, PLL_DIV2 }, ++ [12] = { CLKSEL_VALID, CGA_PLL4, PLL_DIV1 }, ++ [13] = { CLKSEL_VALID, CGA_PLL4, PLL_DIV2 }, ++ } ++}; ++ ++static const struct clockgen_muxinfo t1023_cmux = { ++ { ++ [0] = { CLKSEL_VALID, CGA_PLL1, PLL_DIV1 }, ++ [1] = { CLKSEL_VALID, CGA_PLL1, PLL_DIV2 }, ++ } ++}; ++ ++static const struct clockgen_muxinfo t1040_cmux = { ++ { ++ [0] = { CLKSEL_VALID, CGA_PLL1, PLL_DIV1 }, ++ [1] = { CLKSEL_VALID, CGA_PLL1, PLL_DIV2 }, ++ [4] = { CLKSEL_VALID, CGA_PLL2, PLL_DIV1 }, ++ [5] = { CLKSEL_VALID, CGA_PLL2, PLL_DIV2 }, ++ } ++}; ++ ++ ++static const struct clockgen_muxinfo clockgen2_cmux_cga = { ++ { ++ { CLKSEL_VALID, CGA_PLL1, PLL_DIV1 }, ++ { CLKSEL_VALID, CGA_PLL1, PLL_DIV2 }, ++ { CLKSEL_VALID, CGA_PLL1, PLL_DIV4 }, ++ {}, ++ { CLKSEL_VALID, CGA_PLL2, PLL_DIV1 }, ++ { CLKSEL_VALID, CGA_PLL2, PLL_DIV2 }, ++ { CLKSEL_VALID, CGA_PLL2, PLL_DIV4 }, ++ {}, ++ { CLKSEL_VALID, CGA_PLL3, PLL_DIV1 }, ++ { CLKSEL_VALID, CGA_PLL3, PLL_DIV2 }, ++ { CLKSEL_VALID, CGA_PLL3, PLL_DIV4 }, ++ }, ++}; ++ ++static const struct clockgen_muxinfo clockgen2_cmux_cga12 = { ++ { ++ { CLKSEL_VALID, CGA_PLL1, PLL_DIV1 }, ++ { CLKSEL_VALID, CGA_PLL1, PLL_DIV2 }, ++ { CLKSEL_VALID, CGA_PLL1, PLL_DIV4 }, ++ {}, ++ { CLKSEL_VALID, CGA_PLL2, PLL_DIV1 }, ++ { CLKSEL_VALID, CGA_PLL2, PLL_DIV2 }, ++ { CLKSEL_VALID, CGA_PLL2, PLL_DIV4 }, ++ }, ++}; ++ ++static const struct clockgen_muxinfo clockgen2_cmux_cgb = { ++ { ++ { CLKSEL_VALID, CGB_PLL1, PLL_DIV1 }, ++ { CLKSEL_VALID, CGB_PLL1, PLL_DIV2 }, ++ { CLKSEL_VALID, CGB_PLL1, PLL_DIV4 }, ++ {}, ++ { CLKSEL_VALID, CGB_PLL2, PLL_DIV1 }, ++ { CLKSEL_VALID, CGB_PLL2, PLL_DIV2 }, ++ { CLKSEL_VALID, CGB_PLL2, PLL_DIV4 }, ++ }, ++}; ++ ++static const struct clockgen_muxinfo t1023_hwa1 = { ++ { ++ {}, ++ { CLKSEL_VALID, CGA_PLL1, PLL_DIV1 }, ++ { CLKSEL_VALID, CGA_PLL1, PLL_DIV2 }, ++ { CLKSEL_VALID, CGA_PLL1, PLL_DIV3 }, ++ }, ++}; ++ ++static const struct clockgen_muxinfo t1023_hwa2 = { ++ { ++ [6] = { CLKSEL_VALID, CGA_PLL1, PLL_DIV2 }, ++ }, ++}; ++ ++static const struct clockgen_muxinfo t2080_hwa1 = { ++ { ++ {}, ++ { CLKSEL_VALID, CGA_PLL1, PLL_DIV1 }, ++ { CLKSEL_VALID, CGA_PLL1, PLL_DIV2 }, ++ { CLKSEL_VALID, CGA_PLL1, PLL_DIV3 }, ++ { CLKSEL_VALID, CGA_PLL1, PLL_DIV4 }, ++ { CLKSEL_VALID, PLATFORM_PLL, PLL_DIV1 }, ++ { CLKSEL_VALID, CGA_PLL2, PLL_DIV2 }, ++ { CLKSEL_VALID, CGA_PLL2, PLL_DIV3 }, ++ }, ++}; ++ ++static const struct clockgen_muxinfo t2080_hwa2 = { ++ { ++ {}, ++ { CLKSEL_VALID, CGA_PLL2, PLL_DIV1 }, ++ { CLKSEL_VALID, CGA_PLL2, PLL_DIV2 }, ++ { CLKSEL_VALID, CGA_PLL2, PLL_DIV3 }, ++ { CLKSEL_VALID, CGA_PLL2, PLL_DIV4 }, ++ { CLKSEL_VALID, PLATFORM_PLL, PLL_DIV1 }, ++ { CLKSEL_VALID, CGA_PLL1, PLL_DIV2 }, ++ { CLKSEL_VALID, CGA_PLL1, PLL_DIV3 }, ++ }, ++}; ++ ++static const struct clockgen_muxinfo t4240_hwa1 = { ++ { ++ { CLKSEL_VALID, PLATFORM_PLL, PLL_DIV2 }, ++ { CLKSEL_VALID, CGA_PLL1, PLL_DIV1 }, ++ { CLKSEL_VALID, CGA_PLL1, PLL_DIV2 }, ++ { CLKSEL_VALID, CGA_PLL1, PLL_DIV3 }, ++ { CLKSEL_VALID, CGA_PLL1, PLL_DIV4 }, ++ {}, ++ { CLKSEL_VALID, CGA_PLL2, PLL_DIV2 }, ++ { CLKSEL_VALID, CGA_PLL2, PLL_DIV3 }, ++ }, ++}; ++ ++static const struct clockgen_muxinfo t4240_hwa4 = { ++ { ++ [2] = { CLKSEL_VALID, CGB_PLL1, PLL_DIV2 }, ++ [3] = { CLKSEL_VALID, CGB_PLL1, PLL_DIV3 }, ++ [4] = { CLKSEL_VALID, CGB_PLL1, PLL_DIV4 }, ++ [5] = { CLKSEL_VALID, PLATFORM_PLL, PLL_DIV1 }, ++ [6] = { CLKSEL_VALID, CGB_PLL2, PLL_DIV2 }, ++ }, ++}; ++ ++static const struct clockgen_muxinfo t4240_hwa5 = { ++ { ++ [2] = { CLKSEL_VALID, CGB_PLL2, PLL_DIV2 }, ++ [3] = { CLKSEL_VALID, CGB_PLL2, PLL_DIV3 }, ++ [4] = { CLKSEL_VALID, CGB_PLL2, PLL_DIV4 }, ++ [5] = { CLKSEL_VALID, PLATFORM_PLL, PLL_DIV1 }, ++ [6] = { CLKSEL_VALID, CGB_PLL1, PLL_DIV2 }, ++ [7] = { CLKSEL_VALID, CGB_PLL1, PLL_DIV3 }, ++ }, ++}; ++ ++#define RCWSR7_FM1_CLK_SEL 0x40000000 ++#define RCWSR7_FM2_CLK_SEL 0x20000000 ++#define RCWSR7_HWA_ASYNC_DIV 0x04000000 ++ ++static void __init p2041_init_periph(struct clockgen *cg) ++{ ++ u32 reg; ++ ++ reg = ioread32be(&cg->guts->rcwsr[7]); ++ ++ if (reg & RCWSR7_FM1_CLK_SEL) ++ cg->fman[0] = cg->pll[CGA_PLL2].div[PLL_DIV2].clk; ++ else ++ cg->fman[0] = cg->pll[PLATFORM_PLL].div[PLL_DIV2].clk; ++} ++ ++static void __init p4080_init_periph(struct clockgen *cg) ++{ ++ u32 reg; ++ ++ reg = ioread32be(&cg->guts->rcwsr[7]); ++ ++ if (reg & RCWSR7_FM1_CLK_SEL) ++ cg->fman[0] = cg->pll[CGA_PLL3].div[PLL_DIV2].clk; ++ else ++ cg->fman[0] = cg->pll[PLATFORM_PLL].div[PLL_DIV2].clk; ++ ++ if (reg & RCWSR7_FM2_CLK_SEL) ++ cg->fman[1] = cg->pll[CGA_PLL3].div[PLL_DIV2].clk; ++ else ++ cg->fman[1] = cg->pll[PLATFORM_PLL].div[PLL_DIV2].clk; ++} ++ ++static void __init p5020_init_periph(struct clockgen *cg) ++{ ++ u32 reg; ++ int div = PLL_DIV2; ++ ++ reg = ioread32be(&cg->guts->rcwsr[7]); ++ if (reg & RCWSR7_HWA_ASYNC_DIV) ++ div = PLL_DIV4; ++ ++ if (reg & RCWSR7_FM1_CLK_SEL) ++ cg->fman[0] = cg->pll[CGA_PLL2].div[div].clk; ++ else ++ cg->fman[0] = cg->pll[PLATFORM_PLL].div[PLL_DIV2].clk; ++} ++ ++static void __init p5040_init_periph(struct clockgen *cg) ++{ ++ u32 reg; ++ int div = PLL_DIV2; ++ ++ reg = ioread32be(&cg->guts->rcwsr[7]); ++ if (reg & RCWSR7_HWA_ASYNC_DIV) ++ div = PLL_DIV4; ++ ++ if (reg & RCWSR7_FM1_CLK_SEL) ++ cg->fman[0] = cg->pll[CGA_PLL3].div[div].clk; ++ else ++ cg->fman[0] = cg->pll[PLATFORM_PLL].div[PLL_DIV2].clk; ++ ++ if (reg & RCWSR7_FM2_CLK_SEL) ++ cg->fman[1] = cg->pll[CGA_PLL3].div[div].clk; ++ else ++ cg->fman[1] = cg->pll[PLATFORM_PLL].div[PLL_DIV2].clk; ++} ++ ++static void __init t1023_init_periph(struct clockgen *cg) ++{ ++ cg->fman[0] = cg->hwaccel[1]; ++} ++ ++static void __init t1040_init_periph(struct clockgen *cg) ++{ ++ cg->fman[0] = cg->pll[PLATFORM_PLL].div[PLL_DIV1].clk; ++} ++ ++static void __init t2080_init_periph(struct clockgen *cg) ++{ ++ cg->fman[0] = cg->hwaccel[0]; ++} ++ ++static void __init t4240_init_periph(struct clockgen *cg) ++{ ++ cg->fman[0] = cg->hwaccel[3]; ++ cg->fman[1] = cg->hwaccel[4]; ++} ++ ++static const struct clockgen_chipinfo chipinfo[] = { ++ { ++ .compat = "fsl,b4420-clockgen", ++ .guts_compat = "fsl,b4860-device-config", ++ .init_periph = t2080_init_periph, ++ .cmux_groups = { ++ &clockgen2_cmux_cga12, &clockgen2_cmux_cgb ++ }, ++ .hwaccel = { ++ &t2080_hwa1 ++ }, ++ .cmux_to_group = { ++ 0, 1, 1, 1, -1 ++ }, ++ .pll_mask = 0x3f, ++ .flags = CG_PLL_8BIT, ++ }, ++ { ++ .compat = "fsl,b4860-clockgen", ++ .guts_compat = "fsl,b4860-device-config", ++ .init_periph = t2080_init_periph, ++ .cmux_groups = { ++ &clockgen2_cmux_cga12, &clockgen2_cmux_cgb ++ }, ++ .hwaccel = { ++ &t2080_hwa1 ++ }, ++ .cmux_to_group = { ++ 0, 1, 1, 1, -1 ++ }, ++ .pll_mask = 0x3f, ++ .flags = CG_PLL_8BIT, ++ }, ++ { ++ .compat = "fsl,ls1021a-clockgen", ++ .cmux_groups = { ++ &t1023_cmux ++ }, ++ .cmux_to_group = { ++ 0, -1 ++ }, ++ .pll_mask = 0x03, ++ }, ++ { ++ .compat = "fsl,ls2080a-clockgen", ++ .cmux_groups = { ++ &clockgen2_cmux_cga12, &clockgen2_cmux_cgb ++ }, ++ .cmux_to_group = { ++ 0, 0, 1, 1, -1 ++ }, ++ .pll_mask = 0x37, ++ .flags = CG_VER3 | CG_LITTLE_ENDIAN, ++ }, ++ { ++ .compat = "fsl,ls2088a-clockgen", ++ .cmux_groups = { ++ &clockgen2_cmux_cga12, &clockgen2_cmux_cgb ++ }, ++ .cmux_to_group = { ++ 0, 0, 1, 1, -1 ++ }, ++ .pll_mask = 0x37, ++ .flags = CG_VER3 | CG_LITTLE_ENDIAN, ++ }, ++ { ++ .compat = "fsl,p2041-clockgen", ++ .guts_compat = "fsl,qoriq-device-config-1.0", ++ .init_periph = p2041_init_periph, ++ .cmux_groups = { ++ &p2041_cmux_grp1, &p2041_cmux_grp2 ++ }, ++ .cmux_to_group = { ++ 0, 0, 1, 1, -1 ++ }, ++ .pll_mask = 0x07, ++ }, ++ { ++ .compat = "fsl,p3041-clockgen", ++ .guts_compat = "fsl,qoriq-device-config-1.0", ++ .init_periph = p2041_init_periph, ++ .cmux_groups = { ++ &p2041_cmux_grp1, &p2041_cmux_grp2 ++ }, ++ .cmux_to_group = { ++ 0, 0, 1, 1, -1 ++ }, ++ .pll_mask = 0x07, ++ }, ++ { ++ .compat = "fsl,p4080-clockgen", ++ .guts_compat = "fsl,qoriq-device-config-1.0", ++ .init_periph = p4080_init_periph, ++ .cmux_groups = { ++ &p4080_cmux_grp1, &p4080_cmux_grp2 ++ }, ++ .cmux_to_group = { ++ 0, 0, 0, 0, 1, 1, 1, 1 ++ }, ++ .pll_mask = 0x1f, ++ }, ++ { ++ .compat = "fsl,p5020-clockgen", ++ .guts_compat = "fsl,qoriq-device-config-1.0", ++ .init_periph = p5020_init_periph, ++ .cmux_groups = { ++ &p2041_cmux_grp1, &p2041_cmux_grp2 ++ }, ++ .cmux_to_group = { ++ 0, 1, -1 ++ }, ++ .pll_mask = 0x07, ++ }, ++ { ++ .compat = "fsl,p5040-clockgen", ++ .guts_compat = "fsl,p5040-device-config", ++ .init_periph = p5040_init_periph, ++ .cmux_groups = { ++ &p5040_cmux_grp1, &p5040_cmux_grp2 ++ }, ++ .cmux_to_group = { ++ 0, 0, 1, 1, -1 ++ }, ++ .pll_mask = 0x0f, ++ }, ++ { ++ .compat = "fsl,t1023-clockgen", ++ .guts_compat = "fsl,t1023-device-config", ++ .init_periph = t1023_init_periph, ++ .cmux_groups = { ++ &t1023_cmux ++ }, ++ .hwaccel = { ++ &t1023_hwa1, &t1023_hwa2 ++ }, ++ .cmux_to_group = { ++ 0, 0, -1 ++ }, ++ .pll_mask = 0x03, ++ .flags = CG_PLL_8BIT, ++ }, ++ { ++ .compat = "fsl,t1040-clockgen", ++ .guts_compat = "fsl,t1040-device-config", ++ .init_periph = t1040_init_periph, ++ .cmux_groups = { ++ &t1040_cmux ++ }, ++ .cmux_to_group = { ++ 0, 0, 0, 0, -1 ++ }, ++ .pll_mask = 0x07, ++ .flags = CG_PLL_8BIT, ++ }, ++ { ++ .compat = "fsl,t2080-clockgen", ++ .guts_compat = "fsl,t2080-device-config", ++ .init_periph = t2080_init_periph, ++ .cmux_groups = { ++ &clockgen2_cmux_cga12 ++ }, ++ .hwaccel = { ++ &t2080_hwa1, &t2080_hwa2 ++ }, ++ .cmux_to_group = { ++ 0, -1 ++ }, ++ .pll_mask = 0x07, ++ .flags = CG_PLL_8BIT, ++ }, ++ { ++ .compat = "fsl,t4240-clockgen", ++ .guts_compat = "fsl,t4240-device-config", ++ .init_periph = t4240_init_periph, ++ .cmux_groups = { ++ &clockgen2_cmux_cga, &clockgen2_cmux_cgb ++ }, ++ .hwaccel = { ++ &t4240_hwa1, NULL, NULL, &t4240_hwa4, &t4240_hwa5 ++ }, ++ .cmux_to_group = { ++ 0, 0, 1, -1 ++ }, ++ .pll_mask = 0x3f, ++ .flags = CG_PLL_8BIT, ++ }, ++ {}, ++}; ++ ++struct mux_hwclock { ++ struct clk_hw hw; ++ struct clockgen *cg; ++ const struct clockgen_muxinfo *info; ++ u32 __iomem *reg; ++ u8 parent_to_clksel[NUM_MUX_PARENTS]; ++ s8 clksel_to_parent[NUM_MUX_PARENTS]; ++ int num_parents; ++}; ++ ++#define to_mux_hwclock(p) container_of(p, struct mux_hwclock, hw) ++#define CLKSEL_MASK 0x78000000 ++#define CLKSEL_SHIFT 27 ++ ++static int mux_set_parent(struct clk_hw *hw, u8 idx) ++{ ++ struct mux_hwclock *hwc = to_mux_hwclock(hw); ++ u32 clksel; ++ ++ if (idx >= hwc->num_parents) ++ return -EINVAL; ++ ++ clksel = hwc->parent_to_clksel[idx]; ++ cg_out(hwc->cg, (clksel << CLKSEL_SHIFT) & CLKSEL_MASK, hwc->reg); ++ ++ return 0; ++} ++ ++static u8 mux_get_parent(struct clk_hw *hw) ++{ ++ struct mux_hwclock *hwc = to_mux_hwclock(hw); ++ u32 clksel; ++ s8 ret; ++ ++ clksel = (cg_in(hwc->cg, hwc->reg) & CLKSEL_MASK) >> CLKSEL_SHIFT; ++ ++ ret = hwc->clksel_to_parent[clksel]; ++ if (ret < 0) { ++ pr_err("%s: mux at %p has bad clksel\n", __func__, hwc->reg); ++ return 0; ++ } ++ ++ return ret; ++} ++ ++static const struct clk_ops cmux_ops = { ++ .get_parent = mux_get_parent, ++ .set_parent = mux_set_parent, ++}; ++ ++/* ++ * Don't allow setting for now, as the clock options haven't been ++ * sanitized for additional restrictions. ++ */ ++static const struct clk_ops hwaccel_ops = { ++ .get_parent = mux_get_parent, ++}; ++ ++static const struct clockgen_pll_div *get_pll_div(struct clockgen *cg, ++ struct mux_hwclock *hwc, ++ int idx) ++{ ++ int pll, div; ++ ++ if (!(hwc->info->clksel[idx].flags & CLKSEL_VALID)) ++ return NULL; ++ ++ pll = hwc->info->clksel[idx].pll; ++ div = hwc->info->clksel[idx].div; ++ ++ return &cg->pll[pll].div[div]; ++} ++ ++static struct clk * __init create_mux_common(struct clockgen *cg, ++ struct mux_hwclock *hwc, ++ const struct clk_ops *ops, ++ unsigned long min_rate, ++ unsigned long pct80_rate, ++ const char *fmt, int idx) ++{ ++ struct clk_init_data init = {}; ++ struct clk *clk; ++ const struct clockgen_pll_div *div; ++ const char *parent_names[NUM_MUX_PARENTS]; ++ char name[32]; ++ int i, j; ++ ++ snprintf(name, sizeof(name), fmt, idx); ++ ++ for (i = 0, j = 0; i < NUM_MUX_PARENTS; i++) { ++ unsigned long rate; ++ ++ hwc->clksel_to_parent[i] = -1; ++ ++ div = get_pll_div(cg, hwc, i); ++ if (!div) ++ continue; ++ ++ rate = clk_get_rate(div->clk); ++ ++ if (hwc->info->clksel[i].flags & CLKSEL_80PCT && ++ rate > pct80_rate) ++ continue; ++ if (rate < min_rate) ++ continue; ++ ++ parent_names[j] = div->name; ++ hwc->parent_to_clksel[j] = i; ++ hwc->clksel_to_parent[i] = j; ++ j++; ++ } ++ ++ init.name = name; ++ init.ops = ops; ++ init.parent_names = parent_names; ++ init.num_parents = hwc->num_parents = j; ++ init.flags = 0; ++ hwc->hw.init = &init; ++ hwc->cg = cg; ++ ++ clk = clk_register(NULL, &hwc->hw); ++ if (IS_ERR(clk)) { ++ pr_err("%s: Couldn't register %s: %ld\n", __func__, name, ++ PTR_ERR(clk)); ++ kfree(hwc); ++ return NULL; ++ } ++ ++ return clk; ++} ++ ++static struct clk * __init create_one_cmux(struct clockgen *cg, int idx) ++{ ++ struct mux_hwclock *hwc; ++ const struct clockgen_pll_div *div; ++ unsigned long plat_rate, min_rate; ++ u64 pct80_rate; ++ u32 clksel; ++ ++ hwc = kzalloc(sizeof(*hwc), GFP_KERNEL); ++ if (!hwc) ++ return NULL; ++ ++ if (cg->info.flags & CG_VER3) ++ hwc->reg = cg->regs + 0x70000 + 0x20 * idx; ++ else ++ hwc->reg = cg->regs + 0x20 * idx; ++ ++ hwc->info = cg->info.cmux_groups[cg->info.cmux_to_group[idx]]; ++ ++ /* ++ * Find the rate for the default clksel, and treat it as the ++ * maximum rated core frequency. If this is an incorrect ++ * assumption, certain clock options (possibly including the ++ * default clksel) may be inappropriately excluded on certain ++ * chips. ++ */ ++ clksel = (cg_in(cg, hwc->reg) & CLKSEL_MASK) >> CLKSEL_SHIFT; ++ div = get_pll_div(cg, hwc, clksel); ++ if (!div) ++ return NULL; ++ ++ pct80_rate = clk_get_rate(div->clk); ++ pct80_rate *= 8; ++ do_div(pct80_rate, 10); ++ ++ plat_rate = clk_get_rate(cg->pll[PLATFORM_PLL].div[PLL_DIV1].clk); ++ ++ if (cg->info.flags & CG_CMUX_GE_PLAT) ++ min_rate = plat_rate; ++ else ++ min_rate = plat_rate / 2; ++ ++ return create_mux_common(cg, hwc, &cmux_ops, min_rate, ++ pct80_rate, "cg-cmux%d", idx); ++} ++ ++static struct clk * __init create_one_hwaccel(struct clockgen *cg, int idx) ++{ ++ struct mux_hwclock *hwc; ++ ++ hwc = kzalloc(sizeof(*hwc), GFP_KERNEL); ++ if (!hwc) ++ return NULL; ++ ++ hwc->reg = cg->regs + 0x20 * idx + 0x10; ++ hwc->info = cg->info.hwaccel[idx]; ++ ++ return create_mux_common(cg, hwc, &hwaccel_ops, 0, 0, ++ "cg-hwaccel%d", idx); ++} ++ ++static void __init create_muxes(struct clockgen *cg) ++{ ++ int i; ++ ++ for (i = 0; i < ARRAY_SIZE(cg->cmux); i++) { ++ if (cg->info.cmux_to_group[i] < 0) ++ break; ++ if (cg->info.cmux_to_group[i] >= ++ ARRAY_SIZE(cg->info.cmux_groups)) { ++ WARN_ON_ONCE(1); ++ continue; ++ } ++ ++ cg->cmux[i] = create_one_cmux(cg, i); ++ } ++ ++ for (i = 0; i < ARRAY_SIZE(cg->hwaccel); i++) { ++ if (!cg->info.hwaccel[i]) ++ continue; ++ ++ cg->hwaccel[i] = create_one_hwaccel(cg, i); ++ } ++} ++ ++static void __init clockgen_init(struct device_node *np); ++ ++/* Legacy nodes may get probed before the parent clockgen node */ ++static void __init legacy_init_clockgen(struct device_node *np) ++{ ++ if (!clockgen.node) ++ clockgen_init(of_get_parent(np)); ++} ++ ++/* Legacy node */ ++static void __init core_mux_init(struct device_node *np) ++{ ++ struct clk *clk; ++ struct resource res; ++ int idx, rc; ++ ++ legacy_init_clockgen(np); ++ ++ if (of_address_to_resource(np, 0, &res)) ++ return; ++ ++ idx = (res.start & 0xf0) >> 5; ++ clk = clockgen.cmux[idx]; ++ ++ rc = of_clk_add_provider(np, of_clk_src_simple_get, clk); ++ if (rc) { ++ pr_err("%s: Couldn't register clk provider for node %s: %d\n", ++ __func__, np->name, rc); ++ return; ++ } ++} ++ ++static struct clk *sysclk_from_fixed(struct device_node *node, const char *name) ++{ ++ u32 rate; ++ ++ if (of_property_read_u32(node, "clock-frequency", &rate)) ++ return ERR_PTR(-ENODEV); ++ ++ return clk_register_fixed_rate(NULL, name, NULL, CLK_IS_ROOT, rate); ++} ++ ++static struct clk *sysclk_from_parent(const char *name) ++{ ++ struct clk *clk; ++ const char *parent_name; ++ ++ clk = of_clk_get(clockgen.node, 0); ++ if (IS_ERR(clk)) ++ return clk; ++ ++ /* Register the input clock under the desired name. */ ++ parent_name = __clk_get_name(clk); ++ clk = clk_register_fixed_factor(NULL, name, parent_name, ++ 0, 1, 1); ++ if (IS_ERR(clk)) ++ pr_err("%s: Couldn't register %s: %ld\n", __func__, name, ++ PTR_ERR(clk)); ++ ++ return clk; ++} ++ ++static struct clk * __init create_sysclk(const char *name) ++{ ++ struct device_node *sysclk; ++ struct clk *clk; ++ ++ clk = sysclk_from_fixed(clockgen.node, name); ++ if (!IS_ERR(clk)) ++ return clk; ++ ++ clk = sysclk_from_parent(name); ++ if (!IS_ERR(clk)) ++ return clk; ++ ++ sysclk = of_get_child_by_name(clockgen.node, "sysclk"); ++ if (sysclk) { ++ clk = sysclk_from_fixed(sysclk, name); ++ if (!IS_ERR(clk)) ++ return clk; ++ } ++ ++ pr_err("%s: No input clock\n", __func__); ++ return NULL; ++} ++ ++/* Legacy node */ ++static void __init sysclk_init(struct device_node *node) ++{ ++ struct clk *clk; ++ ++ legacy_init_clockgen(node); ++ ++ clk = clockgen.sysclk; ++ if (clk) ++ of_clk_add_provider(node, of_clk_src_simple_get, clk); ++} ++ ++#define PLL_KILL BIT(31) ++ ++static void __init create_one_pll(struct clockgen *cg, int idx) ++{ ++ u32 __iomem *reg; ++ u32 mult; ++ struct clockgen_pll *pll = &cg->pll[idx]; ++ int i; ++ ++ if (!(cg->info.pll_mask & (1 << idx))) ++ return; ++ ++ if (cg->info.flags & CG_VER3) { ++ switch (idx) { ++ case PLATFORM_PLL: ++ reg = cg->regs + 0x60080; ++ break; ++ case CGA_PLL1: ++ reg = cg->regs + 0x80; ++ break; ++ case CGA_PLL2: ++ reg = cg->regs + 0xa0; ++ break; ++ case CGB_PLL1: ++ reg = cg->regs + 0x10080; ++ break; ++ case CGB_PLL2: ++ reg = cg->regs + 0x100a0; ++ break; ++ default: ++ WARN_ONCE(1, "index %d\n", idx); ++ return; ++ } ++ } else { ++ if (idx == PLATFORM_PLL) ++ reg = cg->regs + 0xc00; ++ else ++ reg = cg->regs + 0x800 + 0x20 * (idx - 1); ++ } ++ ++ /* Get the multiple of PLL */ ++ mult = cg_in(cg, reg); ++ ++ /* Check if this PLL is disabled */ ++ if (mult & PLL_KILL) { ++ pr_debug("%s(): pll %p disabled\n", __func__, reg); ++ return; ++ } ++ ++ if ((cg->info.flags & CG_VER3) || ++ ((cg->info.flags & CG_PLL_8BIT) && idx != PLATFORM_PLL)) ++ mult = (mult & GENMASK(8, 1)) >> 1; ++ else ++ mult = (mult & GENMASK(6, 1)) >> 1; ++ ++ for (i = 0; i < ARRAY_SIZE(pll->div); i++) { ++ struct clk *clk; ++ ++ snprintf(pll->div[i].name, sizeof(pll->div[i].name), ++ "cg-pll%d-div%d", idx, i + 1); ++ ++ clk = clk_register_fixed_factor(NULL, ++ pll->div[i].name, "cg-sysclk", 0, mult, i + 1); ++ if (IS_ERR(clk)) { ++ pr_err("%s: %s: register failed %ld\n", ++ __func__, pll->div[i].name, PTR_ERR(clk)); ++ continue; ++ } ++ ++ pll->div[i].clk = clk; ++ } ++} ++ ++static void __init create_plls(struct clockgen *cg) ++{ ++ int i; ++ ++ for (i = 0; i < ARRAY_SIZE(cg->pll); i++) ++ create_one_pll(cg, i); ++} ++ ++static void __init legacy_pll_init(struct device_node *np, int idx) ++{ ++ struct clockgen_pll *pll; ++ struct clk_onecell_data *onecell_data; ++ struct clk **subclks; ++ int count, rc; ++ ++ legacy_init_clockgen(np); ++ ++ pll = &clockgen.pll[idx]; ++ count = of_property_count_strings(np, "clock-output-names"); ++ ++ BUILD_BUG_ON(ARRAY_SIZE(pll->div) < 4); ++ subclks = kcalloc(4, sizeof(struct clk *), GFP_KERNEL); ++ if (!subclks) ++ return; ++ ++ onecell_data = kmalloc(sizeof(*onecell_data), GFP_KERNEL); ++ if (!onecell_data) ++ goto err_clks; ++ ++ if (count <= 3) { ++ subclks[0] = pll->div[0].clk; ++ subclks[1] = pll->div[1].clk; ++ subclks[2] = pll->div[3].clk; ++ } else { ++ subclks[0] = pll->div[0].clk; ++ subclks[1] = pll->div[1].clk; ++ subclks[2] = pll->div[2].clk; ++ subclks[3] = pll->div[3].clk; ++ } ++ ++ onecell_data->clks = subclks; ++ onecell_data->clk_num = count; ++ ++ rc = of_clk_add_provider(np, of_clk_src_onecell_get, onecell_data); ++ if (rc) { ++ pr_err("%s: Couldn't register clk provider for node %s: %d\n", ++ __func__, np->name, rc); ++ goto err_cell; ++ } ++ ++ return; ++err_cell: ++ kfree(onecell_data); ++err_clks: ++ kfree(subclks); ++} ++ ++/* Legacy node */ ++static void __init pltfrm_pll_init(struct device_node *np) ++{ ++ legacy_pll_init(np, PLATFORM_PLL); ++} ++ ++/* Legacy node */ ++static void __init core_pll_init(struct device_node *np) ++{ ++ struct resource res; ++ int idx; ++ ++ if (of_address_to_resource(np, 0, &res)) ++ return; ++ ++ if ((res.start & 0xfff) == 0xc00) { ++ /* ++ * ls1021a devtree labels the platform PLL ++ * with the core PLL compatible ++ */ ++ pltfrm_pll_init(np); ++ } else { ++ idx = (res.start & 0xf0) >> 5; ++ legacy_pll_init(np, CGA_PLL1 + idx); ++ } ++} ++ ++static struct clk *clockgen_clk_get(struct of_phandle_args *clkspec, void *data) ++{ ++ struct clockgen *cg = data; ++ struct clk *clk; ++ struct clockgen_pll *pll; ++ u32 type, idx; ++ ++ if (clkspec->args_count < 2) { ++ pr_err("%s: insufficient phandle args\n", __func__); ++ return ERR_PTR(-EINVAL); ++ } ++ ++ type = clkspec->args[0]; ++ idx = clkspec->args[1]; ++ ++ switch (type) { ++ case 0: ++ if (idx != 0) ++ goto bad_args; ++ clk = cg->sysclk; ++ break; ++ case 1: ++ if (idx >= ARRAY_SIZE(cg->cmux)) ++ goto bad_args; ++ clk = cg->cmux[idx]; ++ break; ++ case 2: ++ if (idx >= ARRAY_SIZE(cg->hwaccel)) ++ goto bad_args; ++ clk = cg->hwaccel[idx]; ++ break; ++ case 3: ++ if (idx >= ARRAY_SIZE(cg->fman)) ++ goto bad_args; ++ clk = cg->fman[idx]; ++ break; ++ case 4: ++ pll = &cg->pll[PLATFORM_PLL]; ++ if (idx >= ARRAY_SIZE(pll->div)) ++ goto bad_args; ++ clk = pll->div[idx].clk; ++ break; ++ default: ++ goto bad_args; ++ } ++ ++ if (!clk) ++ return ERR_PTR(-ENOENT); ++ return clk; ++ ++bad_args: ++ pr_err("%s: Bad phandle args %u %u\n", __func__, type, idx); ++ return ERR_PTR(-EINVAL); ++} ++ ++#ifdef CONFIG_PPC ++ ++static const u32 a4510_svrs[] __initconst = { ++ (SVR_P2040 << 8) | 0x10, /* P2040 1.0 */ ++ (SVR_P2040 << 8) | 0x11, /* P2040 1.1 */ ++ (SVR_P2041 << 8) | 0x10, /* P2041 1.0 */ ++ (SVR_P2041 << 8) | 0x11, /* P2041 1.1 */ ++ (SVR_P3041 << 8) | 0x10, /* P3041 1.0 */ ++ (SVR_P3041 << 8) | 0x11, /* P3041 1.1 */ ++ (SVR_P4040 << 8) | 0x20, /* P4040 2.0 */ ++ (SVR_P4080 << 8) | 0x20, /* P4080 2.0 */ ++ (SVR_P5010 << 8) | 0x10, /* P5010 1.0 */ ++ (SVR_P5010 << 8) | 0x20, /* P5010 2.0 */ ++ (SVR_P5020 << 8) | 0x10, /* P5020 1.0 */ ++ (SVR_P5021 << 8) | 0x10, /* P5021 1.0 */ ++ (SVR_P5040 << 8) | 0x10, /* P5040 1.0 */ ++}; ++ ++#define SVR_SECURITY 0x80000 /* The Security (E) bit */ ++ ++static bool __init has_erratum_a4510(void) ++{ ++ u32 svr = mfspr(SPRN_SVR); ++ int i; ++ ++ svr &= ~SVR_SECURITY; ++ ++ for (i = 0; i < ARRAY_SIZE(a4510_svrs); i++) { ++ if (svr == a4510_svrs[i]) ++ return true; ++ } ++ ++ return false; ++} ++#else ++static bool __init has_erratum_a4510(void) ++{ ++ return false; ++} ++#endif ++ ++static void __init clockgen_init(struct device_node *np) ++{ ++ int i, ret; ++ bool is_old_ls1021a = false; ++ ++ /* May have already been called by a legacy probe */ ++ if (clockgen.node) ++ return; ++ ++ clockgen.node = np; ++ clockgen.regs = of_iomap(np, 0); ++ if (!clockgen.regs && ++ of_device_is_compatible(of_root, "fsl,ls1021a")) { ++ /* Compatibility hack for old, broken device trees */ ++ clockgen.regs = ioremap(0x1ee1000, 0x1000); ++ is_old_ls1021a = true; ++ } ++ if (!clockgen.regs) { ++ pr_err("%s(): %s: of_iomap() failed\n", __func__, np->name); ++ return; ++ } ++ ++ for (i = 0; i < ARRAY_SIZE(chipinfo); i++) { ++ if (of_device_is_compatible(np, chipinfo[i].compat)) ++ break; ++ if (is_old_ls1021a && ++ !strcmp(chipinfo[i].compat, "fsl,ls1021a-clockgen")) ++ break; ++ } ++ ++ if (i == ARRAY_SIZE(chipinfo)) { ++ pr_err("%s: unknown clockgen node %s\n", __func__, ++ np->full_name); ++ goto err; ++ } ++ clockgen.info = chipinfo[i]; ++ ++ if (clockgen.info.guts_compat) { ++ struct device_node *guts; ++ ++ guts = of_find_compatible_node(NULL, NULL, ++ clockgen.info.guts_compat); ++ if (guts) { ++ clockgen.guts = of_iomap(guts, 0); ++ if (!clockgen.guts) { ++ pr_err("%s: Couldn't map %s regs\n", __func__, ++ guts->full_name); ++ } ++ } ++ ++ } ++ ++ if (has_erratum_a4510()) ++ clockgen.info.flags |= CG_CMUX_GE_PLAT; ++ ++ clockgen.sysclk = create_sysclk("cg-sysclk"); ++ create_plls(&clockgen); ++ create_muxes(&clockgen); ++ ++ if (clockgen.info.init_periph) ++ clockgen.info.init_periph(&clockgen); ++ ++ ret = of_clk_add_provider(np, clockgen_clk_get, &clockgen); ++ if (ret) { ++ pr_err("%s: Couldn't register clk provider for node %s: %d\n", ++ __func__, np->name, ret); ++ } ++ ++ return; ++err: ++ iounmap(clockgen.regs); ++ clockgen.regs = NULL; ++} ++ ++CLK_OF_DECLARE(qoriq_clockgen_1, "fsl,qoriq-clockgen-1.0", clockgen_init); ++CLK_OF_DECLARE(qoriq_clockgen_2, "fsl,qoriq-clockgen-2.0", clockgen_init); ++CLK_OF_DECLARE(qoriq_clockgen_ls1021a, "fsl,ls1021a-clockgen", clockgen_init); ++CLK_OF_DECLARE(qoriq_clockgen_ls2080a, "fsl,ls2080a-clockgen", clockgen_init); ++CLK_OF_DECLARE(qoriq_clockgen_ls2088a, "fsl,ls2088a-clockgen", clockgen_init); ++ ++/* Legacy nodes */ ++CLK_OF_DECLARE(qoriq_sysclk_1, "fsl,qoriq-sysclk-1.0", sysclk_init); ++CLK_OF_DECLARE(qoriq_sysclk_2, "fsl,qoriq-sysclk-2.0", sysclk_init); ++CLK_OF_DECLARE(qoriq_core_pll_1, "fsl,qoriq-core-pll-1.0", core_pll_init); ++CLK_OF_DECLARE(qoriq_core_pll_2, "fsl,qoriq-core-pll-2.0", core_pll_init); ++CLK_OF_DECLARE(qoriq_core_mux_1, "fsl,qoriq-core-mux-1.0", core_mux_init); ++CLK_OF_DECLARE(qoriq_core_mux_2, "fsl,qoriq-core-mux-2.0", core_mux_init); ++CLK_OF_DECLARE(qoriq_pltfrm_pll_1, "fsl,qoriq-platform-pll-1.0", pltfrm_pll_init); ++CLK_OF_DECLARE(qoriq_pltfrm_pll_2, "fsl,qoriq-platform-pll-2.0", pltfrm_pll_init); +diff --git a/drivers/cpufreq/Kconfig.powerpc b/drivers/cpufreq/Kconfig.powerpc +index 72564b7..7ea2441 100644 +--- a/drivers/cpufreq/Kconfig.powerpc ++++ b/drivers/cpufreq/Kconfig.powerpc +@@ -26,7 +26,7 @@ config CPU_FREQ_MAPLE + config PPC_CORENET_CPUFREQ + tristate "CPU frequency scaling driver for Freescale E500MC SoCs" + depends on PPC_E500MC && OF && COMMON_CLK +- select CLK_PPC_CORENET ++ select CLK_QORIQ + help + This adds the CPUFreq driver support for Freescale e500mc, + e5500 and e6500 series SoCs which are capable of changing +diff --git a/drivers/dma/acpi-dma.c b/drivers/dma/acpi-dma.c +index de361a1..5a63564 100644 +--- a/drivers/dma/acpi-dma.c ++++ b/drivers/dma/acpi-dma.c +@@ -43,7 +43,7 @@ static int acpi_dma_parse_resource_group(const struct acpi_csrt_group *grp, + { + const struct acpi_csrt_shared_info *si; + struct list_head resource_list; +- struct resource_list_entry *rentry; ++ struct resource_entry *rentry; + resource_size_t mem = 0, irq = 0; + int ret; + +@@ -56,10 +56,10 @@ static int acpi_dma_parse_resource_group(const struct acpi_csrt_group *grp, + return 0; + + list_for_each_entry(rentry, &resource_list, node) { +- if (resource_type(&rentry->res) == IORESOURCE_MEM) +- mem = rentry->res.start; +- else if (resource_type(&rentry->res) == IORESOURCE_IRQ) +- irq = rentry->res.start; ++ if (resource_type(rentry->res) == IORESOURCE_MEM) ++ mem = rentry->res->start; ++ else if (resource_type(rentry->res) == IORESOURCE_IRQ) ++ irq = rentry->res->start; + } + + acpi_dev_free_resource_list(&resource_list); +diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig +index 06e99eb..bbf8ae4 100644 +--- a/drivers/i2c/busses/Kconfig ++++ b/drivers/i2c/busses/Kconfig +@@ -526,10 +526,10 @@ config I2C_IBM_IIC + + config I2C_IMX + tristate "IMX I2C interface" +- depends on ARCH_MXC ++ depends on ARCH_MXC || ARCH_LAYERSCAPE + help + Say Y here if you want to use the IIC bus controller on +- the Freescale i.MX/MXC processors. ++ the Freescale i.MX/MXC and layerscape processors. + + This driver can also be built as a module. If so, the module + will be called i2c-imx. +diff --git a/drivers/i2c/busses/i2c-imx.c b/drivers/i2c/busses/i2c-imx.c +index e9fb7cf..13f88f9 100644 +--- a/drivers/i2c/busses/i2c-imx.c ++++ b/drivers/i2c/busses/i2c-imx.c +@@ -33,6 +33,10 @@ + *******************************************************************************/ + + #include ++#include ++#include ++#include ++#include + #include + #include + #include +@@ -47,6 +51,7 @@ + #include + #include + #include ++#include + #include + + /** Defines ******************************************************************** +@@ -58,6 +63,15 @@ + /* Default value */ + #define IMX_I2C_BIT_RATE 100000 /* 100kHz */ + ++/* ++ * Enable DMA if transfer byte size is bigger than this threshold. ++ * As the hardware request, it must bigger than 4 bytes.\ ++ * I have set '16' here, maybe it's not the best but I think it's ++ * the appropriate. ++ */ ++#define DMA_THRESHOLD 16 ++#define DMA_TIMEOUT 1000 ++ + /* IMX I2C registers: + * the I2C register offset is different between SoCs, + * to provid support for all these chips, split the +@@ -83,6 +97,7 @@ + #define I2SR_IBB 0x20 + #define I2SR_IAAS 0x40 + #define I2SR_ICF 0x80 ++#define I2CR_DMAEN 0x02 + #define I2CR_RSTA 0x04 + #define I2CR_TXAK 0x08 + #define I2CR_MTX 0x10 +@@ -169,6 +184,17 @@ struct imx_i2c_hwdata { + unsigned i2cr_ien_opcode; + }; + ++struct imx_i2c_dma { ++ struct dma_chan *chan_tx; ++ struct dma_chan *chan_rx; ++ struct dma_chan *chan_using; ++ struct completion cmd_complete; ++ dma_addr_t dma_buf; ++ unsigned int dma_len; ++ enum dma_transfer_direction dma_transfer_dir; ++ enum dma_data_direction dma_data_dir; ++}; ++ + struct imx_i2c_struct { + struct i2c_adapter adapter; + struct clk *clk; +@@ -181,6 +207,8 @@ struct imx_i2c_struct { + unsigned int cur_clk; + unsigned int bitrate; + const struct imx_i2c_hwdata *hwdata; ++ ++ struct imx_i2c_dma *dma; + }; + + static const struct imx_i2c_hwdata imx1_i2c_hwdata = { +@@ -251,6 +279,162 @@ static inline unsigned char imx_i2c_read_reg(struct imx_i2c_struct *i2c_imx, + return readb(i2c_imx->base + (reg << i2c_imx->hwdata->regshift)); + } + ++/* Functions for DMA support */ ++static void i2c_imx_dma_request(struct imx_i2c_struct *i2c_imx, ++ dma_addr_t phy_addr) ++{ ++ struct imx_i2c_dma *dma; ++ struct dma_slave_config dma_sconfig; ++ struct device *dev = &i2c_imx->adapter.dev; ++ int ret; ++ ++ dma = devm_kzalloc(dev, sizeof(*dma), GFP_KERNEL); ++ if (!dma) ++ return; ++ ++ dma->chan_tx = dma_request_slave_channel(dev, "tx"); ++ if (!dma->chan_tx) { ++ dev_dbg(dev, "can't request DMA tx channel\n"); ++ goto fail_al; ++ } ++ ++ dma_sconfig.dst_addr = phy_addr + ++ (IMX_I2C_I2DR << i2c_imx->hwdata->regshift); ++ dma_sconfig.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE; ++ dma_sconfig.dst_maxburst = 1; ++ dma_sconfig.direction = DMA_MEM_TO_DEV; ++ ret = dmaengine_slave_config(dma->chan_tx, &dma_sconfig); ++ if (ret < 0) { ++ dev_dbg(dev, "can't configure tx channel\n"); ++ goto fail_tx; ++ } ++ ++ dma->chan_rx = dma_request_slave_channel(dev, "rx"); ++ if (!dma->chan_rx) { ++ dev_dbg(dev, "can't request DMA rx channel\n"); ++ goto fail_tx; ++ } ++ ++ dma_sconfig.src_addr = phy_addr + ++ (IMX_I2C_I2DR << i2c_imx->hwdata->regshift); ++ dma_sconfig.src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE; ++ dma_sconfig.src_maxburst = 1; ++ dma_sconfig.direction = DMA_DEV_TO_MEM; ++ ret = dmaengine_slave_config(dma->chan_rx, &dma_sconfig); ++ if (ret < 0) { ++ dev_dbg(dev, "can't configure rx channel\n"); ++ goto fail_rx; ++ } ++ ++ i2c_imx->dma = dma; ++ init_completion(&dma->cmd_complete); ++ dev_info(dev, "using %s (tx) and %s (rx) for DMA transfers\n", ++ dma_chan_name(dma->chan_tx), dma_chan_name(dma->chan_rx)); ++ ++ return; ++ ++fail_rx: ++ dma_release_channel(dma->chan_rx); ++fail_tx: ++ dma_release_channel(dma->chan_tx); ++fail_al: ++ devm_kfree(dev, dma); ++ dev_info(dev, "can't use DMA\n"); ++} ++ ++static void i2c_imx_dma_callback(void *arg) ++{ ++ struct imx_i2c_struct *i2c_imx = (struct imx_i2c_struct *)arg; ++ struct imx_i2c_dma *dma = i2c_imx->dma; ++ ++ dma_unmap_single(dma->chan_using->device->dev, dma->dma_buf, ++ dma->dma_len, dma->dma_data_dir); ++ complete(&dma->cmd_complete); ++} ++ ++static int i2c_imx_dma_xfer(struct imx_i2c_struct *i2c_imx, ++ struct i2c_msg *msgs) ++{ ++ struct imx_i2c_dma *dma = i2c_imx->dma; ++ struct dma_async_tx_descriptor *txdesc; ++ struct device *dev = &i2c_imx->adapter.dev; ++ struct device *chan_dev = dma->chan_using->device->dev; ++ ++ dma->dma_buf = dma_map_single(chan_dev, msgs->buf, ++ dma->dma_len, dma->dma_data_dir); ++ if (dma_mapping_error(chan_dev, dma->dma_buf)) { ++ dev_err(dev, "DMA mapping failed\n"); ++ goto err_map; ++ } ++ ++ txdesc = dmaengine_prep_slave_single(dma->chan_using, dma->dma_buf, ++ dma->dma_len, dma->dma_transfer_dir, ++ DMA_PREP_INTERRUPT | DMA_CTRL_ACK); ++ if (!txdesc) { ++ dev_err(dev, "Not able to get desc for DMA xfer\n"); ++ goto err_desc; ++ } ++ ++ txdesc->callback = i2c_imx_dma_callback; ++ txdesc->callback_param = i2c_imx; ++ if (dma_submit_error(dmaengine_submit(txdesc))) { ++ dev_err(dev, "DMA submit failed\n"); ++ goto err_submit; ++ } ++ ++ dma_async_issue_pending(dma->chan_using); ++ return 0; ++ ++err_submit: ++err_desc: ++ dma_unmap_single(chan_dev, dma->dma_buf, ++ dma->dma_len, dma->dma_data_dir); ++err_map: ++ return -EINVAL; ++} ++ ++static void i2c_imx_dma_free(struct imx_i2c_struct *i2c_imx) ++{ ++ struct imx_i2c_dma *dma = i2c_imx->dma; ++ ++ dma->dma_buf = 0; ++ dma->dma_len = 0; ++ ++ dma_release_channel(dma->chan_tx); ++ dma->chan_tx = NULL; ++ ++ dma_release_channel(dma->chan_rx); ++ dma->chan_rx = NULL; ++ ++ dma->chan_using = NULL; ++} ++ ++/* ++ * When a system reset does not cause all I2C devices to be reset, it is ++ * sometimes necessary to force the I2C module to become the I2C bus master ++ * out of reset and drive SCL A slave can hold bus low to cause bus hang. ++ * Thus, SDA can be driven low by another I2C device while this I2C module ++ * is coming out of reset and will stay low indefinitely. ++ * The I2C master has to generate 9 clock pulses to get the bus free or idle. ++ */ ++static void imx_i2c_fixup(struct imx_i2c_struct *i2c_imx) ++{ ++ int k; ++ u32 delay_val = 1000000 / i2c_imx->cur_clk + 1; ++ ++ if (delay_val < 2) ++ delay_val = 2; ++ ++ for (k = 9; k; k--) { ++ imx_i2c_write_reg(I2CR_IEN, i2c_imx, IMX_I2C_I2CR); ++ imx_i2c_write_reg((I2CR_MSTA | I2CR_MTX) & (~I2CR_IEN), ++ i2c_imx, IMX_I2C_I2CR); ++ imx_i2c_read_reg(i2c_imx, IMX_I2C_I2DR); ++ imx_i2c_write_reg(0, i2c_imx, IMX_I2C_I2CR); ++ udelay(delay_val << 1); ++ } ++} ++ + /** Functions for IMX I2C adapter driver *************************************** + *******************************************************************************/ + +@@ -276,8 +460,15 @@ static int i2c_imx_bus_busy(struct imx_i2c_struct *i2c_imx, int for_busy) + if (!for_busy && !(temp & I2SR_IBB)) + break; + if (time_after(jiffies, orig_jiffies + msecs_to_jiffies(500))) { ++ u8 status = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2SR); ++ + dev_dbg(&i2c_imx->adapter.dev, + "<%s> I2C bus is busy\n", __func__); ++ if ((status & (I2SR_ICF | I2SR_IBB | I2CR_TXAK)) != 0) { ++ imx_i2c_write_reg(status & ~I2SR_IAL, i2c_imx, ++ IMX_I2C_I2CR); ++ imx_i2c_fixup(i2c_imx); ++ } + return -ETIMEDOUT; + } + schedule(); +@@ -382,6 +573,7 @@ static int i2c_imx_start(struct imx_i2c_struct *i2c_imx) + i2c_imx->stopped = 0; + + temp |= I2CR_IIEN | I2CR_MTX | I2CR_TXAK; ++ temp &= ~I2CR_DMAEN; + imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR); + return result; + } +@@ -395,6 +587,8 @@ static void i2c_imx_stop(struct imx_i2c_struct *i2c_imx) + dev_dbg(&i2c_imx->adapter.dev, "<%s>\n", __func__); + temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR); + temp &= ~(I2CR_MSTA | I2CR_MTX); ++ if (i2c_imx->dma) ++ temp &= ~I2CR_DMAEN; + imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR); + } + if (is_imx1_i2c(i2c_imx)) { +@@ -435,6 +629,157 @@ static irqreturn_t i2c_imx_isr(int irq, void *dev_id) + return IRQ_NONE; + } + ++static int i2c_imx_dma_write(struct imx_i2c_struct *i2c_imx, ++ struct i2c_msg *msgs) ++{ ++ int result; ++ unsigned long time_left; ++ unsigned int temp = 0; ++ unsigned long orig_jiffies = jiffies; ++ struct imx_i2c_dma *dma = i2c_imx->dma; ++ struct device *dev = &i2c_imx->adapter.dev; ++ ++ dma->chan_using = dma->chan_tx; ++ dma->dma_transfer_dir = DMA_MEM_TO_DEV; ++ dma->dma_data_dir = DMA_TO_DEVICE; ++ dma->dma_len = msgs->len - 1; ++ result = i2c_imx_dma_xfer(i2c_imx, msgs); ++ if (result) ++ return result; ++ ++ temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR); ++ temp |= I2CR_DMAEN; ++ imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR); ++ ++ /* ++ * Write slave address. ++ * The first byte must be transmitted by the CPU. ++ */ ++ imx_i2c_write_reg(msgs->addr << 1, i2c_imx, IMX_I2C_I2DR); ++ reinit_completion(&i2c_imx->dma->cmd_complete); ++ time_left = wait_for_completion_timeout( ++ &i2c_imx->dma->cmd_complete, ++ msecs_to_jiffies(DMA_TIMEOUT)); ++ if (time_left == 0) { ++ dmaengine_terminate_all(dma->chan_using); ++ return -ETIMEDOUT; ++ } ++ ++ /* Waiting for transfer complete. */ ++ while (1) { ++ temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2SR); ++ if (temp & I2SR_ICF) ++ break; ++ if (time_after(jiffies, orig_jiffies + ++ msecs_to_jiffies(DMA_TIMEOUT))) { ++ dev_dbg(dev, "<%s> Timeout\n", __func__); ++ return -ETIMEDOUT; ++ } ++ schedule(); ++ } ++ ++ temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR); ++ temp &= ~I2CR_DMAEN; ++ imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR); ++ ++ /* The last data byte must be transferred by the CPU. */ ++ imx_i2c_write_reg(msgs->buf[msgs->len-1], ++ i2c_imx, IMX_I2C_I2DR); ++ result = i2c_imx_trx_complete(i2c_imx); ++ if (result) ++ return result; ++ ++ return i2c_imx_acked(i2c_imx); ++} ++ ++static int i2c_imx_dma_read(struct imx_i2c_struct *i2c_imx, ++ struct i2c_msg *msgs, bool is_lastmsg) ++{ ++ int result; ++ unsigned long time_left; ++ unsigned int temp; ++ unsigned long orig_jiffies = jiffies; ++ struct imx_i2c_dma *dma = i2c_imx->dma; ++ struct device *dev = &i2c_imx->adapter.dev; ++ ++ temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR); ++ temp |= I2CR_DMAEN; ++ imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR); ++ ++ dma->chan_using = dma->chan_rx; ++ dma->dma_transfer_dir = DMA_DEV_TO_MEM; ++ dma->dma_data_dir = DMA_FROM_DEVICE; ++ /* The last two data bytes must be transferred by the CPU. */ ++ dma->dma_len = msgs->len - 2; ++ result = i2c_imx_dma_xfer(i2c_imx, msgs); ++ if (result) ++ return result; ++ ++ reinit_completion(&i2c_imx->dma->cmd_complete); ++ time_left = wait_for_completion_timeout( ++ &i2c_imx->dma->cmd_complete, ++ msecs_to_jiffies(DMA_TIMEOUT)); ++ if (time_left == 0) { ++ dmaengine_terminate_all(dma->chan_using); ++ return -ETIMEDOUT; ++ } ++ ++ /* waiting for transfer complete. */ ++ while (1) { ++ temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2SR); ++ if (temp & I2SR_ICF) ++ break; ++ if (time_after(jiffies, orig_jiffies + ++ msecs_to_jiffies(DMA_TIMEOUT))) { ++ dev_dbg(dev, "<%s> Timeout\n", __func__); ++ return -ETIMEDOUT; ++ } ++ schedule(); ++ } ++ ++ temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR); ++ temp &= ~I2CR_DMAEN; ++ imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR); ++ ++ /* read n-1 byte data */ ++ temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR); ++ temp |= I2CR_TXAK; ++ imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR); ++ ++ msgs->buf[msgs->len-2] = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2DR); ++ /* read n byte data */ ++ result = i2c_imx_trx_complete(i2c_imx); ++ if (result) ++ return result; ++ ++ if (is_lastmsg) { ++ /* ++ * It must generate STOP before read I2DR to prevent ++ * controller from generating another clock cycle ++ */ ++ dev_dbg(dev, "<%s> clear MSTA\n", __func__); ++ temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR); ++ temp &= ~(I2CR_MSTA | I2CR_MTX); ++ imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR); ++ i2c_imx_bus_busy(i2c_imx, 0); ++ i2c_imx->stopped = 1; ++ } else { ++ /* ++ * For i2c master receiver repeat restart operation like: ++ * read -> repeat MSTA -> read/write ++ * The controller must set MTX before read the last byte in ++ * the first read operation, otherwise the first read cost ++ * one extra clock cycle. ++ */ ++ temp = readb(i2c_imx->base + IMX_I2C_I2CR); ++ temp |= I2CR_MTX; ++ writeb(temp, i2c_imx->base + IMX_I2C_I2CR); ++ } ++ msgs->buf[msgs->len-1] = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2DR); ++ ++ return 0; ++} ++ + static int i2c_imx_write(struct imx_i2c_struct *i2c_imx, struct i2c_msg *msgs) + { + int i, result; +@@ -504,6 +849,9 @@ static int i2c_imx_read(struct imx_i2c_struct *i2c_imx, struct i2c_msg *msgs, bo + + dev_dbg(&i2c_imx->adapter.dev, "<%s> read data\n", __func__); + ++ if (i2c_imx->dma && msgs->len >= DMA_THRESHOLD && !block_data) ++ return i2c_imx_dma_read(i2c_imx, msgs, is_lastmsg); ++ + /* read data */ + for (i = 0; i < msgs->len; i++) { + u8 len = 0; +@@ -577,6 +925,13 @@ static int i2c_imx_xfer(struct i2c_adapter *adapter, + + dev_dbg(&i2c_imx->adapter.dev, "<%s>\n", __func__); + ++ /* workround for ERR010027: ensure that the I2C BUS is idle ++ before switching to master mode and attempting a Start cycle ++ */ ++ result = i2c_imx_bus_busy(i2c_imx, 0); ++ if (result) ++ goto fail0; ++ + /* Start I2C transfer */ + result = i2c_imx_start(i2c_imx); + if (result) +@@ -618,8 +973,12 @@ static int i2c_imx_xfer(struct i2c_adapter *adapter, + #endif + if (msgs[i].flags & I2C_M_RD) + result = i2c_imx_read(i2c_imx, &msgs[i], is_lastmsg); +- else +- result = i2c_imx_write(i2c_imx, &msgs[i]); ++ else { ++ if (i2c_imx->dma && msgs[i].len >= DMA_THRESHOLD) ++ result = i2c_imx_dma_write(i2c_imx, &msgs[i]); ++ else ++ result = i2c_imx_write(i2c_imx, &msgs[i]); ++ } + if (result) + goto fail0; + } +@@ -654,6 +1013,7 @@ static int i2c_imx_probe(struct platform_device *pdev) + struct imxi2c_platform_data *pdata = dev_get_platdata(&pdev->dev); + void __iomem *base; + int irq, ret; ++ dma_addr_t phy_addr; + + dev_dbg(&pdev->dev, "<%s>\n", __func__); + +@@ -668,6 +1028,7 @@ static int i2c_imx_probe(struct platform_device *pdev) + if (IS_ERR(base)) + return PTR_ERR(base); + ++ phy_addr = (dma_addr_t)res->start; + i2c_imx = devm_kzalloc(&pdev->dev, sizeof(struct imx_i2c_struct), + GFP_KERNEL); + if (!i2c_imx) +@@ -701,7 +1062,7 @@ static int i2c_imx_probe(struct platform_device *pdev) + return ret; + } + /* Request IRQ */ +- ret = devm_request_irq(&pdev->dev, irq, i2c_imx_isr, 0, ++ ret = devm_request_irq(&pdev->dev, irq, i2c_imx_isr, IRQF_SHARED, + pdev->name, i2c_imx); + if (ret) { + dev_err(&pdev->dev, "can't claim irq %d\n", irq); +@@ -743,6 +1104,9 @@ static int i2c_imx_probe(struct platform_device *pdev) + i2c_imx->adapter.name); + dev_info(&i2c_imx->adapter.dev, "IMX I2C adapter registered\n"); + ++ /* Init DMA config if support*/ ++ i2c_imx_dma_request(i2c_imx, phy_addr); ++ + return 0; /* Return OK */ + + clk_disable: +@@ -758,6 +1122,9 @@ static int i2c_imx_remove(struct platform_device *pdev) + dev_dbg(&i2c_imx->adapter.dev, "adapter removed\n"); + i2c_del_adapter(&i2c_imx->adapter); + ++ if (i2c_imx->dma) ++ i2c_imx_dma_free(i2c_imx); ++ + /* setup chip registers to defaults */ + imx_i2c_write_reg(0, i2c_imx, IMX_I2C_IADR); + imx_i2c_write_reg(0, i2c_imx, IMX_I2C_IFDR); +diff --git a/drivers/i2c/muxes/i2c-mux-pca9541.c b/drivers/i2c/muxes/i2c-mux-pca9541.c +index cb77277..0c8d4d2 100644 +--- a/drivers/i2c/muxes/i2c-mux-pca9541.c ++++ b/drivers/i2c/muxes/i2c-mux-pca9541.c +@@ -104,7 +104,7 @@ static int pca9541_reg_write(struct i2c_client *client, u8 command, u8 val) + buf[0] = command; + buf[1] = val; + msg.buf = buf; +- ret = adap->algo->master_xfer(adap, &msg, 1); ++ ret = __i2c_transfer(adap, &msg, 1); + } else { + union i2c_smbus_data data; + +@@ -144,7 +144,7 @@ static int pca9541_reg_read(struct i2c_client *client, u8 command) + .buf = &val + } + }; +- ret = adap->algo->master_xfer(adap, msg, 2); ++ ret = __i2c_transfer(adap, msg, 2); + if (ret == 2) + ret = val; + else if (ret >= 0) +diff --git a/drivers/i2c/muxes/i2c-mux-pca954x.c b/drivers/i2c/muxes/i2c-mux-pca954x.c +index ec11b40..28540a4 100644 +--- a/drivers/i2c/muxes/i2c-mux-pca954x.c ++++ b/drivers/i2c/muxes/i2c-mux-pca954x.c +@@ -41,6 +41,7 @@ + #include + #include + #include ++#include + #include + #include + +@@ -62,6 +63,7 @@ struct pca954x { + struct i2c_adapter *virt_adaps[PCA954X_MAX_NCHANS]; + + u8 last_chan; /* last register value */ ++ u8 disable_mux; /* do not disable mux if val not 0 */ + }; + + struct chip_desc { +@@ -133,7 +135,7 @@ static int pca954x_reg_write(struct i2c_adapter *adap, + msg.len = 1; + buf[0] = val; + msg.buf = buf; +- ret = adap->algo->master_xfer(adap, &msg, 1); ++ ret = __i2c_transfer(adap, &msg, 1); + } else { + union i2c_smbus_data data; + ret = adap->algo->smbus_xfer(adap, client->addr, +@@ -173,6 +175,13 @@ static int pca954x_deselect_mux(struct i2c_adapter *adap, + { + struct pca954x *data = i2c_get_clientdata(client); + ++#ifdef CONFIG_ARCH_LAYERSCAPE ++ if (data->disable_mux != 0) ++ data->last_chan = chips[data->type].nchans; ++ else ++ data->last_chan = 0; ++ return pca954x_reg_write(adap, client, data->disable_mux); ++#endif + /* Deselect active channel */ + data->last_chan = 0; + return pca954x_reg_write(adap, client, data->last_chan); +@@ -186,6 +195,8 @@ static int pca954x_probe(struct i2c_client *client, + { + struct i2c_adapter *adap = to_i2c_adapter(client->dev.parent); + struct pca954x_platform_data *pdata = dev_get_platdata(&client->dev); ++ struct device_node *of_node = client->dev.of_node; ++ bool idle_disconnect_dt; + struct gpio_desc *gpio; + int num, force, class; + struct pca954x *data; +@@ -198,27 +209,55 @@ static int pca954x_probe(struct i2c_client *client, + if (!data) + return -ENOMEM; + ++#ifdef CONFIG_ARCH_LAYERSCAPE ++ /* The point here is that you must not disable a mux if there ++ * are no pullups on the input or you mess up the I2C. This ++ * needs to be put into the DTS really as the kernel cannot ++ * know this otherwise. ++ */ ++ data->type = id->driver_data; ++ data->disable_mux = of_node && ++ of_property_read_bool(of_node, "i2c-mux-never-disable") && ++ chips[data->type].muxtype == pca954x_ismux ? ++ chips[data->type].enable : 0; ++ /* force the first selection */ ++ if (data->disable_mux != 0) ++ data->last_chan = chips[data->type].nchans; ++ else ++ data->last_chan = 0; ++#endif + i2c_set_clientdata(client, data); + + /* Get the mux out of reset if a reset GPIO is specified. */ +- gpio = devm_gpiod_get(&client->dev, "reset"); +- if (!IS_ERR(gpio)) +- gpiod_direction_output(gpio, 0); ++ gpio = devm_gpiod_get_optional(&client->dev, "reset", GPIOD_OUT_LOW); ++ if (IS_ERR(gpio)) ++ return PTR_ERR(gpio); + + /* Write the mux register at addr to verify + * that the mux is in fact present. This also + * initializes the mux to disconnected state. + */ ++#ifdef CONFIG_ARCH_LAYERSCAPE ++ if (i2c_smbus_write_byte(client, data->disable_mux) < 0) { ++#else + if (i2c_smbus_write_byte(client, 0) < 0) { ++#endif + dev_warn(&client->dev, "probe failed\n"); + return -ENODEV; + } + ++#ifndef CONFIG_ARCH_LAYERSCAPE + data->type = id->driver_data; + data->last_chan = 0; /* force the first selection */ ++#endif ++ ++ idle_disconnect_dt = of_node && ++ of_property_read_bool(of_node, "i2c-mux-idle-disconnect"); + + /* Now create an adapter for each channel */ + for (num = 0; num < chips[data->type].nchans; num++) { ++ bool idle_disconnect_pd = false; ++ + force = 0; /* dynamic adap number */ + class = 0; /* no class by default */ + if (pdata) { +@@ -229,12 +268,13 @@ static int pca954x_probe(struct i2c_client *client, + } else + /* discard unconfigured channels */ + break; ++ idle_disconnect_pd = pdata->modes[num].deselect_on_exit; + } + + data->virt_adaps[num] = + i2c_add_mux_adapter(adap, &client->dev, client, + force, num, class, pca954x_select_chan, +- (pdata && pdata->modes[num].deselect_on_exit) ++ (idle_disconnect_pd || idle_disconnect_dt) + ? pca954x_deselect_mux : NULL); + + if (data->virt_adaps[num] == NULL) { +@@ -280,6 +320,13 @@ static int pca954x_resume(struct device *dev) + struct i2c_client *client = to_i2c_client(dev); + struct pca954x *data = i2c_get_clientdata(client); + ++#ifdef CONFIG_ARCH_LAYERSCAPE ++ if (data->disable_mux != 0) ++ data->last_chan = chips[data->type].nchans; ++ else ++ data->last_chan = 0; ++ return i2c_smbus_write_byte(client, data->disable_mux); ++#endif + data->last_chan = 0; + return i2c_smbus_write_byte(client, 0); + } +diff --git a/drivers/iommu/Kconfig b/drivers/iommu/Kconfig +index dd51122..2cdcc76 100644 +--- a/drivers/iommu/Kconfig ++++ b/drivers/iommu/Kconfig +@@ -13,9 +13,35 @@ menuconfig IOMMU_SUPPORT + + if IOMMU_SUPPORT + ++menu "Generic IOMMU Pagetable Support" ++ ++# Selected by the actual pagetable implementations ++config IOMMU_IO_PGTABLE ++ bool ++ ++config IOMMU_IO_PGTABLE_LPAE ++ bool "ARMv7/v8 Long Descriptor Format" ++ select IOMMU_IO_PGTABLE ++ help ++ Enable support for the ARM long descriptor pagetable format. ++ This allocator supports 4K/2M/1G, 16K/32M and 64K/512M page ++ sizes at both stage-1 and stage-2, as well as address spaces ++ up to 48-bits in size. ++ ++config IOMMU_IO_PGTABLE_LPAE_SELFTEST ++ bool "LPAE selftests" ++ depends on IOMMU_IO_PGTABLE_LPAE ++ help ++ Enable self-tests for LPAE page table allocator. This performs ++ a series of page-table consistency checks during boot. ++ ++ If unsure, say N here. ++ ++endmenu ++ + config OF_IOMMU + def_bool y +- depends on OF ++ depends on OF && IOMMU_API + + config FSL_PAMU + bool "Freescale IOMMU support" +@@ -291,13 +317,13 @@ config SPAPR_TCE_IOMMU + + config ARM_SMMU + bool "ARM Ltd. System MMU (SMMU) Support" +- depends on ARM64 || (ARM_LPAE && OF) ++ depends on ARM64 || ARM + select IOMMU_API ++ select IOMMU_IO_PGTABLE_LPAE + select ARM_DMA_USE_IOMMU if ARM + help + Support for implementations of the ARM System MMU architecture +- versions 1 and 2. The driver supports both v7l and v8l table +- formats with 4k and 64k page sizes. ++ versions 1 and 2. + + Say Y here if your SoC includes an IOMMU device implementing + the ARM SMMU architecture. +diff --git a/drivers/iommu/Makefile b/drivers/iommu/Makefile +index 16edef7..269cdd8 100644 +--- a/drivers/iommu/Makefile ++++ b/drivers/iommu/Makefile +@@ -1,6 +1,8 @@ + obj-$(CONFIG_IOMMU_API) += iommu.o + obj-$(CONFIG_IOMMU_API) += iommu-traces.o + obj-$(CONFIG_IOMMU_API) += iommu-sysfs.o ++obj-$(CONFIG_IOMMU_IO_PGTABLE) += io-pgtable.o ++obj-$(CONFIG_IOMMU_IO_PGTABLE_LPAE) += io-pgtable-arm.o + obj-$(CONFIG_OF_IOMMU) += of_iommu.o + obj-$(CONFIG_MSM_IOMMU) += msm_iommu.o msm_iommu_dev.o + obj-$(CONFIG_AMD_IOMMU) += amd_iommu.o amd_iommu_init.o +diff --git a/drivers/iommu/amd_iommu.c b/drivers/iommu/amd_iommu.c +index af3daf8..f7131fa 100644 +--- a/drivers/iommu/amd_iommu.c ++++ b/drivers/iommu/amd_iommu.c +@@ -343,8 +343,9 @@ static u16 get_alias(struct device *dev) + */ + if (pci_alias == devid && + PCI_BUS_NUM(ivrs_alias) == pdev->bus->number) { +- pdev->dev_flags |= PCI_DEV_FLAGS_DMA_ALIAS_DEVFN; +- pdev->dma_alias_devfn = ivrs_alias & 0xff; ++ pdev->dev_flags |= PCI_DEV_FLAGS_DMA_ALIAS_DEVID; ++ pdev->dma_alias_devid = PCI_DEVID(pdev->bus->number, ++ ivrs_alias & 0xff); + pr_info("AMD-Vi: Added PCI DMA alias %02x.%d for %s\n", + PCI_SLOT(ivrs_alias), PCI_FUNC(ivrs_alias), + dev_name(dev)); +@@ -3432,6 +3433,7 @@ static const struct iommu_ops amd_iommu_ops = { + .detach_dev = amd_iommu_detach_device, + .map = amd_iommu_map, + .unmap = amd_iommu_unmap, ++ .map_sg = default_iommu_map_sg, + .iova_to_phys = amd_iommu_iova_to_phys, + .pgsize_bitmap = AMD_IOMMU_PGSIZES, + }; +diff --git a/drivers/iommu/arm-smmu.c b/drivers/iommu/arm-smmu.c +index 60558f7..10e584b 100644 +--- a/drivers/iommu/arm-smmu.c ++++ b/drivers/iommu/arm-smmu.c +@@ -23,8 +23,6 @@ + * - Stream-matching and stream-indexing + * - v7/v8 long-descriptor format + * - Non-secure access to the SMMU +- * - 4k and 64k pages, with contiguous pte hints. +- * - Up to 48-bit addressing (dependent on VA_BITS) + * - Context fault reporting + */ + +@@ -36,7 +34,7 @@ + #include + #include + #include +-#include ++#include + #include + #include + #include +@@ -46,6 +44,16 @@ + + #include + ++#include "io-pgtable.h" ++ ++#ifdef CONFIG_FSL_MC_BUS ++#include <../drivers/staging/fsl-mc/include/mc.h> ++#endif ++ ++#ifdef CONFIG_PCI_LAYERSCAPE ++#include <../drivers/pci/host/pci-layerscape.h> ++#endif ++ + #include + + /* Maximum number of stream IDs assigned to a single device */ +@@ -71,40 +79,6 @@ + ((smmu->options & ARM_SMMU_OPT_SECURE_CFG_ACCESS) \ + ? 0x400 : 0)) + +-/* Page table bits */ +-#define ARM_SMMU_PTE_XN (((pteval_t)3) << 53) +-#define ARM_SMMU_PTE_CONT (((pteval_t)1) << 52) +-#define ARM_SMMU_PTE_AF (((pteval_t)1) << 10) +-#define ARM_SMMU_PTE_SH_NS (((pteval_t)0) << 8) +-#define ARM_SMMU_PTE_SH_OS (((pteval_t)2) << 8) +-#define ARM_SMMU_PTE_SH_IS (((pteval_t)3) << 8) +-#define ARM_SMMU_PTE_PAGE (((pteval_t)3) << 0) +- +-#if PAGE_SIZE == SZ_4K +-#define ARM_SMMU_PTE_CONT_ENTRIES 16 +-#elif PAGE_SIZE == SZ_64K +-#define ARM_SMMU_PTE_CONT_ENTRIES 32 +-#else +-#define ARM_SMMU_PTE_CONT_ENTRIES 1 +-#endif +- +-#define ARM_SMMU_PTE_CONT_SIZE (PAGE_SIZE * ARM_SMMU_PTE_CONT_ENTRIES) +-#define ARM_SMMU_PTE_CONT_MASK (~(ARM_SMMU_PTE_CONT_SIZE - 1)) +- +-/* Stage-1 PTE */ +-#define ARM_SMMU_PTE_AP_UNPRIV (((pteval_t)1) << 6) +-#define ARM_SMMU_PTE_AP_RDONLY (((pteval_t)2) << 6) +-#define ARM_SMMU_PTE_ATTRINDX_SHIFT 2 +-#define ARM_SMMU_PTE_nG (((pteval_t)1) << 11) +- +-/* Stage-2 PTE */ +-#define ARM_SMMU_PTE_HAP_FAULT (((pteval_t)0) << 6) +-#define ARM_SMMU_PTE_HAP_READ (((pteval_t)1) << 6) +-#define ARM_SMMU_PTE_HAP_WRITE (((pteval_t)2) << 6) +-#define ARM_SMMU_PTE_MEMATTR_OIWB (((pteval_t)0xf) << 2) +-#define ARM_SMMU_PTE_MEMATTR_NC (((pteval_t)0x5) << 2) +-#define ARM_SMMU_PTE_MEMATTR_DEV (((pteval_t)0x1) << 2) +- + /* Configuration registers */ + #define ARM_SMMU_GR0_sCR0 0x0 + #define sCR0_CLIENTPD (1 << 0) +@@ -132,17 +106,12 @@ + #define ARM_SMMU_GR0_sGFSYNR0 0x50 + #define ARM_SMMU_GR0_sGFSYNR1 0x54 + #define ARM_SMMU_GR0_sGFSYNR2 0x58 +-#define ARM_SMMU_GR0_PIDR0 0xfe0 +-#define ARM_SMMU_GR0_PIDR1 0xfe4 +-#define ARM_SMMU_GR0_PIDR2 0xfe8 + + #define ID0_S1TS (1 << 30) + #define ID0_S2TS (1 << 29) + #define ID0_NTS (1 << 28) + #define ID0_SMS (1 << 27) +-#define ID0_PTFS_SHIFT 24 +-#define ID0_PTFS_MASK 0x2 +-#define ID0_PTFS_V8_ONLY 0x2 ++#define ID0_ATOSNS (1 << 26) + #define ID0_CTTW (1 << 14) + #define ID0_NUMIRPT_SHIFT 16 + #define ID0_NUMIRPT_MASK 0xff +@@ -169,11 +138,7 @@ + #define ID2_PTFS_16K (1 << 13) + #define ID2_PTFS_64K (1 << 14) + +-#define PIDR2_ARCH_SHIFT 4 +-#define PIDR2_ARCH_MASK 0xf +- + /* Global TLB invalidation */ +-#define ARM_SMMU_GR0_STLBIALL 0x60 + #define ARM_SMMU_GR0_TLBIVMID 0x64 + #define ARM_SMMU_GR0_TLBIALLNSNH 0x68 + #define ARM_SMMU_GR0_TLBIALLH 0x6c +@@ -231,13 +196,25 @@ + #define ARM_SMMU_CB_TTBCR2 0x10 + #define ARM_SMMU_CB_TTBR0_LO 0x20 + #define ARM_SMMU_CB_TTBR0_HI 0x24 ++#define ARM_SMMU_CB_TTBR1_LO 0x28 ++#define ARM_SMMU_CB_TTBR1_HI 0x2c + #define ARM_SMMU_CB_TTBCR 0x30 + #define ARM_SMMU_CB_S1_MAIR0 0x38 ++#define ARM_SMMU_CB_S1_MAIR1 0x3c ++#define ARM_SMMU_CB_PAR_LO 0x50 ++#define ARM_SMMU_CB_PAR_HI 0x54 + #define ARM_SMMU_CB_FSR 0x58 + #define ARM_SMMU_CB_FAR_LO 0x60 + #define ARM_SMMU_CB_FAR_HI 0x64 + #define ARM_SMMU_CB_FSYNR0 0x68 ++#define ARM_SMMU_CB_S1_TLBIVA 0x600 + #define ARM_SMMU_CB_S1_TLBIASID 0x610 ++#define ARM_SMMU_CB_S1_TLBIVAL 0x620 ++#define ARM_SMMU_CB_S2_TLBIIPAS2 0x630 ++#define ARM_SMMU_CB_S2_TLBIIPAS2L 0x638 ++#define ARM_SMMU_CB_ATS1PR_LO 0x800 ++#define ARM_SMMU_CB_ATS1PR_HI 0x804 ++#define ARM_SMMU_CB_ATSR 0x8f0 + + #define SCTLR_S1_ASIDPNE (1 << 12) + #define SCTLR_CFCFG (1 << 7) +@@ -249,64 +226,17 @@ + #define SCTLR_M (1 << 0) + #define SCTLR_EAE_SBOP (SCTLR_AFE | SCTLR_TRE) + +-#define RESUME_RETRY (0 << 0) +-#define RESUME_TERMINATE (1 << 0) +- +-#define TTBCR_EAE (1 << 31) ++#define CB_PAR_F (1 << 0) + +-#define TTBCR_PASIZE_SHIFT 16 +-#define TTBCR_PASIZE_MASK 0x7 ++#define ATSR_ACTIVE (1 << 0) + +-#define TTBCR_TG0_4K (0 << 14) +-#define TTBCR_TG0_64K (1 << 14) +- +-#define TTBCR_SH0_SHIFT 12 +-#define TTBCR_SH0_MASK 0x3 +-#define TTBCR_SH_NS 0 +-#define TTBCR_SH_OS 2 +-#define TTBCR_SH_IS 3 +- +-#define TTBCR_ORGN0_SHIFT 10 +-#define TTBCR_IRGN0_SHIFT 8 +-#define TTBCR_RGN_MASK 0x3 +-#define TTBCR_RGN_NC 0 +-#define TTBCR_RGN_WBWA 1 +-#define TTBCR_RGN_WT 2 +-#define TTBCR_RGN_WB 3 +- +-#define TTBCR_SL0_SHIFT 6 +-#define TTBCR_SL0_MASK 0x3 +-#define TTBCR_SL0_LVL_2 0 +-#define TTBCR_SL0_LVL_1 1 +- +-#define TTBCR_T1SZ_SHIFT 16 +-#define TTBCR_T0SZ_SHIFT 0 +-#define TTBCR_SZ_MASK 0xf ++#define RESUME_RETRY (0 << 0) ++#define RESUME_TERMINATE (1 << 0) + + #define TTBCR2_SEP_SHIFT 15 +-#define TTBCR2_SEP_MASK 0x7 +- +-#define TTBCR2_PASIZE_SHIFT 0 +-#define TTBCR2_PASIZE_MASK 0x7 +- +-/* Common definitions for PASize and SEP fields */ +-#define TTBCR2_ADDR_32 0 +-#define TTBCR2_ADDR_36 1 +-#define TTBCR2_ADDR_40 2 +-#define TTBCR2_ADDR_42 3 +-#define TTBCR2_ADDR_44 4 +-#define TTBCR2_ADDR_48 5 +- +-#define TTBRn_HI_ASID_SHIFT 16 +- +-#define MAIR_ATTR_SHIFT(n) ((n) << 3) +-#define MAIR_ATTR_MASK 0xff +-#define MAIR_ATTR_DEVICE 0x04 +-#define MAIR_ATTR_NC 0x44 +-#define MAIR_ATTR_WBRWA 0xff +-#define MAIR_ATTR_IDX_NC 0 +-#define MAIR_ATTR_IDX_CACHE 1 +-#define MAIR_ATTR_IDX_DEV 2 ++#define TTBCR2_SEP_UPSTREAM (0x7 << TTBCR2_SEP_SHIFT) ++ ++#define TTBRn_HI_ASID_SHIFT 16 + + #define FSR_MULTI (1 << 31) + #define FSR_SS (1 << 30) +@@ -345,6 +275,7 @@ struct arm_smmu_smr { + struct arm_smmu_master_cfg { + int num_streamids; + u16 streamids[MAX_MASTER_STREAMIDS]; ++ u16 mask; + struct arm_smmu_smr *smrs; + }; + +@@ -366,6 +297,7 @@ struct arm_smmu_device { + #define ARM_SMMU_FEAT_TRANS_S1 (1 << 2) + #define ARM_SMMU_FEAT_TRANS_S2 (1 << 3) + #define ARM_SMMU_FEAT_TRANS_NESTED (1 << 4) ++#define ARM_SMMU_FEAT_TRANS_OPS (1 << 5) + u32 features; + + #define ARM_SMMU_OPT_SECURE_CFG_ACCESS (1 << 0) +@@ -380,10 +312,9 @@ struct arm_smmu_device { + u32 num_mapping_groups; + DECLARE_BITMAP(smr_map, ARM_SMMU_MAX_SMRS); + +- unsigned long s1_input_size; +- unsigned long s1_output_size; +- unsigned long s2_input_size; +- unsigned long s2_output_size; ++ unsigned long va_size; ++ unsigned long ipa_size; ++ unsigned long pa_size; + + u32 num_global_irqs; + u32 num_context_irqs; +@@ -397,19 +328,33 @@ struct arm_smmu_cfg { + u8 cbndx; + u8 irptndx; + u32 cbar; +- pgd_t *pgd; + }; + #define INVALID_IRPTNDX 0xff + + #define ARM_SMMU_CB_ASID(cfg) ((cfg)->cbndx) + #define ARM_SMMU_CB_VMID(cfg) ((cfg)->cbndx + 1) + ++enum arm_smmu_domain_stage { ++ ARM_SMMU_DOMAIN_S1 = 0, ++ ARM_SMMU_DOMAIN_S2, ++ ARM_SMMU_DOMAIN_NESTED, ++}; ++ + struct arm_smmu_domain { + struct arm_smmu_device *smmu; ++ struct io_pgtable_ops *pgtbl_ops; ++ spinlock_t pgtbl_lock; + struct arm_smmu_cfg cfg; +- spinlock_t lock; ++ enum arm_smmu_domain_stage stage; ++ struct mutex init_mutex; /* Protects smmu pointer */ ++ struct iommu_domain domain; + }; + ++static struct iommu_ops arm_smmu_ops; ++#ifdef CONFIG_FSL_MC_BUS ++static struct iommu_ops arm_fsl_mc_smmu_ops; ++#endif ++ + static DEFINE_SPINLOCK(arm_smmu_devices_lock); + static LIST_HEAD(arm_smmu_devices); + +@@ -422,6 +367,43 @@ static struct arm_smmu_option_prop arm_smmu_options[] = { + { ARM_SMMU_OPT_SECURE_CFG_ACCESS, "calxeda,smmu-secure-config-access" }, + { 0, NULL}, + }; ++#define CONFIG_AIOP_ERRATA ++#ifdef CONFIG_AIOP_ERRATA ++/* ++ * PL = 1, BMT = 1, VA = 1 ++ */ ++#define AIOP_SMR_VALUE 0x380 ++/* ++ * Following should be set: ++ * SHCFG: 0x3 ++ * MTCFG: 0x1 ++ * MemAttr: 0xf ++ * Type: 0x1 ++ * RACFG: 0x2 ++ * WACFG: 0x2 ++ */ ++#define AIOP_S2CR_VALUE 0xA1FB00 ++ ++static void arm_smmu_aiop_attr_trans(struct arm_smmu_device *smmu) ++{ ++ void __iomem *gr0_base = ARM_SMMU_GR0(smmu); ++ u16 mask = 0x7c7f; ++ int index; ++ u32 reg; ++ /* reserve one smr group for AIOP */ ++ index = --smmu->num_mapping_groups; ++ ++ reg = SMR_VALID | AIOP_SMR_VALUE << SMR_ID_SHIFT | ++ mask << SMR_MASK_SHIFT; ++ writel(reg, gr0_base + ARM_SMMU_GR0_SMR(index)); ++ writel(AIOP_S2CR_VALUE, gr0_base + ARM_SMMU_GR0_S2CR(index)); ++} ++#endif ++ ++static struct arm_smmu_domain *to_smmu_domain(struct iommu_domain *dom) ++{ ++ return container_of(dom, struct arm_smmu_domain, domain); ++} + + static void parse_driver_options(struct arm_smmu_device *smmu) + { +@@ -447,6 +429,16 @@ static struct device_node *dev_get_dev_node(struct device *dev) + return bus->bridge->parent->of_node; + } + ++#ifdef CONFIG_FSL_MC_BUS ++ if (dev->bus == &fsl_mc_bus_type) { ++ /* ++ * Get to the MC device tree node. ++ */ ++ while (dev->bus == &fsl_mc_bus_type) ++ dev = dev->parent; ++ } ++#endif ++ + return dev->of_node; + } + +@@ -590,7 +582,7 @@ static void __arm_smmu_free_bitmap(unsigned long *map, int idx) + } + + /* Wait for any pending TLB invalidations to complete */ +-static void arm_smmu_tlb_sync(struct arm_smmu_device *smmu) ++static void __arm_smmu_tlb_sync(struct arm_smmu_device *smmu) + { + int count = 0; + void __iomem *gr0_base = ARM_SMMU_GR0(smmu); +@@ -608,12 +600,19 @@ static void arm_smmu_tlb_sync(struct arm_smmu_device *smmu) + } + } + +-static void arm_smmu_tlb_inv_context(struct arm_smmu_domain *smmu_domain) ++static void arm_smmu_tlb_sync(void *cookie) + { ++ struct arm_smmu_domain *smmu_domain = cookie; ++ __arm_smmu_tlb_sync(smmu_domain->smmu); ++} ++ ++static void arm_smmu_tlb_inv_context(void *cookie) ++{ ++ struct arm_smmu_domain *smmu_domain = cookie; + struct arm_smmu_cfg *cfg = &smmu_domain->cfg; + struct arm_smmu_device *smmu = smmu_domain->smmu; +- void __iomem *base = ARM_SMMU_GR0(smmu); + bool stage1 = cfg->cbar != CBAR_TYPE_S2_TRANS; ++ void __iomem *base; + + if (stage1) { + base = ARM_SMMU_CB_BASE(smmu) + ARM_SMMU_CB(smmu, cfg->cbndx); +@@ -625,16 +624,83 @@ static void arm_smmu_tlb_inv_context(struct arm_smmu_domain *smmu_domain) + base + ARM_SMMU_GR0_TLBIVMID); + } + +- arm_smmu_tlb_sync(smmu); ++ __arm_smmu_tlb_sync(smmu); ++} ++ ++static void arm_smmu_tlb_inv_range_nosync(unsigned long iova, size_t size, ++ bool leaf, void *cookie) ++{ ++ struct arm_smmu_domain *smmu_domain = cookie; ++ struct arm_smmu_cfg *cfg = &smmu_domain->cfg; ++ struct arm_smmu_device *smmu = smmu_domain->smmu; ++ bool stage1 = cfg->cbar != CBAR_TYPE_S2_TRANS; ++ void __iomem *reg; ++ ++ if (stage1) { ++ reg = ARM_SMMU_CB_BASE(smmu) + ARM_SMMU_CB(smmu, cfg->cbndx); ++ reg += leaf ? ARM_SMMU_CB_S1_TLBIVAL : ARM_SMMU_CB_S1_TLBIVA; ++ ++ if (!IS_ENABLED(CONFIG_64BIT) || smmu->version == ARM_SMMU_V1) { ++ iova &= ~12UL; ++ iova |= ARM_SMMU_CB_ASID(cfg); ++ writel_relaxed(iova, reg); ++#ifdef CONFIG_64BIT ++ } else { ++ iova >>= 12; ++ iova |= (u64)ARM_SMMU_CB_ASID(cfg) << 48; ++ writeq_relaxed(iova, reg); ++#endif ++ } ++#ifdef CONFIG_64BIT ++ } else if (smmu->version == ARM_SMMU_V2) { ++ reg = ARM_SMMU_CB_BASE(smmu) + ARM_SMMU_CB(smmu, cfg->cbndx); ++ reg += leaf ? ARM_SMMU_CB_S2_TLBIIPAS2L : ++ ARM_SMMU_CB_S2_TLBIIPAS2; ++ writeq_relaxed(iova >> 12, reg); ++#endif ++ } else { ++ reg = ARM_SMMU_GR0(smmu) + ARM_SMMU_GR0_TLBIVMID; ++ writel_relaxed(ARM_SMMU_CB_VMID(cfg), reg); ++ } ++} ++ ++static void arm_smmu_flush_pgtable(void *addr, size_t size, void *cookie) ++{ ++ struct arm_smmu_domain *smmu_domain = cookie; ++ struct arm_smmu_device *smmu = smmu_domain->smmu; ++ unsigned long offset = (unsigned long)addr & ~PAGE_MASK; ++ ++ ++ /* Ensure new page tables are visible to the hardware walker */ ++ if (smmu->features & ARM_SMMU_FEAT_COHERENT_WALK) { ++ dsb(ishst); ++ } else { ++ /* ++ * If the SMMU can't walk tables in the CPU caches, treat them ++ * like non-coherent DMA since we need to flush the new entries ++ * all the way out to memory. There's no possibility of ++ * recursion here as the SMMU table walker will not be wired ++ * through another SMMU. ++ */ ++ dma_map_page(smmu->dev, virt_to_page(addr), offset, size, ++ DMA_TO_DEVICE); ++ } + } + ++static struct iommu_gather_ops arm_smmu_gather_ops = { ++ .tlb_flush_all = arm_smmu_tlb_inv_context, ++ .tlb_add_flush = arm_smmu_tlb_inv_range_nosync, ++ .tlb_sync = arm_smmu_tlb_sync, ++ .flush_pgtable = arm_smmu_flush_pgtable, ++}; ++ + static irqreturn_t arm_smmu_context_fault(int irq, void *dev) + { + int flags, ret; + u32 fsr, far, fsynr, resume; + unsigned long iova; + struct iommu_domain *domain = dev; +- struct arm_smmu_domain *smmu_domain = domain->priv; ++ struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain); + struct arm_smmu_cfg *cfg = &smmu_domain->cfg; + struct arm_smmu_device *smmu = smmu_domain->smmu; + void __iomem *cb_base; +@@ -705,29 +771,8 @@ static irqreturn_t arm_smmu_global_fault(int irq, void *dev) + return IRQ_HANDLED; + } + +-static void arm_smmu_flush_pgtable(struct arm_smmu_device *smmu, void *addr, +- size_t size) +-{ +- unsigned long offset = (unsigned long)addr & ~PAGE_MASK; +- +- +- /* Ensure new page tables are visible to the hardware walker */ +- if (smmu->features & ARM_SMMU_FEAT_COHERENT_WALK) { +- dsb(ishst); +- } else { +- /* +- * If the SMMU can't walk tables in the CPU caches, treat them +- * like non-coherent DMA since we need to flush the new entries +- * all the way out to memory. There's no possibility of +- * recursion here as the SMMU table walker will not be wired +- * through another SMMU. +- */ +- dma_map_page(smmu->dev, virt_to_page(addr), offset, size, +- DMA_TO_DEVICE); +- } +-} +- +-static void arm_smmu_init_context_bank(struct arm_smmu_domain *smmu_domain) ++static void arm_smmu_init_context_bank(struct arm_smmu_domain *smmu_domain, ++ struct io_pgtable_cfg *pgtbl_cfg) + { + u32 reg; + bool stage1; +@@ -740,6 +785,20 @@ static void arm_smmu_init_context_bank(struct arm_smmu_domain *smmu_domain) + stage1 = cfg->cbar != CBAR_TYPE_S2_TRANS; + cb_base = ARM_SMMU_CB_BASE(smmu) + ARM_SMMU_CB(smmu, cfg->cbndx); + ++ if (smmu->version > ARM_SMMU_V1) { ++ /* ++ * CBA2R. ++ * *Must* be initialised before CBAR thanks to VMID16 ++ * architectural oversight affected some implementations. ++ */ ++#ifdef CONFIG_64BIT ++ reg = CBA2R_RW64_64BIT; ++#else ++ reg = CBA2R_RW64_32BIT; ++#endif ++ writel_relaxed(reg, gr1_base + ARM_SMMU_GR1_CBA2R(cfg->cbndx)); ++ } ++ + /* CBAR */ + reg = cfg->cbar; + if (smmu->version == ARM_SMMU_V1) +@@ -757,135 +816,51 @@ static void arm_smmu_init_context_bank(struct arm_smmu_domain *smmu_domain) + } + writel_relaxed(reg, gr1_base + ARM_SMMU_GR1_CBAR(cfg->cbndx)); + +- if (smmu->version > ARM_SMMU_V1) { +- /* CBA2R */ +-#ifdef CONFIG_64BIT +- reg = CBA2R_RW64_64BIT; +-#else +- reg = CBA2R_RW64_32BIT; +-#endif +- writel_relaxed(reg, +- gr1_base + ARM_SMMU_GR1_CBA2R(cfg->cbndx)); +- +- /* TTBCR2 */ +- switch (smmu->s1_input_size) { +- case 32: +- reg = (TTBCR2_ADDR_32 << TTBCR2_SEP_SHIFT); +- break; +- case 36: +- reg = (TTBCR2_ADDR_36 << TTBCR2_SEP_SHIFT); +- break; +- case 39: +- case 40: +- reg = (TTBCR2_ADDR_40 << TTBCR2_SEP_SHIFT); +- break; +- case 42: +- reg = (TTBCR2_ADDR_42 << TTBCR2_SEP_SHIFT); +- break; +- case 44: +- reg = (TTBCR2_ADDR_44 << TTBCR2_SEP_SHIFT); +- break; +- case 48: +- reg = (TTBCR2_ADDR_48 << TTBCR2_SEP_SHIFT); +- break; +- } +- +- switch (smmu->s1_output_size) { +- case 32: +- reg |= (TTBCR2_ADDR_32 << TTBCR2_PASIZE_SHIFT); +- break; +- case 36: +- reg |= (TTBCR2_ADDR_36 << TTBCR2_PASIZE_SHIFT); +- break; +- case 39: +- case 40: +- reg |= (TTBCR2_ADDR_40 << TTBCR2_PASIZE_SHIFT); +- break; +- case 42: +- reg |= (TTBCR2_ADDR_42 << TTBCR2_PASIZE_SHIFT); +- break; +- case 44: +- reg |= (TTBCR2_ADDR_44 << TTBCR2_PASIZE_SHIFT); +- break; +- case 48: +- reg |= (TTBCR2_ADDR_48 << TTBCR2_PASIZE_SHIFT); +- break; +- } +- +- if (stage1) +- writel_relaxed(reg, cb_base + ARM_SMMU_CB_TTBCR2); +- } ++ /* TTBRs */ ++ if (stage1) { ++ reg = pgtbl_cfg->arm_lpae_s1_cfg.ttbr[0]; ++ writel_relaxed(reg, cb_base + ARM_SMMU_CB_TTBR0_LO); ++ reg = pgtbl_cfg->arm_lpae_s1_cfg.ttbr[0] >> 32; ++ reg |= ARM_SMMU_CB_ASID(cfg) << TTBRn_HI_ASID_SHIFT; ++ writel_relaxed(reg, cb_base + ARM_SMMU_CB_TTBR0_HI); + +- /* TTBR0 */ +- arm_smmu_flush_pgtable(smmu, cfg->pgd, +- PTRS_PER_PGD * sizeof(pgd_t)); +- reg = __pa(cfg->pgd); +- writel_relaxed(reg, cb_base + ARM_SMMU_CB_TTBR0_LO); +- reg = (phys_addr_t)__pa(cfg->pgd) >> 32; +- if (stage1) ++ reg = pgtbl_cfg->arm_lpae_s1_cfg.ttbr[1]; ++ writel_relaxed(reg, cb_base + ARM_SMMU_CB_TTBR1_LO); ++ reg = pgtbl_cfg->arm_lpae_s1_cfg.ttbr[1] >> 32; + reg |= ARM_SMMU_CB_ASID(cfg) << TTBRn_HI_ASID_SHIFT; +- writel_relaxed(reg, cb_base + ARM_SMMU_CB_TTBR0_HI); ++ writel_relaxed(reg, cb_base + ARM_SMMU_CB_TTBR1_HI); ++ } else { ++ reg = pgtbl_cfg->arm_lpae_s2_cfg.vttbr; ++ writel_relaxed(reg, cb_base + ARM_SMMU_CB_TTBR0_LO); ++ reg = pgtbl_cfg->arm_lpae_s2_cfg.vttbr >> 32; ++ writel_relaxed(reg, cb_base + ARM_SMMU_CB_TTBR0_HI); ++ } + +- /* +- * TTBCR +- * We use long descriptor, with inner-shareable WBWA tables in TTBR0. +- */ +- if (smmu->version > ARM_SMMU_V1) { +- if (PAGE_SIZE == SZ_4K) +- reg = TTBCR_TG0_4K; +- else +- reg = TTBCR_TG0_64K; +- +- if (!stage1) { +- reg |= (64 - smmu->s2_input_size) << TTBCR_T0SZ_SHIFT; +- +- switch (smmu->s2_output_size) { +- case 32: +- reg |= (TTBCR2_ADDR_32 << TTBCR_PASIZE_SHIFT); +- break; +- case 36: +- reg |= (TTBCR2_ADDR_36 << TTBCR_PASIZE_SHIFT); +- break; +- case 40: +- reg |= (TTBCR2_ADDR_40 << TTBCR_PASIZE_SHIFT); +- break; +- case 42: +- reg |= (TTBCR2_ADDR_42 << TTBCR_PASIZE_SHIFT); +- break; +- case 44: +- reg |= (TTBCR2_ADDR_44 << TTBCR_PASIZE_SHIFT); +- break; +- case 48: +- reg |= (TTBCR2_ADDR_48 << TTBCR_PASIZE_SHIFT); +- break; +- } +- } else { +- reg |= (64 - smmu->s1_input_size) << TTBCR_T0SZ_SHIFT; ++ /* TTBCR */ ++ if (stage1) { ++ reg = pgtbl_cfg->arm_lpae_s1_cfg.tcr; ++ writel_relaxed(reg, cb_base + ARM_SMMU_CB_TTBCR); ++ if (smmu->version > ARM_SMMU_V1) { ++ reg = pgtbl_cfg->arm_lpae_s1_cfg.tcr >> 32; ++ reg |= TTBCR2_SEP_UPSTREAM; ++ writel_relaxed(reg, cb_base + ARM_SMMU_CB_TTBCR2); + } + } else { +- reg = 0; ++ reg = pgtbl_cfg->arm_lpae_s2_cfg.vtcr; ++ writel_relaxed(reg, cb_base + ARM_SMMU_CB_TTBCR); + } + +- reg |= TTBCR_EAE | +- (TTBCR_SH_IS << TTBCR_SH0_SHIFT) | +- (TTBCR_RGN_WBWA << TTBCR_ORGN0_SHIFT) | +- (TTBCR_RGN_WBWA << TTBCR_IRGN0_SHIFT); +- +- if (!stage1) +- reg |= (TTBCR_SL0_LVL_1 << TTBCR_SL0_SHIFT); +- +- writel_relaxed(reg, cb_base + ARM_SMMU_CB_TTBCR); +- +- /* MAIR0 (stage-1 only) */ ++ /* MAIRs (stage-1 only) */ + if (stage1) { +- reg = (MAIR_ATTR_NC << MAIR_ATTR_SHIFT(MAIR_ATTR_IDX_NC)) | +- (MAIR_ATTR_WBRWA << MAIR_ATTR_SHIFT(MAIR_ATTR_IDX_CACHE)) | +- (MAIR_ATTR_DEVICE << MAIR_ATTR_SHIFT(MAIR_ATTR_IDX_DEV)); ++ reg = pgtbl_cfg->arm_lpae_s1_cfg.mair[0]; + writel_relaxed(reg, cb_base + ARM_SMMU_CB_S1_MAIR0); ++ reg = pgtbl_cfg->arm_lpae_s1_cfg.mair[1]; ++ writel_relaxed(reg, cb_base + ARM_SMMU_CB_S1_MAIR1); + } + + /* SCTLR */ +- reg = SCTLR_CFCFG | SCTLR_CFIE | SCTLR_CFRE | SCTLR_M | SCTLR_EAE_SBOP; ++ /* Disable stall mode */ ++ reg = SCTLR_CFIE | SCTLR_CFRE | SCTLR_M | SCTLR_EAE_SBOP; + if (stage1) + reg |= SCTLR_S1_ASIDPNE; + #ifdef __BIG_ENDIAN +@@ -898,27 +873,69 @@ static int arm_smmu_init_domain_context(struct iommu_domain *domain, + struct arm_smmu_device *smmu) + { + int irq, start, ret = 0; +- unsigned long flags; +- struct arm_smmu_domain *smmu_domain = domain->priv; ++ unsigned long ias, oas; ++ struct io_pgtable_ops *pgtbl_ops; ++ struct io_pgtable_cfg pgtbl_cfg; ++ enum io_pgtable_fmt fmt; ++ struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain); + struct arm_smmu_cfg *cfg = &smmu_domain->cfg; + +- spin_lock_irqsave(&smmu_domain->lock, flags); ++ mutex_lock(&smmu_domain->init_mutex); + if (smmu_domain->smmu) + goto out_unlock; + +- if (smmu->features & ARM_SMMU_FEAT_TRANS_NESTED) { ++ /* ++ * Mapping the requested stage onto what we support is surprisingly ++ * complicated, mainly because the spec allows S1+S2 SMMUs without ++ * support for nested translation. That means we end up with the ++ * following table: ++ * ++ * Requested Supported Actual ++ * S1 N S1 ++ * S1 S1+S2 S1 ++ * S1 S2 S2 ++ * S1 S1 S1 ++ * N N N ++ * N S1+S2 S2 ++ * N S2 S2 ++ * N S1 S1 ++ * ++ * Note that you can't actually request stage-2 mappings. ++ */ ++ if (!(smmu->features & ARM_SMMU_FEAT_TRANS_S1)) ++ smmu_domain->stage = ARM_SMMU_DOMAIN_S2; ++ if (!(smmu->features & ARM_SMMU_FEAT_TRANS_S2)) ++ smmu_domain->stage = ARM_SMMU_DOMAIN_S1; ++ ++ switch (smmu_domain->stage) { ++ case ARM_SMMU_DOMAIN_S1: ++ cfg->cbar = CBAR_TYPE_S1_TRANS_S2_BYPASS; ++ start = smmu->num_s2_context_banks; ++ ias = smmu->va_size; ++ oas = smmu->ipa_size; ++ if (IS_ENABLED(CONFIG_64BIT)) ++ fmt = ARM_64_LPAE_S1; ++ else ++ fmt = ARM_32_LPAE_S1; ++ break; ++ case ARM_SMMU_DOMAIN_NESTED: + /* + * We will likely want to change this if/when KVM gets + * involved. + */ +- cfg->cbar = CBAR_TYPE_S1_TRANS_S2_BYPASS; +- start = smmu->num_s2_context_banks; +- } else if (smmu->features & ARM_SMMU_FEAT_TRANS_S1) { +- cfg->cbar = CBAR_TYPE_S1_TRANS_S2_BYPASS; +- start = smmu->num_s2_context_banks; +- } else { ++ case ARM_SMMU_DOMAIN_S2: + cfg->cbar = CBAR_TYPE_S2_TRANS; + start = 0; ++ ias = smmu->ipa_size; ++ oas = smmu->pa_size; ++ if (IS_ENABLED(CONFIG_64BIT)) ++ fmt = ARM_64_LPAE_S2; ++ else ++ fmt = ARM_32_LPAE_S2; ++ break; ++ default: ++ ret = -EINVAL; ++ goto out_unlock; + } + + ret = __arm_smmu_alloc_bitmap(smmu->context_map, start, +@@ -934,10 +951,33 @@ static int arm_smmu_init_domain_context(struct iommu_domain *domain, + cfg->irptndx = cfg->cbndx; + } + +- ACCESS_ONCE(smmu_domain->smmu) = smmu; +- arm_smmu_init_context_bank(smmu_domain); +- spin_unlock_irqrestore(&smmu_domain->lock, flags); ++ pgtbl_cfg = (struct io_pgtable_cfg) { ++ .pgsize_bitmap = arm_smmu_ops.pgsize_bitmap, ++ .ias = ias, ++ .oas = oas, ++ .tlb = &arm_smmu_gather_ops, ++ }; ++ ++ smmu_domain->smmu = smmu; ++ pgtbl_ops = alloc_io_pgtable_ops(fmt, &pgtbl_cfg, smmu_domain); ++ if (!pgtbl_ops) { ++ ret = -ENOMEM; ++ goto out_clear_smmu; ++ } ++ ++ /* Update our support page sizes to reflect the page table format */ ++ arm_smmu_ops.pgsize_bitmap = pgtbl_cfg.pgsize_bitmap; ++#ifdef CONFIG_FSL_MC_BUS ++ arm_fsl_mc_smmu_ops.pgsize_bitmap = pgtbl_cfg.pgsize_bitmap; ++#endif ++ ++ /* Initialise the context bank with our page table cfg */ ++ arm_smmu_init_context_bank(smmu_domain, &pgtbl_cfg); + ++ /* ++ * Request context fault interrupt. Do this last to avoid the ++ * handler seeing a half-initialised domain state. ++ */ + irq = smmu->irqs[smmu->num_global_irqs + cfg->irptndx]; + ret = request_irq(irq, arm_smmu_context_fault, IRQF_SHARED, + "arm-smmu-context-fault", domain); +@@ -947,16 +987,22 @@ static int arm_smmu_init_domain_context(struct iommu_domain *domain, + cfg->irptndx = INVALID_IRPTNDX; + } + ++ mutex_unlock(&smmu_domain->init_mutex); ++ ++ /* Publish page table ops for map/unmap */ ++ smmu_domain->pgtbl_ops = pgtbl_ops; + return 0; + ++out_clear_smmu: ++ smmu_domain->smmu = NULL; + out_unlock: +- spin_unlock_irqrestore(&smmu_domain->lock, flags); ++ mutex_unlock(&smmu_domain->init_mutex); + return ret; + } + + static void arm_smmu_destroy_domain_context(struct iommu_domain *domain) + { +- struct arm_smmu_domain *smmu_domain = domain->priv; ++ struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain); + struct arm_smmu_device *smmu = smmu_domain->smmu; + struct arm_smmu_cfg *cfg = &smmu_domain->cfg; + void __iomem *cb_base; +@@ -965,24 +1011,30 @@ static void arm_smmu_destroy_domain_context(struct iommu_domain *domain) + if (!smmu) + return; + +- /* Disable the context bank and nuke the TLB before freeing it. */ ++ /* ++ * Disable the context bank and free the page tables before freeing ++ * it. ++ */ + cb_base = ARM_SMMU_CB_BASE(smmu) + ARM_SMMU_CB(smmu, cfg->cbndx); + writel_relaxed(0, cb_base + ARM_SMMU_CB_SCTLR); +- arm_smmu_tlb_inv_context(smmu_domain); + + if (cfg->irptndx != INVALID_IRPTNDX) { + irq = smmu->irqs[smmu->num_global_irqs + cfg->irptndx]; + free_irq(irq, domain); + } + ++ if (smmu_domain->pgtbl_ops) ++ free_io_pgtable_ops(smmu_domain->pgtbl_ops); ++ + __arm_smmu_free_bitmap(smmu->context_map, cfg->cbndx); + } + +-static int arm_smmu_domain_init(struct iommu_domain *domain) ++static struct iommu_domain *arm_smmu_domain_alloc(unsigned type) + { + struct arm_smmu_domain *smmu_domain; +- pgd_t *pgd; + ++ if (type != IOMMU_DOMAIN_UNMANAGED) ++ return NULL; + /* + * Allocate the domain and initialise some of its data structures. + * We can't really do anything meaningful until we've added a +@@ -990,95 +1042,23 @@ static int arm_smmu_domain_init(struct iommu_domain *domain) + */ + smmu_domain = kzalloc(sizeof(*smmu_domain), GFP_KERNEL); + if (!smmu_domain) +- return -ENOMEM; ++ return NULL; + +- pgd = kcalloc(PTRS_PER_PGD, sizeof(pgd_t), GFP_KERNEL); +- if (!pgd) +- goto out_free_domain; +- smmu_domain->cfg.pgd = pgd; ++ mutex_init(&smmu_domain->init_mutex); ++ spin_lock_init(&smmu_domain->pgtbl_lock); + +- spin_lock_init(&smmu_domain->lock); +- domain->priv = smmu_domain; +- return 0; +- +-out_free_domain: +- kfree(smmu_domain); +- return -ENOMEM; ++ return &smmu_domain->domain; + } + +-static void arm_smmu_free_ptes(pmd_t *pmd) ++static void arm_smmu_domain_free(struct iommu_domain *domain) + { +- pgtable_t table = pmd_pgtable(*pmd); +- +- __free_page(table); +-} +- +-static void arm_smmu_free_pmds(pud_t *pud) +-{ +- int i; +- pmd_t *pmd, *pmd_base = pmd_offset(pud, 0); +- +- pmd = pmd_base; +- for (i = 0; i < PTRS_PER_PMD; ++i) { +- if (pmd_none(*pmd)) +- continue; +- +- arm_smmu_free_ptes(pmd); +- pmd++; +- } +- +- pmd_free(NULL, pmd_base); +-} +- +-static void arm_smmu_free_puds(pgd_t *pgd) +-{ +- int i; +- pud_t *pud, *pud_base = pud_offset(pgd, 0); +- +- pud = pud_base; +- for (i = 0; i < PTRS_PER_PUD; ++i) { +- if (pud_none(*pud)) +- continue; +- +- arm_smmu_free_pmds(pud); +- pud++; +- } +- +- pud_free(NULL, pud_base); +-} +- +-static void arm_smmu_free_pgtables(struct arm_smmu_domain *smmu_domain) +-{ +- int i; +- struct arm_smmu_cfg *cfg = &smmu_domain->cfg; +- pgd_t *pgd, *pgd_base = cfg->pgd; +- +- /* +- * Recursively free the page tables for this domain. We don't +- * care about speculative TLB filling because the tables should +- * not be active in any context bank at this point (SCTLR.M is 0). +- */ +- pgd = pgd_base; +- for (i = 0; i < PTRS_PER_PGD; ++i) { +- if (pgd_none(*pgd)) +- continue; +- arm_smmu_free_puds(pgd); +- pgd++; +- } +- +- kfree(pgd_base); +-} +- +-static void arm_smmu_domain_destroy(struct iommu_domain *domain) +-{ +- struct arm_smmu_domain *smmu_domain = domain->priv; ++ struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain); + + /* + * Free the domain resources. We assume that all devices have + * already been detached. + */ + arm_smmu_destroy_domain_context(domain); +- arm_smmu_free_pgtables(smmu_domain); + kfree(smmu_domain); + } + +@@ -1113,7 +1093,7 @@ static int arm_smmu_master_configure_smrs(struct arm_smmu_device *smmu, + + smrs[i] = (struct arm_smmu_smr) { + .idx = idx, +- .mask = 0, /* We don't currently share SMRs */ ++ .mask = cfg->mask, + .id = cfg->streamids[i], + }; + } +@@ -1209,8 +1189,8 @@ static void arm_smmu_domain_remove_master(struct arm_smmu_domain *smmu_domain, + static int arm_smmu_attach_dev(struct iommu_domain *domain, struct device *dev) + { + int ret; +- struct arm_smmu_domain *smmu_domain = domain->priv; +- struct arm_smmu_device *smmu, *dom_smmu; ++ struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain); ++ struct arm_smmu_device *smmu; + struct arm_smmu_master_cfg *cfg; + + smmu = find_smmu_for_device(dev); +@@ -1224,21 +1204,16 @@ static int arm_smmu_attach_dev(struct iommu_domain *domain, struct device *dev) + return -EEXIST; + } + ++ /* Ensure that the domain is finalised */ ++ ret = arm_smmu_init_domain_context(domain, smmu); ++ if (IS_ERR_VALUE(ret)) ++ return ret; ++ + /* + * Sanity check the domain. We don't support domains across + * different SMMUs. + */ +- dom_smmu = ACCESS_ONCE(smmu_domain->smmu); +- if (!dom_smmu) { +- /* Now that we have a master, we can finalise the domain */ +- ret = arm_smmu_init_domain_context(domain, smmu); +- if (IS_ERR_VALUE(ret)) +- return ret; +- +- dom_smmu = smmu_domain->smmu; +- } +- +- if (dom_smmu != smmu) { ++ if (smmu_domain->smmu != smmu) { + dev_err(dev, + "cannot attach to SMMU %s whilst already attached to domain on SMMU %s\n", + dev_name(smmu_domain->smmu->dev), dev_name(smmu->dev)); +@@ -1258,7 +1233,7 @@ static int arm_smmu_attach_dev(struct iommu_domain *domain, struct device *dev) + + static void arm_smmu_detach_dev(struct iommu_domain *domain, struct device *dev) + { +- struct arm_smmu_domain *smmu_domain = domain->priv; ++ struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain); + struct arm_smmu_master_cfg *cfg; + + cfg = find_smmu_master_cfg(dev); +@@ -1269,292 +1244,106 @@ static void arm_smmu_detach_dev(struct iommu_domain *domain, struct device *dev) + arm_smmu_domain_remove_master(smmu_domain, cfg); + } + +-static bool arm_smmu_pte_is_contiguous_range(unsigned long addr, +- unsigned long end) +-{ +- return !(addr & ~ARM_SMMU_PTE_CONT_MASK) && +- (addr + ARM_SMMU_PTE_CONT_SIZE <= end); +-} +- +-static int arm_smmu_alloc_init_pte(struct arm_smmu_device *smmu, pmd_t *pmd, +- unsigned long addr, unsigned long end, +- unsigned long pfn, int prot, int stage) +-{ +- pte_t *pte, *start; +- pteval_t pteval = ARM_SMMU_PTE_PAGE | ARM_SMMU_PTE_AF | ARM_SMMU_PTE_XN; +- +- if (pmd_none(*pmd)) { +- /* Allocate a new set of tables */ +- pgtable_t table = alloc_page(GFP_ATOMIC|__GFP_ZERO); +- +- if (!table) +- return -ENOMEM; +- +- arm_smmu_flush_pgtable(smmu, page_address(table), PAGE_SIZE); +- pmd_populate(NULL, pmd, table); +- arm_smmu_flush_pgtable(smmu, pmd, sizeof(*pmd)); +- } +- +- if (stage == 1) { +- pteval |= ARM_SMMU_PTE_AP_UNPRIV | ARM_SMMU_PTE_nG; +- if (!(prot & IOMMU_WRITE) && (prot & IOMMU_READ)) +- pteval |= ARM_SMMU_PTE_AP_RDONLY; +- +- if (prot & IOMMU_CACHE) +- pteval |= (MAIR_ATTR_IDX_CACHE << +- ARM_SMMU_PTE_ATTRINDX_SHIFT); +- } else { +- pteval |= ARM_SMMU_PTE_HAP_FAULT; +- if (prot & IOMMU_READ) +- pteval |= ARM_SMMU_PTE_HAP_READ; +- if (prot & IOMMU_WRITE) +- pteval |= ARM_SMMU_PTE_HAP_WRITE; +- if (prot & IOMMU_CACHE) +- pteval |= ARM_SMMU_PTE_MEMATTR_OIWB; +- else +- pteval |= ARM_SMMU_PTE_MEMATTR_NC; +- } +- +- /* If no access, create a faulting entry to avoid TLB fills */ +- if (prot & IOMMU_EXEC) +- pteval &= ~ARM_SMMU_PTE_XN; +- else if (!(prot & (IOMMU_READ | IOMMU_WRITE))) +- pteval &= ~ARM_SMMU_PTE_PAGE; +- +- pteval |= ARM_SMMU_PTE_SH_IS; +- start = pmd_page_vaddr(*pmd) + pte_index(addr); +- pte = start; +- +- /* +- * Install the page table entries. This is fairly complicated +- * since we attempt to make use of the contiguous hint in the +- * ptes where possible. The contiguous hint indicates a series +- * of ARM_SMMU_PTE_CONT_ENTRIES ptes mapping a physically +- * contiguous region with the following constraints: +- * +- * - The region start is aligned to ARM_SMMU_PTE_CONT_SIZE +- * - Each pte in the region has the contiguous hint bit set +- * +- * This complicates unmapping (also handled by this code, when +- * neither IOMMU_READ or IOMMU_WRITE are set) because it is +- * possible, yet highly unlikely, that a client may unmap only +- * part of a contiguous range. This requires clearing of the +- * contiguous hint bits in the range before installing the new +- * faulting entries. +- * +- * Note that re-mapping an address range without first unmapping +- * it is not supported, so TLB invalidation is not required here +- * and is instead performed at unmap and domain-init time. +- */ +- do { +- int i = 1; +- +- pteval &= ~ARM_SMMU_PTE_CONT; +- +- if (arm_smmu_pte_is_contiguous_range(addr, end)) { +- i = ARM_SMMU_PTE_CONT_ENTRIES; +- pteval |= ARM_SMMU_PTE_CONT; +- } else if (pte_val(*pte) & +- (ARM_SMMU_PTE_CONT | ARM_SMMU_PTE_PAGE)) { +- int j; +- pte_t *cont_start; +- unsigned long idx = pte_index(addr); +- +- idx &= ~(ARM_SMMU_PTE_CONT_ENTRIES - 1); +- cont_start = pmd_page_vaddr(*pmd) + idx; +- for (j = 0; j < ARM_SMMU_PTE_CONT_ENTRIES; ++j) +- pte_val(*(cont_start + j)) &= +- ~ARM_SMMU_PTE_CONT; +- +- arm_smmu_flush_pgtable(smmu, cont_start, +- sizeof(*pte) * +- ARM_SMMU_PTE_CONT_ENTRIES); +- } +- +- do { +- *pte = pfn_pte(pfn, __pgprot(pteval)); +- } while (pte++, pfn++, addr += PAGE_SIZE, --i); +- } while (addr != end); +- +- arm_smmu_flush_pgtable(smmu, start, sizeof(*pte) * (pte - start)); +- return 0; +-} +- +-static int arm_smmu_alloc_init_pmd(struct arm_smmu_device *smmu, pud_t *pud, +- unsigned long addr, unsigned long end, +- phys_addr_t phys, int prot, int stage) ++static int arm_smmu_map(struct iommu_domain *domain, unsigned long iova, ++ phys_addr_t paddr, size_t size, int prot) + { + int ret; +- pmd_t *pmd; +- unsigned long next, pfn = __phys_to_pfn(phys); +- +-#ifndef __PAGETABLE_PMD_FOLDED +- if (pud_none(*pud)) { +- pmd = (pmd_t *)get_zeroed_page(GFP_ATOMIC); +- if (!pmd) +- return -ENOMEM; +- +- arm_smmu_flush_pgtable(smmu, pmd, PAGE_SIZE); +- pud_populate(NULL, pud, pmd); +- arm_smmu_flush_pgtable(smmu, pud, sizeof(*pud)); +- +- pmd += pmd_index(addr); +- } else +-#endif +- pmd = pmd_offset(pud, addr); ++ unsigned long flags; ++ struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain); ++ struct io_pgtable_ops *ops= smmu_domain->pgtbl_ops; + +- do { +- next = pmd_addr_end(addr, end); +- ret = arm_smmu_alloc_init_pte(smmu, pmd, addr, next, pfn, +- prot, stage); +- phys += next - addr; +- pfn = __phys_to_pfn(phys); +- } while (pmd++, addr = next, addr < end); ++ if (!ops) ++ return -ENODEV; + ++ spin_lock_irqsave(&smmu_domain->pgtbl_lock, flags); ++ ret = ops->map(ops, iova, paddr, size, prot); ++ spin_unlock_irqrestore(&smmu_domain->pgtbl_lock, flags); + return ret; + } + +-static int arm_smmu_alloc_init_pud(struct arm_smmu_device *smmu, pgd_t *pgd, +- unsigned long addr, unsigned long end, +- phys_addr_t phys, int prot, int stage) ++static size_t arm_smmu_unmap(struct iommu_domain *domain, unsigned long iova, ++ size_t size) + { +- int ret = 0; +- pud_t *pud; +- unsigned long next; +- +-#ifndef __PAGETABLE_PUD_FOLDED +- if (pgd_none(*pgd)) { +- pud = (pud_t *)get_zeroed_page(GFP_ATOMIC); +- if (!pud) +- return -ENOMEM; +- +- arm_smmu_flush_pgtable(smmu, pud, PAGE_SIZE); +- pgd_populate(NULL, pgd, pud); +- arm_smmu_flush_pgtable(smmu, pgd, sizeof(*pgd)); +- +- pud += pud_index(addr); +- } else +-#endif +- pud = pud_offset(pgd, addr); ++ size_t ret; ++ unsigned long flags; ++ struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain); ++ struct io_pgtable_ops *ops= smmu_domain->pgtbl_ops; + +- do { +- next = pud_addr_end(addr, end); +- ret = arm_smmu_alloc_init_pmd(smmu, pud, addr, next, phys, +- prot, stage); +- phys += next - addr; +- } while (pud++, addr = next, addr < end); ++ if (!ops) ++ return 0; + ++ spin_lock_irqsave(&smmu_domain->pgtbl_lock, flags); ++ ret = ops->unmap(ops, iova, size); ++ spin_unlock_irqrestore(&smmu_domain->pgtbl_lock, flags); + return ret; + } + +-static int arm_smmu_handle_mapping(struct arm_smmu_domain *smmu_domain, +- unsigned long iova, phys_addr_t paddr, +- size_t size, int prot) ++static phys_addr_t arm_smmu_iova_to_phys_hard(struct iommu_domain *domain, ++ dma_addr_t iova) + { +- int ret, stage; +- unsigned long end; +- phys_addr_t input_mask, output_mask; ++ struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain); + struct arm_smmu_device *smmu = smmu_domain->smmu; + struct arm_smmu_cfg *cfg = &smmu_domain->cfg; +- pgd_t *pgd = cfg->pgd; +- unsigned long flags; ++ struct io_pgtable_ops *ops= smmu_domain->pgtbl_ops; ++ struct device *dev = smmu->dev; ++ void __iomem *cb_base; ++ u32 tmp; ++ u64 phys; ++ ++ cb_base = ARM_SMMU_CB_BASE(smmu) + ARM_SMMU_CB(smmu, cfg->cbndx); + +- if (cfg->cbar == CBAR_TYPE_S2_TRANS) { +- stage = 2; +- input_mask = (1ULL << smmu->s2_input_size) - 1; +- output_mask = (1ULL << smmu->s2_output_size) - 1; ++ if (smmu->version == 1) { ++ u32 reg = iova & ~0xfff; ++ writel_relaxed(reg, cb_base + ARM_SMMU_CB_ATS1PR_LO); + } else { +- stage = 1; +- input_mask = (1ULL << smmu->s1_input_size) - 1; +- output_mask = (1ULL << smmu->s1_output_size) - 1; ++ u32 reg = iova & ~0xfff; ++ writel_relaxed(reg, cb_base + ARM_SMMU_CB_ATS1PR_LO); ++ reg = ((u64)iova & ~0xfff) >> 32; ++ writel_relaxed(reg, cb_base + ARM_SMMU_CB_ATS1PR_HI); + } + +- if (!pgd) +- return -EINVAL; +- +- if (size & ~PAGE_MASK) +- return -EINVAL; +- +- if ((phys_addr_t)iova & ~input_mask) +- return -ERANGE; +- +- if (paddr & ~output_mask) +- return -ERANGE; +- +- spin_lock_irqsave(&smmu_domain->lock, flags); +- pgd += pgd_index(iova); +- end = iova + size; +- do { +- unsigned long next = pgd_addr_end(iova, end); +- +- ret = arm_smmu_alloc_init_pud(smmu, pgd, iova, next, paddr, +- prot, stage); +- if (ret) +- goto out_unlock; +- +- paddr += next - iova; +- iova = next; +- } while (pgd++, iova != end); +- +-out_unlock: +- spin_unlock_irqrestore(&smmu_domain->lock, flags); +- +- return ret; +-} +- +-static int arm_smmu_map(struct iommu_domain *domain, unsigned long iova, +- phys_addr_t paddr, size_t size, int prot) +-{ +- struct arm_smmu_domain *smmu_domain = domain->priv; +- +- if (!smmu_domain) +- return -ENODEV; ++ if (readl_poll_timeout_atomic(cb_base + ARM_SMMU_CB_ATSR, tmp, ++ !(tmp & ATSR_ACTIVE), 5, 50)) { ++ dev_err(dev, ++ "iova to phys timed out on 0x%pad. Falling back to software table walk.\n", ++ &iova); ++ return ops->iova_to_phys(ops, iova); ++ } + +- return arm_smmu_handle_mapping(smmu_domain, iova, paddr, size, prot); +-} ++ phys = readl_relaxed(cb_base + ARM_SMMU_CB_PAR_LO); ++ phys |= ((u64)readl_relaxed(cb_base + ARM_SMMU_CB_PAR_HI)) << 32; + +-static size_t arm_smmu_unmap(struct iommu_domain *domain, unsigned long iova, +- size_t size) +-{ +- int ret; +- struct arm_smmu_domain *smmu_domain = domain->priv; ++ if (phys & CB_PAR_F) { ++ dev_err(dev, "translation fault!\n"); ++ dev_err(dev, "PAR = 0x%llx\n", phys); ++ return 0; ++ } + +- ret = arm_smmu_handle_mapping(smmu_domain, iova, 0, size, 0); +- arm_smmu_tlb_inv_context(smmu_domain); +- return ret ? 0 : size; ++ return (phys & GENMASK_ULL(39, 12)) | (iova & 0xfff); + } + + static phys_addr_t arm_smmu_iova_to_phys(struct iommu_domain *domain, +- dma_addr_t iova) ++ dma_addr_t iova) + { +- pgd_t *pgdp, pgd; +- pud_t pud; +- pmd_t pmd; +- pte_t pte; +- struct arm_smmu_domain *smmu_domain = domain->priv; +- struct arm_smmu_cfg *cfg = &smmu_domain->cfg; +- +- pgdp = cfg->pgd; +- if (!pgdp) +- return 0; +- +- pgd = *(pgdp + pgd_index(iova)); +- if (pgd_none(pgd)) +- return 0; ++ phys_addr_t ret; ++ unsigned long flags; ++ struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain); ++ struct io_pgtable_ops *ops= smmu_domain->pgtbl_ops; + +- pud = *pud_offset(&pgd, iova); +- if (pud_none(pud)) ++ if (!ops) + return 0; + +- pmd = *pmd_offset(&pud, iova); +- if (pmd_none(pmd)) +- return 0; ++ spin_lock_irqsave(&smmu_domain->pgtbl_lock, flags); ++ if (smmu_domain->smmu->features & ARM_SMMU_FEAT_TRANS_OPS && ++ smmu_domain->stage == ARM_SMMU_DOMAIN_S1) { ++ ret = arm_smmu_iova_to_phys_hard(domain, iova); ++ } else { ++ ret = ops->iova_to_phys(ops, iova); ++ } + +- pte = *(pmd_page_vaddr(pmd) + pte_index(iova)); +- if (pte_none(pte)) +- return 0; ++ spin_unlock_irqrestore(&smmu_domain->pgtbl_lock, flags); + +- return __pfn_to_phys(pte_pfn(pte)) | (iova & ~PAGE_MASK); ++ return ret; + } + + static bool arm_smmu_capable(enum iommu_cap cap) +@@ -1568,6 +1357,8 @@ static bool arm_smmu_capable(enum iommu_cap cap) + return true; + case IOMMU_CAP_INTR_REMAP: + return true; /* MSIs are just memory writes */ ++ case IOMMU_CAP_NOEXEC: ++ return true; + default: + return false; + } +@@ -1584,81 +1375,248 @@ static void __arm_smmu_release_pci_iommudata(void *data) + kfree(data); + } + +-static int arm_smmu_add_device(struct device *dev) ++static int arm_smmu_add_pci_device(struct pci_dev *pdev) + { +- struct arm_smmu_device *smmu; ++ int i, ret; ++ u16 sid; ++ struct iommu_group *group; + struct arm_smmu_master_cfg *cfg; ++#ifdef CONFIG_PCI_LAYERSCAPE ++ u32 streamid; ++#endif ++ ++ group = iommu_group_get_for_dev(&pdev->dev); ++ if (IS_ERR(group)) ++ return PTR_ERR(group); ++ ++ cfg = iommu_group_get_iommudata(group); ++ if (!cfg) { ++ cfg = kzalloc(sizeof(*cfg), GFP_KERNEL); ++ if (!cfg) { ++ ret = -ENOMEM; ++ goto out_put_group; ++ } ++ ++ iommu_group_set_iommudata(group, cfg, ++ __arm_smmu_release_pci_iommudata); ++ } ++ ++ if (cfg->num_streamids >= MAX_MASTER_STREAMIDS) { ++ ret = -ENOSPC; ++ goto out_put_group; ++ } ++ ++ /* ++ * Assume Stream ID == Requester ID for now. ++ * We need a way to describe the ID mappings in FDT. ++ */ ++ pci_for_each_dma_alias(pdev, __arm_smmu_get_pci_sid, &sid); ++ for (i = 0; i < cfg->num_streamids; ++i) ++ if (cfg->streamids[i] == sid) ++ break; ++ ++ /* Avoid duplicate SIDs, as this can lead to SMR conflicts */ ++ if (i == cfg->num_streamids) ++ cfg->streamids[cfg->num_streamids++] = sid; ++ ++#ifdef CONFIG_PCI_LAYERSCAPE ++ streamid = set_pcie_streamid_translation(pdev, sid); ++ if (~streamid == 0) { ++ ret = -ENODEV; ++ goto out_put_group; ++ } ++ cfg->streamids[0] = streamid; ++ cfg->mask = 0x7c00; ++ ++ pdev->dev_flags |= PCI_DEV_FLAGS_DMA_ALIAS_DEVID; ++ pdev->dma_alias_devid = streamid; ++#endif ++ ++ return 0; ++out_put_group: ++ iommu_group_put(group); ++ return ret; ++} ++ ++static int arm_smmu_add_platform_device(struct device *dev) ++{ + struct iommu_group *group; +- void (*releasefn)(void *) = NULL; +- int ret; ++ struct arm_smmu_master *master; ++ struct arm_smmu_device *smmu = find_smmu_for_device(dev); + +- smmu = find_smmu_for_device(dev); + if (!smmu) + return -ENODEV; + ++ master = find_smmu_master(smmu, dev->of_node); ++ if (!master) ++ return -ENODEV; ++ ++ /* No automatic group creation for platform devices */ + group = iommu_group_alloc(); +- if (IS_ERR(group)) { +- dev_err(dev, "Failed to allocate IOMMU group\n"); ++ if (IS_ERR(group)) + return PTR_ERR(group); ++ ++ iommu_group_set_iommudata(group, &master->cfg, NULL); ++ return iommu_group_add_device(group, dev); ++} ++ ++static int arm_smmu_add_device(struct device *dev) ++{ ++ if (dev_is_pci(dev)) ++ return arm_smmu_add_pci_device(to_pci_dev(dev)); ++ ++ return arm_smmu_add_platform_device(dev); ++} ++ ++static void arm_smmu_remove_device(struct device *dev) ++{ ++ iommu_group_remove_device(dev); ++} ++ ++static int arm_smmu_domain_get_attr(struct iommu_domain *domain, ++ enum iommu_attr attr, void *data) ++{ ++ struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain); ++ ++ switch (attr) { ++ case DOMAIN_ATTR_NESTING: ++ *(int *)data = (smmu_domain->stage == ARM_SMMU_DOMAIN_NESTED); ++ return 0; ++ default: ++ return -ENODEV; + } ++} + +- if (dev_is_pci(dev)) { +- struct pci_dev *pdev = to_pci_dev(dev); ++static int arm_smmu_domain_set_attr(struct iommu_domain *domain, ++ enum iommu_attr attr, void *data) ++{ ++ int ret = 0; ++ struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain); + +- cfg = kzalloc(sizeof(*cfg), GFP_KERNEL); +- if (!cfg) { +- ret = -ENOMEM; +- goto out_put_group; ++ mutex_lock(&smmu_domain->init_mutex); ++ ++ switch (attr) { ++ case DOMAIN_ATTR_NESTING: ++ if (smmu_domain->smmu) { ++ ret = -EPERM; ++ goto out_unlock; + } + +- cfg->num_streamids = 1; ++ if (*(int *)data) ++ smmu_domain->stage = ARM_SMMU_DOMAIN_NESTED; ++ else ++ smmu_domain->stage = ARM_SMMU_DOMAIN_S1; ++ ++ break; ++ default: ++ ret = -ENODEV; ++ } ++ ++out_unlock: ++ mutex_unlock(&smmu_domain->init_mutex); ++ return ret; ++} ++ ++static struct iommu_ops arm_smmu_ops = { ++ .capable = arm_smmu_capable, ++ .domain_alloc = arm_smmu_domain_alloc, ++ .domain_free = arm_smmu_domain_free, ++ .attach_dev = arm_smmu_attach_dev, ++ .detach_dev = arm_smmu_detach_dev, ++ .map = arm_smmu_map, ++ .unmap = arm_smmu_unmap, ++ .iova_to_phys = arm_smmu_iova_to_phys, ++ .add_device = arm_smmu_add_device, ++ .remove_device = arm_smmu_remove_device, ++ .domain_get_attr = arm_smmu_domain_get_attr, ++ .domain_set_attr = arm_smmu_domain_set_attr, ++ .pgsize_bitmap = -1UL, /* Restricted during device attach */ ++}; ++ ++#ifdef CONFIG_FSL_MC_BUS ++ ++static void arm_smmu_release_fsl_mc_iommudata(void *data) ++{ ++ kfree(data); ++} ++ ++/* ++ * IOMMU group creation and stream ID programming for ++ * the LS devices ++ * ++ */ ++static int arm_fsl_mc_smmu_add_device(struct device *dev) ++{ ++ struct device *cont_dev; ++ struct fsl_mc_device *mc_dev; ++ struct iommu_group *group; ++ struct arm_smmu_master_cfg *cfg; ++ int ret = 0; ++ ++ mc_dev = to_fsl_mc_device(dev); ++ if (mc_dev->flags & FSL_MC_IS_DPRC) ++ cont_dev = dev; ++ else ++ cont_dev = mc_dev->dev.parent; ++ ++ get_device(cont_dev); ++ group = iommu_group_get(cont_dev); ++ put_device(cont_dev); ++ if (!group) { ++ void (*releasefn)(void *) = NULL; ++ ++ group = iommu_group_alloc(); ++ if (IS_ERR(group)) ++ return PTR_ERR(group); + /* +- * Assume Stream ID == Requester ID for now. +- * We need a way to describe the ID mappings in FDT. ++ * allocate the cfg for the container and associate it with ++ * the iommu group. In the find cfg function we get the cfg ++ * from the iommu group. + */ +- pci_for_each_dma_alias(pdev, __arm_smmu_get_pci_sid, +- &cfg->streamids[0]); +- releasefn = __arm_smmu_release_pci_iommudata; +- } else { +- struct arm_smmu_master *master; +- +- master = find_smmu_master(smmu, dev->of_node); +- if (!master) { +- ret = -ENODEV; +- goto out_put_group; +- } ++ cfg = kzalloc(sizeof(*cfg), GFP_KERNEL); ++ if (!cfg) ++ return -ENOMEM; + +- cfg = &master->cfg; ++ mc_dev = to_fsl_mc_device(cont_dev); ++ cfg->num_streamids = 1; ++ cfg->streamids[0] = mc_dev->icid; ++ cfg->mask = 0x7c00; ++ releasefn = arm_smmu_release_fsl_mc_iommudata; ++ iommu_group_set_iommudata(group, cfg, releasefn); ++ ret = iommu_group_add_device(group, cont_dev); + } + +- iommu_group_set_iommudata(group, cfg, releasefn); +- ret = iommu_group_add_device(group, dev); ++ if (!ret && cont_dev != dev) ++ ret = iommu_group_add_device(group, dev); + +-out_put_group: + iommu_group_put(group); ++ + return ret; + } + +-static void arm_smmu_remove_device(struct device *dev) ++static void arm_fsl_mc_smmu_remove_device(struct device *dev) + { + iommu_group_remove_device(dev); ++ + } + +-static const struct iommu_ops arm_smmu_ops = { +- .capable = arm_smmu_capable, +- .domain_init = arm_smmu_domain_init, +- .domain_destroy = arm_smmu_domain_destroy, +- .attach_dev = arm_smmu_attach_dev, +- .detach_dev = arm_smmu_detach_dev, +- .map = arm_smmu_map, +- .unmap = arm_smmu_unmap, +- .iova_to_phys = arm_smmu_iova_to_phys, +- .add_device = arm_smmu_add_device, +- .remove_device = arm_smmu_remove_device, +- .pgsize_bitmap = (SECTION_SIZE | +- ARM_SMMU_PTE_CONT_SIZE | +- PAGE_SIZE), ++static struct iommu_ops arm_fsl_mc_smmu_ops = { ++ .capable = arm_smmu_capable, ++ .domain_alloc = arm_smmu_domain_alloc, ++ .domain_free = arm_smmu_domain_free, ++ .attach_dev = arm_smmu_attach_dev, ++ .detach_dev = arm_smmu_detach_dev, ++ .map = arm_smmu_map, ++ .unmap = arm_smmu_unmap, ++ .map_sg = default_iommu_map_sg, ++ .iova_to_phys = arm_smmu_iova_to_phys, ++ .add_device = arm_fsl_mc_smmu_add_device, ++ .remove_device = arm_fsl_mc_smmu_remove_device, ++ .domain_get_attr = arm_smmu_domain_get_attr, ++ .domain_set_attr = arm_smmu_domain_set_attr, ++ .pgsize_bitmap = -1UL, /* Restricted during device attach */ + }; ++#endif + + static void arm_smmu_device_reset(struct arm_smmu_device *smmu) + { +@@ -1686,7 +1644,6 @@ static void arm_smmu_device_reset(struct arm_smmu_device *smmu) + } + + /* Invalidate the TLB, just in case */ +- writel_relaxed(0, gr0_base + ARM_SMMU_GR0_STLBIALL); + writel_relaxed(0, gr0_base + ARM_SMMU_GR0_TLBIALLH); + writel_relaxed(0, gr0_base + ARM_SMMU_GR0_TLBIALLNSNH); + +@@ -1708,7 +1665,7 @@ static void arm_smmu_device_reset(struct arm_smmu_device *smmu) + reg &= ~(sCR0_BSU_MASK << sCR0_BSU_SHIFT); + + /* Push the button */ +- arm_smmu_tlb_sync(smmu); ++ __arm_smmu_tlb_sync(smmu); + writel(reg, ARM_SMMU_GR0_NS(smmu) + ARM_SMMU_GR0_sCR0); + } + +@@ -1742,12 +1699,6 @@ static int arm_smmu_device_cfg_probe(struct arm_smmu_device *smmu) + + /* ID0 */ + id = readl_relaxed(gr0_base + ARM_SMMU_GR0_ID0); +-#ifndef CONFIG_64BIT +- if (((id >> ID0_PTFS_SHIFT) & ID0_PTFS_MASK) == ID0_PTFS_V8_ONLY) { +- dev_err(smmu->dev, "\tno v7 descriptor support!\n"); +- return -ENODEV; +- } +-#endif + + /* Restrict available stages based on module parameter */ + if (force_stage == 1) +@@ -1776,6 +1727,11 @@ static int arm_smmu_device_cfg_probe(struct arm_smmu_device *smmu) + return -ENODEV; + } + ++ if ((id & ID0_S1TS) && ((smmu->version == 1) || !(id & ID0_ATOSNS))) { ++ smmu->features |= ARM_SMMU_FEAT_TRANS_OPS; ++ dev_notice(smmu->dev, "\taddress translation ops\n"); ++ } ++ + if (id & ID0_CTTW) { + smmu->features |= ARM_SMMU_FEAT_COHERENT_WALK; + dev_notice(smmu->dev, "\tcoherent table walk\n"); +@@ -1820,16 +1776,14 @@ static int arm_smmu_device_cfg_probe(struct arm_smmu_device *smmu) + smmu->pgshift = (id & ID1_PAGESIZE) ? 16 : 12; + + /* Check for size mismatch of SMMU address space from mapped region */ +- size = 1 << +- (((id >> ID1_NUMPAGENDXB_SHIFT) & ID1_NUMPAGENDXB_MASK) + 1); ++ size = 1 << (((id >> ID1_NUMPAGENDXB_SHIFT) & ID1_NUMPAGENDXB_MASK) + 1); + size *= 2 << smmu->pgshift; + if (smmu->size != size) + dev_warn(smmu->dev, + "SMMU address space size (0x%lx) differs from mapped region size (0x%lx)!\n", + size, smmu->size); + +- smmu->num_s2_context_banks = (id >> ID1_NUMS2CB_SHIFT) & +- ID1_NUMS2CB_MASK; ++ smmu->num_s2_context_banks = (id >> ID1_NUMS2CB_SHIFT) & ID1_NUMS2CB_MASK; + smmu->num_context_banks = (id >> ID1_NUMCB_SHIFT) & ID1_NUMCB_MASK; + if (smmu->num_s2_context_banks > smmu->num_context_banks) { + dev_err(smmu->dev, "impossible number of S2 context banks!\n"); +@@ -1841,46 +1795,49 @@ static int arm_smmu_device_cfg_probe(struct arm_smmu_device *smmu) + /* ID2 */ + id = readl_relaxed(gr0_base + ARM_SMMU_GR0_ID2); + size = arm_smmu_id_size_to_bits((id >> ID2_IAS_SHIFT) & ID2_IAS_MASK); +- smmu->s1_output_size = min_t(unsigned long, PHYS_MASK_SHIFT, size); +- +- /* Stage-2 input size limited due to pgd allocation (PTRS_PER_PGD) */ +-#ifdef CONFIG_64BIT +- smmu->s2_input_size = min_t(unsigned long, VA_BITS, size); +-#else +- smmu->s2_input_size = min(32UL, size); +-#endif ++ smmu->ipa_size = size; + +- /* The stage-2 output mask is also applied for bypass */ ++ /* The output mask is also applied for bypass */ + size = arm_smmu_id_size_to_bits((id >> ID2_OAS_SHIFT) & ID2_OAS_MASK); +- smmu->s2_output_size = min_t(unsigned long, PHYS_MASK_SHIFT, size); ++ smmu->pa_size = size; ++ ++ /* ++ * What the page table walker can address actually depends on which ++ * descriptor format is in use, but since a) we don't know that yet, ++ * and b) it can vary per context bank, this will have to do... ++ */ ++ if (dma_set_mask_and_coherent(smmu->dev, DMA_BIT_MASK(size))) ++ dev_warn(smmu->dev, ++ "failed to set DMA mask for table walker\n"); + + if (smmu->version == ARM_SMMU_V1) { +- smmu->s1_input_size = 32; ++ smmu->va_size = smmu->ipa_size; ++ size = SZ_4K | SZ_2M | SZ_1G; + } else { +-#ifdef CONFIG_64BIT + size = (id >> ID2_UBS_SHIFT) & ID2_UBS_MASK; +- size = min(VA_BITS, arm_smmu_id_size_to_bits(size)); +-#else +- size = 32; ++ smmu->va_size = arm_smmu_id_size_to_bits(size); ++#ifndef CONFIG_64BIT ++ smmu->va_size = min(32UL, smmu->va_size); + #endif +- smmu->s1_input_size = size; +- +- if ((PAGE_SIZE == SZ_4K && !(id & ID2_PTFS_4K)) || +- (PAGE_SIZE == SZ_64K && !(id & ID2_PTFS_64K)) || +- (PAGE_SIZE != SZ_4K && PAGE_SIZE != SZ_64K)) { +- dev_err(smmu->dev, "CPU page size 0x%lx unsupported\n", +- PAGE_SIZE); +- return -ENODEV; +- } ++ size = 0; ++ if (id & ID2_PTFS_4K) ++ size |= SZ_4K | SZ_2M | SZ_1G; ++ if (id & ID2_PTFS_16K) ++ size |= SZ_16K | SZ_32M; ++ if (id & ID2_PTFS_64K) ++ size |= SZ_64K | SZ_512M; + } + ++ arm_smmu_ops.pgsize_bitmap &= size; ++ dev_notice(smmu->dev, "\tSupported page sizes: 0x%08lx\n", size); ++ + if (smmu->features & ARM_SMMU_FEAT_TRANS_S1) + dev_notice(smmu->dev, "\tStage-1: %lu-bit VA -> %lu-bit IPA\n", +- smmu->s1_input_size, smmu->s1_output_size); ++ smmu->va_size, smmu->ipa_size); + + if (smmu->features & ARM_SMMU_FEAT_TRANS_S2) + dev_notice(smmu->dev, "\tStage-2: %lu-bit IPA -> %lu-bit PA\n", +- smmu->s2_input_size, smmu->s2_output_size); ++ smmu->ipa_size, smmu->pa_size); + + return 0; + } +@@ -2007,6 +1964,10 @@ static int arm_smmu_device_dt_probe(struct platform_device *pdev) + spin_unlock(&arm_smmu_devices_lock); + + arm_smmu_device_reset(smmu); ++ /* AIOP Rev1 errata work around */ ++#ifdef CONFIG_AIOP_ERRATA ++ arm_smmu_aiop_attr_trans(smmu); ++#endif + return 0; + + out_free_irqs: +@@ -2062,7 +2023,6 @@ static int arm_smmu_device_remove(struct platform_device *pdev) + + static struct platform_driver arm_smmu_driver = { + .driver = { +- .owner = THIS_MODULE, + .name = "arm-smmu", + .of_match_table = of_match_ptr(arm_smmu_of_match), + }, +@@ -2072,8 +2032,20 @@ static struct platform_driver arm_smmu_driver = { + + static int __init arm_smmu_init(void) + { ++ struct device_node *np; + int ret; + ++ /* ++ * Play nice with systems that don't have an ARM SMMU by checking that ++ * an ARM SMMU exists in the system before proceeding with the driver ++ * and IOMMU bus operation registration. ++ */ ++ np = of_find_matching_node(NULL, arm_smmu_of_match); ++ if (!np) ++ return 0; ++ ++ of_node_put(np); ++ + ret = platform_driver_register(&arm_smmu_driver); + if (ret) + return ret; +@@ -2092,6 +2064,10 @@ static int __init arm_smmu_init(void) + bus_set_iommu(&pci_bus_type, &arm_smmu_ops); + #endif + ++#ifdef CONFIG_FSL_MC_BUS ++ if (!iommu_present(&fsl_mc_bus_type)) ++ bus_set_iommu(&fsl_mc_bus_type, &arm_fsl_mc_smmu_ops); ++#endif + return 0; + } + +diff --git a/drivers/iommu/exynos-iommu.c b/drivers/iommu/exynos-iommu.c +index 7423318..7ce5273 100644 +--- a/drivers/iommu/exynos-iommu.c ++++ b/drivers/iommu/exynos-iommu.c +@@ -684,7 +684,6 @@ static const struct of_device_id sysmmu_of_match[] __initconst = { + static struct platform_driver exynos_sysmmu_driver __refdata = { + .probe = exynos_sysmmu_probe, + .driver = { +- .owner = THIS_MODULE, + .name = "exynos-sysmmu", + .of_match_table = sysmmu_of_match, + } +@@ -1178,6 +1177,7 @@ static const struct iommu_ops exynos_iommu_ops = { + .detach_dev = exynos_iommu_detach_device, + .map = exynos_iommu_map, + .unmap = exynos_iommu_unmap, ++ .map_sg = default_iommu_map_sg, + .iova_to_phys = exynos_iommu_iova_to_phys, + .add_device = exynos_iommu_add_device, + .remove_device = exynos_iommu_remove_device, +diff --git a/drivers/iommu/fsl_pamu.c b/drivers/iommu/fsl_pamu.c +index 2b6ce93..9396c85 100644 +--- a/drivers/iommu/fsl_pamu.c ++++ b/drivers/iommu/fsl_pamu.c +@@ -31,7 +31,7 @@ + #include + #include + #include +-#include ++#include + + #include "fsl_pamu.h" + +@@ -1227,7 +1227,6 @@ static const struct of_device_id fsl_of_pamu_ids[] = { + static struct platform_driver fsl_of_pamu_driver = { + .driver = { + .name = "fsl-of-pamu", +- .owner = THIS_MODULE, + }, + .probe = fsl_pamu_probe, + }; +diff --git a/drivers/iommu/intel-iommu.c b/drivers/iommu/intel-iommu.c +index 3d1fc73..9e97328 100644 +--- a/drivers/iommu/intel-iommu.c ++++ b/drivers/iommu/intel-iommu.c +@@ -4474,6 +4474,7 @@ static const struct iommu_ops intel_iommu_ops = { + .detach_dev = intel_iommu_detach_device, + .map = intel_iommu_map, + .unmap = intel_iommu_unmap, ++ .map_sg = default_iommu_map_sg, + .iova_to_phys = intel_iommu_iova_to_phys, + .add_device = intel_iommu_add_device, + .remove_device = intel_iommu_remove_device, +diff --git a/drivers/iommu/io-pgtable-arm.c b/drivers/iommu/io-pgtable-arm.c +new file mode 100644 +index 0000000..fd6dd22 +--- /dev/null ++++ b/drivers/iommu/io-pgtable-arm.c +@@ -0,0 +1,997 @@ ++/* ++ * CPU-agnostic ARM page table allocator. ++ * ++ * 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. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ * ++ * Copyright (C) 2014 ARM Limited ++ * ++ * Author: Will Deacon ++ */ ++ ++#define pr_fmt(fmt) "arm-lpae io-pgtable: " fmt ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "io-pgtable.h" ++ ++#define ARM_LPAE_MAX_ADDR_BITS 48 ++#define ARM_LPAE_S2_MAX_CONCAT_PAGES 16 ++#define ARM_LPAE_MAX_LEVELS 4 ++ ++/* Struct accessors */ ++#define io_pgtable_to_data(x) \ ++ container_of((x), struct arm_lpae_io_pgtable, iop) ++ ++#define io_pgtable_ops_to_pgtable(x) \ ++ container_of((x), struct io_pgtable, ops) ++ ++#define io_pgtable_ops_to_data(x) \ ++ io_pgtable_to_data(io_pgtable_ops_to_pgtable(x)) ++ ++/* ++ * For consistency with the architecture, we always consider ++ * ARM_LPAE_MAX_LEVELS levels, with the walk starting at level n >=0 ++ */ ++#define ARM_LPAE_START_LVL(d) (ARM_LPAE_MAX_LEVELS - (d)->levels) ++ ++/* ++ * Calculate the right shift amount to get to the portion describing level l ++ * in a virtual address mapped by the pagetable in d. ++ */ ++#define ARM_LPAE_LVL_SHIFT(l,d) \ ++ ((((d)->levels - ((l) - ARM_LPAE_START_LVL(d) + 1)) \ ++ * (d)->bits_per_level) + (d)->pg_shift) ++ ++#define ARM_LPAE_PAGES_PER_PGD(d) \ ++ DIV_ROUND_UP((d)->pgd_size, 1UL << (d)->pg_shift) ++ ++/* ++ * Calculate the index at level l used to map virtual address a using the ++ * pagetable in d. ++ */ ++#define ARM_LPAE_PGD_IDX(l,d) \ ++ ((l) == ARM_LPAE_START_LVL(d) ? ilog2(ARM_LPAE_PAGES_PER_PGD(d)) : 0) ++ ++#define ARM_LPAE_LVL_IDX(a,l,d) \ ++ (((u64)(a) >> ARM_LPAE_LVL_SHIFT(l,d)) & \ ++ ((1 << ((d)->bits_per_level + ARM_LPAE_PGD_IDX(l,d))) - 1)) ++ ++/* Calculate the block/page mapping size at level l for pagetable in d. */ ++#define ARM_LPAE_BLOCK_SIZE(l,d) \ ++ (1 << (ilog2(sizeof(arm_lpae_iopte)) + \ ++ ((ARM_LPAE_MAX_LEVELS - (l)) * (d)->bits_per_level))) ++ ++/* Page table bits */ ++#define ARM_LPAE_PTE_TYPE_SHIFT 0 ++#define ARM_LPAE_PTE_TYPE_MASK 0x3 ++ ++#define ARM_LPAE_PTE_TYPE_BLOCK 1 ++#define ARM_LPAE_PTE_TYPE_TABLE 3 ++#define ARM_LPAE_PTE_TYPE_PAGE 3 ++ ++#define ARM_LPAE_PTE_NSTABLE (((arm_lpae_iopte)1) << 63) ++#define ARM_LPAE_PTE_XN (((arm_lpae_iopte)3) << 53) ++#define ARM_LPAE_PTE_AF (((arm_lpae_iopte)1) << 10) ++#define ARM_LPAE_PTE_SH_NS (((arm_lpae_iopte)0) << 8) ++#define ARM_LPAE_PTE_SH_OS (((arm_lpae_iopte)2) << 8) ++#define ARM_LPAE_PTE_SH_IS (((arm_lpae_iopte)3) << 8) ++#define ARM_LPAE_PTE_NS (((arm_lpae_iopte)1) << 5) ++#define ARM_LPAE_PTE_VALID (((arm_lpae_iopte)1) << 0) ++ ++#define ARM_LPAE_PTE_ATTR_LO_MASK (((arm_lpae_iopte)0x3ff) << 2) ++/* Ignore the contiguous bit for block splitting */ ++#define ARM_LPAE_PTE_ATTR_HI_MASK (((arm_lpae_iopte)6) << 52) ++#define ARM_LPAE_PTE_ATTR_MASK (ARM_LPAE_PTE_ATTR_LO_MASK | \ ++ ARM_LPAE_PTE_ATTR_HI_MASK) ++ ++/* Stage-1 PTE */ ++#define ARM_LPAE_PTE_AP_UNPRIV (((arm_lpae_iopte)1) << 6) ++#define ARM_LPAE_PTE_AP_RDONLY (((arm_lpae_iopte)2) << 6) ++#define ARM_LPAE_PTE_ATTRINDX_SHIFT 2 ++#define ARM_LPAE_PTE_nG (((arm_lpae_iopte)1) << 11) ++ ++/* Stage-2 PTE */ ++#define ARM_LPAE_PTE_HAP_FAULT (((arm_lpae_iopte)0) << 6) ++#define ARM_LPAE_PTE_HAP_READ (((arm_lpae_iopte)1) << 6) ++#define ARM_LPAE_PTE_HAP_WRITE (((arm_lpae_iopte)2) << 6) ++#define ARM_LPAE_PTE_MEMATTR_OIWB (((arm_lpae_iopte)0xf) << 2) ++#define ARM_LPAE_PTE_MEMATTR_NC (((arm_lpae_iopte)0x5) << 2) ++#define ARM_LPAE_PTE_MEMATTR_DEV (((arm_lpae_iopte)0x1) << 2) ++ ++/* Register bits */ ++#define ARM_32_LPAE_TCR_EAE (1 << 31) ++#define ARM_64_LPAE_S2_TCR_RES1 (1 << 31) ++ ++#define ARM_LPAE_TCR_EPD1 (1 << 23) ++ ++#define ARM_LPAE_TCR_TG0_4K (0 << 14) ++#define ARM_LPAE_TCR_TG0_64K (1 << 14) ++#define ARM_LPAE_TCR_TG0_16K (2 << 14) ++ ++#define ARM_LPAE_TCR_SH0_SHIFT 12 ++#define ARM_LPAE_TCR_SH0_MASK 0x3 ++#define ARM_LPAE_TCR_SH_NS 0 ++#define ARM_LPAE_TCR_SH_OS 2 ++#define ARM_LPAE_TCR_SH_IS 3 ++ ++#define ARM_LPAE_TCR_ORGN0_SHIFT 10 ++#define ARM_LPAE_TCR_IRGN0_SHIFT 8 ++#define ARM_LPAE_TCR_RGN_MASK 0x3 ++#define ARM_LPAE_TCR_RGN_NC 0 ++#define ARM_LPAE_TCR_RGN_WBWA 1 ++#define ARM_LPAE_TCR_RGN_WT 2 ++#define ARM_LPAE_TCR_RGN_WB 3 ++ ++#define ARM_LPAE_TCR_SL0_SHIFT 6 ++#define ARM_LPAE_TCR_SL0_MASK 0x3 ++ ++#define ARM_LPAE_TCR_T0SZ_SHIFT 0 ++#define ARM_LPAE_TCR_SZ_MASK 0xf ++ ++#define ARM_LPAE_TCR_PS_SHIFT 16 ++#define ARM_LPAE_TCR_PS_MASK 0x7 ++ ++#define ARM_LPAE_TCR_IPS_SHIFT 32 ++#define ARM_LPAE_TCR_IPS_MASK 0x7 ++ ++#define ARM_LPAE_TCR_PS_32_BIT 0x0ULL ++#define ARM_LPAE_TCR_PS_36_BIT 0x1ULL ++#define ARM_LPAE_TCR_PS_40_BIT 0x2ULL ++#define ARM_LPAE_TCR_PS_42_BIT 0x3ULL ++#define ARM_LPAE_TCR_PS_44_BIT 0x4ULL ++#define ARM_LPAE_TCR_PS_48_BIT 0x5ULL ++ ++#define ARM_LPAE_MAIR_ATTR_SHIFT(n) ((n) << 3) ++#define ARM_LPAE_MAIR_ATTR_MASK 0xff ++#define ARM_LPAE_MAIR_ATTR_DEVICE 0x04 ++#define ARM_LPAE_MAIR_ATTR_NC 0x44 ++#define ARM_LPAE_MAIR_ATTR_WBRWA 0xff ++#define ARM_LPAE_MAIR_ATTR_IDX_NC 0 ++#define ARM_LPAE_MAIR_ATTR_IDX_CACHE 1 ++#define ARM_LPAE_MAIR_ATTR_IDX_DEV 2 ++ ++/* IOPTE accessors */ ++#define iopte_deref(pte,d) \ ++ (__va((pte) & ((1ULL << ARM_LPAE_MAX_ADDR_BITS) - 1) \ ++ & ~((1ULL << (d)->pg_shift) - 1))) ++ ++#define iopte_type(pte,l) \ ++ (((pte) >> ARM_LPAE_PTE_TYPE_SHIFT) & ARM_LPAE_PTE_TYPE_MASK) ++ ++#define iopte_prot(pte) ((pte) & ARM_LPAE_PTE_ATTR_MASK) ++ ++#define iopte_leaf(pte,l) \ ++ (l == (ARM_LPAE_MAX_LEVELS - 1) ? \ ++ (iopte_type(pte,l) == ARM_LPAE_PTE_TYPE_PAGE) : \ ++ (iopte_type(pte,l) == ARM_LPAE_PTE_TYPE_BLOCK)) ++ ++#define iopte_to_pfn(pte,d) \ ++ (((pte) & ((1ULL << ARM_LPAE_MAX_ADDR_BITS) - 1)) >> (d)->pg_shift) ++ ++#define pfn_to_iopte(pfn,d) \ ++ (((pfn) << (d)->pg_shift) & ((1ULL << ARM_LPAE_MAX_ADDR_BITS) - 1)) ++ ++struct arm_lpae_io_pgtable { ++ struct io_pgtable iop; ++ ++ int levels; ++ size_t pgd_size; ++ unsigned long pg_shift; ++ unsigned long bits_per_level; ++ ++ void *pgd; ++}; ++ ++typedef u64 arm_lpae_iopte; ++ ++static bool selftest_running = false; ++ ++static int arm_lpae_init_pte(struct arm_lpae_io_pgtable *data, ++ unsigned long iova, phys_addr_t paddr, ++ arm_lpae_iopte prot, int lvl, ++ arm_lpae_iopte *ptep) ++{ ++ arm_lpae_iopte pte = prot; ++ ++ /* We require an unmap first */ ++ if (iopte_leaf(*ptep, lvl)) { ++ WARN_ON(!selftest_running); ++ return -EEXIST; ++ } ++ ++ if (data->iop.cfg.quirks & IO_PGTABLE_QUIRK_ARM_NS) ++ pte |= ARM_LPAE_PTE_NS; ++ ++ if (lvl == ARM_LPAE_MAX_LEVELS - 1) ++ pte |= ARM_LPAE_PTE_TYPE_PAGE; ++ else ++ pte |= ARM_LPAE_PTE_TYPE_BLOCK; ++ ++ pte |= ARM_LPAE_PTE_AF | ARM_LPAE_PTE_SH_IS; ++ pte |= pfn_to_iopte(paddr >> data->pg_shift, data); ++ ++ *ptep = pte; ++ data->iop.cfg.tlb->flush_pgtable(ptep, sizeof(*ptep), data->iop.cookie); ++ return 0; ++} ++ ++static int __arm_lpae_map(struct arm_lpae_io_pgtable *data, unsigned long iova, ++ phys_addr_t paddr, size_t size, arm_lpae_iopte prot, ++ int lvl, arm_lpae_iopte *ptep) ++{ ++ arm_lpae_iopte *cptep, pte; ++ void *cookie = data->iop.cookie; ++ size_t block_size = ARM_LPAE_BLOCK_SIZE(lvl, data); ++ ++ /* Find our entry at the current level */ ++ ptep += ARM_LPAE_LVL_IDX(iova, lvl, data); ++ ++ /* If we can install a leaf entry at this level, then do so */ ++ if (size == block_size && (size & data->iop.cfg.pgsize_bitmap)) ++ return arm_lpae_init_pte(data, iova, paddr, prot, lvl, ptep); ++ ++ /* We can't allocate tables at the final level */ ++ if (WARN_ON(lvl >= ARM_LPAE_MAX_LEVELS - 1)) ++ return -EINVAL; ++ ++ /* Grab a pointer to the next level */ ++ pte = *ptep; ++ if (!pte) { ++ cptep = alloc_pages_exact(1UL << data->pg_shift, ++ GFP_ATOMIC | __GFP_ZERO); ++ if (!cptep) ++ return -ENOMEM; ++ ++ data->iop.cfg.tlb->flush_pgtable(cptep, 1UL << data->pg_shift, ++ cookie); ++ pte = __pa(cptep) | ARM_LPAE_PTE_TYPE_TABLE; ++ if (data->iop.cfg.quirks & IO_PGTABLE_QUIRK_ARM_NS) ++ pte |= ARM_LPAE_PTE_NSTABLE; ++ *ptep = pte; ++ data->iop.cfg.tlb->flush_pgtable(ptep, sizeof(*ptep), cookie); ++ } else { ++ cptep = iopte_deref(pte, data); ++ } ++ ++ /* Rinse, repeat */ ++ return __arm_lpae_map(data, iova, paddr, size, prot, lvl + 1, cptep); ++} ++ ++static arm_lpae_iopte arm_lpae_prot_to_pte(struct arm_lpae_io_pgtable *data, ++ int prot) ++{ ++ arm_lpae_iopte pte; ++ ++ if (data->iop.fmt == ARM_64_LPAE_S1 || ++ data->iop.fmt == ARM_32_LPAE_S1) { ++ pte = ARM_LPAE_PTE_AP_UNPRIV | ARM_LPAE_PTE_nG; ++ ++ if (!(prot & IOMMU_WRITE) && (prot & IOMMU_READ)) ++ pte |= ARM_LPAE_PTE_AP_RDONLY; ++ ++ if (prot & IOMMU_CACHE) ++ pte |= (ARM_LPAE_MAIR_ATTR_IDX_CACHE ++ << ARM_LPAE_PTE_ATTRINDX_SHIFT); ++ else if (prot & IOMMU_MMIO) ++ pte |= (ARM_LPAE_MAIR_ATTR_IDX_DEV ++ << ARM_LPAE_PTE_ATTRINDX_SHIFT); ++ } else { ++ pte = ARM_LPAE_PTE_HAP_FAULT; ++ if (prot & IOMMU_READ) ++ pte |= ARM_LPAE_PTE_HAP_READ; ++ if (prot & IOMMU_WRITE) ++ pte |= ARM_LPAE_PTE_HAP_WRITE; ++ if (prot & IOMMU_CACHE) ++ pte |= ARM_LPAE_PTE_MEMATTR_OIWB; ++ else if (prot & IOMMU_MMIO) ++ pte |= ARM_LPAE_PTE_MEMATTR_DEV; ++ else ++ pte |= ARM_LPAE_PTE_MEMATTR_NC; ++ } ++ ++ if (prot & IOMMU_NOEXEC) ++ pte |= ARM_LPAE_PTE_XN; ++ ++ return pte; ++} ++ ++static int arm_lpae_map(struct io_pgtable_ops *ops, unsigned long iova, ++ phys_addr_t paddr, size_t size, int iommu_prot) ++{ ++ struct arm_lpae_io_pgtable *data = io_pgtable_ops_to_data(ops); ++ arm_lpae_iopte *ptep = data->pgd; ++ int lvl = ARM_LPAE_START_LVL(data); ++ arm_lpae_iopte prot; ++ ++ /* If no access, then nothing to do */ ++ if (!(iommu_prot & (IOMMU_READ | IOMMU_WRITE))) ++ return 0; ++ ++ prot = arm_lpae_prot_to_pte(data, iommu_prot); ++ return __arm_lpae_map(data, iova, paddr, size, prot, lvl, ptep); ++} ++ ++static void __arm_lpae_free_pgtable(struct arm_lpae_io_pgtable *data, int lvl, ++ arm_lpae_iopte *ptep) ++{ ++ arm_lpae_iopte *start, *end; ++ unsigned long table_size; ++ ++ /* Only leaf entries at the last level */ ++ if (lvl == ARM_LPAE_MAX_LEVELS - 1) ++ return; ++ ++ if (lvl == ARM_LPAE_START_LVL(data)) ++ table_size = data->pgd_size; ++ else ++ table_size = 1UL << data->pg_shift; ++ ++ start = ptep; ++ end = (void *)ptep + table_size; ++ ++ while (ptep != end) { ++ arm_lpae_iopte pte = *ptep++; ++ ++ if (!pte || iopte_leaf(pte, lvl)) ++ continue; ++ ++ __arm_lpae_free_pgtable(data, lvl + 1, iopte_deref(pte, data)); ++ } ++ ++ free_pages_exact(start, table_size); ++} ++ ++static void arm_lpae_free_pgtable(struct io_pgtable *iop) ++{ ++ struct arm_lpae_io_pgtable *data = io_pgtable_to_data(iop); ++ ++ __arm_lpae_free_pgtable(data, ARM_LPAE_START_LVL(data), data->pgd); ++ kfree(data); ++} ++ ++static int arm_lpae_split_blk_unmap(struct arm_lpae_io_pgtable *data, ++ unsigned long iova, size_t size, ++ arm_lpae_iopte prot, int lvl, ++ arm_lpae_iopte *ptep, size_t blk_size) ++{ ++ unsigned long blk_start, blk_end; ++ phys_addr_t blk_paddr; ++ arm_lpae_iopte table = 0; ++ void *cookie = data->iop.cookie; ++ const struct iommu_gather_ops *tlb = data->iop.cfg.tlb; ++ ++ blk_start = iova & ~(blk_size - 1); ++ blk_end = blk_start + blk_size; ++ blk_paddr = iopte_to_pfn(*ptep, data) << data->pg_shift; ++ ++ for (; blk_start < blk_end; blk_start += size, blk_paddr += size) { ++ arm_lpae_iopte *tablep; ++ ++ /* Unmap! */ ++ if (blk_start == iova) ++ continue; ++ ++ /* __arm_lpae_map expects a pointer to the start of the table */ ++ tablep = &table - ARM_LPAE_LVL_IDX(blk_start, lvl, data); ++ if (__arm_lpae_map(data, blk_start, blk_paddr, size, prot, lvl, ++ tablep) < 0) { ++ if (table) { ++ /* Free the table we allocated */ ++ tablep = iopte_deref(table, data); ++ __arm_lpae_free_pgtable(data, lvl + 1, tablep); ++ } ++ return 0; /* Bytes unmapped */ ++ } ++ } ++ ++ *ptep = table; ++ tlb->flush_pgtable(ptep, sizeof(*ptep), cookie); ++ iova &= ~(blk_size - 1); ++ tlb->tlb_add_flush(iova, blk_size, true, cookie); ++ return size; ++} ++ ++static int __arm_lpae_unmap(struct arm_lpae_io_pgtable *data, ++ unsigned long iova, size_t size, int lvl, ++ arm_lpae_iopte *ptep) ++{ ++ arm_lpae_iopte pte; ++ const struct iommu_gather_ops *tlb = data->iop.cfg.tlb; ++ void *cookie = data->iop.cookie; ++ size_t blk_size = ARM_LPAE_BLOCK_SIZE(lvl, data); ++ ++ ptep += ARM_LPAE_LVL_IDX(iova, lvl, data); ++ pte = *ptep; ++ ++ /* Something went horribly wrong and we ran out of page table */ ++ if (WARN_ON(!pte || (lvl == ARM_LPAE_MAX_LEVELS))) ++ return 0; ++ ++ /* If the size matches this level, we're in the right place */ ++ if (size == blk_size) { ++ *ptep = 0; ++ tlb->flush_pgtable(ptep, sizeof(*ptep), cookie); ++ ++ if (!iopte_leaf(pte, lvl)) { ++ /* Also flush any partial walks */ ++ tlb->tlb_add_flush(iova, size, false, cookie); ++ tlb->tlb_sync(data->iop.cookie); ++ ptep = iopte_deref(pte, data); ++ __arm_lpae_free_pgtable(data, lvl + 1, ptep); ++ } else { ++ tlb->tlb_add_flush(iova, size, true, cookie); ++ } ++ ++ return size; ++ } else if (iopte_leaf(pte, lvl)) { ++ /* ++ * Insert a table at the next level to map the old region, ++ * minus the part we want to unmap ++ */ ++ return arm_lpae_split_blk_unmap(data, iova, size, ++ iopte_prot(pte), lvl, ptep, ++ blk_size); ++ } ++ ++ /* Keep on walkin' */ ++ ptep = iopte_deref(pte, data); ++ return __arm_lpae_unmap(data, iova, size, lvl + 1, ptep); ++} ++ ++static int arm_lpae_unmap(struct io_pgtable_ops *ops, unsigned long iova, ++ size_t size) ++{ ++ size_t unmapped; ++ struct arm_lpae_io_pgtable *data = io_pgtable_ops_to_data(ops); ++ struct io_pgtable *iop = &data->iop; ++ arm_lpae_iopte *ptep = data->pgd; ++ int lvl = ARM_LPAE_START_LVL(data); ++ ++ unmapped = __arm_lpae_unmap(data, iova, size, lvl, ptep); ++ if (unmapped) ++ iop->cfg.tlb->tlb_sync(iop->cookie); ++ ++ return unmapped; ++} ++ ++static phys_addr_t arm_lpae_iova_to_phys(struct io_pgtable_ops *ops, ++ unsigned long iova) ++{ ++ struct arm_lpae_io_pgtable *data = io_pgtable_ops_to_data(ops); ++ arm_lpae_iopte pte, *ptep = data->pgd; ++ int lvl = ARM_LPAE_START_LVL(data); ++ ++ do { ++ /* Valid IOPTE pointer? */ ++ if (!ptep) ++ return 0; ++ ++ /* Grab the IOPTE we're interested in */ ++ pte = *(ptep + ARM_LPAE_LVL_IDX(iova, lvl, data)); ++ ++ /* Valid entry? */ ++ if (!pte) ++ return 0; ++ ++ /* Leaf entry? */ ++ if (iopte_leaf(pte,lvl)) ++ goto found_translation; ++ ++ /* Take it to the next level */ ++ ptep = iopte_deref(pte, data); ++ } while (++lvl < ARM_LPAE_MAX_LEVELS); ++ ++ /* Ran out of page tables to walk */ ++ return 0; ++ ++found_translation: ++ iova &= ((1 << data->pg_shift) - 1); ++ return ((phys_addr_t)iopte_to_pfn(pte,data) << data->pg_shift) | iova; ++} ++ ++static void arm_lpae_restrict_pgsizes(struct io_pgtable_cfg *cfg) ++{ ++ unsigned long granule; ++ ++ /* ++ * We need to restrict the supported page sizes to match the ++ * translation regime for a particular granule. Aim to match ++ * the CPU page size if possible, otherwise prefer smaller sizes. ++ * While we're at it, restrict the block sizes to match the ++ * chosen granule. ++ */ ++ if (cfg->pgsize_bitmap & PAGE_SIZE) ++ granule = PAGE_SIZE; ++ else if (cfg->pgsize_bitmap & ~PAGE_MASK) ++ granule = 1UL << __fls(cfg->pgsize_bitmap & ~PAGE_MASK); ++ else if (cfg->pgsize_bitmap & PAGE_MASK) ++ granule = 1UL << __ffs(cfg->pgsize_bitmap & PAGE_MASK); ++ else ++ granule = 0; ++ ++ switch (granule) { ++ case SZ_4K: ++ cfg->pgsize_bitmap &= (SZ_4K | SZ_2M | SZ_1G); ++ break; ++ case SZ_16K: ++ cfg->pgsize_bitmap &= (SZ_16K | SZ_32M); ++ break; ++ case SZ_64K: ++ cfg->pgsize_bitmap &= (SZ_64K | SZ_512M); ++ break; ++ default: ++ cfg->pgsize_bitmap = 0; ++ } ++} ++ ++static struct arm_lpae_io_pgtable * ++arm_lpae_alloc_pgtable(struct io_pgtable_cfg *cfg) ++{ ++ unsigned long va_bits, pgd_bits; ++ struct arm_lpae_io_pgtable *data; ++ ++ arm_lpae_restrict_pgsizes(cfg); ++ ++ if (!(cfg->pgsize_bitmap & (SZ_4K | SZ_16K | SZ_64K))) ++ return NULL; ++ ++ if (cfg->ias > ARM_LPAE_MAX_ADDR_BITS) ++ return NULL; ++ ++ if (cfg->oas > ARM_LPAE_MAX_ADDR_BITS) ++ return NULL; ++ ++ data = kmalloc(sizeof(*data), GFP_KERNEL); ++ if (!data) ++ return NULL; ++ ++ data->pg_shift = __ffs(cfg->pgsize_bitmap); ++ data->bits_per_level = data->pg_shift - ilog2(sizeof(arm_lpae_iopte)); ++ ++ va_bits = cfg->ias - data->pg_shift; ++ data->levels = DIV_ROUND_UP(va_bits, data->bits_per_level); ++ ++ /* Calculate the actual size of our pgd (without concatenation) */ ++ pgd_bits = va_bits - (data->bits_per_level * (data->levels - 1)); ++ data->pgd_size = 1UL << (pgd_bits + ilog2(sizeof(arm_lpae_iopte))); ++ ++ data->iop.ops = (struct io_pgtable_ops) { ++ .map = arm_lpae_map, ++ .unmap = arm_lpae_unmap, ++ .iova_to_phys = arm_lpae_iova_to_phys, ++ }; ++ ++ return data; ++} ++ ++static struct io_pgtable * ++arm_64_lpae_alloc_pgtable_s1(struct io_pgtable_cfg *cfg, void *cookie) ++{ ++ u64 reg; ++ struct arm_lpae_io_pgtable *data = arm_lpae_alloc_pgtable(cfg); ++ ++ if (!data) ++ return NULL; ++ ++ /* TCR */ ++ reg = (ARM_LPAE_TCR_SH_IS << ARM_LPAE_TCR_SH0_SHIFT) | ++ (ARM_LPAE_TCR_RGN_WBWA << ARM_LPAE_TCR_IRGN0_SHIFT) | ++ (ARM_LPAE_TCR_RGN_WBWA << ARM_LPAE_TCR_ORGN0_SHIFT); ++ ++ switch (1 << data->pg_shift) { ++ case SZ_4K: ++ reg |= ARM_LPAE_TCR_TG0_4K; ++ break; ++ case SZ_16K: ++ reg |= ARM_LPAE_TCR_TG0_16K; ++ break; ++ case SZ_64K: ++ reg |= ARM_LPAE_TCR_TG0_64K; ++ break; ++ } ++ ++ switch (cfg->oas) { ++ case 32: ++ reg |= (ARM_LPAE_TCR_PS_32_BIT << ARM_LPAE_TCR_IPS_SHIFT); ++ break; ++ case 36: ++ reg |= (ARM_LPAE_TCR_PS_36_BIT << ARM_LPAE_TCR_IPS_SHIFT); ++ break; ++ case 40: ++ reg |= (ARM_LPAE_TCR_PS_40_BIT << ARM_LPAE_TCR_IPS_SHIFT); ++ break; ++ case 42: ++ reg |= (ARM_LPAE_TCR_PS_42_BIT << ARM_LPAE_TCR_IPS_SHIFT); ++ break; ++ case 44: ++ reg |= (ARM_LPAE_TCR_PS_44_BIT << ARM_LPAE_TCR_IPS_SHIFT); ++ break; ++ case 48: ++ reg |= (ARM_LPAE_TCR_PS_48_BIT << ARM_LPAE_TCR_IPS_SHIFT); ++ break; ++ default: ++ goto out_free_data; ++ } ++ ++ reg |= (64ULL - cfg->ias) << ARM_LPAE_TCR_T0SZ_SHIFT; ++ ++ /* Disable speculative walks through TTBR1 */ ++ reg |= ARM_LPAE_TCR_EPD1; ++ cfg->arm_lpae_s1_cfg.tcr = reg; ++ ++ /* MAIRs */ ++ reg = (ARM_LPAE_MAIR_ATTR_NC ++ << ARM_LPAE_MAIR_ATTR_SHIFT(ARM_LPAE_MAIR_ATTR_IDX_NC)) | ++ (ARM_LPAE_MAIR_ATTR_WBRWA ++ << ARM_LPAE_MAIR_ATTR_SHIFT(ARM_LPAE_MAIR_ATTR_IDX_CACHE)) | ++ (ARM_LPAE_MAIR_ATTR_DEVICE ++ << ARM_LPAE_MAIR_ATTR_SHIFT(ARM_LPAE_MAIR_ATTR_IDX_DEV)); ++ ++ cfg->arm_lpae_s1_cfg.mair[0] = reg; ++ cfg->arm_lpae_s1_cfg.mair[1] = 0; ++ ++ /* Looking good; allocate a pgd */ ++ data->pgd = alloc_pages_exact(data->pgd_size, GFP_KERNEL | __GFP_ZERO); ++ if (!data->pgd) ++ goto out_free_data; ++ ++ cfg->tlb->flush_pgtable(data->pgd, data->pgd_size, cookie); ++ ++ /* TTBRs */ ++ cfg->arm_lpae_s1_cfg.ttbr[0] = virt_to_phys(data->pgd); ++ cfg->arm_lpae_s1_cfg.ttbr[1] = 0; ++ return &data->iop; ++ ++out_free_data: ++ kfree(data); ++ return NULL; ++} ++ ++static struct io_pgtable * ++arm_64_lpae_alloc_pgtable_s2(struct io_pgtable_cfg *cfg, void *cookie) ++{ ++ u64 reg, sl; ++ struct arm_lpae_io_pgtable *data = arm_lpae_alloc_pgtable(cfg); ++ ++ if (!data) ++ return NULL; ++ ++ /* ++ * Concatenate PGDs at level 1 if possible in order to reduce ++ * the depth of the stage-2 walk. ++ */ ++ if (data->levels == ARM_LPAE_MAX_LEVELS) { ++ unsigned long pgd_pages; ++ ++ pgd_pages = data->pgd_size >> ilog2(sizeof(arm_lpae_iopte)); ++ if (pgd_pages <= ARM_LPAE_S2_MAX_CONCAT_PAGES) { ++ data->pgd_size = pgd_pages << data->pg_shift; ++ data->levels--; ++ } ++ } ++ ++ /* VTCR */ ++ reg = ARM_64_LPAE_S2_TCR_RES1 | ++ (ARM_LPAE_TCR_SH_IS << ARM_LPAE_TCR_SH0_SHIFT) | ++ (ARM_LPAE_TCR_RGN_WBWA << ARM_LPAE_TCR_IRGN0_SHIFT) | ++ (ARM_LPAE_TCR_RGN_WBWA << ARM_LPAE_TCR_ORGN0_SHIFT); ++ ++ sl = ARM_LPAE_START_LVL(data); ++ ++ switch (1 << data->pg_shift) { ++ case SZ_4K: ++ reg |= ARM_LPAE_TCR_TG0_4K; ++ sl++; /* SL0 format is different for 4K granule size */ ++ break; ++ case SZ_16K: ++ reg |= ARM_LPAE_TCR_TG0_16K; ++ break; ++ case SZ_64K: ++ reg |= ARM_LPAE_TCR_TG0_64K; ++ break; ++ } ++ ++ switch (cfg->oas) { ++ case 32: ++ reg |= (ARM_LPAE_TCR_PS_32_BIT << ARM_LPAE_TCR_PS_SHIFT); ++ break; ++ case 36: ++ reg |= (ARM_LPAE_TCR_PS_36_BIT << ARM_LPAE_TCR_PS_SHIFT); ++ break; ++ case 40: ++ reg |= (ARM_LPAE_TCR_PS_40_BIT << ARM_LPAE_TCR_PS_SHIFT); ++ break; ++ case 42: ++ reg |= (ARM_LPAE_TCR_PS_42_BIT << ARM_LPAE_TCR_PS_SHIFT); ++ break; ++ case 44: ++ reg |= (ARM_LPAE_TCR_PS_44_BIT << ARM_LPAE_TCR_PS_SHIFT); ++ break; ++ case 48: ++ reg |= (ARM_LPAE_TCR_PS_48_BIT << ARM_LPAE_TCR_PS_SHIFT); ++ break; ++ default: ++ goto out_free_data; ++ } ++ ++ reg |= (64ULL - cfg->ias) << ARM_LPAE_TCR_T0SZ_SHIFT; ++ reg |= (~sl & ARM_LPAE_TCR_SL0_MASK) << ARM_LPAE_TCR_SL0_SHIFT; ++ cfg->arm_lpae_s2_cfg.vtcr = reg; ++ ++ /* Allocate pgd pages */ ++ data->pgd = alloc_pages_exact(data->pgd_size, GFP_KERNEL | __GFP_ZERO); ++ if (!data->pgd) ++ goto out_free_data; ++ ++ cfg->tlb->flush_pgtable(data->pgd, data->pgd_size, cookie); ++ ++ /* VTTBR */ ++ cfg->arm_lpae_s2_cfg.vttbr = virt_to_phys(data->pgd); ++ return &data->iop; ++ ++out_free_data: ++ kfree(data); ++ return NULL; ++} ++ ++static struct io_pgtable * ++arm_32_lpae_alloc_pgtable_s1(struct io_pgtable_cfg *cfg, void *cookie) ++{ ++ struct io_pgtable *iop; ++ ++ if (cfg->ias > 32 || cfg->oas > 40) ++ return NULL; ++ ++ cfg->pgsize_bitmap &= (SZ_4K | SZ_2M | SZ_1G); ++ iop = arm_64_lpae_alloc_pgtable_s1(cfg, cookie); ++ if (iop) { ++ cfg->arm_lpae_s1_cfg.tcr |= ARM_32_LPAE_TCR_EAE; ++ cfg->arm_lpae_s1_cfg.tcr &= 0xffffffff; ++ } ++ ++ return iop; ++} ++ ++static struct io_pgtable * ++arm_32_lpae_alloc_pgtable_s2(struct io_pgtable_cfg *cfg, void *cookie) ++{ ++ struct io_pgtable *iop; ++ ++ if (cfg->ias > 40 || cfg->oas > 40) ++ return NULL; ++ ++ cfg->pgsize_bitmap &= (SZ_4K | SZ_2M | SZ_1G); ++ iop = arm_64_lpae_alloc_pgtable_s2(cfg, cookie); ++ if (iop) ++ cfg->arm_lpae_s2_cfg.vtcr &= 0xffffffff; ++ ++ return iop; ++} ++ ++struct io_pgtable_init_fns io_pgtable_arm_64_lpae_s1_init_fns = { ++ .alloc = arm_64_lpae_alloc_pgtable_s1, ++ .free = arm_lpae_free_pgtable, ++}; ++ ++struct io_pgtable_init_fns io_pgtable_arm_64_lpae_s2_init_fns = { ++ .alloc = arm_64_lpae_alloc_pgtable_s2, ++ .free = arm_lpae_free_pgtable, ++}; ++ ++struct io_pgtable_init_fns io_pgtable_arm_32_lpae_s1_init_fns = { ++ .alloc = arm_32_lpae_alloc_pgtable_s1, ++ .free = arm_lpae_free_pgtable, ++}; ++ ++struct io_pgtable_init_fns io_pgtable_arm_32_lpae_s2_init_fns = { ++ .alloc = arm_32_lpae_alloc_pgtable_s2, ++ .free = arm_lpae_free_pgtable, ++}; ++ ++#ifdef CONFIG_IOMMU_IO_PGTABLE_LPAE_SELFTEST ++ ++static struct io_pgtable_cfg *cfg_cookie; ++ ++static void dummy_tlb_flush_all(void *cookie) ++{ ++ WARN_ON(cookie != cfg_cookie); ++} ++ ++static void dummy_tlb_add_flush(unsigned long iova, size_t size, bool leaf, ++ void *cookie) ++{ ++ WARN_ON(cookie != cfg_cookie); ++ WARN_ON(!(size & cfg_cookie->pgsize_bitmap)); ++} ++ ++static void dummy_tlb_sync(void *cookie) ++{ ++ WARN_ON(cookie != cfg_cookie); ++} ++ ++static void dummy_flush_pgtable(void *ptr, size_t size, void *cookie) ++{ ++ WARN_ON(cookie != cfg_cookie); ++} ++ ++static struct iommu_gather_ops dummy_tlb_ops __initdata = { ++ .tlb_flush_all = dummy_tlb_flush_all, ++ .tlb_add_flush = dummy_tlb_add_flush, ++ .tlb_sync = dummy_tlb_sync, ++ .flush_pgtable = dummy_flush_pgtable, ++}; ++ ++static void __init arm_lpae_dump_ops(struct io_pgtable_ops *ops) ++{ ++ struct arm_lpae_io_pgtable *data = io_pgtable_ops_to_data(ops); ++ struct io_pgtable_cfg *cfg = &data->iop.cfg; ++ ++ pr_err("cfg: pgsize_bitmap 0x%lx, ias %u-bit\n", ++ cfg->pgsize_bitmap, cfg->ias); ++ pr_err("data: %d levels, 0x%zx pgd_size, %lu pg_shift, %lu bits_per_level, pgd @ %p\n", ++ data->levels, data->pgd_size, data->pg_shift, ++ data->bits_per_level, data->pgd); ++} ++ ++#define __FAIL(ops, i) ({ \ ++ WARN(1, "selftest: test failed for fmt idx %d\n", (i)); \ ++ arm_lpae_dump_ops(ops); \ ++ selftest_running = false; \ ++ -EFAULT; \ ++}) ++ ++static int __init arm_lpae_run_tests(struct io_pgtable_cfg *cfg) ++{ ++ static const enum io_pgtable_fmt fmts[] = { ++ ARM_64_LPAE_S1, ++ ARM_64_LPAE_S2, ++ }; ++ ++ int i, j; ++ unsigned long iova; ++ size_t size; ++ struct io_pgtable_ops *ops; ++ ++ selftest_running = true; ++ ++ for (i = 0; i < ARRAY_SIZE(fmts); ++i) { ++ cfg_cookie = cfg; ++ ops = alloc_io_pgtable_ops(fmts[i], cfg, cfg); ++ if (!ops) { ++ pr_err("selftest: failed to allocate io pgtable ops\n"); ++ return -ENOMEM; ++ } ++ ++ /* ++ * Initial sanity checks. ++ * Empty page tables shouldn't provide any translations. ++ */ ++ if (ops->iova_to_phys(ops, 42)) ++ return __FAIL(ops, i); ++ ++ if (ops->iova_to_phys(ops, SZ_1G + 42)) ++ return __FAIL(ops, i); ++ ++ if (ops->iova_to_phys(ops, SZ_2G + 42)) ++ return __FAIL(ops, i); ++ ++ /* ++ * Distinct mappings of different granule sizes. ++ */ ++ iova = 0; ++ j = find_first_bit(&cfg->pgsize_bitmap, BITS_PER_LONG); ++ while (j != BITS_PER_LONG) { ++ size = 1UL << j; ++ ++ if (ops->map(ops, iova, iova, size, IOMMU_READ | ++ IOMMU_WRITE | ++ IOMMU_NOEXEC | ++ IOMMU_CACHE)) ++ return __FAIL(ops, i); ++ ++ /* Overlapping mappings */ ++ if (!ops->map(ops, iova, iova + size, size, ++ IOMMU_READ | IOMMU_NOEXEC)) ++ return __FAIL(ops, i); ++ ++ if (ops->iova_to_phys(ops, iova + 42) != (iova + 42)) ++ return __FAIL(ops, i); ++ ++ iova += SZ_1G; ++ j++; ++ j = find_next_bit(&cfg->pgsize_bitmap, BITS_PER_LONG, j); ++ } ++ ++ /* Partial unmap */ ++ size = 1UL << __ffs(cfg->pgsize_bitmap); ++ if (ops->unmap(ops, SZ_1G + size, size) != size) ++ return __FAIL(ops, i); ++ ++ /* Remap of partial unmap */ ++ if (ops->map(ops, SZ_1G + size, size, size, IOMMU_READ)) ++ return __FAIL(ops, i); ++ ++ if (ops->iova_to_phys(ops, SZ_1G + size + 42) != (size + 42)) ++ return __FAIL(ops, i); ++ ++ /* Full unmap */ ++ iova = 0; ++ j = find_first_bit(&cfg->pgsize_bitmap, BITS_PER_LONG); ++ while (j != BITS_PER_LONG) { ++ size = 1UL << j; ++ ++ if (ops->unmap(ops, iova, size) != size) ++ return __FAIL(ops, i); ++ ++ if (ops->iova_to_phys(ops, iova + 42)) ++ return __FAIL(ops, i); ++ ++ /* Remap full block */ ++ if (ops->map(ops, iova, iova, size, IOMMU_WRITE)) ++ return __FAIL(ops, i); ++ ++ if (ops->iova_to_phys(ops, iova + 42) != (iova + 42)) ++ return __FAIL(ops, i); ++ ++ iova += SZ_1G; ++ j++; ++ j = find_next_bit(&cfg->pgsize_bitmap, BITS_PER_LONG, j); ++ } ++ ++ free_io_pgtable_ops(ops); ++ } ++ ++ selftest_running = false; ++ return 0; ++} ++ ++static int __init arm_lpae_do_selftests(void) ++{ ++ static const unsigned long pgsize[] = { ++ SZ_4K | SZ_2M | SZ_1G, ++ SZ_16K | SZ_32M, ++ SZ_64K | SZ_512M, ++ }; ++ ++ static const unsigned int ias[] = { ++ 32, 36, 40, 42, 44, 48, ++ }; ++ ++ int i, j, pass = 0, fail = 0; ++ struct io_pgtable_cfg cfg = { ++ .tlb = &dummy_tlb_ops, ++ .oas = 48, ++ }; ++ ++ for (i = 0; i < ARRAY_SIZE(pgsize); ++i) { ++ for (j = 0; j < ARRAY_SIZE(ias); ++j) { ++ cfg.pgsize_bitmap = pgsize[i]; ++ cfg.ias = ias[j]; ++ pr_info("selftest: pgsize_bitmap 0x%08lx, IAS %u\n", ++ pgsize[i], ias[j]); ++ if (arm_lpae_run_tests(&cfg)) ++ fail++; ++ else ++ pass++; ++ } ++ } ++ ++ pr_info("selftest: completed with %d PASS %d FAIL\n", pass, fail); ++ return fail ? -EFAULT : 0; ++} ++subsys_initcall(arm_lpae_do_selftests); ++#endif +diff --git a/drivers/iommu/io-pgtable.c b/drivers/iommu/io-pgtable.c +new file mode 100644 +index 0000000..6436fe2 +--- /dev/null ++++ b/drivers/iommu/io-pgtable.c +@@ -0,0 +1,82 @@ ++/* ++ * Generic page table allocator for IOMMUs. ++ * ++ * 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. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ * ++ * Copyright (C) 2014 ARM Limited ++ * ++ * Author: Will Deacon ++ */ ++ ++#include ++#include ++#include ++ ++#include "io-pgtable.h" ++ ++extern struct io_pgtable_init_fns io_pgtable_arm_32_lpae_s1_init_fns; ++extern struct io_pgtable_init_fns io_pgtable_arm_32_lpae_s2_init_fns; ++extern struct io_pgtable_init_fns io_pgtable_arm_64_lpae_s1_init_fns; ++extern struct io_pgtable_init_fns io_pgtable_arm_64_lpae_s2_init_fns; ++ ++static const struct io_pgtable_init_fns * ++io_pgtable_init_table[IO_PGTABLE_NUM_FMTS] = ++{ ++#ifdef CONFIG_IOMMU_IO_PGTABLE_LPAE ++ [ARM_32_LPAE_S1] = &io_pgtable_arm_32_lpae_s1_init_fns, ++ [ARM_32_LPAE_S2] = &io_pgtable_arm_32_lpae_s2_init_fns, ++ [ARM_64_LPAE_S1] = &io_pgtable_arm_64_lpae_s1_init_fns, ++ [ARM_64_LPAE_S2] = &io_pgtable_arm_64_lpae_s2_init_fns, ++#endif ++}; ++ ++struct io_pgtable_ops *alloc_io_pgtable_ops(enum io_pgtable_fmt fmt, ++ struct io_pgtable_cfg *cfg, ++ void *cookie) ++{ ++ struct io_pgtable *iop; ++ const struct io_pgtable_init_fns *fns; ++ ++ if (fmt >= IO_PGTABLE_NUM_FMTS) ++ return NULL; ++ ++ fns = io_pgtable_init_table[fmt]; ++ if (!fns) ++ return NULL; ++ ++ iop = fns->alloc(cfg, cookie); ++ if (!iop) ++ return NULL; ++ ++ iop->fmt = fmt; ++ iop->cookie = cookie; ++ iop->cfg = *cfg; ++ ++ return &iop->ops; ++} ++ ++/* ++ * It is the IOMMU driver's responsibility to ensure that the page table ++ * is no longer accessible to the walker by this point. ++ */ ++void free_io_pgtable_ops(struct io_pgtable_ops *ops) ++{ ++ struct io_pgtable *iop; ++ ++ if (!ops) ++ return; ++ ++ iop = container_of(ops, struct io_pgtable, ops); ++ iop->cfg.tlb->tlb_flush_all(iop->cookie); ++ io_pgtable_init_table[iop->fmt]->free(iop); ++} +diff --git a/drivers/iommu/io-pgtable.h b/drivers/iommu/io-pgtable.h +new file mode 100644 +index 0000000..10e32f6 +--- /dev/null ++++ b/drivers/iommu/io-pgtable.h +@@ -0,0 +1,143 @@ ++#ifndef __IO_PGTABLE_H ++#define __IO_PGTABLE_H ++ ++/* ++ * Public API for use by IOMMU drivers ++ */ ++enum io_pgtable_fmt { ++ ARM_32_LPAE_S1, ++ ARM_32_LPAE_S2, ++ ARM_64_LPAE_S1, ++ ARM_64_LPAE_S2, ++ IO_PGTABLE_NUM_FMTS, ++}; ++ ++/** ++ * struct iommu_gather_ops - IOMMU callbacks for TLB and page table management. ++ * ++ * @tlb_flush_all: Synchronously invalidate the entire TLB context. ++ * @tlb_add_flush: Queue up a TLB invalidation for a virtual address range. ++ * @tlb_sync: Ensure any queue TLB invalidation has taken effect. ++ * @flush_pgtable: Ensure page table updates are visible to the IOMMU. ++ * ++ * Note that these can all be called in atomic context and must therefore ++ * not block. ++ */ ++struct iommu_gather_ops { ++ void (*tlb_flush_all)(void *cookie); ++ void (*tlb_add_flush)(unsigned long iova, size_t size, bool leaf, ++ void *cookie); ++ void (*tlb_sync)(void *cookie); ++ void (*flush_pgtable)(void *ptr, size_t size, void *cookie); ++}; ++ ++/** ++ * struct io_pgtable_cfg - Configuration data for a set of page tables. ++ * ++ * @quirks: A bitmap of hardware quirks that require some special ++ * action by the low-level page table allocator. ++ * @pgsize_bitmap: A bitmap of page sizes supported by this set of page ++ * tables. ++ * @ias: Input address (iova) size, in bits. ++ * @oas: Output address (paddr) size, in bits. ++ * @tlb: TLB management callbacks for this set of tables. ++ */ ++struct io_pgtable_cfg { ++ #define IO_PGTABLE_QUIRK_ARM_NS (1 << 0) /* Set NS bit in PTEs */ ++ int quirks; ++ unsigned long pgsize_bitmap; ++ unsigned int ias; ++ unsigned int oas; ++ const struct iommu_gather_ops *tlb; ++ ++ /* Low-level data specific to the table format */ ++ union { ++ struct { ++ u64 ttbr[2]; ++ u64 tcr; ++ u64 mair[2]; ++ } arm_lpae_s1_cfg; ++ ++ struct { ++ u64 vttbr; ++ u64 vtcr; ++ } arm_lpae_s2_cfg; ++ }; ++}; ++ ++/** ++ * struct io_pgtable_ops - Page table manipulation API for IOMMU drivers. ++ * ++ * @map: Map a physically contiguous memory region. ++ * @unmap: Unmap a physically contiguous memory region. ++ * @iova_to_phys: Translate iova to physical address. ++ * ++ * These functions map directly onto the iommu_ops member functions with ++ * the same names. ++ */ ++struct io_pgtable_ops { ++ int (*map)(struct io_pgtable_ops *ops, unsigned long iova, ++ phys_addr_t paddr, size_t size, int prot); ++ int (*unmap)(struct io_pgtable_ops *ops, unsigned long iova, ++ size_t size); ++ phys_addr_t (*iova_to_phys)(struct io_pgtable_ops *ops, ++ unsigned long iova); ++}; ++ ++/** ++ * alloc_io_pgtable_ops() - Allocate a page table allocator for use by an IOMMU. ++ * ++ * @fmt: The page table format. ++ * @cfg: The page table configuration. This will be modified to represent ++ * the configuration actually provided by the allocator (e.g. the ++ * pgsize_bitmap may be restricted). ++ * @cookie: An opaque token provided by the IOMMU driver and passed back to ++ * the callback routines in cfg->tlb. ++ */ ++struct io_pgtable_ops *alloc_io_pgtable_ops(enum io_pgtable_fmt fmt, ++ struct io_pgtable_cfg *cfg, ++ void *cookie); ++ ++/** ++ * free_io_pgtable_ops() - Free an io_pgtable_ops structure. The caller ++ * *must* ensure that the page table is no longer ++ * live, but the TLB can be dirty. ++ * ++ * @ops: The ops returned from alloc_io_pgtable_ops. ++ */ ++void free_io_pgtable_ops(struct io_pgtable_ops *ops); ++ ++ ++/* ++ * Internal structures for page table allocator implementations. ++ */ ++ ++/** ++ * struct io_pgtable - Internal structure describing a set of page tables. ++ * ++ * @fmt: The page table format. ++ * @cookie: An opaque token provided by the IOMMU driver and passed back to ++ * any callback routines. ++ * @cfg: A copy of the page table configuration. ++ * @ops: The page table operations in use for this set of page tables. ++ */ ++struct io_pgtable { ++ enum io_pgtable_fmt fmt; ++ void *cookie; ++ struct io_pgtable_cfg cfg; ++ struct io_pgtable_ops ops; ++}; ++ ++/** ++ * struct io_pgtable_init_fns - Alloc/free a set of page tables for a ++ * particular format. ++ * ++ * @alloc: Allocate a set of page tables described by cfg. ++ * @free: Free the page tables associated with iop. ++ */ ++struct io_pgtable_init_fns { ++ struct io_pgtable *(*alloc)(struct io_pgtable_cfg *cfg, void *cookie); ++ void (*free)(struct io_pgtable *iop); ++}; ++ ++#endif /* __IO_PGTABLE_H */ +diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c +index ed8b048..8d8e5a7 100644 +--- a/drivers/iommu/iommu.c ++++ b/drivers/iommu/iommu.c +@@ -591,10 +591,10 @@ static struct iommu_group *get_pci_alias_group(struct pci_dev *pdev, + continue; + + /* We alias them or they alias us */ +- if (((pdev->dev_flags & PCI_DEV_FLAGS_DMA_ALIAS_DEVFN) && +- pdev->dma_alias_devfn == tmp->devfn) || +- ((tmp->dev_flags & PCI_DEV_FLAGS_DMA_ALIAS_DEVFN) && +- tmp->dma_alias_devfn == pdev->devfn)) { ++ if (((pdev->dev_flags & PCI_DEV_FLAGS_DMA_ALIAS_DEVID) && ++ (pdev->dma_alias_devid & 0xff) == tmp->devfn) || ++ ((tmp->dev_flags & PCI_DEV_FLAGS_DMA_ALIAS_DEVID) && ++ (tmp->dma_alias_devid & 0xff) == pdev->devfn)) { + + group = get_pci_alias_group(tmp, devfns); + if (group) { +@@ -737,7 +737,7 @@ static int add_iommu_group(struct device *dev, void *data) + const struct iommu_ops *ops = cb->ops; + + if (!ops->add_device) +- return -ENODEV; ++ return 0; + + WARN_ON(dev->iommu_group); + +@@ -818,7 +818,15 @@ static int iommu_bus_init(struct bus_type *bus, const struct iommu_ops *ops) + kfree(nb); + return err; + } +- return bus_for_each_dev(bus, NULL, &cb, add_iommu_group); ++ ++ err = bus_for_each_dev(bus, NULL, &cb, add_iommu_group); ++ if (err) { ++ bus_unregister_notifier(bus, nb); ++ kfree(nb); ++ return err; ++ } ++ ++ return 0; + } + + /** +@@ -836,13 +844,19 @@ static int iommu_bus_init(struct bus_type *bus, const struct iommu_ops *ops) + */ + int bus_set_iommu(struct bus_type *bus, const struct iommu_ops *ops) + { ++ int err; ++ + if (bus->iommu_ops != NULL) + return -EBUSY; + + bus->iommu_ops = ops; + + /* Do IOMMU specific setup for this bus-type */ +- return iommu_bus_init(bus, ops); ++ err = iommu_bus_init(bus, ops); ++ if (err) ++ bus->iommu_ops = NULL; ++ ++ return err; + } + EXPORT_SYMBOL_GPL(bus_set_iommu); + +@@ -887,36 +901,24 @@ EXPORT_SYMBOL_GPL(iommu_set_fault_handler); + struct iommu_domain *iommu_domain_alloc(struct bus_type *bus) + { + struct iommu_domain *domain; +- int ret; + + if (bus == NULL || bus->iommu_ops == NULL) + return NULL; + +- domain = kzalloc(sizeof(*domain), GFP_KERNEL); ++ domain = bus->iommu_ops->domain_alloc(IOMMU_DOMAIN_UNMANAGED); + if (!domain) + return NULL; + +- domain->ops = bus->iommu_ops; +- +- ret = domain->ops->domain_init(domain); +- if (ret) +- goto out_free; ++ domain->ops = bus->iommu_ops; ++ domain->type = IOMMU_DOMAIN_UNMANAGED; + + return domain; +- +-out_free: +- kfree(domain); +- +- return NULL; + } + EXPORT_SYMBOL_GPL(iommu_domain_alloc); + + void iommu_domain_free(struct iommu_domain *domain) + { +- if (likely(domain->ops->domain_destroy != NULL)) +- domain->ops->domain_destroy(domain); +- +- kfree(domain); ++ domain->ops->domain_free(domain); + } + EXPORT_SYMBOL_GPL(iommu_domain_free); + +@@ -943,6 +945,16 @@ void iommu_detach_device(struct iommu_domain *domain, struct device *dev) + } + EXPORT_SYMBOL_GPL(iommu_detach_device); + ++struct iommu_domain *iommu_get_dev_domain(struct device *dev) ++{ ++ const struct iommu_ops *ops = dev->bus->iommu_ops; ++ ++ if (unlikely(ops == NULL || ops->get_dev_iommu_domain == NULL)) ++ return NULL; ++ ++ return ops->get_dev_iommu_domain(dev); ++} ++EXPORT_SYMBOL_GPL(iommu_get_dev_domain); + /* + * IOMMU groups are really the natrual working unit of the IOMMU, but + * the IOMMU API works on domains and devices. Bridge that gap by +@@ -1035,6 +1047,9 @@ int iommu_map(struct iommu_domain *domain, unsigned long iova, + domain->ops->pgsize_bitmap == 0UL)) + return -ENODEV; + ++ if (unlikely(!(domain->type & __IOMMU_DOMAIN_PAGING))) ++ return -EINVAL; ++ + /* find out the minimum page size supported */ + min_pagesz = 1 << __ffs(domain->ops->pgsize_bitmap); + +@@ -1070,7 +1085,7 @@ int iommu_map(struct iommu_domain *domain, unsigned long iova, + if (ret) + iommu_unmap(domain, orig_iova, orig_size - size); + else +- trace_map(iova, paddr, size); ++ trace_map(orig_iova, paddr, orig_size); + + return ret; + } +@@ -1080,11 +1095,15 @@ size_t iommu_unmap(struct iommu_domain *domain, unsigned long iova, size_t size) + { + size_t unmapped_page, unmapped = 0; + unsigned int min_pagesz; ++ unsigned long orig_iova = iova; + + if (unlikely(domain->ops->unmap == NULL || + domain->ops->pgsize_bitmap == 0UL)) + return -ENODEV; + ++ if (unlikely(!(domain->type & __IOMMU_DOMAIN_PAGING))) ++ return -EINVAL; ++ + /* find out the minimum page size supported */ + min_pagesz = 1 << __ffs(domain->ops->pgsize_bitmap); + +@@ -1119,11 +1138,53 @@ size_t iommu_unmap(struct iommu_domain *domain, unsigned long iova, size_t size) + unmapped += unmapped_page; + } + +- trace_unmap(iova, 0, size); ++ trace_unmap(orig_iova, size, unmapped); + return unmapped; + } + EXPORT_SYMBOL_GPL(iommu_unmap); + ++size_t default_iommu_map_sg(struct iommu_domain *domain, unsigned long iova, ++ struct scatterlist *sg, unsigned int nents, int prot) ++{ ++ struct scatterlist *s; ++ size_t mapped = 0; ++ unsigned int i, min_pagesz; ++ int ret; ++ ++ if (unlikely(domain->ops->pgsize_bitmap == 0UL)) ++ return 0; ++ ++ min_pagesz = 1 << __ffs(domain->ops->pgsize_bitmap); ++ ++ for_each_sg(sg, s, nents, i) { ++ phys_addr_t phys = page_to_phys(sg_page(s)) + s->offset; ++ ++ /* ++ * We are mapping on IOMMU page boundaries, so offset within ++ * the page must be 0. However, the IOMMU may support pages ++ * smaller than PAGE_SIZE, so s->offset may still represent ++ * an offset of that boundary within the CPU page. ++ */ ++ if (!IS_ALIGNED(s->offset, min_pagesz)) ++ goto out_err; ++ ++ ret = iommu_map(domain, iova + mapped, phys, s->length, prot); ++ if (ret) ++ goto out_err; ++ ++ mapped += s->length; ++ } ++ ++ return mapped; ++ ++out_err: ++ /* undo mappings already done */ ++ iommu_unmap(domain, iova, mapped); ++ ++ return 0; ++ ++} ++EXPORT_SYMBOL_GPL(default_iommu_map_sg); + + int iommu_domain_window_enable(struct iommu_domain *domain, u32 wnd_nr, + phys_addr_t paddr, u64 size, int prot) +diff --git a/drivers/iommu/ipmmu-vmsa.c b/drivers/iommu/ipmmu-vmsa.c +index 7dab5cb..f3c5ab6 100644 +--- a/drivers/iommu/ipmmu-vmsa.c ++++ b/drivers/iommu/ipmmu-vmsa.c +@@ -1127,6 +1127,7 @@ static const struct iommu_ops ipmmu_ops = { + .detach_dev = ipmmu_detach_device, + .map = ipmmu_map, + .unmap = ipmmu_unmap, ++ .map_sg = default_iommu_map_sg, + .iova_to_phys = ipmmu_iova_to_phys, + .add_device = ipmmu_add_device, + .remove_device = ipmmu_remove_device, +@@ -1221,7 +1222,6 @@ static int ipmmu_remove(struct platform_device *pdev) + + static struct platform_driver ipmmu_driver = { + .driver = { +- .owner = THIS_MODULE, + .name = "ipmmu-vmsa", + }, + .probe = ipmmu_probe, +diff --git a/drivers/iommu/irq_remapping.c b/drivers/iommu/irq_remapping.c +index 74a1767..2c3f5ad 100644 +--- a/drivers/iommu/irq_remapping.c ++++ b/drivers/iommu/irq_remapping.c +@@ -56,19 +56,13 @@ static int do_setup_msi_irqs(struct pci_dev *dev, int nvec) + unsigned int irq; + struct msi_desc *msidesc; + +- WARN_ON(!list_is_singular(&dev->msi_list)); + msidesc = list_entry(dev->msi_list.next, struct msi_desc, list); +- WARN_ON(msidesc->irq); +- WARN_ON(msidesc->msi_attrib.multiple); +- WARN_ON(msidesc->nvec_used); + + irq = irq_alloc_hwirqs(nvec, dev_to_node(&dev->dev)); + if (irq == 0) + return -ENOSPC; + + nvec_pow2 = __roundup_pow_of_two(nvec); +- msidesc->nvec_used = nvec; +- msidesc->msi_attrib.multiple = ilog2(nvec_pow2); + for (sub_handle = 0; sub_handle < nvec; sub_handle++) { + if (!sub_handle) { + index = msi_alloc_remapped_irq(dev, irq, nvec_pow2); +@@ -96,8 +90,6 @@ error: + * IRQs from tearing down again in default_teardown_msi_irqs() + */ + msidesc->irq = 0; +- msidesc->nvec_used = 0; +- msidesc->msi_attrib.multiple = 0; + + return ret; + } +diff --git a/drivers/iommu/msm_iommu.c b/drivers/iommu/msm_iommu.c +index 6e3dcc2..1c7b78e 100644 +--- a/drivers/iommu/msm_iommu.c ++++ b/drivers/iommu/msm_iommu.c +@@ -681,6 +681,7 @@ static const struct iommu_ops msm_iommu_ops = { + .detach_dev = msm_iommu_detach_dev, + .map = msm_iommu_map, + .unmap = msm_iommu_unmap, ++ .map_sg = default_iommu_map_sg, + .iova_to_phys = msm_iommu_iova_to_phys, + .pgsize_bitmap = MSM_IOMMU_PGSIZES, + }; +diff --git a/drivers/iommu/of_iommu.c b/drivers/iommu/of_iommu.c +index e550ccb..43429ab 100644 +--- a/drivers/iommu/of_iommu.c ++++ b/drivers/iommu/of_iommu.c +@@ -18,9 +18,14 @@ + */ + + #include ++#include + #include + #include + #include ++#include ++ ++static const struct of_device_id __iommu_of_table_sentinel ++ __used __section(__iommu_of_table_end); + + /** + * of_get_dma_window - Parse *dma-window property and returns 0 if found. +@@ -89,3 +94,93 @@ int of_get_dma_window(struct device_node *dn, const char *prefix, int index, + return 0; + } + EXPORT_SYMBOL_GPL(of_get_dma_window); ++ ++struct of_iommu_node { ++ struct list_head list; ++ struct device_node *np; ++ struct iommu_ops *ops; ++}; ++static LIST_HEAD(of_iommu_list); ++static DEFINE_SPINLOCK(of_iommu_lock); ++ ++void of_iommu_set_ops(struct device_node *np, struct iommu_ops *ops) ++{ ++ struct of_iommu_node *iommu = kzalloc(sizeof(*iommu), GFP_KERNEL); ++ ++ if (WARN_ON(!iommu)) ++ return; ++ ++ INIT_LIST_HEAD(&iommu->list); ++ iommu->np = np; ++ iommu->ops = ops; ++ spin_lock(&of_iommu_lock); ++ list_add_tail(&iommu->list, &of_iommu_list); ++ spin_unlock(&of_iommu_lock); ++} ++ ++struct iommu_ops *of_iommu_get_ops(struct device_node *np) ++{ ++ struct of_iommu_node *node; ++ struct iommu_ops *ops = NULL; ++ ++ spin_lock(&of_iommu_lock); ++ list_for_each_entry(node, &of_iommu_list, list) ++ if (node->np == np) { ++ ops = node->ops; ++ break; ++ } ++ spin_unlock(&of_iommu_lock); ++ return ops; ++} ++ ++struct iommu_ops *of_iommu_configure(struct device *dev, ++ struct device_node *master_np) ++{ ++ struct of_phandle_args iommu_spec; ++ struct device_node *np; ++ struct iommu_ops *ops = NULL; ++ int idx = 0; ++ ++ if (dev_is_pci(dev)) { ++ dev_err(dev, "IOMMU is currently not supported for PCI\n"); ++ return NULL; ++ } ++ ++ /* ++ * We don't currently walk up the tree looking for a parent IOMMU. ++ * See the `Notes:' section of ++ * Documentation/devicetree/bindings/iommu/iommu.txt ++ */ ++ while (!of_parse_phandle_with_args(master_np, "iommus", ++ "#iommu-cells", idx, ++ &iommu_spec)) { ++ np = iommu_spec.np; ++ ops = of_iommu_get_ops(np); ++ ++ if (!ops || !ops->of_xlate || ops->of_xlate(dev, &iommu_spec)) ++ goto err_put_node; ++ ++ of_node_put(np); ++ idx++; ++ } ++ ++ return ops; ++ ++err_put_node: ++ of_node_put(np); ++ return NULL; ++} ++ ++void __init of_iommu_init(void) ++{ ++ struct device_node *np; ++ const struct of_device_id *match, *matches = &__iommu_of_table; ++ ++ for_each_matching_node_and_match(np, matches, &match) { ++ const of_iommu_init_fn init_fn = match->data; ++ ++ if (init_fn(np)) ++ pr_err("Failed to initialise IOMMU %s\n", ++ of_node_full_name(np)); ++ } ++} +diff --git a/drivers/iommu/omap-iommu.c b/drivers/iommu/omap-iommu.c +index 3627887..18003c0 100644 +--- a/drivers/iommu/omap-iommu.c ++++ b/drivers/iommu/omap-iommu.c +@@ -1288,6 +1288,7 @@ static const struct iommu_ops omap_iommu_ops = { + .detach_dev = omap_iommu_detach_dev, + .map = omap_iommu_map, + .unmap = omap_iommu_unmap, ++ .map_sg = default_iommu_map_sg, + .iova_to_phys = omap_iommu_iova_to_phys, + .add_device = omap_iommu_add_device, + .remove_device = omap_iommu_remove_device, +diff --git a/drivers/iommu/shmobile-iommu.c b/drivers/iommu/shmobile-iommu.c +index 1333e6f..f1b0077 100644 +--- a/drivers/iommu/shmobile-iommu.c ++++ b/drivers/iommu/shmobile-iommu.c +@@ -361,6 +361,7 @@ static const struct iommu_ops shmobile_iommu_ops = { + .detach_dev = shmobile_iommu_detach_device, + .map = shmobile_iommu_map, + .unmap = shmobile_iommu_unmap, ++ .map_sg = default_iommu_map_sg, + .iova_to_phys = shmobile_iommu_iova_to_phys, + .add_device = shmobile_iommu_add_device, + .pgsize_bitmap = SZ_1M | SZ_64K | SZ_4K, +diff --git a/drivers/iommu/shmobile-ipmmu.c b/drivers/iommu/shmobile-ipmmu.c +index bd97ade..951651a 100644 +--- a/drivers/iommu/shmobile-ipmmu.c ++++ b/drivers/iommu/shmobile-ipmmu.c +@@ -118,7 +118,6 @@ static int ipmmu_probe(struct platform_device *pdev) + static struct platform_driver ipmmu_driver = { + .probe = ipmmu_probe, + .driver = { +- .owner = THIS_MODULE, + .name = "ipmmu", + }, + }; +diff --git a/drivers/iommu/tegra-gart.c b/drivers/iommu/tegra-gart.c +index a6d76ab..f722a0c 100644 +--- a/drivers/iommu/tegra-gart.c ++++ b/drivers/iommu/tegra-gart.c +@@ -425,7 +425,6 @@ static struct platform_driver tegra_gart_driver = { + .probe = tegra_gart_probe, + .remove = tegra_gart_remove, + .driver = { +- .owner = THIS_MODULE, + .name = "tegra-gart", + .pm = &tegra_gart_pm_ops, + .of_match_table = tegra_gart_of_match, +diff --git a/drivers/iommu/tegra-smmu.c b/drivers/iommu/tegra-smmu.c +index 3afdf43..cb0c9bf 100644 +--- a/drivers/iommu/tegra-smmu.c ++++ b/drivers/iommu/tegra-smmu.c +@@ -955,6 +955,7 @@ static const struct iommu_ops smmu_iommu_ops = { + .detach_dev = smmu_iommu_detach_dev, + .map = smmu_iommu_map, + .unmap = smmu_iommu_unmap, ++ .map_sg = default_iommu_map_sg, + .iova_to_phys = smmu_iommu_iova_to_phys, + .pgsize_bitmap = SMMU_IOMMU_PGSIZES, + }; +@@ -1269,7 +1270,6 @@ static struct platform_driver tegra_smmu_driver = { + .probe = tegra_smmu_probe, + .remove = tegra_smmu_remove, + .driver = { +- .owner = THIS_MODULE, + .name = "tegra-smmu", + .pm = &tegra_smmu_pm_ops, + .of_match_table = tegra_smmu_of_match, +diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig +index b21f12f..e72e239 100644 +--- a/drivers/irqchip/Kconfig ++++ b/drivers/irqchip/Kconfig +@@ -5,8 +5,15 @@ config IRQCHIP + config ARM_GIC + bool + select IRQ_DOMAIN ++ select IRQ_DOMAIN_HIERARCHY + select MULTI_IRQ_HANDLER + ++config ARM_GIC_V2M ++ bool ++ depends on ARM_GIC ++ depends on PCI && PCI_MSI ++ select PCI_MSI_IRQ_DOMAIN ++ + config GIC_NON_BANKED + bool + +@@ -14,6 +21,11 @@ config ARM_GIC_V3 + bool + select IRQ_DOMAIN + select MULTI_IRQ_HANDLER ++ select IRQ_DOMAIN_HIERARCHY ++ ++config ARM_GIC_V3_ITS ++ bool ++ select PCI_MSI_IRQ_DOMAIN + + config ARM_NVIC + bool +diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile +index 173bb5f..1c4f9a4 100644 +--- a/drivers/irqchip/Makefile ++++ b/drivers/irqchip/Makefile +@@ -19,7 +19,9 @@ obj-$(CONFIG_ARCH_SUNXI) += irq-sun4i.o + obj-$(CONFIG_ARCH_SUNXI) += irq-sunxi-nmi.o + obj-$(CONFIG_ARCH_SPEAR3XX) += spear-shirq.o + obj-$(CONFIG_ARM_GIC) += irq-gic.o irq-gic-common.o ++obj-$(CONFIG_ARM_GIC_V2M) += irq-gic-v2m.o + obj-$(CONFIG_ARM_GIC_V3) += irq-gic-v3.o irq-gic-common.o ++obj-$(CONFIG_ARM_GIC_V3_ITS) += irq-gic-v3-its.o + obj-$(CONFIG_ARM_NVIC) += irq-nvic.o + obj-$(CONFIG_ARM_VIC) += irq-vic.o + obj-$(CONFIG_ATMEL_AIC_IRQ) += irq-atmel-aic-common.o irq-atmel-aic.o +diff --git a/drivers/irqchip/irq-armada-370-xp.c b/drivers/irqchip/irq-armada-370-xp.c +index 41ac85a..615075d 100644 +--- a/drivers/irqchip/irq-armada-370-xp.c ++++ b/drivers/irqchip/irq-armada-370-xp.c +@@ -131,7 +131,7 @@ static void armada_370_xp_free_msi(int hwirq) + mutex_unlock(&msi_used_lock); + } + +-static int armada_370_xp_setup_msi_irq(struct msi_chip *chip, ++static int armada_370_xp_setup_msi_irq(struct msi_controller *chip, + struct pci_dev *pdev, + struct msi_desc *desc) + { +@@ -158,11 +158,11 @@ static int armada_370_xp_setup_msi_irq(struct msi_chip *chip, + msg.address_hi = 0; + msg.data = 0xf00 | (hwirq + 16); + +- write_msi_msg(virq, &msg); ++ pci_write_msi_msg(virq, &msg); + return 0; + } + +-static void armada_370_xp_teardown_msi_irq(struct msi_chip *chip, ++static void armada_370_xp_teardown_msi_irq(struct msi_controller *chip, + unsigned int irq) + { + struct irq_data *d = irq_get_irq_data(irq); +@@ -174,10 +174,10 @@ static void armada_370_xp_teardown_msi_irq(struct msi_chip *chip, + + static struct irq_chip armada_370_xp_msi_irq_chip = { + .name = "armada_370_xp_msi_irq", +- .irq_enable = unmask_msi_irq, +- .irq_disable = mask_msi_irq, +- .irq_mask = mask_msi_irq, +- .irq_unmask = unmask_msi_irq, ++ .irq_enable = pci_msi_unmask_irq, ++ .irq_disable = pci_msi_mask_irq, ++ .irq_mask = pci_msi_mask_irq, ++ .irq_unmask = pci_msi_unmask_irq, + }; + + static int armada_370_xp_msi_map(struct irq_domain *domain, unsigned int virq, +@@ -197,7 +197,7 @@ static const struct irq_domain_ops armada_370_xp_msi_irq_ops = { + static int armada_370_xp_msi_init(struct device_node *node, + phys_addr_t main_int_phys_base) + { +- struct msi_chip *msi_chip; ++ struct msi_controller *msi_chip; + u32 reg; + int ret; + +diff --git a/drivers/irqchip/irq-atmel-aic.c b/drivers/irqchip/irq-atmel-aic.c +index 9a2cf3c..27fdd8c 100644 +--- a/drivers/irqchip/irq-atmel-aic.c ++++ b/drivers/irqchip/irq-atmel-aic.c +@@ -65,11 +65,11 @@ aic_handle(struct pt_regs *regs) + u32 irqnr; + u32 irqstat; + +- irqnr = irq_reg_readl(gc->reg_base + AT91_AIC_IVR); +- irqstat = irq_reg_readl(gc->reg_base + AT91_AIC_ISR); ++ irqnr = irq_reg_readl(gc, AT91_AIC_IVR); ++ irqstat = irq_reg_readl(gc, AT91_AIC_ISR); + + if (!irqstat) +- irq_reg_writel(0, gc->reg_base + AT91_AIC_EOICR); ++ irq_reg_writel(gc, 0, AT91_AIC_EOICR); + else + handle_domain_irq(aic_domain, irqnr, regs); + } +@@ -80,7 +80,7 @@ static int aic_retrigger(struct irq_data *d) + + /* Enable interrupt on AIC5 */ + irq_gc_lock(gc); +- irq_reg_writel(d->mask, gc->reg_base + AT91_AIC_ISCR); ++ irq_reg_writel(gc, d->mask, AT91_AIC_ISCR); + irq_gc_unlock(gc); + + return 0; +@@ -92,12 +92,12 @@ static int aic_set_type(struct irq_data *d, unsigned type) + unsigned int smr; + int ret; + +- smr = irq_reg_readl(gc->reg_base + AT91_AIC_SMR(d->hwirq)); ++ smr = irq_reg_readl(gc, AT91_AIC_SMR(d->hwirq)); + ret = aic_common_set_type(d, type, &smr); + if (ret) + return ret; + +- irq_reg_writel(smr, gc->reg_base + AT91_AIC_SMR(d->hwirq)); ++ irq_reg_writel(gc, smr, AT91_AIC_SMR(d->hwirq)); + + return 0; + } +@@ -108,8 +108,8 @@ static void aic_suspend(struct irq_data *d) + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + + irq_gc_lock(gc); +- irq_reg_writel(gc->mask_cache, gc->reg_base + AT91_AIC_IDCR); +- irq_reg_writel(gc->wake_active, gc->reg_base + AT91_AIC_IECR); ++ irq_reg_writel(gc, gc->mask_cache, AT91_AIC_IDCR); ++ irq_reg_writel(gc, gc->wake_active, AT91_AIC_IECR); + irq_gc_unlock(gc); + } + +@@ -118,8 +118,8 @@ static void aic_resume(struct irq_data *d) + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + + irq_gc_lock(gc); +- irq_reg_writel(gc->wake_active, gc->reg_base + AT91_AIC_IDCR); +- irq_reg_writel(gc->mask_cache, gc->reg_base + AT91_AIC_IECR); ++ irq_reg_writel(gc, gc->wake_active, AT91_AIC_IDCR); ++ irq_reg_writel(gc, gc->mask_cache, AT91_AIC_IECR); + irq_gc_unlock(gc); + } + +@@ -128,8 +128,8 @@ static void aic_pm_shutdown(struct irq_data *d) + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + + irq_gc_lock(gc); +- irq_reg_writel(0xffffffff, gc->reg_base + AT91_AIC_IDCR); +- irq_reg_writel(0xffffffff, gc->reg_base + AT91_AIC_ICCR); ++ irq_reg_writel(gc, 0xffffffff, AT91_AIC_IDCR); ++ irq_reg_writel(gc, 0xffffffff, AT91_AIC_ICCR); + irq_gc_unlock(gc); + } + #else +@@ -148,24 +148,24 @@ static void __init aic_hw_init(struct irq_domain *domain) + * will not Lock out nIRQ + */ + for (i = 0; i < 8; i++) +- irq_reg_writel(0, gc->reg_base + AT91_AIC_EOICR); ++ irq_reg_writel(gc, 0, AT91_AIC_EOICR); + + /* + * Spurious Interrupt ID in Spurious Vector Register. + * When there is no current interrupt, the IRQ Vector Register + * reads the value stored in AIC_SPU + */ +- irq_reg_writel(0xffffffff, gc->reg_base + AT91_AIC_SPU); ++ irq_reg_writel(gc, 0xffffffff, AT91_AIC_SPU); + + /* No debugging in AIC: Debug (Protect) Control Register */ +- irq_reg_writel(0, gc->reg_base + AT91_AIC_DCR); ++ irq_reg_writel(gc, 0, AT91_AIC_DCR); + + /* Disable and clear all interrupts initially */ +- irq_reg_writel(0xffffffff, gc->reg_base + AT91_AIC_IDCR); +- irq_reg_writel(0xffffffff, gc->reg_base + AT91_AIC_ICCR); ++ irq_reg_writel(gc, 0xffffffff, AT91_AIC_IDCR); ++ irq_reg_writel(gc, 0xffffffff, AT91_AIC_ICCR); + + for (i = 0; i < 32; i++) +- irq_reg_writel(i, gc->reg_base + AT91_AIC_SVR(i)); ++ irq_reg_writel(gc, i, AT91_AIC_SVR(i)); + } + + static int aic_irq_domain_xlate(struct irq_domain *d, +@@ -195,10 +195,10 @@ static int aic_irq_domain_xlate(struct irq_domain *d, + gc = dgc->gc[idx]; + + irq_gc_lock(gc); +- smr = irq_reg_readl(gc->reg_base + AT91_AIC_SMR(*out_hwirq)); ++ smr = irq_reg_readl(gc, AT91_AIC_SMR(*out_hwirq)); + ret = aic_common_set_priority(intspec[2], &smr); + if (!ret) +- irq_reg_writel(smr, gc->reg_base + AT91_AIC_SMR(*out_hwirq)); ++ irq_reg_writel(gc, smr, AT91_AIC_SMR(*out_hwirq)); + irq_gc_unlock(gc); + + return ret; +diff --git a/drivers/irqchip/irq-atmel-aic5.c b/drivers/irqchip/irq-atmel-aic5.c +index a11aae8..a2e8c3f 100644 +--- a/drivers/irqchip/irq-atmel-aic5.c ++++ b/drivers/irqchip/irq-atmel-aic5.c +@@ -75,11 +75,11 @@ aic5_handle(struct pt_regs *regs) + u32 irqnr; + u32 irqstat; + +- irqnr = irq_reg_readl(gc->reg_base + AT91_AIC5_IVR); +- irqstat = irq_reg_readl(gc->reg_base + AT91_AIC5_ISR); ++ irqnr = irq_reg_readl(gc, AT91_AIC5_IVR); ++ irqstat = irq_reg_readl(gc, AT91_AIC5_ISR); + + if (!irqstat) +- irq_reg_writel(0, gc->reg_base + AT91_AIC5_EOICR); ++ irq_reg_writel(gc, 0, AT91_AIC5_EOICR); + else + handle_domain_irq(aic5_domain, irqnr, regs); + } +@@ -92,8 +92,8 @@ static void aic5_mask(struct irq_data *d) + + /* Disable interrupt on AIC5 */ + irq_gc_lock(gc); +- irq_reg_writel(d->hwirq, gc->reg_base + AT91_AIC5_SSR); +- irq_reg_writel(1, gc->reg_base + AT91_AIC5_IDCR); ++ irq_reg_writel(gc, d->hwirq, AT91_AIC5_SSR); ++ irq_reg_writel(gc, 1, AT91_AIC5_IDCR); + gc->mask_cache &= ~d->mask; + irq_gc_unlock(gc); + } +@@ -106,8 +106,8 @@ static void aic5_unmask(struct irq_data *d) + + /* Enable interrupt on AIC5 */ + irq_gc_lock(gc); +- irq_reg_writel(d->hwirq, gc->reg_base + AT91_AIC5_SSR); +- irq_reg_writel(1, gc->reg_base + AT91_AIC5_IECR); ++ irq_reg_writel(gc, d->hwirq, AT91_AIC5_SSR); ++ irq_reg_writel(gc, 1, AT91_AIC5_IECR); + gc->mask_cache |= d->mask; + irq_gc_unlock(gc); + } +@@ -120,8 +120,8 @@ static int aic5_retrigger(struct irq_data *d) + + /* Enable interrupt on AIC5 */ + irq_gc_lock(gc); +- irq_reg_writel(d->hwirq, gc->reg_base + AT91_AIC5_SSR); +- irq_reg_writel(1, gc->reg_base + AT91_AIC5_ISCR); ++ irq_reg_writel(gc, d->hwirq, AT91_AIC5_SSR); ++ irq_reg_writel(gc, 1, AT91_AIC5_ISCR); + irq_gc_unlock(gc); + + return 0; +@@ -136,11 +136,11 @@ static int aic5_set_type(struct irq_data *d, unsigned type) + int ret; + + irq_gc_lock(gc); +- irq_reg_writel(d->hwirq, gc->reg_base + AT91_AIC5_SSR); +- smr = irq_reg_readl(gc->reg_base + AT91_AIC5_SMR); ++ irq_reg_writel(gc, d->hwirq, AT91_AIC5_SSR); ++ smr = irq_reg_readl(gc, AT91_AIC5_SMR); + ret = aic_common_set_type(d, type, &smr); + if (!ret) +- irq_reg_writel(smr, gc->reg_base + AT91_AIC5_SMR); ++ irq_reg_writel(gc, smr, AT91_AIC5_SMR); + irq_gc_unlock(gc); + + return ret; +@@ -162,12 +162,11 @@ static void aic5_suspend(struct irq_data *d) + if ((mask & gc->mask_cache) == (mask & gc->wake_active)) + continue; + +- irq_reg_writel(i + gc->irq_base, +- bgc->reg_base + AT91_AIC5_SSR); ++ irq_reg_writel(bgc, i + gc->irq_base, AT91_AIC5_SSR); + if (mask & gc->wake_active) +- irq_reg_writel(1, bgc->reg_base + AT91_AIC5_IECR); ++ irq_reg_writel(bgc, 1, AT91_AIC5_IECR); + else +- irq_reg_writel(1, bgc->reg_base + AT91_AIC5_IDCR); ++ irq_reg_writel(bgc, 1, AT91_AIC5_IDCR); + } + irq_gc_unlock(bgc); + } +@@ -187,12 +186,11 @@ static void aic5_resume(struct irq_data *d) + if ((mask & gc->mask_cache) == (mask & gc->wake_active)) + continue; + +- irq_reg_writel(i + gc->irq_base, +- bgc->reg_base + AT91_AIC5_SSR); ++ irq_reg_writel(bgc, i + gc->irq_base, AT91_AIC5_SSR); + if (mask & gc->mask_cache) +- irq_reg_writel(1, bgc->reg_base + AT91_AIC5_IECR); ++ irq_reg_writel(bgc, 1, AT91_AIC5_IECR); + else +- irq_reg_writel(1, bgc->reg_base + AT91_AIC5_IDCR); ++ irq_reg_writel(bgc, 1, AT91_AIC5_IDCR); + } + irq_gc_unlock(bgc); + } +@@ -207,10 +205,9 @@ static void aic5_pm_shutdown(struct irq_data *d) + + irq_gc_lock(bgc); + for (i = 0; i < dgc->irqs_per_chip; i++) { +- irq_reg_writel(i + gc->irq_base, +- bgc->reg_base + AT91_AIC5_SSR); +- irq_reg_writel(1, bgc->reg_base + AT91_AIC5_IDCR); +- irq_reg_writel(1, bgc->reg_base + AT91_AIC5_ICCR); ++ irq_reg_writel(bgc, i + gc->irq_base, AT91_AIC5_SSR); ++ irq_reg_writel(bgc, 1, AT91_AIC5_IDCR); ++ irq_reg_writel(bgc, 1, AT91_AIC5_ICCR); + } + irq_gc_unlock(bgc); + } +@@ -230,24 +227,24 @@ static void __init aic5_hw_init(struct irq_domain *domain) + * will not Lock out nIRQ + */ + for (i = 0; i < 8; i++) +- irq_reg_writel(0, gc->reg_base + AT91_AIC5_EOICR); ++ irq_reg_writel(gc, 0, AT91_AIC5_EOICR); + + /* + * Spurious Interrupt ID in Spurious Vector Register. + * When there is no current interrupt, the IRQ Vector Register + * reads the value stored in AIC_SPU + */ +- irq_reg_writel(0xffffffff, gc->reg_base + AT91_AIC5_SPU); ++ irq_reg_writel(gc, 0xffffffff, AT91_AIC5_SPU); + + /* No debugging in AIC: Debug (Protect) Control Register */ +- irq_reg_writel(0, gc->reg_base + AT91_AIC5_DCR); ++ irq_reg_writel(gc, 0, AT91_AIC5_DCR); + + /* Disable and clear all interrupts initially */ + for (i = 0; i < domain->revmap_size; i++) { +- irq_reg_writel(i, gc->reg_base + AT91_AIC5_SSR); +- irq_reg_writel(i, gc->reg_base + AT91_AIC5_SVR); +- irq_reg_writel(1, gc->reg_base + AT91_AIC5_IDCR); +- irq_reg_writel(1, gc->reg_base + AT91_AIC5_ICCR); ++ irq_reg_writel(gc, i, AT91_AIC5_SSR); ++ irq_reg_writel(gc, i, AT91_AIC5_SVR); ++ irq_reg_writel(gc, 1, AT91_AIC5_IDCR); ++ irq_reg_writel(gc, 1, AT91_AIC5_ICCR); + } + } + +@@ -273,11 +270,11 @@ static int aic5_irq_domain_xlate(struct irq_domain *d, + gc = dgc->gc[0]; + + irq_gc_lock(gc); +- irq_reg_writel(*out_hwirq, gc->reg_base + AT91_AIC5_SSR); +- smr = irq_reg_readl(gc->reg_base + AT91_AIC5_SMR); ++ irq_reg_writel(gc, *out_hwirq, AT91_AIC5_SSR); ++ smr = irq_reg_readl(gc, AT91_AIC5_SMR); + ret = aic_common_set_priority(intspec[2], &smr); + if (!ret) +- irq_reg_writel(intspec[2] | smr, gc->reg_base + AT91_AIC5_SMR); ++ irq_reg_writel(gc, intspec[2] | smr, AT91_AIC5_SMR); + irq_gc_unlock(gc); + + return ret; +diff --git a/drivers/irqchip/irq-gic-common.c b/drivers/irqchip/irq-gic-common.c +index 61541ff..ad96ebb 100644 +--- a/drivers/irqchip/irq-gic-common.c ++++ b/drivers/irqchip/irq-gic-common.c +@@ -21,7 +21,7 @@ + + #include "irq-gic-common.h" + +-void gic_configure_irq(unsigned int irq, unsigned int type, ++int gic_configure_irq(unsigned int irq, unsigned int type, + void __iomem *base, void (*sync_access)(void)) + { + u32 enablemask = 1 << (irq % 32); +@@ -29,16 +29,17 @@ void gic_configure_irq(unsigned int irq, unsigned int type, + u32 confmask = 0x2 << ((irq % 16) * 2); + u32 confoff = (irq / 16) * 4; + bool enabled = false; +- u32 val; ++ u32 val, oldval; ++ int ret = 0; + + /* + * Read current configuration register, and insert the config + * for "irq", depending on "type". + */ +- val = readl_relaxed(base + GIC_DIST_CONFIG + confoff); +- if (type == IRQ_TYPE_LEVEL_HIGH) ++ val = oldval = readl_relaxed(base + GIC_DIST_CONFIG + confoff); ++ if (type & IRQ_TYPE_LEVEL_MASK) + val &= ~confmask; +- else if (type == IRQ_TYPE_EDGE_RISING) ++ else if (type & IRQ_TYPE_EDGE_BOTH) + val |= confmask; + + /* +@@ -54,15 +55,20 @@ void gic_configure_irq(unsigned int irq, unsigned int type, + + /* + * Write back the new configuration, and possibly re-enable +- * the interrupt. ++ * the interrupt. If we tried to write a new configuration and failed, ++ * return an error. + */ + writel_relaxed(val, base + GIC_DIST_CONFIG + confoff); ++ if (readl_relaxed(base + GIC_DIST_CONFIG + confoff) != val && val != oldval) ++ ret = -EINVAL; + + if (enabled) + writel_relaxed(enablemask, base + GIC_DIST_ENABLE_SET + enableoff); + + if (sync_access) + sync_access(); ++ ++ return ret; + } + + void __init gic_dist_config(void __iomem *base, int gic_irqs, +diff --git a/drivers/irqchip/irq-gic-common.h b/drivers/irqchip/irq-gic-common.h +index b41f024..35a9884 100644 +--- a/drivers/irqchip/irq-gic-common.h ++++ b/drivers/irqchip/irq-gic-common.h +@@ -20,7 +20,7 @@ + #include + #include + +-void gic_configure_irq(unsigned int irq, unsigned int type, ++int gic_configure_irq(unsigned int irq, unsigned int type, + void __iomem *base, void (*sync_access)(void)); + void gic_dist_config(void __iomem *base, int gic_irqs, + void (*sync_access)(void)); +diff --git a/drivers/irqchip/irq-gic-v2m.c b/drivers/irqchip/irq-gic-v2m.c +new file mode 100644 +index 0000000..fdf7065 +--- /dev/null ++++ b/drivers/irqchip/irq-gic-v2m.c +@@ -0,0 +1,333 @@ ++/* ++ * ARM GIC v2m MSI(-X) support ++ * Support for Message Signaled Interrupts for systems that ++ * implement ARM Generic Interrupt Controller: GICv2m. ++ * ++ * Copyright (C) 2014 Advanced Micro Devices, Inc. ++ * Authors: Suravee Suthikulpanit ++ * Harish Kasiviswanathan ++ * Brandon Anderson ++ * ++ * 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. ++ */ ++ ++#define pr_fmt(fmt) "GICv2m: " fmt ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++/* ++* MSI_TYPER: ++* [31:26] Reserved ++* [25:16] lowest SPI assigned to MSI ++* [15:10] Reserved ++* [9:0] Numer of SPIs assigned to MSI ++*/ ++#define V2M_MSI_TYPER 0x008 ++#define V2M_MSI_TYPER_BASE_SHIFT 16 ++#define V2M_MSI_TYPER_BASE_MASK 0x3FF ++#define V2M_MSI_TYPER_NUM_MASK 0x3FF ++#define V2M_MSI_SETSPI_NS 0x040 ++#define V2M_MIN_SPI 32 ++#define V2M_MAX_SPI 1019 ++ ++#define V2M_MSI_TYPER_BASE_SPI(x) \ ++ (((x) >> V2M_MSI_TYPER_BASE_SHIFT) & V2M_MSI_TYPER_BASE_MASK) ++ ++#define V2M_MSI_TYPER_NUM_SPI(x) ((x) & V2M_MSI_TYPER_NUM_MASK) ++ ++struct v2m_data { ++ spinlock_t msi_cnt_lock; ++ struct msi_controller mchip; ++ struct resource res; /* GICv2m resource */ ++ void __iomem *base; /* GICv2m virt address */ ++ u32 spi_start; /* The SPI number that MSIs start */ ++ u32 nr_spis; /* The number of SPIs for MSIs */ ++ unsigned long *bm; /* MSI vector bitmap */ ++ struct irq_domain *domain; ++}; ++ ++static void gicv2m_mask_msi_irq(struct irq_data *d) ++{ ++ pci_msi_mask_irq(d); ++ irq_chip_mask_parent(d); ++} ++ ++static void gicv2m_unmask_msi_irq(struct irq_data *d) ++{ ++ pci_msi_unmask_irq(d); ++ irq_chip_unmask_parent(d); ++} ++ ++static struct irq_chip gicv2m_msi_irq_chip = { ++ .name = "MSI", ++ .irq_mask = gicv2m_mask_msi_irq, ++ .irq_unmask = gicv2m_unmask_msi_irq, ++ .irq_eoi = irq_chip_eoi_parent, ++ .irq_write_msi_msg = pci_msi_domain_write_msg, ++}; ++ ++static struct msi_domain_info gicv2m_msi_domain_info = { ++ .flags = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS | ++ MSI_FLAG_PCI_MSIX), ++ .chip = &gicv2m_msi_irq_chip, ++}; ++ ++static int gicv2m_set_affinity(struct irq_data *irq_data, ++ const struct cpumask *mask, bool force) ++{ ++ int ret; ++ ++ ret = irq_chip_set_affinity_parent(irq_data, mask, force); ++ if (ret == IRQ_SET_MASK_OK) ++ ret = IRQ_SET_MASK_OK_DONE; ++ ++ return ret; ++} ++ ++static void gicv2m_compose_msi_msg(struct irq_data *data, struct msi_msg *msg) ++{ ++ struct v2m_data *v2m = irq_data_get_irq_chip_data(data); ++ phys_addr_t addr = v2m->res.start + V2M_MSI_SETSPI_NS; ++ ++ msg->address_hi = (u32) (addr >> 32); ++ msg->address_lo = (u32) (addr); ++ msg->data = data->hwirq; ++} ++ ++static struct irq_chip gicv2m_irq_chip = { ++ .name = "GICv2m", ++ .irq_mask = irq_chip_mask_parent, ++ .irq_unmask = irq_chip_unmask_parent, ++ .irq_eoi = irq_chip_eoi_parent, ++ .irq_set_affinity = gicv2m_set_affinity, ++ .irq_compose_msi_msg = gicv2m_compose_msi_msg, ++}; ++ ++static int gicv2m_irq_gic_domain_alloc(struct irq_domain *domain, ++ unsigned int virq, ++ irq_hw_number_t hwirq) ++{ ++ struct of_phandle_args args; ++ struct irq_data *d; ++ int err; ++ ++ args.np = domain->parent->of_node; ++ args.args_count = 3; ++ args.args[0] = 0; ++ args.args[1] = hwirq - 32; ++ args.args[2] = IRQ_TYPE_EDGE_RISING; ++ ++ err = irq_domain_alloc_irqs_parent(domain, virq, 1, &args); ++ if (err) ++ return err; ++ ++ /* Configure the interrupt line to be edge */ ++ d = irq_domain_get_irq_data(domain->parent, virq); ++ d->chip->irq_set_type(d, IRQ_TYPE_EDGE_RISING); ++ return 0; ++} ++ ++static void gicv2m_unalloc_msi(struct v2m_data *v2m, unsigned int hwirq) ++{ ++ int pos; ++ ++ pos = hwirq - v2m->spi_start; ++ if (pos < 0 || pos >= v2m->nr_spis) { ++ pr_err("Failed to teardown msi. Invalid hwirq %d\n", hwirq); ++ return; ++ } ++ ++ spin_lock(&v2m->msi_cnt_lock); ++ __clear_bit(pos, v2m->bm); ++ spin_unlock(&v2m->msi_cnt_lock); ++} ++ ++static int gicv2m_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, ++ unsigned int nr_irqs, void *args) ++{ ++ struct v2m_data *v2m = domain->host_data; ++ int hwirq, offset, err = 0; ++ ++ spin_lock(&v2m->msi_cnt_lock); ++ offset = find_first_zero_bit(v2m->bm, v2m->nr_spis); ++ if (offset < v2m->nr_spis) ++ __set_bit(offset, v2m->bm); ++ else ++ err = -ENOSPC; ++ spin_unlock(&v2m->msi_cnt_lock); ++ ++ if (err) ++ return err; ++ ++ hwirq = v2m->spi_start + offset; ++ ++ err = gicv2m_irq_gic_domain_alloc(domain, virq, hwirq); ++ if (err) { ++ gicv2m_unalloc_msi(v2m, hwirq); ++ return err; ++ } ++ ++ irq_domain_set_hwirq_and_chip(domain, virq, hwirq, ++ &gicv2m_irq_chip, v2m); ++ ++ return 0; ++} ++ ++static void gicv2m_irq_domain_free(struct irq_domain *domain, ++ unsigned int virq, unsigned int nr_irqs) ++{ ++ struct irq_data *d = irq_domain_get_irq_data(domain, virq); ++ struct v2m_data *v2m = irq_data_get_irq_chip_data(d); ++ ++ BUG_ON(nr_irqs != 1); ++ gicv2m_unalloc_msi(v2m, d->hwirq); ++ irq_domain_free_irqs_parent(domain, virq, nr_irqs); ++} ++ ++static const struct irq_domain_ops gicv2m_domain_ops = { ++ .alloc = gicv2m_irq_domain_alloc, ++ .free = gicv2m_irq_domain_free, ++}; ++ ++static bool is_msi_spi_valid(u32 base, u32 num) ++{ ++ if (base < V2M_MIN_SPI) { ++ pr_err("Invalid MSI base SPI (base:%u)\n", base); ++ return false; ++ } ++ ++ if ((num == 0) || (base + num > V2M_MAX_SPI)) { ++ pr_err("Number of SPIs (%u) exceed maximum (%u)\n", ++ num, V2M_MAX_SPI - V2M_MIN_SPI + 1); ++ return false; ++ } ++ ++ return true; ++} ++ ++static int __init gicv2m_init_one(struct device_node *node, ++ struct irq_domain *parent) ++{ ++ int ret; ++ struct v2m_data *v2m; ++ ++ v2m = kzalloc(sizeof(struct v2m_data), GFP_KERNEL); ++ if (!v2m) { ++ pr_err("Failed to allocate struct v2m_data.\n"); ++ return -ENOMEM; ++ } ++ ++ ret = of_address_to_resource(node, 0, &v2m->res); ++ if (ret) { ++ pr_err("Failed to allocate v2m resource.\n"); ++ goto err_free_v2m; ++ } ++ ++ v2m->base = ioremap(v2m->res.start, resource_size(&v2m->res)); ++ if (!v2m->base) { ++ pr_err("Failed to map GICv2m resource\n"); ++ ret = -ENOMEM; ++ goto err_free_v2m; ++ } ++ ++ if (!of_property_read_u32(node, "arm,msi-base-spi", &v2m->spi_start) && ++ !of_property_read_u32(node, "arm,msi-num-spis", &v2m->nr_spis)) { ++ pr_info("Overriding V2M MSI_TYPER (base:%u, num:%u)\n", ++ v2m->spi_start, v2m->nr_spis); ++ } else { ++ u32 typer = readl_relaxed(v2m->base + V2M_MSI_TYPER); ++ ++ v2m->spi_start = V2M_MSI_TYPER_BASE_SPI(typer); ++ v2m->nr_spis = V2M_MSI_TYPER_NUM_SPI(typer); ++ } ++ ++ if (!is_msi_spi_valid(v2m->spi_start, v2m->nr_spis)) { ++ ret = -EINVAL; ++ goto err_iounmap; ++ } ++ ++ v2m->bm = kzalloc(sizeof(long) * BITS_TO_LONGS(v2m->nr_spis), ++ GFP_KERNEL); ++ if (!v2m->bm) { ++ ret = -ENOMEM; ++ goto err_iounmap; ++ } ++ ++ v2m->domain = irq_domain_add_tree(NULL, &gicv2m_domain_ops, v2m); ++ if (!v2m->domain) { ++ pr_err("Failed to create GICv2m domain\n"); ++ ret = -ENOMEM; ++ goto err_free_bm; ++ } ++ ++ v2m->domain->parent = parent; ++ v2m->mchip.of_node = node; ++ v2m->mchip.domain = pci_msi_create_irq_domain(node, ++ &gicv2m_msi_domain_info, ++ v2m->domain); ++ if (!v2m->mchip.domain) { ++ pr_err("Failed to create MSI domain\n"); ++ ret = -ENOMEM; ++ goto err_free_domains; ++ } ++ ++ spin_lock_init(&v2m->msi_cnt_lock); ++ ++ ret = of_pci_msi_chip_add(&v2m->mchip); ++ if (ret) { ++ pr_err("Failed to add msi_chip.\n"); ++ goto err_free_domains; ++ } ++ ++ pr_info("Node %s: range[%#lx:%#lx], SPI[%d:%d]\n", node->name, ++ (unsigned long)v2m->res.start, (unsigned long)v2m->res.end, ++ v2m->spi_start, (v2m->spi_start + v2m->nr_spis)); ++ ++ return 0; ++ ++err_free_domains: ++ if (v2m->mchip.domain) ++ irq_domain_remove(v2m->mchip.domain); ++ if (v2m->domain) ++ irq_domain_remove(v2m->domain); ++err_free_bm: ++ kfree(v2m->bm); ++err_iounmap: ++ iounmap(v2m->base); ++err_free_v2m: ++ kfree(v2m); ++ return ret; ++} ++ ++static struct of_device_id gicv2m_device_id[] = { ++ { .compatible = "arm,gic-v2m-frame", }, ++ {}, ++}; ++ ++int __init gicv2m_of_init(struct device_node *node, struct irq_domain *parent) ++{ ++ int ret = 0; ++ struct device_node *child; ++ ++ for (child = of_find_matching_node(node, gicv2m_device_id); child; ++ child = of_find_matching_node(child, gicv2m_device_id)) { ++ if (!of_find_property(child, "msi-controller", NULL)) ++ continue; ++ ++ ret = gicv2m_init_one(child, parent); ++ if (ret) { ++ of_node_put(node); ++ break; ++ } ++ } ++ ++ return ret; ++} +diff --git a/drivers/irqchip/irq-gic-v3-its.c b/drivers/irqchip/irq-gic-v3-its.c +new file mode 100644 +index 0000000..d689158 +--- /dev/null ++++ b/drivers/irqchip/irq-gic-v3-its.c +@@ -0,0 +1,1630 @@ ++/* ++ * Copyright (C) 2013, 2014 ARM Limited, All Rights Reserved. ++ * Author: Marc Zyngier ++ * ++ * 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. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#include ++#include ++#include ++ ++#include "irqchip.h" ++ ++#define ITS_FLAGS_CMDQ_NEEDS_FLUSHING (1 << 0) ++ ++#define RDIST_FLAGS_PROPBASE_NEEDS_FLUSHING (1 << 0) ++ ++/* ++ * Collection structure - just an ID, and a redistributor address to ++ * ping. We use one per CPU as a bag of interrupts assigned to this ++ * CPU. ++ */ ++struct its_collection { ++ u64 target_address; ++ u16 col_id; ++}; ++ ++/* ++ * The ITS structure - contains most of the infrastructure, with the ++ * msi_controller, the command queue, the collections, and the list of ++ * devices writing to it. ++ */ ++struct its_node { ++ raw_spinlock_t lock; ++ struct list_head entry; ++ struct msi_controller msi_chip; ++ struct irq_domain *domain; ++ void __iomem *base; ++ unsigned long phys_base; ++ struct its_cmd_block *cmd_base; ++ struct its_cmd_block *cmd_write; ++ void *tables[GITS_BASER_NR_REGS]; ++ struct its_collection *collections; ++ struct list_head its_device_list; ++ u64 flags; ++ u32 ite_size; ++}; ++ ++#define ITS_ITT_ALIGN SZ_256 ++ ++struct event_lpi_map { ++ unsigned long *lpi_map; ++ u16 *col_map; ++ irq_hw_number_t lpi_base; ++ int nr_lpis; ++}; ++ ++/* ++ * The ITS view of a device - belongs to an ITS, a collection, owns an ++ * interrupt translation table, and a list of interrupts. ++ */ ++struct its_device { ++ struct list_head entry; ++ struct its_node *its; ++ struct event_lpi_map event_map; ++ void *itt; ++ u32 nr_ites; ++ u32 device_id; ++}; ++ ++static LIST_HEAD(its_nodes); ++static DEFINE_SPINLOCK(its_lock); ++static struct device_node *gic_root_node; ++static struct rdists *gic_rdists; ++ ++#define gic_data_rdist() (raw_cpu_ptr(gic_rdists->rdist)) ++#define gic_data_rdist_rd_base() (gic_data_rdist()->rd_base) ++ ++static struct its_collection *dev_event_to_col(struct its_device *its_dev, ++ u32 event) ++{ ++ struct its_node *its = its_dev->its; ++ ++ return its->collections + its_dev->event_map.col_map[event]; ++} ++ ++/* ++ * ITS command descriptors - parameters to be encoded in a command ++ * block. ++ */ ++struct its_cmd_desc { ++ union { ++ struct { ++ struct its_device *dev; ++ u32 event_id; ++ } its_inv_cmd; ++ ++ struct { ++ struct its_device *dev; ++ u32 event_id; ++ } its_int_cmd; ++ ++ struct { ++ struct its_device *dev; ++ int valid; ++ } its_mapd_cmd; ++ ++ struct { ++ struct its_collection *col; ++ int valid; ++ } its_mapc_cmd; ++ ++ struct { ++ struct its_device *dev; ++ u32 phys_id; ++ u32 event_id; ++ } its_mapvi_cmd; ++ ++ struct { ++ struct its_device *dev; ++ struct its_collection *col; ++ u32 event_id; ++ } its_movi_cmd; ++ ++ struct { ++ struct its_device *dev; ++ u32 event_id; ++ } its_discard_cmd; ++ ++ struct { ++ struct its_collection *col; ++ } its_invall_cmd; ++ }; ++}; ++ ++/* ++ * The ITS command block, which is what the ITS actually parses. ++ */ ++struct its_cmd_block { ++ u64 raw_cmd[4]; ++}; ++ ++#define ITS_CMD_QUEUE_SZ SZ_64K ++#define ITS_CMD_QUEUE_NR_ENTRIES (ITS_CMD_QUEUE_SZ / sizeof(struct its_cmd_block)) ++ ++typedef struct its_collection *(*its_cmd_builder_t)(struct its_cmd_block *, ++ struct its_cmd_desc *); ++ ++static void its_encode_cmd(struct its_cmd_block *cmd, u8 cmd_nr) ++{ ++ cmd->raw_cmd[0] &= ~0xffUL; ++ cmd->raw_cmd[0] |= cmd_nr; ++} ++ ++static void its_encode_devid(struct its_cmd_block *cmd, u32 devid) ++{ ++ cmd->raw_cmd[0] &= BIT_ULL(32) - 1; ++ cmd->raw_cmd[0] |= ((u64)devid) << 32; ++} ++ ++static void its_encode_event_id(struct its_cmd_block *cmd, u32 id) ++{ ++ cmd->raw_cmd[1] &= ~0xffffffffUL; ++ cmd->raw_cmd[1] |= id; ++} ++ ++static void its_encode_phys_id(struct its_cmd_block *cmd, u32 phys_id) ++{ ++ cmd->raw_cmd[1] &= 0xffffffffUL; ++ cmd->raw_cmd[1] |= ((u64)phys_id) << 32; ++} ++ ++static void its_encode_size(struct its_cmd_block *cmd, u8 size) ++{ ++ cmd->raw_cmd[1] &= ~0x1fUL; ++ cmd->raw_cmd[1] |= size & 0x1f; ++} ++ ++static void its_encode_itt(struct its_cmd_block *cmd, u64 itt_addr) ++{ ++ cmd->raw_cmd[2] &= ~0xffffffffffffUL; ++ cmd->raw_cmd[2] |= itt_addr & 0xffffffffff00UL; ++} ++ ++static void its_encode_valid(struct its_cmd_block *cmd, int valid) ++{ ++ cmd->raw_cmd[2] &= ~(1UL << 63); ++ cmd->raw_cmd[2] |= ((u64)!!valid) << 63; ++} ++ ++static void its_encode_target(struct its_cmd_block *cmd, u64 target_addr) ++{ ++ cmd->raw_cmd[2] &= ~(0xffffffffUL << 16); ++ cmd->raw_cmd[2] |= (target_addr & (0xffffffffUL << 16)); ++} ++ ++static void its_encode_collection(struct its_cmd_block *cmd, u16 col) ++{ ++ cmd->raw_cmd[2] &= ~0xffffUL; ++ cmd->raw_cmd[2] |= col; ++} ++ ++static inline void its_fixup_cmd(struct its_cmd_block *cmd) ++{ ++ /* Let's fixup BE commands */ ++ cmd->raw_cmd[0] = cpu_to_le64(cmd->raw_cmd[0]); ++ cmd->raw_cmd[1] = cpu_to_le64(cmd->raw_cmd[1]); ++ cmd->raw_cmd[2] = cpu_to_le64(cmd->raw_cmd[2]); ++ cmd->raw_cmd[3] = cpu_to_le64(cmd->raw_cmd[3]); ++} ++ ++static struct its_collection *its_build_mapd_cmd(struct its_cmd_block *cmd, ++ struct its_cmd_desc *desc) ++{ ++ unsigned long itt_addr; ++ u8 size = ilog2(desc->its_mapd_cmd.dev->nr_ites); ++ ++ itt_addr = virt_to_phys(desc->its_mapd_cmd.dev->itt); ++ itt_addr = ALIGN(itt_addr, ITS_ITT_ALIGN); ++ ++ its_encode_cmd(cmd, GITS_CMD_MAPD); ++ its_encode_devid(cmd, desc->its_mapd_cmd.dev->device_id); ++ its_encode_size(cmd, size - 1); ++ its_encode_itt(cmd, itt_addr); ++ its_encode_valid(cmd, desc->its_mapd_cmd.valid); ++ ++ its_fixup_cmd(cmd); ++ ++ return NULL; ++} ++ ++static struct its_collection *its_build_mapc_cmd(struct its_cmd_block *cmd, ++ struct its_cmd_desc *desc) ++{ ++ its_encode_cmd(cmd, GITS_CMD_MAPC); ++ its_encode_collection(cmd, desc->its_mapc_cmd.col->col_id); ++ its_encode_target(cmd, desc->its_mapc_cmd.col->target_address); ++ its_encode_valid(cmd, desc->its_mapc_cmd.valid); ++ ++ its_fixup_cmd(cmd); ++ ++ return desc->its_mapc_cmd.col; ++} ++ ++static struct its_collection *its_build_mapvi_cmd(struct its_cmd_block *cmd, ++ struct its_cmd_desc *desc) ++{ ++ struct its_collection *col; ++ ++ col = dev_event_to_col(desc->its_mapvi_cmd.dev, ++ desc->its_mapvi_cmd.event_id); ++ ++ its_encode_cmd(cmd, GITS_CMD_MAPVI); ++ its_encode_devid(cmd, desc->its_mapvi_cmd.dev->device_id); ++ its_encode_event_id(cmd, desc->its_mapvi_cmd.event_id); ++ its_encode_phys_id(cmd, desc->its_mapvi_cmd.phys_id); ++ its_encode_collection(cmd, col->col_id); ++ ++ its_fixup_cmd(cmd); ++ ++ return col; ++} ++ ++static struct its_collection *its_build_movi_cmd(struct its_cmd_block *cmd, ++ struct its_cmd_desc *desc) ++{ ++ struct its_collection *col; ++ ++ col = dev_event_to_col(desc->its_movi_cmd.dev, ++ desc->its_movi_cmd.event_id); ++ ++ its_encode_cmd(cmd, GITS_CMD_MOVI); ++ its_encode_devid(cmd, desc->its_movi_cmd.dev->device_id); ++ its_encode_event_id(cmd, desc->its_movi_cmd.event_id); ++ its_encode_collection(cmd, desc->its_movi_cmd.col->col_id); ++ ++ its_fixup_cmd(cmd); ++ ++ return col; ++} ++ ++static struct its_collection *its_build_discard_cmd(struct its_cmd_block *cmd, ++ struct its_cmd_desc *desc) ++{ ++ struct its_collection *col; ++ ++ col = dev_event_to_col(desc->its_discard_cmd.dev, ++ desc->its_discard_cmd.event_id); ++ ++ its_encode_cmd(cmd, GITS_CMD_DISCARD); ++ its_encode_devid(cmd, desc->its_discard_cmd.dev->device_id); ++ its_encode_event_id(cmd, desc->its_discard_cmd.event_id); ++ ++ its_fixup_cmd(cmd); ++ ++ return col; ++} ++ ++static struct its_collection *its_build_inv_cmd(struct its_cmd_block *cmd, ++ struct its_cmd_desc *desc) ++{ ++ struct its_collection *col; ++ ++ col = dev_event_to_col(desc->its_inv_cmd.dev, ++ desc->its_inv_cmd.event_id); ++ ++ its_encode_cmd(cmd, GITS_CMD_INV); ++ its_encode_devid(cmd, desc->its_inv_cmd.dev->device_id); ++ its_encode_event_id(cmd, desc->its_inv_cmd.event_id); ++ ++ its_fixup_cmd(cmd); ++ ++ return col; ++} ++ ++static struct its_collection *its_build_invall_cmd(struct its_cmd_block *cmd, ++ struct its_cmd_desc *desc) ++{ ++ its_encode_cmd(cmd, GITS_CMD_INVALL); ++ its_encode_collection(cmd, desc->its_mapc_cmd.col->col_id); ++ ++ its_fixup_cmd(cmd); ++ ++ return NULL; ++} ++ ++static u64 its_cmd_ptr_to_offset(struct its_node *its, ++ struct its_cmd_block *ptr) ++{ ++ return (ptr - its->cmd_base) * sizeof(*ptr); ++} ++ ++static int its_queue_full(struct its_node *its) ++{ ++ int widx; ++ int ridx; ++ ++ widx = its->cmd_write - its->cmd_base; ++ ridx = readl_relaxed(its->base + GITS_CREADR) / sizeof(struct its_cmd_block); ++ ++ /* This is incredibly unlikely to happen, unless the ITS locks up. */ ++ if (((widx + 1) % ITS_CMD_QUEUE_NR_ENTRIES) == ridx) ++ return 1; ++ ++ return 0; ++} ++ ++static struct its_cmd_block *its_allocate_entry(struct its_node *its) ++{ ++ struct its_cmd_block *cmd; ++ u32 count = 1000000; /* 1s! */ ++ ++ while (its_queue_full(its)) { ++ count--; ++ if (!count) { ++ pr_err_ratelimited("ITS queue not draining\n"); ++ return NULL; ++ } ++ cpu_relax(); ++ udelay(1); ++ } ++ ++ cmd = its->cmd_write++; ++ ++ /* Handle queue wrapping */ ++ if (its->cmd_write == (its->cmd_base + ITS_CMD_QUEUE_NR_ENTRIES)) ++ its->cmd_write = its->cmd_base; ++ ++ return cmd; ++} ++ ++static struct its_cmd_block *its_post_commands(struct its_node *its) ++{ ++ u64 wr = its_cmd_ptr_to_offset(its, its->cmd_write); ++ ++ writel_relaxed(wr, its->base + GITS_CWRITER); ++ ++ return its->cmd_write; ++} ++ ++static void its_flush_cmd(struct its_node *its, struct its_cmd_block *cmd) ++{ ++ /* ++ * Make sure the commands written to memory are observable by ++ * the ITS. ++ */ ++ if (its->flags & ITS_FLAGS_CMDQ_NEEDS_FLUSHING) ++ __flush_dcache_area(cmd, sizeof(*cmd)); ++ else ++ dsb(ishst); ++} ++ ++static void its_wait_for_range_completion(struct its_node *its, ++ struct its_cmd_block *from, ++ struct its_cmd_block *to) ++{ ++ u64 rd_idx, from_idx, to_idx; ++ u32 count = 1000000; /* 1s! */ ++ ++ from_idx = its_cmd_ptr_to_offset(its, from); ++ to_idx = its_cmd_ptr_to_offset(its, to); ++ ++ while (1) { ++ rd_idx = readl_relaxed(its->base + GITS_CREADR); ++ if (rd_idx >= to_idx || rd_idx < from_idx) ++ break; ++ ++ count--; ++ if (!count) { ++ pr_err_ratelimited("ITS queue timeout\n"); ++ return; ++ } ++ cpu_relax(); ++ udelay(1); ++ } ++} ++ ++static void its_send_single_command(struct its_node *its, ++ its_cmd_builder_t builder, ++ struct its_cmd_desc *desc) ++{ ++ struct its_cmd_block *cmd, *sync_cmd, *next_cmd; ++ struct its_collection *sync_col; ++ unsigned long flags; ++ ++ raw_spin_lock_irqsave(&its->lock, flags); ++ ++ cmd = its_allocate_entry(its); ++ if (!cmd) { /* We're soooooo screewed... */ ++ pr_err_ratelimited("ITS can't allocate, dropping command\n"); ++ raw_spin_unlock_irqrestore(&its->lock, flags); ++ return; ++ } ++ sync_col = builder(cmd, desc); ++ its_flush_cmd(its, cmd); ++ ++ if (sync_col) { ++ sync_cmd = its_allocate_entry(its); ++ if (!sync_cmd) { ++ pr_err_ratelimited("ITS can't SYNC, skipping\n"); ++ goto post; ++ } ++ its_encode_cmd(sync_cmd, GITS_CMD_SYNC); ++ its_encode_target(sync_cmd, sync_col->target_address); ++ its_fixup_cmd(sync_cmd); ++ its_flush_cmd(its, sync_cmd); ++ } ++ ++post: ++ next_cmd = its_post_commands(its); ++ raw_spin_unlock_irqrestore(&its->lock, flags); ++ ++ its_wait_for_range_completion(its, cmd, next_cmd); ++} ++ ++static void its_send_inv(struct its_device *dev, u32 event_id) ++{ ++ struct its_cmd_desc desc; ++ ++ desc.its_inv_cmd.dev = dev; ++ desc.its_inv_cmd.event_id = event_id; ++ ++ its_send_single_command(dev->its, its_build_inv_cmd, &desc); ++} ++ ++static void its_send_mapd(struct its_device *dev, int valid) ++{ ++ struct its_cmd_desc desc; ++ ++ desc.its_mapd_cmd.dev = dev; ++ desc.its_mapd_cmd.valid = !!valid; ++ ++ its_send_single_command(dev->its, its_build_mapd_cmd, &desc); ++} ++ ++static void its_send_mapc(struct its_node *its, struct its_collection *col, ++ int valid) ++{ ++ struct its_cmd_desc desc; ++ ++ desc.its_mapc_cmd.col = col; ++ desc.its_mapc_cmd.valid = !!valid; ++ ++ its_send_single_command(its, its_build_mapc_cmd, &desc); ++} ++ ++static void its_send_mapvi(struct its_device *dev, u32 irq_id, u32 id) ++{ ++ struct its_cmd_desc desc; ++ ++ desc.its_mapvi_cmd.dev = dev; ++ desc.its_mapvi_cmd.phys_id = irq_id; ++ desc.its_mapvi_cmd.event_id = id; ++ ++ its_send_single_command(dev->its, its_build_mapvi_cmd, &desc); ++} ++ ++static void its_send_movi(struct its_device *dev, ++ struct its_collection *col, u32 id) ++{ ++ struct its_cmd_desc desc; ++ ++ desc.its_movi_cmd.dev = dev; ++ desc.its_movi_cmd.col = col; ++ desc.its_movi_cmd.event_id = id; ++ ++ its_send_single_command(dev->its, its_build_movi_cmd, &desc); ++} ++ ++static void its_send_discard(struct its_device *dev, u32 id) ++{ ++ struct its_cmd_desc desc; ++ ++ desc.its_discard_cmd.dev = dev; ++ desc.its_discard_cmd.event_id = id; ++ ++ its_send_single_command(dev->its, its_build_discard_cmd, &desc); ++} ++ ++static void its_send_invall(struct its_node *its, struct its_collection *col) ++{ ++ struct its_cmd_desc desc; ++ ++ desc.its_invall_cmd.col = col; ++ ++ its_send_single_command(its, its_build_invall_cmd, &desc); ++} ++ ++/* ++ * irqchip functions - assumes MSI, mostly. ++ */ ++ ++static inline u32 its_get_event_id(struct irq_data *d) ++{ ++ struct its_device *its_dev = irq_data_get_irq_chip_data(d); ++ return d->hwirq - its_dev->event_map.lpi_base; ++} ++ ++static void lpi_set_config(struct irq_data *d, bool enable) ++{ ++ struct its_device *its_dev = irq_data_get_irq_chip_data(d); ++ irq_hw_number_t hwirq = d->hwirq; ++ u32 id = its_get_event_id(d); ++ u8 *cfg = page_address(gic_rdists->prop_page) + hwirq - 8192; ++ ++ if (enable) ++ *cfg |= LPI_PROP_ENABLED; ++ else ++ *cfg &= ~LPI_PROP_ENABLED; ++ ++ /* ++ * Make the above write visible to the redistributors. ++ * And yes, we're flushing exactly: One. Single. Byte. ++ * Humpf... ++ */ ++ if (gic_rdists->flags & RDIST_FLAGS_PROPBASE_NEEDS_FLUSHING) ++ __flush_dcache_area(cfg, sizeof(*cfg)); ++ else ++ dsb(ishst); ++ its_send_inv(its_dev, id); ++} ++ ++static void its_mask_irq(struct irq_data *d) ++{ ++ lpi_set_config(d, false); ++} ++ ++static void its_unmask_irq(struct irq_data *d) ++{ ++ lpi_set_config(d, true); ++} ++ ++static void its_eoi_irq(struct irq_data *d) ++{ ++ gic_write_eoir(d->hwirq); ++} ++ ++static int its_set_affinity(struct irq_data *d, const struct cpumask *mask_val, ++ bool force) ++{ ++ unsigned int cpu = cpumask_any_and(mask_val, cpu_online_mask); ++ struct its_device *its_dev = irq_data_get_irq_chip_data(d); ++ struct its_collection *target_col; ++ u32 id = its_get_event_id(d); ++ ++ if (cpu >= nr_cpu_ids) ++ return -EINVAL; ++ ++ target_col = &its_dev->its->collections[cpu]; ++ its_send_movi(its_dev, target_col, id); ++ its_dev->event_map.col_map[id] = cpu; ++ ++ return IRQ_SET_MASK_OK_DONE; ++} ++ ++static void its_irq_compose_msi_msg(struct irq_data *d, struct msi_msg *msg) ++{ ++ struct its_device *its_dev = irq_data_get_irq_chip_data(d); ++ struct its_node *its; ++ u64 addr; ++ ++ its = its_dev->its; ++ addr = its->phys_base + GITS_TRANSLATER; ++ ++ msg->address_lo = addr & ((1UL << 32) - 1); ++ msg->address_hi = addr >> 32; ++ msg->data = its_get_event_id(d); ++} ++ ++static struct irq_chip its_irq_chip = { ++ .name = "ITS", ++ .irq_mask = its_mask_irq, ++ .irq_unmask = its_unmask_irq, ++ .irq_eoi = its_eoi_irq, ++ .irq_set_affinity = its_set_affinity, ++ .irq_compose_msi_msg = its_irq_compose_msi_msg, ++}; ++ ++static void its_mask_msi_irq(struct irq_data *d) ++{ ++ pci_msi_mask_irq(d); ++ irq_chip_mask_parent(d); ++} ++ ++static void its_unmask_msi_irq(struct irq_data *d) ++{ ++ pci_msi_unmask_irq(d); ++ irq_chip_unmask_parent(d); ++} ++ ++static struct irq_chip its_msi_irq_chip = { ++ .name = "ITS-MSI", ++ .irq_unmask = its_unmask_msi_irq, ++ .irq_mask = its_mask_msi_irq, ++ .irq_eoi = irq_chip_eoi_parent, ++ .irq_write_msi_msg = pci_msi_domain_write_msg, ++}; ++ ++/* ++ * How we allocate LPIs: ++ * ++ * The GIC has id_bits bits for interrupt identifiers. From there, we ++ * must subtract 8192 which are reserved for SGIs/PPIs/SPIs. Then, as ++ * we allocate LPIs by chunks of 32, we can shift the whole thing by 5 ++ * bits to the right. ++ * ++ * This gives us (((1UL << id_bits) - 8192) >> 5) possible allocations. ++ */ ++#define IRQS_PER_CHUNK_SHIFT 5 ++#define IRQS_PER_CHUNK (1 << IRQS_PER_CHUNK_SHIFT) ++ ++static unsigned long *lpi_bitmap; ++static u32 lpi_chunks; ++static DEFINE_SPINLOCK(lpi_lock); ++ ++static int its_lpi_to_chunk(int lpi) ++{ ++ return (lpi - 8192) >> IRQS_PER_CHUNK_SHIFT; ++} ++ ++static int its_chunk_to_lpi(int chunk) ++{ ++ return (chunk << IRQS_PER_CHUNK_SHIFT) + 8192; ++} ++ ++static int its_lpi_init(u32 id_bits) ++{ ++ lpi_chunks = its_lpi_to_chunk(1UL << id_bits); ++ ++ lpi_bitmap = kzalloc(BITS_TO_LONGS(lpi_chunks) * sizeof(long), ++ GFP_KERNEL); ++ if (!lpi_bitmap) { ++ lpi_chunks = 0; ++ return -ENOMEM; ++ } ++ ++ pr_info("ITS: Allocated %d chunks for LPIs\n", (int)lpi_chunks); ++ return 0; ++} ++ ++static unsigned long *its_lpi_alloc_chunks(int nr_irqs, int *base, int *nr_ids) ++{ ++ unsigned long *bitmap = NULL; ++ int chunk_id; ++ int nr_chunks; ++ int i; ++ ++ nr_chunks = DIV_ROUND_UP(nr_irqs, IRQS_PER_CHUNK); ++ ++ spin_lock(&lpi_lock); ++ ++ do { ++ chunk_id = bitmap_find_next_zero_area(lpi_bitmap, lpi_chunks, ++ 0, nr_chunks, 0); ++ if (chunk_id < lpi_chunks) ++ break; ++ ++ nr_chunks--; ++ } while (nr_chunks > 0); ++ ++ if (!nr_chunks) ++ goto out; ++ ++ bitmap = kzalloc(BITS_TO_LONGS(nr_chunks * IRQS_PER_CHUNK) * sizeof (long), ++ GFP_ATOMIC); ++ if (!bitmap) ++ goto out; ++ ++ for (i = 0; i < nr_chunks; i++) ++ set_bit(chunk_id + i, lpi_bitmap); ++ ++ *base = its_chunk_to_lpi(chunk_id); ++ *nr_ids = nr_chunks * IRQS_PER_CHUNK; ++ ++out: ++ spin_unlock(&lpi_lock); ++ ++ if (!bitmap) ++ *base = *nr_ids = 0; ++ ++ return bitmap; ++} ++ ++static void its_lpi_free(struct event_lpi_map *map) ++{ ++ int base = map->lpi_base; ++ int nr_ids = map->nr_lpis; ++ int lpi; ++ ++ spin_lock(&lpi_lock); ++ ++ for (lpi = base; lpi < (base + nr_ids); lpi += IRQS_PER_CHUNK) { ++ int chunk = its_lpi_to_chunk(lpi); ++ BUG_ON(chunk > lpi_chunks); ++ if (test_bit(chunk, lpi_bitmap)) { ++ clear_bit(chunk, lpi_bitmap); ++ } else { ++ pr_err("Bad LPI chunk %d\n", chunk); ++ } ++ } ++ ++ spin_unlock(&lpi_lock); ++ ++ kfree(map->lpi_map); ++ kfree(map->col_map); ++} ++ ++/* ++ * We allocate 64kB for PROPBASE. That gives us at most 64K LPIs to ++ * deal with (one configuration byte per interrupt). PENDBASE has to ++ * be 64kB aligned (one bit per LPI, plus 8192 bits for SPI/PPI/SGI). ++ */ ++#define LPI_PROPBASE_SZ SZ_64K ++#define LPI_PENDBASE_SZ (LPI_PROPBASE_SZ / 8 + SZ_1K) ++ ++/* ++ * This is how many bits of ID we need, including the useless ones. ++ */ ++#define LPI_NRBITS ilog2(LPI_PROPBASE_SZ + SZ_8K) ++ ++#define LPI_PROP_DEFAULT_PRIO 0xa0 ++ ++static int __init its_alloc_lpi_tables(void) ++{ ++ phys_addr_t paddr; ++ ++ gic_rdists->prop_page = alloc_pages(GFP_NOWAIT, ++ get_order(LPI_PROPBASE_SZ)); ++ if (!gic_rdists->prop_page) { ++ pr_err("Failed to allocate PROPBASE\n"); ++ return -ENOMEM; ++ } ++ ++ paddr = page_to_phys(gic_rdists->prop_page); ++ pr_info("GIC: using LPI property table @%pa\n", &paddr); ++ ++ /* Priority 0xa0, Group-1, disabled */ ++ memset(page_address(gic_rdists->prop_page), ++ LPI_PROP_DEFAULT_PRIO | LPI_PROP_GROUP1, ++ LPI_PROPBASE_SZ); ++ ++ /* Make sure the GIC will observe the written configuration */ ++ __flush_dcache_area(page_address(gic_rdists->prop_page), LPI_PROPBASE_SZ); ++ ++ return 0; ++} ++ ++static const char *its_base_type_string[] = { ++ [GITS_BASER_TYPE_DEVICE] = "Devices", ++ [GITS_BASER_TYPE_VCPU] = "Virtual CPUs", ++ [GITS_BASER_TYPE_CPU] = "Physical CPUs", ++ [GITS_BASER_TYPE_COLLECTION] = "Interrupt Collections", ++ [GITS_BASER_TYPE_RESERVED5] = "Reserved (5)", ++ [GITS_BASER_TYPE_RESERVED6] = "Reserved (6)", ++ [GITS_BASER_TYPE_RESERVED7] = "Reserved (7)", ++}; ++ ++static void its_free_tables(struct its_node *its) ++{ ++ int i; ++ ++ for (i = 0; i < GITS_BASER_NR_REGS; i++) { ++ if (its->tables[i]) { ++ free_page((unsigned long)its->tables[i]); ++ its->tables[i] = NULL; ++ } ++ } ++} ++ ++static int its_alloc_tables(struct its_node *its) ++{ ++ int err; ++ int i; ++ int psz = SZ_64K; ++ u64 shr = GITS_BASER_InnerShareable; ++ u64 cache = GITS_BASER_WaWb; ++ ++ for (i = 0; i < GITS_BASER_NR_REGS; i++) { ++ u64 val = readq_relaxed(its->base + GITS_BASER + i * 8); ++ u64 type = GITS_BASER_TYPE(val); ++ u64 entry_size = GITS_BASER_ENTRY_SIZE(val); ++ int order = get_order(psz); ++ int alloc_size; ++ u64 tmp; ++ void *base; ++ ++ if (type == GITS_BASER_TYPE_NONE) ++ continue; ++ ++ /* ++ * Allocate as many entries as required to fit the ++ * range of device IDs that the ITS can grok... The ID ++ * space being incredibly sparse, this results in a ++ * massive waste of memory. ++ * ++ * For other tables, only allocate a single page. ++ */ ++ if (type == GITS_BASER_TYPE_DEVICE) { ++ u64 typer = readq_relaxed(its->base + GITS_TYPER); ++ u32 ids = GITS_TYPER_DEVBITS(typer); ++ ++ /* ++ * 'order' was initialized earlier to the default page ++ * granule of the the ITS. We can't have an allocation ++ * smaller than that. If the requested allocation ++ * is smaller, round up to the default page granule. ++ */ ++ order = max(get_order((1UL << ids) * entry_size), ++ order); ++ if (order >= MAX_ORDER) { ++ order = MAX_ORDER - 1; ++ pr_warn("%s: Device Table too large, reduce its page order to %u\n", ++ its->msi_chip.of_node->full_name, order); ++ } ++ } ++ ++ alloc_size = (1 << order) * PAGE_SIZE; ++ base = (void *)__get_free_pages(GFP_KERNEL | __GFP_ZERO, order); ++ if (!base) { ++ err = -ENOMEM; ++ goto out_free; ++ } ++ ++ its->tables[i] = base; ++ ++retry_baser: ++ val = (virt_to_phys(base) | ++ (type << GITS_BASER_TYPE_SHIFT) | ++ ((entry_size - 1) << GITS_BASER_ENTRY_SIZE_SHIFT) | ++ cache | ++ shr | ++ GITS_BASER_VALID); ++ ++ switch (psz) { ++ case SZ_4K: ++ val |= GITS_BASER_PAGE_SIZE_4K; ++ break; ++ case SZ_16K: ++ val |= GITS_BASER_PAGE_SIZE_16K; ++ break; ++ case SZ_64K: ++ val |= GITS_BASER_PAGE_SIZE_64K; ++ break; ++ } ++ ++ val |= (alloc_size / psz) - 1; ++ ++ writeq_relaxed(val, its->base + GITS_BASER + i * 8); ++ tmp = readq_relaxed(its->base + GITS_BASER + i * 8); ++ ++ if ((val ^ tmp) & GITS_BASER_SHAREABILITY_MASK) { ++ /* ++ * Shareability didn't stick. Just use ++ * whatever the read reported, which is likely ++ * to be the only thing this redistributor ++ * supports. If that's zero, make it ++ * non-cacheable as well. ++ */ ++ shr = tmp & GITS_BASER_SHAREABILITY_MASK; ++ if (!shr) { ++ cache = GITS_BASER_nC; ++ __flush_dcache_area(base, alloc_size); ++ } ++ goto retry_baser; ++ } ++ ++ if ((val ^ tmp) & GITS_BASER_PAGE_SIZE_MASK) { ++ /* ++ * Page size didn't stick. Let's try a smaller ++ * size and retry. If we reach 4K, then ++ * something is horribly wrong... ++ */ ++ switch (psz) { ++ case SZ_16K: ++ psz = SZ_4K; ++ goto retry_baser; ++ case SZ_64K: ++ psz = SZ_16K; ++ goto retry_baser; ++ } ++ } ++ ++ if (val != tmp) { ++ pr_err("ITS: %s: GITS_BASER%d doesn't stick: %lx %lx\n", ++ its->msi_chip.of_node->full_name, i, ++ (unsigned long) val, (unsigned long) tmp); ++ err = -ENXIO; ++ goto out_free; ++ } ++ ++ pr_info("ITS: allocated %d %s @%lx (psz %dK, shr %d)\n", ++ (int)(alloc_size / entry_size), ++ its_base_type_string[type], ++ (unsigned long)virt_to_phys(base), ++ psz / SZ_1K, (int)shr >> GITS_BASER_SHAREABILITY_SHIFT); ++ } ++ ++ return 0; ++ ++out_free: ++ its_free_tables(its); ++ ++ return err; ++} ++ ++static int its_alloc_collections(struct its_node *its) ++{ ++ its->collections = kzalloc(nr_cpu_ids * sizeof(*its->collections), ++ GFP_KERNEL); ++ if (!its->collections) ++ return -ENOMEM; ++ ++ return 0; ++} ++ ++static void its_cpu_init_lpis(void) ++{ ++ void __iomem *rbase = gic_data_rdist_rd_base(); ++ struct page *pend_page; ++ u64 val, tmp; ++ ++ /* If we didn't allocate the pending table yet, do it now */ ++ pend_page = gic_data_rdist()->pend_page; ++ if (!pend_page) { ++ phys_addr_t paddr; ++ /* ++ * The pending pages have to be at least 64kB aligned, ++ * hence the 'max(LPI_PENDBASE_SZ, SZ_64K)' below. ++ */ ++ pend_page = alloc_pages(GFP_NOWAIT | __GFP_ZERO, ++ get_order(max(LPI_PENDBASE_SZ, SZ_64K))); ++ if (!pend_page) { ++ pr_err("Failed to allocate PENDBASE for CPU%d\n", ++ smp_processor_id()); ++ return; ++ } ++ ++ /* Make sure the GIC will observe the zero-ed page */ ++ __flush_dcache_area(page_address(pend_page), LPI_PENDBASE_SZ); ++ ++ paddr = page_to_phys(pend_page); ++ pr_info("CPU%d: using LPI pending table @%pa\n", ++ smp_processor_id(), &paddr); ++ gic_data_rdist()->pend_page = pend_page; ++ } ++ ++ /* Disable LPIs */ ++ val = readl_relaxed(rbase + GICR_CTLR); ++ val &= ~GICR_CTLR_ENABLE_LPIS; ++ writel_relaxed(val, rbase + GICR_CTLR); ++ ++ /* ++ * Make sure any change to the table is observable by the GIC. ++ */ ++ dsb(sy); ++ ++ /* set PROPBASE */ ++ val = (page_to_phys(gic_rdists->prop_page) | ++ GICR_PROPBASER_InnerShareable | ++ GICR_PROPBASER_WaWb | ++ ((LPI_NRBITS - 1) & GICR_PROPBASER_IDBITS_MASK)); ++ ++ writeq_relaxed(val, rbase + GICR_PROPBASER); ++ tmp = readq_relaxed(rbase + GICR_PROPBASER); ++ ++ if ((tmp ^ val) & GICR_PROPBASER_SHAREABILITY_MASK) { ++ if (!(tmp & GICR_PROPBASER_SHAREABILITY_MASK)) { ++ /* ++ * The HW reports non-shareable, we must ++ * remove the cacheability attributes as ++ * well. ++ */ ++ val &= ~(GICR_PROPBASER_SHAREABILITY_MASK | ++ GICR_PROPBASER_CACHEABILITY_MASK); ++ val |= GICR_PROPBASER_nC; ++ writeq_relaxed(val, rbase + GICR_PROPBASER); ++ } ++ pr_info_once("GIC: using cache flushing for LPI property table\n"); ++ gic_rdists->flags |= RDIST_FLAGS_PROPBASE_NEEDS_FLUSHING; ++ } ++ ++ /* set PENDBASE */ ++ val = (page_to_phys(pend_page) | ++ GICR_PENDBASER_InnerShareable | ++ GICR_PENDBASER_WaWb); ++ ++ writeq_relaxed(val, rbase + GICR_PENDBASER); ++ tmp = readq_relaxed(rbase + GICR_PENDBASER); ++ ++ if (!(tmp & GICR_PENDBASER_SHAREABILITY_MASK)) { ++ /* ++ * The HW reports non-shareable, we must remove the ++ * cacheability attributes as well. ++ */ ++ val &= ~(GICR_PENDBASER_SHAREABILITY_MASK | ++ GICR_PENDBASER_CACHEABILITY_MASK); ++ val |= GICR_PENDBASER_nC; ++ writeq_relaxed(val, rbase + GICR_PENDBASER); ++ } ++ ++ /* Enable LPIs */ ++ val = readl_relaxed(rbase + GICR_CTLR); ++ val |= GICR_CTLR_ENABLE_LPIS; ++ writel_relaxed(val, rbase + GICR_CTLR); ++ ++ /* Make sure the GIC has seen the above */ ++ dsb(sy); ++} ++ ++static void its_cpu_init_collection(void) ++{ ++ struct its_node *its; ++ int cpu; ++ ++ spin_lock(&its_lock); ++ cpu = smp_processor_id(); ++ ++ list_for_each_entry(its, &its_nodes, entry) { ++ u64 target; ++ ++ /* ++ * We now have to bind each collection to its target ++ * redistributor. ++ */ ++ if (readq_relaxed(its->base + GITS_TYPER) & GITS_TYPER_PTA) { ++ /* ++ * This ITS wants the physical address of the ++ * redistributor. ++ */ ++ target = gic_data_rdist()->phys_base; ++ } else { ++ /* ++ * This ITS wants a linear CPU number. ++ */ ++ target = readq_relaxed(gic_data_rdist_rd_base() + GICR_TYPER); ++ target = GICR_TYPER_CPU_NUMBER(target) << 16; ++ } ++ ++ /* Perform collection mapping */ ++ its->collections[cpu].target_address = target; ++ its->collections[cpu].col_id = cpu; ++ ++ its_send_mapc(its, &its->collections[cpu], 1); ++ its_send_invall(its, &its->collections[cpu]); ++ } ++ ++ spin_unlock(&its_lock); ++} ++ ++static struct its_device *its_find_device(struct its_node *its, u32 dev_id) ++{ ++ struct its_device *its_dev = NULL, *tmp; ++ unsigned long flags; ++ ++ raw_spin_lock_irqsave(&its->lock, flags); ++ ++ list_for_each_entry(tmp, &its->its_device_list, entry) { ++ if (tmp->device_id == dev_id) { ++ its_dev = tmp; ++ break; ++ } ++ } ++ ++ raw_spin_unlock_irqrestore(&its->lock, flags); ++ ++ return its_dev; ++} ++ ++static struct its_device *its_create_device(struct its_node *its, u32 dev_id, ++ int nvecs) ++{ ++ struct its_device *dev; ++ unsigned long *lpi_map; ++ unsigned long flags; ++ u16 *col_map = NULL; ++ void *itt; ++ int lpi_base; ++ int nr_lpis; ++ int nr_ites; ++ int sz; ++ ++ dev = kzalloc(sizeof(*dev), GFP_KERNEL); ++ /* ++ * At least one bit of EventID is being used, hence a minimum ++ * of two entries. No, the architecture doesn't let you ++ * express an ITT with a single entry. ++ */ ++ nr_ites = max(2UL, roundup_pow_of_two(nvecs)); ++ sz = nr_ites * its->ite_size; ++ sz = max(sz, ITS_ITT_ALIGN) + ITS_ITT_ALIGN - 1; ++ itt = kzalloc(sz, GFP_KERNEL); ++ lpi_map = its_lpi_alloc_chunks(nvecs, &lpi_base, &nr_lpis); ++ if (lpi_map) ++ col_map = kzalloc(sizeof(*col_map) * nr_lpis, GFP_KERNEL); ++ ++ if (!dev || !itt || !lpi_map || !col_map) { ++ kfree(dev); ++ kfree(itt); ++ kfree(lpi_map); ++ kfree(col_map); ++ return NULL; ++ } ++ ++ __flush_dcache_area(itt, sz); ++ ++ dev->its = its; ++ dev->itt = itt; ++ dev->nr_ites = nr_ites; ++ dev->event_map.lpi_map = lpi_map; ++ dev->event_map.col_map = col_map; ++ dev->event_map.lpi_base = lpi_base; ++ dev->event_map.nr_lpis = nr_lpis; ++ dev->device_id = dev_id; ++ INIT_LIST_HEAD(&dev->entry); ++ ++ raw_spin_lock_irqsave(&its->lock, flags); ++ list_add(&dev->entry, &its->its_device_list); ++ raw_spin_unlock_irqrestore(&its->lock, flags); ++ ++ /* Map device to its ITT */ ++ its_send_mapd(dev, 1); ++ ++ return dev; ++} ++ ++static void its_free_device(struct its_device *its_dev) ++{ ++ unsigned long flags; ++ ++ raw_spin_lock_irqsave(&its_dev->its->lock, flags); ++ list_del(&its_dev->entry); ++ raw_spin_unlock_irqrestore(&its_dev->its->lock, flags); ++ kfree(its_dev->itt); ++ kfree(its_dev); ++} ++ ++static int its_alloc_device_irq(struct its_device *dev, irq_hw_number_t *hwirq) ++{ ++ int idx; ++ ++ idx = find_first_zero_bit(dev->event_map.lpi_map, ++ dev->event_map.nr_lpis); ++ if (idx == dev->event_map.nr_lpis) ++ return -ENOSPC; ++ ++ *hwirq = dev->event_map.lpi_base + idx; ++ set_bit(idx, dev->event_map.lpi_map); ++ ++ return 0; ++} ++ ++struct its_pci_alias { ++ struct pci_dev *pdev; ++ u32 dev_id; ++ u32 count; ++}; ++ ++static int its_pci_msi_vec_count(struct pci_dev *pdev) ++{ ++ int msi, msix; ++ ++ msi = max(pci_msi_vec_count(pdev), 0); ++ msix = max(pci_msix_vec_count(pdev), 0); ++ ++ return max(msi, msix); ++} ++ ++static int its_get_pci_alias(struct pci_dev *pdev, u16 alias, void *data) ++{ ++ struct its_pci_alias *dev_alias = data; ++ ++ dev_alias->dev_id = alias; ++ if (pdev != dev_alias->pdev) ++ dev_alias->count += its_pci_msi_vec_count(dev_alias->pdev); ++ ++ return 0; ++} ++ ++int __its_msi_prepare(struct irq_domain *domain, u32 dev_id, ++ struct device *dev, int nvec, msi_alloc_info_t *info) ++{ ++ struct its_node *its; ++ struct its_device *its_dev; ++ ++ its = domain->parent->host_data; ++ ++ its_dev = its_find_device(its, dev_id); ++ if (its_dev) { ++ /* ++ * We already have seen this ID, probably through ++ * another alias (PCI bridge of some sort). No need to ++ * create the device. ++ */ ++ dev_dbg(dev, "Reusing ITT for devID %x\n", dev_id); ++ goto out; ++ } ++ ++ its_dev = its_create_device(its, dev_id, nvec); ++ if (!its_dev) ++ return -ENOMEM; ++ ++ dev_dbg(dev, "ITT %d entries, %d bits\n", ++ nvec, ilog2(nvec)); ++out: ++ info->scratchpad[0].ptr = its_dev; ++ info->scratchpad[1].ptr = dev; ++ ++ return 0; ++} ++ ++static int its_msi_prepare(struct irq_domain *domain, struct device *dev, ++ int nvec, msi_alloc_info_t *info) ++{ ++ struct pci_dev *pdev; ++ struct its_pci_alias dev_alias; ++ u32 dev_id; ++ ++ if (!dev_is_pci(dev)) ++ return -EINVAL; ++ ++ pdev = to_pci_dev(dev); ++ dev_alias.pdev = pdev; ++ dev_alias.count = nvec; ++ ++ pci_for_each_dma_alias(pdev, its_get_pci_alias, &dev_alias); ++ ++ dev_dbg(dev, "ITT %d entries, %d bits\n", nvec, ilog2(nvec)); ++ dev_id = PCI_DEVID(pdev->bus->number, pdev->devfn); ++ return __its_msi_prepare(domain, dev_alias.dev_id, ++ dev, dev_alias.count, info); ++} ++ ++static struct msi_domain_ops its_pci_msi_ops = { ++ .msi_prepare = its_msi_prepare, ++}; ++ ++static struct msi_domain_info its_pci_msi_domain_info = { ++ .flags = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS | ++ MSI_FLAG_MULTI_PCI_MSI | MSI_FLAG_PCI_MSIX), ++ .ops = &its_pci_msi_ops, ++ .chip = &its_msi_irq_chip, ++}; ++ ++static int its_irq_gic_domain_alloc(struct irq_domain *domain, ++ unsigned int virq, ++ irq_hw_number_t hwirq) ++{ ++ struct of_phandle_args args; ++ ++ args.np = domain->parent->of_node; ++ args.args_count = 3; ++ args.args[0] = GIC_IRQ_TYPE_LPI; ++ args.args[1] = hwirq; ++ args.args[2] = IRQ_TYPE_EDGE_RISING; ++ ++ return irq_domain_alloc_irqs_parent(domain, virq, 1, &args); ++} ++ ++static int its_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, ++ unsigned int nr_irqs, void *args) ++{ ++ msi_alloc_info_t *info = args; ++ struct its_device *its_dev = info->scratchpad[0].ptr; ++ irq_hw_number_t hwirq; ++ int err; ++ int i; ++ ++ for (i = 0; i < nr_irqs; i++) { ++ err = its_alloc_device_irq(its_dev, &hwirq); ++ if (err) ++ return err; ++ ++ err = its_irq_gic_domain_alloc(domain, virq + i, hwirq); ++ if (err) ++ return err; ++ ++ irq_domain_set_hwirq_and_chip(domain, virq + i, ++ hwirq, &its_irq_chip, its_dev); ++ dev_dbg(info->scratchpad[1].ptr, "ID:%d pID:%d vID:%d\n", ++ (int)(hwirq - its_dev->event_map.lpi_base), ++ (int)hwirq, virq + i); ++ } ++ ++ return 0; ++} ++ ++static void its_irq_domain_activate(struct irq_domain *domain, ++ struct irq_data *d) ++{ ++ struct its_device *its_dev = irq_data_get_irq_chip_data(d); ++ u32 event = its_get_event_id(d); ++ ++ /* Bind the LPI to the first possible CPU */ ++ its_dev->event_map.col_map[event] = cpumask_first(cpu_online_mask); ++ ++ /* Map the GIC IRQ and event to the device */ ++ its_send_mapvi(its_dev, d->hwirq, event); ++} ++ ++static void its_irq_domain_deactivate(struct irq_domain *domain, ++ struct irq_data *d) ++{ ++ struct its_device *its_dev = irq_data_get_irq_chip_data(d); ++ u32 event = its_get_event_id(d); ++ ++ /* Stop the delivery of interrupts */ ++ its_send_discard(its_dev, event); ++} ++ ++static void its_irq_domain_free(struct irq_domain *domain, unsigned int virq, ++ unsigned int nr_irqs) ++{ ++ struct irq_data *d = irq_domain_get_irq_data(domain, virq); ++ struct its_device *its_dev = irq_data_get_irq_chip_data(d); ++ int i; ++ ++ for (i = 0; i < nr_irqs; i++) { ++ struct irq_data *data = irq_domain_get_irq_data(domain, ++ virq + i); ++ u32 event = its_get_event_id(data); ++ ++ /* Mark interrupt index as unused */ ++ clear_bit(event, its_dev->event_map.lpi_map); ++ ++ /* Nuke the entry in the domain */ ++ irq_domain_reset_irq_data(data); ++ } ++ ++ /* If all interrupts have been freed, start mopping the floor */ ++ if (bitmap_empty(its_dev->event_map.lpi_map, ++ its_dev->event_map.nr_lpis)) { ++ its_lpi_free(&its_dev->event_map); ++ ++ /* Unmap device/itt */ ++ its_send_mapd(its_dev, 0); ++ its_free_device(its_dev); ++ } ++ ++ irq_domain_free_irqs_parent(domain, virq, nr_irqs); ++} ++ ++static const struct irq_domain_ops its_domain_ops = { ++ .alloc = its_irq_domain_alloc, ++ .free = its_irq_domain_free, ++ .activate = its_irq_domain_activate, ++ .deactivate = its_irq_domain_deactivate, ++}; ++ ++static int its_force_quiescent(void __iomem *base) ++{ ++ u32 count = 1000000; /* 1s */ ++ u32 val; ++ ++ val = readl_relaxed(base + GITS_CTLR); ++ if (val & GITS_CTLR_QUIESCENT) ++ return 0; ++ ++ /* Disable the generation of all interrupts to this ITS */ ++ val &= ~GITS_CTLR_ENABLE; ++ writel_relaxed(val, base + GITS_CTLR); ++ ++ /* Poll GITS_CTLR and wait until ITS becomes quiescent */ ++ while (1) { ++ val = readl_relaxed(base + GITS_CTLR); ++ if (val & GITS_CTLR_QUIESCENT) ++ return 0; ++ ++ count--; ++ if (!count) ++ return -EBUSY; ++ ++ cpu_relax(); ++ udelay(1); ++ } ++} ++ ++static int its_probe(struct device_node *node, struct irq_domain *parent) ++{ ++ struct resource res; ++ struct its_node *its; ++ void __iomem *its_base; ++ u32 val; ++ u64 baser, tmp; ++ int err; ++ ++ err = of_address_to_resource(node, 0, &res); ++ if (err) { ++ pr_warn("%s: no regs?\n", node->full_name); ++ return -ENXIO; ++ } ++ ++ its_base = ioremap(res.start, resource_size(&res)); ++ if (!its_base) { ++ pr_warn("%s: unable to map registers\n", node->full_name); ++ return -ENOMEM; ++ } ++ ++ val = readl_relaxed(its_base + GITS_PIDR2) & GIC_PIDR2_ARCH_MASK; ++ if (val != 0x30 && val != 0x40) { ++ pr_warn("%s: no ITS detected, giving up\n", node->full_name); ++ err = -ENODEV; ++ goto out_unmap; ++ } ++ ++ err = its_force_quiescent(its_base); ++ if (err) { ++ pr_warn("%s: failed to quiesce, giving up\n", ++ node->full_name); ++ goto out_unmap; ++ } ++ ++ pr_info("ITS: %s\n", node->full_name); ++ ++ its = kzalloc(sizeof(*its), GFP_KERNEL); ++ if (!its) { ++ err = -ENOMEM; ++ goto out_unmap; ++ } ++ ++ raw_spin_lock_init(&its->lock); ++ INIT_LIST_HEAD(&its->entry); ++ INIT_LIST_HEAD(&its->its_device_list); ++ its->base = its_base; ++ its->phys_base = res.start; ++ its->msi_chip.of_node = node; ++ its->ite_size = ((readl_relaxed(its_base + GITS_TYPER) >> 4) & 0xf) + 1; ++ ++ its->cmd_base = kzalloc(ITS_CMD_QUEUE_SZ, GFP_KERNEL); ++ if (!its->cmd_base) { ++ err = -ENOMEM; ++ goto out_free_its; ++ } ++ its->cmd_write = its->cmd_base; ++ ++ err = its_alloc_tables(its); ++ if (err) ++ goto out_free_cmd; ++ ++ err = its_alloc_collections(its); ++ if (err) ++ goto out_free_tables; ++ ++ baser = (virt_to_phys(its->cmd_base) | ++ GITS_CBASER_WaWb | ++ GITS_CBASER_InnerShareable | ++ (ITS_CMD_QUEUE_SZ / SZ_4K - 1) | ++ GITS_CBASER_VALID); ++ ++ writeq_relaxed(baser, its->base + GITS_CBASER); ++ tmp = readq_relaxed(its->base + GITS_CBASER); ++ ++ if ((tmp ^ baser) & GITS_CBASER_SHAREABILITY_MASK) { ++ if (!(tmp & GITS_CBASER_SHAREABILITY_MASK)) { ++ /* ++ * The HW reports non-shareable, we must ++ * remove the cacheability attributes as ++ * well. ++ */ ++ baser &= ~(GITS_CBASER_SHAREABILITY_MASK | ++ GITS_CBASER_CACHEABILITY_MASK); ++ baser |= GITS_CBASER_nC; ++ writeq_relaxed(baser, its->base + GITS_CBASER); ++ } ++ pr_info("ITS: using cache flushing for cmd queue\n"); ++ its->flags |= ITS_FLAGS_CMDQ_NEEDS_FLUSHING; ++ } ++ ++ writeq_relaxed(0, its->base + GITS_CWRITER); ++ writel_relaxed(GITS_CTLR_ENABLE, its->base + GITS_CTLR); ++ ++ if (of_property_read_bool(its->msi_chip.of_node, "msi-controller")) { ++ its->domain = irq_domain_add_tree(node, &its_domain_ops, its); ++ if (!its->domain) { ++ err = -ENOMEM; ++ goto out_free_tables; ++ } ++ ++ its->domain->parent = parent; ++ its->domain->bus_token = DOMAIN_BUS_NEXUS; ++ ++ its->msi_chip.domain = pci_msi_create_irq_domain(node, ++ &its_pci_msi_domain_info, ++ its->domain); ++ if (!its->msi_chip.domain) { ++ err = -ENOMEM; ++ goto out_free_domains; ++ } ++ ++ err = of_pci_msi_chip_add(&its->msi_chip); ++ if (err) ++ goto out_free_domains; ++ } ++ ++ spin_lock(&its_lock); ++ list_add(&its->entry, &its_nodes); ++ spin_unlock(&its_lock); ++ ++ return 0; ++ ++out_free_domains: ++ if (its->msi_chip.domain) ++ irq_domain_remove(its->msi_chip.domain); ++ if (its->domain) ++ irq_domain_remove(its->domain); ++out_free_tables: ++ its_free_tables(its); ++out_free_cmd: ++ kfree(its->cmd_base); ++out_free_its: ++ kfree(its); ++out_unmap: ++ iounmap(its_base); ++ pr_err("ITS: failed probing %s (%d)\n", node->full_name, err); ++ return err; ++} ++ ++static bool gic_rdists_supports_plpis(void) ++{ ++ return !!(readl_relaxed(gic_data_rdist_rd_base() + GICR_TYPER) & GICR_TYPER_PLPIS); ++} ++ ++int its_cpu_init(void) ++{ ++ if (!list_empty(&its_nodes)) { ++ if (!gic_rdists_supports_plpis()) { ++ pr_info("CPU%d: LPIs not supported\n", smp_processor_id()); ++ return -ENXIO; ++ } ++ its_cpu_init_lpis(); ++ its_cpu_init_collection(); ++ } ++ ++ return 0; ++} ++ ++static struct of_device_id its_device_id[] = { ++ { .compatible = "arm,gic-v3-its", }, ++ {}, ++}; ++ ++int its_init(struct device_node *node, struct rdists *rdists, ++ struct irq_domain *parent_domain) ++{ ++ struct device_node *np; ++ ++ for (np = of_find_matching_node(node, its_device_id); np; ++ np = of_find_matching_node(np, its_device_id)) { ++ its_probe(np, parent_domain); ++ } ++ ++ if (list_empty(&its_nodes)) { ++ pr_warn("ITS: No ITS available, not enabling LPIs\n"); ++ return -ENXIO; ++ } ++ ++ gic_rdists = rdists; ++ gic_root_node = node; ++ ++ its_alloc_lpi_tables(); ++ its_lpi_init(rdists->id_bits); ++ ++ return 0; ++} +diff --git a/drivers/irqchip/irq-gic-v3.c b/drivers/irqchip/irq-gic-v3.c +index aa17ae8..fd8850d 100644 +--- a/drivers/irqchip/irq-gic-v3.c ++++ b/drivers/irqchip/irq-gic-v3.c +@@ -34,20 +34,25 @@ + #include "irq-gic-common.h" + #include "irqchip.h" + ++struct redist_region { ++ void __iomem *redist_base; ++ phys_addr_t phys_base; ++}; ++ + struct gic_chip_data { + void __iomem *dist_base; +- void __iomem **redist_base; +- void __iomem * __percpu *rdist; ++ struct redist_region *redist_regions; ++ struct rdists rdists; + struct irq_domain *domain; + u64 redist_stride; +- u32 redist_regions; ++ u32 nr_redist_regions; + unsigned int irq_nr; + }; + + static struct gic_chip_data gic_data __read_mostly; + +-#define gic_data_rdist() (this_cpu_ptr(gic_data.rdist)) +-#define gic_data_rdist_rd_base() (*gic_data_rdist()) ++#define gic_data_rdist() (this_cpu_ptr(gic_data.rdists.rdist)) ++#define gic_data_rdist_rd_base() (gic_data_rdist()->rd_base) + #define gic_data_rdist_sgi_base() (gic_data_rdist_rd_base() + SZ_64K) + + /* Our default, arbitrary priority value. Linux only uses one anyway. */ +@@ -71,9 +76,6 @@ static inline void __iomem *gic_dist_base(struct irq_data *d) + if (d->hwirq <= 1023) /* SPI -> dist_base */ + return gic_data.dist_base; + +- if (d->hwirq >= 8192) +- BUG(); /* LPI Detected!!! */ +- + return NULL; + } + +@@ -236,7 +238,9 @@ static int gic_set_type(struct irq_data *d, unsigned int type) + if (irq < 16) + return -EINVAL; + +- if (type != IRQ_TYPE_LEVEL_HIGH && type != IRQ_TYPE_EDGE_RISING) ++ /* SPIs have restrictions on the supported types */ ++ if (irq >= 32 && type != IRQ_TYPE_LEVEL_HIGH && ++ type != IRQ_TYPE_EDGE_RISING) + return -EINVAL; + + if (gic_irq_in_rdist(d)) { +@@ -247,9 +251,7 @@ static int gic_set_type(struct irq_data *d, unsigned int type) + rwp_wait = gic_dist_wait_for_rwp; + } + +- gic_configure_irq(irq, type, base, rwp_wait); +- +- return 0; ++ return gic_configure_irq(irq, type, base, rwp_wait); + } + + static u64 gic_mpidr_to_affinity(u64 mpidr) +@@ -271,11 +273,11 @@ static asmlinkage void __exception_irq_entry gic_handle_irq(struct pt_regs *regs + do { + irqnr = gic_read_iar(); + +- if (likely(irqnr > 15 && irqnr < 1020)) { ++ if (likely(irqnr > 15 && irqnr < 1020) || irqnr >= 8192) { + int err; + err = handle_domain_irq(gic_data.domain, irqnr, regs); + if (err) { +- WARN_ONCE(true, "Unexpected SPI received!\n"); ++ WARN_ONCE(true, "Unexpected interrupt received!\n"); + gic_write_eoir(irqnr); + } + continue; +@@ -333,8 +335,8 @@ static int gic_populate_rdist(void) + MPIDR_AFFINITY_LEVEL(mpidr, 1) << 8 | + MPIDR_AFFINITY_LEVEL(mpidr, 0)); + +- for (i = 0; i < gic_data.redist_regions; i++) { +- void __iomem *ptr = gic_data.redist_base[i]; ++ for (i = 0; i < gic_data.nr_redist_regions; i++) { ++ void __iomem *ptr = gic_data.redist_regions[i].redist_base; + u32 reg; + + reg = readl_relaxed(ptr + GICR_PIDR2) & GIC_PIDR2_ARCH_MASK; +@@ -347,10 +349,13 @@ static int gic_populate_rdist(void) + do { + typer = readq_relaxed(ptr + GICR_TYPER); + if ((typer >> 32) == aff) { ++ u64 offset = ptr - gic_data.redist_regions[i].redist_base; + gic_data_rdist_rd_base() = ptr; +- pr_info("CPU%d: found redistributor %llx @%p\n", ++ gic_data_rdist()->phys_base = gic_data.redist_regions[i].phys_base + offset; ++ pr_info("CPU%d: found redistributor %llx region %d:%pa\n", + smp_processor_id(), +- (unsigned long long)mpidr, ptr); ++ (unsigned long long)mpidr, ++ i, &gic_data_rdist()->phys_base); + return 0; + } + +@@ -385,6 +390,11 @@ static void gic_cpu_sys_reg_init(void) + gic_write_grpen1(1); + } + ++static int gic_dist_supports_lpis(void) ++{ ++ return !!(readl_relaxed(gic_data.dist_base + GICD_TYPER) & GICD_TYPER_LPIS); ++} ++ + static void gic_cpu_init(void) + { + void __iomem *rbase; +@@ -399,6 +409,10 @@ static void gic_cpu_init(void) + + gic_cpu_config(rbase, gic_redist_wait_for_rwp); + ++ /* Give LPIs a spin */ ++ if (IS_ENABLED(CONFIG_ARM_GIC_V3_ITS) && gic_dist_supports_lpis()) ++ its_cpu_init(); ++ + /* initialise system registers */ + gic_cpu_sys_reg_init(); + } +@@ -452,7 +466,7 @@ static u16 gic_compute_target_list(int *base_cpu, const struct cpumask *mask, + tlist |= 1 << (mpidr & 0xf); + + cpu = cpumask_next(cpu, mask); +- if (cpu == nr_cpu_ids) ++ if (cpu >= nr_cpu_ids) + goto out; + + mpidr = cpu_logical_map(cpu); +@@ -467,15 +481,19 @@ out: + return tlist; + } + ++#define MPIDR_TO_SGI_AFFINITY(cluster_id, level) \ ++ (MPIDR_AFFINITY_LEVEL(cluster_id, level) \ ++ << ICC_SGI1R_AFFINITY_## level ##_SHIFT) ++ + static void gic_send_sgi(u64 cluster_id, u16 tlist, unsigned int irq) + { + u64 val; + +- val = (MPIDR_AFFINITY_LEVEL(cluster_id, 3) << 48 | +- MPIDR_AFFINITY_LEVEL(cluster_id, 2) << 32 | +- irq << 24 | +- MPIDR_AFFINITY_LEVEL(cluster_id, 1) << 16 | +- tlist); ++ val = (MPIDR_TO_SGI_AFFINITY(cluster_id, 3) | ++ MPIDR_TO_SGI_AFFINITY(cluster_id, 2) | ++ irq << ICC_SGI1R_SGI_ID_SHIFT | ++ MPIDR_TO_SGI_AFFINITY(cluster_id, 1) | ++ tlist << ICC_SGI1R_TARGET_LIST_SHIFT); + + pr_debug("CPU%d: ICC_SGI1R_EL1 %llx\n", smp_processor_id(), val); + gic_write_sgi1r(val); +@@ -585,26 +603,43 @@ static struct irq_chip gic_chip = { + .irq_set_affinity = gic_set_affinity, + }; + ++#define GIC_ID_NR (1U << gic_data.rdists.id_bits) ++ + static int gic_irq_domain_map(struct irq_domain *d, unsigned int irq, + irq_hw_number_t hw) + { + /* SGIs are private to the core kernel */ + if (hw < 16) + return -EPERM; ++ /* Nothing here */ ++ if (hw >= gic_data.irq_nr && hw < 8192) ++ return -EPERM; ++ /* Off limits */ ++ if (hw >= GIC_ID_NR) ++ return -EPERM; ++ + /* PPIs */ + if (hw < 32) { + irq_set_percpu_devid(irq); +- irq_set_chip_and_handler(irq, &gic_chip, +- handle_percpu_devid_irq); ++ irq_domain_set_info(d, irq, hw, &gic_chip, d->host_data, ++ handle_percpu_devid_irq, NULL, NULL); + set_irq_flags(irq, IRQF_VALID | IRQF_NOAUTOEN); + } + /* SPIs */ + if (hw >= 32 && hw < gic_data.irq_nr) { +- irq_set_chip_and_handler(irq, &gic_chip, +- handle_fasteoi_irq); ++ irq_domain_set_info(d, irq, hw, &gic_chip, d->host_data, ++ handle_fasteoi_irq, NULL, NULL); + set_irq_flags(irq, IRQF_VALID | IRQF_PROBE); + } +- irq_set_chip_data(irq, d->host_data); ++ /* LPIs */ ++ if (hw >= 8192 && hw < GIC_ID_NR) { ++ if (!gic_dist_supports_lpis()) ++ return -EPERM; ++ irq_domain_set_info(d, irq, hw, &gic_chip, d->host_data, ++ handle_fasteoi_irq, NULL, NULL); ++ set_irq_flags(irq, IRQF_VALID); ++ } ++ + return 0; + } + +@@ -625,6 +660,9 @@ static int gic_irq_domain_xlate(struct irq_domain *d, + case 1: /* PPI */ + *out_hwirq = intspec[1] + 16; + break; ++ case GIC_IRQ_TYPE_LPI: /* LPI */ ++ *out_hwirq = intspec[1]; ++ break; + default: + return -EINVAL; + } +@@ -633,17 +671,50 @@ static int gic_irq_domain_xlate(struct irq_domain *d, + return 0; + } + ++static int gic_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, ++ unsigned int nr_irqs, void *arg) ++{ ++ int i, ret; ++ irq_hw_number_t hwirq; ++ unsigned int type = IRQ_TYPE_NONE; ++ struct of_phandle_args *irq_data = arg; ++ ++ ret = gic_irq_domain_xlate(domain, irq_data->np, irq_data->args, ++ irq_data->args_count, &hwirq, &type); ++ if (ret) ++ return ret; ++ ++ for (i = 0; i < nr_irqs; i++) ++ gic_irq_domain_map(domain, virq + i, hwirq + i); ++ ++ return 0; ++} ++ ++static void gic_irq_domain_free(struct irq_domain *domain, unsigned int virq, ++ unsigned int nr_irqs) ++{ ++ int i; ++ ++ for (i = 0; i < nr_irqs; i++) { ++ struct irq_data *d = irq_domain_get_irq_data(domain, virq + i); ++ irq_set_handler(virq + i, NULL); ++ irq_domain_reset_irq_data(d); ++ } ++} ++ + static const struct irq_domain_ops gic_irq_domain_ops = { +- .map = gic_irq_domain_map, + .xlate = gic_irq_domain_xlate, ++ .alloc = gic_irq_domain_alloc, ++ .free = gic_irq_domain_free, + }; + + static int __init gic_of_init(struct device_node *node, struct device_node *parent) + { + void __iomem *dist_base; +- void __iomem **redist_base; ++ struct redist_region *rdist_regs; + u64 redist_stride; +- u32 redist_regions; ++ u32 nr_redist_regions; ++ u32 typer; + u32 reg; + int gic_irqs; + int err; +@@ -664,54 +735,63 @@ static int __init gic_of_init(struct device_node *node, struct device_node *pare + goto out_unmap_dist; + } + +- if (of_property_read_u32(node, "#redistributor-regions", &redist_regions)) +- redist_regions = 1; ++ if (of_property_read_u32(node, "#redistributor-regions", &nr_redist_regions)) ++ nr_redist_regions = 1; + +- redist_base = kzalloc(sizeof(*redist_base) * redist_regions, GFP_KERNEL); +- if (!redist_base) { ++ rdist_regs = kzalloc(sizeof(*rdist_regs) * nr_redist_regions, GFP_KERNEL); ++ if (!rdist_regs) { + err = -ENOMEM; + goto out_unmap_dist; + } + +- for (i = 0; i < redist_regions; i++) { +- redist_base[i] = of_iomap(node, 1 + i); +- if (!redist_base[i]) { ++ for (i = 0; i < nr_redist_regions; i++) { ++ struct resource res; ++ int ret; ++ ++ ret = of_address_to_resource(node, 1 + i, &res); ++ rdist_regs[i].redist_base = of_iomap(node, 1 + i); ++ if (ret || !rdist_regs[i].redist_base) { + pr_err("%s: couldn't map region %d\n", + node->full_name, i); + err = -ENODEV; + goto out_unmap_rdist; + } ++ rdist_regs[i].phys_base = res.start; + } + + if (of_property_read_u64(node, "redistributor-stride", &redist_stride)) + redist_stride = 0; + + gic_data.dist_base = dist_base; +- gic_data.redist_base = redist_base; +- gic_data.redist_regions = redist_regions; ++ gic_data.redist_regions = rdist_regs; ++ gic_data.nr_redist_regions = nr_redist_regions; + gic_data.redist_stride = redist_stride; + + /* + * Find out how many interrupts are supported. + * The GIC only supports up to 1020 interrupt sources (SGI+PPI+SPI) + */ +- gic_irqs = readl_relaxed(gic_data.dist_base + GICD_TYPER) & 0x1f; +- gic_irqs = (gic_irqs + 1) * 32; ++ typer = readl_relaxed(gic_data.dist_base + GICD_TYPER); ++ gic_data.rdists.id_bits = GICD_TYPER_ID_BITS(typer); ++ gic_irqs = GICD_TYPER_IRQS(typer); + if (gic_irqs > 1020) + gic_irqs = 1020; + gic_data.irq_nr = gic_irqs; + + gic_data.domain = irq_domain_add_tree(node, &gic_irq_domain_ops, + &gic_data); +- gic_data.rdist = alloc_percpu(typeof(*gic_data.rdist)); ++ gic_data.rdists.rdist = alloc_percpu(typeof(*gic_data.rdists.rdist)); + +- if (WARN_ON(!gic_data.domain) || WARN_ON(!gic_data.rdist)) { ++ if (WARN_ON(!gic_data.domain) || WARN_ON(!gic_data.rdists.rdist)) { + err = -ENOMEM; + goto out_free; + } + + set_handle_irq(gic_handle_irq); + ++ if (IS_ENABLED(CONFIG_ARM_GIC_V3_ITS) && gic_dist_supports_lpis()) ++ its_init(node, &gic_data.rdists, gic_data.domain); ++ + gic_smp_init(); + gic_dist_init(); + gic_cpu_init(); +@@ -722,12 +802,12 @@ static int __init gic_of_init(struct device_node *node, struct device_node *pare + out_free: + if (gic_data.domain) + irq_domain_remove(gic_data.domain); +- free_percpu(gic_data.rdist); ++ free_percpu(gic_data.rdists.rdist); + out_unmap_rdist: +- for (i = 0; i < redist_regions; i++) +- if (redist_base[i]) +- iounmap(redist_base[i]); +- kfree(redist_base); ++ for (i = 0; i < nr_redist_regions; i++) ++ if (rdist_regs[i].redist_base) ++ iounmap(rdist_regs[i].redist_base); ++ kfree(rdist_regs); + out_unmap_dist: + iounmap(dist_base); + return err; +diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c +index 38493ff..ab0b1cb 100644 +--- a/drivers/irqchip/irq-gic.c ++++ b/drivers/irqchip/irq-gic.c +@@ -188,12 +188,15 @@ static int gic_set_type(struct irq_data *d, unsigned int type) + { + void __iomem *base = gic_dist_base(d); + unsigned int gicirq = gic_irq(d); ++ int ret; + + /* Interrupt configuration for SGIs can't be changed */ + if (gicirq < 16) + return -EINVAL; + +- if (type != IRQ_TYPE_LEVEL_HIGH && type != IRQ_TYPE_EDGE_RISING) ++ /* SPIs have restrictions on the supported types */ ++ if (gicirq >= 32 && type != IRQ_TYPE_LEVEL_HIGH && ++ type != IRQ_TYPE_EDGE_RISING) + return -EINVAL; + + raw_spin_lock(&irq_controller_lock); +@@ -201,11 +204,11 @@ static int gic_set_type(struct irq_data *d, unsigned int type) + if (gic_arch_extn.irq_set_type) + gic_arch_extn.irq_set_type(d, type); + +- gic_configure_irq(gicirq, type, base, NULL); ++ ret = gic_configure_irq(gicirq, type, base, NULL); + + raw_spin_unlock(&irq_controller_lock); + +- return 0; ++ return ret; + } + + static int gic_retrigger(struct irq_data *d) +@@ -788,17 +791,16 @@ static int gic_irq_domain_map(struct irq_domain *d, unsigned int irq, + { + if (hw < 32) { + irq_set_percpu_devid(irq); +- irq_set_chip_and_handler(irq, &gic_chip, +- handle_percpu_devid_irq); ++ irq_domain_set_info(d, irq, hw, &gic_chip, d->host_data, ++ handle_percpu_devid_irq, NULL, NULL); + set_irq_flags(irq, IRQF_VALID | IRQF_NOAUTOEN); + } else { +- irq_set_chip_and_handler(irq, &gic_chip, +- handle_fasteoi_irq); ++ irq_domain_set_info(d, irq, hw, &gic_chip, d->host_data, ++ handle_fasteoi_irq, NULL, NULL); + set_irq_flags(irq, IRQF_VALID | IRQF_PROBE); + + gic_routable_irq_domain_ops->map(d, irq, hw); + } +- irq_set_chip_data(irq, d->host_data); + return 0; + } + +@@ -858,6 +860,31 @@ static struct notifier_block gic_cpu_notifier = { + }; + #endif + ++static int gic_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, ++ unsigned int nr_irqs, void *arg) ++{ ++ int i, ret; ++ irq_hw_number_t hwirq; ++ unsigned int type = IRQ_TYPE_NONE; ++ struct of_phandle_args *irq_data = arg; ++ ++ ret = gic_irq_domain_xlate(domain, irq_data->np, irq_data->args, ++ irq_data->args_count, &hwirq, &type); ++ if (ret) ++ return ret; ++ ++ for (i = 0; i < nr_irqs; i++) ++ gic_irq_domain_map(domain, virq + i, hwirq + i); ++ ++ return 0; ++} ++ ++static const struct irq_domain_ops gic_irq_domain_hierarchy_ops = { ++ .xlate = gic_irq_domain_xlate, ++ .alloc = gic_irq_domain_alloc, ++ .free = irq_domain_free_irqs_top, ++}; ++ + static const struct irq_domain_ops gic_irq_domain_ops = { + .map = gic_irq_domain_map, + .unmap = gic_irq_domain_unmap, +@@ -948,18 +975,6 @@ void __init gic_init_bases(unsigned int gic_nr, int irq_start, + gic_cpu_map[i] = 0xff; + + /* +- * For primary GICs, skip over SGIs. +- * For secondary GICs, skip over PPIs, too. +- */ +- if (gic_nr == 0 && (irq_start & 31) > 0) { +- hwirq_base = 16; +- if (irq_start != -1) +- irq_start = (irq_start & ~31) + 16; +- } else { +- hwirq_base = 32; +- } +- +- /* + * Find out how many interrupts are supported. + * The GIC only supports up to 1020 interrupt sources. + */ +@@ -969,10 +984,31 @@ void __init gic_init_bases(unsigned int gic_nr, int irq_start, + gic_irqs = 1020; + gic->gic_irqs = gic_irqs; + +- gic_irqs -= hwirq_base; /* calculate # of irqs to allocate */ ++ if (node) { /* DT case */ ++ const struct irq_domain_ops *ops = &gic_irq_domain_hierarchy_ops; ++ ++ if (!of_property_read_u32(node, "arm,routable-irqs", ++ &nr_routable_irqs)) { ++ ops = &gic_irq_domain_ops; ++ gic_irqs = nr_routable_irqs; ++ } ++ ++ gic->domain = irq_domain_add_linear(node, gic_irqs, ops, gic); ++ } else { /* Non-DT case */ ++ /* ++ * For primary GICs, skip over SGIs. ++ * For secondary GICs, skip over PPIs, too. ++ */ ++ if (gic_nr == 0 && (irq_start & 31) > 0) { ++ hwirq_base = 16; ++ if (irq_start != -1) ++ irq_start = (irq_start & ~31) + 16; ++ } else { ++ hwirq_base = 32; ++ } ++ ++ gic_irqs -= hwirq_base; /* calculate # of irqs to allocate */ + +- if (of_property_read_u32(node, "arm,routable-irqs", +- &nr_routable_irqs)) { + irq_base = irq_alloc_descs(irq_start, 16, gic_irqs, + numa_node_id()); + if (IS_ERR_VALUE(irq_base)) { +@@ -983,10 +1019,6 @@ void __init gic_init_bases(unsigned int gic_nr, int irq_start, + + gic->domain = irq_domain_add_legacy(node, gic_irqs, irq_base, + hwirq_base, &gic_irq_domain_ops, gic); +- } else { +- gic->domain = irq_domain_add_linear(node, nr_routable_irqs, +- &gic_irq_domain_ops, +- gic); + } + + if (WARN_ON(!gic->domain)) +@@ -1037,6 +1069,10 @@ gic_of_init(struct device_node *node, struct device_node *parent) + irq = irq_of_parse_and_map(node, 0); + gic_cascade_irq(gic_cnt, irq); + } ++ ++ if (IS_ENABLED(CONFIG_ARM_GIC_V2M)) ++ gicv2m_of_init(node, gic_data[gic_cnt].domain); ++ + gic_cnt++; + return 0; + } +diff --git a/drivers/irqchip/irq-hip04.c b/drivers/irqchip/irq-hip04.c +index 9c8f833..5507a0c 100644 +--- a/drivers/irqchip/irq-hip04.c ++++ b/drivers/irqchip/irq-hip04.c +@@ -120,21 +120,24 @@ static int hip04_irq_set_type(struct irq_data *d, unsigned int type) + { + void __iomem *base = hip04_dist_base(d); + unsigned int irq = hip04_irq(d); ++ int ret; + + /* Interrupt configuration for SGIs can't be changed */ + if (irq < 16) + return -EINVAL; + +- if (type != IRQ_TYPE_LEVEL_HIGH && type != IRQ_TYPE_EDGE_RISING) ++ /* SPIs have restrictions on the supported types */ ++ if (irq >= 32 && type != IRQ_TYPE_LEVEL_HIGH && ++ type != IRQ_TYPE_EDGE_RISING) + return -EINVAL; + + raw_spin_lock(&irq_controller_lock); + +- gic_configure_irq(irq, type, base, NULL); ++ ret = gic_configure_irq(irq, type, base, NULL); + + raw_spin_unlock(&irq_controller_lock); + +- return 0; ++ return ret; + } + + #ifdef CONFIG_SMP +diff --git a/drivers/irqchip/irq-sunxi-nmi.c b/drivers/irqchip/irq-sunxi-nmi.c +index eb9b59e..6b2b582 100644 +--- a/drivers/irqchip/irq-sunxi-nmi.c ++++ b/drivers/irqchip/irq-sunxi-nmi.c +@@ -50,12 +50,12 @@ static struct sunxi_sc_nmi_reg_offs sun6i_reg_offs = { + static inline void sunxi_sc_nmi_write(struct irq_chip_generic *gc, u32 off, + u32 val) + { +- irq_reg_writel(val, gc->reg_base + off); ++ irq_reg_writel(gc, val, off); + } + + static inline u32 sunxi_sc_nmi_read(struct irq_chip_generic *gc, u32 off) + { +- return irq_reg_readl(gc->reg_base + off); ++ return irq_reg_readl(gc, off); + } + + static void sunxi_sc_nmi_handle_irq(unsigned int irq, struct irq_desc *desc) +diff --git a/drivers/irqchip/irq-tb10x.c b/drivers/irqchip/irq-tb10x.c +index 7c44c99..accc200 100644 +--- a/drivers/irqchip/irq-tb10x.c ++++ b/drivers/irqchip/irq-tb10x.c +@@ -43,12 +43,12 @@ + static inline void ab_irqctl_writereg(struct irq_chip_generic *gc, u32 reg, + u32 val) + { +- irq_reg_writel(val, gc->reg_base + reg); ++ irq_reg_writel(gc, val, reg); + } + + static inline u32 ab_irqctl_readreg(struct irq_chip_generic *gc, u32 reg) + { +- return irq_reg_readl(gc->reg_base + reg); ++ return irq_reg_readl(gc, reg); + } + + static int tb10x_irq_set_type(struct irq_data *data, unsigned int flow_type) +diff --git a/drivers/memory/Kconfig b/drivers/memory/Kconfig +index 6d91c27..d6af99f 100644 +--- a/drivers/memory/Kconfig ++++ b/drivers/memory/Kconfig +@@ -83,6 +83,6 @@ config FSL_CORENET_CF + + config FSL_IFC + bool +- depends on FSL_SOC ++ depends on FSL_SOC || ARCH_LAYERSCAPE + + endif +diff --git a/drivers/memory/fsl_ifc.c b/drivers/memory/fsl_ifc.c +index 3d5d792..1b182b1 100644 +--- a/drivers/memory/fsl_ifc.c ++++ b/drivers/memory/fsl_ifc.c +@@ -22,6 +22,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -30,7 +31,9 @@ + #include + #include + #include +-#include ++#include ++#include ++#include + + struct fsl_ifc_ctrl *fsl_ifc_ctrl_dev; + EXPORT_SYMBOL(fsl_ifc_ctrl_dev); +@@ -58,11 +61,11 @@ int fsl_ifc_find(phys_addr_t addr_base) + { + int i = 0; + +- if (!fsl_ifc_ctrl_dev || !fsl_ifc_ctrl_dev->regs) ++ if (!fsl_ifc_ctrl_dev || !fsl_ifc_ctrl_dev->gregs) + return -ENODEV; + +- for (i = 0; i < ARRAY_SIZE(fsl_ifc_ctrl_dev->regs->cspr_cs); i++) { +- u32 cspr = in_be32(&fsl_ifc_ctrl_dev->regs->cspr_cs[i].cspr); ++ for (i = 0; i < fsl_ifc_ctrl_dev->banks; i++) { ++ u32 cspr = ifc_in32(&fsl_ifc_ctrl_dev->gregs->cspr_cs[i].cspr); + if (cspr & CSPR_V && (cspr & CSPR_BA) == + convert_ifc_address(addr_base)) + return i; +@@ -74,21 +77,21 @@ EXPORT_SYMBOL(fsl_ifc_find); + + static int fsl_ifc_ctrl_init(struct fsl_ifc_ctrl *ctrl) + { +- struct fsl_ifc_regs __iomem *ifc = ctrl->regs; ++ struct fsl_ifc_global __iomem *ifc = ctrl->gregs; + + /* + * Clear all the common status and event registers + */ +- if (in_be32(&ifc->cm_evter_stat) & IFC_CM_EVTER_STAT_CSER) +- out_be32(&ifc->cm_evter_stat, IFC_CM_EVTER_STAT_CSER); ++ if (ifc_in32(&ifc->cm_evter_stat) & IFC_CM_EVTER_STAT_CSER) ++ ifc_out32(IFC_CM_EVTER_STAT_CSER, &ifc->cm_evter_stat); + + /* enable all error and events */ +- out_be32(&ifc->cm_evter_en, IFC_CM_EVTER_EN_CSEREN); ++ ifc_out32(IFC_CM_EVTER_EN_CSEREN, &ifc->cm_evter_en); + + /* enable all error and event interrupts */ +- out_be32(&ifc->cm_evter_intr_en, IFC_CM_EVTER_INTR_EN_CSERIREN); +- out_be32(&ifc->cm_erattr0, 0x0); +- out_be32(&ifc->cm_erattr1, 0x0); ++ ifc_out32(IFC_CM_EVTER_INTR_EN_CSERIREN, &ifc->cm_evter_intr_en); ++ ifc_out32(0x0, &ifc->cm_erattr0); ++ ifc_out32(0x0, &ifc->cm_erattr1); + + return 0; + } +@@ -103,7 +106,7 @@ static int fsl_ifc_ctrl_remove(struct platform_device *dev) + irq_dispose_mapping(ctrl->nand_irq); + irq_dispose_mapping(ctrl->irq); + +- iounmap(ctrl->regs); ++ iounmap(ctrl->gregs); + + dev_set_drvdata(&dev->dev, NULL); + kfree(ctrl); +@@ -121,15 +124,15 @@ static DEFINE_SPINLOCK(nand_irq_lock); + + static u32 check_nand_stat(struct fsl_ifc_ctrl *ctrl) + { +- struct fsl_ifc_regs __iomem *ifc = ctrl->regs; ++ struct fsl_ifc_runtime __iomem *ifc = ctrl->rregs; + unsigned long flags; + u32 stat; + + spin_lock_irqsave(&nand_irq_lock, flags); + +- stat = in_be32(&ifc->ifc_nand.nand_evter_stat); ++ stat = ifc_in32(&ifc->ifc_nand.nand_evter_stat); + if (stat) { +- out_be32(&ifc->ifc_nand.nand_evter_stat, stat); ++ ifc_out32(stat, &ifc->ifc_nand.nand_evter_stat); + ctrl->nand_stat = stat; + wake_up(&ctrl->nand_wait); + } +@@ -156,21 +159,21 @@ static irqreturn_t fsl_ifc_nand_irq(int irqno, void *data) + static irqreturn_t fsl_ifc_ctrl_irq(int irqno, void *data) + { + struct fsl_ifc_ctrl *ctrl = data; +- struct fsl_ifc_regs __iomem *ifc = ctrl->regs; ++ struct fsl_ifc_global __iomem *ifc = ctrl->gregs; + u32 err_axiid, err_srcid, status, cs_err, err_addr; + irqreturn_t ret = IRQ_NONE; + + /* read for chip select error */ +- cs_err = in_be32(&ifc->cm_evter_stat); ++ cs_err = ifc_in32(&ifc->cm_evter_stat); + if (cs_err) { + dev_err(ctrl->dev, "transaction sent to IFC is not mapped to" + "any memory bank 0x%08X\n", cs_err); + /* clear the chip select error */ +- out_be32(&ifc->cm_evter_stat, IFC_CM_EVTER_STAT_CSER); ++ ifc_out32(IFC_CM_EVTER_STAT_CSER, &ifc->cm_evter_stat); + + /* read error attribute registers print the error information */ +- status = in_be32(&ifc->cm_erattr0); +- err_addr = in_be32(&ifc->cm_erattr1); ++ status = ifc_in32(&ifc->cm_erattr0); ++ err_addr = ifc_in32(&ifc->cm_erattr1); + + if (status & IFC_CM_ERATTR0_ERTYP_READ) + dev_err(ctrl->dev, "Read transaction error" +@@ -213,7 +216,8 @@ static irqreturn_t fsl_ifc_ctrl_irq(int irqno, void *data) + static int fsl_ifc_ctrl_probe(struct platform_device *dev) + { + int ret = 0; +- ++ int version, banks; ++ void __iomem *addr; + + dev_info(&dev->dev, "Freescale Integrated Flash Controller\n"); + +@@ -224,16 +228,41 @@ static int fsl_ifc_ctrl_probe(struct platform_device *dev) + dev_set_drvdata(&dev->dev, fsl_ifc_ctrl_dev); + + /* IOMAP the entire IFC region */ +- fsl_ifc_ctrl_dev->regs = of_iomap(dev->dev.of_node, 0); +- if (!fsl_ifc_ctrl_dev->regs) { ++ fsl_ifc_ctrl_dev->gregs = of_iomap(dev->dev.of_node, 0); ++ if (!fsl_ifc_ctrl_dev->gregs) { + dev_err(&dev->dev, "failed to get memory region\n"); + ret = -ENODEV; + goto err; + } + ++ if (of_property_read_bool(dev->dev.of_node, "little-endian")) { ++ fsl_ifc_ctrl_dev->little_endian = true; ++ dev_dbg(&dev->dev, "IFC REGISTERS are LITTLE endian\n"); ++ } else { ++ fsl_ifc_ctrl_dev->little_endian = false; ++ dev_dbg(&dev->dev, "IFC REGISTERS are BIG endian\n"); ++ } ++ ++ version = ifc_in32(&fsl_ifc_ctrl_dev->gregs->ifc_rev) & ++ FSL_IFC_VERSION_MASK; ++ ++ banks = (version == FSL_IFC_VERSION_1_0_0) ? 4 : 8; ++ dev_info(&dev->dev, "IFC version %d.%d, %d banks\n", ++ version >> 24, (version >> 16) & 0xf, banks); ++ ++ fsl_ifc_ctrl_dev->version = version; ++ fsl_ifc_ctrl_dev->banks = banks; ++ ++ addr = fsl_ifc_ctrl_dev->gregs; ++ if (version >= FSL_IFC_VERSION_2_0_0) ++ addr += PGOFFSET_64K; ++ else ++ addr += PGOFFSET_4K; ++ fsl_ifc_ctrl_dev->rregs = addr; ++ + /* get the Controller level irq */ + fsl_ifc_ctrl_dev->irq = irq_of_parse_and_map(dev->dev.of_node, 0); +- if (fsl_ifc_ctrl_dev->irq == NO_IRQ) { ++ if (fsl_ifc_ctrl_dev->irq == 0) { + dev_err(&dev->dev, "failed to get irq resource " + "for IFC\n"); + ret = -ENODEV; +diff --git a/drivers/mfd/vexpress-sysreg.c b/drivers/mfd/vexpress-sysreg.c +index 9e21e4f..8f43ab8 100644 +--- a/drivers/mfd/vexpress-sysreg.c ++++ b/drivers/mfd/vexpress-sysreg.c +@@ -223,7 +223,7 @@ static int vexpress_sysreg_probe(struct platform_device *pdev) + vexpress_config_set_master(vexpress_sysreg_get_master()); + + /* Confirm board type against DT property, if available */ +- if (of_property_read_u32(of_allnodes, "arm,hbi", &dt_hbi) == 0) { ++ if (of_property_read_u32(of_root, "arm,hbi", &dt_hbi) == 0) { + u32 id = vexpress_get_procid(VEXPRESS_SITE_MASTER); + u32 hbi = (id >> SYS_PROCIDx_HBI_SHIFT) & SYS_HBI_MASK; + +diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c +index 10ecc0a..d356dbc 100644 +--- a/drivers/mmc/card/block.c ++++ b/drivers/mmc/card/block.c +@@ -2402,6 +2402,10 @@ static const struct mmc_fixup blk_fixups[] = + * + * N.B. This doesn't affect SD cards. + */ ++ MMC_FIXUP("SDMB-32", CID_MANFID_SANDISK, CID_OEMID_ANY, add_quirk_mmc, ++ MMC_QUIRK_BLK_NO_CMD23), ++ MMC_FIXUP("SDM032", CID_MANFID_SANDISK, CID_OEMID_ANY, add_quirk_mmc, ++ MMC_QUIRK_BLK_NO_CMD23), + MMC_FIXUP("MMC08G", CID_MANFID_TOSHIBA, CID_OEMID_ANY, add_quirk_mmc, + MMC_QUIRK_BLK_NO_CMD23), + MMC_FIXUP("MMC16G", CID_MANFID_TOSHIBA, CID_OEMID_ANY, add_quirk_mmc, +diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig +index 1386065..b8c9b73 100644 +--- a/drivers/mmc/host/Kconfig ++++ b/drivers/mmc/host/Kconfig +@@ -66,7 +66,7 @@ config MMC_SDHCI_BIG_ENDIAN_32BIT_BYTE_SWAPPER + has the effect of scrambling the addresses and formats of data + accessed in sizes other than the datum size. + +- This is the case for the Freescale eSDHC and Nintendo Wii SDHCI. ++ This is the case for the Nintendo Wii SDHCI. + + config MMC_SDHCI_PCI + tristate "SDHCI support on PCI bus" +@@ -130,8 +130,10 @@ config MMC_SDHCI_OF_ARASAN + config MMC_SDHCI_OF_ESDHC + tristate "SDHCI OF support for the Freescale eSDHC controller" + depends on MMC_SDHCI_PLTFM +- depends on PPC_OF +- select MMC_SDHCI_BIG_ENDIAN_32BIT_BYTE_SWAPPER ++ depends on PPC || ARCH_MXC || ARCH_LAYERSCAPE ++ select MMC_SDHCI_IO_ACCESSORS ++ select FSL_SOC_DRIVERS ++ select FSL_GUTS + help + This selects the Freescale eSDHC controller support. + +@@ -142,7 +144,7 @@ config MMC_SDHCI_OF_ESDHC + config MMC_SDHCI_OF_HLWD + tristate "SDHCI OF support for the Nintendo Wii SDHCI controllers" + depends on MMC_SDHCI_PLTFM +- depends on PPC_OF ++ depends on PPC + select MMC_SDHCI_BIG_ENDIAN_32BIT_BYTE_SWAPPER + help + This selects the Secure Digital Host Controller Interface (SDHCI) +diff --git a/drivers/mmc/host/sdhci-esdhc.h b/drivers/mmc/host/sdhci-esdhc.h +index a870c42..f2baede 100644 +--- a/drivers/mmc/host/sdhci-esdhc.h ++++ b/drivers/mmc/host/sdhci-esdhc.h +@@ -21,16 +21,23 @@ + #define ESDHC_DEFAULT_QUIRKS (SDHCI_QUIRK_FORCE_BLK_SZ_2048 | \ + SDHCI_QUIRK_NO_BUSY_IRQ | \ + SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK | \ +- SDHCI_QUIRK_PIO_NEEDS_DELAY) ++ SDHCI_QUIRK_PIO_NEEDS_DELAY | \ ++ SDHCI_QUIRK_NO_HISPD_BIT) ++ ++#define ESDHC_PROCTL 0x28 + + #define ESDHC_SYSTEM_CONTROL 0x2c + #define ESDHC_CLOCK_MASK 0x0000fff0 + #define ESDHC_PREDIV_SHIFT 8 + #define ESDHC_DIVIDER_SHIFT 4 ++#define ESDHC_CLOCK_CRDEN 0x00000008 + #define ESDHC_CLOCK_PEREN 0x00000004 + #define ESDHC_CLOCK_HCKEN 0x00000002 + #define ESDHC_CLOCK_IPGEN 0x00000001 + ++#define ESDHC_PRESENT_STATE 0x24 ++#define ESDHC_CLOCK_STABLE 0x00000008 ++ + /* pltfm-specific */ + #define ESDHC_HOST_CONTROL_LE 0x20 + +diff --git a/drivers/mmc/host/sdhci-of-esdhc.c b/drivers/mmc/host/sdhci-of-esdhc.c +index 8872c85..4a4a693 100644 +--- a/drivers/mmc/host/sdhci-of-esdhc.c ++++ b/drivers/mmc/host/sdhci-of-esdhc.c +@@ -18,128 +18,334 @@ + #include + #include + #include ++#include ++#include + #include + #include "sdhci-pltfm.h" + #include "sdhci-esdhc.h" + + #define VENDOR_V_22 0x12 + #define VENDOR_V_23 0x13 +-static u32 esdhc_readl(struct sdhci_host *host, int reg) ++ ++struct sdhci_esdhc { ++ u8 vendor_ver; ++ u8 spec_ver; ++ u32 soc_ver; ++ u8 soc_rev; ++}; ++ ++/** ++ * esdhc_read*_fixup - Fixup the value read from incompatible eSDHC register ++ * to make it compatible with SD spec. ++ * ++ * @host: pointer to sdhci_host ++ * @spec_reg: SD spec register address ++ * @value: 32bit eSDHC register value on spec_reg address ++ * ++ * In SD spec, there are 8/16/32/64 bits registers, while all of eSDHC ++ * registers are 32 bits. There are differences in register size, register ++ * address, register function, bit position and function between eSDHC spec ++ * and SD spec. ++ * ++ * Return a fixed up register value ++ */ ++static u32 esdhc_readl_fixup(struct sdhci_host *host, ++ int spec_reg, u32 value) + { ++ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); ++ struct sdhci_esdhc *esdhc = pltfm_host->priv; + u32 ret; + +- ret = in_be32(host->ioaddr + reg); + /* + * The bit of ADMA flag in eSDHC is not compatible with standard + * SDHC register, so set fake flag SDHCI_CAN_DO_ADMA2 when ADMA is + * supported by eSDHC. + * And for many FSL eSDHC controller, the reset value of field +- * SDHCI_CAN_DO_ADMA1 is one, but some of them can't support ADMA, ++ * SDHCI_CAN_DO_ADMA1 is 1, but some of them can't support ADMA, + * only these vendor version is greater than 2.2/0x12 support ADMA. +- * For FSL eSDHC, must aligned 4-byte, so use 0xFC to read the +- * the verdor version number, oxFE is SDHCI_HOST_VERSION. + */ +- if ((reg == SDHCI_CAPABILITIES) && (ret & SDHCI_CAN_DO_ADMA1)) { +- u32 tmp = in_be32(host->ioaddr + SDHCI_SLOT_INT_STATUS); +- tmp = (tmp & SDHCI_VENDOR_VER_MASK) >> SDHCI_VENDOR_VER_SHIFT; +- if (tmp > VENDOR_V_22) +- ret |= SDHCI_CAN_DO_ADMA2; ++ if ((spec_reg == SDHCI_CAPABILITIES) && (value & SDHCI_CAN_DO_ADMA1)) { ++ if (esdhc->vendor_ver > VENDOR_V_22) { ++ ret = value | SDHCI_CAN_DO_ADMA2; ++ return ret; ++ } + } +- ++ ret = value; + return ret; + } + +-static u16 esdhc_readw(struct sdhci_host *host, int reg) ++static u16 esdhc_readw_fixup(struct sdhci_host *host, ++ int spec_reg, u32 value) + { + u16 ret; +- int base = reg & ~0x3; +- int shift = (reg & 0x2) * 8; ++ int shift = (spec_reg & 0x2) * 8; + +- if (unlikely(reg == SDHCI_HOST_VERSION)) +- ret = in_be32(host->ioaddr + base) & 0xffff; ++ if (spec_reg == SDHCI_HOST_VERSION) ++ ret = value & 0xffff; + else +- ret = (in_be32(host->ioaddr + base) >> shift) & 0xffff; ++ ret = (value >> shift) & 0xffff; + return ret; + } + +-static u8 esdhc_readb(struct sdhci_host *host, int reg) ++static u8 esdhc_readb_fixup(struct sdhci_host *host, ++ int spec_reg, u32 value) + { +- int base = reg & ~0x3; +- int shift = (reg & 0x3) * 8; +- u8 ret = (in_be32(host->ioaddr + base) >> shift) & 0xff; ++ u8 ret; ++ u8 dma_bits; ++ int shift = (spec_reg & 0x3) * 8; ++ ++ ret = (value >> shift) & 0xff; + + /* + * "DMA select" locates at offset 0x28 in SD specification, but on + * P5020 or P3041, it locates at 0x29. + */ +- if (reg == SDHCI_HOST_CONTROL) { +- u32 dma_bits; +- +- dma_bits = in_be32(host->ioaddr + reg); ++ if (spec_reg == SDHCI_HOST_CONTROL) { + /* DMA select is 22,23 bits in Protocol Control Register */ +- dma_bits = (dma_bits >> 5) & SDHCI_CTRL_DMA_MASK; +- ++ dma_bits = (value >> 5) & SDHCI_CTRL_DMA_MASK; + /* fixup the result */ + ret &= ~SDHCI_CTRL_DMA_MASK; + ret |= dma_bits; + } +- + return ret; + } + +-static void esdhc_writel(struct sdhci_host *host, u32 val, int reg) ++/** ++ * esdhc_write*_fixup - Fixup the SD spec register value so that it could be ++ * written into eSDHC register. ++ * ++ * @host: pointer to sdhci_host ++ * @spec_reg: SD spec register address ++ * @value: 8/16/32bit SD spec register value that would be written ++ * @old_value: 32bit eSDHC register value on spec_reg address ++ * ++ * In SD spec, there are 8/16/32/64 bits registers, while all of eSDHC ++ * registers are 32 bits. There are differences in register size, register ++ * address, register function, bit position and function between eSDHC spec ++ * and SD spec. ++ * ++ * Return a fixed up register value ++ */ ++static u32 esdhc_writel_fixup(struct sdhci_host *host, ++ int spec_reg, u32 value, u32 old_value) + { ++ u32 ret; ++ + /* +- * Enable IRQSTATEN[BGESEN] is just to set IRQSTAT[BGE] +- * when SYSCTL[RSTD]) is set for some special operations. +- * No any impact other operation. ++ * Enabling IRQSTATEN[BGESEN] is just to set IRQSTAT[BGE] ++ * when SYSCTL[RSTD] is set for some special operations. ++ * No any impact on other operation. + */ +- if (reg == SDHCI_INT_ENABLE) +- val |= SDHCI_INT_BLK_GAP; +- sdhci_be32bs_writel(host, val, reg); ++ if (spec_reg == SDHCI_INT_ENABLE) ++ ret = value | SDHCI_INT_BLK_GAP; ++ else ++ ret = value; ++ ++ return ret; + } + +-static void esdhc_writew(struct sdhci_host *host, u16 val, int reg) ++static u32 esdhc_writew_fixup(struct sdhci_host *host, ++ int spec_reg, u16 value, u32 old_value) + { +- if (reg == SDHCI_BLOCK_SIZE) { ++ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); ++ int shift = (spec_reg & 0x2) * 8; ++ u32 ret; ++ ++ switch (spec_reg) { ++ case SDHCI_TRANSFER_MODE: ++ /* ++ * Postpone this write, we must do it together with a ++ * command write that is down below. Return old value. ++ */ ++ pltfm_host->xfer_mode_shadow = value; ++ return old_value; ++ case SDHCI_COMMAND: ++ ret = (value << 16) | pltfm_host->xfer_mode_shadow; ++ return ret; ++ } ++ ++ ret = old_value & (~(0xffff << shift)); ++ ret |= (value << shift); ++ ++ if (spec_reg == SDHCI_BLOCK_SIZE) { + /* + * Two last DMA bits are reserved, and first one is used for + * non-standard blksz of 4096 bytes that we don't support + * yet. So clear the DMA boundary bits. + */ +- val &= ~SDHCI_MAKE_BLKSZ(0x7, 0); ++ ret &= (~SDHCI_MAKE_BLKSZ(0x7, 0)); + } +- sdhci_be32bs_writew(host, val, reg); ++ return ret; + } + +-static void esdhc_writeb(struct sdhci_host *host, u8 val, int reg) ++static u32 esdhc_writeb_fixup(struct sdhci_host *host, ++ int spec_reg, u8 value, u32 old_value) + { ++ u32 ret; ++ u32 dma_bits; ++ u8 tmp; ++ int shift = (spec_reg & 0x3) * 8; ++ ++ /* ++ * eSDHC doesn't have a standard power control register, so we do ++ * nothing here to avoid incorrect operation. ++ */ ++ if (spec_reg == SDHCI_POWER_CONTROL) ++ return old_value; + /* + * "DMA select" location is offset 0x28 in SD specification, but on + * P5020 or P3041, it's located at 0x29. + */ +- if (reg == SDHCI_HOST_CONTROL) { +- u32 dma_bits; +- ++ if (spec_reg == SDHCI_HOST_CONTROL) { + /* + * If host control register is not standard, exit + * this function + */ + if (host->quirks2 & SDHCI_QUIRK2_BROKEN_HOST_CONTROL) +- return; ++ return old_value; + + /* DMA select is 22,23 bits in Protocol Control Register */ +- dma_bits = (val & SDHCI_CTRL_DMA_MASK) << 5; +- clrsetbits_be32(host->ioaddr + reg , SDHCI_CTRL_DMA_MASK << 5, +- dma_bits); +- val &= ~SDHCI_CTRL_DMA_MASK; +- val |= in_be32(host->ioaddr + reg) & SDHCI_CTRL_DMA_MASK; ++ dma_bits = (value & SDHCI_CTRL_DMA_MASK) << 5; ++ ret = (old_value & (~(SDHCI_CTRL_DMA_MASK << 5))) | dma_bits; ++ tmp = (value & (~SDHCI_CTRL_DMA_MASK)) | ++ (old_value & SDHCI_CTRL_DMA_MASK); ++ ret = (ret & (~0xff)) | tmp; ++ ++ /* Prevent SDHCI core from writing reserved bits (e.g. HISPD) */ ++ ret &= ~ESDHC_HOST_CONTROL_RES; ++ return ret; + } + +- /* Prevent SDHCI core from writing reserved bits (e.g. HISPD). */ +- if (reg == SDHCI_HOST_CONTROL) +- val &= ~ESDHC_HOST_CONTROL_RES; +- sdhci_be32bs_writeb(host, val, reg); ++ ret = (old_value & (~(0xff << shift))) | (value << shift); ++ return ret; ++} ++ ++static u32 esdhc_be_readl(struct sdhci_host *host, int reg) ++{ ++ u32 ret; ++ u32 value; ++ ++ value = ioread32be(host->ioaddr + reg); ++ ret = esdhc_readl_fixup(host, reg, value); ++ ++ return ret; ++} ++ ++static u32 esdhc_le_readl(struct sdhci_host *host, int reg) ++{ ++ u32 ret; ++ u32 value; ++ ++ value = ioread32(host->ioaddr + reg); ++ ret = esdhc_readl_fixup(host, reg, value); ++ ++ return ret; ++} ++ ++static u16 esdhc_be_readw(struct sdhci_host *host, int reg) ++{ ++ u16 ret; ++ u32 value; ++ int base = reg & ~0x3; ++ ++ value = ioread32be(host->ioaddr + base); ++ ret = esdhc_readw_fixup(host, reg, value); ++ return ret; ++} ++ ++static u16 esdhc_le_readw(struct sdhci_host *host, int reg) ++{ ++ u16 ret; ++ u32 value; ++ int base = reg & ~0x3; ++ ++ value = ioread32(host->ioaddr + base); ++ ret = esdhc_readw_fixup(host, reg, value); ++ return ret; ++} ++ ++static u8 esdhc_be_readb(struct sdhci_host *host, int reg) ++{ ++ u8 ret; ++ u32 value; ++ int base = reg & ~0x3; ++ ++ value = ioread32be(host->ioaddr + base); ++ ret = esdhc_readb_fixup(host, reg, value); ++ return ret; ++} ++ ++static u8 esdhc_le_readb(struct sdhci_host *host, int reg) ++{ ++ u8 ret; ++ u32 value; ++ int base = reg & ~0x3; ++ ++ value = ioread32(host->ioaddr + base); ++ ret = esdhc_readb_fixup(host, reg, value); ++ return ret; ++} ++ ++static void esdhc_be_writel(struct sdhci_host *host, u32 val, int reg) ++{ ++ u32 value; ++ ++ value = esdhc_writel_fixup(host, reg, val, 0); ++ iowrite32be(value, host->ioaddr + reg); ++} ++ ++static void esdhc_le_writel(struct sdhci_host *host, u32 val, int reg) ++{ ++ u32 value; ++ ++ value = esdhc_writel_fixup(host, reg, val, 0); ++ iowrite32(value, host->ioaddr + reg); ++} ++ ++static void esdhc_be_writew(struct sdhci_host *host, u16 val, int reg) ++{ ++ int base = reg & ~0x3; ++ u32 value; ++ u32 ret; ++ ++ value = ioread32be(host->ioaddr + base); ++ ret = esdhc_writew_fixup(host, reg, val, value); ++ if (reg != SDHCI_TRANSFER_MODE) ++ iowrite32be(ret, host->ioaddr + base); ++} ++ ++static void esdhc_le_writew(struct sdhci_host *host, u16 val, int reg) ++{ ++ int base = reg & ~0x3; ++ u32 value; ++ u32 ret; ++ ++ value = ioread32(host->ioaddr + base); ++ ret = esdhc_writew_fixup(host, reg, val, value); ++ if (reg != SDHCI_TRANSFER_MODE) ++ iowrite32(ret, host->ioaddr + base); ++} ++ ++static void esdhc_be_writeb(struct sdhci_host *host, u8 val, int reg) ++{ ++ int base = reg & ~0x3; ++ u32 value; ++ u32 ret; ++ ++ value = ioread32be(host->ioaddr + base); ++ ret = esdhc_writeb_fixup(host, reg, val, value); ++ iowrite32be(ret, host->ioaddr + base); ++} ++ ++static void esdhc_le_writeb(struct sdhci_host *host, u8 val, int reg) ++{ ++ int base = reg & ~0x3; ++ u32 value; ++ u32 ret; ++ ++ value = ioread32(host->ioaddr + base); ++ ret = esdhc_writeb_fixup(host, reg, val, value); ++ iowrite32(ret, host->ioaddr + base); + } + + /* +@@ -149,37 +355,116 @@ static void esdhc_writeb(struct sdhci_host *host, u8 val, int reg) + * For Continue, apply soft reset for data(SYSCTL[RSTD]); + * and re-issue the entire read transaction from beginning. + */ +-static void esdhci_of_adma_workaround(struct sdhci_host *host, u32 intmask) ++static void esdhc_of_adma_workaround(struct sdhci_host *host, u32 intmask) + { +- u32 tmp; ++ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); ++ struct sdhci_esdhc *esdhc = pltfm_host->priv; + bool applicable; + dma_addr_t dmastart; + dma_addr_t dmanow; + +- tmp = in_be32(host->ioaddr + SDHCI_SLOT_INT_STATUS); +- tmp = (tmp & SDHCI_VENDOR_VER_MASK) >> SDHCI_VENDOR_VER_SHIFT; +- + applicable = (intmask & SDHCI_INT_DATA_END) && +- (intmask & SDHCI_INT_BLK_GAP) && +- (tmp == VENDOR_V_23); +- if (!applicable) ++ (intmask & SDHCI_INT_BLK_GAP) && ++ (esdhc->vendor_ver == VENDOR_V_23); ++ if (applicable) { ++ ++ sdhci_reset(host, SDHCI_RESET_DATA); ++ host->data->error = 0; ++ dmastart = sg_dma_address(host->data->sg); ++ dmanow = dmastart + host->data->bytes_xfered; ++ /* ++ * Force update to the next DMA block boundary. ++ */ ++ dmanow = (dmanow & ~(SDHCI_DEFAULT_BOUNDARY_SIZE - 1)) + ++ SDHCI_DEFAULT_BOUNDARY_SIZE; ++ host->data->bytes_xfered = dmanow - dmastart; ++ sdhci_writel(host, dmanow, SDHCI_DMA_ADDRESS); ++ + return; ++ } + +- host->data->error = 0; +- dmastart = sg_dma_address(host->data->sg); +- dmanow = dmastart + host->data->bytes_xfered; + /* +- * Force update to the next DMA block boundary. ++ * Check for A-004388: eSDHC DMA might not stop if error ++ * occurs on system transaction ++ * Impact list: ++ * T4240-4160-R1.0 B4860-4420-R1.0-R2.0 P1010-1014-R1.0 ++ * P3041-R1.0-R2.0-R1.1 P2041-2040-R1.0-R1.1-R2.0 ++ * P5020-5010-R2.0-R1.0 P5040-5021-R2.0-R2.1 + */ +- dmanow = (dmanow & ~(SDHCI_DEFAULT_BOUNDARY_SIZE - 1)) + +- SDHCI_DEFAULT_BOUNDARY_SIZE; +- host->data->bytes_xfered = dmanow - dmastart; +- sdhci_writel(host, dmanow, SDHCI_DMA_ADDRESS); ++ if (!(((esdhc->soc_ver == SVR_T4240) && (esdhc->soc_rev == 0x10)) || ++ ((esdhc->soc_ver == SVR_T4160) && (esdhc->soc_rev == 0x10)) || ++ ((esdhc->soc_ver == SVR_B4860) && (esdhc->soc_rev == 0x10)) || ++ ((esdhc->soc_ver == SVR_B4860) && (esdhc->soc_rev == 0x20)) || ++ ((esdhc->soc_ver == SVR_B4420) && (esdhc->soc_rev == 0x10)) || ++ ((esdhc->soc_ver == SVR_B4420) && (esdhc->soc_rev == 0x20)) || ++ ((esdhc->soc_ver == SVR_P1010) && (esdhc->soc_rev == 0x10)) || ++ ((esdhc->soc_ver == SVR_P1014) && (esdhc->soc_rev == 0x10)) || ++ ((esdhc->soc_ver == SVR_P3041) && (esdhc->soc_rev <= 0x20)) || ++ ((esdhc->soc_ver == SVR_P2041) && (esdhc->soc_rev <= 0x20)) || ++ ((esdhc->soc_ver == SVR_P2040) && (esdhc->soc_rev <= 0x20)) || ++ ((esdhc->soc_ver == SVR_P5020) && (esdhc->soc_rev <= 0x20)) || ++ ((esdhc->soc_ver == SVR_P5010) && (esdhc->soc_rev <= 0x20)) || ++ ((esdhc->soc_ver == SVR_P5040) && (esdhc->soc_rev <= 0x21)) || ++ ((esdhc->soc_ver == SVR_P5021) && (esdhc->soc_rev <= 0x21)))) ++ return; ++ ++ sdhci_reset(host, SDHCI_RESET_DATA); ++ ++ if (host->flags & SDHCI_USE_ADMA) { ++ u32 mod, i, offset; ++ u8 *desc; ++ dma_addr_t addr; ++ struct scatterlist *sg; ++ __le32 *dataddr; ++ __le32 *cmdlen; ++ ++ /* ++ * If block count was enabled, in case read transfer there ++ * is no data was corrupted ++ */ ++ mod = sdhci_readl(host, SDHCI_TRANSFER_MODE); ++ if ((mod & SDHCI_TRNS_BLK_CNT_EN) && ++ (host->data->flags & MMC_DATA_READ)) ++ host->data->error = 0; ++ ++ BUG_ON(!host->data); ++ desc = host->adma_table; ++ for_each_sg(host->data->sg, sg, host->sg_count, i) { ++ addr = sg_dma_address(sg); ++ offset = (4 - (addr & 0x3)) & 0x3; ++ if (offset) ++ desc += 8; ++ desc += 8; ++ } ++ ++ /* ++ * Add an extra zero descriptor next to the ++ * terminating descriptor. ++ */ ++ desc += 8; ++ WARN_ON((desc - (u8 *)(host->adma_table)) > (128 * 2 + 1) * 4); ++ ++ dataddr = (__le32 __force *)(desc + 4); ++ cmdlen = (__le32 __force *)desc; ++ ++ cmdlen[0] = cpu_to_le32(0); ++ dataddr[0] = cpu_to_le32(0); ++ } ++ ++ if ((host->flags & SDHCI_USE_SDMA) && ++ (host->data->flags & MMC_DATA_READ)) ++ host->data->error = 0; ++ ++ return; + } + + static int esdhc_of_enable_dma(struct sdhci_host *host) + { +- setbits32(host->ioaddr + ESDHC_DMA_SYSCTL, ESDHC_DMA_SNOOP); ++ u32 value; ++ ++ value = sdhci_readl(host, ESDHC_DMA_SYSCTL); ++ value |= ESDHC_DMA_SNOOP; ++ sdhci_writel(host, value, ESDHC_DMA_SYSCTL); + return 0; + } + +@@ -199,15 +484,22 @@ static unsigned int esdhc_of_get_min_clock(struct sdhci_host *host) + + static void esdhc_of_set_clock(struct sdhci_host *host, unsigned int clock) + { +- int pre_div = 2; ++ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); ++ struct sdhci_esdhc *esdhc = pltfm_host->priv; ++ int pre_div = 1; + int div = 1; + u32 temp; ++ u32 timeout; + + host->mmc->actual_clock = 0; + + if (clock == 0) + return; + ++ /* Workaround to start pre_div at 2 for VNN < VENDOR_V_23 */ ++ if (esdhc->vendor_ver < VENDOR_V_23) ++ pre_div = 2; ++ + /* Workaround to reduce the clock frequency for p1010 esdhc */ + if (of_find_compatible_node(NULL, NULL, "fsl,p1010-esdhc")) { + if (clock > 20000000) +@@ -218,7 +510,7 @@ static void esdhc_of_set_clock(struct sdhci_host *host, unsigned int clock) + + temp = sdhci_readl(host, ESDHC_SYSTEM_CONTROL); + temp &= ~(ESDHC_CLOCK_IPGEN | ESDHC_CLOCK_HCKEN | ESDHC_CLOCK_PEREN +- | ESDHC_CLOCK_MASK); ++ | ESDHC_CLOCK_CRDEN | ESDHC_CLOCK_MASK); + sdhci_writel(host, temp, ESDHC_SYSTEM_CONTROL); + + while (host->max_clk / pre_div / 16 > clock && pre_div < 256) +@@ -229,7 +521,7 @@ static void esdhc_of_set_clock(struct sdhci_host *host, unsigned int clock) + + dev_dbg(mmc_dev(host->mmc), "desired SD clock: %d, actual: %d\n", + clock, host->max_clk / pre_div / div); +- ++ host->mmc->actual_clock = host->max_clk / pre_div / div; + pre_div >>= 1; + div--; + +@@ -238,70 +530,117 @@ static void esdhc_of_set_clock(struct sdhci_host *host, unsigned int clock) + | (div << ESDHC_DIVIDER_SHIFT) + | (pre_div << ESDHC_PREDIV_SHIFT)); + sdhci_writel(host, temp, ESDHC_SYSTEM_CONTROL); +- mdelay(1); +-} + +-static void esdhc_of_platform_init(struct sdhci_host *host) +-{ +- u32 vvn; +- +- vvn = in_be32(host->ioaddr + SDHCI_SLOT_INT_STATUS); +- vvn = (vvn & SDHCI_VENDOR_VER_MASK) >> SDHCI_VENDOR_VER_SHIFT; +- if (vvn == VENDOR_V_22) +- host->quirks2 |= SDHCI_QUIRK2_HOST_NO_CMD23; ++ /* Wait max 20 ms */ ++ timeout = 20; ++ while (!(sdhci_readl(host, ESDHC_PRESENT_STATE) & ESDHC_CLOCK_STABLE)) { ++ if (timeout == 0) { ++ pr_err("%s: Internal clock never stabilised.\n", ++ mmc_hostname(host->mmc)); ++ return; ++ } ++ timeout--; ++ mdelay(1); ++ } + +- if (vvn > VENDOR_V_22) +- host->quirks &= ~SDHCI_QUIRK_NO_BUSY_IRQ; ++ temp |= ESDHC_CLOCK_CRDEN; ++ sdhci_writel(host, temp, ESDHC_SYSTEM_CONTROL); + } + + static void esdhc_pltfm_set_bus_width(struct sdhci_host *host, int width) + { + u32 ctrl; + ++ ctrl = sdhci_readl(host, ESDHC_PROCTL); ++ ctrl &= (~ESDHC_CTRL_BUSWIDTH_MASK); + switch (width) { + case MMC_BUS_WIDTH_8: +- ctrl = ESDHC_CTRL_8BITBUS; ++ ctrl |= ESDHC_CTRL_8BITBUS; + break; + + case MMC_BUS_WIDTH_4: +- ctrl = ESDHC_CTRL_4BITBUS; ++ ctrl |= ESDHC_CTRL_4BITBUS; + break; + + default: +- ctrl = 0; + break; + } + +- clrsetbits_be32(host->ioaddr + SDHCI_HOST_CONTROL, +- ESDHC_CTRL_BUSWIDTH_MASK, ctrl); ++ sdhci_writel(host, ctrl, ESDHC_PROCTL); + } + +-static const struct sdhci_ops sdhci_esdhc_ops = { +- .read_l = esdhc_readl, +- .read_w = esdhc_readw, +- .read_b = esdhc_readb, +- .write_l = esdhc_writel, +- .write_w = esdhc_writew, +- .write_b = esdhc_writeb, +- .set_clock = esdhc_of_set_clock, +- .enable_dma = esdhc_of_enable_dma, +- .get_max_clock = esdhc_of_get_max_clock, +- .get_min_clock = esdhc_of_get_min_clock, +- .platform_init = esdhc_of_platform_init, +- .adma_workaround = esdhci_of_adma_workaround, +- .set_bus_width = esdhc_pltfm_set_bus_width, +- .reset = sdhci_reset, +- .set_uhs_signaling = sdhci_set_uhs_signaling, +-}; ++/* ++ * A-003980: SDHC: Glitch is generated on the card clock with software reset ++ * or clock divider change ++ * Workaround: ++ * A simple workaround is to disable the SD card clock before the software ++ * reset, and enable it when the module resumes normal operation. The Host ++ * and the SD card are in a master-slave relationship. The Host provides ++ * clock and control transfer across the interface. Therefore, any existing ++ * operation is discarded when the Host controller is reset. ++ */ ++static int esdhc_of_reset_workaround(struct sdhci_host *host, u8 mask) ++{ ++ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); ++ struct sdhci_esdhc *esdhc = pltfm_host->priv; ++ bool disable_clk_before_reset = false; ++ u32 temp; + +-#ifdef CONFIG_PM ++ /* ++ * Check for A-003980 ++ * Impact list: ++ * T4240-4160-R1.0-R2.0 B4860-4420-R1.0-R2.0 P5040-5021-R1.0-R2.0-R2.1 ++ * P5020-5010-R1.0-R2.0 P3041-R1.0-R1.1-R2.0 P2041-2040-R1.0-R1.1-R2.0 ++ * P1010-1014-R1.0 ++ */ ++ if (((esdhc->soc_ver == SVR_T4240) && (esdhc->soc_rev == 0x10)) || ++ ((esdhc->soc_ver == SVR_T4240) && (esdhc->soc_rev == 0x20)) || ++ ((esdhc->soc_ver == SVR_T4160) && (esdhc->soc_rev == 0x10)) || ++ ((esdhc->soc_ver == SVR_T4160) && (esdhc->soc_rev == 0x20)) || ++ ((esdhc->soc_ver == SVR_B4860) && (esdhc->soc_rev == 0x10)) || ++ ((esdhc->soc_ver == SVR_B4860) && (esdhc->soc_rev == 0x20)) || ++ ((esdhc->soc_ver == SVR_B4420) && (esdhc->soc_rev == 0x10)) || ++ ((esdhc->soc_ver == SVR_B4420) && (esdhc->soc_rev == 0x20)) || ++ ((esdhc->soc_ver == SVR_P5040) && (esdhc->soc_rev <= 0x21)) || ++ ((esdhc->soc_ver == SVR_P5021) && (esdhc->soc_rev <= 0x21)) || ++ ((esdhc->soc_ver == SVR_P5020) && (esdhc->soc_rev <= 0x20)) || ++ ((esdhc->soc_ver == SVR_P5010) && (esdhc->soc_rev <= 0x20)) || ++ ((esdhc->soc_ver == SVR_P3041) && (esdhc->soc_rev <= 0x20)) || ++ ((esdhc->soc_ver == SVR_P2041) && (esdhc->soc_rev <= 0x20)) || ++ ((esdhc->soc_ver == SVR_P2040) && (esdhc->soc_rev <= 0x20)) || ++ ((esdhc->soc_ver == SVR_P1014) && (esdhc->soc_rev == 0x10)) || ++ ((esdhc->soc_ver == SVR_P1010) && (esdhc->soc_rev == 0x10))) ++ disable_clk_before_reset = true; ++ ++ if (disable_clk_before_reset && (mask & SDHCI_RESET_ALL)) { ++ temp = sdhci_readl(host, ESDHC_SYSTEM_CONTROL); ++ temp &= ~ESDHC_CLOCK_CRDEN; ++ sdhci_writel(host, temp, ESDHC_SYSTEM_CONTROL); ++ sdhci_reset(host, mask); ++ temp = sdhci_readl(host, ESDHC_SYSTEM_CONTROL); ++ temp |= ESDHC_CLOCK_CRDEN; ++ sdhci_writel(host, temp, ESDHC_SYSTEM_CONTROL); ++ return 1; ++ } ++ return 0; ++} ++ ++static void esdhc_reset(struct sdhci_host *host, u8 mask) ++{ ++ if (!esdhc_of_reset_workaround(host, mask)) ++ sdhci_reset(host, mask); + ++ sdhci_writel(host, host->ier, SDHCI_INT_ENABLE); ++ sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE); ++} ++ ++#ifdef CONFIG_PM + static u32 esdhc_proctl; + static int esdhc_of_suspend(struct device *dev) + { + struct sdhci_host *host = dev_get_drvdata(dev); + +- esdhc_proctl = sdhci_be32bs_readl(host, SDHCI_HOST_CONTROL); ++ esdhc_proctl = sdhci_readl(host, SDHCI_HOST_CONTROL); + + return sdhci_suspend_host(host); + } +@@ -311,11 +650,8 @@ static int esdhc_of_resume(struct device *dev) + struct sdhci_host *host = dev_get_drvdata(dev); + int ret = sdhci_resume_host(host); + +- if (ret == 0) { +- /* Isn't this already done by sdhci_resume_host() ? --rmk */ +- esdhc_of_enable_dma(host); +- sdhci_be32bs_writel(host, esdhc_proctl, SDHCI_HOST_CONTROL); +- } ++ if (ret == 0) ++ sdhci_writel(host, esdhc_proctl, SDHCI_HOST_CONTROL); + + return ret; + } +@@ -329,30 +665,120 @@ static const struct dev_pm_ops esdhc_pmops = { + #define ESDHC_PMOPS NULL + #endif + +-static const struct sdhci_pltfm_data sdhci_esdhc_pdata = { +- /* +- * card detection could be handled via GPIO +- * eSDHC cannot support End Attribute in NOP ADMA descriptor +- */ ++static const struct sdhci_ops sdhci_esdhc_be_ops = { ++ .read_l = esdhc_be_readl, ++ .read_w = esdhc_be_readw, ++ .read_b = esdhc_be_readb, ++ .write_l = esdhc_be_writel, ++ .write_w = esdhc_be_writew, ++ .write_b = esdhc_be_writeb, ++ .set_clock = esdhc_of_set_clock, ++ .enable_dma = esdhc_of_enable_dma, ++ .get_max_clock = esdhc_of_get_max_clock, ++ .get_min_clock = esdhc_of_get_min_clock, ++ .adma_workaround = esdhc_of_adma_workaround, ++ .set_bus_width = esdhc_pltfm_set_bus_width, ++ .reset = esdhc_reset, ++ .set_uhs_signaling = sdhci_set_uhs_signaling, ++}; ++ ++static const struct sdhci_ops sdhci_esdhc_le_ops = { ++ .read_l = esdhc_le_readl, ++ .read_w = esdhc_le_readw, ++ .read_b = esdhc_le_readb, ++ .write_l = esdhc_le_writel, ++ .write_w = esdhc_le_writew, ++ .write_b = esdhc_le_writeb, ++ .set_clock = esdhc_of_set_clock, ++ .enable_dma = esdhc_of_enable_dma, ++ .get_max_clock = esdhc_of_get_max_clock, ++ .get_min_clock = esdhc_of_get_min_clock, ++ .adma_workaround = esdhc_of_adma_workaround, ++ .set_bus_width = esdhc_pltfm_set_bus_width, ++ .reset = esdhc_reset, ++ .set_uhs_signaling = sdhci_set_uhs_signaling, ++}; ++ ++static const struct sdhci_pltfm_data sdhci_esdhc_be_pdata = { + .quirks = ESDHC_DEFAULT_QUIRKS | SDHCI_QUIRK_BROKEN_CARD_DETECTION + | SDHCI_QUIRK_NO_CARD_NO_RESET + | SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC, +- .ops = &sdhci_esdhc_ops, ++ .ops = &sdhci_esdhc_be_ops, + }; + ++static const struct sdhci_pltfm_data sdhci_esdhc_le_pdata = { ++ .quirks = ESDHC_DEFAULT_QUIRKS | SDHCI_QUIRK_BROKEN_CARD_DETECTION ++ | SDHCI_QUIRK_NO_CARD_NO_RESET ++ | SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC, ++ .ops = &sdhci_esdhc_le_ops, ++}; ++ ++static void esdhc_init(struct platform_device *pdev, struct sdhci_host *host) ++{ ++ struct sdhci_pltfm_host *pltfm_host; ++ struct sdhci_esdhc *esdhc; ++ u16 host_ver; ++ u32 svr; ++ ++ pltfm_host = sdhci_priv(host); ++ esdhc = devm_kzalloc(&pdev->dev, sizeof(struct sdhci_esdhc), ++ GFP_KERNEL); ++ pltfm_host->priv = esdhc; ++ ++ svr = guts_get_svr(); ++ esdhc->soc_ver = SVR_SOC_VER(svr); ++ esdhc->soc_rev = SVR_REV(svr); ++ ++ host_ver = sdhci_readw(host, SDHCI_HOST_VERSION); ++ esdhc->vendor_ver = (host_ver & SDHCI_VENDOR_VER_MASK) >> ++ SDHCI_VENDOR_VER_SHIFT; ++ esdhc->spec_ver = host_ver & SDHCI_SPEC_VER_MASK; ++} ++ + static int sdhci_esdhc_probe(struct platform_device *pdev) + { + struct sdhci_host *host; + struct device_node *np; ++ struct sdhci_pltfm_host *pltfm_host; ++ struct sdhci_esdhc *esdhc; + int ret; + +- host = sdhci_pltfm_init(pdev, &sdhci_esdhc_pdata, 0); ++ np = pdev->dev.of_node; ++ ++ if (of_get_property(np, "little-endian", NULL)) ++ host = sdhci_pltfm_init(pdev, &sdhci_esdhc_le_pdata, 0); ++ else ++ host = sdhci_pltfm_init(pdev, &sdhci_esdhc_be_pdata, 0); ++ + if (IS_ERR(host)) + return PTR_ERR(host); + ++ esdhc_init(pdev, host); ++ + sdhci_get_of_property(pdev); + +- np = pdev->dev.of_node; ++ pltfm_host = sdhci_priv(host); ++ esdhc = pltfm_host->priv; ++ if (esdhc->vendor_ver == VENDOR_V_22) ++ host->quirks2 |= SDHCI_QUIRK2_HOST_NO_CMD23; ++ ++ if (esdhc->vendor_ver > VENDOR_V_22) ++ host->quirks &= ~SDHCI_QUIRK_NO_BUSY_IRQ; ++ ++ if (of_device_is_compatible(np, "fsl,p5040-esdhc") || ++ of_device_is_compatible(np, "fsl,p5020-esdhc") || ++ of_device_is_compatible(np, "fsl,p4080-esdhc") || ++ of_device_is_compatible(np, "fsl,p1020-esdhc") || ++ of_device_is_compatible(np, "fsl,t1040-esdhc") || ++ of_device_is_compatible(np, "fsl,ls1021a-esdhc") || ++ of_device_is_compatible(np, "fsl,ls2080a-esdhc") || ++ of_device_is_compatible(np, "fsl,ls2085a-esdhc") || ++ of_device_is_compatible(np, "fsl,ls1043a-esdhc")) ++ host->quirks &= ~SDHCI_QUIRK_BROKEN_CARD_DETECTION; ++ ++ if (of_device_is_compatible(np, "fsl,ls1021a-esdhc")) ++ host->quirks |= SDHCI_QUIRK_BROKEN_TIMEOUT_VAL; ++ + if (of_device_is_compatible(np, "fsl,p2020-esdhc")) { + /* + * Freescale messed up with P2020 as it has a non-standard +@@ -362,13 +788,19 @@ static int sdhci_esdhc_probe(struct platform_device *pdev) + } + + /* call to generic mmc_of_parse to support additional capabilities */ +- mmc_of_parse(host->mmc); ++ ret = mmc_of_parse(host->mmc); ++ if (ret) ++ goto err; ++ + mmc_of_parse_voltage(np, &host->ocr_mask); + + ret = sdhci_add_host(host); + if (ret) +- sdhci_pltfm_free(pdev); ++ goto err; + ++ return 0; ++ err: ++ sdhci_pltfm_free(pdev); + return ret; + } + +diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c +index 023c201..8af38a6 100644 +--- a/drivers/mmc/host/sdhci.c ++++ b/drivers/mmc/host/sdhci.c +@@ -44,8 +44,6 @@ + + #define MAX_TUNING_LOOP 40 + +-#define ADMA_SIZE ((128 * 2 + 1) * 4) +- + static unsigned int debug_quirks = 0; + static unsigned int debug_quirks2; + +@@ -119,10 +117,17 @@ static void sdhci_dumpregs(struct sdhci_host *host) + pr_debug(DRIVER_NAME ": Host ctl2: 0x%08x\n", + sdhci_readw(host, SDHCI_HOST_CONTROL2)); + +- if (host->flags & SDHCI_USE_ADMA) +- pr_debug(DRIVER_NAME ": ADMA Err: 0x%08x | ADMA Ptr: 0x%08x\n", +- readl(host->ioaddr + SDHCI_ADMA_ERROR), +- readl(host->ioaddr + SDHCI_ADMA_ADDRESS)); ++ if (host->flags & SDHCI_USE_ADMA) { ++ if (host->flags & SDHCI_USE_64_BIT_DMA) ++ pr_debug(DRIVER_NAME ": ADMA Err: 0x%08x | ADMA Ptr: 0x%08x%08x\n", ++ readl(host->ioaddr + SDHCI_ADMA_ERROR), ++ readl(host->ioaddr + SDHCI_ADMA_ADDRESS_HI), ++ readl(host->ioaddr + SDHCI_ADMA_ADDRESS)); ++ else ++ pr_debug(DRIVER_NAME ": ADMA Err: 0x%08x | ADMA Ptr: 0x%08x\n", ++ readl(host->ioaddr + SDHCI_ADMA_ERROR), ++ readl(host->ioaddr + SDHCI_ADMA_ADDRESS)); ++ } + + pr_debug(DRIVER_NAME ": ===========================================\n"); + } +@@ -231,6 +236,9 @@ static void sdhci_init(struct sdhci_host *host, int soft) + SDHCI_INT_TIMEOUT | SDHCI_INT_DATA_END | + SDHCI_INT_RESPONSE; + ++ if (host->flags & SDHCI_AUTO_CMD12) ++ host->ier |= SDHCI_INT_ACMD12ERR; ++ + sdhci_writel(host, host->ier, SDHCI_INT_ENABLE); + sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE); + +@@ -448,18 +456,26 @@ static void sdhci_kunmap_atomic(void *buffer, unsigned long *flags) + local_irq_restore(*flags); + } + +-static void sdhci_set_adma_desc(u8 *desc, u32 addr, int len, unsigned cmd) ++static void sdhci_adma_write_desc(struct sdhci_host *host, void *desc, ++ dma_addr_t addr, int len, unsigned cmd) + { +- __le32 *dataddr = (__le32 __force *)(desc + 4); +- __le16 *cmdlen = (__le16 __force *)desc; ++ struct sdhci_adma2_64_desc *dma_desc = desc; ++ ++ /* 32-bit and 64-bit descriptors have these members in same position */ ++ dma_desc->cmd = cpu_to_le16(cmd); ++ dma_desc->len = cpu_to_le16(len); ++ dma_desc->addr_lo = cpu_to_le32((u32)addr); + +- /* SDHCI specification says ADMA descriptors should be 4 byte +- * aligned, so using 16 or 32bit operations should be safe. */ ++ if (host->flags & SDHCI_USE_64_BIT_DMA) ++ dma_desc->addr_hi = cpu_to_le32((u64)addr >> 32); ++} + +- cmdlen[0] = cpu_to_le16(cmd); +- cmdlen[1] = cpu_to_le16(len); ++static void sdhci_adma_mark_end(void *desc) ++{ ++ struct sdhci_adma2_64_desc *dma_desc = desc; + +- dataddr[0] = cpu_to_le32(addr); ++ /* 32-bit and 64-bit descriptors have 'cmd' in same position */ ++ dma_desc->cmd |= cpu_to_le16(ADMA2_END); + } + + static int sdhci_adma_table_pre(struct sdhci_host *host, +@@ -467,8 +483,8 @@ static int sdhci_adma_table_pre(struct sdhci_host *host, + { + int direction; + +- u8 *desc; +- u8 *align; ++ void *desc; ++ void *align; + dma_addr_t addr; + dma_addr_t align_addr; + int len, offset; +@@ -489,17 +505,17 @@ static int sdhci_adma_table_pre(struct sdhci_host *host, + direction = DMA_TO_DEVICE; + + host->align_addr = dma_map_single(mmc_dev(host->mmc), +- host->align_buffer, 128 * 4, direction); ++ host->align_buffer, host->align_buffer_sz, direction); + if (dma_mapping_error(mmc_dev(host->mmc), host->align_addr)) + goto fail; +- BUG_ON(host->align_addr & 0x3); ++ BUG_ON(host->align_addr & host->align_mask); + + host->sg_count = dma_map_sg(mmc_dev(host->mmc), + data->sg, data->sg_len, direction); + if (host->sg_count == 0) + goto unmap_align; + +- desc = host->adma_desc; ++ desc = host->adma_table; + align = host->align_buffer; + + align_addr = host->align_addr; +@@ -515,24 +531,27 @@ static int sdhci_adma_table_pre(struct sdhci_host *host, + * the (up to three) bytes that screw up the + * alignment. + */ +- offset = (4 - (addr & 0x3)) & 0x3; ++ offset = (host->align_sz - (addr & host->align_mask)) & ++ host->align_mask; + if (offset) { + if (data->flags & MMC_DATA_WRITE) { + buffer = sdhci_kmap_atomic(sg, &flags); +- WARN_ON(((long)buffer & PAGE_MASK) > (PAGE_SIZE - 3)); ++ WARN_ON(((long)buffer & (PAGE_SIZE - 1)) > ++ (PAGE_SIZE - offset)); + memcpy(align, buffer, offset); + sdhci_kunmap_atomic(buffer, &flags); + } + + /* tran, valid */ +- sdhci_set_adma_desc(desc, align_addr, offset, 0x21); ++ sdhci_adma_write_desc(host, desc, align_addr, offset, ++ ADMA2_TRAN_VALID); + + BUG_ON(offset > 65536); + +- align += 4; +- align_addr += 4; ++ align += host->align_sz; ++ align_addr += host->align_sz; + +- desc += 8; ++ desc += host->desc_sz; + + addr += offset; + len -= offset; +@@ -541,23 +560,23 @@ static int sdhci_adma_table_pre(struct sdhci_host *host, + BUG_ON(len > 65536); + + /* tran, valid */ +- sdhci_set_adma_desc(desc, addr, len, 0x21); +- desc += 8; ++ sdhci_adma_write_desc(host, desc, addr, len, ADMA2_TRAN_VALID); ++ desc += host->desc_sz; + + /* + * If this triggers then we have a calculation bug + * somewhere. :/ + */ +- WARN_ON((desc - host->adma_desc) > ADMA_SIZE); ++ WARN_ON((desc - host->adma_table) >= host->adma_table_sz); + } + + if (host->quirks & SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC) { + /* + * Mark the last descriptor as the terminating descriptor + */ +- if (desc != host->adma_desc) { +- desc -= 8; +- desc[0] |= 0x2; /* end */ ++ if (desc != host->adma_table) { ++ desc -= host->desc_sz; ++ sdhci_adma_mark_end(desc); + } + } else { + /* +@@ -565,7 +584,7 @@ static int sdhci_adma_table_pre(struct sdhci_host *host, + */ + + /* nop, end, valid */ +- sdhci_set_adma_desc(desc, 0, 0, 0x3); ++ sdhci_adma_write_desc(host, desc, 0, 0, ADMA2_NOP_END_VALID); + } + + /* +@@ -573,14 +592,14 @@ static int sdhci_adma_table_pre(struct sdhci_host *host, + */ + if (data->flags & MMC_DATA_WRITE) { + dma_sync_single_for_device(mmc_dev(host->mmc), +- host->align_addr, 128 * 4, direction); ++ host->align_addr, host->align_buffer_sz, direction); + } + + return 0; + + unmap_align: + dma_unmap_single(mmc_dev(host->mmc), host->align_addr, +- 128 * 4, direction); ++ host->align_buffer_sz, direction); + fail: + return -EINVAL; + } +@@ -592,7 +611,7 @@ static void sdhci_adma_table_post(struct sdhci_host *host, + + struct scatterlist *sg; + int i, size; +- u8 *align; ++ void *align; + char *buffer; + unsigned long flags; + bool has_unaligned; +@@ -603,12 +622,12 @@ static void sdhci_adma_table_post(struct sdhci_host *host, + direction = DMA_TO_DEVICE; + + dma_unmap_single(mmc_dev(host->mmc), host->align_addr, +- 128 * 4, direction); ++ host->align_buffer_sz, direction); + + /* Do a quick scan of the SG list for any unaligned mappings */ + has_unaligned = false; + for_each_sg(data->sg, sg, host->sg_count, i) +- if (sg_dma_address(sg) & 3) { ++ if (sg_dma_address(sg) & host->align_mask) { + has_unaligned = true; + break; + } +@@ -620,15 +639,17 @@ static void sdhci_adma_table_post(struct sdhci_host *host, + align = host->align_buffer; + + for_each_sg(data->sg, sg, host->sg_count, i) { +- if (sg_dma_address(sg) & 0x3) { +- size = 4 - (sg_dma_address(sg) & 0x3); ++ if (sg_dma_address(sg) & host->align_mask) { ++ size = host->align_sz - ++ (sg_dma_address(sg) & host->align_mask); + + buffer = sdhci_kmap_atomic(sg, &flags); +- WARN_ON(((long)buffer & PAGE_MASK) > (PAGE_SIZE - 3)); ++ WARN_ON(((long)buffer & (PAGE_SIZE - 1)) > ++ (PAGE_SIZE - size)); + memcpy(buffer, align, size); + sdhci_kunmap_atomic(buffer, &flags); + +- align += 4; ++ align += host->align_sz; + } + } + } +@@ -822,6 +843,10 @@ static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_command *cmd) + } else { + sdhci_writel(host, host->adma_addr, + SDHCI_ADMA_ADDRESS); ++ if (host->flags & SDHCI_USE_64_BIT_DMA) ++ sdhci_writel(host, ++ (u64)host->adma_addr >> 32, ++ SDHCI_ADMA_ADDRESS_HI); + } + } else { + int sg_cnt; +@@ -855,10 +880,14 @@ static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_command *cmd) + ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL); + ctrl &= ~SDHCI_CTRL_DMA_MASK; + if ((host->flags & SDHCI_REQ_USE_DMA) && +- (host->flags & SDHCI_USE_ADMA)) +- ctrl |= SDHCI_CTRL_ADMA32; +- else ++ (host->flags & SDHCI_USE_ADMA)) { ++ if (host->flags & SDHCI_USE_64_BIT_DMA) ++ ctrl |= SDHCI_CTRL_ADMA64; ++ else ++ ctrl |= SDHCI_CTRL_ADMA32; ++ } else { + ctrl |= SDHCI_CTRL_SDMA; ++ } + sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL); + } + +@@ -1797,6 +1826,10 @@ static int sdhci_do_start_signal_voltage_switch(struct sdhci_host *host, + ctrl |= SDHCI_CTRL_VDD_180; + sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2); + ++ /* Some controller need to do more when switching */ ++ if (host->ops->voltage_switch) ++ host->ops->voltage_switch(host); ++ + /* 1.8V regulator output should be stable within 5 ms */ + ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2); + if (ctrl & SDHCI_CTRL_VDD_180) +@@ -2250,7 +2283,7 @@ static void sdhci_cmd_irq(struct sdhci_host *host, u32 intmask, u32 *mask) + if (intmask & SDHCI_INT_TIMEOUT) + host->cmd->error = -ETIMEDOUT; + else if (intmask & (SDHCI_INT_CRC | SDHCI_INT_END_BIT | +- SDHCI_INT_INDEX)) ++ SDHCI_INT_INDEX | SDHCI_INT_ACMD12ERR)) + host->cmd->error = -EILSEQ; + + if (host->cmd->error) { +@@ -2292,32 +2325,36 @@ static void sdhci_cmd_irq(struct sdhci_host *host, u32 intmask, u32 *mask) + } + + #ifdef CONFIG_MMC_DEBUG +-static void sdhci_show_adma_error(struct sdhci_host *host) ++static void sdhci_adma_show_error(struct sdhci_host *host) + { + const char *name = mmc_hostname(host->mmc); +- u8 *desc = host->adma_desc; +- __le32 *dma; +- __le16 *len; +- u8 attr; ++ void *desc = host->adma_table; + + sdhci_dumpregs(host); + + while (true) { +- dma = (__le32 *)(desc + 4); +- len = (__le16 *)(desc + 2); +- attr = *desc; +- +- DBG("%s: %p: DMA 0x%08x, LEN 0x%04x, Attr=0x%02x\n", +- name, desc, le32_to_cpu(*dma), le16_to_cpu(*len), attr); ++ struct sdhci_adma2_64_desc *dma_desc = desc; ++ ++ if (host->flags & SDHCI_USE_64_BIT_DMA) ++ DBG("%s: %p: DMA 0x%08x%08x, LEN 0x%04x, Attr=0x%02x\n", ++ name, desc, le32_to_cpu(dma_desc->addr_hi), ++ le32_to_cpu(dma_desc->addr_lo), ++ le16_to_cpu(dma_desc->len), ++ le16_to_cpu(dma_desc->cmd)); ++ else ++ DBG("%s: %p: DMA 0x%08x, LEN 0x%04x, Attr=0x%02x\n", ++ name, desc, le32_to_cpu(dma_desc->addr_lo), ++ le16_to_cpu(dma_desc->len), ++ le16_to_cpu(dma_desc->cmd)); + +- desc += 8; ++ desc += host->desc_sz; + +- if (attr & 2) ++ if (dma_desc->cmd & cpu_to_le16(ADMA2_END)) + break; + } + } + #else +-static void sdhci_show_adma_error(struct sdhci_host *host) { } ++static void sdhci_adma_show_error(struct sdhci_host *host) { } + #endif + + static void sdhci_data_irq(struct sdhci_host *host, u32 intmask) +@@ -2380,7 +2417,7 @@ static void sdhci_data_irq(struct sdhci_host *host, u32 intmask) + host->data->error = -EILSEQ; + else if (intmask & SDHCI_INT_ADMA_ERROR) { + pr_err("%s: ADMA error\n", mmc_hostname(host->mmc)); +- sdhci_show_adma_error(host); ++ sdhci_adma_show_error(host); + host->data->error = -EIO; + if (host->ops->adma_workaround) + host->ops->adma_workaround(host, intmask); +@@ -2859,6 +2896,16 @@ int sdhci_add_host(struct sdhci_host *host) + host->flags &= ~SDHCI_USE_ADMA; + } + ++ /* ++ * It is assumed that a 64-bit capable device has set a 64-bit DMA mask ++ * and *must* do 64-bit DMA. A driver has the opportunity to change ++ * that during the first call to ->enable_dma(). Similarly ++ * SDHCI_QUIRK2_BROKEN_64_BIT_DMA must be left to the drivers to ++ * implement. ++ */ ++ if (sdhci_readl(host, SDHCI_CAPABILITIES) & SDHCI_CAN_64BIT) ++ host->flags |= SDHCI_USE_64_BIT_DMA; ++ + if (host->flags & (SDHCI_USE_SDMA | SDHCI_USE_ADMA)) { + if (host->ops->enable_dma) { + if (host->ops->enable_dma(host)) { +@@ -2870,33 +2917,59 @@ int sdhci_add_host(struct sdhci_host *host) + } + } + ++ /* SDMA does not support 64-bit DMA */ ++ if (host->flags & SDHCI_USE_64_BIT_DMA) ++ host->flags &= ~SDHCI_USE_SDMA; ++ + if (host->flags & SDHCI_USE_ADMA) { + /* +- * We need to allocate descriptors for all sg entries +- * (128) and potentially one alignment transfer for +- * each of those entries. ++ * The DMA descriptor table size is calculated as the maximum ++ * number of segments times 2, to allow for an alignment ++ * descriptor for each segment, plus 1 for a nop end descriptor, ++ * all multipled by the descriptor size. + */ +- host->adma_desc = dma_alloc_coherent(mmc_dev(mmc), +- ADMA_SIZE, &host->adma_addr, +- GFP_KERNEL); +- host->align_buffer = kmalloc(128 * 4, GFP_KERNEL); +- if (!host->adma_desc || !host->align_buffer) { +- dma_free_coherent(mmc_dev(mmc), ADMA_SIZE, +- host->adma_desc, host->adma_addr); ++ if (host->flags & SDHCI_USE_64_BIT_DMA) { ++ host->adma_table_sz = (SDHCI_MAX_SEGS * 2 + 1) * ++ SDHCI_ADMA2_64_DESC_SZ; ++ host->align_buffer_sz = SDHCI_MAX_SEGS * ++ SDHCI_ADMA2_64_ALIGN; ++ host->desc_sz = SDHCI_ADMA2_64_DESC_SZ; ++ host->align_sz = SDHCI_ADMA2_64_ALIGN; ++ host->align_mask = SDHCI_ADMA2_64_ALIGN - 1; ++ } else { ++ host->adma_table_sz = (SDHCI_MAX_SEGS * 2 + 1) * ++ SDHCI_ADMA2_32_DESC_SZ; ++ host->align_buffer_sz = SDHCI_MAX_SEGS * ++ SDHCI_ADMA2_32_ALIGN; ++ host->desc_sz = SDHCI_ADMA2_32_DESC_SZ; ++ host->align_sz = SDHCI_ADMA2_32_ALIGN; ++ host->align_mask = SDHCI_ADMA2_32_ALIGN - 1; ++ } ++ host->adma_table = dma_alloc_coherent(mmc_dev(mmc), ++ host->adma_table_sz, ++ &host->adma_addr, ++ GFP_KERNEL); ++ host->align_buffer = kmalloc(host->align_buffer_sz, GFP_KERNEL); ++ if (!host->adma_table || !host->align_buffer) { ++ if (host->adma_table) ++ dma_free_coherent(mmc_dev(mmc), ++ host->adma_table_sz, ++ host->adma_table, ++ host->adma_addr); + kfree(host->align_buffer); + pr_warn("%s: Unable to allocate ADMA buffers - falling back to standard DMA\n", + mmc_hostname(mmc)); + host->flags &= ~SDHCI_USE_ADMA; +- host->adma_desc = NULL; ++ host->adma_table = NULL; + host->align_buffer = NULL; +- } else if (host->adma_addr & 3) { ++ } else if (host->adma_addr & host->align_mask) { + pr_warn("%s: unable to allocate aligned ADMA descriptor\n", + mmc_hostname(mmc)); + host->flags &= ~SDHCI_USE_ADMA; +- dma_free_coherent(mmc_dev(mmc), ADMA_SIZE, +- host->adma_desc, host->adma_addr); ++ dma_free_coherent(mmc_dev(mmc), host->adma_table_sz, ++ host->adma_table, host->adma_addr); + kfree(host->align_buffer); +- host->adma_desc = NULL; ++ host->adma_table = NULL; + host->align_buffer = NULL; + } + } +@@ -2995,7 +3068,8 @@ int sdhci_add_host(struct sdhci_host *host) + /* Auto-CMD23 stuff only works in ADMA or PIO. */ + if ((host->version >= SDHCI_SPEC_300) && + ((host->flags & SDHCI_USE_ADMA) || +- !(host->flags & SDHCI_USE_SDMA))) { ++ !(host->flags & SDHCI_USE_SDMA)) && ++ !(host->quirks2 & SDHCI_QUIRK2_ACMD23_BROKEN)) { + host->flags |= SDHCI_AUTO_CMD23; + DBG("%s: Auto-CMD23 available\n", mmc_hostname(mmc)); + } else { +@@ -3152,13 +3226,14 @@ int sdhci_add_host(struct sdhci_host *host) + SDHCI_MAX_CURRENT_MULTIPLIER; + } + +- /* If OCR set by external regulators, use it instead */ ++ /* If OCR set by host, use it instead. */ ++ if (host->ocr_mask) ++ ocr_avail = host->ocr_mask; ++ ++ /* If OCR set by external regulators, give it highest prio. */ + if (mmc->ocr_avail) + ocr_avail = mmc->ocr_avail; + +- if (host->ocr_mask) +- ocr_avail &= host->ocr_mask; +- + mmc->ocr_avail = ocr_avail; + mmc->ocr_avail_sdio = ocr_avail; + if (host->ocr_avail_sdio) +@@ -3185,11 +3260,11 @@ int sdhci_add_host(struct sdhci_host *host) + * can do scatter/gather or not. + */ + if (host->flags & SDHCI_USE_ADMA) +- mmc->max_segs = 128; ++ mmc->max_segs = SDHCI_MAX_SEGS; + else if (host->flags & SDHCI_USE_SDMA) + mmc->max_segs = 1; + else /* PIO */ +- mmc->max_segs = 128; ++ mmc->max_segs = SDHCI_MAX_SEGS; + + /* + * Maximum number of sectors in one transfer. Limited by DMA boundary +@@ -3287,7 +3362,8 @@ int sdhci_add_host(struct sdhci_host *host) + + pr_info("%s: SDHCI controller on %s [%s] using %s\n", + mmc_hostname(mmc), host->hw_name, dev_name(mmc_dev(mmc)), +- (host->flags & SDHCI_USE_ADMA) ? "ADMA" : ++ (host->flags & SDHCI_USE_ADMA) ? ++ (host->flags & SDHCI_USE_64_BIT_DMA) ? "ADMA 64-bit" : "ADMA" : + (host->flags & SDHCI_USE_SDMA) ? "DMA" : "PIO"); + + sdhci_enable_card_detection(host); +@@ -3355,12 +3431,12 @@ void sdhci_remove_host(struct sdhci_host *host, int dead) + if (!IS_ERR(mmc->supply.vqmmc)) + regulator_disable(mmc->supply.vqmmc); + +- if (host->adma_desc) +- dma_free_coherent(mmc_dev(mmc), ADMA_SIZE, +- host->adma_desc, host->adma_addr); ++ if (host->adma_table) ++ dma_free_coherent(mmc_dev(mmc), host->adma_table_sz, ++ host->adma_table, host->adma_addr); + kfree(host->align_buffer); + +- host->adma_desc = NULL; ++ host->adma_table = NULL; + host->align_buffer = NULL; + } + +diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h +index 31896a7..5220f36 100644 +--- a/drivers/mmc/host/sdhci.h ++++ b/drivers/mmc/host/sdhci.h +@@ -227,6 +227,7 @@ + /* 55-57 reserved */ + + #define SDHCI_ADMA_ADDRESS 0x58 ++#define SDHCI_ADMA_ADDRESS_HI 0x5C + + /* 60-FB reserved */ + +@@ -266,6 +267,46 @@ + #define SDHCI_DEFAULT_BOUNDARY_SIZE (512 * 1024) + #define SDHCI_DEFAULT_BOUNDARY_ARG (ilog2(SDHCI_DEFAULT_BOUNDARY_SIZE) - 12) + ++/* ADMA2 32-bit DMA descriptor size */ ++#define SDHCI_ADMA2_32_DESC_SZ 8 ++ ++/* ADMA2 32-bit DMA alignment */ ++#define SDHCI_ADMA2_32_ALIGN 4 ++ ++/* ADMA2 32-bit descriptor */ ++struct sdhci_adma2_32_desc { ++ __le16 cmd; ++ __le16 len; ++ __le32 addr; ++} __packed __aligned(SDHCI_ADMA2_32_ALIGN); ++ ++/* ADMA2 64-bit DMA descriptor size */ ++#define SDHCI_ADMA2_64_DESC_SZ 12 ++ ++/* ADMA2 64-bit DMA alignment */ ++#define SDHCI_ADMA2_64_ALIGN 8 ++ ++/* ++ * ADMA2 64-bit descriptor. Note 12-byte descriptor can't always be 8-byte ++ * aligned. ++ */ ++struct sdhci_adma2_64_desc { ++ __le16 cmd; ++ __le16 len; ++ __le32 addr_lo; ++ __le32 addr_hi; ++} __packed __aligned(4); ++ ++#define ADMA2_TRAN_VALID 0x21 ++#define ADMA2_NOP_END_VALID 0x3 ++#define ADMA2_END 0x2 ++ ++/* ++ * Maximum segments assuming a 512KiB maximum requisition size and a minimum ++ * 4KiB page size. ++ */ ++#define SDHCI_MAX_SEGS 128 ++ + struct sdhci_ops { + #ifdef CONFIG_MMC_SDHCI_IO_ACCESSORS + u32 (*read_l)(struct sdhci_host *host, int reg); +@@ -296,6 +337,7 @@ struct sdhci_ops { + void (*adma_workaround)(struct sdhci_host *host, u32 intmask); + void (*platform_init)(struct sdhci_host *host); + void (*card_event)(struct sdhci_host *host); ++ void (*voltage_switch)(struct sdhci_host *host); + }; + + #ifdef CONFIG_MMC_SDHCI_IO_ACCESSORS +diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig +index dd10646..34ce759 100644 +--- a/drivers/mtd/nand/Kconfig ++++ b/drivers/mtd/nand/Kconfig +@@ -429,7 +429,7 @@ config MTD_NAND_FSL_ELBC + + config MTD_NAND_FSL_IFC + tristate "NAND support for Freescale IFC controller" +- depends on MTD_NAND && FSL_SOC ++ depends on MTD_NAND && (FSL_SOC || ARCH_LAYERSCAPE) + select FSL_IFC + select MEMORY + help +diff --git a/drivers/mtd/nand/fsl_ifc_nand.c b/drivers/mtd/nand/fsl_ifc_nand.c +index 2338124..c8be272 100644 +--- a/drivers/mtd/nand/fsl_ifc_nand.c ++++ b/drivers/mtd/nand/fsl_ifc_nand.c +@@ -31,7 +31,6 @@ + #include + #include + +-#define FSL_IFC_V1_1_0 0x01010000 + #define ERR_BYTE 0xFF /* Value returned for read + bytes when read failed */ + #define IFC_TIMEOUT_MSECS 500 /* Maximum number of mSecs to wait +@@ -234,13 +233,13 @@ static void set_addr(struct mtd_info *mtd, int column, int page_addr, int oob) + struct nand_chip *chip = mtd->priv; + struct fsl_ifc_mtd *priv = chip->priv; + struct fsl_ifc_ctrl *ctrl = priv->ctrl; +- struct fsl_ifc_regs __iomem *ifc = ctrl->regs; ++ struct fsl_ifc_runtime __iomem *ifc = ctrl->rregs; + int buf_num; + + ifc_nand_ctrl->page = page_addr; + /* Program ROW0/COL0 */ +- iowrite32be(page_addr, &ifc->ifc_nand.row0); +- iowrite32be((oob ? IFC_NAND_COL_MS : 0) | column, &ifc->ifc_nand.col0); ++ ifc_out32(page_addr, &ifc->ifc_nand.row0); ++ ifc_out32((oob ? IFC_NAND_COL_MS : 0) | column, &ifc->ifc_nand.col0); + + buf_num = page_addr & priv->bufnum_mask; + +@@ -297,28 +296,28 @@ static void fsl_ifc_run_command(struct mtd_info *mtd) + struct fsl_ifc_mtd *priv = chip->priv; + struct fsl_ifc_ctrl *ctrl = priv->ctrl; + struct fsl_ifc_nand_ctrl *nctrl = ifc_nand_ctrl; +- struct fsl_ifc_regs __iomem *ifc = ctrl->regs; ++ struct fsl_ifc_runtime __iomem *ifc = ctrl->rregs; + u32 eccstat[4]; + int i; + + /* set the chip select for NAND Transaction */ +- iowrite32be(priv->bank << IFC_NAND_CSEL_SHIFT, +- &ifc->ifc_nand.nand_csel); ++ ifc_out32(priv->bank << IFC_NAND_CSEL_SHIFT, ++ &ifc->ifc_nand.nand_csel); + + dev_vdbg(priv->dev, + "%s: fir0=%08x fcr0=%08x\n", + __func__, +- ioread32be(&ifc->ifc_nand.nand_fir0), +- ioread32be(&ifc->ifc_nand.nand_fcr0)); ++ ifc_in32(&ifc->ifc_nand.nand_fir0), ++ ifc_in32(&ifc->ifc_nand.nand_fcr0)); + + ctrl->nand_stat = 0; + + /* start read/write seq */ +- iowrite32be(IFC_NAND_SEQ_STRT_FIR_STRT, &ifc->ifc_nand.nandseq_strt); ++ ifc_out32(IFC_NAND_SEQ_STRT_FIR_STRT, &ifc->ifc_nand.nandseq_strt); + + /* wait for command complete flag or timeout */ + wait_event_timeout(ctrl->nand_wait, ctrl->nand_stat, +- IFC_TIMEOUT_MSECS * HZ/1000); ++ msecs_to_jiffies(IFC_TIMEOUT_MSECS)); + + /* ctrl->nand_stat will be updated from IRQ context */ + if (!ctrl->nand_stat) +@@ -337,7 +336,7 @@ static void fsl_ifc_run_command(struct mtd_info *mtd) + int sector_end = sector + chip->ecc.steps - 1; + + for (i = sector / 4; i <= sector_end / 4; i++) +- eccstat[i] = ioread32be(&ifc->ifc_nand.nand_eccstat[i]); ++ eccstat[i] = ifc_in32(&ifc->ifc_nand.nand_eccstat[i]); + + for (i = sector; i <= sector_end; i++) { + errors = check_read_ecc(mtd, ctrl, eccstat, i); +@@ -373,37 +372,37 @@ static void fsl_ifc_do_read(struct nand_chip *chip, + { + struct fsl_ifc_mtd *priv = chip->priv; + struct fsl_ifc_ctrl *ctrl = priv->ctrl; +- struct fsl_ifc_regs __iomem *ifc = ctrl->regs; ++ struct fsl_ifc_runtime __iomem *ifc = ctrl->rregs; + + /* Program FIR/IFC_NAND_FCR0 for Small/Large page */ + if (mtd->writesize > 512) { +- iowrite32be((IFC_FIR_OP_CW0 << IFC_NAND_FIR0_OP0_SHIFT) | +- (IFC_FIR_OP_CA0 << IFC_NAND_FIR0_OP1_SHIFT) | +- (IFC_FIR_OP_RA0 << IFC_NAND_FIR0_OP2_SHIFT) | +- (IFC_FIR_OP_CMD1 << IFC_NAND_FIR0_OP3_SHIFT) | +- (IFC_FIR_OP_RBCD << IFC_NAND_FIR0_OP4_SHIFT), +- &ifc->ifc_nand.nand_fir0); +- iowrite32be(0x0, &ifc->ifc_nand.nand_fir1); +- +- iowrite32be((NAND_CMD_READ0 << IFC_NAND_FCR0_CMD0_SHIFT) | +- (NAND_CMD_READSTART << IFC_NAND_FCR0_CMD1_SHIFT), +- &ifc->ifc_nand.nand_fcr0); ++ ifc_out32((IFC_FIR_OP_CW0 << IFC_NAND_FIR0_OP0_SHIFT) | ++ (IFC_FIR_OP_CA0 << IFC_NAND_FIR0_OP1_SHIFT) | ++ (IFC_FIR_OP_RA0 << IFC_NAND_FIR0_OP2_SHIFT) | ++ (IFC_FIR_OP_CMD1 << IFC_NAND_FIR0_OP3_SHIFT) | ++ (IFC_FIR_OP_RBCD << IFC_NAND_FIR0_OP4_SHIFT), ++ &ifc->ifc_nand.nand_fir0); ++ ifc_out32(0x0, &ifc->ifc_nand.nand_fir1); ++ ++ ifc_out32((NAND_CMD_READ0 << IFC_NAND_FCR0_CMD0_SHIFT) | ++ (NAND_CMD_READSTART << IFC_NAND_FCR0_CMD1_SHIFT), ++ &ifc->ifc_nand.nand_fcr0); + } else { +- iowrite32be((IFC_FIR_OP_CW0 << IFC_NAND_FIR0_OP0_SHIFT) | +- (IFC_FIR_OP_CA0 << IFC_NAND_FIR0_OP1_SHIFT) | +- (IFC_FIR_OP_RA0 << IFC_NAND_FIR0_OP2_SHIFT) | +- (IFC_FIR_OP_RBCD << IFC_NAND_FIR0_OP3_SHIFT), +- &ifc->ifc_nand.nand_fir0); +- iowrite32be(0x0, &ifc->ifc_nand.nand_fir1); ++ ifc_out32((IFC_FIR_OP_CW0 << IFC_NAND_FIR0_OP0_SHIFT) | ++ (IFC_FIR_OP_CA0 << IFC_NAND_FIR0_OP1_SHIFT) | ++ (IFC_FIR_OP_RA0 << IFC_NAND_FIR0_OP2_SHIFT) | ++ (IFC_FIR_OP_RBCD << IFC_NAND_FIR0_OP3_SHIFT), ++ &ifc->ifc_nand.nand_fir0); ++ ifc_out32(0x0, &ifc->ifc_nand.nand_fir1); + + if (oob) +- iowrite32be(NAND_CMD_READOOB << +- IFC_NAND_FCR0_CMD0_SHIFT, +- &ifc->ifc_nand.nand_fcr0); ++ ifc_out32(NAND_CMD_READOOB << ++ IFC_NAND_FCR0_CMD0_SHIFT, ++ &ifc->ifc_nand.nand_fcr0); + else +- iowrite32be(NAND_CMD_READ0 << +- IFC_NAND_FCR0_CMD0_SHIFT, +- &ifc->ifc_nand.nand_fcr0); ++ ifc_out32(NAND_CMD_READ0 << ++ IFC_NAND_FCR0_CMD0_SHIFT, ++ &ifc->ifc_nand.nand_fcr0); + } + } + +@@ -413,7 +412,7 @@ static void fsl_ifc_cmdfunc(struct mtd_info *mtd, unsigned int command, + struct nand_chip *chip = mtd->priv; + struct fsl_ifc_mtd *priv = chip->priv; + struct fsl_ifc_ctrl *ctrl = priv->ctrl; +- struct fsl_ifc_regs __iomem *ifc = ctrl->regs; ++ struct fsl_ifc_runtime __iomem *ifc = ctrl->rregs; + + /* clear the read buffer */ + ifc_nand_ctrl->read_bytes = 0; +@@ -423,7 +422,7 @@ static void fsl_ifc_cmdfunc(struct mtd_info *mtd, unsigned int command, + switch (command) { + /* READ0 read the entire buffer to use hardware ECC. */ + case NAND_CMD_READ0: +- iowrite32be(0, &ifc->ifc_nand.nand_fbcr); ++ ifc_out32(0, &ifc->ifc_nand.nand_fbcr); + set_addr(mtd, 0, page_addr, 0); + + ifc_nand_ctrl->read_bytes = mtd->writesize + mtd->oobsize; +@@ -438,7 +437,7 @@ static void fsl_ifc_cmdfunc(struct mtd_info *mtd, unsigned int command, + + /* READOOB reads only the OOB because no ECC is performed. */ + case NAND_CMD_READOOB: +- iowrite32be(mtd->oobsize - column, &ifc->ifc_nand.nand_fbcr); ++ ifc_out32(mtd->oobsize - column, &ifc->ifc_nand.nand_fbcr); + set_addr(mtd, column, page_addr, 1); + + ifc_nand_ctrl->read_bytes = mtd->writesize + mtd->oobsize; +@@ -454,19 +453,19 @@ static void fsl_ifc_cmdfunc(struct mtd_info *mtd, unsigned int command, + if (command == NAND_CMD_PARAM) + timing = IFC_FIR_OP_RBCD; + +- iowrite32be((IFC_FIR_OP_CW0 << IFC_NAND_FIR0_OP0_SHIFT) | +- (IFC_FIR_OP_UA << IFC_NAND_FIR0_OP1_SHIFT) | +- (timing << IFC_NAND_FIR0_OP2_SHIFT), +- &ifc->ifc_nand.nand_fir0); +- iowrite32be(command << IFC_NAND_FCR0_CMD0_SHIFT, +- &ifc->ifc_nand.nand_fcr0); +- iowrite32be(column, &ifc->ifc_nand.row3); ++ ifc_out32((IFC_FIR_OP_CW0 << IFC_NAND_FIR0_OP0_SHIFT) | ++ (IFC_FIR_OP_UA << IFC_NAND_FIR0_OP1_SHIFT) | ++ (timing << IFC_NAND_FIR0_OP2_SHIFT), ++ &ifc->ifc_nand.nand_fir0); ++ ifc_out32(command << IFC_NAND_FCR0_CMD0_SHIFT, ++ &ifc->ifc_nand.nand_fcr0); ++ ifc_out32(column, &ifc->ifc_nand.row3); + + /* + * although currently it's 8 bytes for READID, we always read + * the maximum 256 bytes(for PARAM) + */ +- iowrite32be(256, &ifc->ifc_nand.nand_fbcr); ++ ifc_out32(256, &ifc->ifc_nand.nand_fbcr); + ifc_nand_ctrl->read_bytes = 256; + + set_addr(mtd, 0, 0, 0); +@@ -481,16 +480,16 @@ static void fsl_ifc_cmdfunc(struct mtd_info *mtd, unsigned int command, + + /* ERASE2 uses the block and page address from ERASE1 */ + case NAND_CMD_ERASE2: +- iowrite32be((IFC_FIR_OP_CW0 << IFC_NAND_FIR0_OP0_SHIFT) | +- (IFC_FIR_OP_RA0 << IFC_NAND_FIR0_OP1_SHIFT) | +- (IFC_FIR_OP_CMD1 << IFC_NAND_FIR0_OP2_SHIFT), +- &ifc->ifc_nand.nand_fir0); ++ ifc_out32((IFC_FIR_OP_CW0 << IFC_NAND_FIR0_OP0_SHIFT) | ++ (IFC_FIR_OP_RA0 << IFC_NAND_FIR0_OP1_SHIFT) | ++ (IFC_FIR_OP_CMD1 << IFC_NAND_FIR0_OP2_SHIFT), ++ &ifc->ifc_nand.nand_fir0); + +- iowrite32be((NAND_CMD_ERASE1 << IFC_NAND_FCR0_CMD0_SHIFT) | +- (NAND_CMD_ERASE2 << IFC_NAND_FCR0_CMD1_SHIFT), +- &ifc->ifc_nand.nand_fcr0); ++ ifc_out32((NAND_CMD_ERASE1 << IFC_NAND_FCR0_CMD0_SHIFT) | ++ (NAND_CMD_ERASE2 << IFC_NAND_FCR0_CMD1_SHIFT), ++ &ifc->ifc_nand.nand_fcr0); + +- iowrite32be(0, &ifc->ifc_nand.nand_fbcr); ++ ifc_out32(0, &ifc->ifc_nand.nand_fbcr); + ifc_nand_ctrl->read_bytes = 0; + fsl_ifc_run_command(mtd); + return; +@@ -507,19 +506,18 @@ static void fsl_ifc_cmdfunc(struct mtd_info *mtd, unsigned int command, + (NAND_CMD_STATUS << IFC_NAND_FCR0_CMD1_SHIFT) | + (NAND_CMD_PAGEPROG << IFC_NAND_FCR0_CMD2_SHIFT); + +- iowrite32be( +- (IFC_FIR_OP_CW0 << IFC_NAND_FIR0_OP0_SHIFT) | +- (IFC_FIR_OP_CA0 << IFC_NAND_FIR0_OP1_SHIFT) | +- (IFC_FIR_OP_RA0 << IFC_NAND_FIR0_OP2_SHIFT) | +- (IFC_FIR_OP_WBCD << IFC_NAND_FIR0_OP3_SHIFT) | +- (IFC_FIR_OP_CMD2 << IFC_NAND_FIR0_OP4_SHIFT), +- &ifc->ifc_nand.nand_fir0); +- iowrite32be( +- (IFC_FIR_OP_CW1 << IFC_NAND_FIR1_OP5_SHIFT) | +- (IFC_FIR_OP_RDSTAT << +- IFC_NAND_FIR1_OP6_SHIFT) | +- (IFC_FIR_OP_NOP << IFC_NAND_FIR1_OP7_SHIFT), +- &ifc->ifc_nand.nand_fir1); ++ ifc_out32( ++ (IFC_FIR_OP_CW0 << IFC_NAND_FIR0_OP0_SHIFT) | ++ (IFC_FIR_OP_CA0 << IFC_NAND_FIR0_OP1_SHIFT) | ++ (IFC_FIR_OP_RA0 << IFC_NAND_FIR0_OP2_SHIFT) | ++ (IFC_FIR_OP_WBCD << IFC_NAND_FIR0_OP3_SHIFT) | ++ (IFC_FIR_OP_CMD2 << IFC_NAND_FIR0_OP4_SHIFT), ++ &ifc->ifc_nand.nand_fir0); ++ ifc_out32( ++ (IFC_FIR_OP_CW1 << IFC_NAND_FIR1_OP5_SHIFT) | ++ (IFC_FIR_OP_RDSTAT << IFC_NAND_FIR1_OP6_SHIFT) | ++ (IFC_FIR_OP_NOP << IFC_NAND_FIR1_OP7_SHIFT), ++ &ifc->ifc_nand.nand_fir1); + } else { + nand_fcr0 = ((NAND_CMD_PAGEPROG << + IFC_NAND_FCR0_CMD1_SHIFT) | +@@ -528,20 +526,19 @@ static void fsl_ifc_cmdfunc(struct mtd_info *mtd, unsigned int command, + (NAND_CMD_STATUS << + IFC_NAND_FCR0_CMD3_SHIFT)); + +- iowrite32be( ++ ifc_out32( + (IFC_FIR_OP_CW0 << IFC_NAND_FIR0_OP0_SHIFT) | + (IFC_FIR_OP_CMD2 << IFC_NAND_FIR0_OP1_SHIFT) | + (IFC_FIR_OP_CA0 << IFC_NAND_FIR0_OP2_SHIFT) | + (IFC_FIR_OP_RA0 << IFC_NAND_FIR0_OP3_SHIFT) | + (IFC_FIR_OP_WBCD << IFC_NAND_FIR0_OP4_SHIFT), + &ifc->ifc_nand.nand_fir0); +- iowrite32be( +- (IFC_FIR_OP_CMD1 << IFC_NAND_FIR1_OP5_SHIFT) | +- (IFC_FIR_OP_CW3 << IFC_NAND_FIR1_OP6_SHIFT) | +- (IFC_FIR_OP_RDSTAT << +- IFC_NAND_FIR1_OP7_SHIFT) | +- (IFC_FIR_OP_NOP << IFC_NAND_FIR1_OP8_SHIFT), +- &ifc->ifc_nand.nand_fir1); ++ ifc_out32( ++ (IFC_FIR_OP_CMD1 << IFC_NAND_FIR1_OP5_SHIFT) | ++ (IFC_FIR_OP_CW3 << IFC_NAND_FIR1_OP6_SHIFT) | ++ (IFC_FIR_OP_RDSTAT << IFC_NAND_FIR1_OP7_SHIFT) | ++ (IFC_FIR_OP_NOP << IFC_NAND_FIR1_OP8_SHIFT), ++ &ifc->ifc_nand.nand_fir1); + + if (column >= mtd->writesize) + nand_fcr0 |= +@@ -556,7 +553,7 @@ static void fsl_ifc_cmdfunc(struct mtd_info *mtd, unsigned int command, + column -= mtd->writesize; + ifc_nand_ctrl->oob = 1; + } +- iowrite32be(nand_fcr0, &ifc->ifc_nand.nand_fcr0); ++ ifc_out32(nand_fcr0, &ifc->ifc_nand.nand_fcr0); + set_addr(mtd, column, page_addr, ifc_nand_ctrl->oob); + return; + } +@@ -564,24 +561,26 @@ static void fsl_ifc_cmdfunc(struct mtd_info *mtd, unsigned int command, + /* PAGEPROG reuses all of the setup from SEQIN and adds the length */ + case NAND_CMD_PAGEPROG: { + if (ifc_nand_ctrl->oob) { +- iowrite32be(ifc_nand_ctrl->index - +- ifc_nand_ctrl->column, +- &ifc->ifc_nand.nand_fbcr); ++ ifc_out32(ifc_nand_ctrl->index - ++ ifc_nand_ctrl->column, ++ &ifc->ifc_nand.nand_fbcr); + } else { +- iowrite32be(0, &ifc->ifc_nand.nand_fbcr); ++ ifc_out32(0, &ifc->ifc_nand.nand_fbcr); + } + + fsl_ifc_run_command(mtd); + return; + } + +- case NAND_CMD_STATUS: +- iowrite32be((IFC_FIR_OP_CW0 << IFC_NAND_FIR0_OP0_SHIFT) | +- (IFC_FIR_OP_RB << IFC_NAND_FIR0_OP1_SHIFT), +- &ifc->ifc_nand.nand_fir0); +- iowrite32be(NAND_CMD_STATUS << IFC_NAND_FCR0_CMD0_SHIFT, +- &ifc->ifc_nand.nand_fcr0); +- iowrite32be(1, &ifc->ifc_nand.nand_fbcr); ++ case NAND_CMD_STATUS: { ++ void __iomem *addr; ++ ++ ifc_out32((IFC_FIR_OP_CW0 << IFC_NAND_FIR0_OP0_SHIFT) | ++ (IFC_FIR_OP_RB << IFC_NAND_FIR0_OP1_SHIFT), ++ &ifc->ifc_nand.nand_fir0); ++ ifc_out32(NAND_CMD_STATUS << IFC_NAND_FCR0_CMD0_SHIFT, ++ &ifc->ifc_nand.nand_fcr0); ++ ifc_out32(1, &ifc->ifc_nand.nand_fbcr); + set_addr(mtd, 0, 0, 0); + ifc_nand_ctrl->read_bytes = 1; + +@@ -591,17 +590,19 @@ static void fsl_ifc_cmdfunc(struct mtd_info *mtd, unsigned int command, + * The chip always seems to report that it is + * write-protected, even when it is not. + */ ++ addr = ifc_nand_ctrl->addr; + if (chip->options & NAND_BUSWIDTH_16) +- setbits16(ifc_nand_ctrl->addr, NAND_STATUS_WP); ++ ifc_out16(ifc_in16(addr) | (NAND_STATUS_WP), addr); + else +- setbits8(ifc_nand_ctrl->addr, NAND_STATUS_WP); ++ ifc_out8(ifc_in8(addr) | (NAND_STATUS_WP), addr); + return; ++ } + + case NAND_CMD_RESET: +- iowrite32be(IFC_FIR_OP_CW0 << IFC_NAND_FIR0_OP0_SHIFT, +- &ifc->ifc_nand.nand_fir0); +- iowrite32be(NAND_CMD_RESET << IFC_NAND_FCR0_CMD0_SHIFT, +- &ifc->ifc_nand.nand_fcr0); ++ ifc_out32(IFC_FIR_OP_CW0 << IFC_NAND_FIR0_OP0_SHIFT, ++ &ifc->ifc_nand.nand_fir0); ++ ifc_out32(NAND_CMD_RESET << IFC_NAND_FCR0_CMD0_SHIFT, ++ &ifc->ifc_nand.nand_fcr0); + fsl_ifc_run_command(mtd); + return; + +@@ -659,7 +660,7 @@ static uint8_t fsl_ifc_read_byte(struct mtd_info *mtd) + */ + if (ifc_nand_ctrl->index < ifc_nand_ctrl->read_bytes) { + offset = ifc_nand_ctrl->index++; +- return in_8(ifc_nand_ctrl->addr + offset); ++ return ifc_in8(ifc_nand_ctrl->addr + offset); + } + + dev_err(priv->dev, "%s: beyond end of buffer\n", __func__); +@@ -681,7 +682,7 @@ static uint8_t fsl_ifc_read_byte16(struct mtd_info *mtd) + * next byte. + */ + if (ifc_nand_ctrl->index < ifc_nand_ctrl->read_bytes) { +- data = in_be16(ifc_nand_ctrl->addr + ifc_nand_ctrl->index); ++ data = ifc_in16(ifc_nand_ctrl->addr + ifc_nand_ctrl->index); + ifc_nand_ctrl->index += 2; + return (uint8_t) data; + } +@@ -723,22 +724,22 @@ static int fsl_ifc_wait(struct mtd_info *mtd, struct nand_chip *chip) + { + struct fsl_ifc_mtd *priv = chip->priv; + struct fsl_ifc_ctrl *ctrl = priv->ctrl; +- struct fsl_ifc_regs __iomem *ifc = ctrl->regs; ++ struct fsl_ifc_runtime __iomem *ifc = ctrl->rregs; + u32 nand_fsr; + + /* Use READ_STATUS command, but wait for the device to be ready */ +- iowrite32be((IFC_FIR_OP_CW0 << IFC_NAND_FIR0_OP0_SHIFT) | +- (IFC_FIR_OP_RDSTAT << IFC_NAND_FIR0_OP1_SHIFT), +- &ifc->ifc_nand.nand_fir0); +- iowrite32be(NAND_CMD_STATUS << IFC_NAND_FCR0_CMD0_SHIFT, +- &ifc->ifc_nand.nand_fcr0); +- iowrite32be(1, &ifc->ifc_nand.nand_fbcr); ++ ifc_out32((IFC_FIR_OP_CW0 << IFC_NAND_FIR0_OP0_SHIFT) | ++ (IFC_FIR_OP_RDSTAT << IFC_NAND_FIR0_OP1_SHIFT), ++ &ifc->ifc_nand.nand_fir0); ++ ifc_out32(NAND_CMD_STATUS << IFC_NAND_FCR0_CMD0_SHIFT, ++ &ifc->ifc_nand.nand_fcr0); ++ ifc_out32(1, &ifc->ifc_nand.nand_fbcr); + set_addr(mtd, 0, 0, 0); + ifc_nand_ctrl->read_bytes = 1; + + fsl_ifc_run_command(mtd); + +- nand_fsr = ioread32be(&ifc->ifc_nand.nand_fsr); ++ nand_fsr = ifc_in32(&ifc->ifc_nand.nand_fsr); + + /* + * The chip always seems to report that it is +@@ -825,67 +826,72 @@ static int fsl_ifc_chip_init_tail(struct mtd_info *mtd) + static void fsl_ifc_sram_init(struct fsl_ifc_mtd *priv) + { + struct fsl_ifc_ctrl *ctrl = priv->ctrl; +- struct fsl_ifc_regs __iomem *ifc = ctrl->regs; ++ struct fsl_ifc_runtime __iomem *ifc_runtime = ctrl->rregs; ++ struct fsl_ifc_global __iomem *ifc_global = ctrl->gregs; + uint32_t csor = 0, csor_8k = 0, csor_ext = 0; + uint32_t cs = priv->bank; + + /* Save CSOR and CSOR_ext */ +- csor = ioread32be(&ifc->csor_cs[cs].csor); +- csor_ext = ioread32be(&ifc->csor_cs[cs].csor_ext); ++ csor = ifc_in32(&ifc_global->csor_cs[cs].csor); ++ csor_ext = ifc_in32(&ifc_global->csor_cs[cs].csor_ext); + + /* chage PageSize 8K and SpareSize 1K*/ + csor_8k = (csor & ~(CSOR_NAND_PGS_MASK)) | 0x0018C000; +- iowrite32be(csor_8k, &ifc->csor_cs[cs].csor); +- iowrite32be(0x0000400, &ifc->csor_cs[cs].csor_ext); ++ ifc_out32(csor_8k, &ifc_global->csor_cs[cs].csor); ++ ifc_out32(0x0000400, &ifc_global->csor_cs[cs].csor_ext); + + /* READID */ +- iowrite32be((IFC_FIR_OP_CW0 << IFC_NAND_FIR0_OP0_SHIFT) | ++ ifc_out32((IFC_FIR_OP_CW0 << IFC_NAND_FIR0_OP0_SHIFT) | + (IFC_FIR_OP_UA << IFC_NAND_FIR0_OP1_SHIFT) | + (IFC_FIR_OP_RB << IFC_NAND_FIR0_OP2_SHIFT), +- &ifc->ifc_nand.nand_fir0); +- iowrite32be(NAND_CMD_READID << IFC_NAND_FCR0_CMD0_SHIFT, +- &ifc->ifc_nand.nand_fcr0); +- iowrite32be(0x0, &ifc->ifc_nand.row3); ++ &ifc_runtime->ifc_nand.nand_fir0); ++ ifc_out32(NAND_CMD_READID << IFC_NAND_FCR0_CMD0_SHIFT, ++ &ifc_runtime->ifc_nand.nand_fcr0); ++ ifc_out32(0x0, &ifc_runtime->ifc_nand.row3); + +- iowrite32be(0x0, &ifc->ifc_nand.nand_fbcr); ++ ifc_out32(0x0, &ifc_runtime->ifc_nand.nand_fbcr); + + /* Program ROW0/COL0 */ +- iowrite32be(0x0, &ifc->ifc_nand.row0); +- iowrite32be(0x0, &ifc->ifc_nand.col0); ++ ifc_out32(0x0, &ifc_runtime->ifc_nand.row0); ++ ifc_out32(0x0, &ifc_runtime->ifc_nand.col0); + + /* set the chip select for NAND Transaction */ +- iowrite32be(cs << IFC_NAND_CSEL_SHIFT, &ifc->ifc_nand.nand_csel); ++ ifc_out32(cs << IFC_NAND_CSEL_SHIFT, ++ &ifc_runtime->ifc_nand.nand_csel); + + /* start read seq */ +- iowrite32be(IFC_NAND_SEQ_STRT_FIR_STRT, &ifc->ifc_nand.nandseq_strt); ++ ifc_out32(IFC_NAND_SEQ_STRT_FIR_STRT, ++ &ifc_runtime->ifc_nand.nandseq_strt); + + /* wait for command complete flag or timeout */ + wait_event_timeout(ctrl->nand_wait, ctrl->nand_stat, +- IFC_TIMEOUT_MSECS * HZ/1000); ++ msecs_to_jiffies(IFC_TIMEOUT_MSECS)); + + if (ctrl->nand_stat != IFC_NAND_EVTER_STAT_OPC) + printk(KERN_ERR "fsl-ifc: Failed to Initialise SRAM\n"); + + /* Restore CSOR and CSOR_ext */ +- iowrite32be(csor, &ifc->csor_cs[cs].csor); +- iowrite32be(csor_ext, &ifc->csor_cs[cs].csor_ext); ++ ifc_out32(csor, &ifc_global->csor_cs[cs].csor); ++ ifc_out32(csor_ext, &ifc_global->csor_cs[cs].csor_ext); + } + + static int fsl_ifc_chip_init(struct fsl_ifc_mtd *priv) + { + struct fsl_ifc_ctrl *ctrl = priv->ctrl; +- struct fsl_ifc_regs __iomem *ifc = ctrl->regs; ++ struct fsl_ifc_global __iomem *ifc_global = ctrl->gregs; ++ struct fsl_ifc_runtime __iomem *ifc_runtime = ctrl->rregs; + struct nand_chip *chip = &priv->chip; + struct nand_ecclayout *layout; +- u32 csor, ver; ++ u32 csor; + + /* Fill in fsl_ifc_mtd structure */ + priv->mtd.priv = chip; +- priv->mtd.owner = THIS_MODULE; ++ priv->mtd.dev.parent = priv->dev; + + /* fill in nand_chip structure */ + /* set up function call table */ +- if ((ioread32be(&ifc->cspr_cs[priv->bank].cspr)) & CSPR_PORT_SIZE_16) ++ if ((ifc_in32(&ifc_global->cspr_cs[priv->bank].cspr)) ++ & CSPR_PORT_SIZE_16) + chip->read_byte = fsl_ifc_read_byte16; + else + chip->read_byte = fsl_ifc_read_byte; +@@ -899,13 +905,14 @@ static int fsl_ifc_chip_init(struct fsl_ifc_mtd *priv) + chip->bbt_td = &bbt_main_descr; + chip->bbt_md = &bbt_mirror_descr; + +- iowrite32be(0x0, &ifc->ifc_nand.ncfgr); ++ ifc_out32(0x0, &ifc_runtime->ifc_nand.ncfgr); + + /* set up nand options */ + chip->bbt_options = NAND_BBT_USE_FLASH; + chip->options = NAND_NO_SUBPAGE_WRITE; + +- if (ioread32be(&ifc->cspr_cs[priv->bank].cspr) & CSPR_PORT_SIZE_16) { ++ if (ifc_in32(&ifc_global->cspr_cs[priv->bank].cspr) ++ & CSPR_PORT_SIZE_16) { + chip->read_byte = fsl_ifc_read_byte16; + chip->options |= NAND_BUSWIDTH_16; + } else { +@@ -918,7 +925,7 @@ static int fsl_ifc_chip_init(struct fsl_ifc_mtd *priv) + chip->ecc.read_page = fsl_ifc_read_page; + chip->ecc.write_page = fsl_ifc_write_page; + +- csor = ioread32be(&ifc->csor_cs[priv->bank].csor); ++ csor = ifc_in32(&ifc_global->csor_cs[priv->bank].csor); + + /* Hardware generates ECC per 512 Bytes */ + chip->ecc.size = 512; +@@ -984,8 +991,7 @@ static int fsl_ifc_chip_init(struct fsl_ifc_mtd *priv) + chip->ecc.mode = NAND_ECC_SOFT; + } + +- ver = ioread32be(&ifc->ifc_rev); +- if (ver == FSL_IFC_V1_1_0) ++ if (ctrl->version == FSL_IFC_VERSION_1_1_0) + fsl_ifc_sram_init(priv); + + return 0; +@@ -1005,10 +1011,10 @@ static int fsl_ifc_chip_remove(struct fsl_ifc_mtd *priv) + return 0; + } + +-static int match_bank(struct fsl_ifc_regs __iomem *ifc, int bank, ++static int match_bank(struct fsl_ifc_global __iomem *ifc_global, int bank, + phys_addr_t addr) + { +- u32 cspr = ioread32be(&ifc->cspr_cs[bank].cspr); ++ u32 cspr = ifc_in32(&ifc_global->cspr_cs[bank].cspr); + + if (!(cspr & CSPR_V)) + return 0; +@@ -1022,7 +1028,7 @@ static DEFINE_MUTEX(fsl_ifc_nand_mutex); + + static int fsl_ifc_nand_probe(struct platform_device *dev) + { +- struct fsl_ifc_regs __iomem *ifc; ++ struct fsl_ifc_runtime __iomem *ifc; + struct fsl_ifc_mtd *priv; + struct resource res; + static const char *part_probe_types[] +@@ -1033,9 +1039,9 @@ static int fsl_ifc_nand_probe(struct platform_device *dev) + struct mtd_part_parser_data ppdata; + + ppdata.of_node = dev->dev.of_node; +- if (!fsl_ifc_ctrl_dev || !fsl_ifc_ctrl_dev->regs) ++ if (!fsl_ifc_ctrl_dev || !fsl_ifc_ctrl_dev->rregs) + return -ENODEV; +- ifc = fsl_ifc_ctrl_dev->regs; ++ ifc = fsl_ifc_ctrl_dev->rregs; + + /* get, allocate and map the memory resource */ + ret = of_address_to_resource(node, 0, &res); +@@ -1045,12 +1051,12 @@ static int fsl_ifc_nand_probe(struct platform_device *dev) + } + + /* find which chip select it is connected to */ +- for (bank = 0; bank < FSL_IFC_BANK_COUNT; bank++) { +- if (match_bank(ifc, bank, res.start)) ++ for (bank = 0; bank < fsl_ifc_ctrl_dev->banks; bank++) { ++ if (match_bank(fsl_ifc_ctrl_dev->gregs, bank, res.start)) + break; + } + +- if (bank >= FSL_IFC_BANK_COUNT) { ++ if (bank >= fsl_ifc_ctrl_dev->banks) { + dev_err(&dev->dev, "%s: address did not match any chip selects\n", + __func__); + return -ENODEV; +@@ -1094,16 +1100,16 @@ static int fsl_ifc_nand_probe(struct platform_device *dev) + + dev_set_drvdata(priv->dev, priv); + +- iowrite32be(IFC_NAND_EVTER_EN_OPC_EN | +- IFC_NAND_EVTER_EN_FTOER_EN | +- IFC_NAND_EVTER_EN_WPER_EN, +- &ifc->ifc_nand.nand_evter_en); ++ ifc_out32(IFC_NAND_EVTER_EN_OPC_EN | ++ IFC_NAND_EVTER_EN_FTOER_EN | ++ IFC_NAND_EVTER_EN_WPER_EN, ++ &ifc->ifc_nand.nand_evter_en); + + /* enable NAND Machine Interrupts */ +- iowrite32be(IFC_NAND_EVTER_INTR_OPCIR_EN | +- IFC_NAND_EVTER_INTR_FTOERIR_EN | +- IFC_NAND_EVTER_INTR_WPERIR_EN, +- &ifc->ifc_nand.nand_evter_intr_en); ++ ifc_out32(IFC_NAND_EVTER_INTR_OPCIR_EN | ++ IFC_NAND_EVTER_INTR_FTOERIR_EN | ++ IFC_NAND_EVTER_INTR_WPERIR_EN, ++ &ifc->ifc_nand.nand_evter_intr_en); + priv->mtd.name = kasprintf(GFP_KERNEL, "%llx.flash", (u64)res.start); + if (!priv->mtd.name) { + ret = -ENOMEM; +@@ -1163,6 +1169,7 @@ static const struct of_device_id fsl_ifc_nand_match[] = { + }, + {} + }; ++MODULE_DEVICE_TABLE(of, fsl_ifc_nand_match); + + static struct platform_driver fsl_ifc_nand_driver = { + .driver = { +diff --git a/drivers/net/ethernet/freescale/Kconfig b/drivers/net/ethernet/freescale/Kconfig +index 2703083..0c1c97d 100644 +--- a/drivers/net/ethernet/freescale/Kconfig ++++ b/drivers/net/ethernet/freescale/Kconfig +@@ -7,7 +7,8 @@ config NET_VENDOR_FREESCALE + default y + depends on FSL_SOC || QUICC_ENGINE || CPM1 || CPM2 || PPC_MPC512x || \ + M523x || M527x || M5272 || M528x || M520x || M532x || \ +- ARCH_MXC || ARCH_MXS || (PPC_MPC52xx && PPC_BESTCOMM) ++ ARCH_MXC || ARCH_MXS || (PPC_MPC52xx && PPC_BESTCOMM) || \ ++ ARCH_LAYERSCAPE + ---help--- + If you have a network (Ethernet) card belonging to this class, say Y + and read the Ethernet-HOWTO, available from +@@ -58,18 +59,17 @@ source "drivers/net/ethernet/freescale/fs_enet/Kconfig" + + config FSL_PQ_MDIO + tristate "Freescale PQ MDIO" +- depends on FSL_SOC + select PHYLIB + ---help--- + This driver supports the MDIO bus used by the gianfar and UCC drivers. + + config FSL_XGMAC_MDIO + tristate "Freescale XGMAC MDIO" +- depends on FSL_SOC + select PHYLIB + select OF_MDIO + ---help--- +- This driver supports the MDIO bus on the Fman 10G Ethernet MACs. ++ This driver supports the MDIO bus on the Fman 10G Ethernet MACs and ++ on mEMAC (which supports both Clauses 22 and 45) + + config UCC_GETH + tristate "Freescale QE Gigabit Ethernet" +diff --git a/drivers/net/ethernet/freescale/fec_mpc52xx.c b/drivers/net/ethernet/freescale/fec_mpc52xx.c +index ff55fbb..76ff046 100644 +--- a/drivers/net/ethernet/freescale/fec_mpc52xx.c ++++ b/drivers/net/ethernet/freescale/fec_mpc52xx.c +@@ -1057,7 +1057,7 @@ static int mpc52xx_fec_of_resume(struct platform_device *op) + } + #endif + +-static struct of_device_id mpc52xx_fec_match[] = { ++static const struct of_device_id mpc52xx_fec_match[] = { + { .compatible = "fsl,mpc5200b-fec", }, + { .compatible = "fsl,mpc5200-fec", }, + { .compatible = "mpc5200-fec", }, +diff --git a/drivers/net/ethernet/freescale/fec_mpc52xx_phy.c b/drivers/net/ethernet/freescale/fec_mpc52xx_phy.c +index e052890..1e647be 100644 +--- a/drivers/net/ethernet/freescale/fec_mpc52xx_phy.c ++++ b/drivers/net/ethernet/freescale/fec_mpc52xx_phy.c +@@ -134,7 +134,7 @@ static int mpc52xx_fec_mdio_remove(struct platform_device *of) + return 0; + } + +-static struct of_device_id mpc52xx_fec_mdio_match[] = { ++static const struct of_device_id mpc52xx_fec_mdio_match[] = { + { .compatible = "fsl,mpc5200b-mdio", }, + { .compatible = "fsl,mpc5200-mdio", }, + { .compatible = "mpc5200b-fec-phy", }, +diff --git a/drivers/net/ethernet/freescale/fs_enet/fs_enet-main.c b/drivers/net/ethernet/freescale/fs_enet/fs_enet-main.c +index c92c3b7..dc0da6c 100644 +--- a/drivers/net/ethernet/freescale/fs_enet/fs_enet-main.c ++++ b/drivers/net/ethernet/freescale/fs_enet/fs_enet-main.c +@@ -886,7 +886,7 @@ static const struct net_device_ops fs_enet_netdev_ops = { + #endif + }; + +-static struct of_device_id fs_enet_match[]; ++static const struct of_device_id fs_enet_match[]; + static int fs_enet_probe(struct platform_device *ofdev) + { + const struct of_device_id *match; +@@ -1047,7 +1047,7 @@ static int fs_enet_remove(struct platform_device *ofdev) + return 0; + } + +-static struct of_device_id fs_enet_match[] = { ++static const struct of_device_id fs_enet_match[] = { + #ifdef CONFIG_FS_ENET_HAS_SCC + { + .compatible = "fsl,cpm1-scc-enet", +diff --git a/drivers/net/ethernet/freescale/fs_enet/mii-bitbang.c b/drivers/net/ethernet/freescale/fs_enet/mii-bitbang.c +index 3d3fde6..9ec396b 100644 +--- a/drivers/net/ethernet/freescale/fs_enet/mii-bitbang.c ++++ b/drivers/net/ethernet/freescale/fs_enet/mii-bitbang.c +@@ -213,7 +213,7 @@ static int fs_enet_mdio_remove(struct platform_device *ofdev) + return 0; + } + +-static struct of_device_id fs_enet_mdio_bb_match[] = { ++static const struct of_device_id fs_enet_mdio_bb_match[] = { + { + .compatible = "fsl,cpm2-mdio-bitbang", + }, +diff --git a/drivers/net/ethernet/freescale/fs_enet/mii-fec.c b/drivers/net/ethernet/freescale/fs_enet/mii-fec.c +index ebf5d64..72205b0 100644 +--- a/drivers/net/ethernet/freescale/fs_enet/mii-fec.c ++++ b/drivers/net/ethernet/freescale/fs_enet/mii-fec.c +@@ -95,7 +95,7 @@ static int fs_enet_fec_mii_write(struct mii_bus *bus, int phy_id, int location, + + } + +-static struct of_device_id fs_enet_mdio_fec_match[]; ++static const struct of_device_id fs_enet_mdio_fec_match[]; + static int fs_enet_mdio_probe(struct platform_device *ofdev) + { + const struct of_device_id *match; +@@ -208,7 +208,7 @@ static int fs_enet_mdio_remove(struct platform_device *ofdev) + return 0; + } + +-static struct of_device_id fs_enet_mdio_fec_match[] = { ++static const struct of_device_id fs_enet_mdio_fec_match[] = { + { + .compatible = "fsl,pq1-fec-mdio", + }, +diff --git a/drivers/net/ethernet/freescale/fsl_pq_mdio.c b/drivers/net/ethernet/freescale/fsl_pq_mdio.c +index 964c6bf..f94fa63 100644 +--- a/drivers/net/ethernet/freescale/fsl_pq_mdio.c ++++ b/drivers/net/ethernet/freescale/fsl_pq_mdio.c +@@ -294,7 +294,7 @@ static void ucc_configure(phys_addr_t start, phys_addr_t end) + + #endif + +-static struct of_device_id fsl_pq_mdio_match[] = { ++static const struct of_device_id fsl_pq_mdio_match[] = { + #if defined(CONFIG_GIANFAR) || defined(CONFIG_GIANFAR_MODULE) + { + .compatible = "fsl,gianfar-tbi", +diff --git a/drivers/net/ethernet/freescale/gianfar.c b/drivers/net/ethernet/freescale/gianfar.c +index 4fdf0aa..0359cfd 100644 +--- a/drivers/net/ethernet/freescale/gianfar.c ++++ b/drivers/net/ethernet/freescale/gianfar.c +@@ -86,11 +86,11 @@ + #include + #include + #include ++#include + + #include + #ifdef CONFIG_PPC + #include +-#include + #endif + #include + #include +@@ -1720,8 +1720,10 @@ static void gfar_configure_serdes(struct net_device *dev) + * everything for us? Resetting it takes the link down and requires + * several seconds for it to come back. + */ +- if (phy_read(tbiphy, MII_BMSR) & BMSR_LSTATUS) ++ if (phy_read(tbiphy, MII_BMSR) & BMSR_LSTATUS) { ++ put_device(&tbiphy->dev); + return; ++ } + + /* Single clk mode, mii mode off(for serdes communication) */ + phy_write(tbiphy, MII_TBICON, TBICON_CLK_SELECT); +@@ -3455,7 +3457,7 @@ static noinline void gfar_update_link_state(struct gfar_private *priv) + phy_print_status(phydev); + } + +-static struct of_device_id gfar_match[] = ++static const struct of_device_id gfar_match[] = + { + { + .type = "network", +diff --git a/drivers/net/ethernet/freescale/gianfar_ptp.c b/drivers/net/ethernet/freescale/gianfar_ptp.c +index bb56800..c7c75de 100644 +--- a/drivers/net/ethernet/freescale/gianfar_ptp.c ++++ b/drivers/net/ethernet/freescale/gianfar_ptp.c +@@ -554,7 +554,7 @@ static int gianfar_ptp_remove(struct platform_device *dev) + return 0; + } + +-static struct of_device_id match_table[] = { ++static const struct of_device_id match_table[] = { + { .compatible = "fsl,etsec-ptp" }, + {}, + }; +diff --git a/drivers/net/ethernet/freescale/ucc_geth.c b/drivers/net/ethernet/freescale/ucc_geth.c +index 3cf0478..741a7d4 100644 +--- a/drivers/net/ethernet/freescale/ucc_geth.c ++++ b/drivers/net/ethernet/freescale/ucc_geth.c +@@ -3930,7 +3930,7 @@ static int ucc_geth_remove(struct platform_device* ofdev) + return 0; + } + +-static struct of_device_id ucc_geth_match[] = { ++static const struct of_device_id ucc_geth_match[] = { + { + .type = "network", + .compatible = "ucc_geth", +diff --git a/drivers/net/ethernet/freescale/xgmac_mdio.c b/drivers/net/ethernet/freescale/xgmac_mdio.c +index 6e7db66..7b8fe86 100644 +--- a/drivers/net/ethernet/freescale/xgmac_mdio.c ++++ b/drivers/net/ethernet/freescale/xgmac_mdio.c +@@ -32,31 +32,62 @@ struct tgec_mdio_controller { + __be32 mdio_addr; /* MDIO address */ + } __packed; + ++#define MDIO_STAT_ENC BIT(6) + #define MDIO_STAT_CLKDIV(x) (((x>>1) & 0xff) << 8) +-#define MDIO_STAT_BSY (1 << 0) +-#define MDIO_STAT_RD_ER (1 << 1) ++#define MDIO_STAT_BSY BIT(0) ++#define MDIO_STAT_RD_ER BIT(1) + #define MDIO_CTL_DEV_ADDR(x) (x & 0x1f) + #define MDIO_CTL_PORT_ADDR(x) ((x & 0x1f) << 5) +-#define MDIO_CTL_PRE_DIS (1 << 10) +-#define MDIO_CTL_SCAN_EN (1 << 11) +-#define MDIO_CTL_POST_INC (1 << 14) +-#define MDIO_CTL_READ (1 << 15) ++#define MDIO_CTL_PRE_DIS BIT(10) ++#define MDIO_CTL_SCAN_EN BIT(11) ++#define MDIO_CTL_POST_INC BIT(14) ++#define MDIO_CTL_READ BIT(15) + + #define MDIO_DATA(x) (x & 0xffff) +-#define MDIO_DATA_BSY (1 << 31) ++#define MDIO_DATA_BSY BIT(31) ++ ++struct mdio_fsl_priv { ++ struct tgec_mdio_controller __iomem *mdio_base; ++ bool is_little_endian; ++}; ++ ++static u32 xgmac_read32(void __iomem *regs, ++ bool is_little_endian) ++{ ++ if (is_little_endian) ++ return ioread32(regs); ++ else ++ return ioread32be(regs); ++} ++ ++static void xgmac_write32(u32 value, ++ void __iomem *regs, ++ bool is_little_endian) ++{ ++ if (is_little_endian) ++ iowrite32(value, regs); ++ else ++ iowrite32be(value, regs); ++} + + /* + * Wait until the MDIO bus is free + */ + static int xgmac_wait_until_free(struct device *dev, +- struct tgec_mdio_controller __iomem *regs) ++ struct tgec_mdio_controller __iomem *regs, ++ bool is_little_endian) + { +- uint32_t status; ++ unsigned int timeout; + + /* Wait till the bus is free */ +- status = spin_event_timeout( +- !((in_be32(®s->mdio_stat)) & MDIO_STAT_BSY), TIMEOUT, 0); +- if (!status) { ++ timeout = TIMEOUT; ++ while ((xgmac_read32(®s->mdio_stat, is_little_endian) & ++ MDIO_STAT_BSY) && timeout) { ++ cpu_relax(); ++ timeout--; ++ } ++ ++ if (!timeout) { + dev_err(dev, "timeout waiting for bus to be free\n"); + return -ETIMEDOUT; + } +@@ -68,14 +99,20 @@ static int xgmac_wait_until_free(struct device *dev, + * Wait till the MDIO read or write operation is complete + */ + static int xgmac_wait_until_done(struct device *dev, +- struct tgec_mdio_controller __iomem *regs) ++ struct tgec_mdio_controller __iomem *regs, ++ bool is_little_endian) + { +- uint32_t status; ++ unsigned int timeout; + + /* Wait till the MDIO write is complete */ +- status = spin_event_timeout( +- !((in_be32(®s->mdio_data)) & MDIO_DATA_BSY), TIMEOUT, 0); +- if (!status) { ++ timeout = TIMEOUT; ++ while ((xgmac_read32(®s->mdio_stat, is_little_endian) & ++ MDIO_STAT_BSY) && timeout) { ++ cpu_relax(); ++ timeout--; ++ } ++ ++ if (!timeout) { + dev_err(dev, "timeout waiting for operation to complete\n"); + return -ETIMEDOUT; + } +@@ -90,32 +127,47 @@ static int xgmac_wait_until_done(struct device *dev, + */ + static int xgmac_mdio_write(struct mii_bus *bus, int phy_id, int regnum, u16 value) + { +- struct tgec_mdio_controller __iomem *regs = bus->priv; +- uint16_t dev_addr = regnum >> 16; ++ struct mdio_fsl_priv *priv = (struct mdio_fsl_priv *)bus->priv; ++ struct tgec_mdio_controller __iomem *regs = priv->mdio_base; ++ uint16_t dev_addr; ++ u32 mdio_ctl, mdio_stat; + int ret; ++ bool endian = priv->is_little_endian; ++ ++ mdio_stat = xgmac_read32(®s->mdio_stat, endian); ++ if (regnum & MII_ADDR_C45) { ++ /* Clause 45 (ie 10G) */ ++ dev_addr = (regnum >> 16) & 0x1f; ++ mdio_stat |= MDIO_STAT_ENC; ++ } else { ++ /* Clause 22 (ie 1G) */ ++ dev_addr = regnum & 0x1f; ++ mdio_stat &= ~MDIO_STAT_ENC; ++ } + +- /* Setup the MII Mgmt clock speed */ +- out_be32(®s->mdio_stat, MDIO_STAT_CLKDIV(100)); ++ xgmac_write32(mdio_stat, ®s->mdio_stat, endian); + +- ret = xgmac_wait_until_free(&bus->dev, regs); ++ ret = xgmac_wait_until_free(&bus->dev, regs, endian); + if (ret) + return ret; + + /* Set the port and dev addr */ +- out_be32(®s->mdio_ctl, +- MDIO_CTL_PORT_ADDR(phy_id) | MDIO_CTL_DEV_ADDR(dev_addr)); ++ mdio_ctl = MDIO_CTL_PORT_ADDR(phy_id) | MDIO_CTL_DEV_ADDR(dev_addr); ++ xgmac_write32(mdio_ctl, ®s->mdio_ctl, endian); + + /* Set the register address */ +- out_be32(®s->mdio_addr, regnum & 0xffff); ++ if (regnum & MII_ADDR_C45) { ++ xgmac_write32(regnum & 0xffff, ®s->mdio_addr, endian); + +- ret = xgmac_wait_until_free(&bus->dev, regs); +- if (ret) +- return ret; ++ ret = xgmac_wait_until_free(&bus->dev, regs, endian); ++ if (ret) ++ return ret; ++ } + + /* Write the value to the register */ +- out_be32(®s->mdio_data, MDIO_DATA(value)); ++ xgmac_write32(MDIO_DATA(value), ®s->mdio_data, endian); + +- ret = xgmac_wait_until_done(&bus->dev, regs); ++ ret = xgmac_wait_until_done(&bus->dev, regs, endian); + if (ret) + return ret; + +@@ -129,74 +181,70 @@ static int xgmac_mdio_write(struct mii_bus *bus, int phy_id, int regnum, u16 val + */ + static int xgmac_mdio_read(struct mii_bus *bus, int phy_id, int regnum) + { +- struct tgec_mdio_controller __iomem *regs = bus->priv; +- uint16_t dev_addr = regnum >> 16; ++ struct mdio_fsl_priv *priv = (struct mdio_fsl_priv *)bus->priv; ++ struct tgec_mdio_controller __iomem *regs = priv->mdio_base; ++ uint16_t dev_addr; ++ uint32_t mdio_stat; + uint32_t mdio_ctl; + uint16_t value; + int ret; ++ bool endian = priv->is_little_endian; ++ ++ mdio_stat = xgmac_read32(®s->mdio_stat, endian); ++ if (regnum & MII_ADDR_C45) { ++ dev_addr = (regnum >> 16) & 0x1f; ++ mdio_stat |= MDIO_STAT_ENC; ++ } else { ++ dev_addr = regnum & 0x1f; ++ mdio_stat &= ~MDIO_STAT_ENC; ++ } + +- /* Setup the MII Mgmt clock speed */ +- out_be32(®s->mdio_stat, MDIO_STAT_CLKDIV(100)); ++ xgmac_write32(mdio_stat, ®s->mdio_stat, endian); + +- ret = xgmac_wait_until_free(&bus->dev, regs); ++ ret = xgmac_wait_until_free(&bus->dev, regs, endian); + if (ret) + return ret; + + /* Set the Port and Device Addrs */ + mdio_ctl = MDIO_CTL_PORT_ADDR(phy_id) | MDIO_CTL_DEV_ADDR(dev_addr); +- out_be32(®s->mdio_ctl, mdio_ctl); ++ xgmac_write32(mdio_ctl, ®s->mdio_ctl, endian); + + /* Set the register address */ +- out_be32(®s->mdio_addr, regnum & 0xffff); ++ if (regnum & MII_ADDR_C45) { ++ xgmac_write32(regnum & 0xffff, ®s->mdio_addr, endian); + +- ret = xgmac_wait_until_free(&bus->dev, regs); +- if (ret) +- return ret; ++ ret = xgmac_wait_until_free(&bus->dev, regs, endian); ++ if (ret) ++ return ret; ++ } + + /* Initiate the read */ +- out_be32(®s->mdio_ctl, mdio_ctl | MDIO_CTL_READ); ++ xgmac_write32(mdio_ctl | MDIO_CTL_READ, ®s->mdio_ctl, endian); + +- ret = xgmac_wait_until_done(&bus->dev, regs); ++ ret = xgmac_wait_until_done(&bus->dev, regs, endian); + if (ret) + return ret; + + /* Return all Fs if nothing was there */ +- if (in_be32(®s->mdio_stat) & MDIO_STAT_RD_ER) { ++ if (xgmac_read32(®s->mdio_stat, endian) & MDIO_STAT_RD_ER) { + dev_err(&bus->dev, + "Error while reading PHY%d reg at %d.%hhu\n", + phy_id, dev_addr, regnum); + return 0xffff; + } + +- value = in_be32(®s->mdio_data) & 0xffff; ++ value = xgmac_read32(®s->mdio_data, endian) & 0xffff; + dev_dbg(&bus->dev, "read %04x\n", value); + + return value; + } + +-/* Reset the MIIM registers, and wait for the bus to free */ +-static int xgmac_mdio_reset(struct mii_bus *bus) +-{ +- struct tgec_mdio_controller __iomem *regs = bus->priv; +- int ret; +- +- mutex_lock(&bus->mdio_lock); +- +- /* Setup the MII Mgmt clock speed */ +- out_be32(®s->mdio_stat, MDIO_STAT_CLKDIV(100)); +- +- ret = xgmac_wait_until_free(&bus->dev, regs); +- +- mutex_unlock(&bus->mdio_lock); +- +- return ret; +-} +- + static int xgmac_mdio_probe(struct platform_device *pdev) + { + struct device_node *np = pdev->dev.of_node; + struct mii_bus *bus; + struct resource res; ++ struct mdio_fsl_priv *priv; + int ret; + + ret = of_address_to_resource(np, 0, &res); +@@ -205,25 +253,30 @@ static int xgmac_mdio_probe(struct platform_device *pdev) + return ret; + } + +- bus = mdiobus_alloc_size(PHY_MAX_ADDR * sizeof(int)); ++ bus = mdiobus_alloc_size(sizeof(struct mdio_fsl_priv)); + if (!bus) + return -ENOMEM; + + bus->name = "Freescale XGMAC MDIO Bus"; + bus->read = xgmac_mdio_read; + bus->write = xgmac_mdio_write; +- bus->reset = xgmac_mdio_reset; +- bus->irq = bus->priv; + bus->parent = &pdev->dev; + snprintf(bus->id, MII_BUS_ID_SIZE, "%llx", (unsigned long long)res.start); + + /* Set the PHY base address */ +- bus->priv = of_iomap(np, 0); +- if (!bus->priv) { ++ priv = bus->priv; ++ priv->mdio_base = of_iomap(np, 0); ++ if (!priv->mdio_base) { + ret = -ENOMEM; + goto err_ioremap; + } + ++ if (of_get_property(pdev->dev.of_node, ++ "little-endian", NULL)) ++ priv->is_little_endian = true; ++ else ++ priv->is_little_endian = false; ++ + ret = of_mdiobus_register(bus, np); + if (ret) { + dev_err(&pdev->dev, "cannot register MDIO bus\n"); +@@ -235,7 +288,7 @@ static int xgmac_mdio_probe(struct platform_device *pdev) + return 0; + + err_registration: +- iounmap(bus->priv); ++ iounmap(priv->mdio_base); + + err_ioremap: + mdiobus_free(bus); +@@ -254,10 +307,13 @@ static int xgmac_mdio_remove(struct platform_device *pdev) + return 0; + } + +-static struct of_device_id xgmac_mdio_match[] = { ++static const struct of_device_id xgmac_mdio_match[] = { + { + .compatible = "fsl,fman-xmdio", + }, ++ { ++ .compatible = "fsl,fman-memac-mdio", ++ }, + {}, + }; + MODULE_DEVICE_TABLE(of, xgmac_mdio_match); +diff --git a/drivers/net/ethernet/intel/igb/e1000_82575.c b/drivers/net/ethernet/intel/igb/e1000_82575.c +index 051ea94..2a04baa 100644 +--- a/drivers/net/ethernet/intel/igb/e1000_82575.c ++++ b/drivers/net/ethernet/intel/igb/e1000_82575.c +@@ -286,6 +286,9 @@ static s32 igb_init_phy_params_82575(struct e1000_hw *hw) + phy->ops.set_d3_lplu_state = igb_set_d3_lplu_state_82580; + phy->ops.force_speed_duplex = igb_phy_force_speed_duplex_m88; + break; ++ case BCM54616_E_PHY_ID: ++ phy->type = e1000_phy_bcm54616; ++ break; + default: + ret_val = -E1000_ERR_PHY; + goto out; +@@ -1550,6 +1553,7 @@ static s32 igb_setup_copper_link_82575(struct e1000_hw *hw) + case e1000_i350: + case e1000_i210: + case e1000_i211: ++ case e1000_i354: + phpm_reg = rd32(E1000_82580_PHY_POWER_MGMT); + phpm_reg &= ~E1000_82580_PM_GO_LINKD; + wr32(E1000_82580_PHY_POWER_MGMT, phpm_reg); +@@ -1593,6 +1597,8 @@ static s32 igb_setup_copper_link_82575(struct e1000_hw *hw) + case e1000_phy_82580: + ret_val = igb_copper_link_setup_82580(hw); + break; ++ case e1000_phy_bcm54616: ++ break; + default: + ret_val = -E1000_ERR_PHY; + break; +diff --git a/drivers/net/ethernet/intel/igb/e1000_defines.h b/drivers/net/ethernet/intel/igb/e1000_defines.h +index 217f813..5322fbf 100644 +--- a/drivers/net/ethernet/intel/igb/e1000_defines.h ++++ b/drivers/net/ethernet/intel/igb/e1000_defines.h +@@ -860,6 +860,7 @@ + #define M88_VENDOR 0x0141 + #define I210_I_PHY_ID 0x01410C00 + #define M88E1543_E_PHY_ID 0x01410EA0 ++#define BCM54616_E_PHY_ID 0x3625D10 + + /* M88E1000 Specific Registers */ + #define M88E1000_PHY_SPEC_CTRL 0x10 /* PHY Specific Control Register */ +diff --git a/drivers/net/ethernet/intel/igb/e1000_hw.h b/drivers/net/ethernet/intel/igb/e1000_hw.h +index 2003b37..d82c96b 100644 +--- a/drivers/net/ethernet/intel/igb/e1000_hw.h ++++ b/drivers/net/ethernet/intel/igb/e1000_hw.h +@@ -128,6 +128,7 @@ enum e1000_phy_type { + e1000_phy_ife, + e1000_phy_82580, + e1000_phy_i210, ++ e1000_phy_bcm54616, + }; + + enum e1000_bus_type { +diff --git a/drivers/net/ethernet/intel/igb/igb_main.c b/drivers/net/ethernet/intel/igb/igb_main.c +index e0f3664..013c1f1 100644 +--- a/drivers/net/ethernet/intel/igb/igb_main.c ++++ b/drivers/net/ethernet/intel/igb/igb_main.c +@@ -108,6 +108,7 @@ static const struct pci_device_id igb_pci_tbl[] = { + { PCI_VDEVICE(INTEL, E1000_DEV_ID_82575EB_COPPER), board_82575 }, + { PCI_VDEVICE(INTEL, E1000_DEV_ID_82575EB_FIBER_SERDES), board_82575 }, + { PCI_VDEVICE(INTEL, E1000_DEV_ID_82575GB_QUAD_COPPER), board_82575 }, ++ { PCI_VDEVICE(INTEL, E1000_DEV_ID_I354_SGMII), board_82575 }, + /* required last entry */ + {0, } + }; +diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig +index 75472cf..cdc9f8a 100644 +--- a/drivers/net/phy/Kconfig ++++ b/drivers/net/phy/Kconfig +@@ -14,6 +14,11 @@ if PHYLIB + + comment "MII PHY device drivers" + ++config AQUANTIA_PHY ++ tristate "Drivers for the Aquantia PHYs" ++ ---help--- ++ Currently supports the Aquantia AQ1202, AQ2104, AQR105, AQR405 ++ + config AT803X_PHY + tristate "Drivers for Atheros AT803X PHYs" + ---help--- +@@ -60,6 +65,11 @@ config VITESSE_PHY + ---help--- + Currently supports the vsc8244 + ++config TERANETICS_PHY ++ tristate "Drivers for the Teranetics PHYs" ++ ---help--- ++ Currently supports the Teranetics TN2020 ++ + config SMSC_PHY + tristate "Drivers for SMSC PHYs" + ---help--- +@@ -119,8 +129,8 @@ config MICREL_PHY + Supports the KSZ9021, VSC8201, KS8001 PHYs. + + config FIXED_PHY +- bool "Driver for MDIO Bus/PHY emulation with fixed speed/link PHYs" +- depends on PHYLIB=y ++ tristate "Driver for MDIO Bus/PHY emulation with fixed speed/link PHYs" ++ depends on PHYLIB + ---help--- + Adds the platform "fixed" MDIO Bus to cover the boards that use + PHYs that are not connected to the real MDIO bus. +@@ -202,6 +212,11 @@ config MDIO_BUS_MUX_MMIOREG + the FPGA's registers. + + Currently, only 8-bit registers are supported. ++config FSL_10GBASE_KR ++ tristate "Support for 10GBASE-KR on Freescale XFI interface" ++ depends on OF_MDIO ++ help ++ This module provides a driver for Freescale XFI's 10GBASE-KR. + + config MDIO_BCM_UNIMAC + tristate "Broadcom UniMAC MDIO bus controller" +diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile +index eb3b18b..8ad4ac6 100644 +--- a/drivers/net/phy/Makefile ++++ b/drivers/net/phy/Makefile +@@ -3,12 +3,14 @@ + libphy-objs := phy.o phy_device.o mdio_bus.o + + obj-$(CONFIG_PHYLIB) += libphy.o ++obj-$(CONFIG_AQUANTIA_PHY) += aquantia.o + obj-$(CONFIG_MARVELL_PHY) += marvell.o + obj-$(CONFIG_DAVICOM_PHY) += davicom.o + obj-$(CONFIG_CICADA_PHY) += cicada.o + obj-$(CONFIG_LXT_PHY) += lxt.o + obj-$(CONFIG_QSEMI_PHY) += qsemi.o + obj-$(CONFIG_SMSC_PHY) += smsc.o ++obj-$(CONFIG_TERANETICS_PHY) += teranetics.o + obj-$(CONFIG_VITESSE_PHY) += vitesse.o + obj-$(CONFIG_BROADCOM_PHY) += broadcom.o + obj-$(CONFIG_BCM63XX_PHY) += bcm63xx.o +@@ -17,7 +19,7 @@ obj-$(CONFIG_BCM87XX_PHY) += bcm87xx.o + obj-$(CONFIG_ICPLUS_PHY) += icplus.o + obj-$(CONFIG_REALTEK_PHY) += realtek.o + obj-$(CONFIG_LSI_ET1011C_PHY) += et1011c.o +-obj-$(CONFIG_FIXED_PHY) += fixed.o ++obj-$(CONFIG_FIXED_PHY) += fixed_phy.o + obj-$(CONFIG_MDIO_BITBANG) += mdio-bitbang.o + obj-$(CONFIG_MDIO_GPIO) += mdio-gpio.o + obj-$(CONFIG_NATIONAL_PHY) += national.o +@@ -31,6 +33,7 @@ obj-$(CONFIG_AMD_PHY) += amd.o + obj-$(CONFIG_MDIO_BUS_MUX) += mdio-mux.o + obj-$(CONFIG_MDIO_BUS_MUX_GPIO) += mdio-mux-gpio.o + obj-$(CONFIG_MDIO_BUS_MUX_MMIOREG) += mdio-mux-mmioreg.o ++obj-$(CONFIG_FSL_10GBASE_KR) += fsl_10gkr.o + obj-$(CONFIG_MDIO_SUN4I) += mdio-sun4i.o + obj-$(CONFIG_MDIO_MOXART) += mdio-moxart.o + obj-$(CONFIG_AMD_XGBE_PHY) += amd-xgbe-phy.o +diff --git a/drivers/net/phy/aquantia.c b/drivers/net/phy/aquantia.c +new file mode 100644 +index 0000000..d6111af +--- /dev/null ++++ b/drivers/net/phy/aquantia.c +@@ -0,0 +1,201 @@ ++/* ++ * Driver for Aquantia PHY ++ * ++ * Author: Shaohui Xie ++ * ++ * Copyright 2015 Freescale Semiconductor, Inc. ++ * ++ * This file is licensed under the terms of the GNU General Public License ++ * version 2. This program is licensed "as is" without any warranty of any ++ * kind, whether express or implied. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define PHY_ID_AQ1202 0x03a1b445 ++#define PHY_ID_AQ2104 0x03a1b460 ++#define PHY_ID_AQR105 0x03a1b4a2 ++#define PHY_ID_AQR405 0x03a1b4b0 ++ ++#define PHY_AQUANTIA_FEATURES (SUPPORTED_10000baseT_Full | \ ++ SUPPORTED_1000baseT_Full | \ ++ SUPPORTED_100baseT_Full | \ ++ PHY_DEFAULT_FEATURES) ++ ++static int aquantia_config_aneg(struct phy_device *phydev) ++{ ++ phydev->supported = PHY_AQUANTIA_FEATURES; ++ phydev->advertising = phydev->supported; ++ ++ return 0; ++} ++ ++static int aquantia_aneg_done(struct phy_device *phydev) ++{ ++ int reg; ++ ++ reg = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1); ++ return (reg < 0) ? reg : (reg & BMSR_ANEGCOMPLETE); ++} ++ ++static int aquantia_config_intr(struct phy_device *phydev) ++{ ++ int err; ++ ++ if (phydev->interrupts == PHY_INTERRUPT_ENABLED) { ++ err = phy_write_mmd(phydev, MDIO_MMD_AN, 0xd401, 1); ++ if (err < 0) ++ return err; ++ ++ err = phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xff00, 1); ++ if (err < 0) ++ return err; ++ ++ err = phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xff01, 0x1001); ++ } else { ++ err = phy_write_mmd(phydev, MDIO_MMD_AN, 0xd401, 0); ++ if (err < 0) ++ return err; ++ ++ err = phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xff00, 0); ++ if (err < 0) ++ return err; ++ ++ err = phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xff01, 0); ++ } ++ ++ return err; ++} ++ ++static int aquantia_ack_interrupt(struct phy_device *phydev) ++{ ++ int reg; ++ ++ reg = phy_read_mmd(phydev, MDIO_MMD_AN, 0xcc01); ++ return (reg < 0) ? reg : 0; ++} ++ ++static int aquantia_read_status(struct phy_device *phydev) ++{ ++ int reg; ++ ++ reg = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1); ++ reg = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1); ++ if (reg & MDIO_STAT1_LSTATUS) ++ phydev->link = 1; ++ else ++ phydev->link = 0; ++ ++ reg = phy_read_mmd(phydev, MDIO_MMD_AN, 0xc800); ++ mdelay(10); ++ reg = phy_read_mmd(phydev, MDIO_MMD_AN, 0xc800); ++ ++ switch (reg) { ++ case 0x9: ++ phydev->speed = SPEED_2500; ++ break; ++ case 0x5: ++ phydev->speed = SPEED_1000; ++ break; ++ case 0x3: ++ phydev->speed = SPEED_100; ++ break; ++ case 0x7: ++ default: ++ phydev->speed = SPEED_10000; ++ break; ++ } ++ phydev->duplex = DUPLEX_FULL; ++ ++ return 0; ++} ++ ++static struct phy_driver aquantia_driver[] = { ++{ ++ .phy_id = PHY_ID_AQ1202, ++ .phy_id_mask = 0xfffffff0, ++ .name = "Aquantia AQ1202", ++ .features = PHY_AQUANTIA_FEATURES, ++ .flags = PHY_HAS_INTERRUPT, ++ .aneg_done = aquantia_aneg_done, ++ .config_aneg = aquantia_config_aneg, ++ .config_intr = aquantia_config_intr, ++ .ack_interrupt = aquantia_ack_interrupt, ++ .read_status = aquantia_read_status, ++ .driver = { .owner = THIS_MODULE,}, ++}, ++{ ++ .phy_id = PHY_ID_AQ2104, ++ .phy_id_mask = 0xfffffff0, ++ .name = "Aquantia AQ2104", ++ .features = PHY_AQUANTIA_FEATURES, ++ .flags = PHY_HAS_INTERRUPT, ++ .aneg_done = aquantia_aneg_done, ++ .config_aneg = aquantia_config_aneg, ++ .config_intr = aquantia_config_intr, ++ .ack_interrupt = aquantia_ack_interrupt, ++ .read_status = aquantia_read_status, ++ .driver = { .owner = THIS_MODULE,}, ++}, ++{ ++ .phy_id = PHY_ID_AQR105, ++ .phy_id_mask = 0xfffffff0, ++ .name = "Aquantia AQR105", ++ .features = PHY_AQUANTIA_FEATURES, ++ .flags = PHY_HAS_INTERRUPT, ++ .aneg_done = aquantia_aneg_done, ++ .config_aneg = aquantia_config_aneg, ++ .config_intr = aquantia_config_intr, ++ .ack_interrupt = aquantia_ack_interrupt, ++ .read_status = aquantia_read_status, ++ .driver = { .owner = THIS_MODULE,}, ++}, ++{ ++ .phy_id = PHY_ID_AQR405, ++ .phy_id_mask = 0xfffffff0, ++ .name = "Aquantia AQR405", ++ .features = PHY_AQUANTIA_FEATURES, ++ .flags = PHY_HAS_INTERRUPT, ++ .aneg_done = aquantia_aneg_done, ++ .config_aneg = aquantia_config_aneg, ++ .config_intr = aquantia_config_intr, ++ .ack_interrupt = aquantia_ack_interrupt, ++ .read_status = aquantia_read_status, ++ .driver = { .owner = THIS_MODULE,}, ++}, ++}; ++ ++static int __init aquantia_init(void) ++{ ++ return phy_drivers_register(aquantia_driver, ++ ARRAY_SIZE(aquantia_driver)); ++} ++ ++static void __exit aquantia_exit(void) ++{ ++ return phy_drivers_unregister(aquantia_driver, ++ ARRAY_SIZE(aquantia_driver)); ++} ++ ++module_init(aquantia_init); ++module_exit(aquantia_exit); ++ ++static struct mdio_device_id __maybe_unused aquantia_tbl[] = { ++ { PHY_ID_AQ1202, 0xfffffff0 }, ++ { PHY_ID_AQ2104, 0xfffffff0 }, ++ { PHY_ID_AQR105, 0xfffffff0 }, ++ { PHY_ID_AQR405, 0xfffffff0 }, ++ { } ++}; ++ ++MODULE_DEVICE_TABLE(mdio, aquantia_tbl); ++ ++MODULE_DESCRIPTION("Aquantia PHY driver"); ++MODULE_AUTHOR("Shaohui Xie "); ++MODULE_LICENSE("GPL v2"); +diff --git a/drivers/net/phy/at803x.c b/drivers/net/phy/at803x.c +index fdc1b41..a4f0886 100644 +--- a/drivers/net/phy/at803x.c ++++ b/drivers/net/phy/at803x.c +@@ -307,6 +307,8 @@ static struct phy_driver at803x_driver[] = { + .flags = PHY_HAS_INTERRUPT, + .config_aneg = genphy_config_aneg, + .read_status = genphy_read_status, ++ .ack_interrupt = at803x_ack_interrupt, ++ .config_intr = at803x_config_intr, + .driver = { + .owner = THIS_MODULE, + }, +@@ -326,6 +328,8 @@ static struct phy_driver at803x_driver[] = { + .flags = PHY_HAS_INTERRUPT, + .config_aneg = genphy_config_aneg, + .read_status = genphy_read_status, ++ .ack_interrupt = at803x_ack_interrupt, ++ .config_intr = at803x_config_intr, + .driver = { + .owner = THIS_MODULE, + }, +diff --git a/drivers/net/phy/fixed.c b/drivers/net/phy/fixed.c +deleted file mode 100644 +index 47872ca..0000000 +--- a/drivers/net/phy/fixed.c ++++ /dev/null +@@ -1,336 +0,0 @@ +-/* +- * Fixed MDIO bus (MDIO bus emulation with fixed PHYs) +- * +- * Author: Vitaly Bordug +- * Anton Vorontsov +- * +- * Copyright (c) 2006-2007 MontaVista Software, Inc. +- * +- * This program is free software; you can redistribute it and/or modify it +- * under the terms of the GNU General Public License as published by the +- * Free Software Foundation; either version 2 of the License, or (at your +- * option) any later version. +- */ +- +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +- +-#define MII_REGS_NUM 29 +- +-struct fixed_mdio_bus { +- int irqs[PHY_MAX_ADDR]; +- struct mii_bus *mii_bus; +- struct list_head phys; +-}; +- +-struct fixed_phy { +- int addr; +- u16 regs[MII_REGS_NUM]; +- struct phy_device *phydev; +- struct fixed_phy_status status; +- int (*link_update)(struct net_device *, struct fixed_phy_status *); +- struct list_head node; +-}; +- +-static struct platform_device *pdev; +-static struct fixed_mdio_bus platform_fmb = { +- .phys = LIST_HEAD_INIT(platform_fmb.phys), +-}; +- +-static int fixed_phy_update_regs(struct fixed_phy *fp) +-{ +- u16 bmsr = BMSR_ANEGCAPABLE; +- u16 bmcr = 0; +- u16 lpagb = 0; +- u16 lpa = 0; +- +- if (fp->status.duplex) { +- bmcr |= BMCR_FULLDPLX; +- +- switch (fp->status.speed) { +- case 1000: +- bmsr |= BMSR_ESTATEN; +- bmcr |= BMCR_SPEED1000; +- lpagb |= LPA_1000FULL; +- break; +- case 100: +- bmsr |= BMSR_100FULL; +- bmcr |= BMCR_SPEED100; +- lpa |= LPA_100FULL; +- break; +- case 10: +- bmsr |= BMSR_10FULL; +- lpa |= LPA_10FULL; +- break; +- default: +- pr_warn("fixed phy: unknown speed\n"); +- return -EINVAL; +- } +- } else { +- switch (fp->status.speed) { +- case 1000: +- bmsr |= BMSR_ESTATEN; +- bmcr |= BMCR_SPEED1000; +- lpagb |= LPA_1000HALF; +- break; +- case 100: +- bmsr |= BMSR_100HALF; +- bmcr |= BMCR_SPEED100; +- lpa |= LPA_100HALF; +- break; +- case 10: +- bmsr |= BMSR_10HALF; +- lpa |= LPA_10HALF; +- break; +- default: +- pr_warn("fixed phy: unknown speed\n"); +- return -EINVAL; +- } +- } +- +- if (fp->status.link) +- bmsr |= BMSR_LSTATUS | BMSR_ANEGCOMPLETE; +- +- if (fp->status.pause) +- lpa |= LPA_PAUSE_CAP; +- +- if (fp->status.asym_pause) +- lpa |= LPA_PAUSE_ASYM; +- +- fp->regs[MII_PHYSID1] = 0; +- fp->regs[MII_PHYSID2] = 0; +- +- fp->regs[MII_BMSR] = bmsr; +- fp->regs[MII_BMCR] = bmcr; +- fp->regs[MII_LPA] = lpa; +- fp->regs[MII_STAT1000] = lpagb; +- +- return 0; +-} +- +-static int fixed_mdio_read(struct mii_bus *bus, int phy_addr, int reg_num) +-{ +- struct fixed_mdio_bus *fmb = bus->priv; +- struct fixed_phy *fp; +- +- if (reg_num >= MII_REGS_NUM) +- return -1; +- +- /* We do not support emulating Clause 45 over Clause 22 register reads +- * return an error instead of bogus data. +- */ +- switch (reg_num) { +- case MII_MMD_CTRL: +- case MII_MMD_DATA: +- return -1; +- default: +- break; +- } +- +- list_for_each_entry(fp, &fmb->phys, node) { +- if (fp->addr == phy_addr) { +- /* Issue callback if user registered it. */ +- if (fp->link_update) { +- fp->link_update(fp->phydev->attached_dev, +- &fp->status); +- fixed_phy_update_regs(fp); +- } +- return fp->regs[reg_num]; +- } +- } +- +- return 0xFFFF; +-} +- +-static int fixed_mdio_write(struct mii_bus *bus, int phy_addr, int reg_num, +- u16 val) +-{ +- return 0; +-} +- +-/* +- * If something weird is required to be done with link/speed, +- * network driver is able to assign a function to implement this. +- * May be useful for PHY's that need to be software-driven. +- */ +-int fixed_phy_set_link_update(struct phy_device *phydev, +- int (*link_update)(struct net_device *, +- struct fixed_phy_status *)) +-{ +- struct fixed_mdio_bus *fmb = &platform_fmb; +- struct fixed_phy *fp; +- +- if (!link_update || !phydev || !phydev->bus) +- return -EINVAL; +- +- list_for_each_entry(fp, &fmb->phys, node) { +- if (fp->addr == phydev->addr) { +- fp->link_update = link_update; +- fp->phydev = phydev; +- return 0; +- } +- } +- +- return -ENOENT; +-} +-EXPORT_SYMBOL_GPL(fixed_phy_set_link_update); +- +-int fixed_phy_add(unsigned int irq, int phy_addr, +- struct fixed_phy_status *status) +-{ +- int ret; +- struct fixed_mdio_bus *fmb = &platform_fmb; +- struct fixed_phy *fp; +- +- fp = kzalloc(sizeof(*fp), GFP_KERNEL); +- if (!fp) +- return -ENOMEM; +- +- memset(fp->regs, 0xFF, sizeof(fp->regs[0]) * MII_REGS_NUM); +- +- fmb->irqs[phy_addr] = irq; +- +- fp->addr = phy_addr; +- fp->status = *status; +- +- ret = fixed_phy_update_regs(fp); +- if (ret) +- goto err_regs; +- +- list_add_tail(&fp->node, &fmb->phys); +- +- return 0; +- +-err_regs: +- kfree(fp); +- return ret; +-} +-EXPORT_SYMBOL_GPL(fixed_phy_add); +- +-void fixed_phy_del(int phy_addr) +-{ +- struct fixed_mdio_bus *fmb = &platform_fmb; +- struct fixed_phy *fp, *tmp; +- +- list_for_each_entry_safe(fp, tmp, &fmb->phys, node) { +- if (fp->addr == phy_addr) { +- list_del(&fp->node); +- kfree(fp); +- return; +- } +- } +-} +-EXPORT_SYMBOL_GPL(fixed_phy_del); +- +-static int phy_fixed_addr; +-static DEFINE_SPINLOCK(phy_fixed_addr_lock); +- +-struct phy_device *fixed_phy_register(unsigned int irq, +- struct fixed_phy_status *status, +- struct device_node *np) +-{ +- struct fixed_mdio_bus *fmb = &platform_fmb; +- struct phy_device *phy; +- int phy_addr; +- int ret; +- +- /* Get the next available PHY address, up to PHY_MAX_ADDR */ +- spin_lock(&phy_fixed_addr_lock); +- if (phy_fixed_addr == PHY_MAX_ADDR) { +- spin_unlock(&phy_fixed_addr_lock); +- return ERR_PTR(-ENOSPC); +- } +- phy_addr = phy_fixed_addr++; +- spin_unlock(&phy_fixed_addr_lock); +- +- ret = fixed_phy_add(PHY_POLL, phy_addr, status); +- if (ret < 0) +- return ERR_PTR(ret); +- +- phy = get_phy_device(fmb->mii_bus, phy_addr, false); +- if (!phy || IS_ERR(phy)) { +- fixed_phy_del(phy_addr); +- return ERR_PTR(-EINVAL); +- } +- +- of_node_get(np); +- phy->dev.of_node = np; +- +- ret = phy_device_register(phy); +- if (ret) { +- phy_device_free(phy); +- of_node_put(np); +- fixed_phy_del(phy_addr); +- return ERR_PTR(ret); +- } +- +- return phy; +-} +- +-static int __init fixed_mdio_bus_init(void) +-{ +- struct fixed_mdio_bus *fmb = &platform_fmb; +- int ret; +- +- pdev = platform_device_register_simple("Fixed MDIO bus", 0, NULL, 0); +- if (IS_ERR(pdev)) { +- ret = PTR_ERR(pdev); +- goto err_pdev; +- } +- +- fmb->mii_bus = mdiobus_alloc(); +- if (fmb->mii_bus == NULL) { +- ret = -ENOMEM; +- goto err_mdiobus_reg; +- } +- +- snprintf(fmb->mii_bus->id, MII_BUS_ID_SIZE, "fixed-0"); +- fmb->mii_bus->name = "Fixed MDIO Bus"; +- fmb->mii_bus->priv = fmb; +- fmb->mii_bus->parent = &pdev->dev; +- fmb->mii_bus->read = &fixed_mdio_read; +- fmb->mii_bus->write = &fixed_mdio_write; +- fmb->mii_bus->irq = fmb->irqs; +- +- ret = mdiobus_register(fmb->mii_bus); +- if (ret) +- goto err_mdiobus_alloc; +- +- return 0; +- +-err_mdiobus_alloc: +- mdiobus_free(fmb->mii_bus); +-err_mdiobus_reg: +- platform_device_unregister(pdev); +-err_pdev: +- return ret; +-} +-module_init(fixed_mdio_bus_init); +- +-static void __exit fixed_mdio_bus_exit(void) +-{ +- struct fixed_mdio_bus *fmb = &platform_fmb; +- struct fixed_phy *fp, *tmp; +- +- mdiobus_unregister(fmb->mii_bus); +- mdiobus_free(fmb->mii_bus); +- platform_device_unregister(pdev); +- +- list_for_each_entry_safe(fp, tmp, &fmb->phys, node) { +- list_del(&fp->node); +- kfree(fp); +- } +-} +-module_exit(fixed_mdio_bus_exit); +- +-MODULE_DESCRIPTION("Fixed MDIO bus (MDIO bus emulation with fixed PHYs)"); +-MODULE_AUTHOR("Vitaly Bordug"); +-MODULE_LICENSE("GPL"); +diff --git a/drivers/net/phy/fixed_phy.c b/drivers/net/phy/fixed_phy.c +new file mode 100644 +index 0000000..88b8194 +--- /dev/null ++++ b/drivers/net/phy/fixed_phy.c +@@ -0,0 +1,370 @@ ++/* ++ * Fixed MDIO bus (MDIO bus emulation with fixed PHYs) ++ * ++ * Author: Vitaly Bordug ++ * Anton Vorontsov ++ * ++ * Copyright (c) 2006-2007 MontaVista Software, Inc. ++ * ++ * This program is free software; you can redistribute it and/or modify it ++ * under the terms of the GNU General Public License as published by the ++ * Free Software Foundation; either version 2 of the License, or (at your ++ * option) any later version. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define MII_REGS_NUM 29 ++ ++struct fixed_mdio_bus { ++ int irqs[PHY_MAX_ADDR]; ++ struct mii_bus *mii_bus; ++ struct list_head phys; ++}; ++ ++struct fixed_phy { ++ int addr; ++ u16 regs[MII_REGS_NUM]; ++ struct phy_device *phydev; ++ struct fixed_phy_status status; ++ int (*link_update)(struct net_device *, struct fixed_phy_status *); ++ struct list_head node; ++}; ++ ++static struct platform_device *pdev; ++static struct fixed_mdio_bus platform_fmb = { ++ .phys = LIST_HEAD_INIT(platform_fmb.phys), ++}; ++ ++static int fixed_phy_update_regs(struct fixed_phy *fp) ++{ ++ u16 bmsr = BMSR_ANEGCAPABLE; ++ u16 bmcr = 0; ++ u16 lpagb = 0; ++ u16 lpa = 0; ++ ++ if (fp->status.duplex) { ++ bmcr |= BMCR_FULLDPLX; ++ ++ switch (fp->status.speed) { ++ case 10000: ++ break; ++ case 1000: ++ bmsr |= BMSR_ESTATEN; ++ bmcr |= BMCR_SPEED1000; ++ lpagb |= LPA_1000FULL; ++ break; ++ case 100: ++ bmsr |= BMSR_100FULL; ++ bmcr |= BMCR_SPEED100; ++ lpa |= LPA_100FULL; ++ break; ++ case 10: ++ bmsr |= BMSR_10FULL; ++ lpa |= LPA_10FULL; ++ break; ++ default: ++ pr_warn("fixed phy: unknown speed\n"); ++ return -EINVAL; ++ } ++ } else { ++ switch (fp->status.speed) { ++ case 10000: ++ break; ++ case 1000: ++ bmsr |= BMSR_ESTATEN; ++ bmcr |= BMCR_SPEED1000; ++ lpagb |= LPA_1000HALF; ++ break; ++ case 100: ++ bmsr |= BMSR_100HALF; ++ bmcr |= BMCR_SPEED100; ++ lpa |= LPA_100HALF; ++ break; ++ case 10: ++ bmsr |= BMSR_10HALF; ++ lpa |= LPA_10HALF; ++ break; ++ default: ++ pr_warn("fixed phy: unknown speed\n"); ++ return -EINVAL; ++ } ++ } ++ ++ if (fp->status.link) ++ bmsr |= BMSR_LSTATUS | BMSR_ANEGCOMPLETE; ++ ++ if (fp->status.pause) ++ lpa |= LPA_PAUSE_CAP; ++ ++ if (fp->status.asym_pause) ++ lpa |= LPA_PAUSE_ASYM; ++ ++ fp->regs[MII_PHYSID1] = 0; ++ fp->regs[MII_PHYSID2] = 0; ++ ++ fp->regs[MII_BMSR] = bmsr; ++ fp->regs[MII_BMCR] = bmcr; ++ fp->regs[MII_LPA] = lpa; ++ fp->regs[MII_STAT1000] = lpagb; ++ ++ return 0; ++} ++ ++static int fixed_mdio_read(struct mii_bus *bus, int phy_addr, int reg_num) ++{ ++ struct fixed_mdio_bus *fmb = bus->priv; ++ struct fixed_phy *fp; ++ ++ if (reg_num >= MII_REGS_NUM) ++ return -1; ++ ++ /* We do not support emulating Clause 45 over Clause 22 register reads ++ * return an error instead of bogus data. ++ */ ++ switch (reg_num) { ++ case MII_MMD_CTRL: ++ case MII_MMD_DATA: ++ return -1; ++ default: ++ break; ++ } ++ ++ list_for_each_entry(fp, &fmb->phys, node) { ++ if (fp->addr == phy_addr) { ++ /* Issue callback if user registered it. */ ++ if (fp->link_update) { ++ fp->link_update(fp->phydev->attached_dev, ++ &fp->status); ++ fixed_phy_update_regs(fp); ++ } ++ return fp->regs[reg_num]; ++ } ++ } ++ ++ return 0xFFFF; ++} ++ ++static int fixed_mdio_write(struct mii_bus *bus, int phy_addr, int reg_num, ++ u16 val) ++{ ++ return 0; ++} ++ ++/* ++ * If something weird is required to be done with link/speed, ++ * network driver is able to assign a function to implement this. ++ * May be useful for PHY's that need to be software-driven. ++ */ ++int fixed_phy_set_link_update(struct phy_device *phydev, ++ int (*link_update)(struct net_device *, ++ struct fixed_phy_status *)) ++{ ++ struct fixed_mdio_bus *fmb = &platform_fmb; ++ struct fixed_phy *fp; ++ ++ if (!phydev || !phydev->bus) ++ return -EINVAL; ++ ++ list_for_each_entry(fp, &fmb->phys, node) { ++ if (fp->addr == phydev->addr) { ++ fp->link_update = link_update; ++ fp->phydev = phydev; ++ return 0; ++ } ++ } ++ ++ return -ENOENT; ++} ++EXPORT_SYMBOL_GPL(fixed_phy_set_link_update); ++ ++int fixed_phy_update_state(struct phy_device *phydev, ++ const struct fixed_phy_status *status, ++ const struct fixed_phy_status *changed) ++{ ++ struct fixed_mdio_bus *fmb = &platform_fmb; ++ struct fixed_phy *fp; ++ ++ if (!phydev || !phydev->bus) ++ return -EINVAL; ++ ++ list_for_each_entry(fp, &fmb->phys, node) { ++ if (fp->addr == phydev->addr) { ++#define _UPD(x) if (changed->x) \ ++ fp->status.x = status->x ++ _UPD(link); ++ _UPD(speed); ++ _UPD(duplex); ++ _UPD(pause); ++ _UPD(asym_pause); ++#undef _UPD ++ fixed_phy_update_regs(fp); ++ return 0; ++ } ++ } ++ ++ return -ENOENT; ++} ++EXPORT_SYMBOL(fixed_phy_update_state); ++ ++int fixed_phy_add(unsigned int irq, int phy_addr, ++ struct fixed_phy_status *status) ++{ ++ int ret; ++ struct fixed_mdio_bus *fmb = &platform_fmb; ++ struct fixed_phy *fp; ++ ++ fp = kzalloc(sizeof(*fp), GFP_KERNEL); ++ if (!fp) ++ return -ENOMEM; ++ ++ memset(fp->regs, 0xFF, sizeof(fp->regs[0]) * MII_REGS_NUM); ++ ++ fmb->irqs[phy_addr] = irq; ++ ++ fp->addr = phy_addr; ++ fp->status = *status; ++ ++ ret = fixed_phy_update_regs(fp); ++ if (ret) ++ goto err_regs; ++ ++ list_add_tail(&fp->node, &fmb->phys); ++ ++ return 0; ++ ++err_regs: ++ kfree(fp); ++ return ret; ++} ++EXPORT_SYMBOL_GPL(fixed_phy_add); ++ ++void fixed_phy_del(int phy_addr) ++{ ++ struct fixed_mdio_bus *fmb = &platform_fmb; ++ struct fixed_phy *fp, *tmp; ++ ++ list_for_each_entry_safe(fp, tmp, &fmb->phys, node) { ++ if (fp->addr == phy_addr) { ++ list_del(&fp->node); ++ kfree(fp); ++ return; ++ } ++ } ++} ++EXPORT_SYMBOL_GPL(fixed_phy_del); ++ ++static int phy_fixed_addr; ++static DEFINE_SPINLOCK(phy_fixed_addr_lock); ++ ++struct phy_device *fixed_phy_register(unsigned int irq, ++ struct fixed_phy_status *status, ++ struct device_node *np) ++{ ++ struct fixed_mdio_bus *fmb = &platform_fmb; ++ struct phy_device *phy; ++ int phy_addr; ++ int ret; ++ ++ /* Get the next available PHY address, up to PHY_MAX_ADDR */ ++ spin_lock(&phy_fixed_addr_lock); ++ if (phy_fixed_addr == PHY_MAX_ADDR) { ++ spin_unlock(&phy_fixed_addr_lock); ++ return ERR_PTR(-ENOSPC); ++ } ++ phy_addr = phy_fixed_addr++; ++ spin_unlock(&phy_fixed_addr_lock); ++ ++ ret = fixed_phy_add(PHY_POLL, phy_addr, status); ++ if (ret < 0) ++ return ERR_PTR(ret); ++ ++ phy = get_phy_device(fmb->mii_bus, phy_addr, false); ++ if (!phy || IS_ERR(phy)) { ++ fixed_phy_del(phy_addr); ++ return ERR_PTR(-EINVAL); ++ } ++ ++ of_node_get(np); ++ phy->dev.of_node = np; ++ ++ ret = phy_device_register(phy); ++ if (ret) { ++ phy_device_free(phy); ++ of_node_put(np); ++ fixed_phy_del(phy_addr); ++ return ERR_PTR(ret); ++ } ++ ++ return phy; ++} ++EXPORT_SYMBOL_GPL(fixed_phy_register); ++ ++static int __init fixed_mdio_bus_init(void) ++{ ++ struct fixed_mdio_bus *fmb = &platform_fmb; ++ int ret; ++ ++ pdev = platform_device_register_simple("Fixed MDIO bus", 0, NULL, 0); ++ if (IS_ERR(pdev)) { ++ ret = PTR_ERR(pdev); ++ goto err_pdev; ++ } ++ ++ fmb->mii_bus = mdiobus_alloc(); ++ if (fmb->mii_bus == NULL) { ++ ret = -ENOMEM; ++ goto err_mdiobus_reg; ++ } ++ ++ snprintf(fmb->mii_bus->id, MII_BUS_ID_SIZE, "fixed-0"); ++ fmb->mii_bus->name = "Fixed MDIO Bus"; ++ fmb->mii_bus->priv = fmb; ++ fmb->mii_bus->parent = &pdev->dev; ++ fmb->mii_bus->read = &fixed_mdio_read; ++ fmb->mii_bus->write = &fixed_mdio_write; ++ fmb->mii_bus->irq = fmb->irqs; ++ ++ ret = mdiobus_register(fmb->mii_bus); ++ if (ret) ++ goto err_mdiobus_alloc; ++ ++ return 0; ++ ++err_mdiobus_alloc: ++ mdiobus_free(fmb->mii_bus); ++err_mdiobus_reg: ++ platform_device_unregister(pdev); ++err_pdev: ++ return ret; ++} ++module_init(fixed_mdio_bus_init); ++ ++static void __exit fixed_mdio_bus_exit(void) ++{ ++ struct fixed_mdio_bus *fmb = &platform_fmb; ++ struct fixed_phy *fp, *tmp; ++ ++ mdiobus_unregister(fmb->mii_bus); ++ mdiobus_free(fmb->mii_bus); ++ platform_device_unregister(pdev); ++ ++ list_for_each_entry_safe(fp, tmp, &fmb->phys, node) { ++ list_del(&fp->node); ++ kfree(fp); ++ } ++} ++module_exit(fixed_mdio_bus_exit); ++ ++MODULE_DESCRIPTION("Fixed MDIO bus (MDIO bus emulation with fixed PHYs)"); ++MODULE_AUTHOR("Vitaly Bordug"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/net/phy/fsl_10gkr.c b/drivers/net/phy/fsl_10gkr.c +new file mode 100644 +index 0000000..3713726 +--- /dev/null ++++ b/drivers/net/phy/fsl_10gkr.c +@@ -0,0 +1,1467 @@ ++/* Freescale XFI 10GBASE-KR driver. ++ * Author: Shaohui Xie ++ * ++ * Copyright 2014 Freescale Semiconductor, Inc. ++ * ++ * Licensed under the GPL-2 or later. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define FSL_XFI_PCS_PHY_ID 0x7C000012 ++#define FSL_XFI_PCS_PHY_ID2 0x0083e400 ++ ++/* Freescale XFI PCS MMD */ ++#define FSL_XFI_PMD 0x1 ++#define FSL_XFI_PCS 0x3 ++#define FSL_XFI_AN 0x7 ++#define FSL_XFI_VS1 0x1e ++ ++/* Freescale XFI PMD registers */ ++#define FSL_XFI_PMD_CTRL 0x0 ++#define FSL_XFI_KR_PMD_CTRL 0x0096 ++#define FSL_XFI_KR_PMD_STATUS 0x0097 ++#define FSL_XFI_KR_LP_CU 0x0098 ++#define FSL_XFI_KR_LP_STATUS 0x0099 ++#define FSL_XFI_KR_LD_CU 0x009a ++#define FSL_XFI_KR_LD_STATUS 0x009b ++ ++/* PMD define */ ++#define PMD_RESET 0x1 ++#define PMD_STATUS_SUP_STAT 0x4 ++#define PMD_STATUS_FRAME_LOCK 0x2 ++#define TRAIN_EN 0x3 ++#define TRAIN_DISABLE 0x1 ++#define RX_STAT 0x1 ++ ++/* Freescale XFI PCS registers */ ++#define FSL_XFI_PCS_CTRL 0x0 ++#define FSL_XFI_PCS_STATUS 0x1 ++ ++/* Freescale XFI Auto-Negotiation Registers */ ++#define FSL_XFI_AN_CTRL 0x0000 ++#define FSL_XFI_LNK_STATUS 0x0001 ++#define FSL_XFI_AN_AD_1 0x0011 ++#define FSL_XFI_BP_STATUS 0x0030 ++ ++#define XFI_AN_AD1 0x85 ++#define XF_AN_RESTART 0x1200 ++#define XFI_AN_LNK_STAT_UP 0x4 ++ ++/* Freescale XFI Vendor-Specific 1 Registers */ ++#define FSL_XFI_PCS_INTR_EVENT 0x0002 ++#define FSL_XFI_PCS_INTR_MASK 0x0003 ++#define FSL_XFI_AN_INTR_EVENT 0x0004 ++#define FSL_XFI_AN_INTR_MASK 0x0005 ++#define FSL_XFI_LT_INTR_EVENT 0x0006 ++#define FSL_XFI_LT_INTR_MASK 0x0007 ++ ++/* C(-1) */ ++#define BIN_M1 0 ++/* C(1) */ ++#define BIN_LONG 1 ++#define BIN_M1_SEL 6 ++#define BIN_Long_SEL 7 ++#define CDR_SEL_MASK 0x00070000 ++#define BIN_SNAPSHOT_NUM 5 ++#define BIN_M1_THRESHOLD 3 ++#define BIN_LONG_THRESHOLD 2 ++ ++#define PRE_COE_MASK 0x03c00000 ++#define POST_COE_MASK 0x001f0000 ++#define ZERO_COE_MASK 0x00003f00 ++#define PRE_COE_SHIFT 22 ++#define POST_COE_SHIFT 16 ++#define ZERO_COE_SHIFT 8 ++ ++#define PRE_COE_MAX 0x0 ++#define PRE_COE_MIN 0x8 ++#define POST_COE_MAX 0x0 ++#define POST_COE_MIN 0x10 ++#define ZERO_COE_MAX 0x30 ++#define ZERO_COE_MIN 0x0 ++ ++#define TECR0_INIT 0x24200000 ++#define RATIO_PREQ 0x3 ++#define RATIO_PST1Q 0xd ++#define RATIO_EQ 0x20 ++ ++#define GCR1_CTL_SNP_START_MASK 0x00002000 ++#define GCR1_SNP_START_MASK 0x00000040 ++#define RECR1_SNP_DONE_MASK 0x00000004 ++#define RECR1_CTL_SNP_DONE_MASK 0x00000002 ++#define TCSR1_SNP_DATA_MASK 0x0000ffc0 ++#define TCSR1_SNP_DATA_SHIFT 6 ++#define TCSR1_EQ_SNPBIN_SIGN_MASK 0x100 ++ ++#define RECR1_GAINK2_MASK 0x0f000000 ++#define RECR1_GAINK2_SHIFT 24 ++#define RECR1_GAINK3_MASK 0x000f0000 ++#define RECR1_GAINK3_SHIFT 16 ++#define RECR1_OFFSET_MASK 0x00003f80 ++#define RECR1_OFFSET_SHIFT 7 ++#define RECR1_BLW_MASK 0x00000f80 ++#define RECR1_BLW_SHIFT 7 ++#define EYE_CTRL_SHIFT 12 ++#define BASE_WAND_SHIFT 10 ++ ++#define XGKR_TIMEOUT 1050 ++#define AN_ABILITY_MASK 0x9 ++#define AN_10GKR_MASK 0x8 ++#define LT_10GKR_MASK 0x4 ++#define TRAIN_FAIL 0x8 ++ ++#define INCREMENT 1 ++#define DECREMENT 2 ++#define TIMEOUT_LONG 3 ++#define TIMEOUT_M1 3 ++ ++#define RX_READY_MASK 0x8000 ++#define PRESET_MASK 0x2000 ++#define INIT_MASK 0x1000 ++#define COP1_MASK 0x30 ++#define COP1_SHIFT 4 ++#define COZ_MASK 0xc ++#define COZ_SHIFT 2 ++#define COM1_MASK 0x3 ++#define COM1_SHIFT 0 ++#define REQUEST_MASK 0x3f ++#define LD_ALL_MASK (PRESET_MASK | INIT_MASK | \ ++ COP1_MASK | COZ_MASK | COM1_MASK) ++ ++#define FSL_SERDES_INSTANCE1_BASE 0xffe0ea000 ++#define FSL_SERDES_INSTANCE2_BASE 0xffe0eb000 ++#define FSL_LANE_A_BASE 0x800 ++#define FSL_LANE_B_BASE 0x840 ++#define FSL_LANE_C_BASE 0x880 ++#define FSL_LANE_D_BASE 0x8C0 ++#define FSL_LANE_E_BASE 0x900 ++#define FSL_LANE_F_BASE 0x940 ++#define FSL_LANE_G_BASE 0x980 ++#define FSL_LANE_H_BASE 0x9C0 ++#define GCR0_RESET_MASK 0x600000 ++ ++#define NEW_ALGORITHM_TRAIN_TX ++#ifdef NEW_ALGORITHM_TRAIN_TX ++#define FORCE_INC_COP1_NUMBER 0 ++#define FORCE_INC_COM1_NUMBER 1 ++#endif ++ ++enum fsl_xgkr_driver { ++ FSL_XGKR_REV1, ++ FSL_XGKR_REV2, ++ FSL_XGKR_INV ++}; ++ ++static struct phy_driver fsl_xgkr_driver[FSL_XGKR_INV]; ++ ++enum coe_filed { ++ COE_COP1, ++ COE_COZ, ++ COE_COM ++}; ++ ++enum coe_update { ++ COE_NOTUPDATED, ++ COE_UPDATED, ++ COE_MIN, ++ COE_MAX, ++ COE_INV ++}; ++ ++enum serdes_inst { ++ SERDES_1, ++ SERDES_2, ++ SERDES_MAX ++}; ++ ++enum lane_inst { ++ LANE_A, ++ LANE_B, ++ LANE_C, ++ LANE_D, ++ LANE_E, ++ LANE_F, ++ LANE_G, ++ LANE_H, ++ LANE_MAX ++}; ++ ++struct serdes_map { ++ const char *serdes_name; ++ unsigned long serdes_base; ++}; ++ ++struct lane_map { ++ const char *lane_name; ++ unsigned long lane_base; ++}; ++ ++const struct serdes_map s_map[SERDES_MAX] = { ++ {"serdes-1", FSL_SERDES_INSTANCE1_BASE}, ++ {"serdes-2", FSL_SERDES_INSTANCE2_BASE} ++}; ++ ++const struct lane_map l_map[LANE_MAX] = { ++ {"lane-a", FSL_LANE_A_BASE}, ++ {"lane-b", FSL_LANE_B_BASE}, ++ {"lane-c", FSL_LANE_C_BASE}, ++ {"lane-d", FSL_LANE_D_BASE}, ++ {"lane-e", FSL_LANE_E_BASE}, ++ {"lane-f", FSL_LANE_F_BASE}, ++ {"lane-g", FSL_LANE_G_BASE}, ++ {"lane-h", FSL_LANE_H_BASE} ++}; ++ ++struct per_lane_ctrl_status { ++ __be32 gcr0; /* 0x.000 - General Control Register 0 */ ++ __be32 gcr1; /* 0x.004 - General Control Register 1 */ ++ __be32 gcr2; /* 0x.008 - General Control Register 2 */ ++ __be32 resv1; /* 0x.00C - Reserved */ ++ __be32 recr0; /* 0x.010 - Receive Equalization Control Register 0 */ ++ __be32 recr1; /* 0x.014 - Receive Equalization Control Register 1 */ ++ __be32 tecr0; /* 0x.018 - Transmit Equalization Control Register 0 */ ++ __be32 resv2; /* 0x.01C - Reserved */ ++ __be32 tlcr0; /* 0x.020 - TTL Control Register 0 */ ++ __be32 tlcr1; /* 0x.024 - TTL Control Register 1 */ ++ __be32 tlcr2; /* 0x.028 - TTL Control Register 2 */ ++ __be32 tlcr3; /* 0x.02C - TTL Control Register 3 */ ++ __be32 tcsr0; /* 0x.030 - Test Control/Status Register 0 */ ++ __be32 tcsr1; /* 0x.034 - Test Control/Status Register 1 */ ++ __be32 tcsr2; /* 0x.038 - Test Control/Status Register 2 */ ++ __be32 tcsr3; /* 0x.03C - Test Control/Status Register 3 */ ++}; ++ ++struct training_state_machine { ++ bool bin_m1_late_early; ++ bool bin_long_late_early; ++ bool bin_m1_stop; ++ bool bin_long_stop; ++ bool tx_complete; ++ bool an_ok; ++ bool link_up; ++ bool running; ++ bool sent_init; ++ int m1_min_max_cnt; ++ int long_min_max_cnt; ++#ifdef NEW_ALGORITHM_TRAIN_TX ++ int pre_inc; ++ int post_inc; ++#endif ++}; ++ ++struct fsl_xgkr_inst { ++ void *reg_base; ++ struct mii_bus *bus; ++ struct phy_device *phydev; ++ struct training_state_machine t_s_m; ++ u32 ld_update; ++ u32 ld_status; ++ u32 ratio_preq; ++ u32 ratio_pst1q; ++ u32 adpt_eq; ++}; ++ ++struct fsl_xgkr_wk { ++ struct work_struct xgkr_wk; ++ struct list_head xgkr_list; ++ struct fsl_xgkr_inst *xgkr_inst; ++}; ++ ++LIST_HEAD(fsl_xgkr_list); ++ ++static struct timer_list xgkr_timer; ++static int fire_timer; ++static struct workqueue_struct *xgkr_wq; ++ ++static void init_state_machine(struct training_state_machine *s_m) ++{ ++ s_m->bin_m1_late_early = true; ++ s_m->bin_long_late_early = false; ++ s_m->bin_m1_stop = false; ++ s_m->bin_long_stop = false; ++ s_m->tx_complete = false; ++ s_m->an_ok = false; ++ s_m->link_up = false; ++ s_m->running = false; ++ s_m->sent_init = false; ++ s_m->m1_min_max_cnt = 0; ++ s_m->long_min_max_cnt = 0; ++#ifdef NEW_ALGORITHM_TRAIN_TX ++ s_m->pre_inc = FORCE_INC_COM1_NUMBER; ++ s_m->post_inc = FORCE_INC_COP1_NUMBER; ++#endif ++} ++ ++void tune_tecr0(struct fsl_xgkr_inst *inst) ++{ ++ struct per_lane_ctrl_status *reg_base; ++ u32 val; ++ ++ reg_base = (struct per_lane_ctrl_status *)inst->reg_base; ++ ++ val = TECR0_INIT | ++ inst->adpt_eq << ZERO_COE_SHIFT | ++ inst->ratio_preq << PRE_COE_SHIFT | ++ inst->ratio_pst1q << POST_COE_SHIFT; ++ ++ /* reset the lane */ ++ iowrite32be(ioread32be(®_base->gcr0) & ~GCR0_RESET_MASK, ++ ®_base->gcr0); ++ udelay(1); ++ iowrite32be(val, ®_base->tecr0); ++ udelay(1); ++ /* unreset the lane */ ++ iowrite32be(ioread32be(®_base->gcr0) | GCR0_RESET_MASK, ++ ®_base->gcr0); ++ udelay(1); ++} ++ ++static void start_lt(struct phy_device *phydev) ++{ ++ phy_write_mmd(phydev, FSL_XFI_PMD, FSL_XFI_KR_PMD_CTRL, TRAIN_EN); ++} ++ ++static void stop_lt(struct phy_device *phydev) ++{ ++ phy_write_mmd(phydev, FSL_XFI_PMD, FSL_XFI_KR_PMD_CTRL, TRAIN_DISABLE); ++} ++ ++static void reset_gcr0(struct fsl_xgkr_inst *inst) ++{ ++ struct per_lane_ctrl_status *reg_base; ++ ++ reg_base = (struct per_lane_ctrl_status *)inst->reg_base; ++ ++ iowrite32be(ioread32be(®_base->gcr0) & ~GCR0_RESET_MASK, ++ ®_base->gcr0); ++ udelay(1); ++ iowrite32be(ioread32be(®_base->gcr0) | GCR0_RESET_MASK, ++ ®_base->gcr0); ++ udelay(1); ++} ++ ++static void reset_lt(struct phy_device *phydev) ++{ ++ phy_write_mmd(phydev, FSL_XFI_PMD, FSL_XFI_PMD_CTRL, PMD_RESET); ++ phy_write_mmd(phydev, FSL_XFI_PMD, FSL_XFI_KR_PMD_CTRL, TRAIN_DISABLE); ++ phy_write_mmd(phydev, FSL_XFI_PMD, FSL_XFI_KR_LD_CU, 0); ++ phy_write_mmd(phydev, FSL_XFI_PMD, FSL_XFI_KR_LD_STATUS, 0); ++ phy_write_mmd(phydev, FSL_XFI_PMD, FSL_XFI_KR_PMD_STATUS, 0); ++ phy_write_mmd(phydev, FSL_XFI_PMD, FSL_XFI_KR_LP_CU, 0); ++ phy_write_mmd(phydev, FSL_XFI_PMD, FSL_XFI_KR_LP_STATUS, 0); ++} ++ ++static void start_an(struct phy_device *phydev) ++{ ++ reset_lt(phydev); ++ phy_write_mmd(phydev, FSL_XFI_AN, FSL_XFI_AN_AD_1, XFI_AN_AD1); ++ phy_write_mmd(phydev, FSL_XFI_AN, FSL_XFI_AN_CTRL, XF_AN_RESTART); ++} ++ ++static void ld_coe_status(struct fsl_xgkr_inst *inst) ++{ ++ phy_write_mmd(inst->phydev, FSL_XFI_PMD, ++ FSL_XFI_KR_LD_STATUS, inst->ld_status); ++} ++ ++static void ld_coe_update(struct fsl_xgkr_inst *inst) ++{ ++ phy_write_mmd(inst->phydev, FSL_XFI_PMD, ++ FSL_XFI_KR_LD_CU, inst->ld_update); ++} ++ ++static void init_inst(struct fsl_xgkr_inst *inst, int reset) ++{ ++ if (reset) { ++ inst->ratio_preq = RATIO_PREQ; ++ inst->ratio_pst1q = RATIO_PST1Q; ++ inst->adpt_eq = RATIO_EQ; ++ tune_tecr0(inst); ++ } ++ ++ inst->ld_status &= RX_READY_MASK; ++ ld_coe_status(inst); ++ ++ /* init state machine */ ++ init_state_machine(&inst->t_s_m); ++ ++ inst->ld_update = 0; ++ ld_coe_update(inst); ++ ++ inst->ld_status &= ~RX_READY_MASK; ++ ld_coe_status(inst); ++} ++ ++#ifdef NEW_ALGORITHM_TRAIN_TX ++static int get_median_gaink2(u32 *reg) ++{ ++ int gaink2_snap_shot[BIN_SNAPSHOT_NUM]; ++ u32 rx_eq_snp; ++ struct per_lane_ctrl_status *reg_base; ++ int timeout; ++ int i, j, tmp, pos; ++ ++ reg_base = (struct per_lane_ctrl_status *)reg; ++ ++ for (i = 0; i < BIN_SNAPSHOT_NUM; i++) { ++ /* wait RECR1_CTL_SNP_DONE_MASK has cleared */ ++ timeout = 100; ++ while (ioread32be(®_base->recr1) & ++ RECR1_CTL_SNP_DONE_MASK) { ++ udelay(1); ++ timeout--; ++ if (timeout == 0) ++ break; ++ } ++ ++ /* start snap shot */ ++ iowrite32be((ioread32be(®_base->gcr1) | ++ GCR1_CTL_SNP_START_MASK), ++ ®_base->gcr1); ++ ++ /* wait for SNP done */ ++ timeout = 100; ++ while (!(ioread32be(®_base->recr1) & ++ RECR1_CTL_SNP_DONE_MASK)) { ++ udelay(1); ++ timeout--; ++ if (timeout == 0) ++ break; ++ } ++ ++ /* read and save the snap shot */ ++ rx_eq_snp = ioread32be(®_base->recr1); ++ gaink2_snap_shot[i] = (rx_eq_snp & RECR1_GAINK2_MASK) >> ++ RECR1_GAINK2_SHIFT; ++ ++ /* terminate the snap shot by setting GCR1[REQ_CTL_SNP] */ ++ iowrite32be((ioread32be(®_base->gcr1) & ++ ~GCR1_CTL_SNP_START_MASK), ++ ®_base->gcr1); ++ } ++ ++ /* get median of the 5 snap shot */ ++ for (i = 0; i < BIN_SNAPSHOT_NUM - 1; i++) { ++ tmp = gaink2_snap_shot[i]; ++ pos = i; ++ for (j = i + 1; j < BIN_SNAPSHOT_NUM; j++) { ++ if (gaink2_snap_shot[j] < tmp) { ++ tmp = gaink2_snap_shot[j]; ++ pos = j; ++ } ++ } ++ ++ gaink2_snap_shot[pos] = gaink2_snap_shot[i]; ++ gaink2_snap_shot[i] = tmp; ++ } ++ ++ return gaink2_snap_shot[2]; ++} ++#endif ++ ++static bool is_bin_early(int bin_sel, void __iomem *reg) ++{ ++ bool early = false; ++ int bin_snap_shot[BIN_SNAPSHOT_NUM]; ++ int i, negative_count = 0; ++ struct per_lane_ctrl_status *reg_base; ++ int timeout; ++ ++ reg_base = (struct per_lane_ctrl_status *)reg; ++ ++ for (i = 0; i < BIN_SNAPSHOT_NUM; i++) { ++ /* wait RECR1_SNP_DONE_MASK has cleared */ ++ timeout = 100; ++ while ((ioread32be(®_base->recr1) & RECR1_SNP_DONE_MASK)) { ++ udelay(1); ++ timeout--; ++ if (timeout == 0) ++ break; ++ } ++ ++ /* set TCSR1[CDR_SEL] to BinM1/BinLong */ ++ if (bin_sel == BIN_M1) { ++ iowrite32be((ioread32be(®_base->tcsr1) & ++ ~CDR_SEL_MASK) | BIN_M1_SEL, ++ ®_base->tcsr1); ++ } else { ++ iowrite32be((ioread32be(®_base->tcsr1) & ++ ~CDR_SEL_MASK) | BIN_Long_SEL, ++ ®_base->tcsr1); ++ } ++ ++ /* start snap shot */ ++ iowrite32be(ioread32be(®_base->gcr1) | GCR1_SNP_START_MASK, ++ ®_base->gcr1); ++ ++ /* wait for SNP done */ ++ timeout = 100; ++ while (!(ioread32be(®_base->recr1) & RECR1_SNP_DONE_MASK)) { ++ udelay(1); ++ timeout--; ++ if (timeout == 0) ++ break; ++ } ++ ++ /* read and save the snap shot */ ++ bin_snap_shot[i] = (ioread32be(®_base->tcsr1) & ++ TCSR1_SNP_DATA_MASK) >> TCSR1_SNP_DATA_SHIFT; ++ if (bin_snap_shot[i] & TCSR1_EQ_SNPBIN_SIGN_MASK) ++ negative_count++; ++ ++ /* terminate the snap shot by setting GCR1[REQ_CTL_SNP] */ ++ iowrite32be(ioread32be(®_base->gcr1) & ~GCR1_SNP_START_MASK, ++ ®_base->gcr1); ++ } ++ ++ if (((bin_sel == BIN_M1) && negative_count > BIN_M1_THRESHOLD) || ++ ((bin_sel == BIN_LONG && negative_count > BIN_LONG_THRESHOLD))) { ++ early = true; ++ } ++ ++ return early; ++} ++ ++static void train_tx(struct fsl_xgkr_inst *inst) ++{ ++ struct phy_device *phydev = inst->phydev; ++ struct training_state_machine *s_m = &inst->t_s_m; ++ bool bin_m1_early, bin_long_early; ++ u32 lp_status, old_ld_update; ++ u32 status_cop1, status_coz, status_com1; ++ u32 req_cop1, req_coz, req_com1, req_preset, req_init; ++ u32 temp; ++#ifdef NEW_ALGORITHM_TRAIN_TX ++ u32 median_gaink2; ++#endif ++ ++recheck: ++ if (s_m->bin_long_stop && s_m->bin_m1_stop) { ++ s_m->tx_complete = true; ++ inst->ld_status |= RX_READY_MASK; ++ ld_coe_status(inst); ++ /* tell LP we are ready */ ++ phy_write_mmd(phydev, FSL_XFI_PMD, ++ FSL_XFI_KR_PMD_STATUS, RX_STAT); ++ return; ++ } ++ ++ /* We start by checking the current LP status. If we got any responses, ++ * we can clear up the appropriate update request so that the ++ * subsequent code may easily issue new update requests if needed. ++ */ ++ lp_status = phy_read_mmd(phydev, FSL_XFI_PMD, FSL_XFI_KR_LP_STATUS) & ++ REQUEST_MASK; ++ status_cop1 = (lp_status & COP1_MASK) >> COP1_SHIFT; ++ status_coz = (lp_status & COZ_MASK) >> COZ_SHIFT; ++ status_com1 = (lp_status & COM1_MASK) >> COM1_SHIFT; ++ ++ old_ld_update = inst->ld_update; ++ req_cop1 = (old_ld_update & COP1_MASK) >> COP1_SHIFT; ++ req_coz = (old_ld_update & COZ_MASK) >> COZ_SHIFT; ++ req_com1 = (old_ld_update & COM1_MASK) >> COM1_SHIFT; ++ req_preset = old_ld_update & PRESET_MASK; ++ req_init = old_ld_update & INIT_MASK; ++ ++ /* IEEE802.3-2008, 72.6.10.2.3.1 ++ * We may clear PRESET when all coefficients show UPDATED or MAX. ++ */ ++ if (req_preset) { ++ if ((status_cop1 == COE_UPDATED || status_cop1 == COE_MAX) && ++ (status_coz == COE_UPDATED || status_coz == COE_MAX) && ++ (status_com1 == COE_UPDATED || status_com1 == COE_MAX)) { ++ inst->ld_update &= ~PRESET_MASK; ++ } ++ } ++ ++ /* IEEE802.3-2008, 72.6.10.2.3.2 ++ * We may clear INITIALIZE when no coefficients show NOT UPDATED. ++ */ ++ if (req_init) { ++ if (status_cop1 != COE_NOTUPDATED && ++ status_coz != COE_NOTUPDATED && ++ status_com1 != COE_NOTUPDATED) { ++ inst->ld_update &= ~INIT_MASK; ++ } ++ } ++ ++ /* IEEE802.3-2008, 72.6.10.2.3.2 ++ * we send initialize to the other side to ensure default settings ++ * for the LP. Naturally, we should do this only once. ++ */ ++ if (!s_m->sent_init) { ++ if (!lp_status && !(old_ld_update & (LD_ALL_MASK))) { ++ inst->ld_update |= INIT_MASK; ++ s_m->sent_init = true; ++ } ++ } ++ ++ /* IEEE802.3-2008, 72.6.10.2.3.3 ++ * We set coefficient requests to HOLD when we get the information ++ * about any updates On clearing our prior response, we also update ++ * our internal status. ++ */ ++ if (status_cop1 != COE_NOTUPDATED) { ++ if (req_cop1) { ++ inst->ld_update &= ~COP1_MASK; ++#ifdef NEW_ALGORITHM_TRAIN_TX ++ if (s_m->post_inc) { ++ if (req_cop1 == INCREMENT && ++ status_cop1 == COE_MAX) { ++ s_m->post_inc = 0; ++ s_m->bin_long_stop = true; ++ s_m->bin_m1_stop = true; ++ } else { ++ s_m->post_inc -= 1; ++ } ++ ++ ld_coe_update(inst); ++ goto recheck; ++ } ++#endif ++ if ((req_cop1 == DECREMENT && status_cop1 == COE_MIN) || ++ (req_cop1 == INCREMENT && status_cop1 == COE_MAX)) { ++ s_m->long_min_max_cnt++; ++ if (s_m->long_min_max_cnt >= TIMEOUT_LONG) { ++ s_m->bin_long_stop = true; ++ ld_coe_update(inst); ++ goto recheck; ++ } ++ } ++ } ++ } ++ ++ if (status_coz != COE_NOTUPDATED) { ++ if (req_coz) ++ inst->ld_update &= ~COZ_MASK; ++ } ++ ++ if (status_com1 != COE_NOTUPDATED) { ++ if (req_com1) { ++ inst->ld_update &= ~COM1_MASK; ++#ifdef NEW_ALGORITHM_TRAIN_TX ++ if (s_m->pre_inc) { ++ if (req_com1 == INCREMENT && ++ status_com1 == COE_MAX) ++ s_m->pre_inc = 0; ++ else ++ s_m->pre_inc -= 1; ++ ++ ld_coe_update(inst); ++ goto recheck; ++ } ++#endif ++ /* Stop If we have reached the limit for a parameter. */ ++ if ((req_com1 == DECREMENT && status_com1 == COE_MIN) || ++ (req_com1 == INCREMENT && status_com1 == COE_MAX)) { ++ s_m->m1_min_max_cnt++; ++ if (s_m->m1_min_max_cnt >= TIMEOUT_M1) { ++ s_m->bin_m1_stop = true; ++ ld_coe_update(inst); ++ goto recheck; ++ } ++ } ++ } ++ } ++ ++ if (old_ld_update != inst->ld_update) { ++ ld_coe_update(inst); ++ /* Redo these status checks and updates until we have no more ++ * changes, to speed up the overall process. ++ */ ++ goto recheck; ++ } ++ ++ /* Do nothing if we have pending request. */ ++ if ((req_coz || req_com1 || req_cop1)) ++ return; ++ else if (lp_status) ++ /* No pending request but LP status was not reverted to ++ * not updated. ++ */ ++ return; ++ ++#ifdef NEW_ALGORITHM_TRAIN_TX ++ if (!(inst->ld_update & (PRESET_MASK | INIT_MASK))) { ++ if (s_m->pre_inc) { ++ inst->ld_update = INCREMENT << COM1_SHIFT; ++ ld_coe_update(inst); ++ return; ++ } ++ ++ if (status_cop1 != COE_MAX) { ++ median_gaink2 = get_median_gaink2(inst->reg_base); ++ if (median_gaink2 == 0xf) { ++ s_m->post_inc = 1; ++ } else { ++ /* Gaink2 median lower than "F" */ ++ s_m->bin_m1_stop = true; ++ s_m->bin_long_stop = true; ++ goto recheck; ++ } ++ } else { ++ /* C1 MAX */ ++ s_m->bin_m1_stop = true; ++ s_m->bin_long_stop = true; ++ goto recheck; ++ } ++ ++ if (s_m->post_inc) { ++ inst->ld_update = INCREMENT << COP1_SHIFT; ++ ld_coe_update(inst); ++ return; ++ } ++ } ++#endif ++ ++ /* snapshot and select bin */ ++ bin_m1_early = is_bin_early(BIN_M1, inst->reg_base); ++ bin_long_early = is_bin_early(BIN_LONG, inst->reg_base); ++ ++ if (!s_m->bin_m1_stop && !s_m->bin_m1_late_early && bin_m1_early) { ++ s_m->bin_m1_stop = true; ++ goto recheck; ++ } ++ ++ if (!s_m->bin_long_stop && ++ s_m->bin_long_late_early && !bin_long_early) { ++ s_m->bin_long_stop = true; ++ goto recheck; ++ } ++ ++ /* IEEE802.3-2008, 72.6.10.2.3.3 ++ * We only request coefficient updates when no PRESET/INITIALIZE is ++ * pending! We also only request coefficient updates when the ++ * corresponding status is NOT UPDATED and nothing is pending. ++ */ ++ if (!(inst->ld_update & (PRESET_MASK | INIT_MASK))) { ++ if (!s_m->bin_long_stop) { ++ /* BinM1 correction means changing COM1 */ ++ if (!status_com1 && !(inst->ld_update & COM1_MASK)) { ++ /* Avoid BinM1Late by requesting an ++ * immediate decrement. ++ */ ++ if (!bin_m1_early) { ++ /* request decrement c(-1) */ ++ temp = DECREMENT << COM1_SHIFT; ++ inst->ld_update |= temp; ++ ld_coe_update(inst); ++ s_m->bin_m1_late_early = bin_m1_early; ++ return; ++ } ++ } ++ ++ /* BinLong correction means changing COP1 */ ++ if (!status_cop1 && !(inst->ld_update & COP1_MASK)) { ++ /* Locate BinLong transition point (if any) ++ * while avoiding BinM1Late. ++ */ ++ if (bin_long_early) { ++ /* request increment c(1) */ ++ temp = INCREMENT << COP1_SHIFT; ++ inst->ld_update |= temp; ++ } else { ++ /* request decrement c(1) */ ++ temp = DECREMENT << COP1_SHIFT; ++ inst->ld_update |= temp; ++ } ++ ++ ld_coe_update(inst); ++ s_m->bin_long_late_early = bin_long_early; ++ } ++ /* We try to finish BinLong before we do BinM1 */ ++ return; ++ } ++ ++ if (!s_m->bin_m1_stop) { ++ /* BinM1 correction means changing COM1 */ ++ if (!status_com1 && !(inst->ld_update & COM1_MASK)) { ++ /* Locate BinM1 transition point (if any) */ ++ if (bin_m1_early) { ++ /* request increment c(-1) */ ++ temp = INCREMENT << COM1_SHIFT; ++ inst->ld_update |= temp; ++ } else { ++ /* request decrement c(-1) */ ++ temp = DECREMENT << COM1_SHIFT; ++ inst->ld_update |= temp; ++ } ++ ++ ld_coe_update(inst); ++ s_m->bin_m1_late_early = bin_m1_early; ++ } ++ } ++ } ++} ++ ++static int check_an_link(struct phy_device *phydev) ++{ ++ int val; ++ int timeout = 100; ++ ++ while (timeout--) { ++ val = phy_read_mmd(phydev, FSL_XFI_AN, FSL_XFI_LNK_STATUS); ++ if (val & XFI_AN_LNK_STAT_UP) ++ return 1; ++ usleep_range(100, 500); ++ } ++ ++ return 0; ++} ++ ++static int is_link_training_fail(struct phy_device *phydev) ++{ ++ int val; ++ ++ val = phy_read_mmd(phydev, FSL_XFI_PMD, FSL_XFI_KR_PMD_STATUS); ++ if (!(val & TRAIN_FAIL) && (val & RX_STAT)) { ++ /* check LNK_STAT for sure */ ++ if (check_an_link(phydev)) ++ return 0; ++ return 1; ++ } ++ return 1; ++} ++ ++static int check_rx(struct phy_device *phydev) ++{ ++ return phy_read_mmd(phydev, FSL_XFI_PMD, FSL_XFI_KR_LP_STATUS) & ++ RX_READY_MASK; ++} ++ ++/* Coefficient values have hardware restrictions */ ++static int is_ld_valid(u32 *ld_coe) ++{ ++ u32 ratio_pst1q = *ld_coe; ++ u32 adpt_eq = *(ld_coe + 1); ++ u32 ratio_preq = *(ld_coe + 2); ++ ++ if ((ratio_pst1q + adpt_eq + ratio_preq) > 48) ++ return 0; ++ ++ if (((ratio_pst1q + adpt_eq + ratio_preq) * 4) >= ++ ((adpt_eq - ratio_pst1q - ratio_preq) * 17)) ++ return 0; ++ ++ if (ratio_preq > ratio_pst1q) ++ return 0; ++ ++ if (ratio_preq > 8) ++ return 0; ++ ++ if (adpt_eq < 26) ++ return 0; ++ ++ if (ratio_pst1q > 16) ++ return 0; ++ ++ return 1; ++} ++ ++#define VAL_INVALID 0xff ++ ++static const u32 preq_table[] = {0x0, 0x1, 0x3, 0x5, ++ 0x7, 0x9, 0xb, 0xc, VAL_INVALID}; ++static const u32 pst1q_table[] = {0x0, 0x1, 0x3, 0x5, ++ 0x7, 0x9, 0xb, 0xd, 0xf, 0x10, VAL_INVALID}; ++ ++static int is_value_allowed(const u32 *val_table, u32 val) ++{ ++ int i; ++ ++ for (i = 0;; i++) { ++ if (*(val_table + i) == VAL_INVALID) ++ return 0; ++ if (*(val_table + i) == val) ++ return 1; ++ } ++} ++ ++static int inc_dec(struct fsl_xgkr_inst *inst, int field, int request) ++{ ++ u32 ld_limit[3], ld_coe[3], step[3]; ++ ++ ld_coe[0] = inst->ratio_pst1q; ++ ld_coe[1] = inst->adpt_eq; ++ ld_coe[2] = inst->ratio_preq; ++ ++ /* Information specific to the Freescale SerDes for 10GBase-KR: ++ * Incrementing C(+1) means *decrementing* RATIO_PST1Q ++ * Incrementing C(0) means incrementing ADPT_EQ ++ * Incrementing C(-1) means *decrementing* RATIO_PREQ ++ */ ++ step[0] = -1; ++ step[1] = 1; ++ step[2] = -1; ++ ++ switch (request) { ++ case INCREMENT: ++ ld_limit[0] = POST_COE_MAX; ++ ld_limit[1] = ZERO_COE_MAX; ++ ld_limit[2] = PRE_COE_MAX; ++ if (ld_coe[field] != ld_limit[field]) ++ ld_coe[field] += step[field]; ++ else ++ /* MAX */ ++ return 2; ++ break; ++ case DECREMENT: ++ ld_limit[0] = POST_COE_MIN; ++ ld_limit[1] = ZERO_COE_MIN; ++ ld_limit[2] = PRE_COE_MIN; ++ if (ld_coe[field] != ld_limit[field]) ++ ld_coe[field] -= step[field]; ++ else ++ /* MIN */ ++ return 1; ++ break; ++ default: ++ break; ++ } ++ ++ if (is_ld_valid(ld_coe)) { ++ /* accept new ld */ ++ inst->ratio_pst1q = ld_coe[0]; ++ inst->adpt_eq = ld_coe[1]; ++ inst->ratio_preq = ld_coe[2]; ++ /* only some values for preq and pst1q can be used. ++ * for preq: 0x0, 0x1, 0x3, 0x5, 0x7, 0x9, 0xb, 0xc. ++ * for pst1q: 0x0, 0x1, 0x3, 0x5, 0x7, 0x9, 0xb, 0xd, 0xf, 0x10. ++ */ ++ if (!is_value_allowed((const u32 *)&preq_table, ld_coe[2])) { ++ dev_dbg(&inst->phydev->dev, ++ "preq skipped value: %d.\n", ld_coe[2]); ++ return 0; ++ } ++ ++ if (!is_value_allowed((const u32 *)&pst1q_table, ld_coe[0])) { ++ dev_dbg(&inst->phydev->dev, ++ "pst1q skipped value: %d.\n", ld_coe[0]); ++ return 0; ++ } ++ ++ tune_tecr0(inst); ++ } else { ++ if (request == DECREMENT) ++ /* MIN */ ++ return 1; ++ if (request == INCREMENT) ++ /* MAX */ ++ return 2; ++ } ++ ++ return 0; ++} ++ ++static void min_max_updated(struct fsl_xgkr_inst *inst, int field, int new_ld) ++{ ++ u32 ld_coe[] = {COE_UPDATED, COE_MIN, COE_MAX}; ++ u32 mask, val; ++ ++ switch (field) { ++ case COE_COP1: ++ mask = COP1_MASK; ++ val = ld_coe[new_ld] << COP1_SHIFT; ++ break; ++ case COE_COZ: ++ mask = COZ_MASK; ++ val = ld_coe[new_ld] << COZ_SHIFT; ++ break; ++ case COE_COM: ++ mask = COM1_MASK; ++ val = ld_coe[new_ld] << COM1_SHIFT; ++ break; ++ default: ++ return; ++ break; ++ } ++ ++ inst->ld_status &= ~mask; ++ inst->ld_status |= val; ++} ++ ++static void check_request(struct fsl_xgkr_inst *inst, int request) ++{ ++ int cop1_req, coz_req, com_req; ++ int old_status, new_ld_sta; ++ ++ cop1_req = (request & COP1_MASK) >> COP1_SHIFT; ++ coz_req = (request & COZ_MASK) >> COZ_SHIFT; ++ com_req = (request & COM1_MASK) >> COM1_SHIFT; ++ ++ /* IEEE802.3-2008, 72.6.10.2.5 ++ * Ensure we only act on INCREMENT/DECREMENT when we are in NOT UPDATED! ++ */ ++ old_status = inst->ld_status; ++ ++ if (cop1_req && !(inst->ld_status & COP1_MASK)) { ++ new_ld_sta = inc_dec(inst, COE_COP1, cop1_req); ++ min_max_updated(inst, COE_COP1, new_ld_sta); ++ } ++ ++ if (coz_req && !(inst->ld_status & COZ_MASK)) { ++ new_ld_sta = inc_dec(inst, COE_COZ, coz_req); ++ min_max_updated(inst, COE_COZ, new_ld_sta); ++ } ++ ++ if (com_req && !(inst->ld_status & COM1_MASK)) { ++ new_ld_sta = inc_dec(inst, COE_COM, com_req); ++ min_max_updated(inst, COE_COM, new_ld_sta); ++ } ++ ++ if (old_status != inst->ld_status) ++ ld_coe_status(inst); ++ ++} ++ ++static void preset(struct fsl_xgkr_inst *inst) ++{ ++ /* These are all MAX values from the IEEE802.3 perspective! */ ++ inst->ratio_pst1q = POST_COE_MAX; ++ inst->adpt_eq = ZERO_COE_MAX; ++ inst->ratio_preq = PRE_COE_MAX; ++ ++ tune_tecr0(inst); ++ inst->ld_status &= ~(COP1_MASK | COZ_MASK | COM1_MASK); ++ inst->ld_status |= COE_MAX << COP1_SHIFT | ++ COE_MAX << COZ_SHIFT | ++ COE_MAX << COM1_SHIFT; ++ ld_coe_status(inst); ++} ++ ++static void initialize(struct fsl_xgkr_inst *inst) ++{ ++ inst->ratio_preq = RATIO_PREQ; ++ inst->ratio_pst1q = RATIO_PST1Q; ++ inst->adpt_eq = RATIO_EQ; ++ ++ tune_tecr0(inst); ++ inst->ld_status &= ~(COP1_MASK | COZ_MASK | COM1_MASK); ++ inst->ld_status |= COE_UPDATED << COP1_SHIFT | ++ COE_UPDATED << COZ_SHIFT | ++ COE_UPDATED << COM1_SHIFT; ++ ld_coe_status(inst); ++} ++ ++static void train_rx(struct fsl_xgkr_inst *inst) ++{ ++ struct phy_device *phydev = inst->phydev; ++ int request, old_ld_status; ++ ++ /* get request from LP */ ++ request = phy_read_mmd(phydev, FSL_XFI_PMD, FSL_XFI_KR_LP_CU) & ++ (LD_ALL_MASK); ++ old_ld_status = inst->ld_status; ++ ++ /* IEEE802.3-2008, 72.6.10.2.5 ++ * Ensure we always go to NOT UDPATED for status reporting in ++ * response to HOLD requests. ++ * IEEE802.3-2008, 72.6.10.2.3.1/2 ++ * ... but only if PRESET/INITIALIZE are not active to ensure ++ * we keep status until they are released! ++ */ ++ if (!(request & (PRESET_MASK | INIT_MASK))) { ++ if (!(request & COP1_MASK)) ++ inst->ld_status &= ~COP1_MASK; ++ ++ if (!(request & COZ_MASK)) ++ inst->ld_status &= ~COZ_MASK; ++ ++ if (!(request & COM1_MASK)) ++ inst->ld_status &= ~COM1_MASK; ++ ++ if (old_ld_status != inst->ld_status) ++ ld_coe_status(inst); ++ ++ } ++ ++ /* As soon as the LP shows ready, no need to do any more updates. */ ++ if (check_rx(phydev)) { ++ /* LP receiver is ready */ ++ if (inst->ld_status & (COP1_MASK | COZ_MASK | COM1_MASK)) { ++ inst->ld_status &= ~(COP1_MASK | COZ_MASK | COM1_MASK); ++ ld_coe_status(inst); ++ } ++ } else { ++ /* IEEE802.3-2008, 72.6.10.2.3.1/2 ++ * only act on PRESET/INITIALIZE if all status is NOT UPDATED. ++ */ ++ if (request & (PRESET_MASK | INIT_MASK)) { ++ if (!(inst->ld_status & ++ (COP1_MASK | COZ_MASK | COM1_MASK))) { ++ if (request & PRESET_MASK) ++ preset(inst); ++ ++ if (request & INIT_MASK) ++ initialize(inst); ++ } ++ } ++ ++ /* LP Coefficient are not in HOLD */ ++ if (request & REQUEST_MASK) ++ check_request(inst, request & REQUEST_MASK); ++ } ++} ++ ++static void xgkr_wq_state_machine(struct work_struct *work) ++{ ++ struct fsl_xgkr_wk *wk = container_of(work, ++ struct fsl_xgkr_wk, xgkr_wk); ++ struct fsl_xgkr_inst *inst = wk->xgkr_inst; ++ struct training_state_machine *s_m = &inst->t_s_m; ++ struct phy_device *phydev = inst->phydev; ++ int val = 0, i; ++ int an_state, lt_state; ++ unsigned long dead_line; ++ int rx_ok, tx_ok; ++ ++ if (s_m->link_up) { ++ /* check abnormal link down events when link is up, for ex. ++ * the cable is pulled out or link partner is down. ++ */ ++ an_state = phy_read_mmd(phydev, FSL_XFI_AN, FSL_XFI_LNK_STATUS); ++ if (!(an_state & XFI_AN_LNK_STAT_UP)) { ++ dev_info(&phydev->dev, ++ "Detect hotplug, restart training!\n"); ++ init_inst(inst, 1); ++ start_an(phydev); ++ } ++ s_m->running = false; ++ return; ++ } ++ ++ if (!s_m->an_ok) { ++ an_state = phy_read_mmd(phydev, FSL_XFI_AN, FSL_XFI_BP_STATUS); ++ if (!(an_state & AN_10GKR_MASK)) { ++ s_m->running = false; ++ return; ++ } else ++ s_m->an_ok = true; ++ } ++ ++ dev_info(&phydev->dev, "is training.\n"); ++ ++ start_lt(phydev); ++ for (i = 0; i < 2;) { ++ /* i < 1 also works, but start one more try immediately when ++ * failed can adjust our training frequency to match other ++ * devices. This can help the link being established more ++ * quickly. ++ */ ++ dead_line = jiffies + msecs_to_jiffies(500); ++ while (time_before(jiffies, dead_line)) { ++ val = phy_read_mmd(phydev, FSL_XFI_PMD, ++ FSL_XFI_KR_PMD_STATUS); ++ if (val & TRAIN_FAIL) { ++ /* LT failed already, reset lane to avoid ++ * it run into hanging, then start LT again. ++ */ ++ reset_gcr0(inst); ++ start_lt(phydev); ++ } else if (val & PMD_STATUS_SUP_STAT && ++ val & PMD_STATUS_FRAME_LOCK) ++ break; ++ usleep_range(100, 500); ++ } ++ ++ if (!(val & PMD_STATUS_FRAME_LOCK && ++ val & PMD_STATUS_SUP_STAT)) { ++ i++; ++ continue; ++ } ++ ++ /* init process */ ++ rx_ok = tx_ok = false; ++ /* the LT should be finished in 500ms, failed or OK. */ ++ dead_line = jiffies + msecs_to_jiffies(500); ++ ++ while (time_before(jiffies, dead_line)) { ++ /* check if the LT is already failed */ ++ lt_state = phy_read_mmd(phydev, FSL_XFI_PMD, ++ FSL_XFI_KR_PMD_STATUS); ++ if (lt_state & TRAIN_FAIL) { ++ reset_gcr0(inst); ++ break; ++ } ++ ++ rx_ok = check_rx(phydev); ++ tx_ok = s_m->tx_complete; ++ ++ if (rx_ok && tx_ok) ++ break; ++ ++ if (!rx_ok) ++ train_rx(inst); ++ ++ if (!tx_ok) ++ train_tx(inst); ++ usleep_range(100, 500); ++ } ++ ++ i++; ++ /* check LT result */ ++ if (is_link_training_fail(phydev)) { ++ /* reset state machine */ ++ init_inst(inst, 0); ++ continue; ++ } else { ++ stop_lt(phydev); ++ s_m->running = false; ++ s_m->link_up = true; ++ dev_info(&phydev->dev, "LT training is SUCCEEDED!\n"); ++ break; ++ } ++ } ++ ++ if (!s_m->link_up) { ++ /* reset state machine */ ++ init_inst(inst, 0); ++ } ++} ++ ++static void xgkr_timer_handle(unsigned long arg) ++{ ++ struct list_head *pos; ++ struct fsl_xgkr_wk *wk; ++ struct fsl_xgkr_inst *xgkr_inst; ++ struct phy_device *phydev; ++ struct training_state_machine *s_m; ++ ++ list_for_each(pos, &fsl_xgkr_list) { ++ wk = list_entry(pos, struct fsl_xgkr_wk, xgkr_list); ++ xgkr_inst = wk->xgkr_inst; ++ phydev = xgkr_inst->phydev; ++ s_m = &xgkr_inst->t_s_m; ++ ++ if (!s_m->running && (!s_m->an_ok || s_m->link_up)) { ++ s_m->running = true; ++ queue_work(xgkr_wq, (struct work_struct *)wk); ++ } ++ } ++ ++ if (!list_empty(&fsl_xgkr_list)) ++ mod_timer(&xgkr_timer, ++ jiffies + msecs_to_jiffies(XGKR_TIMEOUT)); ++} ++ ++static int fsl_xgkr_bind_serdes(const char *lane_name, ++ struct phy_device *phydev) ++{ ++ unsigned long serdes_base; ++ unsigned long lane_base; ++ int i; ++ ++ for (i = 0; i < SERDES_MAX; i++) { ++ if (strstr(lane_name, s_map[i].serdes_name)) { ++ serdes_base = s_map[i].serdes_base; ++ break; ++ } ++ } ++ ++ if (i == SERDES_MAX) ++ goto serdes_err; ++ ++ for (i = 0; i < LANE_MAX; i++) { ++ if (strstr(lane_name, l_map[i].lane_name)) { ++ lane_base = l_map[i].lane_base; ++ break; ++ } ++ } ++ ++ if (i == LANE_MAX) ++ goto lane_err; ++ ++ phydev->priv = ioremap(serdes_base + lane_base, ++ sizeof(struct per_lane_ctrl_status)); ++ if (!phydev->priv) ++ return -ENOMEM; ++ ++ return 0; ++ ++serdes_err: ++ dev_err(&phydev->dev, "Unknown SerDes name"); ++ return -EINVAL; ++lane_err: ++ dev_err(&phydev->dev, "Unknown Lane name"); ++ return -EINVAL; ++} ++ ++static int fsl_xgkr_probe(struct phy_device *phydev) ++{ ++ struct fsl_xgkr_inst *xgkr_inst; ++ struct fsl_xgkr_wk *xgkr_wk; ++ struct device_node *child; ++ const char *lane_name; ++ int len; ++ ++ child = phydev->dev.of_node; ++ ++ /* if there is lane-instance property, 10G-KR need to run */ ++ lane_name = of_get_property(child, "lane-instance", &len); ++ if (!lane_name || (fsl_xgkr_bind_serdes(lane_name, phydev))) ++ return 0; ++ ++ xgkr_inst = kzalloc(sizeof(struct fsl_xgkr_inst), GFP_KERNEL); ++ if (!xgkr_inst) ++ goto mem_err1; ++ ++ xgkr_inst->reg_base = phydev->priv; ++ ++ xgkr_inst->bus = phydev->bus; ++ ++ xgkr_inst->phydev = phydev; ++ ++ init_inst(xgkr_inst, 1); ++ ++ xgkr_wk = kzalloc(sizeof(struct fsl_xgkr_wk), GFP_KERNEL); ++ if (!xgkr_wk) ++ goto mem_err2; ++ ++ xgkr_wk->xgkr_inst = xgkr_inst; ++ phydev->priv = xgkr_wk; ++ ++ list_add(&xgkr_wk->xgkr_list, &fsl_xgkr_list); ++ ++ if (!fire_timer) { ++ setup_timer(&xgkr_timer, xgkr_timer_handle, ++ (unsigned long)&fsl_xgkr_list); ++ mod_timer(&xgkr_timer, ++ jiffies + msecs_to_jiffies(XGKR_TIMEOUT)); ++ fire_timer = 1; ++ xgkr_wq = create_workqueue("fsl_xgkr"); ++ } ++ INIT_WORK((struct work_struct *)xgkr_wk, xgkr_wq_state_machine); ++ ++ /* start auto-negotiation to detect link partner */ ++ start_an(phydev); ++ ++ return 0; ++mem_err2: ++ kfree(xgkr_inst); ++mem_err1: ++ dev_err(&phydev->dev, "failed to allocate memory!\n"); ++ return -ENOMEM; ++} ++ ++static int fsl_xgkr_config_init(struct phy_device *phydev) ++{ ++ return 0; ++} ++ ++static int fsl_xgkr_config_aneg(struct phy_device *phydev) ++{ ++ return 0; ++} ++ ++static void fsl_xgkr_remove(struct phy_device *phydev) ++{ ++ struct fsl_xgkr_wk *wk = (struct fsl_xgkr_wk *)phydev->priv; ++ struct fsl_xgkr_inst *xgkr_inst = wk->xgkr_inst; ++ struct list_head *this, *next; ++ struct fsl_xgkr_wk *tmp; ++ ++ list_for_each_safe(this, next, &fsl_xgkr_list) { ++ tmp = list_entry(this, struct fsl_xgkr_wk, xgkr_list); ++ if (tmp == wk) { ++ cancel_work_sync((struct work_struct *)wk); ++ list_del(this); ++ } ++ } ++ ++ if (list_empty(&fsl_xgkr_list)) ++ del_timer(&xgkr_timer); ++ ++ if (xgkr_inst->reg_base) ++ iounmap(xgkr_inst->reg_base); ++ ++ kfree(xgkr_inst); ++ kfree(wk); ++} ++ ++static int fsl_xgkr_read_status(struct phy_device *phydev) ++{ ++ int val = phy_read_mmd(phydev, FSL_XFI_AN, FSL_XFI_LNK_STATUS); ++ ++ phydev->speed = SPEED_10000; ++ phydev->duplex = 1; ++ ++ if (val & XFI_AN_LNK_STAT_UP) ++ phydev->link = 1; ++ else ++ phydev->link = 0; ++ ++ return 0; ++} ++ ++static int fsl_xgkr_match_phy_device(struct phy_device *phydev) ++{ ++ return phydev->c45_ids.device_ids[3] == FSL_XFI_PCS_PHY_ID; ++} ++ ++static int fsl_xgkr_match_phy_device2(struct phy_device *phydev) ++{ ++ return phydev->c45_ids.device_ids[3] == FSL_XFI_PCS_PHY_ID2; ++} ++ ++static struct phy_driver fsl_xgkr_driver[] = { ++ { ++ .phy_id = FSL_XFI_PCS_PHY_ID, ++ .name = "Freescale 10G KR Rev1", ++ .phy_id_mask = 0xffffffff, ++ .features = PHY_GBIT_FEATURES, ++ .flags = PHY_HAS_INTERRUPT, ++ .probe = fsl_xgkr_probe, ++ .config_init = &fsl_xgkr_config_init, ++ .config_aneg = &fsl_xgkr_config_aneg, ++ .read_status = &fsl_xgkr_read_status, ++ .match_phy_device = fsl_xgkr_match_phy_device, ++ .remove = fsl_xgkr_remove, ++ .driver = { .owner = THIS_MODULE,}, ++ }, ++ { ++ .phy_id = FSL_XFI_PCS_PHY_ID2, ++ .name = "Freescale 10G KR Rev2", ++ .phy_id_mask = 0xffffffff, ++ .features = PHY_GBIT_FEATURES, ++ .flags = PHY_HAS_INTERRUPT, ++ .probe = fsl_xgkr_probe, ++ .config_init = &fsl_xgkr_config_init, ++ .config_aneg = &fsl_xgkr_config_aneg, ++ .read_status = &fsl_xgkr_read_status, ++ .match_phy_device = fsl_xgkr_match_phy_device2, ++ .remove = fsl_xgkr_remove, ++ .driver = { .owner = THIS_MODULE,}, ++ }, ++}; ++ ++static int __init fsl_xgkr_init(void) ++{ ++ return phy_drivers_register(fsl_xgkr_driver, ++ ARRAY_SIZE(fsl_xgkr_driver)); ++} ++ ++static void __exit fsl_xgkr_exit(void) ++{ ++ phy_drivers_unregister(fsl_xgkr_driver, ++ ARRAY_SIZE(fsl_xgkr_driver)); ++} ++ ++module_init(fsl_xgkr_init); ++module_exit(fsl_xgkr_exit); ++ ++static struct mdio_device_id __maybe_unused freescale_tbl[] = { ++ { FSL_XFI_PCS_PHY_ID, 0xffffffff }, ++ { FSL_XFI_PCS_PHY_ID2, 0xffffffff }, ++ { } ++}; ++ ++MODULE_DEVICE_TABLE(mdio, freescale_tbl); +diff --git a/drivers/net/phy/marvell.c b/drivers/net/phy/marvell.c +index 225c033..969a198 100644 +--- a/drivers/net/phy/marvell.c ++++ b/drivers/net/phy/marvell.c +@@ -50,6 +50,7 @@ + #define MII_M1011_PHY_SCR 0x10 + #define MII_M1011_PHY_SCR_AUTO_CROSS 0x0060 + ++#define MII_M1145_PHY_EXT_ADDR_PAGE 0x16 + #define MII_M1145_PHY_EXT_SR 0x1b + #define MII_M1145_PHY_EXT_CR 0x14 + #define MII_M1145_RGMII_RX_DELAY 0x0080 +@@ -495,6 +496,16 @@ static int m88e1111_config_init(struct phy_device *phydev) + err = phy_write(phydev, MII_M1111_PHY_EXT_SR, temp); + if (err < 0) + return err; ++ ++ /* make sure copper is selected */ ++ err = phy_read(phydev, MII_M1145_PHY_EXT_ADDR_PAGE); ++ if (err < 0) ++ return err; ++ ++ err = phy_write(phydev, MII_M1145_PHY_EXT_ADDR_PAGE, ++ err & (~0xff)); ++ if (err < 0) ++ return err; + } + + if (phydev->interface == PHY_INTERFACE_MODE_RTBI) { +diff --git a/drivers/net/phy/mdio_bus.c b/drivers/net/phy/mdio_bus.c +index 50051f2..accd605 100644 +--- a/drivers/net/phy/mdio_bus.c ++++ b/drivers/net/phy/mdio_bus.c +@@ -288,8 +288,11 @@ int mdiobus_register(struct mii_bus *bus) + + error: + while (--i >= 0) { +- if (bus->phy_map[i]) +- device_unregister(&bus->phy_map[i]->dev); ++ struct phy_device *phydev = bus->phy_map[i]; ++ if (phydev) { ++ phy_device_remove(phydev); ++ phy_device_free(phydev); ++ } + } + device_del(&bus->dev); + return err; +@@ -305,9 +308,11 @@ void mdiobus_unregister(struct mii_bus *bus) + + device_del(&bus->dev); + for (i = 0; i < PHY_MAX_ADDR; i++) { +- if (bus->phy_map[i]) +- device_unregister(&bus->phy_map[i]->dev); +- bus->phy_map[i] = NULL; ++ struct phy_device *phydev = bus->phy_map[i]; ++ if (phydev) { ++ phy_device_remove(phydev); ++ phy_device_free(phydev); ++ } + } + } + EXPORT_SYMBOL(mdiobus_unregister); +@@ -421,6 +426,8 @@ static int mdio_bus_match(struct device *dev, struct device_driver *drv) + { + struct phy_device *phydev = to_phy_device(dev); + struct phy_driver *phydrv = to_phy_driver(drv); ++ const int num_ids = ARRAY_SIZE(phydev->c45_ids.device_ids); ++ int i; + + if (of_driver_match_device(dev, drv)) + return 1; +@@ -428,8 +435,21 @@ static int mdio_bus_match(struct device *dev, struct device_driver *drv) + if (phydrv->match_phy_device) + return phydrv->match_phy_device(phydev); + +- return (phydrv->phy_id & phydrv->phy_id_mask) == +- (phydev->phy_id & phydrv->phy_id_mask); ++ if (phydev->is_c45) { ++ for (i = 1; i < num_ids; i++) { ++ if (!(phydev->c45_ids.devices_in_package & (1 << i))) ++ continue; ++ ++ if ((phydrv->phy_id & phydrv->phy_id_mask) == ++ (phydev->c45_ids.device_ids[i] & ++ phydrv->phy_id_mask)) ++ return 1; ++ } ++ return 0; ++ } else { ++ return (phydrv->phy_id & phydrv->phy_id_mask) == ++ (phydev->phy_id & phydrv->phy_id_mask); ++ } + } + + #ifdef CONFIG_PM +diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c +index 91d6d03..840075e 100644 +--- a/drivers/net/phy/phy.c ++++ b/drivers/net/phy/phy.c +@@ -768,6 +768,7 @@ void phy_state_machine(struct work_struct *work) + container_of(dwork, struct phy_device, state_queue); + bool needs_aneg = false, do_suspend = false, do_resume = false; + int err = 0; ++ int old_link; + + mutex_lock(&phydev->lock); + +@@ -814,6 +815,9 @@ void phy_state_machine(struct work_struct *work) + needs_aneg = true; + break; + case PHY_NOLINK: ++ if (phy_interrupt_is_valid(phydev)) ++ break; ++ + err = phy_read_status(phydev); + if (err) + break; +@@ -851,11 +855,18 @@ void phy_state_machine(struct work_struct *work) + phydev->adjust_link(phydev->attached_dev); + break; + case PHY_RUNNING: +- /* Only register a CHANGE if we are +- * polling or ignoring interrupts ++ /* Only register a CHANGE if we are polling or ignoring ++ * interrupts and link changed since latest checking. + */ +- if (!phy_interrupt_is_valid(phydev)) +- phydev->state = PHY_CHANGELINK; ++ if (!phy_interrupt_is_valid(phydev)) { ++ old_link = phydev->link; ++ err = phy_read_status(phydev); ++ if (err) ++ break; ++ ++ if (old_link != phydev->link) ++ phydev->state = PHY_CHANGELINK; ++ } + break; + case PHY_CHANGELINK: + err = phy_read_status(phydev); +diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c +index 70a0d88..07b1aa9 100644 +--- a/drivers/net/phy/phy_device.c ++++ b/drivers/net/phy/phy_device.c +@@ -205,6 +205,37 @@ struct phy_device *phy_device_create(struct mii_bus *bus, int addr, int phy_id, + } + EXPORT_SYMBOL(phy_device_create); + ++/* get_phy_c45_devs_in_pkg - reads a MMD's devices in package registers. ++ * @bus: the target MII bus ++ * @addr: PHY address on the MII bus ++ * @dev_addr: MMD address in the PHY. ++ * @devices_in_package: where to store the devices in package information. ++ * ++ * Description: reads devices in package registers of a MMD at @dev_addr ++ * from PHY at @addr on @bus. ++ * ++ * Returns: 0 on success, -EIO on failure. ++ */ ++static int get_phy_c45_devs_in_pkg(struct mii_bus *bus, int addr, int dev_addr, ++ u32 *devices_in_package) ++{ ++ int phy_reg, reg_addr; ++ ++ reg_addr = MII_ADDR_C45 | dev_addr << 16 | MDIO_DEVS2; ++ phy_reg = mdiobus_read(bus, addr, reg_addr); ++ if (phy_reg < 0) ++ return -EIO; ++ *devices_in_package = (phy_reg & 0xffff) << 16; ++ ++ reg_addr = MII_ADDR_C45 | dev_addr << 16 | MDIO_DEVS1; ++ phy_reg = mdiobus_read(bus, addr, reg_addr); ++ if (phy_reg < 0) ++ return -EIO; ++ *devices_in_package |= (phy_reg & 0xffff); ++ ++ return 0; ++} ++ + /** + * get_phy_c45_ids - reads the specified addr for its 802.3-c45 IDs. + * @bus: the target MII bus +@@ -223,31 +254,32 @@ static int get_phy_c45_ids(struct mii_bus *bus, int addr, u32 *phy_id, + int phy_reg; + int i, reg_addr; + const int num_ids = ARRAY_SIZE(c45_ids->device_ids); ++ u32 *devs = &c45_ids->devices_in_package; + +- /* Find first non-zero Devices In package. Device +- * zero is reserved, so don't probe it. ++ /* Find first non-zero Devices In package. Device zero is reserved ++ * for 802.3 c45 complied PHYs, so don't probe it at first. + */ +- for (i = 1; +- i < num_ids && c45_ids->devices_in_package == 0; +- i++) { +- reg_addr = MII_ADDR_C45 | i << 16 | MDIO_DEVS2; +- phy_reg = mdiobus_read(bus, addr, reg_addr); +- if (phy_reg < 0) +- return -EIO; +- c45_ids->devices_in_package = (phy_reg & 0xffff) << 16; +- +- reg_addr = MII_ADDR_C45 | i << 16 | MDIO_DEVS1; +- phy_reg = mdiobus_read(bus, addr, reg_addr); ++ for (i = 1; i < num_ids && *devs == 0; i++) { ++ phy_reg = get_phy_c45_devs_in_pkg(bus, addr, i, devs); + if (phy_reg < 0) + return -EIO; +- c45_ids->devices_in_package |= (phy_reg & 0xffff); + +- /* If mostly Fs, there is no device there, +- * let's get out of here. +- */ +- if ((c45_ids->devices_in_package & 0x1fffffff) == 0x1fffffff) { +- *phy_id = 0xffffffff; +- return 0; ++ if ((*devs & 0x1fffffff) == 0x1fffffff) { ++ /* If mostly Fs, there is no device there, ++ * then let's continue to probe more, as some ++ * 10G PHYs have zero Devices In package, ++ * e.g. Cortina CS4315/CS4340 PHY. ++ */ ++ phy_reg = get_phy_c45_devs_in_pkg(bus, addr, 0, devs); ++ if (phy_reg < 0) ++ return -EIO; ++ /* no device there, let's get out of here */ ++ if ((*devs & 0x1fffffff) == 0x1fffffff) { ++ *phy_id = 0xffffffff; ++ return 0; ++ } else { ++ break; ++ } + } + } + +@@ -376,6 +408,24 @@ int phy_device_register(struct phy_device *phydev) + EXPORT_SYMBOL(phy_device_register); + + /** ++ * phy_device_remove - Remove a previously registered phy device from the MDIO bus ++ * @phydev: phy_device structure to remove ++ * ++ * This doesn't free the phy_device itself, it merely reverses the effects ++ * of phy_device_register(). Use phy_device_free() to free the device ++ * after calling this function. ++ */ ++void phy_device_remove(struct phy_device *phydev) ++{ ++ struct mii_bus *bus = phydev->bus; ++ int addr = phydev->addr; ++ ++ device_del(&phydev->dev); ++ bus->phy_map[addr] = NULL; ++} ++EXPORT_SYMBOL(phy_device_remove); ++ ++/** + * phy_find_first - finds the first PHY device on the bus + * @bus: the target MII bus + */ +diff --git a/drivers/net/phy/realtek.c b/drivers/net/phy/realtek.c +index 45483fd..badcf24 100644 +--- a/drivers/net/phy/realtek.c ++++ b/drivers/net/phy/realtek.c +@@ -22,8 +22,12 @@ + #define RTL821x_INER 0x12 + #define RTL821x_INER_INIT 0x6400 + #define RTL821x_INSR 0x13 ++#define RTL8211E_INER_LINK_STATUS 0x400 + +-#define RTL8211E_INER_LINK_STATUS 0x400 ++#define RTL8211F_INER_LINK_STATUS 0x0010 ++#define RTL8211F_INSR 0x1d ++#define RTL8211F_PAGE_SELECT 0x1f ++#define RTL8211F_TX_DELAY 0x100 + + MODULE_DESCRIPTION("Realtek PHY driver"); + MODULE_AUTHOR("Johnson Leung"); +@@ -38,6 +42,18 @@ static int rtl821x_ack_interrupt(struct phy_device *phydev) + return (err < 0) ? err : 0; + } + ++static int rtl8211f_ack_interrupt(struct phy_device *phydev) ++{ ++ int err; ++ ++ phy_write(phydev, RTL8211F_PAGE_SELECT, 0xa43); ++ err = phy_read(phydev, RTL8211F_INSR); ++ /* restore to default page 0 */ ++ phy_write(phydev, RTL8211F_PAGE_SELECT, 0x0); ++ ++ return (err < 0) ? err : 0; ++} ++ + static int rtl8211b_config_intr(struct phy_device *phydev) + { + int err; +@@ -64,6 +80,41 @@ static int rtl8211e_config_intr(struct phy_device *phydev) + return err; + } + ++static int rtl8211f_config_intr(struct phy_device *phydev) ++{ ++ int err; ++ ++ if (phydev->interrupts == PHY_INTERRUPT_ENABLED) ++ err = phy_write(phydev, RTL821x_INER, ++ RTL8211F_INER_LINK_STATUS); ++ else ++ err = phy_write(phydev, RTL821x_INER, 0); ++ ++ return err; ++} ++ ++static int rtl8211f_config_init(struct phy_device *phydev) ++{ ++ int ret; ++ u16 reg; ++ ++ ret = genphy_config_init(phydev); ++ if (ret < 0) ++ return ret; ++ ++ if (phydev->interface == PHY_INTERFACE_MODE_RGMII) { ++ /* enable TXDLY */ ++ phy_write(phydev, RTL8211F_PAGE_SELECT, 0xd08); ++ reg = phy_read(phydev, 0x11); ++ reg |= RTL8211F_TX_DELAY; ++ phy_write(phydev, 0x11, reg); ++ /* restore to default page 0 */ ++ phy_write(phydev, RTL8211F_PAGE_SELECT, 0x0); ++ } ++ ++ return 0; ++} ++ + static struct phy_driver realtek_drvs[] = { + { + .phy_id = 0x00008201, +@@ -86,6 +137,19 @@ static struct phy_driver realtek_drvs[] = { + .config_intr = &rtl8211b_config_intr, + .driver = { .owner = THIS_MODULE,}, + }, { ++ .phy_id = 0x001cc914, ++ .name = "RTL8211DN Gigabit Ethernet", ++ .phy_id_mask = 0x001fffff, ++ .features = PHY_GBIT_FEATURES, ++ .flags = PHY_HAS_INTERRUPT, ++ .config_aneg = genphy_config_aneg, ++ .read_status = genphy_read_status, ++ .ack_interrupt = rtl821x_ack_interrupt, ++ .config_intr = rtl8211e_config_intr, ++ .suspend = genphy_suspend, ++ .resume = genphy_resume, ++ .driver = { .owner = THIS_MODULE,}, ++ }, { + .phy_id = 0x001cc915, + .name = "RTL8211E Gigabit Ethernet", + .phy_id_mask = 0x001fffff, +@@ -98,6 +162,20 @@ static struct phy_driver realtek_drvs[] = { + .suspend = genphy_suspend, + .resume = genphy_resume, + .driver = { .owner = THIS_MODULE,}, ++ }, { ++ .phy_id = 0x001cc916, ++ .name = "RTL8211F Gigabit Ethernet", ++ .phy_id_mask = 0x001fffff, ++ .features = PHY_GBIT_FEATURES, ++ .flags = PHY_HAS_INTERRUPT, ++ .config_aneg = &genphy_config_aneg, ++ .config_init = &rtl8211f_config_init, ++ .read_status = &genphy_read_status, ++ .ack_interrupt = &rtl8211f_ack_interrupt, ++ .config_intr = &rtl8211f_config_intr, ++ .suspend = genphy_suspend, ++ .resume = genphy_resume, ++ .driver = { .owner = THIS_MODULE }, + }, + }; + +@@ -116,7 +194,9 @@ module_exit(realtek_exit); + + static struct mdio_device_id __maybe_unused realtek_tbl[] = { + { 0x001cc912, 0x001fffff }, ++ { 0x001cc914, 0x001fffff }, + { 0x001cc915, 0x001fffff }, ++ { 0x001cc916, 0x001fffff }, + { } + }; + +diff --git a/drivers/net/phy/teranetics.c b/drivers/net/phy/teranetics.c +new file mode 100644 +index 0000000..91e1bec +--- /dev/null ++++ b/drivers/net/phy/teranetics.c +@@ -0,0 +1,135 @@ ++/* ++ * Driver for Teranetics PHY ++ * ++ * Author: Shaohui Xie ++ * ++ * Copyright 2015 Freescale Semiconductor, Inc. ++ * ++ * This file is licensed under the terms of the GNU General Public License ++ * version 2. This program is licensed "as is" without any warranty of any ++ * kind, whether express or implied. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++MODULE_DESCRIPTION("Teranetics PHY driver"); ++MODULE_AUTHOR("Shaohui Xie "); ++MODULE_LICENSE("GPL v2"); ++ ++#define PHY_ID_TN2020 0x00a19410 ++#define MDIO_PHYXS_LNSTAT_SYNC0 0x0001 ++#define MDIO_PHYXS_LNSTAT_SYNC1 0x0002 ++#define MDIO_PHYXS_LNSTAT_SYNC2 0x0004 ++#define MDIO_PHYXS_LNSTAT_SYNC3 0x0008 ++#define MDIO_PHYXS_LNSTAT_ALIGN 0x1000 ++ ++#define MDIO_PHYXS_LANE_READY (MDIO_PHYXS_LNSTAT_SYNC0 | \ ++ MDIO_PHYXS_LNSTAT_SYNC1 | \ ++ MDIO_PHYXS_LNSTAT_SYNC2 | \ ++ MDIO_PHYXS_LNSTAT_SYNC3 | \ ++ MDIO_PHYXS_LNSTAT_ALIGN) ++ ++static int teranetics_config_init(struct phy_device *phydev) ++{ ++ phydev->supported = SUPPORTED_10000baseT_Full; ++ phydev->advertising = SUPPORTED_10000baseT_Full; ++ ++ return 0; ++} ++ ++static int teranetics_soft_reset(struct phy_device *phydev) ++{ ++ return 0; ++} ++ ++static int teranetics_aneg_done(struct phy_device *phydev) ++{ ++ int reg; ++ ++ /* auto negotiation state can only be checked when using copper ++ * port, if using fiber port, just lie it's done. ++ */ ++ if (!phy_read_mmd(phydev, MDIO_MMD_VEND1, 93)) { ++ reg = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1); ++ return (reg < 0) ? reg : (reg & BMSR_ANEGCOMPLETE); ++ } ++ ++ return 1; ++} ++ ++static int teranetics_config_aneg(struct phy_device *phydev) ++{ ++ return 0; ++} ++ ++static int teranetics_read_status(struct phy_device *phydev) ++{ ++ int reg; ++ ++ phydev->link = 1; ++ ++ phydev->speed = SPEED_10000; ++ phydev->duplex = DUPLEX_FULL; ++ ++ if (!phy_read_mmd(phydev, MDIO_MMD_VEND1, 93)) { ++ reg = phy_read_mmd(phydev, MDIO_MMD_PHYXS, MDIO_PHYXS_LNSTAT); ++ if (reg < 0 || ++ !((reg & MDIO_PHYXS_LANE_READY) == MDIO_PHYXS_LANE_READY)) { ++ phydev->link = 0; ++ return 0; ++ } ++ ++ reg = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1); ++ if (reg < 0 || !(reg & MDIO_STAT1_LSTATUS)) ++ phydev->link = 0; ++ } ++ ++ return 0; ++} ++ ++static int teranetics_match_phy_device(struct phy_device *phydev) ++{ ++ return phydev->c45_ids.device_ids[3] == PHY_ID_TN2020; ++} ++ ++static struct phy_driver teranetics_driver[] = { ++{ ++ .phy_id = PHY_ID_TN2020, ++ .phy_id_mask = 0xffffffff, ++ .name = "Teranetics TN2020", ++ .soft_reset = teranetics_soft_reset, ++ .aneg_done = teranetics_aneg_done, ++ .config_init = teranetics_config_init, ++ .config_aneg = teranetics_config_aneg, ++ .read_status = teranetics_read_status, ++ .match_phy_device = teranetics_match_phy_device, ++ .driver = { .owner = THIS_MODULE,}, ++}, ++}; ++ ++static int __init teranetics_init(void) ++{ ++ return phy_drivers_register(teranetics_driver, ++ ARRAY_SIZE(teranetics_driver)); ++} ++ ++static void __exit teranetics_exit(void) ++{ ++ return phy_drivers_unregister(teranetics_driver, ++ ARRAY_SIZE(teranetics_driver)); ++} ++ ++module_init(teranetics_init); ++module_exit(teranetics_exit); ++ ++static struct mdio_device_id __maybe_unused teranetics_tbl[] = { ++ { PHY_ID_TN2020, 0xffffffff }, ++ { } ++}; ++ ++MODULE_DEVICE_TABLE(mdio, teranetics_tbl); +diff --git a/drivers/of/base.c b/drivers/of/base.c +index 469d2b7..210c876 100644 +--- a/drivers/of/base.c ++++ b/drivers/of/base.c +@@ -32,8 +32,8 @@ + + LIST_HEAD(aliases_lookup); + +-struct device_node *of_allnodes; +-EXPORT_SYMBOL(of_allnodes); ++struct device_node *of_root; ++EXPORT_SYMBOL(of_root); + struct device_node *of_chosen; + struct device_node *of_aliases; + struct device_node *of_stdout; +@@ -48,7 +48,7 @@ struct kset *of_kset; + */ + DEFINE_MUTEX(of_mutex); + +-/* use when traversing tree through the allnext, child, sibling, ++/* use when traversing tree through the child, sibling, + * or parent members of struct device_node. + */ + DEFINE_RAW_SPINLOCK(devtree_lock); +@@ -204,7 +204,7 @@ static int __init of_init(void) + mutex_unlock(&of_mutex); + + /* Symlink in /proc as required by userspace ABI */ +- if (of_allnodes) ++ if (of_root) + proc_symlink("device-tree", NULL, "/sys/firmware/devicetree/base"); + + return 0; +@@ -245,6 +245,23 @@ struct property *of_find_property(const struct device_node *np, + } + EXPORT_SYMBOL(of_find_property); + ++struct device_node *__of_find_all_nodes(struct device_node *prev) ++{ ++ struct device_node *np; ++ if (!prev) { ++ np = of_root; ++ } else if (prev->child) { ++ np = prev->child; ++ } else { ++ /* Walk back up looking for a sibling, or the end of the structure */ ++ np = prev; ++ while (np->parent && !np->sibling) ++ np = np->parent; ++ np = np->sibling; /* Might be null at the end of the tree */ ++ } ++ return np; ++} ++ + /** + * of_find_all_nodes - Get next node in global list + * @prev: Previous node or NULL to start iteration +@@ -259,10 +276,8 @@ struct device_node *of_find_all_nodes(struct device_node *prev) + unsigned long flags; + + raw_spin_lock_irqsave(&devtree_lock, flags); +- np = prev ? prev->allnext : of_allnodes; +- for (; np != NULL; np = np->allnext) +- if (of_node_get(np)) +- break; ++ np = __of_find_all_nodes(prev); ++ of_node_get(np); + of_node_put(prev); + raw_spin_unlock_irqrestore(&devtree_lock, flags); + return np; +@@ -736,7 +751,7 @@ struct device_node *of_find_node_by_path(const char *path) + unsigned long flags; + + if (strcmp(path, "/") == 0) +- return of_node_get(of_allnodes); ++ return of_node_get(of_root); + + /* The path could begin with an alias */ + if (*path != '/') { +@@ -761,7 +776,7 @@ struct device_node *of_find_node_by_path(const char *path) + /* Step down the tree matching path components */ + raw_spin_lock_irqsave(&devtree_lock, flags); + if (!np) +- np = of_node_get(of_allnodes); ++ np = of_node_get(of_root); + while (np && *path == '/') { + path++; /* Increment past '/' delimiter */ + np = __of_find_node_by_path(np, path); +@@ -790,8 +805,7 @@ struct device_node *of_find_node_by_name(struct device_node *from, + unsigned long flags; + + raw_spin_lock_irqsave(&devtree_lock, flags); +- np = from ? from->allnext : of_allnodes; +- for (; np; np = np->allnext) ++ for_each_of_allnodes_from(from, np) + if (np->name && (of_node_cmp(np->name, name) == 0) + && of_node_get(np)) + break; +@@ -820,8 +834,7 @@ struct device_node *of_find_node_by_type(struct device_node *from, + unsigned long flags; + + raw_spin_lock_irqsave(&devtree_lock, flags); +- np = from ? from->allnext : of_allnodes; +- for (; np; np = np->allnext) ++ for_each_of_allnodes_from(from, np) + if (np->type && (of_node_cmp(np->type, type) == 0) + && of_node_get(np)) + break; +@@ -852,12 +865,10 @@ struct device_node *of_find_compatible_node(struct device_node *from, + unsigned long flags; + + raw_spin_lock_irqsave(&devtree_lock, flags); +- np = from ? from->allnext : of_allnodes; +- for (; np; np = np->allnext) { ++ for_each_of_allnodes_from(from, np) + if (__of_device_is_compatible(np, compatible, type, NULL) && + of_node_get(np)) + break; +- } + of_node_put(from); + raw_spin_unlock_irqrestore(&devtree_lock, flags); + return np; +@@ -884,8 +895,7 @@ struct device_node *of_find_node_with_property(struct device_node *from, + unsigned long flags; + + raw_spin_lock_irqsave(&devtree_lock, flags); +- np = from ? from->allnext : of_allnodes; +- for (; np; np = np->allnext) { ++ for_each_of_allnodes_from(from, np) { + for (pp = np->properties; pp; pp = pp->next) { + if (of_prop_cmp(pp->name, prop_name) == 0) { + of_node_get(np); +@@ -967,8 +977,7 @@ struct device_node *of_find_matching_node_and_match(struct device_node *from, + *match = NULL; + + raw_spin_lock_irqsave(&devtree_lock, flags); +- np = from ? from->allnext : of_allnodes; +- for (; np; np = np->allnext) { ++ for_each_of_allnodes_from(from, np) { + m = __of_match_node(matches, np); + if (m && of_node_get(np)) { + if (match) +@@ -1025,7 +1034,7 @@ struct device_node *of_find_node_by_phandle(phandle handle) + return NULL; + + raw_spin_lock_irqsave(&devtree_lock, flags); +- for (np = of_allnodes; np; np = np->allnext) ++ for_each_of_allnodes(np) + if (np->phandle == handle) + break; + of_node_get(np); +diff --git a/drivers/of/device.c b/drivers/of/device.c +index 46d6c75..20c1332 100644 +--- a/drivers/of/device.c ++++ b/drivers/of/device.c +@@ -2,6 +2,9 @@ + #include + #include + #include ++#include ++#include ++#include + #include + #include + #include +@@ -66,6 +69,87 @@ int of_device_add(struct platform_device *ofdev) + return device_add(&ofdev->dev); + } + ++/** ++ * of_dma_configure - Setup DMA configuration ++ * @dev: Device to apply DMA configuration ++ * @np: Pointer to OF node having DMA configuration ++ * ++ * Try to get devices's DMA configuration from DT and update it ++ * accordingly. ++ * ++ * If platform code needs to use its own special DMA configuration, it ++ * can use a platform bus notifier and handle BUS_NOTIFY_ADD_DEVICE events ++ * to fix up DMA configuration. ++ */ ++void of_dma_configure(struct device *dev, struct device_node *np) ++{ ++ u64 dma_addr, paddr, size; ++ int ret; ++ bool coherent; ++ unsigned long offset; ++ struct iommu_ops *iommu; ++ ++ /* ++ * Set default coherent_dma_mask to 32 bit. Drivers are expected to ++ * setup the correct supported mask. ++ */ ++ if (!dev->coherent_dma_mask) ++ dev->coherent_dma_mask = DMA_BIT_MASK(32); ++ ++ /* ++ * Set it to coherent_dma_mask by default if the architecture ++ * code has not set it. ++ */ ++ if (!dev->dma_mask) ++ dev->dma_mask = &dev->coherent_dma_mask; ++ ++ ret = of_dma_get_range(np, &dma_addr, &paddr, &size); ++ if (ret < 0) { ++ dma_addr = offset = 0; ++ size = dev->coherent_dma_mask + 1; ++ } else { ++ offset = PFN_DOWN(paddr - dma_addr); ++ ++ /* ++ * Add a work around to treat the size as mask + 1 in case ++ * it is defined in DT as a mask. ++ */ ++ if (size & 1) { ++ dev_warn(dev, "Invalid size 0x%llx for dma-range\n", ++ size); ++ size = size + 1; ++ } ++ ++ if (!size) { ++ dev_err(dev, "Adjusted size 0x%llx invalid\n", size); ++ return; ++ } ++ dev_dbg(dev, "dma_pfn_offset(%#08lx)\n", offset); ++ } ++ ++ dev->dma_pfn_offset = offset; ++ ++ /* ++ * Limit coherent and dma mask based on size and default mask ++ * set by the driver. ++ */ ++ dev->coherent_dma_mask = min(dev->coherent_dma_mask, ++ DMA_BIT_MASK(ilog2(dma_addr + size))); ++ *dev->dma_mask = min((*dev->dma_mask), ++ DMA_BIT_MASK(ilog2(dma_addr + size))); ++ ++ coherent = of_dma_is_coherent(np); ++ dev_dbg(dev, "device is%sdma coherent\n", ++ coherent ? " " : " not "); ++ ++ iommu = of_iommu_configure(dev, np); ++ dev_dbg(dev, "device is%sbehind an iommu\n", ++ iommu ? " " : " not "); ++ ++ arch_setup_dma_ops(dev, dma_addr, size, iommu, coherent); ++} ++EXPORT_SYMBOL_GPL(of_dma_configure); ++ + int of_device_register(struct platform_device *pdev) + { + device_initialize(&pdev->dev); +diff --git a/drivers/of/dynamic.c b/drivers/of/dynamic.c +index d499417..d43f305 100644 +--- a/drivers/of/dynamic.c ++++ b/drivers/of/dynamic.c +@@ -117,8 +117,6 @@ void __of_attach_node(struct device_node *np) + + np->child = NULL; + np->sibling = np->parent->child; +- np->allnext = np->parent->allnext; +- np->parent->allnext = np; + np->parent->child = np; + of_node_clear_flag(np, OF_DETACHED); + } +@@ -154,17 +152,6 @@ void __of_detach_node(struct device_node *np) + if (WARN_ON(!parent)) + return; + +- if (of_allnodes == np) +- of_allnodes = np->allnext; +- else { +- struct device_node *prev; +- for (prev = of_allnodes; +- prev->allnext != np; +- prev = prev->allnext) +- ; +- prev->allnext = np->allnext; +- } +- + if (parent->child == np) + parent->child = np->sibling; + else { +diff --git a/drivers/of/fdt.c b/drivers/of/fdt.c +index d134710..f6eda02 100644 +--- a/drivers/of/fdt.c ++++ b/drivers/of/fdt.c +@@ -145,15 +145,15 @@ static void *unflatten_dt_alloc(void **mem, unsigned long size, + * @mem: Memory chunk to use for allocating device nodes and properties + * @p: pointer to node in flat tree + * @dad: Parent struct device_node +- * @allnextpp: pointer to ->allnext from last allocated device_node + * @fpsize: Size of the node path up at the current depth. + */ + static void * unflatten_dt_node(void *blob, + void *mem, + int *poffset, + struct device_node *dad, +- struct device_node ***allnextpp, +- unsigned long fpsize) ++ struct device_node **nodepp, ++ unsigned long fpsize, ++ bool dryrun) + { + const __be32 *p; + struct device_node *np; +@@ -200,7 +200,7 @@ static void * unflatten_dt_node(void *blob, + + np = unflatten_dt_alloc(&mem, sizeof(struct device_node) + allocl, + __alignof__(struct device_node)); +- if (allnextpp) { ++ if (!dryrun) { + char *fn; + of_node_init(np); + np->full_name = fn = ((char *)np) + sizeof(*np); +@@ -222,8 +222,6 @@ static void * unflatten_dt_node(void *blob, + memcpy(fn, pathp, l); + + prev_pp = &np->properties; +- **allnextpp = np; +- *allnextpp = &np->allnext; + if (dad != NULL) { + np->parent = dad; + /* we temporarily use the next field as `last_child'*/ +@@ -254,7 +252,7 @@ static void * unflatten_dt_node(void *blob, + has_name = 1; + pp = unflatten_dt_alloc(&mem, sizeof(struct property), + __alignof__(struct property)); +- if (allnextpp) { ++ if (!dryrun) { + /* We accept flattened tree phandles either in + * ePAPR-style "phandle" properties, or the + * legacy "linux,phandle" properties. If both +@@ -296,7 +294,7 @@ static void * unflatten_dt_node(void *blob, + sz = (pa - ps) + 1; + pp = unflatten_dt_alloc(&mem, sizeof(struct property) + sz, + __alignof__(struct property)); +- if (allnextpp) { ++ if (!dryrun) { + pp->name = "name"; + pp->length = sz; + pp->value = pp + 1; +@@ -308,7 +306,7 @@ static void * unflatten_dt_node(void *blob, + (char *)pp->value); + } + } +- if (allnextpp) { ++ if (!dryrun) { + *prev_pp = NULL; + np->name = of_get_property(np, "name", NULL); + np->type = of_get_property(np, "device_type", NULL); +@@ -324,11 +322,13 @@ static void * unflatten_dt_node(void *blob, + if (depth < 0) + depth = 0; + while (*poffset > 0 && depth > old_depth) +- mem = unflatten_dt_node(blob, mem, poffset, np, allnextpp, +- fpsize); ++ mem = unflatten_dt_node(blob, mem, poffset, np, NULL, ++ fpsize, dryrun); + + if (*poffset < 0 && *poffset != -FDT_ERR_NOTFOUND) + pr_err("unflatten: error %d processing FDT\n", *poffset); ++ if (nodepp) ++ *nodepp = np; + + return mem; + } +@@ -352,7 +352,6 @@ static void __unflatten_device_tree(void *blob, + unsigned long size; + int start; + void *mem; +- struct device_node **allnextp = mynodes; + + pr_debug(" -> unflatten_device_tree()\n"); + +@@ -373,7 +372,7 @@ static void __unflatten_device_tree(void *blob, + + /* First pass, scan for size */ + start = 0; +- size = (unsigned long)unflatten_dt_node(blob, NULL, &start, NULL, NULL, 0); ++ size = (unsigned long)unflatten_dt_node(blob, NULL, &start, NULL, NULL, 0, true); + size = ALIGN(size, 4); + + pr_debug(" size is %lx, allocating...\n", size); +@@ -388,11 +387,10 @@ static void __unflatten_device_tree(void *blob, + + /* Second pass, do actual unflattening */ + start = 0; +- unflatten_dt_node(blob, mem, &start, NULL, &allnextp, 0); ++ unflatten_dt_node(blob, mem, &start, NULL, mynodes, 0, false); + if (be32_to_cpup(mem + size) != 0xdeadbeef) + pr_warning("End of tree marker overwritten: %08x\n", + be32_to_cpup(mem + size)); +- *allnextp = NULL; + + pr_debug(" <- unflatten_device_tree()\n"); + } +@@ -1039,7 +1037,7 @@ bool __init early_init_dt_scan(void *params) + */ + void __init unflatten_device_tree(void) + { +- __unflatten_device_tree(initial_boot_params, &of_allnodes, ++ __unflatten_device_tree(initial_boot_params, &of_root, + early_init_dt_alloc_memory_arch); + + /* Get pointer to "/chosen" and "/aliases" nodes for use everywhere */ +diff --git a/drivers/of/irq.c b/drivers/of/irq.c +index b97363a..4419e62 100644 +--- a/drivers/of/irq.c ++++ b/drivers/of/irq.c +@@ -18,6 +18,7 @@ + * driver. + */ + ++#include + #include + #include + #include +@@ -576,3 +577,23 @@ err: + kfree(desc); + } + } ++ ++/** ++ * of_msi_configure - Set the msi_domain field of a device ++ * @dev: device structure to associate with an MSI irq domain ++ * @np: device node for that device ++ */ ++void of_msi_configure(struct device *dev, struct device_node *np) ++{ ++ struct device_node *msi_np; ++ struct irq_domain *d; ++ ++ msi_np = of_parse_phandle(np, "msi-parent", 0); ++ if (!msi_np) ++ return; ++ ++ d = irq_find_matching_host(msi_np, DOMAIN_BUS_PLATFORM_MSI); ++ if (!d) ++ d = irq_find_host(msi_np); ++ dev_set_msi_domain(dev, d); ++} +diff --git a/drivers/of/of_pci.c b/drivers/of/of_pci.c +index ecc5fa5..5751dc5 100644 +--- a/drivers/of/of_pci.c ++++ b/drivers/of/of_pci.c +@@ -2,6 +2,7 @@ + #include + #include + #include ++#include + #include + #include + +@@ -116,6 +117,26 @@ int of_get_pci_domain_nr(struct device_node *node) + } + EXPORT_SYMBOL_GPL(of_get_pci_domain_nr); + ++/** ++ * of_pci_dma_configure - Setup DMA configuration ++ * @dev: ptr to pci_dev struct of the PCI device ++ * ++ * Function to update PCI devices's DMA configuration using the same ++ * info from the OF node of host bridge's parent (if any). ++ */ ++void of_pci_dma_configure(struct pci_dev *pci_dev) ++{ ++ struct device *dev = &pci_dev->dev; ++ struct device *bridge = pci_get_host_bridge_device(pci_dev); ++ ++ if (!bridge->parent) ++ return; ++ ++ of_dma_configure(dev, bridge->parent->of_node); ++ pci_put_host_bridge_device(bridge); ++} ++EXPORT_SYMBOL_GPL(of_pci_dma_configure); ++ + #if defined(CONFIG_OF_ADDRESS) + /** + * of_pci_get_host_bridge_resources - Parse PCI host bridge resources from DT +@@ -140,7 +161,7 @@ int of_pci_get_host_bridge_resources(struct device_node *dev, + unsigned char busno, unsigned char bus_max, + struct list_head *resources, resource_size_t *io_base) + { +- struct pci_host_bridge_window *window; ++ struct resource_entry *window; + struct resource *res; + struct resource *bus_range; + struct of_pci_range range; +@@ -226,10 +247,9 @@ int of_pci_get_host_bridge_resources(struct device_node *dev, + conversion_failed: + kfree(res); + parse_failed: +- list_for_each_entry(window, resources, list) ++ resource_list_for_each_entry(window, resources) + kfree(window->res); + pci_free_resource_list(resources); +- kfree(bus_range); + return err; + } + EXPORT_SYMBOL_GPL(of_pci_get_host_bridge_resources); +@@ -240,7 +260,7 @@ EXPORT_SYMBOL_GPL(of_pci_get_host_bridge_resources); + static LIST_HEAD(of_pci_msi_chip_list); + static DEFINE_MUTEX(of_pci_msi_chip_mutex); + +-int of_pci_msi_chip_add(struct msi_chip *chip) ++int of_pci_msi_chip_add(struct msi_controller *chip) + { + if (!of_property_read_bool(chip->of_node, "msi-controller")) + return -EINVAL; +@@ -253,7 +273,7 @@ int of_pci_msi_chip_add(struct msi_chip *chip) + } + EXPORT_SYMBOL_GPL(of_pci_msi_chip_add); + +-void of_pci_msi_chip_remove(struct msi_chip *chip) ++void of_pci_msi_chip_remove(struct msi_controller *chip) + { + mutex_lock(&of_pci_msi_chip_mutex); + list_del(&chip->list); +@@ -261,9 +281,9 @@ void of_pci_msi_chip_remove(struct msi_chip *chip) + } + EXPORT_SYMBOL_GPL(of_pci_msi_chip_remove); + +-struct msi_chip *of_pci_find_msi_chip_by_node(struct device_node *of_node) ++struct msi_controller *of_pci_find_msi_chip_by_node(struct device_node *of_node) + { +- struct msi_chip *c; ++ struct msi_controller *c; + + mutex_lock(&of_pci_msi_chip_mutex); + list_for_each_entry(c, &of_pci_msi_chip_list, list) { +diff --git a/drivers/of/pdt.c b/drivers/of/pdt.c +index 36b4035..d2acae8 100644 +--- a/drivers/of/pdt.c ++++ b/drivers/of/pdt.c +@@ -25,8 +25,7 @@ + + static struct of_pdt_ops *of_pdt_prom_ops __initdata; + +-void __initdata (*of_pdt_build_more)(struct device_node *dp, +- struct device_node ***nextp); ++void __initdata (*of_pdt_build_more)(struct device_node *dp); + + #if defined(CONFIG_SPARC) + unsigned int of_pdt_unique_id __initdata; +@@ -192,8 +191,7 @@ static struct device_node * __init of_pdt_create_node(phandle node, + } + + static struct device_node * __init of_pdt_build_tree(struct device_node *parent, +- phandle node, +- struct device_node ***nextp) ++ phandle node) + { + struct device_node *ret = NULL, *prev_sibling = NULL; + struct device_node *dp; +@@ -210,16 +208,12 @@ static struct device_node * __init of_pdt_build_tree(struct device_node *parent, + ret = dp; + prev_sibling = dp; + +- *(*nextp) = dp; +- *nextp = &dp->allnext; +- + dp->full_name = of_pdt_build_full_name(dp); + +- dp->child = of_pdt_build_tree(dp, +- of_pdt_prom_ops->getchild(node), nextp); ++ dp->child = of_pdt_build_tree(dp, of_pdt_prom_ops->getchild(node)); + + if (of_pdt_build_more) +- of_pdt_build_more(dp, nextp); ++ of_pdt_build_more(dp); + + node = of_pdt_prom_ops->getsibling(node); + } +@@ -234,20 +228,17 @@ static void * __init kernel_tree_alloc(u64 size, u64 align) + + void __init of_pdt_build_devicetree(phandle root_node, struct of_pdt_ops *ops) + { +- struct device_node **nextp; +- + BUG_ON(!ops); + of_pdt_prom_ops = ops; + +- of_allnodes = of_pdt_create_node(root_node, NULL); ++ of_root = of_pdt_create_node(root_node, NULL); + #if defined(CONFIG_SPARC) +- of_allnodes->path_component_name = ""; ++ of_root->path_component_name = ""; + #endif +- of_allnodes->full_name = "/"; ++ of_root->full_name = "/"; + +- nextp = &of_allnodes->allnext; +- of_allnodes->child = of_pdt_build_tree(of_allnodes, +- of_pdt_prom_ops->getchild(of_allnodes->phandle), &nextp); ++ of_root->child = of_pdt_build_tree(of_root, ++ of_pdt_prom_ops->getchild(of_root->phandle)); + + /* Get pointer to "/chosen" and "/aliases" nodes for use everywhere */ + of_alias_scan(kernel_tree_alloc); +diff --git a/drivers/of/platform.c b/drivers/of/platform.c +index 3b64d0b..8a002d6 100644 +--- a/drivers/of/platform.c ++++ b/drivers/of/platform.c +@@ -25,6 +25,7 @@ + + const struct of_device_id of_default_bus_match_table[] = { + { .compatible = "simple-bus", }, ++ { .compatible = "simple-mfd", }, + #ifdef CONFIG_ARM_AMBA + { .compatible = "arm,amba-bus", }, + #endif /* CONFIG_ARM_AMBA */ +@@ -138,7 +139,7 @@ struct platform_device *of_device_alloc(struct device_node *np, + } + + dev->dev.of_node = of_node_get(np); +- dev->dev.parent = parent; ++ dev->dev.parent = parent ? : &platform_bus; + + if (bus_id) + dev_set_name(&dev->dev, "%s", bus_id); +@@ -149,57 +150,9 @@ struct platform_device *of_device_alloc(struct device_node *np, + } + EXPORT_SYMBOL(of_device_alloc); + +-/** +- * of_dma_configure - Setup DMA configuration +- * @dev: Device to apply DMA configuration +- * +- * Try to get devices's DMA configuration from DT and update it +- * accordingly. +- * +- * In case if platform code need to use own special DMA configuration,it +- * can use Platform bus notifier and handle BUS_NOTIFY_ADD_DEVICE event +- * to fix up DMA configuration. +- */ +-static void of_dma_configure(struct device *dev) ++static void of_dma_deconfigure(struct device *dev) + { +- u64 dma_addr, paddr, size; +- int ret; +- +- /* +- * Set default dma-mask to 32 bit. Drivers are expected to setup +- * the correct supported dma_mask. +- */ +- dev->coherent_dma_mask = DMA_BIT_MASK(32); +- +- /* +- * Set it to coherent_dma_mask by default if the architecture +- * code has not set it. +- */ +- if (!dev->dma_mask) +- dev->dma_mask = &dev->coherent_dma_mask; +- +- /* +- * if dma-coherent property exist, call arch hook to setup +- * dma coherent operations. +- */ +- if (of_dma_is_coherent(dev->of_node)) { +- set_arch_dma_coherent_ops(dev); +- dev_dbg(dev, "device is dma coherent\n"); +- } +- +- /* +- * if dma-ranges property doesn't exist - just return else +- * setup the dma offset +- */ +- ret = of_dma_get_range(dev->of_node, &dma_addr, &paddr, &size); +- if (ret < 0) { +- dev_dbg(dev, "no dma range information to setup\n"); +- return; +- } +- +- /* DMA ranges found. Calculate and set dma_pfn_offset */ +- dev->dma_pfn_offset = PFN_DOWN(paddr - dma_addr); +- dev_dbg(dev, "dma_pfn_offset(%#08lx)\n", dev->dma_pfn_offset); ++ arch_teardown_dma_ops(dev); + } + + /** +@@ -228,16 +181,13 @@ static struct platform_device *of_platform_device_create_pdata( + if (!dev) + goto err_clear_flag; + +- of_dma_configure(&dev->dev); + dev->dev.bus = &platform_bus_type; + dev->dev.platform_data = platform_data; +- +- /* We do not fill the DMA ops for platform devices by default. +- * This is currently the responsibility of the platform code +- * to do such, possibly using a device notifier +- */ ++ of_dma_configure(&dev->dev, dev->dev.of_node); ++ of_msi_configure(&dev->dev, dev->dev.of_node); + + if (of_device_add(dev) != 0) { ++ of_dma_deconfigure(&dev->dev); + platform_device_put(dev); + goto err_clear_flag; + } +@@ -291,13 +241,13 @@ static struct amba_device *of_amba_device_create(struct device_node *node, + + /* setup generic device info */ + dev->dev.of_node = of_node_get(node); +- dev->dev.parent = parent; ++ dev->dev.parent = parent ? : &platform_bus; + dev->dev.platform_data = platform_data; + if (bus_id) + dev_set_name(&dev->dev, "%s", bus_id); + else + of_device_make_bus_id(&dev->dev); +- of_dma_configure(&dev->dev); ++ of_dma_configure(&dev->dev, dev->dev.of_node); + + /* Allow the HW Peripheral ID to be overridden */ + prop = of_get_property(node, "arm,primecell-periphid", NULL); +@@ -500,6 +450,7 @@ int of_platform_populate(struct device_node *root, + if (rc) + break; + } ++ of_node_set_flag(root, OF_POPULATED_BUS); + + of_node_put(root); + return rc; +@@ -523,6 +474,7 @@ static int of_platform_device_destroy(struct device *dev, void *data) + amba_device_unregister(to_amba_device(dev)); + #endif + ++ of_dma_deconfigure(dev); + of_node_clear_flag(dev->of_node, OF_POPULATED); + of_node_clear_flag(dev->of_node, OF_POPULATED_BUS); + return 0; +@@ -542,8 +494,75 @@ static int of_platform_device_destroy(struct device *dev, void *data) + */ + void of_platform_depopulate(struct device *parent) + { +- device_for_each_child(parent, NULL, of_platform_device_destroy); ++ if (parent->of_node && of_node_check_flag(parent->of_node, OF_POPULATED_BUS)) { ++ device_for_each_child(parent, NULL, of_platform_device_destroy); ++ of_node_clear_flag(parent->of_node, OF_POPULATED_BUS); ++ } + } + EXPORT_SYMBOL_GPL(of_platform_depopulate); + ++#ifdef CONFIG_OF_DYNAMIC ++static int of_platform_notify(struct notifier_block *nb, ++ unsigned long action, void *arg) ++{ ++ struct of_reconfig_data *rd = arg; ++ struct platform_device *pdev_parent, *pdev; ++ bool children_left; ++ ++ switch (of_reconfig_get_state_change(action, rd)) { ++ case OF_RECONFIG_CHANGE_ADD: ++ /* verify that the parent is a bus */ ++ if (!of_node_check_flag(rd->dn->parent, OF_POPULATED_BUS)) ++ return NOTIFY_OK; /* not for us */ ++ ++ /* already populated? (driver using of_populate manually) */ ++ if (of_node_check_flag(rd->dn, OF_POPULATED)) ++ return NOTIFY_OK; ++ ++ /* pdev_parent may be NULL when no bus platform device */ ++ pdev_parent = of_find_device_by_node(rd->dn->parent); ++ pdev = of_platform_device_create(rd->dn, NULL, ++ pdev_parent ? &pdev_parent->dev : NULL); ++ of_dev_put(pdev_parent); ++ ++ if (pdev == NULL) { ++ pr_err("%s: failed to create for '%s'\n", ++ __func__, rd->dn->full_name); ++ /* of_platform_device_create tosses the error code */ ++ return notifier_from_errno(-EINVAL); ++ } ++ break; ++ ++ case OF_RECONFIG_CHANGE_REMOVE: ++ ++ /* already depopulated? */ ++ if (!of_node_check_flag(rd->dn, OF_POPULATED)) ++ return NOTIFY_OK; ++ ++ /* find our device by node */ ++ pdev = of_find_device_by_node(rd->dn); ++ if (pdev == NULL) ++ return NOTIFY_OK; /* no? not meant for us */ ++ ++ /* unregister takes one ref away */ ++ of_platform_device_destroy(&pdev->dev, &children_left); ++ ++ /* and put the reference of the find */ ++ of_dev_put(pdev); ++ break; ++ } ++ ++ return NOTIFY_OK; ++} ++ ++static struct notifier_block platform_of_notifier = { ++ .notifier_call = of_platform_notify, ++}; ++ ++void of_platform_register_reconfig_notifier(void) ++{ ++ WARN_ON(of_reconfig_notifier_register(&platform_of_notifier)); ++} ++#endif /* CONFIG_OF_DYNAMIC */ ++ + #endif /* CONFIG_OF_ADDRESS */ +diff --git a/drivers/of/selftest.c b/drivers/of/selftest.c +index e2d79af..e40089e 100644 +--- a/drivers/of/selftest.c ++++ b/drivers/of/selftest.c +@@ -148,7 +148,7 @@ static void __init of_selftest_dynamic(void) + + static int __init of_selftest_check_node_linkage(struct device_node *np) + { +- struct device_node *child, *allnext_index = np; ++ struct device_node *child; + int count = 0, rc; + + for_each_child_of_node(np, child) { +@@ -158,14 +158,6 @@ static int __init of_selftest_check_node_linkage(struct device_node *np) + return -EINVAL; + } + +- while (allnext_index && allnext_index != child) +- allnext_index = allnext_index->allnext; +- if (allnext_index != child) { +- pr_err("Node %s is ordered differently in sibling and allnode lists\n", +- child->name); +- return -EINVAL; +- } +- + rc = of_selftest_check_node_linkage(child); + if (rc < 0) + return rc; +@@ -180,12 +172,12 @@ static void __init of_selftest_check_tree_linkage(void) + struct device_node *np; + int allnode_count = 0, child_count; + +- if (!of_allnodes) ++ if (!of_root) + return; + + for_each_of_allnodes(np) + allnode_count++; +- child_count = of_selftest_check_node_linkage(of_allnodes); ++ child_count = of_selftest_check_node_linkage(of_root); + + selftest(child_count > 0, "Device node data structure is corrupted\n"); + selftest(child_count == allnode_count, "allnodes list size (%i) doesn't match" +@@ -775,33 +767,29 @@ static void update_node_properties(struct device_node *np, + */ + static int attach_node_and_children(struct device_node *np) + { +- struct device_node *next, *root = np, *dup; ++ struct device_node *next, *dup, *child; + +- /* skip root node */ +- np = np->child; +- /* storing a copy in temporary node */ +- dup = np; ++ dup = of_find_node_by_path(np->full_name); ++ if (dup) { ++ update_node_properties(np, dup); ++ return 0; ++ } + +- while (dup) { ++ /* Children of the root need to be remembered for removal */ ++ if (np->parent == of_root) { + if (WARN_ON(last_node_index >= NO_OF_NODES)) + return -EINVAL; +- nodes[last_node_index++] = dup; +- dup = dup->sibling; ++ nodes[last_node_index++] = np; + } +- dup = NULL; + +- while (np) { +- next = np->allnext; +- dup = of_find_node_by_path(np->full_name); +- if (dup) +- update_node_properties(np, dup); +- else { +- np->child = NULL; +- if (np->parent == root) +- np->parent = of_allnodes; +- of_attach_node(np); +- } +- np = next; ++ child = np->child; ++ np->child = NULL; ++ np->sibling = NULL; ++ of_attach_node(np); ++ while (child) { ++ next = child->sibling; ++ attach_node_and_children(child); ++ child = next; + } + + return 0; +@@ -846,10 +834,10 @@ static int __init selftest_data_add(void) + return -EINVAL; + } + +- if (!of_allnodes) { ++ if (!of_root) { + /* enabling flag for removing nodes */ + selftest_live_tree = true; +- of_allnodes = selftest_data_node; ++ of_root = selftest_data_node; + + for_each_of_allnodes(np) + __of_attach_node_sysfs(np); +@@ -859,7 +847,14 @@ static int __init selftest_data_add(void) + } + + /* attach the sub-tree to live tree */ +- return attach_node_and_children(selftest_data_node); ++ np = selftest_data_node->child; ++ while (np) { ++ struct device_node *next = np->sibling; ++ np->parent = of_root; ++ attach_node_and_children(np); ++ np = next; ++ } ++ return 0; + } + + /** +@@ -889,10 +884,10 @@ static void selftest_data_remove(void) + of_node_put(of_chosen); + of_aliases = NULL; + of_chosen = NULL; +- for_each_child_of_node(of_allnodes, np) ++ for_each_child_of_node(of_root, np) + detach_node_and_children(np); +- __of_detach_node_sysfs(of_allnodes); +- of_allnodes = NULL; ++ __of_detach_node_sysfs(of_root); ++ of_root = NULL; + return; + } + +diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig +index 893503f..cced842 100644 +--- a/drivers/pci/Kconfig ++++ b/drivers/pci/Kconfig +@@ -4,6 +4,7 @@ + config PCI_MSI + bool "Message Signaled Interrupts (MSI and MSI-X)" + depends on PCI ++ select GENERIC_MSI_IRQ + help + This allows device drivers to enable MSI (Message Signaled + Interrupts). Message Signaled Interrupts enable a device to +@@ -16,6 +17,11 @@ config PCI_MSI + + If you don't know what to do here, say Y. + ++config PCI_MSI_IRQ_DOMAIN ++ bool ++ depends on PCI_MSI ++ select GENERIC_MSI_IRQ_DOMAIN ++ + config PCI_DEBUG + bool "PCI Debugging" + depends on PCI && DEBUG_KERNEL +diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile +index e04fe2d..e9815ac 100644 +--- a/drivers/pci/Makefile ++++ b/drivers/pci/Makefile +@@ -35,6 +35,7 @@ obj-$(CONFIG_PCI_IOV) += iov.o + # + obj-$(CONFIG_ALPHA) += setup-irq.o + obj-$(CONFIG_ARM) += setup-irq.o ++obj-$(CONFIG_ARM64) += setup-irq.o + obj-$(CONFIG_UNICORE32) += setup-irq.o + obj-$(CONFIG_SUPERH) += setup-irq.o + obj-$(CONFIG_MIPS) += setup-irq.o +diff --git a/drivers/pci/access.c b/drivers/pci/access.c +index 7f249b9..b965c12 100644 +--- a/drivers/pci/access.c ++++ b/drivers/pci/access.c +@@ -67,6 +67,93 @@ EXPORT_SYMBOL(pci_bus_write_config_byte); + EXPORT_SYMBOL(pci_bus_write_config_word); + EXPORT_SYMBOL(pci_bus_write_config_dword); + ++int pci_generic_config_read(struct pci_bus *bus, unsigned int devfn, ++ int where, int size, u32 *val) ++{ ++ void __iomem *addr; ++ ++ addr = bus->ops->map_bus(bus, devfn, where); ++ if (!addr) { ++ *val = ~0; ++ return PCIBIOS_DEVICE_NOT_FOUND; ++ } ++ ++ if (size == 1) ++ *val = readb(addr); ++ else if (size == 2) ++ *val = readw(addr); ++ else ++ *val = readl(addr); ++ ++ return PCIBIOS_SUCCESSFUL; ++} ++EXPORT_SYMBOL_GPL(pci_generic_config_read); ++ ++int pci_generic_config_write(struct pci_bus *bus, unsigned int devfn, ++ int where, int size, u32 val) ++{ ++ void __iomem *addr; ++ ++ addr = bus->ops->map_bus(bus, devfn, where); ++ if (!addr) ++ return PCIBIOS_DEVICE_NOT_FOUND; ++ ++ if (size == 1) ++ writeb(val, addr); ++ else if (size == 2) ++ writew(val, addr); ++ else ++ writel(val, addr); ++ ++ return PCIBIOS_SUCCESSFUL; ++} ++EXPORT_SYMBOL_GPL(pci_generic_config_write); ++ ++int pci_generic_config_read32(struct pci_bus *bus, unsigned int devfn, ++ int where, int size, u32 *val) ++{ ++ void __iomem *addr; ++ ++ addr = bus->ops->map_bus(bus, devfn, where & ~0x3); ++ if (!addr) { ++ *val = ~0; ++ return PCIBIOS_DEVICE_NOT_FOUND; ++ } ++ ++ *val = readl(addr); ++ ++ if (size <= 2) ++ *val = (*val >> (8 * (where & 3))) & ((1 << (size * 8)) - 1); ++ ++ return PCIBIOS_SUCCESSFUL; ++} ++EXPORT_SYMBOL_GPL(pci_generic_config_read32); ++ ++int pci_generic_config_write32(struct pci_bus *bus, unsigned int devfn, ++ int where, int size, u32 val) ++{ ++ void __iomem *addr; ++ u32 mask, tmp; ++ ++ addr = bus->ops->map_bus(bus, devfn, where & ~0x3); ++ if (!addr) ++ return PCIBIOS_DEVICE_NOT_FOUND; ++ ++ if (size == 4) { ++ writel(val, addr); ++ return PCIBIOS_SUCCESSFUL; ++ } else { ++ mask = ~(((1 << (size * 8)) - 1) << ((where & 0x3) * 8)); ++ } ++ ++ tmp = readl(addr) & mask; ++ tmp |= val << ((where & 0x3) * 8); ++ writel(tmp, addr); ++ ++ return PCIBIOS_SUCCESSFUL; ++} ++EXPORT_SYMBOL_GPL(pci_generic_config_write32); ++ + /** + * pci_bus_set_ops - Set raw operations of pci bus + * @bus: pci bus struct +diff --git a/drivers/pci/bus.c b/drivers/pci/bus.c +index 8fb1618..90fa3a7 100644 +--- a/drivers/pci/bus.c ++++ b/drivers/pci/bus.c +@@ -20,17 +20,16 @@ + void pci_add_resource_offset(struct list_head *resources, struct resource *res, + resource_size_t offset) + { +- struct pci_host_bridge_window *window; ++ struct resource_entry *entry; + +- window = kzalloc(sizeof(struct pci_host_bridge_window), GFP_KERNEL); +- if (!window) { ++ entry = resource_list_create_entry(res, 0); ++ if (!entry) { + printk(KERN_ERR "PCI: can't add host bridge window %pR\n", res); + return; + } + +- window->res = res; +- window->offset = offset; +- list_add_tail(&window->list, resources); ++ entry->offset = offset; ++ resource_list_add_tail(entry, resources); + } + EXPORT_SYMBOL(pci_add_resource_offset); + +@@ -42,12 +41,7 @@ EXPORT_SYMBOL(pci_add_resource); + + void pci_free_resource_list(struct list_head *resources) + { +- struct pci_host_bridge_window *window, *tmp; +- +- list_for_each_entry_safe(window, tmp, resources, list) { +- list_del(&window->list); +- kfree(window); +- } ++ resource_list_free(resources); + } + EXPORT_SYMBOL(pci_free_resource_list); + +diff --git a/drivers/pci/host-bridge.c b/drivers/pci/host-bridge.c +index 0e5f3c9..3e5bbf9 100644 +--- a/drivers/pci/host-bridge.c ++++ b/drivers/pci/host-bridge.c +@@ -23,6 +23,20 @@ static struct pci_host_bridge *find_pci_host_bridge(struct pci_bus *bus) + return to_pci_host_bridge(root_bus->bridge); + } + ++struct device *pci_get_host_bridge_device(struct pci_dev *dev) ++{ ++ struct pci_bus *root_bus = find_pci_root_bus(dev->bus); ++ struct device *bridge = root_bus->bridge; ++ ++ kobject_get(&bridge->kobj); ++ return bridge; ++} ++ ++void pci_put_host_bridge_device(struct device *dev) ++{ ++ kobject_put(&dev->kobj); ++} ++ + void pci_set_host_bridge_release(struct pci_host_bridge *bridge, + void (*release_fn)(struct pci_host_bridge *), + void *release_data) +@@ -35,10 +49,10 @@ void pcibios_resource_to_bus(struct pci_bus *bus, struct pci_bus_region *region, + struct resource *res) + { + struct pci_host_bridge *bridge = find_pci_host_bridge(bus); +- struct pci_host_bridge_window *window; ++ struct resource_entry *window; + resource_size_t offset = 0; + +- list_for_each_entry(window, &bridge->windows, list) { ++ resource_list_for_each_entry(window, &bridge->windows) { + if (resource_contains(window->res, res)) { + offset = window->offset; + break; +@@ -60,10 +74,10 @@ void pcibios_bus_to_resource(struct pci_bus *bus, struct resource *res, + struct pci_bus_region *region) + { + struct pci_host_bridge *bridge = find_pci_host_bridge(bus); +- struct pci_host_bridge_window *window; ++ struct resource_entry *window; + resource_size_t offset = 0; + +- list_for_each_entry(window, &bridge->windows, list) { ++ resource_list_for_each_entry(window, &bridge->windows) { + struct pci_bus_region bus_region; + + if (resource_type(res) != resource_type(window->res)) +diff --git a/drivers/pci/host/Kconfig b/drivers/pci/host/Kconfig +index 3dc25fa..dafa3dc 100644 +--- a/drivers/pci/host/Kconfig ++++ b/drivers/pci/host/Kconfig +@@ -50,7 +50,7 @@ config PCI_RCAR_GEN2_PCIE + + config PCI_HOST_GENERIC + bool "Generic PCI host controller" +- depends on ARM && OF ++ depends on (ARM || ARM64) && OF + help + Say Y here if you want to support a simple generic PCI host + controller, such as the one emulated by kvmtool. +@@ -86,9 +86,26 @@ config PCI_XGENE + depends on ARCH_XGENE + depends on OF + select PCIEPORTBUS ++ select PCI_MSI_IRQ_DOMAIN if PCI_MSI + help + Say Y here if you want internal PCI support on APM X-Gene SoC. + There are 5 internal PCIe ports available. Each port is GEN3 capable + and have varied lanes from x1 to x8. + ++config PCI_XGENE_MSI ++ bool "X-Gene v1 PCIe MSI feature" ++ depends on PCI_XGENE && PCI_MSI ++ default y ++ help ++ Say Y here if you want PCIe MSI support for the APM X-Gene v1 SoC. ++ This MSI driver supports 5 PCIe ports on the APM X-Gene v1 SoC. ++ ++config PCI_LAYERSCAPE ++ bool "Freescale Layerscape PCIe controller" ++ depends on OF && (ARM || ARCH_LAYERSCAPE) ++ select PCIE_DW ++ select MFD_SYSCON ++ help ++ Say Y here if you want PCIe controller support on Layerscape SoCs. ++ + endmenu +diff --git a/drivers/pci/host/Makefile b/drivers/pci/host/Makefile +index 26b3461..029685e 100644 +--- a/drivers/pci/host/Makefile ++++ b/drivers/pci/host/Makefile +@@ -1,3 +1,4 @@ ++obj-$(CONFIG_PCIE_DW_BASE) += pcie-designware-base.o + obj-$(CONFIG_PCIE_DW) += pcie-designware.o + obj-$(CONFIG_PCI_DRA7XX) += pci-dra7xx.o + obj-$(CONFIG_PCI_EXYNOS) += pci-exynos.o +@@ -11,3 +12,5 @@ obj-$(CONFIG_PCIE_SPEAR13XX) += pcie-spear13xx.o + obj-$(CONFIG_PCI_KEYSTONE) += pci-keystone-dw.o pci-keystone.o + obj-$(CONFIG_PCIE_XILINX) += pcie-xilinx.o + obj-$(CONFIG_PCI_XGENE) += pci-xgene.o ++obj-$(CONFIG_PCI_XGENE_MSI) += pci-xgene-msi.o ++obj-$(CONFIG_PCI_LAYERSCAPE) += pci-layerscape.o +diff --git a/drivers/pci/host/pci-dra7xx.c b/drivers/pci/host/pci-dra7xx.c +index 52b34fe..84a45cf 100644 +--- a/drivers/pci/host/pci-dra7xx.c ++++ b/drivers/pci/host/pci-dra7xx.c +@@ -61,6 +61,7 @@ + + #define PCIECTRL_DRA7XX_CONF_PHY_CS 0x010C + #define LINK_UP BIT(16) ++#define DRA7XX_CPU_TO_BUS_ADDR 0x0FFFFFFF + + struct dra7xx_pcie { + void __iomem *base; +@@ -144,6 +145,12 @@ static void dra7xx_pcie_enable_interrupts(struct pcie_port *pp) + static void dra7xx_pcie_host_init(struct pcie_port *pp) + { + dw_pcie_setup_rc(pp); ++ ++ pp->io_base &= DRA7XX_CPU_TO_BUS_ADDR; ++ pp->mem_base &= DRA7XX_CPU_TO_BUS_ADDR; ++ pp->cfg0_base &= DRA7XX_CPU_TO_BUS_ADDR; ++ pp->cfg1_base &= DRA7XX_CPU_TO_BUS_ADDR; ++ + dra7xx_pcie_establish_link(pp); + if (IS_ENABLED(CONFIG_PCI_MSI)) + dw_pcie_msi_init(pp); +@@ -160,7 +167,6 @@ static int dra7xx_pcie_intx_map(struct irq_domain *domain, unsigned int irq, + { + irq_set_chip_and_handler(irq, &dummy_irq_chip, handle_simple_irq); + irq_set_chip_data(irq, domain->host_data); +- set_irq_flags(irq, IRQF_VALID); + + return 0; + } +diff --git a/drivers/pci/host/pci-exynos.c b/drivers/pci/host/pci-exynos.c +index c5d0ca3..2fd6b4e 100644 +--- a/drivers/pci/host/pci-exynos.c ++++ b/drivers/pci/host/pci-exynos.c +@@ -466,7 +466,7 @@ static int exynos_pcie_rd_own_conf(struct pcie_port *pp, int where, int size, + int ret; + + exynos_pcie_sideband_dbi_r_mode(pp, true); +- ret = dw_pcie_cfg_read(pp->dbi_base + (where & ~0x3), where, size, val); ++ ret = dw_pcie_cfg_read(pp->dbi_base + where, size, val); + exynos_pcie_sideband_dbi_r_mode(pp, false); + return ret; + } +@@ -477,8 +477,7 @@ static int exynos_pcie_wr_own_conf(struct pcie_port *pp, int where, int size, + int ret; + + exynos_pcie_sideband_dbi_w_mode(pp, true); +- ret = dw_pcie_cfg_write(pp->dbi_base + (where & ~0x3), +- where, size, val); ++ ret = dw_pcie_cfg_write(pp->dbi_base + where, size, val); + exynos_pcie_sideband_dbi_w_mode(pp, false); + return ret; + } +diff --git a/drivers/pci/host/pci-host-generic.c b/drivers/pci/host/pci-host-generic.c +index 3d2076f..83fb705 100644 +--- a/drivers/pci/host/pci-host-generic.c ++++ b/drivers/pci/host/pci-host-generic.c +@@ -32,13 +32,22 @@ struct gen_pci_cfg_bus_ops { + + struct gen_pci_cfg_windows { + struct resource res; +- struct resource bus_range; ++ struct resource *bus_range; + void __iomem **win; + + const struct gen_pci_cfg_bus_ops *ops; + }; + ++/* ++ * ARM pcibios functions expect the ARM struct pci_sys_data as the PCI ++ * sysdata. Add pci_sys_data as the first element in struct gen_pci so ++ * that when we use a gen_pci pointer as sysdata, it is also a pointer to ++ * a struct pci_sys_data. ++ */ + struct gen_pci { ++#ifdef CONFIG_ARM ++ struct pci_sys_data sys; ++#endif + struct pci_host_bridge host; + struct gen_pci_cfg_windows cfg; + struct list_head resources; +@@ -48,9 +57,8 @@ static void __iomem *gen_pci_map_cfg_bus_cam(struct pci_bus *bus, + unsigned int devfn, + int where) + { +- struct pci_sys_data *sys = bus->sysdata; +- struct gen_pci *pci = sys->private_data; +- resource_size_t idx = bus->number - pci->cfg.bus_range.start; ++ struct gen_pci *pci = bus->sysdata; ++ resource_size_t idx = bus->number - pci->cfg.bus_range->start; + + return pci->cfg.win[idx] + ((devfn << 8) | where); + } +@@ -64,9 +72,8 @@ static void __iomem *gen_pci_map_cfg_bus_ecam(struct pci_bus *bus, + unsigned int devfn, + int where) + { +- struct pci_sys_data *sys = bus->sysdata; +- struct gen_pci *pci = sys->private_data; +- resource_size_t idx = bus->number - pci->cfg.bus_range.start; ++ struct gen_pci *pci = bus->sysdata; ++ resource_size_t idx = bus->number - pci->cfg.bus_range->start; + + return pci->cfg.win[idx] + ((devfn << 12) | where); + } +@@ -76,55 +83,9 @@ static struct gen_pci_cfg_bus_ops gen_pci_cfg_ecam_bus_ops = { + .map_bus = gen_pci_map_cfg_bus_ecam, + }; + +-static int gen_pci_config_read(struct pci_bus *bus, unsigned int devfn, +- int where, int size, u32 *val) +-{ +- void __iomem *addr; +- struct pci_sys_data *sys = bus->sysdata; +- struct gen_pci *pci = sys->private_data; +- +- addr = pci->cfg.ops->map_bus(bus, devfn, where); +- +- switch (size) { +- case 1: +- *val = readb(addr); +- break; +- case 2: +- *val = readw(addr); +- break; +- default: +- *val = readl(addr); +- } +- +- return PCIBIOS_SUCCESSFUL; +-} +- +-static int gen_pci_config_write(struct pci_bus *bus, unsigned int devfn, +- int where, int size, u32 val) +-{ +- void __iomem *addr; +- struct pci_sys_data *sys = bus->sysdata; +- struct gen_pci *pci = sys->private_data; +- +- addr = pci->cfg.ops->map_bus(bus, devfn, where); +- +- switch (size) { +- case 1: +- writeb(val, addr); +- break; +- case 2: +- writew(val, addr); +- break; +- default: +- writel(val, addr); +- } +- +- return PCIBIOS_SUCCESSFUL; +-} +- + static struct pci_ops gen_pci_ops = { +- .read = gen_pci_config_read, +- .write = gen_pci_config_write, ++ .read = pci_generic_config_read, ++ .write = pci_generic_config_write, + }; + + static const struct of_device_id gen_pci_of_match[] = { +@@ -138,106 +99,50 @@ static const struct of_device_id gen_pci_of_match[] = { + }; + MODULE_DEVICE_TABLE(of, gen_pci_of_match); + +-static int gen_pci_calc_io_offset(struct device *dev, +- struct of_pci_range *range, +- struct resource *res, +- resource_size_t *offset) +-{ +- static atomic_t wins = ATOMIC_INIT(0); +- int err, idx, max_win; +- unsigned int window; +- +- if (!PAGE_ALIGNED(range->cpu_addr)) +- return -EINVAL; +- +- max_win = (IO_SPACE_LIMIT + 1) / SZ_64K; +- idx = atomic_inc_return(&wins); +- if (idx > max_win) +- return -ENOSPC; +- +- window = (idx - 1) * SZ_64K; +- err = pci_ioremap_io(window, range->cpu_addr); +- if (err) +- return err; +- +- of_pci_range_to_resource(range, dev->of_node, res); +- res->start = window; +- res->end = res->start + range->size - 1; +- *offset = window - range->pci_addr; +- return 0; +-} +- +-static int gen_pci_calc_mem_offset(struct device *dev, +- struct of_pci_range *range, +- struct resource *res, +- resource_size_t *offset) +-{ +- of_pci_range_to_resource(range, dev->of_node, res); +- *offset = range->cpu_addr - range->pci_addr; +- return 0; +-} +- + static void gen_pci_release_of_pci_ranges(struct gen_pci *pci) + { +- struct pci_host_bridge_window *win; +- +- list_for_each_entry(win, &pci->resources, list) +- release_resource(win->res); +- + pci_free_resource_list(&pci->resources); + } + + static int gen_pci_parse_request_of_pci_ranges(struct gen_pci *pci) + { +- struct of_pci_range range; +- struct of_pci_range_parser parser; + int err, res_valid = 0; + struct device *dev = pci->host.dev.parent; + struct device_node *np = dev->of_node; ++ resource_size_t iobase; ++ struct resource_entry *win; + +- if (of_pci_range_parser_init(&parser, np)) { +- dev_err(dev, "missing \"ranges\" property\n"); +- return -EINVAL; +- } +- +- for_each_of_pci_range(&parser, &range) { +- struct resource *parent, *res; +- resource_size_t offset; +- u32 restype = range.flags & IORESOURCE_TYPE_BITS; ++ err = of_pci_get_host_bridge_resources(np, 0, 0xff, &pci->resources, ++ &iobase); ++ if (err) ++ return err; + +- res = devm_kmalloc(dev, sizeof(*res), GFP_KERNEL); +- if (!res) { +- err = -ENOMEM; +- goto out_release_res; +- } ++ resource_list_for_each_entry(win, &pci->resources) { ++ struct resource *parent, *res = win->res; + +- switch (restype) { ++ switch (resource_type(res)) { + case IORESOURCE_IO: + parent = &ioport_resource; +- err = gen_pci_calc_io_offset(dev, &range, res, &offset); ++ err = pci_remap_iospace(res, iobase); ++ if (err) { ++ dev_warn(dev, "error %d: failed to map resource %pR\n", ++ err, res); ++ continue; ++ } + break; + case IORESOURCE_MEM: + parent = &iomem_resource; +- err = gen_pci_calc_mem_offset(dev, &range, res, &offset); +- res_valid |= !(res->flags & IORESOURCE_PREFETCH || err); ++ res_valid |= !(res->flags & IORESOURCE_PREFETCH); + break; ++ case IORESOURCE_BUS: ++ pci->cfg.bus_range = res; + default: +- err = -EINVAL; + continue; + } + +- if (err) { +- dev_warn(dev, +- "error %d: failed to add resource [type 0x%x, %lld bytes]\n", +- err, restype, range.size); +- continue; +- } +- +- err = request_resource(parent, res); ++ err = devm_request_resource(dev, parent, res); + if (err) + goto out_release_res; +- +- pci_add_resource_offset(&pci->resources, res, offset); + } + + if (!res_valid) { +@@ -262,38 +167,30 @@ static int gen_pci_parse_map_cfg_windows(struct gen_pci *pci) + struct device *dev = pci->host.dev.parent; + struct device_node *np = dev->of_node; + +- if (of_pci_parse_bus_range(np, &pci->cfg.bus_range)) +- pci->cfg.bus_range = (struct resource) { +- .name = np->name, +- .start = 0, +- .end = 0xff, +- .flags = IORESOURCE_BUS, +- }; +- + err = of_address_to_resource(np, 0, &pci->cfg.res); + if (err) { + dev_err(dev, "missing \"reg\" property\n"); + return err; + } + +- pci->cfg.win = devm_kcalloc(dev, resource_size(&pci->cfg.bus_range), ++ /* Limit the bus-range to fit within reg */ ++ bus_max = pci->cfg.bus_range->start + ++ (resource_size(&pci->cfg.res) >> pci->cfg.ops->bus_shift) - 1; ++ pci->cfg.bus_range->end = min_t(resource_size_t, ++ pci->cfg.bus_range->end, bus_max); ++ ++ pci->cfg.win = devm_kcalloc(dev, resource_size(pci->cfg.bus_range), + sizeof(*pci->cfg.win), GFP_KERNEL); + if (!pci->cfg.win) + return -ENOMEM; + +- /* Limit the bus-range to fit within reg */ +- bus_max = pci->cfg.bus_range.start + +- (resource_size(&pci->cfg.res) >> pci->cfg.ops->bus_shift) - 1; +- pci->cfg.bus_range.end = min_t(resource_size_t, pci->cfg.bus_range.end, +- bus_max); +- + /* Map our Configuration Space windows */ + if (!devm_request_mem_region(dev, pci->cfg.res.start, + resource_size(&pci->cfg.res), + "Configuration Space")) + return -ENOMEM; + +- bus_range = &pci->cfg.bus_range; ++ bus_range = pci->cfg.bus_range; + for (busn = bus_range->start; busn <= bus_range->end; ++busn) { + u32 idx = busn - bus_range->start; + u32 sz = 1 << pci->cfg.ops->bus_shift; +@@ -305,18 +202,9 @@ static int gen_pci_parse_map_cfg_windows(struct gen_pci *pci) + return -ENOMEM; + } + +- /* Register bus resource */ +- pci_add_resource(&pci->resources, bus_range); + return 0; + } + +-static int gen_pci_setup(int nr, struct pci_sys_data *sys) +-{ +- struct gen_pci *pci = sys->private_data; +- list_splice_init(&pci->resources, &sys->resources); +- return 1; +-} +- + static int gen_pci_probe(struct platform_device *pdev) + { + int err; +@@ -326,13 +214,7 @@ static int gen_pci_probe(struct platform_device *pdev) + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct gen_pci *pci = devm_kzalloc(dev, sizeof(*pci), GFP_KERNEL); +- struct hw_pci hw = { +- .nr_controllers = 1, +- .private_data = (void **)&pci, +- .setup = gen_pci_setup, +- .map_irq = of_irq_parse_and_map_pci, +- .ops = &gen_pci_ops, +- }; ++ struct pci_bus *bus, *child; + + if (!pci) + return -ENOMEM; +@@ -353,6 +235,7 @@ static int gen_pci_probe(struct platform_device *pdev) + + of_id = of_match_node(gen_pci_of_match, np); + pci->cfg.ops = of_id->data; ++ gen_pci_ops.map_bus = pci->cfg.ops->map_bus; + pci->host.dev.parent = dev; + INIT_LIST_HEAD(&pci->host.windows); + INIT_LIST_HEAD(&pci->resources); +@@ -369,7 +252,27 @@ static int gen_pci_probe(struct platform_device *pdev) + return err; + } + +- pci_common_init_dev(dev, &hw); ++ /* Do not reassign resources if probe only */ ++ if (!pci_has_flag(PCI_PROBE_ONLY)) ++ pci_add_flags(PCI_REASSIGN_ALL_RSRC | PCI_REASSIGN_ALL_BUS); ++ ++ bus = pci_scan_root_bus(dev, 0, &gen_pci_ops, pci, &pci->resources); ++ if (!bus) { ++ dev_err(dev, "Scanning rootbus failed"); ++ return -ENODEV; ++ } ++ ++ pci_fixup_irqs(pci_common_swizzle, of_irq_parse_and_map_pci); ++ ++ if (!pci_has_flag(PCI_PROBE_ONLY)) { ++ pci_bus_size_bridges(bus); ++ pci_bus_assign_resources(bus); ++ ++ list_for_each_entry(child, &bus->children, node) ++ pcie_bus_configure_settings(child); ++ } ++ ++ pci_bus_add_devices(bus); + return 0; + } + +diff --git a/drivers/pci/host/pci-keystone-dw.c b/drivers/pci/host/pci-keystone-dw.c +index 34086ce..c1b5980 100644 +--- a/drivers/pci/host/pci-keystone-dw.c ++++ b/drivers/pci/host/pci-keystone-dw.c +@@ -70,7 +70,7 @@ static inline void update_reg_offset_bit_pos(u32 offset, u32 *reg_offset, + *bit_pos = offset >> 3; + } + +-u32 ks_dw_pcie_get_msi_addr(struct pcie_port *pp) ++phys_addr_t ks_dw_pcie_get_msi_addr(struct pcie_port *pp) + { + struct keystone_pcie *ks_pcie = to_keystone_pcie(pp); + +@@ -104,14 +104,13 @@ static void ks_dw_pcie_msi_irq_ack(struct irq_data *d) + { + u32 offset, reg_offset, bit_pos; + struct keystone_pcie *ks_pcie; +- unsigned int irq = d->irq; + struct msi_desc *msi; + struct pcie_port *pp; + +- msi = irq_get_msi_desc(irq); +- pp = sys_to_pcie(msi->dev->bus->sysdata); ++ msi = irq_data_get_msi_desc(d); ++ pp = sys_to_pcie(msi_desc_to_pci_sysdata(msi)); + ks_pcie = to_keystone_pcie(pp); +- offset = irq - irq_linear_revmap(pp->irq_domain, 0); ++ offset = d->irq - irq_linear_revmap(pp->irq_domain, 0); + update_reg_offset_bit_pos(offset, ®_offset, &bit_pos); + + writel(BIT(bit_pos), +@@ -142,20 +141,19 @@ void ks_dw_pcie_msi_clear_irq(struct pcie_port *pp, int irq) + static void ks_dw_pcie_msi_irq_mask(struct irq_data *d) + { + struct keystone_pcie *ks_pcie; +- unsigned int irq = d->irq; + struct msi_desc *msi; + struct pcie_port *pp; + u32 offset; + +- msi = irq_get_msi_desc(irq); +- pp = sys_to_pcie(msi->dev->bus->sysdata); ++ msi = irq_data_get_msi_desc(d); ++ pp = sys_to_pcie(msi_desc_to_pci_sysdata(msi)); + ks_pcie = to_keystone_pcie(pp); +- offset = irq - irq_linear_revmap(pp->irq_domain, 0); ++ offset = d->irq - irq_linear_revmap(pp->irq_domain, 0); + + /* Mask the end point if PVM implemented */ + if (IS_ENABLED(CONFIG_PCI_MSI)) { + if (msi->msi_attrib.maskbit) +- mask_msi_irq(d); ++ pci_msi_mask_irq(d); + } + + ks_dw_pcie_msi_clear_irq(pp, offset); +@@ -164,20 +162,19 @@ static void ks_dw_pcie_msi_irq_mask(struct irq_data *d) + static void ks_dw_pcie_msi_irq_unmask(struct irq_data *d) + { + struct keystone_pcie *ks_pcie; +- unsigned int irq = d->irq; + struct msi_desc *msi; + struct pcie_port *pp; + u32 offset; + +- msi = irq_get_msi_desc(irq); +- pp = sys_to_pcie(msi->dev->bus->sysdata); ++ msi = irq_data_get_msi_desc(d); ++ pp = sys_to_pcie(msi_desc_to_pci_sysdata(msi)); + ks_pcie = to_keystone_pcie(pp); +- offset = irq - irq_linear_revmap(pp->irq_domain, 0); ++ offset = d->irq - irq_linear_revmap(pp->irq_domain, 0); + + /* Mask the end point if PVM implemented */ + if (IS_ENABLED(CONFIG_PCI_MSI)) { + if (msi->msi_attrib.maskbit) +- unmask_msi_irq(d); ++ pci_msi_unmask_irq(d); + } + + ks_dw_pcie_msi_set_irq(pp, offset); +@@ -196,7 +193,6 @@ static int ks_dw_pcie_msi_map(struct irq_domain *domain, unsigned int irq, + irq_set_chip_and_handler(irq, &ks_dw_pcie_msi_irq_chip, + handle_level_irq); + irq_set_chip_data(irq, domain->host_data); +- set_irq_flags(irq, IRQF_VALID); + + return 0; + } +@@ -205,7 +201,7 @@ const struct irq_domain_ops ks_dw_pcie_msi_domain_ops = { + .map = ks_dw_pcie_msi_map, + }; + +-int ks_dw_pcie_msi_host_init(struct pcie_port *pp, struct msi_chip *chip) ++int ks_dw_pcie_msi_host_init(struct pcie_port *pp, struct msi_controller *chip) + { + struct keystone_pcie *ks_pcie = to_keystone_pcie(pp); + int i; +@@ -277,7 +273,6 @@ static int ks_dw_pcie_init_legacy_irq_map(struct irq_domain *d, + irq_set_chip_and_handler(irq, &ks_dw_pcie_legacy_irq_chip, + handle_level_irq); + irq_set_chip_data(irq, d->host_data); +- set_irq_flags(irq, IRQF_VALID); + + return 0; + } +@@ -327,7 +322,7 @@ static void ks_dw_pcie_clear_dbi_mode(void __iomem *reg_virt) + void ks_dw_pcie_setup_rc_app_regs(struct keystone_pcie *ks_pcie) + { + struct pcie_port *pp = &ks_pcie->pp; +- u32 start = pp->mem.start, end = pp->mem.end; ++ u32 start = pp->mem->start, end = pp->mem->end; + int i, tr_size; + + /* Disable BARs for inbound access */ +@@ -403,7 +398,7 @@ int ks_dw_pcie_rd_other_conf(struct pcie_port *pp, struct pci_bus *bus, + + addr = ks_pcie_cfg_setup(ks_pcie, bus_num, devfn); + +- return dw_pcie_cfg_read(addr + (where & ~0x3), where, size, val); ++ return dw_pcie_cfg_read(addr + where, size, val); + } + + int ks_dw_pcie_wr_other_conf(struct pcie_port *pp, struct pci_bus *bus, +@@ -415,7 +410,7 @@ int ks_dw_pcie_wr_other_conf(struct pcie_port *pp, struct pci_bus *bus, + + addr = ks_pcie_cfg_setup(ks_pcie, bus_num, devfn); + +- return dw_pcie_cfg_write(addr + (where & ~0x3), where, size, val); ++ return dw_pcie_cfg_write(addr + where, size, val); + } + + /** +diff --git a/drivers/pci/host/pci-keystone.h b/drivers/pci/host/pci-keystone.h +index 1fc1fce..f0944e8 100644 +--- a/drivers/pci/host/pci-keystone.h ++++ b/drivers/pci/host/pci-keystone.h +@@ -37,7 +37,7 @@ struct keystone_pcie { + + /* Keystone DW specific MSI controller APIs/definitions */ + void ks_dw_pcie_handle_msi_irq(struct keystone_pcie *ks_pcie, int offset); +-u32 ks_dw_pcie_get_msi_addr(struct pcie_port *pp); ++phys_addr_t ks_dw_pcie_get_msi_addr(struct pcie_port *pp); + + /* Keystone specific PCI controller APIs */ + void ks_dw_pcie_enable_legacy_irqs(struct keystone_pcie *ks_pcie); +@@ -55,4 +55,4 @@ void ks_dw_pcie_msi_set_irq(struct pcie_port *pp, int irq); + void ks_dw_pcie_msi_clear_irq(struct pcie_port *pp, int irq); + void ks_dw_pcie_v3_65_scan_bus(struct pcie_port *pp); + int ks_dw_pcie_msi_host_init(struct pcie_port *pp, +- struct msi_chip *chip); ++ struct msi_controller *chip); +diff --git a/drivers/pci/host/pci-layerscape.c b/drivers/pci/host/pci-layerscape.c +new file mode 100644 +index 0000000..baa1232 +--- /dev/null ++++ b/drivers/pci/host/pci-layerscape.c +@@ -0,0 +1,729 @@ ++/* ++ * PCIe host controller driver for Freescale Layerscape SoCs ++ * ++ * Copyright (C) 2014 Freescale Semiconductor. ++ * ++ * Author: Minghuan Lian ++ * ++ * 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 ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "pcie-designware.h" ++ ++/* PEX1/2 Misc Ports Status Register */ ++#define SCFG_PEXMSCPORTSR(pex_idx) (0x94 + (pex_idx) * 4) ++#define SCFG_PEXPMWRCR(pex_idx) (0x5c + (pex_idx) * 0x64) ++#define LTSSM_STATE_SHIFT 20 ++#define LTSSM_STATE_MASK 0x3f ++#define LTSSM_PCIE_L0 0x11 /* L0 state */ ++#define LTSSM_PCIE_L2_IDLE 0x15 /* L2 idle state */ ++ ++#define PCIE_SRIOV_OFFSET 0x178 ++ ++/* CS2 */ ++#define PCIE_CS2_OFFSET 0x1000 /* For PCIe without SR-IOV */ ++#define PCIE_ENABLE_CS2 0x80000000 /* For PCIe with SR-IOV */ ++ ++/* PEX Internal Configuration Registers */ ++#define PCIE_STRFMR1 0x71c /* Symbol Timer & Filter Mask Register1 */ ++#define PCIE_DBI_RO_WR_EN 0x8bc /* DBI Read-Only Write Enable Register */ ++#define PCIE_ABSERR 0x8d0 /* Bridge Slave Error Response Register */ ++#define PCIE_ABSERR_SETTING 0x9401 /* Forward error of non-posted request */ ++ ++/* PEX LUT registers */ ++#define PCIE_LUT_DBG 0x7FC /* PEX LUT Debug Register */ ++#define PCIE_LUT_CTRL0 0x7f8 ++#define PCIE_LUT_UDR(n) (0x800 + (n) * 8) ++#define PCIE_LUT_LDR(n) (0x804 + (n) * 8) ++#define PCIE_LUT_MASK_ALL 0xffff ++#define PCIE_LUT_DR_NUM 32 ++#define PCIE_LUT_ENABLE (1 << 31) ++ ++#define PCIE_PM_SCR 0x44 ++#define PCIE_PM_SCR_PMEEN 0x10 ++#define PCIE_PM_SCR_PMEPS_D0 0xfffc ++#define PCIE_PM_SCR_PMEPS_D3 0x3 ++#define PCIE_PM_SCR_PME_STATE 0x8000 ++ ++#define PCIE_PEX_DCR 0x78 ++#define PCIE_PEX_DCR_AUXPOWEREN 0x0400 ++ ++#define PCIE_PEX_SSR 0x8a ++#define PCIE_PEX_SSR_PDS 0x40 ++ ++#define PCIE_PEX_RCR 0x8c ++#define PCIE_PEX_RCR_PMEIE 0x0008 ++ ++#define PCIE_PEX_RSR 0x90 ++#define PCIE_PEX_PMES 0x00010000 ++ ++#define QIXIS_RST_FORCE_3 0x45 ++#define QIXIS_RST_FORCE_3_PCIESLOT 0xe0 ++ ++#define CPLD_RST_PCIE_SLOT 0x14 ++#define CPLD_RST_PCIESLOT 0x3 ++ ++#define PCIE_IATU_NUM 6 ++ ++struct ls_pcie; ++ ++struct ls_pcie_pm_data { ++ void __iomem *fpga; ++ void __iomem *cpld; ++}; ++ ++struct ls_pcie_pm_ops { ++ u32 (*get_link_state)(struct ls_pcie *pcie); ++ int (*send_turn_off_message)(struct ls_pcie *pcie); ++ void (*clear_turn_off_message)(struct ls_pcie *pcie); ++ void (*reset_slot)(struct ls_pcie *pcie, ++ struct ls_pcie_pm_data *pm_data); ++}; ++ ++struct ls_pcie_drvdata { ++ u32 lut_offset; ++ u32 ltssm_shift; ++ struct pcie_host_ops *ops; ++ struct ls_pcie_pm_ops *pm; ++}; ++ ++struct ls_pcie { ++ struct list_head list_node; ++ void __iomem *dbi; ++ void __iomem *lut; ++ struct regmap *scfg; ++ struct pcie_port pp; ++ const struct ls_pcie_drvdata *drvdata; ++ struct ls_pcie_pm_data pm_data; ++ int index; ++ const u32 *avail_streamids; ++ int streamid_index; ++ int pme_irq; ++ bool in_slot; ++}; ++ ++#define to_ls_pcie(x) container_of(x, struct ls_pcie, pp) ++ ++static void ls_pcie_host_init(struct pcie_port *pp); ++ ++u32 set_pcie_streamid_translation(struct pci_dev *pdev, u32 devid) ++{ ++ u32 index, streamid; ++ struct pcie_port *pp = pdev->bus->sysdata; ++ struct ls_pcie *pcie = to_ls_pcie(pp); ++ ++ if (!pcie->avail_streamids || !pcie->streamid_index) ++ return ~(u32)0; ++ ++ index = --pcie->streamid_index; ++ /* mask is set as all zeroes, want to match all bits */ ++ iowrite32((devid << 16), pcie->lut + PCIE_LUT_UDR(index)); ++ streamid = be32_to_cpup(&pcie->avail_streamids[index]); ++ iowrite32(streamid | PCIE_LUT_ENABLE, pcie->lut + PCIE_LUT_LDR(index)); ++ ++ return streamid; ++} ++ ++LIST_HEAD(hose_list); ++ ++static bool ls_pcie_is_bridge(struct ls_pcie *pcie) ++{ ++ u32 header_type; ++ ++ header_type = ioread8(pcie->dbi + PCI_HEADER_TYPE); ++ header_type &= 0x7f; ++ ++ return header_type == PCI_HEADER_TYPE_BRIDGE; ++} ++ ++/* Clear multi-function bit */ ++static void ls_pcie_clear_multifunction(struct ls_pcie *pcie) ++{ ++ iowrite8(PCI_HEADER_TYPE_BRIDGE, pcie->dbi + PCI_HEADER_TYPE); ++} ++ ++/* Fix class value */ ++static void ls_pcie_fix_class(struct ls_pcie *pcie) ++{ ++ iowrite16(PCI_CLASS_BRIDGE_PCI, pcie->dbi + PCI_CLASS_DEVICE); ++} ++ ++/* Drop MSG TLP except for Vendor MSG */ ++static void ls_pcie_drop_msg_tlp(struct ls_pcie *pcie) ++{ ++ u32 val; ++ ++ val = ioread32(pcie->dbi + PCIE_STRFMR1); ++ val &= 0xDFFFFFFF; ++ iowrite32(val, pcie->dbi + PCIE_STRFMR1); ++} ++ ++/* Disable all bars in RC mode */ ++static void ls_pcie_disable_bars(struct ls_pcie *pcie) ++{ ++ u32 header; ++ ++ header = ioread32(pcie->dbi + PCIE_SRIOV_OFFSET); ++ if (PCI_EXT_CAP_ID(header) == PCI_EXT_CAP_ID_SRIOV) { ++ iowrite32(PCIE_ENABLE_CS2, pcie->lut + PCIE_LUT_CTRL0); ++ iowrite32(0, pcie->dbi + PCI_BASE_ADDRESS_0); ++ iowrite32(0, pcie->dbi + PCI_BASE_ADDRESS_1); ++ iowrite32(0, pcie->dbi + PCI_ROM_ADDRESS1); ++ iowrite32(0, pcie->lut + PCIE_LUT_CTRL0); ++ } else { ++ iowrite32(0, ++ pcie->dbi + PCIE_CS2_OFFSET + PCI_BASE_ADDRESS_0); ++ iowrite32(0, ++ pcie->dbi + PCIE_CS2_OFFSET + PCI_BASE_ADDRESS_1); ++ iowrite32(0, ++ pcie->dbi + PCIE_CS2_OFFSET + PCI_ROM_ADDRESS1); ++ } ++} ++ ++static void ls_pcie_disable_outbound_atus(struct ls_pcie *pcie) ++{ ++ int i; ++ ++ for (i = 0; i < PCIE_IATU_NUM; i++) ++ dw_pcie_disable_outbound_atu(&pcie->pp, i); ++} ++ ++/* Forward error response of outbound non-posted requests */ ++static void ls_pcie_fix_error_response(struct ls_pcie *pcie) ++{ ++ iowrite32(PCIE_ABSERR_SETTING, pcie->dbi + PCIE_ABSERR); ++} ++ ++static int ls1021_pcie_link_up(struct pcie_port *pp) ++{ ++ u32 state; ++ struct ls_pcie *pcie = to_ls_pcie(pp); ++ ++ if (!pcie->scfg) ++ return 0; ++ ++ regmap_read(pcie->scfg, SCFG_PEXMSCPORTSR(pcie->index), &state); ++ state = (state >> LTSSM_STATE_SHIFT) & LTSSM_STATE_MASK; ++ ++ if (state < LTSSM_PCIE_L0) ++ return 0; ++ ++ return 1; ++} ++ ++static u32 ls1021_pcie_get_link_state(struct ls_pcie *pcie) ++{ ++ u32 state; ++ ++ if (!pcie->scfg) ++ return 0; ++ ++ regmap_read(pcie->scfg, SCFG_PEXMSCPORTSR(pcie->index), &state); ++ state = (state >> LTSSM_STATE_SHIFT) & LTSSM_STATE_MASK; ++ ++ return state; ++} ++ ++static int ls1021_pcie_send_turn_off_message(struct ls_pcie *pcie) ++{ ++ u32 val; ++ ++ if (!pcie->scfg) ++ return -EINVAL; ++ ++ /* Send Turn_off message */ ++ regmap_read(pcie->scfg, SCFG_PEXPMWRCR(pcie->index), &val); ++ val |= 0x80000000; ++ regmap_write(pcie->scfg, SCFG_PEXPMWRCR(pcie->index), val); ++ ++ return 0; ++} ++ ++static void ls1021_pcie_clear_turn_off_message(struct ls_pcie *pcie) ++{ ++ u32 val; ++ ++ if (!pcie->scfg) ++ return; ++ ++ /* Clear Turn_off message */ ++ regmap_read(pcie->scfg, SCFG_PEXPMWRCR(pcie->index), &val); ++ val &= 0x00000000; ++ regmap_write(pcie->scfg, SCFG_PEXPMWRCR(pcie->index), val); ++} ++ ++static void ls1021_pcie_reset_slot(struct ls_pcie *pcie, ++ struct ls_pcie_pm_data *pm_data) ++{ ++ u8 val; ++ ++ /* Try to reset PCIe slot to relink EP */ ++ if (pm_data->fpga) { ++ /* PULL DOWN PCIe RST# */ ++ val = ioread8(pm_data->fpga + QIXIS_RST_FORCE_3); ++ val |= QIXIS_RST_FORCE_3_PCIESLOT; ++ iowrite8(val, pm_data->fpga + QIXIS_RST_FORCE_3); ++ ++ /* PULL ON PCIe RST# */ ++ val = ioread8(pm_data->fpga + QIXIS_RST_FORCE_3); ++ val &= 0x0; ++ iowrite8(val, pm_data->fpga + QIXIS_RST_FORCE_3); ++ } ++ ++ if (pm_data->cpld) { ++ /* PULL DOWN PCIe RST# */ ++ val = ioread8(pm_data->cpld + CPLD_RST_PCIE_SLOT); ++ val &= 0x0; ++ iowrite8(val, pm_data->cpld + CPLD_RST_PCIE_SLOT); ++ ++ /* PULL ON PCIe RST# */ ++ val = ioread8(pm_data->cpld + CPLD_RST_PCIE_SLOT); ++ val |= CPLD_RST_PCIESLOT; ++ iowrite8(val, pm_data->cpld + CPLD_RST_PCIE_SLOT); ++ } ++} ++ ++static void ls1021_pcie_host_init(struct pcie_port *pp) ++{ ++ struct ls_pcie *pcie = to_ls_pcie(pp); ++ u32 index[2]; ++ ++ pcie->scfg = syscon_regmap_lookup_by_phandle(pp->dev->of_node, ++ "fsl,pcie-scfg"); ++ if (IS_ERR(pcie->scfg)) { ++ dev_err(pp->dev, "No syscfg phandle specified\n"); ++ pcie->scfg = NULL; ++ return; ++ } ++ ++ if (of_property_read_u32_array(pp->dev->of_node, ++ "fsl,pcie-scfg", index, 2)) { ++ pcie->scfg = NULL; ++ return; ++ } ++ pcie->index = index[1]; ++ ++ ls_pcie_host_init(pp); ++ ++ dw_pcie_setup_rc(pp); ++} ++ ++static int ls_pcie_link_up(struct pcie_port *pp) ++{ ++ struct ls_pcie *pcie = to_ls_pcie(pp); ++ u32 state, offset; ++ ++ if (of_get_property(pp->dev->of_node, "fsl,lut_diff", NULL)) ++ offset = 0x407fc; ++ else ++ offset = PCIE_LUT_DBG; ++ ++ state = (ioread32(pcie->lut + offset) >> ++ pcie->drvdata->ltssm_shift) & ++ LTSSM_STATE_MASK; ++ ++ if (state < LTSSM_PCIE_L0) ++ return 0; ++ ++ return 1; ++} ++ ++static u32 ls_pcie_get_link_state(struct ls_pcie *pcie) ++{ ++ return (ioread32(pcie->lut + PCIE_LUT_DBG) >> ++ pcie->drvdata->ltssm_shift) & ++ LTSSM_STATE_MASK; ++} ++ ++static void ls_pcie_host_init(struct pcie_port *pp) ++{ ++ struct ls_pcie *pcie = to_ls_pcie(pp); ++ ++ iowrite32(1, pcie->dbi + PCIE_DBI_RO_WR_EN); ++ ls_pcie_fix_class(pcie); ++ ls_pcie_clear_multifunction(pcie); ++ ls_pcie_drop_msg_tlp(pcie); ++ iowrite32(0, pcie->dbi + PCIE_DBI_RO_WR_EN); ++ ++ ls_pcie_disable_bars(pcie); ++ ls_pcie_disable_outbound_atus(pcie); ++ ls_pcie_fix_error_response(pcie); ++} ++ ++static int ls_pcie_msi_host_init(struct pcie_port *pp, ++ struct msi_controller *chip) ++{ ++ struct device_node *msi_node; ++ struct device_node *np = pp->dev->of_node; ++ ++ /* ++ * The MSI domain is set by the generic of_msi_configure(). This ++ * .msi_host_init() function keeps us from doing the default MSI ++ * domain setup in dw_pcie_host_init() and also enforces the ++ * requirement that "msi-parent" exists. ++ */ ++ msi_node = of_parse_phandle(np, "msi-parent", 0); ++ if (!msi_node) { ++ dev_err(pp->dev, "failed to find msi-parent\n"); ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static struct pcie_host_ops ls1021_pcie_host_ops = { ++ .link_up = ls1021_pcie_link_up, ++ .host_init = ls1021_pcie_host_init, ++ .msi_host_init = ls_pcie_msi_host_init, ++}; ++ ++static struct ls_pcie_pm_ops ls1021_pcie_host_pm_ops = { ++ .get_link_state = &ls1021_pcie_get_link_state, ++ .send_turn_off_message = &ls1021_pcie_send_turn_off_message, ++ .clear_turn_off_message = &ls1021_pcie_clear_turn_off_message, ++ .reset_slot = &ls1021_pcie_reset_slot, ++}; ++ ++static struct pcie_host_ops ls_pcie_host_ops = { ++ .link_up = ls_pcie_link_up, ++ .host_init = ls_pcie_host_init, ++ .msi_host_init = ls_pcie_msi_host_init, ++}; ++ ++static struct ls_pcie_pm_ops ls_pcie_host_pm_ops = { ++ .get_link_state = &ls_pcie_get_link_state, ++}; ++ ++static struct ls_pcie_drvdata ls1021_drvdata = { ++ .ops = &ls1021_pcie_host_ops, ++ .pm = &ls1021_pcie_host_pm_ops, ++}; ++ ++static struct ls_pcie_drvdata ls1043_drvdata = { ++ .lut_offset = 0x10000, ++ .ltssm_shift = 24, ++ .ops = &ls_pcie_host_ops, ++ .pm = &ls_pcie_host_pm_ops, ++}; ++ ++static struct ls_pcie_drvdata ls2080_drvdata = { ++ .lut_offset = 0x80000, ++ .ltssm_shift = 0, ++ .ops = &ls_pcie_host_ops, ++ .pm = &ls_pcie_host_pm_ops, ++}; ++ ++static const struct of_device_id ls_pcie_of_match[] = { ++ { .compatible = "fsl,ls1021a-pcie", .data = &ls1021_drvdata }, ++ { .compatible = "fsl,ls1043a-pcie", .data = &ls1043_drvdata }, ++ { .compatible = "fsl,ls2080a-pcie", .data = &ls2080_drvdata }, ++ { .compatible = "fsl,ls2085a-pcie", .data = &ls2080_drvdata }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(of, ls_pcie_of_match); ++ ++static void ls_pcie_host_hack_pm_init(struct ls_pcie *pcie) ++{ ++ struct device_node *np; ++ struct ls_pcie_pm_data *pm_data = &pcie->pm_data; ++ ++ np = of_find_compatible_node(NULL, NULL, "fsl,ls1021aqds-fpga"); ++ if (np) ++ pm_data->fpga = of_iomap(np, 0); ++ ++ of_node_put(np); ++ ++ np = of_find_compatible_node(NULL, NULL, "fsl,ls1021atwr-cpld"); ++ if (np) ++ pm_data->cpld = of_iomap(np, 0); ++ ++ of_node_put(np); ++} ++ ++static irqreturn_t ls_pcie_pme_irq_handler(int irq, void *data) ++{ ++ struct pcie_port *pp = data; ++ struct ls_pcie *pcie = to_ls_pcie(pp); ++ u32 val; ++ ++ if (pcie->drvdata->pm->clear_turn_off_message) ++ pcie->drvdata->pm->clear_turn_off_message(pcie); ++ ++ /* Clear Host root PME_STATE bit */ ++ val = ioread32(pcie->dbi + PCIE_PEX_RSR); ++ val |= PCIE_PEX_PMES; ++ iowrite32(val, pcie->dbi + PCIE_PEX_RSR); ++ ++ return IRQ_HANDLED; ++} ++ ++static int ls_pcie_host_pme_init(struct ls_pcie *pcie, ++ struct platform_device *pdev) ++{ ++ struct pcie_port *pp; ++ int ret; ++ u16 val; ++ ++ pp = &pcie->pp; ++ ++ if (dw_pcie_link_up(&pcie->pp)) ++ pcie->in_slot = true; ++ else ++ pcie->in_slot = false; ++ ++ pcie->pme_irq = platform_get_irq_byname(pdev, "pme"); ++ if (pcie->pme_irq < 0) { ++ dev_err(&pdev->dev, ++ "failed to get PME IRQ: %d\n", pcie->pme_irq); ++ return pcie->pme_irq; ++ } ++ ++ ret = devm_request_irq(pp->dev, pcie->pme_irq, ls_pcie_pme_irq_handler, ++ IRQF_SHARED, "ls-pcie-pme", pp); ++ if (ret) { ++ dev_err(pp->dev, "Failed to request pme irq\n"); ++ return ret; ++ } ++ ++ ls_pcie_host_hack_pm_init(pcie); ++ ++ /* AUX Power PM Enable */ ++ val = ioread16(pcie->dbi + PCIE_PEX_DCR); ++ val |= PCIE_PEX_DCR_AUXPOWEREN; ++ iowrite16(val, pcie->dbi + PCIE_PEX_DCR); ++ ++ /* Enable PME message */ ++ val = ioread16(pcie->dbi + PCIE_PM_SCR); ++ val |= PCIE_PM_SCR_PMEEN; ++ iowrite16(val, pcie->dbi + PCIE_PM_SCR); ++ ++ /* Clear Host PME_STATE bit */ ++ val = ioread16(pcie->dbi + PCIE_PM_SCR); ++ val |= PCIE_PM_SCR_PME_STATE; ++ iowrite16(val, pcie->dbi + PCIE_PM_SCR); ++ ++ /* Enable Host %d interrupt */ ++ val = ioread16(pcie->dbi + PCIE_PEX_RCR); ++ val |= PCIE_PEX_RCR_PMEIE; ++ iowrite16(val, pcie->dbi + PCIE_PEX_RCR); ++ ++ return 0; ++} ++ ++static int __init ls_add_pcie_port(struct pcie_port *pp, ++ struct platform_device *pdev) ++{ ++ int ret; ++ struct ls_pcie *pcie = to_ls_pcie(pp); ++ ++ pp->dev = &pdev->dev; ++ pp->dbi_base = pcie->dbi; ++ pp->ops = pcie->drvdata->ops; ++ ++ ret = dw_pcie_host_init(pp); ++ if (ret) { ++ dev_err(pp->dev, "failed to initialize host\n"); ++ return ret; ++ } ++ ++ ret = ls_pcie_host_pme_init(pcie, pdev); ++ if (ret) ++ dev_warn(pp->dev, "failed to initialize PME\n"); ++ ++ return 0; ++} ++ ++static int ls_pcie_probe(struct platform_device *pdev) ++{ ++ const struct of_device_id *match; ++ struct ls_pcie *pcie; ++ struct resource *dbi_base; ++ int ret; ++ ++ match = of_match_device(ls_pcie_of_match, &pdev->dev); ++ if (!match) ++ return -ENODEV; ++ ++ pcie = devm_kzalloc(&pdev->dev, sizeof(*pcie), GFP_KERNEL); ++ if (!pcie) ++ return -ENOMEM; ++ ++ dbi_base = platform_get_resource_byname(pdev, IORESOURCE_MEM, "regs"); ++ pcie->dbi = devm_ioremap_resource(&pdev->dev, dbi_base); ++ if (IS_ERR(pcie->dbi)) { ++ dev_err(&pdev->dev, "missing *regs* space\n"); ++ return PTR_ERR(pcie->dbi); ++ } ++ ++ pcie->drvdata = match->data; ++ pcie->lut = pcie->dbi + pcie->drvdata->lut_offset; ++ /* Disable LDR zero */ ++ iowrite32(0, pcie->lut + PCIE_LUT_LDR(0)); ++ ++ if (!ls_pcie_is_bridge(pcie)) ++ return -ENODEV; ++ ++ if (of_device_is_compatible(pdev->dev.of_node, "fsl,ls2085a-pcie") || ++ of_device_is_compatible(pdev->dev.of_node, "fsl,ls2080a-pcie")) { ++ int len; ++ const u32 *prop; ++ struct device_node *np; ++ ++ np = pdev->dev.of_node; ++ prop = (u32 *)of_get_property(np, "available-stream-ids", &len); ++ if (prop) { ++ pcie->avail_streamids = prop; ++ pcie->streamid_index = len/sizeof(u32); ++ } else ++ dev_err(&pdev->dev, "PCIe endpoint partitioning not possible\n"); ++ } ++ ++ ret = ls_add_pcie_port(&pcie->pp, pdev); ++ if (ret < 0) ++ return ret; ++ ++ list_add_tail(&pcie->list_node, &hose_list); ++ ++ platform_set_drvdata(pdev, pcie); ++ ++ return 0; ++} ++ ++#ifdef CONFIG_PM_SLEEP ++static int ls_pcie_pm_do_suspend(struct ls_pcie *pcie) ++{ ++ u32 state; ++ int i = 0; ++ int ret; ++ u16 val; ++ ++ if (!pcie->in_slot) ++ return 0; ++ ++ if (!pcie->drvdata->pm->send_turn_off_message) ++ return 0; ++ ++ ret = pcie->drvdata->pm->send_turn_off_message(pcie); ++ if (ret) ++ return -EINVAL; ++ ++ while (i < 100) { ++ state = pcie->drvdata->pm->get_link_state(pcie); ++ if (state == LTSSM_PCIE_L2_IDLE) ++ break; ++ i++; ++ mdelay(1); ++ } ++ ++ /* Put RC in D3 */ ++ val = ioread16(pcie->dbi + PCIE_PM_SCR); ++ val |= PCIE_PM_SCR_PMEPS_D3; ++ iowrite16(val, pcie->dbi + PCIE_PM_SCR); ++ ++ mdelay(10); ++ ++ return 0; ++} ++ ++static int ls_pcie_pm_do_resume(struct ls_pcie *pcie) ++{ ++ u32 state; ++ int i = 0; ++ u16 val; ++ struct pcie_port *pp = &pcie->pp; ++ ++ if (!pcie->in_slot) ++ return 0; ++ ++ dw_pcie_setup_rc(pp); ++ ls_pcie_host_init(pp); ++ ++ /* Put RC in D0 */ ++ val = ioread16(pcie->dbi + PCIE_PM_SCR); ++ val &= PCIE_PM_SCR_PMEPS_D0; ++ iowrite16(val, pcie->dbi + PCIE_PM_SCR); ++ ++ mdelay(10); ++ ++ state = pcie->drvdata->pm->get_link_state(pcie); ++ if (state == LTSSM_PCIE_L0) ++ return 0; ++ ++ if (!pcie->drvdata->pm->reset_slot) ++ return -EINVAL; ++ ++ pcie->drvdata->pm->reset_slot(pcie, &pcie->pm_data); ++ ++ while (i < 100) { ++ state = pcie->drvdata->pm->get_link_state(pcie); ++ if (state == LTSSM_PCIE_L0) ++ return 0; ++ i++; ++ mdelay(1); ++ } ++ ++ return -EINVAL; ++} ++ ++static int ls_pcie_pm_suspend(void) ++{ ++ struct ls_pcie *hose, *tmp; ++ ++ list_for_each_entry_safe(hose, tmp, &hose_list, list_node) ++ ls_pcie_pm_do_suspend(hose); ++ ++ return 0; ++} ++ ++static void ls_pcie_pm_resume(void) ++{ ++ struct ls_pcie *hose, *tmp; ++ ++ list_for_each_entry_safe(hose, tmp, &hose_list, list_node) ++ ls_pcie_pm_do_resume(hose); ++} ++ ++static struct syscore_ops ls_pcie_syscore_pm_ops = { ++ .suspend = ls_pcie_pm_suspend, ++ .resume = ls_pcie_pm_resume, ++}; ++#endif /* CONFIG_PM_SLEEP */ ++ ++static struct platform_driver ls_pcie_driver = { ++ .probe = ls_pcie_probe, ++ .driver = { ++ .name = "layerscape-pcie", ++ .of_match_table = ls_pcie_of_match, ++ }, ++}; ++ ++static int __init fsl_pci_init(void) ++{ ++#ifdef CONFIG_PM_SLEEP ++ register_syscore_ops(&ls_pcie_syscore_pm_ops); ++#endif ++ return platform_driver_register(&ls_pcie_driver); ++} ++module_init(fsl_pci_init); ++ ++MODULE_AUTHOR("Minghuan Lian "); ++MODULE_DESCRIPTION("Freescale Layerscape PCIe host controller driver"); ++MODULE_LICENSE("GPL v2"); +diff --git a/drivers/pci/host/pci-layerscape.h b/drivers/pci/host/pci-layerscape.h +new file mode 100644 +index 0000000..e90e114 +--- /dev/null ++++ b/drivers/pci/host/pci-layerscape.h +@@ -0,0 +1,13 @@ ++/* ++ * 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. ++ */ ++ ++#ifndef _PCI_LAYERSCAPE_H ++#define _PCI_LAYERSCAPE_H ++ ++/* function for setting up stream id to device id translation */ ++u32 set_pcie_streamid_translation(struct pci_dev *pdev, u32 devid); ++ ++#endif /* _PCI_LAYERSCAPE_H */ +diff --git a/drivers/pci/host/pci-mvebu.c b/drivers/pci/host/pci-mvebu.c +index b1315e1..94b42d1 100644 +--- a/drivers/pci/host/pci-mvebu.c ++++ b/drivers/pci/host/pci-mvebu.c +@@ -99,11 +99,9 @@ struct mvebu_pcie_port; + struct mvebu_pcie { + struct platform_device *pdev; + struct mvebu_pcie_port *ports; +- struct msi_chip *msi; ++ struct msi_controller *msi; + struct resource io; +- char io_name[30]; + struct resource realio; +- char mem_name[30]; + struct resource mem; + struct resource busn; + int nports; +@@ -722,18 +720,9 @@ static int mvebu_pcie_setup(int nr, struct pci_sys_data *sys) + { + struct mvebu_pcie *pcie = sys_to_pcie(sys); + int i; +- int domain = 0; + +-#ifdef CONFIG_PCI_DOMAINS +- domain = sys->domain; +-#endif +- +- snprintf(pcie->mem_name, sizeof(pcie->mem_name), "PCI MEM %04x", +- domain); +- pcie->mem.name = pcie->mem_name; +- +- snprintf(pcie->io_name, sizeof(pcie->io_name), "PCI I/O %04x", domain); +- pcie->realio.name = pcie->io_name; ++ pcie->mem.name = "PCI MEM"; ++ pcie->realio.name = "PCI I/O"; + + if (request_resource(&iomem_resource, &pcie->mem)) + return 0; +diff --git a/drivers/pci/host/pci-tegra.c b/drivers/pci/host/pci-tegra.c +index 19bb19c..971d8d7 100644 +--- a/drivers/pci/host/pci-tegra.c ++++ b/drivers/pci/host/pci-tegra.c +@@ -238,7 +238,7 @@ + ) + + struct tegra_msi { +- struct msi_chip chip; ++ struct msi_controller chip; + DECLARE_BITMAP(used, INT_PCI_MSI_NR); + struct irq_domain *domain; + unsigned long pages; +@@ -259,7 +259,7 @@ struct tegra_pcie_soc_data { + bool has_gen2; + }; + +-static inline struct tegra_msi *to_tegra_msi(struct msi_chip *chip) ++static inline struct tegra_msi *to_tegra_msi(struct msi_controller *chip) + { + return container_of(chip, struct tegra_msi, chip); + } +@@ -1280,8 +1280,8 @@ static irqreturn_t tegra_pcie_msi_irq(int irq, void *data) + return processed > 0 ? IRQ_HANDLED : IRQ_NONE; + } + +-static int tegra_msi_setup_irq(struct msi_chip *chip, struct pci_dev *pdev, +- struct msi_desc *desc) ++static int tegra_msi_setup_irq(struct msi_controller *chip, ++ struct pci_dev *pdev, struct msi_desc *desc) + { + struct tegra_msi *msi = to_tegra_msi(chip); + struct msi_msg msg; +@@ -1305,12 +1305,13 @@ static int tegra_msi_setup_irq(struct msi_chip *chip, struct pci_dev *pdev, + msg.address_hi = 0; + msg.data = hwirq; + +- write_msi_msg(irq, &msg); ++ pci_write_msi_msg(irq, &msg); + + return 0; + } + +-static void tegra_msi_teardown_irq(struct msi_chip *chip, unsigned int irq) ++static void tegra_msi_teardown_irq(struct msi_controller *chip, ++ unsigned int irq) + { + struct tegra_msi *msi = to_tegra_msi(chip); + struct irq_data *d = irq_get_irq_data(irq); +@@ -1322,10 +1323,10 @@ static void tegra_msi_teardown_irq(struct msi_chip *chip, unsigned int irq) + + static struct irq_chip tegra_msi_irq_chip = { + .name = "Tegra PCIe MSI", +- .irq_enable = unmask_msi_irq, +- .irq_disable = mask_msi_irq, +- .irq_mask = mask_msi_irq, +- .irq_unmask = unmask_msi_irq, ++ .irq_enable = pci_msi_unmask_irq, ++ .irq_disable = pci_msi_mask_irq, ++ .irq_mask = pci_msi_mask_irq, ++ .irq_unmask = pci_msi_unmask_irq, + }; + + static int tegra_msi_map(struct irq_domain *domain, unsigned int irq, +@@ -1333,7 +1334,6 @@ static int tegra_msi_map(struct irq_domain *domain, unsigned int irq, + { + irq_set_chip_and_handler(irq, &tegra_msi_irq_chip, handle_simple_irq); + irq_set_chip_data(irq, domain->host_data); +- set_irq_flags(irq, IRQF_VALID); + + tegra_cpuidle_pcie_irqs_in_use(); + +diff --git a/drivers/pci/host/pci-xgene-msi.c b/drivers/pci/host/pci-xgene-msi.c +new file mode 100644 +index 0000000..8e559d1 +--- /dev/null ++++ b/drivers/pci/host/pci-xgene-msi.c +@@ -0,0 +1,595 @@ ++/* ++ * APM X-Gene MSI Driver ++ * ++ * Copyright (c) 2014, Applied Micro Circuits Corporation ++ * Author: Tanmay Inamdar ++ * Duc Dang ++ * ++ * This program is free software; you can redistribute it and/or modify it ++ * under the terms of the GNU General Public License as published by the ++ * Free Software Foundation; either version 2 of the License, or (at your ++ * option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define MSI_IR0 0x000000 ++#define MSI_INT0 0x800000 ++#define IDX_PER_GROUP 8 ++#define IRQS_PER_IDX 16 ++#define NR_HW_IRQS 16 ++#define NR_MSI_VEC (IDX_PER_GROUP * IRQS_PER_IDX * NR_HW_IRQS) ++ ++struct xgene_msi_group { ++ struct xgene_msi *msi; ++ int gic_irq; ++ u32 msi_grp; ++}; ++ ++struct xgene_msi { ++ struct device_node *node; ++ struct msi_controller mchip; ++ struct irq_domain *domain; ++ u64 msi_addr; ++ void __iomem *msi_regs; ++ unsigned long *bitmap; ++ struct mutex bitmap_lock; ++ struct xgene_msi_group *msi_groups; ++ int num_cpus; ++}; ++ ++/* Global data */ ++static struct xgene_msi xgene_msi_ctrl; ++ ++static struct irq_chip xgene_msi_top_irq_chip = { ++ .name = "X-Gene1 MSI", ++ .irq_enable = pci_msi_unmask_irq, ++ .irq_disable = pci_msi_mask_irq, ++ .irq_mask = pci_msi_mask_irq, ++ .irq_unmask = pci_msi_unmask_irq, ++}; ++ ++static struct msi_domain_info xgene_msi_domain_info = { ++ .flags = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS | ++ MSI_FLAG_PCI_MSIX), ++ .chip = &xgene_msi_top_irq_chip, ++}; ++ ++/* ++ * X-Gene v1 has 16 groups of MSI termination registers MSInIRx, where ++ * n is group number (0..F), x is index of registers in each group (0..7) ++ * The register layout is as follows: ++ * MSI0IR0 base_addr ++ * MSI0IR1 base_addr + 0x10000 ++ * ... ... ++ * MSI0IR6 base_addr + 0x60000 ++ * MSI0IR7 base_addr + 0x70000 ++ * MSI1IR0 base_addr + 0x80000 ++ * MSI1IR1 base_addr + 0x90000 ++ * ... ... ++ * MSI1IR7 base_addr + 0xF0000 ++ * MSI2IR0 base_addr + 0x100000 ++ * ... ... ++ * MSIFIR0 base_addr + 0x780000 ++ * MSIFIR1 base_addr + 0x790000 ++ * ... ... ++ * MSIFIR7 base_addr + 0x7F0000 ++ * MSIINT0 base_addr + 0x800000 ++ * MSIINT1 base_addr + 0x810000 ++ * ... ... ++ * MSIINTF base_addr + 0x8F0000 ++ * ++ * Each index register supports 16 MSI vectors (0..15) to generate interrupt. ++ * There are total 16 GIC IRQs assigned for these 16 groups of MSI termination ++ * registers. ++ * ++ * Each MSI termination group has 1 MSIINTn register (n is 0..15) to indicate ++ * the MSI pending status caused by 1 of its 8 index registers. ++ */ ++ ++/* MSInIRx read helper */ ++static u32 xgene_msi_ir_read(struct xgene_msi *msi, ++ u32 msi_grp, u32 msir_idx) ++{ ++ return readl_relaxed(msi->msi_regs + MSI_IR0 + ++ (msi_grp << 19) + (msir_idx << 16)); ++} ++ ++/* MSIINTn read helper */ ++static u32 xgene_msi_int_read(struct xgene_msi *msi, u32 msi_grp) ++{ ++ return readl_relaxed(msi->msi_regs + MSI_INT0 + (msi_grp << 16)); ++} ++ ++/* ++ * With 2048 MSI vectors supported, the MSI message can be constructed using ++ * following scheme: ++ * - Divide into 8 256-vector groups ++ * Group 0: 0-255 ++ * Group 1: 256-511 ++ * Group 2: 512-767 ++ * ... ++ * Group 7: 1792-2047 ++ * - Each 256-vector group is divided into 16 16-vector groups ++ * As an example: 16 16-vector groups for 256-vector group 0-255 is ++ * Group 0: 0-15 ++ * Group 1: 16-32 ++ * ... ++ * Group 15: 240-255 ++ * - The termination address of MSI vector in 256-vector group n and 16-vector ++ * group x is the address of MSIxIRn ++ * - The data for MSI vector in 16-vector group x is x ++ */ ++static u32 hwirq_to_reg_set(unsigned long hwirq) ++{ ++ return (hwirq / (NR_HW_IRQS * IRQS_PER_IDX)); ++} ++ ++static u32 hwirq_to_group(unsigned long hwirq) ++{ ++ return (hwirq % NR_HW_IRQS); ++} ++ ++static u32 hwirq_to_msi_data(unsigned long hwirq) ++{ ++ return ((hwirq / NR_HW_IRQS) % IRQS_PER_IDX); ++} ++ ++static void xgene_compose_msi_msg(struct irq_data *data, struct msi_msg *msg) ++{ ++ struct xgene_msi *msi = irq_data_get_irq_chip_data(data); ++ u32 reg_set = hwirq_to_reg_set(data->hwirq); ++ u32 group = hwirq_to_group(data->hwirq); ++ u64 target_addr = msi->msi_addr + (((8 * group) + reg_set) << 16); ++ ++ msg->address_hi = upper_32_bits(target_addr); ++ msg->address_lo = lower_32_bits(target_addr); ++ msg->data = hwirq_to_msi_data(data->hwirq); ++} ++ ++/* ++ * X-Gene v1 only has 16 MSI GIC IRQs for 2048 MSI vectors. To maintain ++ * the expected behaviour of .set_affinity for each MSI interrupt, the 16 ++ * MSI GIC IRQs are statically allocated to 8 X-Gene v1 cores (2 GIC IRQs ++ * for each core). The MSI vector is moved fom 1 MSI GIC IRQ to another ++ * MSI GIC IRQ to steer its MSI interrupt to correct X-Gene v1 core. As a ++ * consequence, the total MSI vectors that X-Gene v1 supports will be ++ * reduced to 256 (2048/8) vectors. ++ */ ++static int hwirq_to_cpu(unsigned long hwirq) ++{ ++ return (hwirq % xgene_msi_ctrl.num_cpus); ++} ++ ++static unsigned long hwirq_to_canonical_hwirq(unsigned long hwirq) ++{ ++ return (hwirq - hwirq_to_cpu(hwirq)); ++} ++ ++static int xgene_msi_set_affinity(struct irq_data *irqdata, ++ const struct cpumask *mask, bool force) ++{ ++ int target_cpu = cpumask_first(mask); ++ int curr_cpu; ++ ++ curr_cpu = hwirq_to_cpu(irqdata->hwirq); ++ if (curr_cpu == target_cpu) ++ return IRQ_SET_MASK_OK_DONE; ++ ++ /* Update MSI number to target the new CPU */ ++ irqdata->hwirq = hwirq_to_canonical_hwirq(irqdata->hwirq) + target_cpu; ++ ++ return IRQ_SET_MASK_OK; ++} ++ ++static struct irq_chip xgene_msi_bottom_irq_chip = { ++ .name = "MSI", ++ .irq_set_affinity = xgene_msi_set_affinity, ++ .irq_compose_msi_msg = xgene_compose_msi_msg, ++}; ++ ++static int xgene_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, ++ unsigned int nr_irqs, void *args) ++{ ++ struct xgene_msi *msi = domain->host_data; ++ int msi_irq; ++ ++ mutex_lock(&msi->bitmap_lock); ++ ++ msi_irq = bitmap_find_next_zero_area(msi->bitmap, NR_MSI_VEC, 0, ++ msi->num_cpus, 0); ++ if (msi_irq < NR_MSI_VEC) ++ bitmap_set(msi->bitmap, msi_irq, msi->num_cpus); ++ else ++ msi_irq = -ENOSPC; ++ ++ mutex_unlock(&msi->bitmap_lock); ++ ++ if (msi_irq < 0) ++ return msi_irq; ++ ++ irq_domain_set_info(domain, virq, msi_irq, ++ &xgene_msi_bottom_irq_chip, domain->host_data, ++ handle_simple_irq, NULL, NULL); ++ ++ return 0; ++} ++ ++static void xgene_irq_domain_free(struct irq_domain *domain, ++ unsigned int virq, unsigned int nr_irqs) ++{ ++ struct irq_data *d = irq_domain_get_irq_data(domain, virq); ++ struct xgene_msi *msi = irq_data_get_irq_chip_data(d); ++ u32 hwirq; ++ ++ mutex_lock(&msi->bitmap_lock); ++ ++ hwirq = hwirq_to_canonical_hwirq(d->hwirq); ++ bitmap_clear(msi->bitmap, hwirq, msi->num_cpus); ++ ++ mutex_unlock(&msi->bitmap_lock); ++ ++ irq_domain_free_irqs_parent(domain, virq, nr_irqs); ++} ++ ++static const struct irq_domain_ops msi_domain_ops = { ++ .alloc = xgene_irq_domain_alloc, ++ .free = xgene_irq_domain_free, ++}; ++ ++static int xgene_allocate_domains(struct xgene_msi *msi) ++{ ++ msi->domain = irq_domain_add_linear(NULL, NR_MSI_VEC, ++ &msi_domain_ops, msi); ++ if (!msi->domain) ++ return -ENOMEM; ++ ++ msi->mchip.domain = pci_msi_create_irq_domain(msi->mchip.of_node, ++ &xgene_msi_domain_info, ++ msi->domain); ++ ++ if (!msi->mchip.domain) { ++ irq_domain_remove(msi->domain); ++ return -ENOMEM; ++ } ++ ++ return 0; ++} ++ ++static void xgene_free_domains(struct xgene_msi *msi) ++{ ++ if (msi->mchip.domain) ++ irq_domain_remove(msi->mchip.domain); ++ if (msi->domain) ++ irq_domain_remove(msi->domain); ++} ++ ++static int xgene_msi_init_allocator(struct xgene_msi *xgene_msi) ++{ ++ int size = BITS_TO_LONGS(NR_MSI_VEC) * sizeof(long); ++ ++ xgene_msi->bitmap = kzalloc(size, GFP_KERNEL); ++ if (!xgene_msi->bitmap) ++ return -ENOMEM; ++ ++ mutex_init(&xgene_msi->bitmap_lock); ++ ++ xgene_msi->msi_groups = kcalloc(NR_HW_IRQS, ++ sizeof(struct xgene_msi_group), ++ GFP_KERNEL); ++ if (!xgene_msi->msi_groups) ++ return -ENOMEM; ++ ++ return 0; ++} ++ ++static void xgene_msi_isr(unsigned int irq, struct irq_desc *desc) ++{ ++ struct irq_chip *chip = irq_desc_get_chip(desc); ++ struct xgene_msi_group *msi_groups; ++ struct xgene_msi *xgene_msi; ++ unsigned int virq; ++ int msir_index, msir_val, hw_irq; ++ u32 intr_index, grp_select, msi_grp; ++ ++ chained_irq_enter(chip, desc); ++ ++ msi_groups = irq_desc_get_handler_data(desc); ++ xgene_msi = msi_groups->msi; ++ msi_grp = msi_groups->msi_grp; ++ ++ /* ++ * MSIINTn (n is 0..F) indicates if there is a pending MSI interrupt ++ * If bit x of this register is set (x is 0..7), one or more interupts ++ * corresponding to MSInIRx is set. ++ */ ++ grp_select = xgene_msi_int_read(xgene_msi, msi_grp); ++ while (grp_select) { ++ msir_index = ffs(grp_select) - 1; ++ /* ++ * Calculate MSInIRx address to read to check for interrupts ++ * (refer to termination address and data assignment ++ * described in xgene_compose_msi_msg() ) ++ */ ++ msir_val = xgene_msi_ir_read(xgene_msi, msi_grp, msir_index); ++ while (msir_val) { ++ intr_index = ffs(msir_val) - 1; ++ /* ++ * Calculate MSI vector number (refer to the termination ++ * address and data assignment described in ++ * xgene_compose_msi_msg function) ++ */ ++ hw_irq = (((msir_index * IRQS_PER_IDX) + intr_index) * ++ NR_HW_IRQS) + msi_grp; ++ /* ++ * As we have multiple hw_irq that maps to single MSI, ++ * always look up the virq using the hw_irq as seen from ++ * CPU0 ++ */ ++ hw_irq = hwirq_to_canonical_hwirq(hw_irq); ++ virq = irq_find_mapping(xgene_msi->domain, hw_irq); ++ WARN_ON(!virq); ++ if (virq != 0) ++ generic_handle_irq(virq); ++ msir_val &= ~(1 << intr_index); ++ } ++ grp_select &= ~(1 << msir_index); ++ ++ if (!grp_select) { ++ /* ++ * We handled all interrupts happened in this group, ++ * resample this group MSI_INTx register in case ++ * something else has been made pending in the meantime ++ */ ++ grp_select = xgene_msi_int_read(xgene_msi, msi_grp); ++ } ++ } ++ ++ chained_irq_exit(chip, desc); ++} ++ ++static int xgene_msi_remove(struct platform_device *pdev) ++{ ++ int virq, i; ++ struct xgene_msi *msi = platform_get_drvdata(pdev); ++ ++ for (i = 0; i < NR_HW_IRQS; i++) { ++ virq = msi->msi_groups[i].gic_irq; ++ if (virq != 0) { ++ irq_set_chained_handler(virq, NULL); ++ irq_set_handler_data(virq, NULL); ++ } ++ } ++ kfree(msi->msi_groups); ++ ++ kfree(msi->bitmap); ++ msi->bitmap = NULL; ++ ++ xgene_free_domains(msi); ++ ++ return 0; ++} ++ ++static int xgene_msi_hwirq_alloc(unsigned int cpu) ++{ ++ struct xgene_msi *msi = &xgene_msi_ctrl; ++ struct xgene_msi_group *msi_group; ++ cpumask_var_t mask; ++ int i; ++ int err; ++ ++ for (i = cpu; i < NR_HW_IRQS; i += msi->num_cpus) { ++ msi_group = &msi->msi_groups[i]; ++ if (!msi_group->gic_irq) ++ continue; ++ ++ irq_set_chained_handler(msi_group->gic_irq, ++ xgene_msi_isr); ++ err = irq_set_handler_data(msi_group->gic_irq, msi_group); ++ if (err) { ++ pr_err("failed to register GIC IRQ handler\n"); ++ return -EINVAL; ++ } ++ /* ++ * Statically allocate MSI GIC IRQs to each CPU core. ++ * With 8-core X-Gene v1, 2 MSI GIC IRQs are allocated ++ * to each core. ++ */ ++ if (alloc_cpumask_var(&mask, GFP_KERNEL)) { ++ cpumask_clear(mask); ++ cpumask_set_cpu(cpu, mask); ++ err = irq_set_affinity(msi_group->gic_irq, mask); ++ if (err) ++ pr_err("failed to set affinity for GIC IRQ"); ++ free_cpumask_var(mask); ++ } else { ++ pr_err("failed to alloc CPU mask for affinity\n"); ++ err = -EINVAL; ++ } ++ ++ if (err) { ++ irq_set_chained_handler(msi_group->gic_irq, NULL); ++ irq_set_handler_data(msi_group->gic_irq, NULL); ++ return err; ++ } ++ } ++ ++ return 0; ++} ++ ++static void xgene_msi_hwirq_free(unsigned int cpu) ++{ ++ struct xgene_msi *msi = &xgene_msi_ctrl; ++ struct xgene_msi_group *msi_group; ++ int i; ++ ++ for (i = cpu; i < NR_HW_IRQS; i += msi->num_cpus) { ++ msi_group = &msi->msi_groups[i]; ++ if (!msi_group->gic_irq) ++ continue; ++ ++ irq_set_chained_handler(msi_group->gic_irq, NULL); ++ irq_set_handler_data(msi_group->gic_irq, NULL); ++ } ++} ++ ++static int xgene_msi_cpu_callback(struct notifier_block *nfb, ++ unsigned long action, void *hcpu) ++{ ++ unsigned cpu = (unsigned long)hcpu; ++ ++ switch (action) { ++ case CPU_ONLINE: ++ case CPU_ONLINE_FROZEN: ++ xgene_msi_hwirq_alloc(cpu); ++ break; ++ case CPU_DEAD: ++ case CPU_DEAD_FROZEN: ++ xgene_msi_hwirq_free(cpu); ++ break; ++ default: ++ break; ++ } ++ ++ return NOTIFY_OK; ++} ++ ++static struct notifier_block xgene_msi_cpu_notifier = { ++ .notifier_call = xgene_msi_cpu_callback, ++}; ++ ++static const struct of_device_id xgene_msi_match_table[] = { ++ {.compatible = "apm,xgene1-msi"}, ++ {}, ++}; ++ ++static int xgene_msi_probe(struct platform_device *pdev) ++{ ++ struct resource *res; ++ int rc, irq_index; ++ struct xgene_msi *xgene_msi; ++ unsigned int cpu; ++ int virt_msir; ++ u32 msi_val, msi_idx; ++ ++ xgene_msi = &xgene_msi_ctrl; ++ ++ platform_set_drvdata(pdev, xgene_msi); ++ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ xgene_msi->msi_regs = devm_ioremap_resource(&pdev->dev, res); ++ if (IS_ERR(xgene_msi->msi_regs)) { ++ dev_err(&pdev->dev, "no reg space\n"); ++ rc = -EINVAL; ++ goto error; ++ } ++ xgene_msi->msi_addr = res->start; ++ ++ xgene_msi->num_cpus = num_possible_cpus(); ++ ++ rc = xgene_msi_init_allocator(xgene_msi); ++ if (rc) { ++ dev_err(&pdev->dev, "Error allocating MSI bitmap\n"); ++ goto error; ++ } ++ ++ rc = xgene_allocate_domains(xgene_msi); ++ if (rc) { ++ dev_err(&pdev->dev, "Failed to allocate MSI domain\n"); ++ goto error; ++ } ++ ++ for (irq_index = 0; irq_index < NR_HW_IRQS; irq_index++) { ++ virt_msir = platform_get_irq(pdev, irq_index); ++ if (virt_msir < 0) { ++ dev_err(&pdev->dev, "Cannot translate IRQ index %d\n", ++ irq_index); ++ rc = -EINVAL; ++ goto error; ++ } ++ xgene_msi->msi_groups[irq_index].gic_irq = virt_msir; ++ xgene_msi->msi_groups[irq_index].msi_grp = irq_index; ++ xgene_msi->msi_groups[irq_index].msi = xgene_msi; ++ } ++ ++ /* ++ * MSInIRx registers are read-to-clear; before registering ++ * interrupt handlers, read all of them to clear spurious ++ * interrupts that may occur before the driver is probed. ++ */ ++ for (irq_index = 0; irq_index < NR_HW_IRQS; irq_index++) { ++ for (msi_idx = 0; msi_idx < IDX_PER_GROUP; msi_idx++) ++ msi_val = xgene_msi_ir_read(xgene_msi, irq_index, ++ msi_idx); ++ /* Read MSIINTn to confirm */ ++ msi_val = xgene_msi_int_read(xgene_msi, irq_index); ++ if (msi_val) { ++ dev_err(&pdev->dev, "Failed to clear spurious IRQ\n"); ++ rc = -EINVAL; ++ goto error; ++ } ++ } ++ ++ cpu_notifier_register_begin(); ++ ++ for_each_online_cpu(cpu) ++ if (xgene_msi_hwirq_alloc(cpu)) { ++ dev_err(&pdev->dev, "failed to register MSI handlers\n"); ++ cpu_notifier_register_done(); ++ goto error; ++ } ++ ++ rc = __register_hotcpu_notifier(&xgene_msi_cpu_notifier); ++ if (rc) { ++ dev_err(&pdev->dev, "failed to add CPU MSI notifier\n"); ++ cpu_notifier_register_done(); ++ goto error; ++ } ++ ++ cpu_notifier_register_done(); ++ ++ xgene_msi->mchip.of_node = pdev->dev.of_node; ++ rc = of_pci_msi_chip_add(&xgene_msi->mchip); ++ if (rc) { ++ dev_err(&pdev->dev, "failed to add MSI controller chip\n"); ++ goto error_notifier; ++ } ++ ++ dev_info(&pdev->dev, "APM X-Gene PCIe MSI driver loaded\n"); ++ ++ return 0; ++ ++error_notifier: ++ unregister_hotcpu_notifier(&xgene_msi_cpu_notifier); ++error: ++ xgene_msi_remove(pdev); ++ return rc; ++} ++ ++static struct platform_driver xgene_msi_driver = { ++ .driver = { ++ .name = "xgene-msi", ++ .owner = THIS_MODULE, ++ .of_match_table = xgene_msi_match_table, ++ }, ++ .probe = xgene_msi_probe, ++ .remove = xgene_msi_remove, ++}; ++ ++static int __init xgene_pcie_msi_init(void) ++{ ++ return platform_driver_register(&xgene_msi_driver); ++} ++subsys_initcall(xgene_pcie_msi_init); +diff --git a/drivers/pci/host/pci-xgene.c b/drivers/pci/host/pci-xgene.c +index 2988fe1..0dac1fb 100644 +--- a/drivers/pci/host/pci-xgene.c ++++ b/drivers/pci/host/pci-xgene.c +@@ -401,11 +401,11 @@ static int xgene_pcie_map_ranges(struct xgene_pcie_port *port, + struct list_head *res, + resource_size_t io_base) + { +- struct pci_host_bridge_window *window; ++ struct resource_entry *window; + struct device *dev = port->dev; + int ret; + +- list_for_each_entry(window, res, list) { ++ resource_list_for_each_entry(window, res) { + struct resource *res = window->res; + u64 restype = resource_type(res); + +@@ -600,6 +600,23 @@ static int xgene_pcie_setup(struct xgene_pcie_port *port, + return 0; + } + ++static int xgene_pcie_msi_enable(struct pci_bus *bus) ++{ ++ struct device_node *msi_node; ++ ++ msi_node = of_parse_phandle(bus->dev.of_node, ++ "msi-parent", 0); ++ if (!msi_node) ++ return -ENODEV; ++ ++ bus->msi = of_pci_find_msi_chip_by_node(msi_node); ++ if (!bus->msi) ++ return -ENODEV; ++ ++ bus->msi->dev = &bus->dev; ++ return 0; ++} ++ + static int xgene_pcie_probe_bridge(struct platform_device *pdev) + { + struct device_node *dn = pdev->dev.of_node; +@@ -636,6 +653,10 @@ static int xgene_pcie_probe_bridge(struct platform_device *pdev) + if (!bus) + return -ENOMEM; + ++ if (IS_ENABLED(CONFIG_PCI_MSI)) ++ if (xgene_pcie_msi_enable(bus)) ++ dev_info(port->dev, "failed to enable MSI\n"); ++ + pci_scan_child_bus(bus); + pci_assign_unassigned_bus_resources(bus); + pci_bus_add_devices(bus); +diff --git a/drivers/pci/host/pcie-designware.c b/drivers/pci/host/pcie-designware.c +index f69b0d0..0961ffc 100644 +--- a/drivers/pci/host/pcie-designware.c ++++ b/drivers/pci/host/pcie-designware.c +@@ -15,7 +15,6 @@ + #include + #include + #include +-#include + #include + #include + #include +@@ -31,6 +30,7 @@ + #define PORT_LINK_MODE_1_LANES (0x1 << 16) + #define PORT_LINK_MODE_2_LANES (0x3 << 16) + #define PORT_LINK_MODE_4_LANES (0x7 << 16) ++#define PORT_LINK_MODE_8_LANES (0xf << 16) + + #define PCIE_LINK_WIDTH_SPEED_CONTROL 0x80C + #define PORT_LOGIC_SPEED_CHANGE (0x1 << 17) +@@ -38,12 +38,7 @@ + #define PORT_LOGIC_LINK_WIDTH_1_LANES (0x1 << 8) + #define PORT_LOGIC_LINK_WIDTH_2_LANES (0x2 << 8) + #define PORT_LOGIC_LINK_WIDTH_4_LANES (0x4 << 8) +- +-#define PCIE_MSI_ADDR_LO 0x820 +-#define PCIE_MSI_ADDR_HI 0x824 +-#define PCIE_MSI_INTR0_ENABLE 0x828 +-#define PCIE_MSI_INTR0_MASK 0x82C +-#define PCIE_MSI_INTR0_STATUS 0x830 ++#define PORT_LOGIC_LINK_WIDTH_8_LANES (0x8 << 8) + + #define PCIE_ATU_VIEWPORT 0x900 + #define PCIE_ATU_REGION_INBOUND (0x1 << 31) +@@ -67,39 +62,40 @@ + #define PCIE_ATU_FUNC(x) (((x) & 0x7) << 16) + #define PCIE_ATU_UPPER_TARGET 0x91C + +-static struct hw_pci dw_pci; +- +-static unsigned long global_io_offset; +- +-static inline struct pcie_port *sys_to_pcie(struct pci_sys_data *sys) +-{ +- BUG_ON(!sys->private_data); +- +- return sys->private_data; +-} ++static struct pci_ops dw_pcie_ops; + +-int dw_pcie_cfg_read(void __iomem *addr, int where, int size, u32 *val) ++int dw_pcie_cfg_read(void __iomem *addr, int size, u32 *val) + { +- *val = readl(addr); ++ if ((uintptr_t)addr & (size - 1)) { ++ *val = 0; ++ return PCIBIOS_BAD_REGISTER_NUMBER; ++ } + +- if (size == 1) +- *val = (*val >> (8 * (where & 3))) & 0xff; ++ if (size == 4) ++ *val = readl(addr); + else if (size == 2) +- *val = (*val >> (8 * (where & 3))) & 0xffff; +- else if (size != 4) ++ *val = readw(addr); ++ else if (size == 1) ++ *val = readb(addr); ++ else { ++ *val = 0; + return PCIBIOS_BAD_REGISTER_NUMBER; ++ } + + return PCIBIOS_SUCCESSFUL; + } + +-int dw_pcie_cfg_write(void __iomem *addr, int where, int size, u32 val) ++int dw_pcie_cfg_write(void __iomem *addr, int size, u32 val) + { ++ if ((uintptr_t)addr & (size - 1)) ++ return PCIBIOS_BAD_REGISTER_NUMBER; ++ + if (size == 4) + writel(val, addr); + else if (size == 2) +- writew(val, addr + (where & 2)); ++ writew(val, addr); + else if (size == 1) +- writeb(val, addr + (where & 3)); ++ writeb(val, addr); + else + return PCIBIOS_BAD_REGISTER_NUMBER; + +@@ -130,8 +126,7 @@ static int dw_pcie_rd_own_conf(struct pcie_port *pp, int where, int size, + if (pp->ops->rd_own_conf) + ret = pp->ops->rd_own_conf(pp, where, size, val); + else +- ret = dw_pcie_cfg_read(pp->dbi_base + (where & ~0x3), where, +- size, val); ++ ret = dw_pcie_cfg_read(pp->dbi_base + where, size, val); + + return ret; + } +@@ -144,182 +139,33 @@ static int dw_pcie_wr_own_conf(struct pcie_port *pp, int where, int size, + if (pp->ops->wr_own_conf) + ret = pp->ops->wr_own_conf(pp, where, size, val); + else +- ret = dw_pcie_cfg_write(pp->dbi_base + (where & ~0x3), where, +- size, val); +- +- return ret; +-} +- +-static struct irq_chip dw_msi_irq_chip = { +- .name = "PCI-MSI", +- .irq_enable = unmask_msi_irq, +- .irq_disable = mask_msi_irq, +- .irq_mask = mask_msi_irq, +- .irq_unmask = unmask_msi_irq, +-}; +- +-/* MSI int handler */ +-irqreturn_t dw_handle_msi_irq(struct pcie_port *pp) +-{ +- unsigned long val; +- int i, pos, irq; +- irqreturn_t ret = IRQ_NONE; +- +- for (i = 0; i < MAX_MSI_CTRLS; i++) { +- dw_pcie_rd_own_conf(pp, PCIE_MSI_INTR0_STATUS + i * 12, 4, +- (u32 *)&val); +- if (val) { +- ret = IRQ_HANDLED; +- pos = 0; +- while ((pos = find_next_bit(&val, 32, pos)) != 32) { +- irq = irq_find_mapping(pp->irq_domain, +- i * 32 + pos); +- dw_pcie_wr_own_conf(pp, +- PCIE_MSI_INTR0_STATUS + i * 12, +- 4, 1 << pos); +- generic_handle_irq(irq); +- pos++; +- } +- } +- } ++ ret = dw_pcie_cfg_write(pp->dbi_base + where, size, val); + + return ret; + } + +-void dw_pcie_msi_init(struct pcie_port *pp) +-{ +- pp->msi_data = __get_free_pages(GFP_KERNEL, 0); +- +- /* program the msi_data */ +- dw_pcie_wr_own_conf(pp, PCIE_MSI_ADDR_LO, 4, +- virt_to_phys((void *)pp->msi_data)); +- dw_pcie_wr_own_conf(pp, PCIE_MSI_ADDR_HI, 4, 0); +-} +- +-static void dw_pcie_msi_clear_irq(struct pcie_port *pp, int irq) +-{ +- unsigned int res, bit, val; +- +- res = (irq / 32) * 12; +- bit = irq % 32; +- dw_pcie_rd_own_conf(pp, PCIE_MSI_INTR0_ENABLE + res, 4, &val); +- val &= ~(1 << bit); +- dw_pcie_wr_own_conf(pp, PCIE_MSI_INTR0_ENABLE + res, 4, val); +-} +- +-static void clear_irq_range(struct pcie_port *pp, unsigned int irq_base, +- unsigned int nvec, unsigned int pos) +-{ +- unsigned int i; +- +- for (i = 0; i < nvec; i++) { +- irq_set_msi_desc_off(irq_base, i, NULL); +- /* Disable corresponding interrupt on MSI controller */ +- if (pp->ops->msi_clear_irq) +- pp->ops->msi_clear_irq(pp, pos + i); +- else +- dw_pcie_msi_clear_irq(pp, pos + i); +- } +- +- bitmap_release_region(pp->msi_irq_in_use, pos, order_base_2(nvec)); +-} +- +-static void dw_pcie_msi_set_irq(struct pcie_port *pp, int irq) +-{ +- unsigned int res, bit, val; +- +- res = (irq / 32) * 12; +- bit = irq % 32; +- dw_pcie_rd_own_conf(pp, PCIE_MSI_INTR0_ENABLE + res, 4, &val); +- val |= 1 << bit; +- dw_pcie_wr_own_conf(pp, PCIE_MSI_INTR0_ENABLE + res, 4, val); +-} +- +-static int assign_irq(int no_irqs, struct msi_desc *desc, int *pos) ++static void dw_pcie_prog_outbound_atu(struct pcie_port *pp, int index, ++ int type, u64 cpu_addr, u64 pci_addr, u32 size) + { +- int irq, pos0, i; +- struct pcie_port *pp = sys_to_pcie(desc->dev->bus->sysdata); +- +- pos0 = bitmap_find_free_region(pp->msi_irq_in_use, MAX_MSI_IRQS, +- order_base_2(no_irqs)); +- if (pos0 < 0) +- goto no_valid_irq; +- +- irq = irq_find_mapping(pp->irq_domain, pos0); +- if (!irq) +- goto no_valid_irq; +- +- /* +- * irq_create_mapping (called from dw_pcie_host_init) pre-allocates +- * descs so there is no need to allocate descs here. We can therefore +- * assume that if irq_find_mapping above returns non-zero, then the +- * descs are also successfully allocated. +- */ +- +- for (i = 0; i < no_irqs; i++) { +- if (irq_set_msi_desc_off(irq, i, desc) != 0) { +- clear_irq_range(pp, irq, i, pos0); +- goto no_valid_irq; +- } +- /*Enable corresponding interrupt in MSI interrupt controller */ +- if (pp->ops->msi_set_irq) +- pp->ops->msi_set_irq(pp, pos0 + i); +- else +- dw_pcie_msi_set_irq(pp, pos0 + i); +- } +- +- *pos = pos0; +- return irq; +- +-no_valid_irq: +- *pos = pos0; +- return -ENOSPC; +-} +- +-static int dw_msi_setup_irq(struct msi_chip *chip, struct pci_dev *pdev, +- struct msi_desc *desc) +-{ +- int irq, pos; +- struct msi_msg msg; +- struct pcie_port *pp = sys_to_pcie(pdev->bus->sysdata); +- +- if (desc->msi_attrib.is_msix) +- return -EINVAL; +- +- irq = assign_irq(1, desc, &pos); +- if (irq < 0) +- return irq; +- +- if (pp->ops->get_msi_addr) +- msg.address_lo = pp->ops->get_msi_addr(pp); +- else +- msg.address_lo = virt_to_phys((void *)pp->msi_data); +- msg.address_hi = 0x0; +- +- if (pp->ops->get_msi_data) +- msg.data = pp->ops->get_msi_data(pp, pos); +- else +- msg.data = pos; +- +- write_msi_msg(irq, &msg); +- +- return 0; ++ dw_pcie_writel_rc(pp, PCIE_ATU_REGION_OUTBOUND | index, ++ PCIE_ATU_VIEWPORT); ++ dw_pcie_writel_rc(pp, lower_32_bits(cpu_addr), PCIE_ATU_LOWER_BASE); ++ dw_pcie_writel_rc(pp, upper_32_bits(cpu_addr), PCIE_ATU_UPPER_BASE); ++ dw_pcie_writel_rc(pp, lower_32_bits(cpu_addr + size - 1), ++ PCIE_ATU_LIMIT); ++ dw_pcie_writel_rc(pp, lower_32_bits(pci_addr), PCIE_ATU_LOWER_TARGET); ++ dw_pcie_writel_rc(pp, upper_32_bits(pci_addr), PCIE_ATU_UPPER_TARGET); ++ dw_pcie_writel_rc(pp, type, PCIE_ATU_CR1); ++ dw_pcie_writel_rc(pp, PCIE_ATU_ENABLE, PCIE_ATU_CR2); + } + +-static void dw_msi_teardown_irq(struct msi_chip *chip, unsigned int irq) ++void dw_pcie_disable_outbound_atu(struct pcie_port *pp, int index) + { +- struct irq_data *data = irq_get_irq_data(irq); +- struct msi_desc *msi = irq_data_get_msi(data); +- struct pcie_port *pp = sys_to_pcie(msi->dev->bus->sysdata); +- +- clear_irq_range(pp, irq, 1, data->hwirq); ++ dw_pcie_writel_rc(pp, PCIE_ATU_REGION_OUTBOUND | index, ++ PCIE_ATU_VIEWPORT); ++ dw_pcie_writel_rc(pp, 0, PCIE_ATU_CR2); + } + +-static struct msi_chip dw_pcie_msi_chip = { +- .setup_irq = dw_msi_setup_irq, +- .teardown_irq = dw_msi_teardown_irq, +-}; +- + int dw_pcie_link_up(struct pcie_port *pp) + { + if (pp->ops->link_up) +@@ -328,36 +174,42 @@ int dw_pcie_link_up(struct pcie_port *pp) + return 0; + } + +-static int dw_pcie_msi_map(struct irq_domain *domain, unsigned int irq, +- irq_hw_number_t hwirq) ++static int dw_pcie_msi_ctrl_init(struct pcie_port *pp) + { +- irq_set_chip_and_handler(irq, &dw_msi_irq_chip, handle_simple_irq); +- irq_set_chip_data(irq, domain->host_data); +- set_irq_flags(irq, IRQF_VALID); ++ struct device_node *msi_node; ++ ++ if (!IS_ENABLED(CONFIG_PCI_MSI)) { ++ pp->msi = NULL; ++ return 0; ++ } ++ ++ if (pp->msi) ++ return 0; ++ ++ msi_node = of_parse_phandle(pp->dev->of_node, "msi-parent", 0); ++ if (msi_node) { ++ pp->msi = of_pci_find_msi_chip_by_node(msi_node); ++ if (!pp->msi) { ++ dev_err(pp->dev, "Cannot find msi chip of %s\n", ++ msi_node->full_name); ++ return -ENODEV; ++ } else ++ return 0; ++ } + + return 0; + } + +-static const struct irq_domain_ops msi_domain_ops = { +- .map = dw_pcie_msi_map, +-}; +- + int dw_pcie_host_init(struct pcie_port *pp) + { + struct device_node *np = pp->dev->of_node; + struct platform_device *pdev = to_platform_device(pp->dev); +- struct of_pci_range range; +- struct of_pci_range_parser parser; ++ struct pci_bus *bus, *child; + struct resource *cfg_res; +- u32 val, na, ns; +- const __be32 *addrp; +- int i, index, ret; +- +- /* Find the address cell size and the number of cells in order to get +- * the untranslated address. +- */ +- of_property_read_u32(np, "#address-cells", &na); +- ns = of_n_size_cells(np); ++ u32 val; ++ int ret; ++ LIST_HEAD(res); ++ struct resource_entry *win; + + cfg_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "config"); + if (cfg_res) { +@@ -365,87 +217,61 @@ int dw_pcie_host_init(struct pcie_port *pp) + pp->cfg1_size = resource_size(cfg_res)/2; + pp->cfg0_base = cfg_res->start; + pp->cfg1_base = cfg_res->start + pp->cfg0_size; +- +- /* Find the untranslated configuration space address */ +- index = of_property_match_string(np, "reg-names", "config"); +- addrp = of_get_address(np, index, NULL, NULL); +- pp->cfg0_mod_base = of_read_number(addrp, ns); +- pp->cfg1_mod_base = pp->cfg0_mod_base + pp->cfg0_size; +- } else { ++ } else if (!pp->va_cfg0_base) { + dev_err(pp->dev, "missing *config* reg space\n"); + } + +- if (of_pci_range_parser_init(&parser, np)) { +- dev_err(pp->dev, "missing ranges property\n"); +- return -EINVAL; +- } ++ ret = of_pci_get_host_bridge_resources(np, 0, 0xff, &res, &pp->io_base); ++ if (ret) ++ return ret; + + /* Get the I/O and memory ranges from DT */ +- for_each_of_pci_range(&parser, &range) { +- unsigned long restype = range.flags & IORESOURCE_TYPE_BITS; +- if (restype == IORESOURCE_IO) { +- of_pci_range_to_resource(&range, np, &pp->io); +- pp->io.name = "I/O"; +- pp->io.start = max_t(resource_size_t, +- PCIBIOS_MIN_IO, +- range.pci_addr + global_io_offset); +- pp->io.end = min_t(resource_size_t, +- IO_SPACE_LIMIT, +- range.pci_addr + range.size +- + global_io_offset - 1); +- pp->io_size = resource_size(&pp->io); +- pp->io_bus_addr = range.pci_addr; +- pp->io_base = range.cpu_addr; +- +- /* Find the untranslated IO space address */ +- pp->io_mod_base = of_read_number(parser.range - +- parser.np + na, ns); +- } +- if (restype == IORESOURCE_MEM) { +- of_pci_range_to_resource(&range, np, &pp->mem); +- pp->mem.name = "MEM"; +- pp->mem_size = resource_size(&pp->mem); +- pp->mem_bus_addr = range.pci_addr; +- +- /* Find the untranslated MEM space address */ +- pp->mem_mod_base = of_read_number(parser.range - +- parser.np + na, ns); +- } +- if (restype == 0) { +- of_pci_range_to_resource(&range, np, &pp->cfg); +- pp->cfg0_size = resource_size(&pp->cfg)/2; +- pp->cfg1_size = resource_size(&pp->cfg)/2; +- pp->cfg0_base = pp->cfg.start; +- pp->cfg1_base = pp->cfg.start + pp->cfg0_size; +- +- /* Find the untranslated configuration space address */ +- pp->cfg0_mod_base = of_read_number(parser.range - +- parser.np + na, ns); +- pp->cfg1_mod_base = pp->cfg0_mod_base + +- pp->cfg0_size; ++ resource_list_for_each_entry(win, &res) { ++ switch (resource_type(win->res)) { ++ case IORESOURCE_IO: ++ pp->io = win->res; ++ pp->io->name = "I/O"; ++ pp->io_size = resource_size(pp->io); ++ pp->io_bus_addr = pp->io->start - win->offset; ++ ret = pci_remap_iospace(pp->io, pp->io_base); ++ if (ret) { ++ dev_warn(pp->dev, "error %d: failed to map resource %pR\n", ++ ret, pp->io); ++ continue; ++ } ++ pp->io_base = pp->io->start; ++ break; ++ case IORESOURCE_MEM: ++ pp->mem = win->res; ++ pp->mem->name = "MEM"; ++ pp->mem_size = resource_size(pp->mem); ++ pp->mem_bus_addr = pp->mem->start - win->offset; ++ break; ++ case 0: ++ pp->cfg = win->res; ++ pp->cfg0_size = resource_size(pp->cfg)/2; ++ pp->cfg1_size = resource_size(pp->cfg)/2; ++ pp->cfg0_base = pp->cfg->start; ++ pp->cfg1_base = pp->cfg->start + pp->cfg0_size; ++ break; ++ case IORESOURCE_BUS: ++ pp->busn = win->res; ++ break; ++ default: ++ continue; + } + } + +- ret = of_pci_parse_bus_range(np, &pp->busn); +- if (ret < 0) { +- pp->busn.name = np->name; +- pp->busn.start = 0; +- pp->busn.end = 0xff; +- pp->busn.flags = IORESOURCE_BUS; +- dev_dbg(pp->dev, "failed to parse bus-range property: %d, using default %pR\n", +- ret, &pp->busn); +- } +- + if (!pp->dbi_base) { +- pp->dbi_base = devm_ioremap(pp->dev, pp->cfg.start, +- resource_size(&pp->cfg)); ++ pp->dbi_base = devm_ioremap(pp->dev, pp->cfg->start, ++ resource_size(pp->cfg)); + if (!pp->dbi_base) { + dev_err(pp->dev, "error with ioremap\n"); + return -ENOMEM; + } + } + +- pp->mem_base = pp->mem.start; ++ pp->mem_base = pp->mem->start; + + if (!pp->va_cfg0_base) { + pp->va_cfg0_base = devm_ioremap(pp->dev, pp->cfg0_base, +@@ -465,33 +291,18 @@ int dw_pcie_host_init(struct pcie_port *pp) + } + } + +- if (of_property_read_u32(np, "num-lanes", &pp->lanes)) { +- dev_err(pp->dev, "Failed to parse the number of lanes\n"); +- return -EINVAL; +- } +- +- if (IS_ENABLED(CONFIG_PCI_MSI)) { +- if (!pp->ops->msi_host_init) { +- pp->irq_domain = irq_domain_add_linear(pp->dev->of_node, +- MAX_MSI_IRQS, &msi_domain_ops, +- &dw_pcie_msi_chip); +- if (!pp->irq_domain) { +- dev_err(pp->dev, "irq domain init failed\n"); +- return -ENXIO; +- } +- +- for (i = 0; i < MAX_MSI_IRQS; i++) +- irq_create_mapping(pp->irq_domain, i); +- } else { +- ret = pp->ops->msi_host_init(pp, &dw_pcie_msi_chip); +- if (ret < 0) +- return ret; +- } +- } ++ ret = of_property_read_u32(np, "num-lanes", &pp->lanes); ++ if (ret) ++ pp->lanes = 0; + + if (pp->ops->host_init) + pp->ops->host_init(pp); + ++ if (!pp->ops->rd_other_conf) ++ dw_pcie_prog_outbound_atu(pp, PCIE_ATU_REGION_INDEX1, ++ PCIE_ATU_TYPE_MEM, pp->mem_base, ++ pp->mem_bus_addr, pp->mem_size); ++ + dw_pcie_wr_own_conf(pp, PCI_BASE_ADDRESS_0, 4, 0); + + /* program correct class for RC */ +@@ -501,126 +312,113 @@ int dw_pcie_host_init(struct pcie_port *pp) + val |= PORT_LOGIC_SPEED_CHANGE; + dw_pcie_wr_own_conf(pp, PCIE_LINK_WIDTH_SPEED_CONTROL, 4, val); + +- dw_pci.nr_controllers = 1; +- dw_pci.private_data = (void **)&pp; ++ pp->root_bus_nr = pp->busn->start; ++#if 0 ++ bus = pci_scan_root_bus(pp->dev, pp->root_bus_nr, &dw_pcie_ops, ++ pp, &res); ++ if (!bus) ++ return -ENOMEM; ++#else ++ bus = pci_create_root_bus(pp->dev, pp->root_bus_nr, &dw_pcie_ops, ++ pp, &res); ++ if (!bus) ++ return -ENODEV; ++ ++ ret = dw_pcie_msi_ctrl_init(pp); ++ if (ret) ++ return ret; ++ ++ bus->msi = pp->msi; + +- pci_common_init_dev(pp->dev, &dw_pci); +-#ifdef CONFIG_PCI_DOMAINS +- dw_pci.domain++; ++ pci_scan_child_bus(bus); + #endif + +- return 0; +-} ++ if (pp->ops->scan_bus) ++ pp->ops->scan_bus(pp); + +-static void dw_pcie_prog_viewport_cfg0(struct pcie_port *pp, u32 busdev) +-{ +- /* Program viewport 0 : OUTBOUND : CFG0 */ +- dw_pcie_writel_rc(pp, PCIE_ATU_REGION_OUTBOUND | PCIE_ATU_REGION_INDEX0, +- PCIE_ATU_VIEWPORT); +- dw_pcie_writel_rc(pp, pp->cfg0_mod_base, PCIE_ATU_LOWER_BASE); +- dw_pcie_writel_rc(pp, (pp->cfg0_mod_base >> 32), PCIE_ATU_UPPER_BASE); +- dw_pcie_writel_rc(pp, pp->cfg0_mod_base + pp->cfg0_size - 1, +- PCIE_ATU_LIMIT); +- dw_pcie_writel_rc(pp, busdev, PCIE_ATU_LOWER_TARGET); +- dw_pcie_writel_rc(pp, 0, PCIE_ATU_UPPER_TARGET); +- dw_pcie_writel_rc(pp, PCIE_ATU_TYPE_CFG0, PCIE_ATU_CR1); +- dw_pcie_writel_rc(pp, PCIE_ATU_ENABLE, PCIE_ATU_CR2); +-} ++#ifdef CONFIG_ARM ++ /* support old dtbs that incorrectly describe IRQs */ ++ pci_fixup_irqs(pci_common_swizzle, of_irq_parse_and_map_pci); ++#endif + +-static void dw_pcie_prog_viewport_cfg1(struct pcie_port *pp, u32 busdev) +-{ +- /* Program viewport 1 : OUTBOUND : CFG1 */ +- dw_pcie_writel_rc(pp, PCIE_ATU_REGION_OUTBOUND | PCIE_ATU_REGION_INDEX1, +- PCIE_ATU_VIEWPORT); +- dw_pcie_writel_rc(pp, PCIE_ATU_TYPE_CFG1, PCIE_ATU_CR1); +- dw_pcie_writel_rc(pp, pp->cfg1_mod_base, PCIE_ATU_LOWER_BASE); +- dw_pcie_writel_rc(pp, (pp->cfg1_mod_base >> 32), PCIE_ATU_UPPER_BASE); +- dw_pcie_writel_rc(pp, pp->cfg1_mod_base + pp->cfg1_size - 1, +- PCIE_ATU_LIMIT); +- dw_pcie_writel_rc(pp, busdev, PCIE_ATU_LOWER_TARGET); +- dw_pcie_writel_rc(pp, 0, PCIE_ATU_UPPER_TARGET); +- dw_pcie_writel_rc(pp, PCIE_ATU_ENABLE, PCIE_ATU_CR2); +-} ++ if (!pci_has_flag(PCI_PROBE_ONLY)) { ++ pci_bus_size_bridges(bus); ++ pci_bus_assign_resources(bus); + +-static void dw_pcie_prog_viewport_mem_outbound(struct pcie_port *pp) +-{ +- /* Program viewport 0 : OUTBOUND : MEM */ +- dw_pcie_writel_rc(pp, PCIE_ATU_REGION_OUTBOUND | PCIE_ATU_REGION_INDEX0, +- PCIE_ATU_VIEWPORT); +- dw_pcie_writel_rc(pp, PCIE_ATU_TYPE_MEM, PCIE_ATU_CR1); +- dw_pcie_writel_rc(pp, pp->mem_mod_base, PCIE_ATU_LOWER_BASE); +- dw_pcie_writel_rc(pp, (pp->mem_mod_base >> 32), PCIE_ATU_UPPER_BASE); +- dw_pcie_writel_rc(pp, pp->mem_mod_base + pp->mem_size - 1, +- PCIE_ATU_LIMIT); +- dw_pcie_writel_rc(pp, pp->mem_bus_addr, PCIE_ATU_LOWER_TARGET); +- dw_pcie_writel_rc(pp, upper_32_bits(pp->mem_bus_addr), +- PCIE_ATU_UPPER_TARGET); +- dw_pcie_writel_rc(pp, PCIE_ATU_ENABLE, PCIE_ATU_CR2); +-} ++ list_for_each_entry(child, &bus->children, node) ++ pcie_bus_configure_settings(child); ++ } + +-static void dw_pcie_prog_viewport_io_outbound(struct pcie_port *pp) +-{ +- /* Program viewport 1 : OUTBOUND : IO */ +- dw_pcie_writel_rc(pp, PCIE_ATU_REGION_OUTBOUND | PCIE_ATU_REGION_INDEX1, +- PCIE_ATU_VIEWPORT); +- dw_pcie_writel_rc(pp, PCIE_ATU_TYPE_IO, PCIE_ATU_CR1); +- dw_pcie_writel_rc(pp, pp->io_mod_base, PCIE_ATU_LOWER_BASE); +- dw_pcie_writel_rc(pp, (pp->io_mod_base >> 32), PCIE_ATU_UPPER_BASE); +- dw_pcie_writel_rc(pp, pp->io_mod_base + pp->io_size - 1, +- PCIE_ATU_LIMIT); +- dw_pcie_writel_rc(pp, pp->io_bus_addr, PCIE_ATU_LOWER_TARGET); +- dw_pcie_writel_rc(pp, upper_32_bits(pp->io_bus_addr), +- PCIE_ATU_UPPER_TARGET); +- dw_pcie_writel_rc(pp, PCIE_ATU_ENABLE, PCIE_ATU_CR2); ++ pci_bus_add_devices(bus); ++ ++ return 0; + } + + static int dw_pcie_rd_other_conf(struct pcie_port *pp, struct pci_bus *bus, + u32 devfn, int where, int size, u32 *val) + { +- int ret = PCIBIOS_SUCCESSFUL; +- u32 address, busdev; ++ int ret, type; ++ u32 busdev, cfg_size; ++ u64 cpu_addr; ++ void __iomem *va_cfg_base; + + busdev = PCIE_ATU_BUS(bus->number) | PCIE_ATU_DEV(PCI_SLOT(devfn)) | + PCIE_ATU_FUNC(PCI_FUNC(devfn)); +- address = where & ~0x3; + + if (bus->parent->number == pp->root_bus_nr) { +- dw_pcie_prog_viewport_cfg0(pp, busdev); +- ret = dw_pcie_cfg_read(pp->va_cfg0_base + address, where, size, +- val); +- dw_pcie_prog_viewport_mem_outbound(pp); ++ type = PCIE_ATU_TYPE_CFG0; ++ cpu_addr = pp->cfg0_base; ++ cfg_size = pp->cfg0_size; ++ va_cfg_base = pp->va_cfg0_base; + } else { +- dw_pcie_prog_viewport_cfg1(pp, busdev); +- ret = dw_pcie_cfg_read(pp->va_cfg1_base + address, where, size, +- val); +- dw_pcie_prog_viewport_io_outbound(pp); ++ type = PCIE_ATU_TYPE_CFG1; ++ cpu_addr = pp->cfg1_base; ++ cfg_size = pp->cfg1_size; ++ va_cfg_base = pp->va_cfg1_base; + } + ++ dw_pcie_prog_outbound_atu(pp, PCIE_ATU_REGION_INDEX0, ++ type, cpu_addr, ++ busdev, cfg_size); ++ ret = dw_pcie_cfg_read(va_cfg_base + where, size, val); ++ dw_pcie_prog_outbound_atu(pp, PCIE_ATU_REGION_INDEX0, ++ PCIE_ATU_TYPE_IO, pp->io_base, ++ pp->io_bus_addr, pp->io_size); ++ + return ret; + } + + static int dw_pcie_wr_other_conf(struct pcie_port *pp, struct pci_bus *bus, + u32 devfn, int where, int size, u32 val) + { +- int ret = PCIBIOS_SUCCESSFUL; +- u32 address, busdev; ++ int ret, type; ++ u32 busdev, cfg_size; ++ u64 cpu_addr; ++ void __iomem *va_cfg_base; + + busdev = PCIE_ATU_BUS(bus->number) | PCIE_ATU_DEV(PCI_SLOT(devfn)) | + PCIE_ATU_FUNC(PCI_FUNC(devfn)); +- address = where & ~0x3; + + if (bus->parent->number == pp->root_bus_nr) { +- dw_pcie_prog_viewport_cfg0(pp, busdev); +- ret = dw_pcie_cfg_write(pp->va_cfg0_base + address, where, size, +- val); +- dw_pcie_prog_viewport_mem_outbound(pp); ++ type = PCIE_ATU_TYPE_CFG0; ++ cpu_addr = pp->cfg0_base; ++ cfg_size = pp->cfg0_size; ++ va_cfg_base = pp->va_cfg0_base; + } else { +- dw_pcie_prog_viewport_cfg1(pp, busdev); +- ret = dw_pcie_cfg_write(pp->va_cfg1_base + address, where, size, +- val); +- dw_pcie_prog_viewport_io_outbound(pp); ++ type = PCIE_ATU_TYPE_CFG1; ++ cpu_addr = pp->cfg1_base; ++ cfg_size = pp->cfg1_size; ++ va_cfg_base = pp->va_cfg1_base; + } + ++ dw_pcie_prog_outbound_atu(pp, PCIE_ATU_REGION_INDEX0, ++ type, cpu_addr, ++ busdev, cfg_size); ++ ret = dw_pcie_cfg_write(va_cfg_base + where, size, val); ++ dw_pcie_prog_outbound_atu(pp, PCIE_ATU_REGION_INDEX0, ++ PCIE_ATU_TYPE_IO, pp->io_base, ++ pp->io_bus_addr, pp->io_size); ++ + return ret; + } + +@@ -650,7 +448,7 @@ static int dw_pcie_valid_config(struct pcie_port *pp, + static int dw_pcie_rd_conf(struct pci_bus *bus, u32 devfn, int where, + int size, u32 *val) + { +- struct pcie_port *pp = sys_to_pcie(bus->sysdata); ++ struct pcie_port *pp = bus->sysdata; + int ret; + + if (dw_pcie_valid_config(pp, bus, PCI_SLOT(devfn)) == 0) { +@@ -674,7 +472,7 @@ static int dw_pcie_rd_conf(struct pci_bus *bus, u32 devfn, int where, + static int dw_pcie_wr_conf(struct pci_bus *bus, u32 devfn, + int where, int size, u32 val) + { +- struct pcie_port *pp = sys_to_pcie(bus->sysdata); ++ struct pcie_port *pp = bus->sysdata; + int ret; + + if (dw_pcie_valid_config(pp, bus, PCI_SLOT(devfn)) == 0) +@@ -698,81 +496,19 @@ static struct pci_ops dw_pcie_ops = { + .write = dw_pcie_wr_conf, + }; + +-static int dw_pcie_setup(int nr, struct pci_sys_data *sys) +-{ +- struct pcie_port *pp; +- +- pp = sys_to_pcie(sys); +- +- if (global_io_offset < SZ_1M && pp->io_size > 0) { +- sys->io_offset = global_io_offset - pp->io_bus_addr; +- pci_ioremap_io(global_io_offset, pp->io_base); +- global_io_offset += SZ_64K; +- pci_add_resource_offset(&sys->resources, &pp->io, +- sys->io_offset); +- } +- +- sys->mem_offset = pp->mem.start - pp->mem_bus_addr; +- pci_add_resource_offset(&sys->resources, &pp->mem, sys->mem_offset); +- pci_add_resource(&sys->resources, &pp->busn); +- +- return 1; +-} +- +-static struct pci_bus *dw_pcie_scan_bus(int nr, struct pci_sys_data *sys) +-{ +- struct pci_bus *bus; +- struct pcie_port *pp = sys_to_pcie(sys); +- +- pp->root_bus_nr = sys->busnr; +- bus = pci_create_root_bus(pp->dev, sys->busnr, +- &dw_pcie_ops, sys, &sys->resources); +- if (!bus) +- return NULL; +- +- pci_scan_child_bus(bus); +- +- if (bus && pp->ops->scan_bus) +- pp->ops->scan_bus(pp); +- +- return bus; +-} +- +-static int dw_pcie_map_irq(const struct pci_dev *dev, u8 slot, u8 pin) +-{ +- struct pcie_port *pp = sys_to_pcie(dev->bus->sysdata); +- int irq; +- +- irq = of_irq_parse_and_map_pci(dev, slot, pin); +- if (!irq) +- irq = pp->irq; +- +- return irq; +-} +- +-static void dw_pcie_add_bus(struct pci_bus *bus) +-{ +- if (IS_ENABLED(CONFIG_PCI_MSI)) { +- struct pcie_port *pp = sys_to_pcie(bus->sysdata); +- +- dw_pcie_msi_chip.dev = pp->dev; +- bus->msi = &dw_pcie_msi_chip; +- } +-} +- +-static struct hw_pci dw_pci = { +- .setup = dw_pcie_setup, +- .scan = dw_pcie_scan_bus, +- .map_irq = dw_pcie_map_irq, +- .add_bus = dw_pcie_add_bus, +-}; +- + void dw_pcie_setup_rc(struct pcie_port *pp) + { + u32 val; + u32 membase; + u32 memlimit; + ++ dw_pcie_prog_outbound_atu(pp, PCIE_ATU_REGION_INDEX0, ++ PCIE_ATU_TYPE_IO, pp->io_base, ++ pp->io_bus_addr, pp->io_size); ++ dw_pcie_prog_outbound_atu(pp, PCIE_ATU_REGION_INDEX1, ++ PCIE_ATU_TYPE_MEM, pp->mem_base, ++ pp->mem_bus_addr, pp->mem_size); ++ + /* set the number of lanes */ + dw_pcie_readl_rc(pp, PCIE_PORT_LINK_CONTROL, &val); + val &= ~PORT_LINK_MODE_MASK; +@@ -786,6 +522,12 @@ void dw_pcie_setup_rc(struct pcie_port *pp) + case 4: + val |= PORT_LINK_MODE_4_LANES; + break; ++ case 8: ++ val |= PORT_LINK_MODE_8_LANES; ++ break; ++ default: ++ dev_err(pp->dev, "num-lanes %u: invalid value\n", pp->lanes); ++ return; + } + dw_pcie_writel_rc(pp, val, PCIE_PORT_LINK_CONTROL); + +@@ -802,6 +544,9 @@ void dw_pcie_setup_rc(struct pcie_port *pp) + case 4: + val |= PORT_LOGIC_LINK_WIDTH_4_LANES; + break; ++ case 8: ++ val |= PORT_LOGIC_LINK_WIDTH_8_LANES; ++ break; + } + dw_pcie_writel_rc(pp, val, PCIE_LINK_WIDTH_SPEED_CONTROL); + +diff --git a/drivers/pci/host/pcie-designware.h b/drivers/pci/host/pcie-designware.h +index c625675..fcd6431 100644 +--- a/drivers/pci/host/pcie-designware.h ++++ b/drivers/pci/host/pcie-designware.h +@@ -27,28 +27,25 @@ struct pcie_port { + u8 root_bus_nr; + void __iomem *dbi_base; + u64 cfg0_base; +- u64 cfg0_mod_base; + void __iomem *va_cfg0_base; + u32 cfg0_size; + u64 cfg1_base; +- u64 cfg1_mod_base; + void __iomem *va_cfg1_base; + u32 cfg1_size; +- u64 io_base; +- u64 io_mod_base; ++ resource_size_t io_base; + phys_addr_t io_bus_addr; + u32 io_size; + u64 mem_base; +- u64 mem_mod_base; + phys_addr_t mem_bus_addr; + u32 mem_size; +- struct resource cfg; +- struct resource io; +- struct resource mem; +- struct resource busn; ++ struct resource *cfg; ++ struct resource *io; ++ struct resource *mem; ++ struct resource *busn; + int irq; + u32 lanes; + struct pcie_host_ops *ops; ++ struct msi_controller *msi; + int msi_irq; + struct irq_domain *irq_domain; + unsigned long msi_data; +@@ -70,18 +67,19 @@ struct pcie_host_ops { + void (*host_init)(struct pcie_port *pp); + void (*msi_set_irq)(struct pcie_port *pp, int irq); + void (*msi_clear_irq)(struct pcie_port *pp, int irq); +- u32 (*get_msi_addr)(struct pcie_port *pp); ++ phys_addr_t (*get_msi_addr)(struct pcie_port *pp); + u32 (*get_msi_data)(struct pcie_port *pp, int pos); + void (*scan_bus)(struct pcie_port *pp); +- int (*msi_host_init)(struct pcie_port *pp, struct msi_chip *chip); ++ int (*msi_host_init)(struct pcie_port *pp, struct msi_controller *chip); + }; + +-int dw_pcie_cfg_read(void __iomem *addr, int where, int size, u32 *val); +-int dw_pcie_cfg_write(void __iomem *addr, int where, int size, u32 val); ++int dw_pcie_cfg_read(void __iomem *addr, int size, u32 *val); ++int dw_pcie_cfg_write(void __iomem *addr, int size, u32 val); + irqreturn_t dw_handle_msi_irq(struct pcie_port *pp); + void dw_pcie_msi_init(struct pcie_port *pp); + int dw_pcie_link_up(struct pcie_port *pp); + void dw_pcie_setup_rc(struct pcie_port *pp); + int dw_pcie_host_init(struct pcie_port *pp); ++void dw_pcie_disable_outbound_atu(struct pcie_port *pp, int index); + + #endif /* _PCIE_DESIGNWARE_H */ +diff --git a/drivers/pci/host/pcie-rcar.c b/drivers/pci/host/pcie-rcar.c +index 61158e0..f8ec96d 100644 +--- a/drivers/pci/host/pcie-rcar.c ++++ b/drivers/pci/host/pcie-rcar.c +@@ -111,14 +111,14 @@ + struct rcar_msi { + DECLARE_BITMAP(used, INT_PCI_MSI_NR); + struct irq_domain *domain; +- struct msi_chip chip; ++ struct msi_controller chip; + unsigned long pages; + struct mutex lock; + int irq1; + int irq2; + }; + +-static inline struct rcar_msi *to_rcar_msi(struct msi_chip *chip) ++static inline struct rcar_msi *to_rcar_msi(struct msi_controller *chip) + { + return container_of(chip, struct rcar_msi, chip); + } +@@ -404,9 +404,6 @@ static void rcar_pcie_enable(struct rcar_pcie *pcie) + rcar_pci.private_data = (void **)&pcie; + + pci_common_init_dev(&pdev->dev, &rcar_pci); +-#ifdef CONFIG_PCI_DOMAINS +- rcar_pci.domain++; +-#endif + } + + static int phy_wait_for_ack(struct rcar_pcie *pcie) +@@ -622,7 +619,7 @@ static irqreturn_t rcar_pcie_msi_irq(int irq, void *data) + return IRQ_HANDLED; + } + +-static int rcar_msi_setup_irq(struct msi_chip *chip, struct pci_dev *pdev, ++static int rcar_msi_setup_irq(struct msi_controller *chip, struct pci_dev *pdev, + struct msi_desc *desc) + { + struct rcar_msi *msi = to_rcar_msi(chip); +@@ -647,12 +644,12 @@ static int rcar_msi_setup_irq(struct msi_chip *chip, struct pci_dev *pdev, + msg.address_hi = rcar_pci_read_reg(pcie, PCIEMSIAUR); + msg.data = hwirq; + +- write_msi_msg(irq, &msg); ++ pci_write_msi_msg(irq, &msg); + + return 0; + } + +-static void rcar_msi_teardown_irq(struct msi_chip *chip, unsigned int irq) ++static void rcar_msi_teardown_irq(struct msi_controller *chip, unsigned int irq) + { + struct rcar_msi *msi = to_rcar_msi(chip); + struct irq_data *d = irq_get_irq_data(irq); +@@ -662,10 +659,10 @@ static void rcar_msi_teardown_irq(struct msi_chip *chip, unsigned int irq) + + static struct irq_chip rcar_msi_irq_chip = { + .name = "R-Car PCIe MSI", +- .irq_enable = unmask_msi_irq, +- .irq_disable = mask_msi_irq, +- .irq_mask = mask_msi_irq, +- .irq_unmask = unmask_msi_irq, ++ .irq_enable = pci_msi_unmask_irq, ++ .irq_disable = pci_msi_mask_irq, ++ .irq_mask = pci_msi_mask_irq, ++ .irq_unmask = pci_msi_unmask_irq, + }; + + static int rcar_msi_map(struct irq_domain *domain, unsigned int irq, +@@ -673,7 +670,6 @@ static int rcar_msi_map(struct irq_domain *domain, unsigned int irq, + { + irq_set_chip_and_handler(irq, &rcar_msi_irq_chip, handle_simple_irq); + irq_set_chip_data(irq, domain->host_data); +- set_irq_flags(irq, IRQF_VALID); + + return 0; + } +diff --git a/drivers/pci/host/pcie-xilinx.c b/drivers/pci/host/pcie-xilinx.c +index ccc496b..eef849c 100644 +--- a/drivers/pci/host/pcie-xilinx.c ++++ b/drivers/pci/host/pcie-xilinx.c +@@ -297,18 +297,16 @@ static struct pci_ops xilinx_pcie_ops = { + */ + static void xilinx_pcie_destroy_msi(unsigned int irq) + { +- struct irq_desc *desc; + struct msi_desc *msi; + struct xilinx_pcie_port *port; + +- desc = irq_to_desc(irq); +- msi = irq_desc_get_msi_desc(desc); +- port = sys_to_pcie(msi->dev->bus->sysdata); +- +- if (!test_bit(irq, msi_irq_in_use)) ++ if (!test_bit(irq, msi_irq_in_use)) { ++ msi = irq_get_msi_desc(irq); ++ port = sys_to_pcie(msi_desc_to_pci_sys_data(msi)); + dev_err(port->dev, "Trying to free unused MSI#%d\n", irq); +- else ++ } else { + clear_bit(irq, msi_irq_in_use); ++ } + } + + /** +@@ -335,7 +333,8 @@ static int xilinx_pcie_assign_msi(struct xilinx_pcie_port *port) + * @chip: MSI Chip descriptor + * @irq: MSI IRQ to destroy + */ +-static void xilinx_msi_teardown_irq(struct msi_chip *chip, unsigned int irq) ++static void xilinx_msi_teardown_irq(struct msi_controller *chip, ++ unsigned int irq) + { + xilinx_pcie_destroy_msi(irq); + } +@@ -348,7 +347,7 @@ static void xilinx_msi_teardown_irq(struct msi_chip *chip, unsigned int irq) + * + * Return: '0' on success and error value on failure + */ +-static int xilinx_pcie_msi_setup_irq(struct msi_chip *chip, ++static int xilinx_pcie_msi_setup_irq(struct msi_controller *chip, + struct pci_dev *pdev, + struct msi_desc *desc) + { +@@ -374,13 +373,13 @@ static int xilinx_pcie_msi_setup_irq(struct msi_chip *chip, + msg.address_lo = msg_addr; + msg.data = irq; + +- write_msi_msg(irq, &msg); ++ pci_write_msi_msg(irq, &msg); + + return 0; + } + + /* MSI Chip Descriptor */ +-static struct msi_chip xilinx_pcie_msi_chip = { ++static struct msi_controller xilinx_pcie_msi_chip = { + .setup_irq = xilinx_pcie_msi_setup_irq, + .teardown_irq = xilinx_msi_teardown_irq, + }; +@@ -388,10 +387,10 @@ static struct msi_chip xilinx_pcie_msi_chip = { + /* HW Interrupt Chip Descriptor */ + static struct irq_chip xilinx_msi_irq_chip = { + .name = "Xilinx PCIe MSI", +- .irq_enable = unmask_msi_irq, +- .irq_disable = mask_msi_irq, +- .irq_mask = mask_msi_irq, +- .irq_unmask = unmask_msi_irq, ++ .irq_enable = pci_msi_unmask_irq, ++ .irq_disable = pci_msi_mask_irq, ++ .irq_mask = pci_msi_mask_irq, ++ .irq_unmask = pci_msi_unmask_irq, + }; + + /** +@@ -407,7 +406,6 @@ static int xilinx_pcie_msi_map(struct irq_domain *domain, unsigned int irq, + { + irq_set_chip_and_handler(irq, &xilinx_msi_irq_chip, handle_simple_irq); + irq_set_chip_data(irq, domain->host_data); +- set_irq_flags(irq, IRQF_VALID); + + return 0; + } +@@ -431,20 +429,6 @@ static void xilinx_pcie_enable_msi(struct xilinx_pcie_port *port) + pcie_write(port, msg_addr, XILINX_PCIE_REG_MSIBASE2); + } + +-/** +- * xilinx_pcie_add_bus - Add MSI chip info to PCIe bus +- * @bus: PCIe bus +- */ +-static void xilinx_pcie_add_bus(struct pci_bus *bus) +-{ +- if (IS_ENABLED(CONFIG_PCI_MSI)) { +- struct xilinx_pcie_port *port = sys_to_pcie(bus->sysdata); +- +- xilinx_pcie_msi_chip.dev = port->dev; +- bus->msi = &xilinx_pcie_msi_chip; +- } +-} +- + /* INTx Functions */ + + /** +@@ -460,7 +444,6 @@ static int xilinx_pcie_intx_map(struct irq_domain *domain, unsigned int irq, + { + irq_set_chip_and_handler(irq, &dummy_irq_chip, handle_simple_irq); + irq_set_chip_data(irq, domain->host_data); +- set_irq_flags(irq, IRQF_VALID); + + return 0; + } +@@ -730,9 +713,15 @@ static struct pci_bus *xilinx_pcie_scan_bus(int nr, struct pci_sys_data *sys) + struct pci_bus *bus; + + port->root_busno = sys->busnr; +- bus = pci_scan_root_bus(port->dev, sys->busnr, &xilinx_pcie_ops, +- sys, &sys->resources); + ++ if (IS_ENABLED(CONFIG_PCI_MSI)) ++ bus = pci_scan_root_bus_msi(port->dev, sys->busnr, ++ &xilinx_pcie_ops, sys, ++ &sys->resources, ++ &xilinx_pcie_msi_chip); ++ else ++ bus = pci_scan_root_bus(port->dev, sys->busnr, ++ &xilinx_pcie_ops, sys, &sys->resources); + return bus; + } + +@@ -750,7 +739,7 @@ static int xilinx_pcie_parse_and_add_res(struct xilinx_pcie_port *port) + resource_size_t offset; + struct of_pci_range_parser parser; + struct of_pci_range range; +- struct pci_host_bridge_window *win; ++ struct resource_entry *win; + int err = 0, mem_resno = 0; + + /* Get the ranges */ +@@ -820,7 +809,7 @@ static int xilinx_pcie_parse_and_add_res(struct xilinx_pcie_port *port) + + free_resources: + release_child_resources(&iomem_resource); +- list_for_each_entry(win, &port->resources, list) ++ resource_list_for_each_entry(win, &port->resources) + devm_kfree(dev, win->res); + pci_free_resource_list(&port->resources); + +@@ -924,10 +913,13 @@ static int xilinx_pcie_probe(struct platform_device *pdev) + .private_data = (void **)&port, + .setup = xilinx_pcie_setup, + .map_irq = of_irq_parse_and_map_pci, +- .add_bus = xilinx_pcie_add_bus, + .scan = xilinx_pcie_scan_bus, + .ops = &xilinx_pcie_ops, + }; ++ ++#ifdef CONFIG_PCI_MSI ++ xilinx_pcie_msi_chip.dev = port->dev; ++#endif + pci_common_init_dev(dev, &hw); + + return 0; +diff --git a/drivers/pci/msi.c b/drivers/pci/msi.c +index 084587d..5e64d37 100644 +--- a/drivers/pci/msi.c ++++ b/drivers/pci/msi.c +@@ -19,19 +19,81 @@ + #include + #include + #include ++#include + + #include "pci.h" + + static int pci_msi_enable = 1; ++int pci_msi_ignore_mask; + + #define msix_table_size(flags) ((flags & PCI_MSIX_FLAGS_QSIZE) + 1) + ++#ifdef CONFIG_PCI_MSI_IRQ_DOMAIN ++static struct irq_domain *pci_msi_default_domain; ++static DEFINE_MUTEX(pci_msi_domain_lock); ++ ++struct irq_domain * __weak arch_get_pci_msi_domain(struct pci_dev *dev) ++{ ++ return pci_msi_default_domain; ++} ++ ++static struct irq_domain *pci_msi_get_domain(struct pci_dev *dev) ++{ ++ struct irq_domain *domain; ++ ++ domain = dev_get_msi_domain(&dev->dev); ++ if (domain) ++ return domain; ++ ++ return arch_get_pci_msi_domain(dev); ++} ++ ++static int pci_msi_setup_msi_irqs(struct pci_dev *dev, int nvec, int type) ++{ ++ struct irq_domain *domain; ++ ++ domain = pci_msi_get_domain(dev); ++ if (domain) ++ return pci_msi_domain_alloc_irqs(domain, dev, nvec, type); ++ ++ return arch_setup_msi_irqs(dev, nvec, type); ++} ++ ++static void pci_msi_teardown_msi_irqs(struct pci_dev *dev) ++{ ++ struct irq_domain *domain; ++ ++ domain = pci_msi_get_domain(dev); ++ if (domain) ++ pci_msi_domain_free_irqs(domain, dev); ++ else ++ arch_teardown_msi_irqs(dev); ++} ++#else ++#define pci_msi_setup_msi_irqs arch_setup_msi_irqs ++#define pci_msi_teardown_msi_irqs arch_teardown_msi_irqs ++#endif + + /* Arch hooks */ + ++struct msi_controller * __weak pcibios_msi_controller(struct pci_dev *dev) ++{ ++ return NULL; ++} ++ ++static struct msi_controller *pci_msi_controller(struct pci_dev *dev) ++{ ++ struct msi_controller *msi_ctrl = dev->bus->msi; ++ ++ if (msi_ctrl) ++ return msi_ctrl; ++ ++ return pcibios_msi_controller(dev); ++} ++ + int __weak arch_setup_msi_irq(struct pci_dev *dev, struct msi_desc *desc) + { +- struct msi_chip *chip = dev->bus->msi; ++ struct msi_controller *chip = pci_msi_controller(dev); + int err; + + if (!chip || !chip->setup_irq) +@@ -48,7 +110,7 @@ int __weak arch_setup_msi_irq(struct pci_dev *dev, struct msi_desc *desc) + + void __weak arch_teardown_msi_irq(unsigned int irq) + { +- struct msi_chip *chip = irq_get_chip_data(irq); ++ struct msi_controller *chip = irq_get_chip_data(irq); + + if (!chip || !chip->teardown_irq) + return; +@@ -58,9 +120,12 @@ void __weak arch_teardown_msi_irq(unsigned int irq) + + int __weak arch_setup_msi_irqs(struct pci_dev *dev, int nvec, int type) + { ++ struct msi_controller *chip = dev->bus->msi; + struct msi_desc *entry; + int ret; + ++ if (chip && chip->setup_irqs) ++ return chip->setup_irqs(chip, dev, nvec, type); + /* + * If an architecture wants to support multiple MSI, it needs to + * override arch_setup_msi_irqs() +@@ -68,7 +133,7 @@ int __weak arch_setup_msi_irqs(struct pci_dev *dev, int nvec, int type) + if (type == PCI_CAP_ID_MSI && nvec > 1) + return 1; + +- list_for_each_entry(entry, &dev->msi_list, list) { ++ for_each_pci_msi_entry(entry, dev) { + ret = arch_setup_msi_irq(dev, entry); + if (ret < 0) + return ret; +@@ -85,19 +150,13 @@ int __weak arch_setup_msi_irqs(struct pci_dev *dev, int nvec, int type) + */ + void default_teardown_msi_irqs(struct pci_dev *dev) + { ++ int i; + struct msi_desc *entry; + +- list_for_each_entry(entry, &dev->msi_list, list) { +- int i, nvec; +- if (entry->irq == 0) +- continue; +- if (entry->nvec_used) +- nvec = entry->nvec_used; +- else +- nvec = 1 << entry->msi_attrib.multiple; +- for (i = 0; i < nvec; i++) +- arch_teardown_msi_irq(entry->irq + i); +- } ++ for_each_pci_msi_entry(entry, dev) ++ if (entry->irq) ++ for (i = 0; i < entry->nvec_used; i++) ++ arch_teardown_msi_irq(entry->irq + i); + } + + void __weak arch_teardown_msi_irqs(struct pci_dev *dev) +@@ -111,7 +170,7 @@ static void default_restore_msi_irq(struct pci_dev *dev, int irq) + + entry = NULL; + if (dev->msix_enabled) { +- list_for_each_entry(entry, &dev->msi_list, list) { ++ for_each_pci_msi_entry(entry, dev) { + if (irq == entry->irq) + break; + } +@@ -120,7 +179,7 @@ static void default_restore_msi_irq(struct pci_dev *dev, int irq) + } + + if (entry) +- __write_msi_msg(entry, &entry->msg); ++ __pci_write_msi_msg(entry, &entry->msg); + } + + void __weak arch_restore_msi_irqs(struct pci_dev *dev) +@@ -128,27 +187,6 @@ void __weak arch_restore_msi_irqs(struct pci_dev *dev) + return default_restore_msi_irqs(dev); + } + +-static void msi_set_enable(struct pci_dev *dev, int enable) +-{ +- u16 control; +- +- pci_read_config_word(dev, dev->msi_cap + PCI_MSI_FLAGS, &control); +- control &= ~PCI_MSI_FLAGS_ENABLE; +- if (enable) +- control |= PCI_MSI_FLAGS_ENABLE; +- pci_write_config_word(dev, dev->msi_cap + PCI_MSI_FLAGS, control); +-} +- +-static void msix_clear_and_set_ctrl(struct pci_dev *dev, u16 clear, u16 set) +-{ +- u16 ctrl; +- +- pci_read_config_word(dev, dev->msix_cap + PCI_MSIX_FLAGS, &ctrl); +- ctrl &= ~clear; +- ctrl |= set; +- pci_write_config_word(dev, dev->msix_cap + PCI_MSIX_FLAGS, ctrl); +-} +- + static inline __attribute_const__ u32 msi_mask(unsigned x) + { + /* Don't shift by >= width of type */ +@@ -163,28 +201,24 @@ static inline __attribute_const__ u32 msi_mask(unsigned x) + * reliably as devices without an INTx disable bit will then generate a + * level IRQ which will never be cleared. + */ +-u32 default_msi_mask_irq(struct msi_desc *desc, u32 mask, u32 flag) ++u32 __pci_msi_desc_mask_irq(struct msi_desc *desc, u32 mask, u32 flag) + { + u32 mask_bits = desc->masked; + +- if (!desc->msi_attrib.maskbit) ++ if (pci_msi_ignore_mask || !desc->msi_attrib.maskbit) + return 0; + + mask_bits &= ~mask; + mask_bits |= flag; +- pci_write_config_dword(desc->dev, desc->mask_pos, mask_bits); ++ pci_write_config_dword(msi_desc_to_pci_dev(desc), desc->mask_pos, ++ mask_bits); + + return mask_bits; + } + +-__weak u32 arch_msi_mask_irq(struct msi_desc *desc, u32 mask, u32 flag) +-{ +- return default_msi_mask_irq(desc, mask, flag); +-} +- + static void msi_mask_irq(struct msi_desc *desc, u32 mask, u32 flag) + { +- desc->masked = arch_msi_mask_irq(desc, mask, flag); ++ desc->masked = __pci_msi_desc_mask_irq(desc, mask, flag); + } + + /* +@@ -194,11 +228,15 @@ static void msi_mask_irq(struct msi_desc *desc, u32 mask, u32 flag) + * file. This saves a few milliseconds when initialising devices with lots + * of MSI-X interrupts. + */ +-u32 default_msix_mask_irq(struct msi_desc *desc, u32 flag) ++u32 __pci_msix_desc_mask_irq(struct msi_desc *desc, u32 flag) + { + u32 mask_bits = desc->masked; + unsigned offset = desc->msi_attrib.entry_nr * PCI_MSIX_ENTRY_SIZE + + PCI_MSIX_ENTRY_VECTOR_CTRL; ++ ++ if (pci_msi_ignore_mask) ++ return 0; ++ + mask_bits &= ~PCI_MSIX_ENTRY_CTRL_MASKBIT; + if (flag) + mask_bits |= PCI_MSIX_ENTRY_CTRL_MASKBIT; +@@ -207,19 +245,14 @@ u32 default_msix_mask_irq(struct msi_desc *desc, u32 flag) + return mask_bits; + } + +-__weak u32 arch_msix_mask_irq(struct msi_desc *desc, u32 flag) +-{ +- return default_msix_mask_irq(desc, flag); +-} +- + static void msix_mask_irq(struct msi_desc *desc, u32 flag) + { +- desc->masked = arch_msix_mask_irq(desc, flag); ++ desc->masked = __pci_msix_desc_mask_irq(desc, flag); + } + + static void msi_set_mask_bit(struct irq_data *data, u32 flag) + { +- struct msi_desc *desc = irq_data_get_msi(data); ++ struct msi_desc *desc = irq_data_get_msi_desc(data); + + if (desc->msi_attrib.is_msix) { + msix_mask_irq(desc, flag); +@@ -230,12 +263,20 @@ static void msi_set_mask_bit(struct irq_data *data, u32 flag) + } + } + +-void mask_msi_irq(struct irq_data *data) ++/** ++ * pci_msi_mask_irq - Generic irq chip callback to mask PCI/MSI interrupts ++ * @data: pointer to irqdata associated to that interrupt ++ */ ++void pci_msi_mask_irq(struct irq_data *data) + { + msi_set_mask_bit(data, 1); + } + +-void unmask_msi_irq(struct irq_data *data) ++/** ++ * pci_msi_unmask_irq - Generic irq chip callback to unmask PCI/MSI interrupts ++ * @data: pointer to irqdata associated to that interrupt ++ */ ++void pci_msi_unmask_irq(struct irq_data *data) + { + msi_set_mask_bit(data, 0); + } +@@ -244,14 +285,15 @@ void default_restore_msi_irqs(struct pci_dev *dev) + { + struct msi_desc *entry; + +- list_for_each_entry(entry, &dev->msi_list, list) { ++ for_each_pci_msi_entry(entry, dev) + default_restore_msi_irq(dev, entry->irq); +- } + } + +-void __read_msi_msg(struct msi_desc *entry, struct msi_msg *msg) ++void __pci_read_msi_msg(struct msi_desc *entry, struct msi_msg *msg) + { +- BUG_ON(entry->dev->current_state != PCI_D0); ++ struct pci_dev *dev = msi_desc_to_pci_dev(entry); ++ ++ BUG_ON(dev->current_state != PCI_D0); + + if (entry->msi_attrib.is_msix) { + void __iomem *base = entry->mask_base + +@@ -261,7 +303,6 @@ void __read_msi_msg(struct msi_desc *entry, struct msi_msg *msg) + msg->address_hi = readl(base + PCI_MSIX_ENTRY_UPPER_ADDR); + msg->data = readl(base + PCI_MSIX_ENTRY_DATA); + } else { +- struct pci_dev *dev = entry->dev; + int pos = dev->msi_cap; + u16 data; + +@@ -279,34 +320,11 @@ void __read_msi_msg(struct msi_desc *entry, struct msi_msg *msg) + } + } + +-void read_msi_msg(unsigned int irq, struct msi_msg *msg) +-{ +- struct msi_desc *entry = irq_get_msi_desc(irq); +- +- __read_msi_msg(entry, msg); +-} +- +-void __get_cached_msi_msg(struct msi_desc *entry, struct msi_msg *msg) +-{ +- /* Assert that the cache is valid, assuming that +- * valid messages are not all-zeroes. */ +- BUG_ON(!(entry->msg.address_hi | entry->msg.address_lo | +- entry->msg.data)); +- +- *msg = entry->msg; +-} +- +-void get_cached_msi_msg(unsigned int irq, struct msi_msg *msg) ++void __pci_write_msi_msg(struct msi_desc *entry, struct msi_msg *msg) + { +- struct msi_desc *entry = irq_get_msi_desc(irq); +- +- __get_cached_msi_msg(entry, msg); +-} +-EXPORT_SYMBOL_GPL(get_cached_msi_msg); ++ struct pci_dev *dev = msi_desc_to_pci_dev(entry); + +-void __write_msi_msg(struct msi_desc *entry, struct msi_msg *msg) +-{ +- if (entry->dev->current_state != PCI_D0) { ++ if (dev->current_state != PCI_D0) { + /* Don't touch the hardware now */ + } else if (entry->msi_attrib.is_msix) { + void __iomem *base; +@@ -317,7 +335,6 @@ void __write_msi_msg(struct msi_desc *entry, struct msi_msg *msg) + writel(msg->address_hi, base + PCI_MSIX_ENTRY_UPPER_ADDR); + writel(msg->data, base + PCI_MSIX_ENTRY_DATA); + } else { +- struct pci_dev *dev = entry->dev; + int pos = dev->msi_cap; + u16 msgctl; + +@@ -341,38 +358,32 @@ void __write_msi_msg(struct msi_desc *entry, struct msi_msg *msg) + entry->msg = *msg; + } + +-void write_msi_msg(unsigned int irq, struct msi_msg *msg) ++void pci_write_msi_msg(unsigned int irq, struct msi_msg *msg) + { + struct msi_desc *entry = irq_get_msi_desc(irq); + +- __write_msi_msg(entry, msg); ++ __pci_write_msi_msg(entry, msg); + } +-EXPORT_SYMBOL_GPL(write_msi_msg); ++EXPORT_SYMBOL_GPL(pci_write_msi_msg); + + static void free_msi_irqs(struct pci_dev *dev) + { ++ struct list_head *msi_list = dev_to_msi_list(&dev->dev); + struct msi_desc *entry, *tmp; + struct attribute **msi_attrs; + struct device_attribute *dev_attr; +- int count = 0; ++ int i, count = 0; + +- list_for_each_entry(entry, &dev->msi_list, list) { +- int i, nvec; +- if (!entry->irq) +- continue; +- if (entry->nvec_used) +- nvec = entry->nvec_used; +- else +- nvec = 1 << entry->msi_attrib.multiple; +- for (i = 0; i < nvec; i++) +- BUG_ON(irq_has_action(entry->irq + i)); +- } ++ for_each_pci_msi_entry(entry, dev) ++ if (entry->irq) ++ for (i = 0; i < entry->nvec_used; i++) ++ BUG_ON(irq_has_action(entry->irq + i)); + +- arch_teardown_msi_irqs(dev); ++ pci_msi_teardown_msi_irqs(dev); + +- list_for_each_entry_safe(entry, tmp, &dev->msi_list, list) { ++ list_for_each_entry_safe(entry, tmp, msi_list, list) { + if (entry->msi_attrib.is_msix) { +- if (list_is_last(&entry->list, &dev->msi_list)) ++ if (list_is_last(&entry->list, msi_list)) + iounmap(entry->mask_base); + } + +@@ -397,18 +408,6 @@ static void free_msi_irqs(struct pci_dev *dev) + } + } + +-static struct msi_desc *alloc_msi_entry(struct pci_dev *dev) +-{ +- struct msi_desc *desc = kzalloc(sizeof(*desc), GFP_KERNEL); +- if (!desc) +- return NULL; +- +- INIT_LIST_HEAD(&desc->list); +- desc->dev = dev; +- +- return desc; +-} +- + static void pci_intx_for_msi(struct pci_dev *dev, int enable) + { + if (!(dev->dev_flags & PCI_DEV_FLAGS_MSI_INTX_DISABLE_BUG)) +@@ -426,7 +425,7 @@ static void __pci_restore_msi_state(struct pci_dev *dev) + entry = irq_get_msi_desc(dev->irq); + + pci_intx_for_msi(dev, 0); +- msi_set_enable(dev, 0); ++ pci_msi_set_enable(dev, 0); + arch_restore_msi_irqs(dev); + + pci_read_config_word(dev, dev->msi_cap + PCI_MSI_FLAGS, &control); +@@ -443,19 +442,18 @@ static void __pci_restore_msix_state(struct pci_dev *dev) + + if (!dev->msix_enabled) + return; +- BUG_ON(list_empty(&dev->msi_list)); ++ BUG_ON(list_empty(dev_to_msi_list(&dev->dev))); + + /* route the table */ + pci_intx_for_msi(dev, 0); +- msix_clear_and_set_ctrl(dev, 0, ++ pci_msix_clear_and_set_ctrl(dev, 0, + PCI_MSIX_FLAGS_ENABLE | PCI_MSIX_FLAGS_MASKALL); + + arch_restore_msi_irqs(dev); +- list_for_each_entry(entry, &dev->msi_list, list) { ++ for_each_pci_msi_entry(entry, dev) + msix_mask_irq(entry, entry->masked); +- } + +- msix_clear_and_set_ctrl(dev, PCI_MSIX_FLAGS_MASKALL, 0); ++ pci_msix_clear_and_set_ctrl(dev, PCI_MSIX_FLAGS_MASKALL, 0); + } + + void pci_restore_msi_state(struct pci_dev *dev) +@@ -497,9 +495,8 @@ static int populate_msi_sysfs(struct pci_dev *pdev) + int count = 0; + + /* Determine how many msi entries we have */ +- list_for_each_entry(entry, &pdev->msi_list, list) { ++ for_each_pci_msi_entry(entry, pdev) + ++num_msi; +- } + if (!num_msi) + return 0; + +@@ -507,7 +504,7 @@ static int populate_msi_sysfs(struct pci_dev *pdev) + msi_attrs = kzalloc(sizeof(void *) * (num_msi + 1), GFP_KERNEL); + if (!msi_attrs) + return -ENOMEM; +- list_for_each_entry(entry, &pdev->msi_list, list) { ++ for_each_pci_msi_entry(entry, pdev) { + msi_dev_attr = kzalloc(sizeof(*msi_dev_attr), GFP_KERNEL); + if (!msi_dev_attr) + goto error_attrs; +@@ -559,13 +556,13 @@ error_attrs: + return ret; + } + +-static struct msi_desc *msi_setup_entry(struct pci_dev *dev) ++static struct msi_desc *msi_setup_entry(struct pci_dev *dev, int nvec) + { + u16 control; + struct msi_desc *entry; + + /* MSI Entry Initialization */ +- entry = alloc_msi_entry(dev); ++ entry = alloc_msi_entry(&dev->dev); + if (!entry) + return NULL; + +@@ -577,6 +574,8 @@ static struct msi_desc *msi_setup_entry(struct pci_dev *dev) + entry->msi_attrib.maskbit = !!(control & PCI_MSI_FLAGS_MASKBIT); + entry->msi_attrib.default_irq = dev->irq; /* Save IOAPIC IRQ */ + entry->msi_attrib.multi_cap = (control & PCI_MSI_FLAGS_QMASK) >> 1; ++ entry->msi_attrib.multiple = ilog2(__roundup_pow_of_two(nvec)); ++ entry->nvec_used = nvec; + + if (control & PCI_MSI_FLAGS_64BIT) + entry->mask_pos = dev->msi_cap + PCI_MSI_MASK_64; +@@ -594,7 +593,7 @@ static int msi_verify_entries(struct pci_dev *dev) + { + struct msi_desc *entry; + +- list_for_each_entry(entry, &dev->msi_list, list) { ++ for_each_pci_msi_entry(entry, dev) { + if (!dev->no_64bit_msi || !entry->msg.address_hi) + continue; + dev_err(&dev->dev, "Device has broken 64-bit MSI but arch" +@@ -621,9 +620,9 @@ static int msi_capability_init(struct pci_dev *dev, int nvec) + int ret; + unsigned mask; + +- msi_set_enable(dev, 0); /* Disable MSI during set up */ ++ pci_msi_set_enable(dev, 0); /* Disable MSI during set up */ + +- entry = msi_setup_entry(dev); ++ entry = msi_setup_entry(dev, nvec); + if (!entry) + return -ENOMEM; + +@@ -631,10 +630,10 @@ static int msi_capability_init(struct pci_dev *dev, int nvec) + mask = msi_mask(entry->msi_attrib.multi_cap); + msi_mask_irq(entry, mask, mask); + +- list_add_tail(&entry->list, &dev->msi_list); ++ list_add_tail(&entry->list, dev_to_msi_list(&dev->dev)); + + /* Configure MSI capability structure */ +- ret = arch_setup_msi_irqs(dev, nvec, PCI_CAP_ID_MSI); ++ ret = pci_msi_setup_msi_irqs(dev, nvec, PCI_CAP_ID_MSI); + if (ret) { + msi_mask_irq(entry, mask, ~mask); + free_msi_irqs(dev); +@@ -657,7 +656,7 @@ static int msi_capability_init(struct pci_dev *dev, int nvec) + + /* Set MSI enabled bits */ + pci_intx_for_msi(dev, 0); +- msi_set_enable(dev, 1); ++ pci_msi_set_enable(dev, 1); + dev->msi_enabled = 1; + + dev->irq = entry->irq; +@@ -668,11 +667,16 @@ static void __iomem *msix_map_region(struct pci_dev *dev, unsigned nr_entries) + { + resource_size_t phys_addr; + u32 table_offset; ++ unsigned long flags; + u8 bir; + + pci_read_config_dword(dev, dev->msix_cap + PCI_MSIX_TABLE, + &table_offset); + bir = (u8)(table_offset & PCI_MSIX_TABLE_BIR); ++ flags = pci_resource_flags(dev, bir); ++ if (!flags || (flags & IORESOURCE_UNSET)) ++ return NULL; ++ + table_offset &= PCI_MSIX_TABLE_OFFSET; + phys_addr = pci_resource_start(dev, bir) + table_offset; + +@@ -686,7 +690,7 @@ static int msix_setup_entries(struct pci_dev *dev, void __iomem *base, + int i; + + for (i = 0; i < nvec; i++) { +- entry = alloc_msi_entry(dev); ++ entry = alloc_msi_entry(&dev->dev); + if (!entry) { + if (!i) + iounmap(base); +@@ -701,8 +705,9 @@ static int msix_setup_entries(struct pci_dev *dev, void __iomem *base, + entry->msi_attrib.entry_nr = entries[i].entry; + entry->msi_attrib.default_irq = dev->irq; + entry->mask_base = base; ++ entry->nvec_used = 1; + +- list_add_tail(&entry->list, &dev->msi_list); ++ list_add_tail(&entry->list, dev_to_msi_list(&dev->dev)); + } + + return 0; +@@ -714,12 +719,11 @@ static void msix_program_entries(struct pci_dev *dev, + struct msi_desc *entry; + int i = 0; + +- list_for_each_entry(entry, &dev->msi_list, list) { ++ for_each_pci_msi_entry(entry, dev) { + int offset = entries[i].entry * PCI_MSIX_ENTRY_SIZE + + PCI_MSIX_ENTRY_VECTOR_CTRL; + + entries[i].vector = entry->irq; +- irq_set_msi_desc(entry->irq, entry); + entry->masked = readl(entry->mask_base + offset); + msix_mask_irq(entry, 1); + i++; +@@ -744,7 +748,7 @@ static int msix_capability_init(struct pci_dev *dev, + void __iomem *base; + + /* Ensure MSI-X is disabled while it is set up */ +- msix_clear_and_set_ctrl(dev, PCI_MSIX_FLAGS_ENABLE, 0); ++ pci_msix_clear_and_set_ctrl(dev, PCI_MSIX_FLAGS_ENABLE, 0); + + pci_read_config_word(dev, dev->msix_cap + PCI_MSIX_FLAGS, &control); + /* Request & Map MSI-X table region */ +@@ -756,7 +760,7 @@ static int msix_capability_init(struct pci_dev *dev, + if (ret) + return ret; + +- ret = arch_setup_msi_irqs(dev, nvec, PCI_CAP_ID_MSIX); ++ ret = pci_msi_setup_msi_irqs(dev, nvec, PCI_CAP_ID_MSIX); + if (ret) + goto out_avail; + +@@ -770,7 +774,7 @@ static int msix_capability_init(struct pci_dev *dev, + * MSI-X registers. We need to mask all the vectors to prevent + * interrupts coming in before they're fully set up. + */ +- msix_clear_and_set_ctrl(dev, 0, ++ pci_msix_clear_and_set_ctrl(dev, 0, + PCI_MSIX_FLAGS_MASKALL | PCI_MSIX_FLAGS_ENABLE); + + msix_program_entries(dev, entries); +@@ -783,7 +787,7 @@ static int msix_capability_init(struct pci_dev *dev, + pci_intx_for_msi(dev, 0); + dev->msix_enabled = 1; + +- msix_clear_and_set_ctrl(dev, PCI_MSIX_FLAGS_MASKALL, 0); ++ pci_msix_clear_and_set_ctrl(dev, PCI_MSIX_FLAGS_MASKALL, 0); + + return 0; + +@@ -796,7 +800,7 @@ out_avail: + struct msi_desc *entry; + int avail = 0; + +- list_for_each_entry(entry, &dev->msi_list, list) { ++ for_each_pci_msi_entry(entry, dev) { + if (entry->irq != 0) + avail++; + } +@@ -885,17 +889,17 @@ void pci_msi_shutdown(struct pci_dev *dev) + if (!pci_msi_enable || !dev || !dev->msi_enabled) + return; + +- BUG_ON(list_empty(&dev->msi_list)); +- desc = list_first_entry(&dev->msi_list, struct msi_desc, list); ++ BUG_ON(list_empty(dev_to_msi_list(&dev->dev))); ++ desc = first_pci_msi_entry(dev); + +- msi_set_enable(dev, 0); ++ pci_msi_set_enable(dev, 0); + pci_intx_for_msi(dev, 1); + dev->msi_enabled = 0; + + /* Return the device with MSI unmasked as initial states */ + mask = msi_mask(desc->msi_attrib.multi_cap); + /* Keep cached state to be restored */ +- arch_msi_mask_irq(desc, mask, ~mask); ++ __pci_msi_desc_mask_irq(desc, mask, ~mask); + + /* Restore dev->irq to its default pin-assertion irq */ + dev->irq = desc->msi_attrib.default_irq; +@@ -991,12 +995,12 @@ void pci_msix_shutdown(struct pci_dev *dev) + return; + + /* Return the device with MSI-X masked as initial states */ +- list_for_each_entry(entry, &dev->msi_list, list) { ++ for_each_pci_msi_entry(entry, dev) { + /* Keep cached states to be restored */ +- arch_msix_mask_irq(entry, 1); ++ __pci_msix_desc_mask_irq(entry, 1); + } + +- msix_clear_and_set_ctrl(dev, PCI_MSIX_FLAGS_ENABLE, 0); ++ pci_msix_clear_and_set_ctrl(dev, PCI_MSIX_FLAGS_ENABLE, 0); + pci_intx_for_msi(dev, 1); + dev->msix_enabled = 0; + } +@@ -1030,19 +1034,6 @@ EXPORT_SYMBOL(pci_msi_enabled); + + void pci_msi_init_pci_dev(struct pci_dev *dev) + { +- INIT_LIST_HEAD(&dev->msi_list); +- +- /* Disable the msi hardware to avoid screaming interrupts +- * during boot. This is the power on reset default so +- * usually this should be a noop. +- */ +- dev->msi_cap = pci_find_capability(dev, PCI_CAP_ID_MSI); +- if (dev->msi_cap) +- msi_set_enable(dev, 0); +- +- dev->msix_cap = pci_find_capability(dev, PCI_CAP_ID_MSIX); +- if (dev->msix_cap) +- msix_clear_and_set_ctrl(dev, PCI_MSIX_FLAGS_ENABLE, 0); + } + + /** +@@ -1138,3 +1129,217 @@ int pci_enable_msix_range(struct pci_dev *dev, struct msix_entry *entries, + return nvec; + } + EXPORT_SYMBOL(pci_enable_msix_range); ++ ++struct pci_dev *msi_desc_to_pci_dev(struct msi_desc *desc) ++{ ++ return to_pci_dev(desc->dev); ++} ++ ++void *msi_desc_to_pci_sysdata(struct msi_desc *desc) ++{ ++ struct pci_dev *dev = msi_desc_to_pci_dev(desc); ++ ++ return dev->bus->sysdata; ++} ++EXPORT_SYMBOL_GPL(msi_desc_to_pci_sysdata); ++ ++#ifdef CONFIG_PCI_MSI_IRQ_DOMAIN ++/** ++ * pci_msi_domain_write_msg - Helper to write MSI message to PCI config space ++ * @irq_data: Pointer to interrupt data of the MSI interrupt ++ * @msg: Pointer to the message ++ */ ++void pci_msi_domain_write_msg(struct irq_data *irq_data, struct msi_msg *msg) ++{ ++ struct msi_desc *desc = irq_data->msi_desc; ++ ++ /* ++ * For MSI-X desc->irq is always equal to irq_data->irq. For ++ * MSI only the first interrupt of MULTI MSI passes the test. ++ */ ++ if (desc->irq == irq_data->irq) ++ __pci_write_msi_msg(desc, msg); ++} ++ ++/** ++ * pci_msi_domain_calc_hwirq - Generate a unique ID for an MSI source ++ * @dev: Pointer to the PCI device ++ * @desc: Pointer to the msi descriptor ++ * ++ * The ID number is only used within the irqdomain. ++ */ ++irq_hw_number_t pci_msi_domain_calc_hwirq(struct pci_dev *dev, ++ struct msi_desc *desc) ++{ ++ return (irq_hw_number_t)desc->msi_attrib.entry_nr | ++ PCI_DEVID(dev->bus->number, dev->devfn) << 11 | ++ (pci_domain_nr(dev->bus) & 0xFFFFFFFF) << 27; ++} ++ ++static inline bool pci_msi_desc_is_multi_msi(struct msi_desc *desc) ++{ ++ return !desc->msi_attrib.is_msix && desc->nvec_used > 1; ++} ++ ++/** ++ * pci_msi_domain_check_cap - Verify that @domain supports the capabilities for @dev ++ * @domain: The interrupt domain to check ++ * @info: The domain info for verification ++ * @dev: The device to check ++ * ++ * Returns: ++ * 0 if the functionality is supported ++ * 1 if Multi MSI is requested, but the domain does not support it ++ * -ENOTSUPP otherwise ++ */ ++int pci_msi_domain_check_cap(struct irq_domain *domain, ++ struct msi_domain_info *info, struct device *dev) ++{ ++ struct msi_desc *desc = first_pci_msi_entry(to_pci_dev(dev)); ++ ++ /* Special handling to support pci_enable_msi_range() */ ++ if (pci_msi_desc_is_multi_msi(desc) && ++ !(info->flags & MSI_FLAG_MULTI_PCI_MSI)) ++ return 1; ++ else if (desc->msi_attrib.is_msix && !(info->flags & MSI_FLAG_PCI_MSIX)) ++ return -ENOTSUPP; ++ ++ return 0; ++} ++ ++static int pci_msi_domain_handle_error(struct irq_domain *domain, ++ struct msi_desc *desc, int error) ++{ ++ /* Special handling to support pci_enable_msi_range() */ ++ if (pci_msi_desc_is_multi_msi(desc) && error == -ENOSPC) ++ return 1; ++ ++ return error; ++} ++ ++#ifdef GENERIC_MSI_DOMAIN_OPS ++static void pci_msi_domain_set_desc(msi_alloc_info_t *arg, ++ struct msi_desc *desc) ++{ ++ arg->desc = desc; ++ arg->hwirq = pci_msi_domain_calc_hwirq(msi_desc_to_pci_dev(desc), ++ desc); ++} ++#else ++#define pci_msi_domain_set_desc NULL ++#endif ++ ++static struct msi_domain_ops pci_msi_domain_ops_default = { ++ .set_desc = pci_msi_domain_set_desc, ++ .msi_check = pci_msi_domain_check_cap, ++ .handle_error = pci_msi_domain_handle_error, ++}; ++ ++static void pci_msi_domain_update_dom_ops(struct msi_domain_info *info) ++{ ++ struct msi_domain_ops *ops = info->ops; ++ ++ if (ops == NULL) { ++ info->ops = &pci_msi_domain_ops_default; ++ } else { ++ if (ops->set_desc == NULL) ++ ops->set_desc = pci_msi_domain_set_desc; ++ if (ops->msi_check == NULL) ++ ops->msi_check = pci_msi_domain_check_cap; ++ if (ops->handle_error == NULL) ++ ops->handle_error = pci_msi_domain_handle_error; ++ } ++} ++ ++static void pci_msi_domain_update_chip_ops(struct msi_domain_info *info) ++{ ++ struct irq_chip *chip = info->chip; ++ ++ BUG_ON(!chip); ++ if (!chip->irq_write_msi_msg) ++ chip->irq_write_msi_msg = pci_msi_domain_write_msg; ++} ++ ++/** ++ * pci_msi_create_irq_domain - Creat a MSI interrupt domain ++ * @node: Optional device-tree node of the interrupt controller ++ * @info: MSI domain info ++ * @parent: Parent irq domain ++ * ++ * Updates the domain and chip ops and creates a MSI interrupt domain. ++ * ++ * Returns: ++ * A domain pointer or NULL in case of failure. ++ */ ++struct irq_domain *pci_msi_create_irq_domain(struct device_node *node, ++ struct msi_domain_info *info, ++ struct irq_domain *parent) ++{ ++ struct irq_domain *domain; ++ ++ if (info->flags & MSI_FLAG_USE_DEF_DOM_OPS) ++ pci_msi_domain_update_dom_ops(info); ++ if (info->flags & MSI_FLAG_USE_DEF_CHIP_OPS) ++ pci_msi_domain_update_chip_ops(info); ++ ++ domain = msi_create_irq_domain(node, info, parent); ++ if (!domain) ++ return NULL; ++ ++ domain->bus_token = DOMAIN_BUS_PCI_MSI; ++ return domain; ++} ++ ++/** ++ * pci_msi_domain_alloc_irqs - Allocate interrupts for @dev in @domain ++ * @domain: The interrupt domain to allocate from ++ * @dev: The device for which to allocate ++ * @nvec: The number of interrupts to allocate ++ * @type: Unused to allow simpler migration from the arch_XXX interfaces ++ * ++ * Returns: ++ * A virtual interrupt number or an error code in case of failure ++ */ ++int pci_msi_domain_alloc_irqs(struct irq_domain *domain, struct pci_dev *dev, ++ int nvec, int type) ++{ ++ return msi_domain_alloc_irqs(domain, &dev->dev, nvec); ++} ++ ++/** ++ * pci_msi_domain_free_irqs - Free interrupts for @dev in @domain ++ * @domain: The interrupt domain ++ * @dev: The device for which to free interrupts ++ */ ++void pci_msi_domain_free_irqs(struct irq_domain *domain, struct pci_dev *dev) ++{ ++ msi_domain_free_irqs(domain, &dev->dev); ++} ++ ++/** ++ * pci_msi_create_default_irq_domain - Create a default MSI interrupt domain ++ * @node: Optional device-tree node of the interrupt controller ++ * @info: MSI domain info ++ * @parent: Parent irq domain ++ * ++ * Returns: A domain pointer or NULL in case of failure. If successful ++ * the default PCI/MSI irqdomain pointer is updated. ++ */ ++struct irq_domain *pci_msi_create_default_irq_domain(struct device_node *node, ++ struct msi_domain_info *info, struct irq_domain *parent) ++{ ++ struct irq_domain *domain; ++ ++ mutex_lock(&pci_msi_domain_lock); ++ if (pci_msi_default_domain) { ++ pr_err("PCI: default irq domain for PCI MSI has already been created.\n"); ++ domain = NULL; ++ } else { ++ domain = pci_msi_create_irq_domain(node, info, parent); ++ pci_msi_default_domain = domain; ++ } ++ mutex_unlock(&pci_msi_domain_lock); ++ ++ return domain; ++} ++#endif /* CONFIG_PCI_MSI_IRQ_DOMAIN */ +diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c +index ce0aa47..a6783a5 100644 +--- a/drivers/pci/pci.c ++++ b/drivers/pci/pci.c +@@ -2467,6 +2467,7 @@ u8 pci_common_swizzle(struct pci_dev *dev, u8 *pinp) + *pinp = pin; + return PCI_SLOT(dev->devfn); + } ++EXPORT_SYMBOL_GPL(pci_common_swizzle); + + /** + * pci_release_region - Release a PCI bar +diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h +index b5defca..df2169e 100644 +--- a/drivers/pci/pci.h ++++ b/drivers/pci/pci.h +@@ -140,6 +140,27 @@ static inline void pci_no_msi(void) { } + static inline void pci_msi_init_pci_dev(struct pci_dev *dev) { } + #endif + ++static inline void pci_msi_set_enable(struct pci_dev *dev, int enable) ++{ ++ u16 control; ++ ++ pci_read_config_word(dev, dev->msi_cap + PCI_MSI_FLAGS, &control); ++ control &= ~PCI_MSI_FLAGS_ENABLE; ++ if (enable) ++ control |= PCI_MSI_FLAGS_ENABLE; ++ pci_write_config_word(dev, dev->msi_cap + PCI_MSI_FLAGS, control); ++} ++ ++static inline void pci_msix_clear_and_set_ctrl(struct pci_dev *dev, u16 clear, u16 set) ++{ ++ u16 ctrl; ++ ++ pci_read_config_word(dev, dev->msix_cap + PCI_MSIX_FLAGS, &ctrl); ++ ctrl &= ~clear; ++ ctrl |= set; ++ pci_write_config_word(dev, dev->msix_cap + PCI_MSIX_FLAGS, ctrl); ++} ++ + void pci_realloc_get_opt(char *); + + static inline int pci_no_d1d2(struct pci_dev *dev) +diff --git a/drivers/pci/pcie/portdrv_core.c b/drivers/pci/pcie/portdrv_core.c +index 2f0ce66..95ef171 100644 +--- a/drivers/pci/pcie/portdrv_core.c ++++ b/drivers/pci/pcie/portdrv_core.c +@@ -15,6 +15,7 @@ + #include + #include + #include ++#include + + #include "../pci.h" + #include "portdrv.h" +@@ -199,6 +200,28 @@ static int pcie_port_enable_msix(struct pci_dev *dev, int *vectors, int mask) + static int init_service_irqs(struct pci_dev *dev, int *irqs, int mask) + { + int i, irq = -1; ++ int ret; ++ struct device_node *np = NULL; ++ ++ for (i = 0; i < PCIE_PORT_DEVICE_MAXSERVICES; i++) ++ irqs[i] = 0; ++ ++ if (dev->bus->dev.of_node) ++ np = dev->bus->dev.of_node; ++ ++ /* If root port doesn't support MSI/MSI-X/INTx in RC mode, ++ * request irq for aer ++ */ ++ if (IS_ENABLED(CONFIG_OF_IRQ) && np && ++ (mask & PCIE_PORT_SERVICE_PME)) { ++ ret = of_irq_get_byname(np, "aer"); ++ if (ret > 0) { ++ irqs[PCIE_PORT_SERVICE_AER_SHIFT] = ret; ++ if (dev->irq) ++ irq = dev->irq; ++ goto no_msi; ++ } ++ } + + /* + * If MSI cannot be used for PCIe PME or hotplug, we have to use +@@ -224,11 +247,13 @@ static int init_service_irqs(struct pci_dev *dev, int *irqs, int mask) + irq = dev->irq; + + no_msi: +- for (i = 0; i < PCIE_PORT_DEVICE_MAXSERVICES; i++) +- irqs[i] = irq; ++ for (i = 0; i < PCIE_PORT_DEVICE_MAXSERVICES; i++) { ++ if (!irqs[i]) ++ irqs[i] = irq; ++ } + irqs[PCIE_PORT_SERVICE_VC_SHIFT] = -1; + +- if (irq < 0) ++ if (irq < 0 && irqs[PCIE_PORT_SERVICE_AER_SHIFT] < 0) + return -ENODEV; + return 0; + } +diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c +index 3010ffc..0b16384 100644 +--- a/drivers/pci/probe.c ++++ b/drivers/pci/probe.c +@@ -1097,6 +1097,22 @@ int pci_cfg_space_size(struct pci_dev *dev) + + #define LEGACY_IO_RESOURCE (IORESOURCE_IO | IORESOURCE_PCI_FIXED) + ++static void pci_msi_setup_pci_dev(struct pci_dev *dev) ++{ ++ /* ++ * Disable the MSI hardware to avoid screaming interrupts ++ * during boot. This is the power on reset default so ++ * usually this should be a noop. ++ */ ++ dev->msi_cap = pci_find_capability(dev, PCI_CAP_ID_MSI); ++ if (dev->msi_cap) ++ pci_msi_set_enable(dev, 0); ++ ++ dev->msix_cap = pci_find_capability(dev, PCI_CAP_ID_MSIX); ++ if (dev->msix_cap) ++ pci_msix_clear_and_set_ctrl(dev, PCI_MSIX_FLAGS_ENABLE, 0); ++} ++ + /** + * pci_setup_device - fill in class and map information of a device + * @dev: the device structure to fill +@@ -1152,6 +1168,8 @@ int pci_setup_device(struct pci_dev *dev) + /* "Unknown power state" */ + dev->current_state = PCI_UNKNOWN; + ++ pci_msi_setup_pci_dev(dev); ++ + /* Early fixups, before probing the BARs */ + pci_fixup_device(pci_fixup_early, dev); + /* device class may be changed after fixup */ +@@ -1908,7 +1926,7 @@ struct pci_bus *pci_create_root_bus(struct device *parent, int bus, + int error; + struct pci_host_bridge *bridge; + struct pci_bus *b, *b2; +- struct pci_host_bridge_window *window, *n; ++ struct resource_entry *window, *n; + struct resource *res; + resource_size_t offset; + char bus_addr[64]; +@@ -1972,8 +1990,8 @@ struct pci_bus *pci_create_root_bus(struct device *parent, int bus, + printk(KERN_INFO "PCI host bridge to bus %s\n", dev_name(&b->dev)); + + /* Add initial resources to the bus */ +- list_for_each_entry_safe(window, n, resources, list) { +- list_move_tail(&window->list, &bridge->windows); ++ resource_list_for_each_entry_safe(window, n, resources) { ++ list_move_tail(&window->node, &bridge->windows); + res = window->res; + offset = window->offset; + if (res->flags & IORESOURCE_BUS) +@@ -2006,6 +2024,7 @@ err_out: + kfree(b); + return NULL; + } ++EXPORT_SYMBOL_GPL(pci_create_root_bus); + + int pci_bus_insert_busn_res(struct pci_bus *b, int bus, int bus_max) + { +@@ -2073,12 +2092,12 @@ void pci_bus_release_busn_res(struct pci_bus *b) + struct pci_bus *pci_scan_root_bus(struct device *parent, int bus, + struct pci_ops *ops, void *sysdata, struct list_head *resources) + { +- struct pci_host_bridge_window *window; ++ struct resource_entry *window; + bool found = false; + struct pci_bus *b; + int max; + +- list_for_each_entry(window, resources, list) ++ resource_list_for_each_entry(window, resources) + if (window->res->flags & IORESOURCE_BUS) { + found = true; + break; +diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c +index b6d646a..f3681e2 100644 +--- a/drivers/pci/quirks.c ++++ b/drivers/pci/quirks.c +@@ -3516,8 +3516,9 @@ int pci_dev_specific_reset(struct pci_dev *dev, int probe) + static void quirk_dma_func0_alias(struct pci_dev *dev) + { + if (PCI_FUNC(dev->devfn) != 0) { +- dev->dma_alias_devfn = PCI_DEVFN(PCI_SLOT(dev->devfn), 0); +- dev->dev_flags |= PCI_DEV_FLAGS_DMA_ALIAS_DEVFN; ++ dev->dma_alias_devid = PCI_DEVID(dev->bus->number, ++ PCI_DEVFN(PCI_SLOT(dev->devfn), 0)); ++ dev->dev_flags |= PCI_DEV_FLAGS_DMA_ALIAS_DEVID; + } + } + +@@ -3532,8 +3533,9 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_RICOH, 0xe476, quirk_dma_func0_alias); + static void quirk_dma_func1_alias(struct pci_dev *dev) + { + if (PCI_FUNC(dev->devfn) != 1) { +- dev->dma_alias_devfn = PCI_DEVFN(PCI_SLOT(dev->devfn), 1); +- dev->dev_flags |= PCI_DEV_FLAGS_DMA_ALIAS_DEVFN; ++ dev->dma_alias_devid = PCI_DEVID(dev->bus->number, ++ PCI_DEVFN(PCI_SLOT(dev->devfn), 1)); ++ dev->dev_flags |= PCI_DEV_FLAGS_DMA_ALIAS_DEVID; + } + } + +diff --git a/drivers/pci/remove.c b/drivers/pci/remove.c +index 8bd76c9..8a280e9 100644 +--- a/drivers/pci/remove.c ++++ b/drivers/pci/remove.c +@@ -139,6 +139,7 @@ void pci_stop_root_bus(struct pci_bus *bus) + /* stop the host bridge */ + device_release_driver(&host_bridge->dev); + } ++EXPORT_SYMBOL_GPL(pci_stop_root_bus); + + void pci_remove_root_bus(struct pci_bus *bus) + { +@@ -158,3 +159,4 @@ void pci_remove_root_bus(struct pci_bus *bus) + /* remove the host bridge */ + device_unregister(&host_bridge->dev); + } ++EXPORT_SYMBOL_GPL(pci_remove_root_bus); +diff --git a/drivers/pci/search.c b/drivers/pci/search.c +index a81f413..a00924f 100644 +--- a/drivers/pci/search.c ++++ b/drivers/pci/search.c +@@ -40,9 +40,8 @@ int pci_for_each_dma_alias(struct pci_dev *pdev, + * If the device is broken and uses an alias requester ID for + * DMA, iterate over that too. + */ +- if (unlikely(pdev->dev_flags & PCI_DEV_FLAGS_DMA_ALIAS_DEVFN)) { +- ret = fn(pdev, PCI_DEVID(pdev->bus->number, +- pdev->dma_alias_devfn), data); ++ if (unlikely(pdev->dev_flags & PCI_DEV_FLAGS_DMA_ALIAS_DEVID)) { ++ ret = fn(pdev, pdev->dma_alias_devid, data); + if (ret) + return ret; + } +diff --git a/drivers/pci/setup-bus.c b/drivers/pci/setup-bus.c +index e3e17f3..8169597 100644 +--- a/drivers/pci/setup-bus.c ++++ b/drivers/pci/setup-bus.c +@@ -1750,3 +1750,4 @@ void pci_assign_unassigned_bus_resources(struct pci_bus *bus) + __pci_bus_assign_resources(bus, &add_list, NULL); + BUG_ON(!list_empty(&add_list)); + } ++EXPORT_SYMBOL_GPL(pci_assign_unassigned_bus_resources); +diff --git a/drivers/pci/setup-irq.c b/drivers/pci/setup-irq.c +index 4e2d595..95c225b 100644 +--- a/drivers/pci/setup-irq.c ++++ b/drivers/pci/setup-irq.c +@@ -65,3 +65,4 @@ void pci_fixup_irqs(u8 (*swizzle)(struct pci_dev *, u8 *), + for_each_pci_dev(dev) + pdev_fixup_irq(dev, swizzle, map_irq); + } ++EXPORT_SYMBOL_GPL(pci_fixup_irqs); +diff --git a/drivers/pci/xen-pcifront.c b/drivers/pci/xen-pcifront.c +index 116ca37..37d4218 100644 +--- a/drivers/pci/xen-pcifront.c ++++ b/drivers/pci/xen-pcifront.c +@@ -267,7 +267,7 @@ static int pci_frontend_enable_msix(struct pci_dev *dev, + } + + i = 0; +- list_for_each_entry(entry, &dev->msi_list, list) { ++ for_each_pci_msi_entry(entry, dev) { + op.msix_entries[i].entry = entry->msi_attrib.entry_nr; + /* Vector is useless at this point. */ + op.msix_entries[i].vector = -1; +diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig +index f65ff49..b56b084 100644 +--- a/drivers/power/reset/Kconfig ++++ b/drivers/power/reset/Kconfig +@@ -150,5 +150,11 @@ config POWER_RESET_SYSCON + help + Reboot support for generic SYSCON mapped register reset. + ++config POWER_RESET_LAYERSCAPE ++ bool "Freescale LayerScape reset driver" ++ depends on ARCH_LAYERSCAPE ++ help ++ Reboot support for the Freescale LayerScape SoCs. ++ + endif + +diff --git a/drivers/power/reset/Makefile b/drivers/power/reset/Makefile +index 76ce1c5..d924bdb 100644 +--- a/drivers/power/reset/Makefile ++++ b/drivers/power/reset/Makefile +@@ -17,3 +17,4 @@ obj-$(CONFIG_POWER_RESET_VEXPRESS) += vexpress-poweroff.o + obj-$(CONFIG_POWER_RESET_XGENE) += xgene-reboot.o + obj-$(CONFIG_POWER_RESET_KEYSTONE) += keystone-reset.o + obj-$(CONFIG_POWER_RESET_SYSCON) += syscon-reboot.o ++obj-$(CONFIG_POWER_RESET_LAYERSCAPE) += ls-reboot.o +diff --git a/drivers/power/reset/ls-reboot.c b/drivers/power/reset/ls-reboot.c +new file mode 100644 +index 0000000..fa1152c +--- /dev/null ++++ b/drivers/power/reset/ls-reboot.c +@@ -0,0 +1,93 @@ ++/* ++ * Freescale LayerScape reboot driver ++ * ++ * Copyright (c) 2015, Freescale Semiconductor. ++ * Author: Pankaj Chauhan ++ * ++ * 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. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++struct ls_reboot_priv { ++ struct device *dev; ++ u32 *rstcr; ++}; ++ ++static struct ls_reboot_priv *ls_reboot_priv; ++ ++static void ls_reboot(enum reboot_mode reboot_mode, const char *cmd) ++{ ++ struct ls_reboot_priv *priv = ls_reboot_priv; ++ u32 val; ++ unsigned long timeout; ++ ++ if (ls_reboot_priv) { ++ val = readl(priv->rstcr); ++ val |= 0x02; ++ writel(val, priv->rstcr); ++ } ++ ++ timeout = jiffies + HZ; ++ while (time_before(jiffies, timeout)) ++ cpu_relax(); ++ ++} ++ ++static int ls_reboot_probe(struct platform_device *pdev) ++{ ++ ls_reboot_priv = devm_kzalloc(&pdev->dev, ++ sizeof(*ls_reboot_priv), GFP_KERNEL); ++ if (!ls_reboot_priv) { ++ dev_err(&pdev->dev, "out of memory for context\n"); ++ return -ENODEV; ++ } ++ ++ ls_reboot_priv->rstcr = of_iomap(pdev->dev.of_node, 0); ++ if (!ls_reboot_priv->rstcr) { ++ devm_kfree(&pdev->dev, ls_reboot_priv); ++ dev_err(&pdev->dev, "can not map resource\n"); ++ return -ENODEV; ++ } ++ ++ ls_reboot_priv->dev = &pdev->dev; ++ ++ arm_pm_restart = ls_reboot; ++ ++ return 0; ++} ++ ++static struct of_device_id ls_reboot_of_match[] = { ++ { .compatible = "fsl,ls-reset" }, ++ {} ++}; ++ ++static struct platform_driver ls_reboot_driver = { ++ .probe = ls_reboot_probe, ++ .driver = { ++ .name = "ls-reset", ++ .of_match_table = ls_reboot_of_match, ++ }, ++}; ++ ++static int __init ls_reboot_init(void) ++{ ++ return platform_driver_register(&ls_reboot_driver); ++} ++device_initcall(ls_reboot_init); +diff --git a/drivers/soc/Kconfig b/drivers/soc/Kconfig +index 76d6bd4..d4bcacf 100644 +--- a/drivers/soc/Kconfig ++++ b/drivers/soc/Kconfig +@@ -4,4 +4,17 @@ source "drivers/soc/qcom/Kconfig" + source "drivers/soc/ti/Kconfig" + source "drivers/soc/versatile/Kconfig" + ++config FSL_SOC_DRIVERS ++ bool "Freescale Soc Drivers" ++ depends on FSL_SOC || ARCH_MXC || ARCH_LAYERSCAPE ++ default n ++ help ++ Say y here to enable Freescale Soc Device Drivers support. ++ The Soc Drivers provides the device driver that is a specific block ++ or feature on Freescale platform. ++ ++if FSL_SOC_DRIVERS ++ source "drivers/soc/fsl/Kconfig" ++endif ++ + endmenu +diff --git a/drivers/soc/Makefile b/drivers/soc/Makefile +index 063113d..ef82e45 100644 +--- a/drivers/soc/Makefile ++++ b/drivers/soc/Makefile +@@ -6,3 +6,4 @@ obj-$(CONFIG_ARCH_QCOM) += qcom/ + obj-$(CONFIG_ARCH_TEGRA) += tegra/ + obj-$(CONFIG_SOC_TI) += ti/ + obj-$(CONFIG_PLAT_VERSATILE) += versatile/ ++obj-$(CONFIG_FSL_SOC_DRIVERS) += fsl/ +diff --git a/drivers/soc/fsl/Kconfig b/drivers/soc/fsl/Kconfig +new file mode 100644 +index 0000000..92a085e +--- /dev/null ++++ b/drivers/soc/fsl/Kconfig +@@ -0,0 +1,6 @@ ++config FSL_GUTS ++ bool ++ ++if ARM || ARM64 ++source "drivers/soc/fsl/Kconfig.arm" ++endif +diff --git a/drivers/soc/fsl/Kconfig.arm b/drivers/soc/fsl/Kconfig.arm +new file mode 100644 +index 0000000..5f2d214 +--- /dev/null ++++ b/drivers/soc/fsl/Kconfig.arm +@@ -0,0 +1,25 @@ ++# ++# Freescale ARM SOC Drivers ++# ++ ++config LS1_SOC_DRIVERS ++ bool "LS1021A Soc Drivers" ++ depends on SOC_LS1021A ++ default n ++ help ++ Say y here to enable Freescale LS1021A Soc Device Drivers support. ++ The Soc Drivers provides the device driver that is a specific block ++ or feature on LS1021A platform. ++ ++config LS_SOC_DRIVERS ++ bool "Layerscape Soc Drivers" ++ depends on ARCH_LAYERSCAPE ++ default n ++ help ++ Say y here to enable Freescale Layerscape Soc Device Drivers support. ++ The Soc Drivers provides the device driver that is a specific block ++ or feature on Layerscape platform. ++ ++if LS1_SOC_DRIVERS ++ source "drivers/soc/fsl/ls1/Kconfig" ++endif +diff --git a/drivers/soc/fsl/Makefile b/drivers/soc/fsl/Makefile +new file mode 100644 +index 0000000..9fc17b3 +--- /dev/null ++++ b/drivers/soc/fsl/Makefile +@@ -0,0 +1,6 @@ ++# ++# Makefile for Freescale Soc specific device drivers. ++# ++ ++obj-$(CONFIG_LS1_SOC_DRIVERS) += ls1/ ++obj-$(CONFIG_FSL_GUTS) += guts.o +diff --git a/drivers/soc/fsl/guts.c b/drivers/soc/fsl/guts.c +new file mode 100644 +index 0000000..11065c2 +--- /dev/null ++++ b/drivers/soc/fsl/guts.c +@@ -0,0 +1,123 @@ ++/* ++ * Freescale QorIQ Platforms GUTS Driver ++ * ++ * Copyright (C) 2016 Freescale Semiconductor, Inc. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++struct guts { ++ struct ccsr_guts __iomem *regs; ++ bool little_endian; ++}; ++ ++static struct guts *guts; ++ ++u32 guts_get_svr(void) ++{ ++ u32 svr = 0; ++ ++ if ((!guts) || (!(guts->regs))) { ++#ifdef CONFIG_PPC ++ svr = mfspr(SPRN_SVR); ++#endif ++ return svr; ++ } ++ ++ if (guts->little_endian) ++ svr = ioread32(&guts->regs->svr); ++ else ++ svr = ioread32be(&guts->regs->svr); ++ ++ return svr; ++} ++EXPORT_SYMBOL_GPL(guts_get_svr); ++ ++static int guts_probe(struct platform_device *pdev) ++{ ++ struct device_node *np = pdev->dev.of_node; ++ ++ guts = kzalloc(sizeof(*guts), GFP_KERNEL); ++ if (!guts) ++ return -ENOMEM; ++ ++ if (of_property_read_bool(np, "little-endian")) ++ guts->little_endian = true; ++ else ++ guts->little_endian = false; ++ ++ guts->regs = of_iomap(np, 0); ++ if (!(guts->regs)) ++ return -ENOMEM; ++ ++ of_node_put(np); ++ return 0; ++} ++ ++static int guts_remove(struct platform_device *pdev) ++{ ++ iounmap(guts->regs); ++ kfree(guts); ++ return 0; ++} ++ ++/* ++ * Table for matching compatible strings, for device tree ++ * guts node, for Freescale QorIQ SOCs. ++ */ ++static const struct of_device_id guts_of_match[] = { ++ /* For T4 & B4 SOCs */ ++ { .compatible = "fsl,qoriq-device-config-1.0", }, ++ /* For P Series SOCs */ ++ { .compatible = "fsl,qoriq-device-config-2.0", }, ++ { .compatible = "fsl,p1010-guts", }, ++ { .compatible = "fsl,p1020-guts", }, ++ { .compatible = "fsl,p1021-guts", }, ++ { .compatible = "fsl,p1022-guts", }, ++ { .compatible = "fsl,p1023-guts", }, ++ { .compatible = "fsl,p2020-guts", }, ++ /* For BSC Series SOCs */ ++ { .compatible = "fsl,bsc9131-guts", }, ++ { .compatible = "fsl,bsc9132-guts", }, ++ /* For Layerscape Series SOCs */ ++ { .compatible = "fsl,ls1021a-dcfg", }, ++ { .compatible = "fsl,ls1043a-dcfg", }, ++ { .compatible = "fsl,ls2080a-dcfg", }, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, guts_of_match); ++ ++static struct platform_driver guts_driver = { ++ .driver = { ++ .name = "fsl-guts", ++ .of_match_table = guts_of_match, ++ }, ++ .probe = guts_probe, ++ .remove = guts_remove, ++}; ++ ++static int __init guts_drv_init(void) ++{ ++ return platform_driver_register(&guts_driver); ++} ++subsys_initcall(guts_drv_init); ++ ++static void __exit guts_drv_exit(void) ++{ ++ platform_driver_unregister(&guts_driver); ++} ++module_exit(guts_drv_exit); ++ ++MODULE_AUTHOR("Freescale Semiconductor, Inc."); ++MODULE_DESCRIPTION("Freescale QorIQ Platforms GUTS Driver"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/soc/fsl/ls1/Kconfig b/drivers/soc/fsl/ls1/Kconfig +new file mode 100644 +index 0000000..c9b04c4 +--- /dev/null ++++ b/drivers/soc/fsl/ls1/Kconfig +@@ -0,0 +1,11 @@ ++# ++# LS-1 Soc drivers ++# ++config FTM_ALARM ++ bool "FTM alarm driver" ++ depends on SOC_LS1021A ++ default n ++ help ++ Say y here to enable FTM alarm support. The FTM alarm provides ++ alarm functions for wakeup system from deep sleep. There is only ++ one FTM can be used in ALARM(FTM 0). +diff --git a/drivers/soc/fsl/ls1/Makefile b/drivers/soc/fsl/ls1/Makefile +new file mode 100644 +index 0000000..6299aa1 +--- /dev/null ++++ b/drivers/soc/fsl/ls1/Makefile +@@ -0,0 +1 @@ ++obj-$(CONFIG_FTM_ALARM) += ftm_alarm.o +diff --git a/drivers/soc/fsl/ls1/ftm_alarm.c b/drivers/soc/fsl/ls1/ftm_alarm.c +new file mode 100644 +index 0000000..c42b26b +--- /dev/null ++++ b/drivers/soc/fsl/ls1/ftm_alarm.c +@@ -0,0 +1,274 @@ ++/* ++ * Freescale FlexTimer Module (FTM) Alarm driver. ++ * ++ * Copyright 2014 Freescale Semiconductor, Inc. ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License ++ * as published by the Free Software Foundation; either version 2 ++ * of the License, or (at your option) any later version. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define FTM_SC 0x00 ++#define FTM_SC_CLK_SHIFT 3 ++#define FTM_SC_CLK_MASK (0x3 << FTM_SC_CLK_SHIFT) ++#define FTM_SC_CLK(c) ((c) << FTM_SC_CLK_SHIFT) ++#define FTM_SC_PS_MASK 0x7 ++#define FTM_SC_TOIE BIT(6) ++#define FTM_SC_TOF BIT(7) ++ ++#define FTM_SC_CLKS_FIXED_FREQ 0x02 ++ ++#define FTM_CNT 0x04 ++#define FTM_MOD 0x08 ++#define FTM_CNTIN 0x4C ++ ++#define FIXED_FREQ_CLK 32000 ++#define MAX_FREQ_DIV (1 << FTM_SC_PS_MASK) ++#define MAX_COUNT_VAL 0xffff ++ ++static void __iomem *ftm1_base; ++static u32 alarm_freq; ++static bool big_endian; ++ ++static inline u32 ftm_readl(void __iomem *addr) ++{ ++ if (big_endian) ++ return ioread32be(addr); ++ ++ return ioread32(addr); ++} ++ ++static inline void ftm_writel(u32 val, void __iomem *addr) ++{ ++ if (big_endian) ++ iowrite32be(val, addr); ++ else ++ iowrite32(val, addr); ++} ++ ++static inline void ftm_counter_enable(void __iomem *base) ++{ ++ u32 val; ++ ++ /* select and enable counter clock source */ ++ val = ftm_readl(base + FTM_SC); ++ val &= ~(FTM_SC_PS_MASK | FTM_SC_CLK_MASK); ++ val |= (FTM_SC_PS_MASK | FTM_SC_CLK(FTM_SC_CLKS_FIXED_FREQ)); ++ ftm_writel(val, base + FTM_SC); ++} ++ ++static inline void ftm_counter_disable(void __iomem *base) ++{ ++ u32 val; ++ ++ /* disable counter clock source */ ++ val = ftm_readl(base + FTM_SC); ++ val &= ~(FTM_SC_PS_MASK | FTM_SC_CLK_MASK); ++ ftm_writel(val, base + FTM_SC); ++} ++ ++static inline void ftm_irq_acknowledge(void __iomem *base) ++{ ++ u32 val; ++ ++ val = ftm_readl(base + FTM_SC); ++ val &= ~FTM_SC_TOF; ++ ftm_writel(val, base + FTM_SC); ++} ++ ++static inline void ftm_irq_enable(void __iomem *base) ++{ ++ u32 val; ++ ++ val = ftm_readl(base + FTM_SC); ++ val |= FTM_SC_TOIE; ++ ftm_writel(val, base + FTM_SC); ++} ++ ++static inline void ftm_irq_disable(void __iomem *base) ++{ ++ u32 val; ++ ++ val = ftm_readl(base + FTM_SC); ++ val &= ~FTM_SC_TOIE; ++ ftm_writel(val, base + FTM_SC); ++} ++ ++static inline void ftm_reset_counter(void __iomem *base) ++{ ++ /* ++ * The CNT register contains the FTM counter value. ++ * Reset clears the CNT register. Writing any value to COUNT ++ * updates the counter with its initial value, CNTIN. ++ */ ++ ftm_writel(0x00, base + FTM_CNT); ++} ++ ++static u32 time_to_cycle(unsigned long time) ++{ ++ u32 cycle; ++ ++ cycle = time * alarm_freq; ++ if (cycle > MAX_COUNT_VAL) { ++ pr_err("Out of alarm range.\n"); ++ cycle = 0; ++ } ++ ++ return cycle; ++} ++ ++static u32 cycle_to_time(u32 cycle) ++{ ++ return cycle / alarm_freq + 1; ++} ++ ++static void ftm_clean_alarm(void) ++{ ++ ftm_counter_disable(ftm1_base); ++ ++ ftm_writel(0x00, ftm1_base + FTM_CNTIN); ++ ftm_writel(~0UL, ftm1_base + FTM_MOD); ++ ++ ftm_reset_counter(ftm1_base); ++} ++ ++static int ftm_set_alarm(u64 cycle) ++{ ++ ftm_irq_disable(ftm1_base); ++ ++ /* ++ * The counter increments until the value of MOD is reached, ++ * at which point the counter is reloaded with the value of CNTIN. ++ * The TOF (the overflow flag) bit is set when the FTM counter ++ * changes from MOD to CNTIN. So we should using the cycle - 1. ++ */ ++ ftm_writel(cycle - 1, ftm1_base + FTM_MOD); ++ ++ ftm_counter_enable(ftm1_base); ++ ++ ftm_irq_enable(ftm1_base); ++ ++ return 0; ++} ++ ++static irqreturn_t ftm_alarm_interrupt(int irq, void *dev_id) ++{ ++ ftm_irq_acknowledge(ftm1_base); ++ ftm_irq_disable(ftm1_base); ++ ftm_clean_alarm(); ++ ++ return IRQ_HANDLED; ++} ++ ++static ssize_t ftm_alarm_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ u32 count, val; ++ ++ count = ftm_readl(ftm1_base + FTM_MOD); ++ val = ftm_readl(ftm1_base + FTM_CNT); ++ val = (count & MAX_COUNT_VAL) - val; ++ val = cycle_to_time(val); ++ ++ return sprintf(buf, "%u\n", val); ++} ++ ++static ssize_t ftm_alarm_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ u32 cycle; ++ unsigned long time; ++ ++ if (kstrtoul(buf, 0, &time)) ++ return -EINVAL; ++ ++ ftm_clean_alarm(); ++ ++ cycle = time_to_cycle(time); ++ if (!cycle) ++ return -EINVAL; ++ ++ ftm_set_alarm(cycle); ++ ++ return count; ++} ++ ++static struct device_attribute ftm_alarm_attributes = __ATTR(ftm_alarm, 0644, ++ ftm_alarm_show, ftm_alarm_store); ++ ++static int ftm_alarm_probe(struct platform_device *pdev) ++{ ++ struct device_node *np = pdev->dev.of_node; ++ struct resource *r; ++ int irq; ++ int ret; ++ ++ r = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ if (!r) ++ return -ENODEV; ++ ++ ftm1_base = devm_ioremap_resource(&pdev->dev, r); ++ if (IS_ERR(ftm1_base)) ++ return PTR_ERR(ftm1_base); ++ ++ irq = irq_of_parse_and_map(np, 0); ++ if (irq <= 0) { ++ pr_err("ftm: unable to get IRQ from DT, %d\n", irq); ++ return -EINVAL; ++ } ++ ++ big_endian = of_property_read_bool(np, "big-endian"); ++ ++ ret = devm_request_irq(&pdev->dev, irq, ftm_alarm_interrupt, ++ IRQF_NO_SUSPEND, dev_name(&pdev->dev), NULL); ++ if (ret < 0) { ++ dev_err(&pdev->dev, "failed to request irq\n"); ++ return ret; ++ } ++ ++ ret = device_create_file(&pdev->dev, &ftm_alarm_attributes); ++ if (ret) { ++ dev_err(&pdev->dev, "create sysfs fail.\n"); ++ return ret; ++ } ++ ++ alarm_freq = (u32)FIXED_FREQ_CLK / (u32)MAX_FREQ_DIV; ++ ++ ftm_clean_alarm(); ++ ++ device_init_wakeup(&pdev->dev, true); ++ ++ return ret; ++} ++ ++static const struct of_device_id ftm_alarm_match[] = { ++ { .compatible = "fsl,ftm-alarm", }, ++ { .compatible = "fsl,ftm-timer", }, ++ { }, ++}; ++ ++static struct platform_driver ftm_alarm_driver = { ++ .probe = ftm_alarm_probe, ++ .driver = { ++ .name = "ftm-alarm", ++ .owner = THIS_MODULE, ++ .of_match_table = ftm_alarm_match, ++ }, ++}; ++ ++static int __init ftm_alarm_init(void) ++{ ++ return platform_driver_register(&ftm_alarm_driver); ++} ++device_initcall(ftm_alarm_init); +diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig +index 4690ae9..43ff2b5 100644 +--- a/drivers/staging/Kconfig ++++ b/drivers/staging/Kconfig +@@ -108,4 +108,8 @@ source "drivers/staging/skein/Kconfig" + + source "drivers/staging/unisys/Kconfig" + ++source "drivers/staging/fsl-mc/Kconfig" ++ ++source "drivers/staging/fsl-dpaa2/Kconfig" ++ + endif # STAGING +diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile +index c780a0e..a9bd303 100644 +--- a/drivers/staging/Makefile ++++ b/drivers/staging/Makefile +@@ -46,3 +46,5 @@ obj-$(CONFIG_MTD_SPINAND_MT29F) += mt29f_spinand/ + obj-$(CONFIG_GS_FPGABOOT) += gs_fpgaboot/ + obj-$(CONFIG_CRYPTO_SKEIN) += skein/ + obj-$(CONFIG_UNISYSSPAR) += unisys/ ++obj-$(CONFIG_FSL_MC_BUS) += fsl-mc/ ++obj-$(CONFIG_FSL_DPAA2) += fsl-dpaa2/ +diff --git a/drivers/staging/fsl-dpaa2/Kconfig b/drivers/staging/fsl-dpaa2/Kconfig +new file mode 100644 +index 0000000..3fe47bc +--- /dev/null ++++ b/drivers/staging/fsl-dpaa2/Kconfig +@@ -0,0 +1,12 @@ ++# ++# Freescale device configuration ++# ++ ++config FSL_DPAA2 ++ bool "Freescale DPAA2 devices" ++ depends on FSL_MC_BUS ++ ---help--- ++ Build drivers for Freescale DataPath Acceleration Architecture (DPAA2) family of SoCs. ++# TODO move DPIO driver in-here? ++source "drivers/staging/fsl-dpaa2/ethernet/Kconfig" ++source "drivers/staging/fsl-dpaa2/mac/Kconfig" +diff --git a/drivers/staging/fsl-dpaa2/Makefile b/drivers/staging/fsl-dpaa2/Makefile +new file mode 100644 +index 0000000..bc687a1 +--- /dev/null ++++ b/drivers/staging/fsl-dpaa2/Makefile +@@ -0,0 +1,6 @@ ++# ++# Makefile for the Freescale network device drivers. ++# ++ ++obj-$(CONFIG_FSL_DPAA2_ETH) += ethernet/ ++obj-$(CONFIG_FSL_DPAA2_MAC) += mac/ +diff --git a/drivers/staging/fsl-dpaa2/ethernet/Kconfig b/drivers/staging/fsl-dpaa2/ethernet/Kconfig +new file mode 100644 +index 0000000..df91da2 +--- /dev/null ++++ b/drivers/staging/fsl-dpaa2/ethernet/Kconfig +@@ -0,0 +1,36 @@ ++# ++# Freescale DPAA Ethernet driver configuration ++# ++# Copyright (C) 2014-2015 Freescale Semiconductor, Inc. ++# ++# This file is released under the GPLv2 ++# ++ ++menuconfig FSL_DPAA2_ETH ++ tristate "Freescale DPAA2 Ethernet" ++ depends on FSL_DPAA2 && FSL_MC_BUS && FSL_MC_DPIO ++ select FSL_DPAA2_MAC ++ default y ++ ---help--- ++ Freescale Data Path Acceleration Architecture Ethernet ++ driver, using the Freescale MC bus driver. ++ ++if FSL_DPAA2_ETH ++ ++config FSL_DPAA2_ETH_USE_ERR_QUEUE ++ bool "Enable Rx error queue" ++ default n ++ ---help--- ++ Allow Rx error frames to be enqueued on an error queue ++ and processed by the driver (by default they are dropped ++ in hardware). ++ This may impact performance, recommended for debugging ++ purposes only. ++ ++config FSL_DPAA2_ETH_DEBUGFS ++ depends on DEBUG_FS && FSL_QBMAN_DEBUG ++ bool "Enable debugfs support" ++ default n ++ ---help--- ++ Enable advanced statistics through debugfs interface. ++endif +diff --git a/drivers/staging/fsl-dpaa2/ethernet/Makefile b/drivers/staging/fsl-dpaa2/ethernet/Makefile +new file mode 100644 +index 0000000..74bff15 +--- /dev/null ++++ b/drivers/staging/fsl-dpaa2/ethernet/Makefile +@@ -0,0 +1,21 @@ ++# ++# Makefile for the Freescale DPAA Ethernet controllers ++# ++# Copyright (C) 2014-2015 Freescale Semiconductor, Inc. ++# ++# This file is released under the GPLv2 ++# ++ ++ccflags-y += -DVERSION=\"\" ++ ++obj-$(CONFIG_FSL_DPAA2_ETH) += fsl-dpaa2-eth.o ++ ++fsl-dpaa2-eth-objs := dpaa2-eth.o dpaa2-ethtool.o dpni.o ++fsl-dpaa2-eth-${CONFIG_FSL_DPAA2_ETH_DEBUGFS} += dpaa2-eth-debugfs.o ++ ++#Needed by the tracing framework ++CFLAGS_dpaa2-eth.o := -I$(src) ++ ++ifeq ($(CONFIG_FSL_DPAA2_ETH_GCOV),y) ++ GCOV_PROFILE := y ++endif +diff --git a/drivers/staging/fsl-dpaa2/ethernet/dpaa2-eth-debugfs.c b/drivers/staging/fsl-dpaa2/ethernet/dpaa2-eth-debugfs.c +new file mode 100644 +index 0000000..c397983 +--- /dev/null ++++ b/drivers/staging/fsl-dpaa2/ethernet/dpaa2-eth-debugfs.c +@@ -0,0 +1,317 @@ ++ ++/* Copyright 2015 Freescale Semiconductor Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * * Neither the name of Freescale Semiconductor nor the ++ * names of its contributors may be used to endorse or promote products ++ * derived from this software without specific prior written permission. ++ * ++ * ++ * ALTERNATIVELY, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") as published by the Free Software ++ * Foundation, either version 2 of that License or (at your option) any ++ * later version. ++ * ++ * THIS SOFTWARE IS PROVIDED BY Freescale Semiconductor ``AS IS'' AND ANY ++ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ++ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE ++ * DISCLAIMED. IN NO EVENT SHALL Freescale Semiconductor BE LIABLE FOR ANY ++ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ++ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; ++ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ++ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ++ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS ++ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++#include ++#include ++#include "dpaa2-eth.h" ++#include "dpaa2-eth-debugfs.h" ++ ++#define DPAA2_ETH_DBG_ROOT "dpaa2-eth" ++ ++static struct dentry *dpaa2_dbg_root; ++ ++static int dpaa2_dbg_cpu_show(struct seq_file *file, void *offset) ++{ ++ struct dpaa2_eth_priv *priv = (struct dpaa2_eth_priv *)file->private; ++ struct rtnl_link_stats64 *stats; ++ struct dpaa2_eth_drv_stats *extras; ++ int i; ++ ++ seq_printf(file, "Per-CPU stats for %s\n", priv->net_dev->name); ++ seq_printf(file, "%s%16s%16s%16s%16s%16s%16s%16s%16s\n", ++ "CPU", "Rx", "Rx Err", "Rx SG", "Tx", "Tx Err", "Tx conf", ++ "Tx SG", "Enq busy"); ++ ++ for_each_online_cpu(i) { ++ stats = per_cpu_ptr(priv->percpu_stats, i); ++ extras = per_cpu_ptr(priv->percpu_extras, i); ++ seq_printf(file, "%3d%16llu%16llu%16llu%16llu%16llu%16llu%16llu%16llu\n", ++ i, ++ stats->rx_packets, ++ stats->rx_errors, ++ extras->rx_sg_frames, ++ stats->tx_packets, ++ stats->tx_errors, ++ extras->tx_conf_frames, ++ extras->tx_sg_frames, ++ extras->tx_portal_busy); ++ } ++ ++ return 0; ++} ++ ++static int dpaa2_dbg_cpu_open(struct inode *inode, struct file *file) ++{ ++ int err; ++ struct dpaa2_eth_priv *priv = (struct dpaa2_eth_priv *)inode->i_private; ++ ++ err = single_open(file, dpaa2_dbg_cpu_show, priv); ++ if (err < 0) ++ netdev_err(priv->net_dev, "single_open() failed\n"); ++ ++ return err; ++} ++ ++static const struct file_operations dpaa2_dbg_cpu_ops = { ++ .open = dpaa2_dbg_cpu_open, ++ .read = seq_read, ++ .llseek = seq_lseek, ++ .release = single_release, ++}; ++ ++static char *fq_type_to_str(struct dpaa2_eth_fq *fq) ++{ ++ switch (fq->type) { ++ case DPAA2_RX_FQ: ++ return "Rx"; ++ case DPAA2_TX_CONF_FQ: ++ return "Tx conf"; ++ case DPAA2_RX_ERR_FQ: ++ return "Rx err"; ++ default: ++ return "N/A"; ++ } ++} ++ ++static int dpaa2_dbg_fqs_show(struct seq_file *file, void *offset) ++{ ++ struct dpaa2_eth_priv *priv = (struct dpaa2_eth_priv *)file->private; ++ struct dpaa2_eth_fq *fq; ++ u32 fcnt, bcnt; ++ int i, err; ++ ++ seq_printf(file, "FQ stats for %s:\n", priv->net_dev->name); ++ seq_printf(file, "%s%16s%16s%16s%16s\n", ++ "VFQID", "CPU", "Type", "Frames", "Pending frames"); ++ ++ for (i = 0; i < priv->num_fqs; i++) { ++ fq = &priv->fq[i]; ++ err = dpaa2_io_query_fq_count(NULL, fq->fqid, &fcnt, &bcnt); ++ if (err) ++ fcnt = 0; ++ ++ seq_printf(file, "%5d%16d%16s%16llu%16u\n", ++ fq->fqid, ++ fq->target_cpu, ++ fq_type_to_str(fq), ++ fq->stats.frames, ++ fcnt); ++ } ++ ++ return 0; ++} ++ ++static int dpaa2_dbg_fqs_open(struct inode *inode, struct file *file) ++{ ++ int err; ++ struct dpaa2_eth_priv *priv = (struct dpaa2_eth_priv *)inode->i_private; ++ ++ err = single_open(file, dpaa2_dbg_fqs_show, priv); ++ if (err < 0) ++ netdev_err(priv->net_dev, "single_open() failed\n"); ++ ++ return err; ++} ++ ++static const struct file_operations dpaa2_dbg_fq_ops = { ++ .open = dpaa2_dbg_fqs_open, ++ .read = seq_read, ++ .llseek = seq_lseek, ++ .release = single_release, ++}; ++ ++static int dpaa2_dbg_ch_show(struct seq_file *file, void *offset) ++{ ++ struct dpaa2_eth_priv *priv = (struct dpaa2_eth_priv *)file->private; ++ struct dpaa2_eth_channel *ch; ++ int i; ++ ++ seq_printf(file, "Channel stats for %s:\n", priv->net_dev->name); ++ seq_printf(file, "%s%16s%16s%16s%16s%16s\n", ++ "CHID", "CPU", "Deq busy", "Frames", "CDANs", ++ "Avg frm/CDAN"); ++ ++ for (i = 0; i < priv->num_channels; i++) { ++ ch = priv->channel[i]; ++ seq_printf(file, "%4d%16d%16llu%16llu%16llu%16llu\n", ++ ch->ch_id, ++ ch->nctx.desired_cpu, ++ ch->stats.dequeue_portal_busy, ++ ch->stats.frames, ++ ch->stats.cdan, ++ ch->stats.frames / ch->stats.cdan); ++ } ++ ++ return 0; ++} ++ ++static int dpaa2_dbg_ch_open(struct inode *inode, struct file *file) ++{ ++ int err; ++ struct dpaa2_eth_priv *priv = (struct dpaa2_eth_priv *)inode->i_private; ++ ++ err = single_open(file, dpaa2_dbg_ch_show, priv); ++ if (err < 0) ++ netdev_err(priv->net_dev, "single_open() failed\n"); ++ ++ return err; ++} ++ ++static const struct file_operations dpaa2_dbg_ch_ops = { ++ .open = dpaa2_dbg_ch_open, ++ .read = seq_read, ++ .llseek = seq_lseek, ++ .release = single_release, ++}; ++ ++static ssize_t dpaa2_dbg_reset_write(struct file *file, const char __user *buf, ++ size_t count, loff_t *offset) ++{ ++ struct dpaa2_eth_priv *priv = file->private_data; ++ struct rtnl_link_stats64 *percpu_stats; ++ struct dpaa2_eth_drv_stats *percpu_extras; ++ struct dpaa2_eth_fq *fq; ++ struct dpaa2_eth_channel *ch; ++ int i; ++ ++ for_each_online_cpu(i) { ++ percpu_stats = per_cpu_ptr(priv->percpu_stats, i); ++ memset(percpu_stats, 0, sizeof(*percpu_stats)); ++ ++ percpu_extras = per_cpu_ptr(priv->percpu_extras, i); ++ memset(percpu_extras, 0, sizeof(*percpu_extras)); ++ } ++ ++ for (i = 0; i < priv->num_fqs; i++) { ++ fq = &priv->fq[i]; ++ memset(&fq->stats, 0, sizeof(fq->stats)); ++ } ++ ++ for_each_cpu(i, &priv->dpio_cpumask) { ++ ch = priv->channel[i]; ++ memset(&ch->stats, 0, sizeof(ch->stats)); ++ } ++ ++ return count; ++} ++ ++static const struct file_operations dpaa2_dbg_reset_ops = { ++ .open = simple_open, ++ .write = dpaa2_dbg_reset_write, ++}; ++ ++void dpaa2_dbg_add(struct dpaa2_eth_priv *priv) ++{ ++ if (!dpaa2_dbg_root) ++ return; ++ ++ /* Create a directory for the interface */ ++ priv->dbg.dir = debugfs_create_dir(priv->net_dev->name, ++ dpaa2_dbg_root); ++ if (!priv->dbg.dir) { ++ netdev_err(priv->net_dev, "debugfs_create_dir() failed\n"); ++ return; ++ } ++ ++ /* per-cpu stats file */ ++ priv->dbg.cpu_stats = debugfs_create_file("cpu_stats", S_IRUGO, ++ priv->dbg.dir, priv, ++ &dpaa2_dbg_cpu_ops); ++ if (!priv->dbg.cpu_stats) { ++ netdev_err(priv->net_dev, "debugfs_create_file() failed\n"); ++ goto err_cpu_stats; ++ } ++ ++ /* per-fq stats file */ ++ priv->dbg.fq_stats = debugfs_create_file("fq_stats", S_IRUGO, ++ priv->dbg.dir, priv, ++ &dpaa2_dbg_fq_ops); ++ if (!priv->dbg.fq_stats) { ++ netdev_err(priv->net_dev, "debugfs_create_file() failed\n"); ++ goto err_fq_stats; ++ } ++ ++ /* per-fq stats file */ ++ priv->dbg.ch_stats = debugfs_create_file("ch_stats", S_IRUGO, ++ priv->dbg.dir, priv, ++ &dpaa2_dbg_ch_ops); ++ if (!priv->dbg.fq_stats) { ++ netdev_err(priv->net_dev, "debugfs_create_file() failed\n"); ++ goto err_ch_stats; ++ } ++ ++ /* reset stats */ ++ priv->dbg.reset_stats = debugfs_create_file("reset_stats", S_IWUSR, ++ priv->dbg.dir, priv, ++ &dpaa2_dbg_reset_ops); ++ if (!priv->dbg.reset_stats) { ++ netdev_err(priv->net_dev, "debugfs_create_file() failed\n"); ++ goto err_reset_stats; ++ } ++ ++ return; ++ ++err_reset_stats: ++ debugfs_remove(priv->dbg.ch_stats); ++err_ch_stats: ++ debugfs_remove(priv->dbg.fq_stats); ++err_fq_stats: ++ debugfs_remove(priv->dbg.cpu_stats); ++err_cpu_stats: ++ debugfs_remove(priv->dbg.dir); ++} ++ ++void dpaa2_dbg_remove(struct dpaa2_eth_priv *priv) ++{ ++ debugfs_remove(priv->dbg.reset_stats); ++ debugfs_remove(priv->dbg.fq_stats); ++ debugfs_remove(priv->dbg.ch_stats); ++ debugfs_remove(priv->dbg.cpu_stats); ++ debugfs_remove(priv->dbg.dir); ++} ++ ++void dpaa2_eth_dbg_init(void) ++{ ++ dpaa2_dbg_root = debugfs_create_dir(DPAA2_ETH_DBG_ROOT, NULL); ++ if (!dpaa2_dbg_root) { ++ pr_err("DPAA2-ETH: debugfs create failed\n"); ++ return; ++ } ++ ++ pr_info("DPAA2-ETH: debugfs created\n"); ++} ++ ++void __exit dpaa2_eth_dbg_exit(void) ++{ ++ debugfs_remove(dpaa2_dbg_root); ++} ++ +diff --git a/drivers/staging/fsl-dpaa2/ethernet/dpaa2-eth-debugfs.h b/drivers/staging/fsl-dpaa2/ethernet/dpaa2-eth-debugfs.h +new file mode 100644 +index 0000000..7ba706c +--- /dev/null ++++ b/drivers/staging/fsl-dpaa2/ethernet/dpaa2-eth-debugfs.h +@@ -0,0 +1,61 @@ ++/* Copyright 2015 Freescale Semiconductor Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * * Neither the name of Freescale Semiconductor nor the ++ * names of its contributors may be used to endorse or promote products ++ * derived from this software without specific prior written permission. ++ * ++ * ++ * ALTERNATIVELY, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") as published by the Free Software ++ * Foundation, either version 2 of that License or (at your option) any ++ * later version. ++ * ++ * THIS SOFTWARE IS PROVIDED BY Freescale Semiconductor ``AS IS'' AND ANY ++ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ++ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE ++ * DISCLAIMED. IN NO EVENT SHALL Freescale Semiconductor BE LIABLE FOR ANY ++ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ++ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; ++ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ++ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ++ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS ++ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++#ifndef DPAA2_ETH_DEBUGFS_H ++#define DPAA2_ETH_DEBUGFS_H ++ ++#include ++#include "dpaa2-eth.h" ++ ++extern struct dpaa2_eth_priv *priv; ++ ++struct dpaa2_debugfs { ++ struct dentry *dir; ++ struct dentry *fq_stats; ++ struct dentry *ch_stats; ++ struct dentry *cpu_stats; ++ struct dentry *reset_stats; ++}; ++ ++#ifdef CONFIG_FSL_DPAA2_ETH_DEBUGFS ++void dpaa2_eth_dbg_init(void); ++void dpaa2_eth_dbg_exit(void); ++void dpaa2_dbg_add(struct dpaa2_eth_priv *priv); ++void dpaa2_dbg_remove(struct dpaa2_eth_priv *priv); ++#else ++static inline void dpaa2_eth_dbg_init(void) {} ++static inline void dpaa2_eth_dbg_exit(void) {} ++static inline void dpaa2_dbg_add(struct dpaa2_eth_priv *priv) {} ++static inline void dpaa2_dbg_remove(struct dpaa2_eth_priv *priv) {} ++#endif /* CONFIG_FSL_DPAA2_ETH_DEBUGFS */ ++ ++#endif /* DPAA2_ETH_DEBUGFS_H */ ++ +diff --git a/drivers/staging/fsl-dpaa2/ethernet/dpaa2-eth-trace.h b/drivers/staging/fsl-dpaa2/ethernet/dpaa2-eth-trace.h +new file mode 100644 +index 0000000..3b040e8 +--- /dev/null ++++ b/drivers/staging/fsl-dpaa2/ethernet/dpaa2-eth-trace.h +@@ -0,0 +1,185 @@ ++/* Copyright 2014-2015 Freescale Semiconductor Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * * Neither the name of Freescale Semiconductor nor the ++ * names of its contributors may be used to endorse or promote products ++ * derived from this software without specific prior written permission. ++ * ++ * ++ * ALTERNATIVELY, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") as published by the Free Software ++ * Foundation, either version 2 of that License or (at your option) any ++ * later version. ++ * ++ * THIS SOFTWARE IS PROVIDED BY Freescale Semiconductor ``AS IS'' AND ANY ++ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ++ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE ++ * DISCLAIMED. IN NO EVENT SHALL Freescale Semiconductor BE LIABLE FOR ANY ++ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ++ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; ++ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ++ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ++ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS ++ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++#undef TRACE_SYSTEM ++#define TRACE_SYSTEM dpaa2_eth ++ ++#if !defined(_DPAA2_ETH_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) ++#define _DPAA2_ETH_TRACE_H ++ ++#include ++#include ++#include "dpaa2-eth.h" ++#include ++ ++#define TR_FMT "[%s] fd: addr=0x%llx, len=%u, off=%u" ++/* trace_printk format for raw buffer event class */ ++#define TR_BUF_FMT "[%s] vaddr=%p size=%zu dma_addr=%pad map_size=%zu bpid=%d" ++ ++/* This is used to declare a class of events. ++ * individual events of this type will be defined below. ++ */ ++ ++/* Store details about a frame descriptor */ ++DECLARE_EVENT_CLASS(dpaa2_eth_fd, ++ /* Trace function prototype */ ++ TP_PROTO(struct net_device *netdev, ++ const struct dpaa2_fd *fd), ++ ++ /* Repeat argument list here */ ++ TP_ARGS(netdev, fd), ++ ++ /* A structure containing the relevant information we want ++ * to record. Declare name and type for each normal element, ++ * name, type and size for arrays. Use __string for variable ++ * length strings. ++ */ ++ TP_STRUCT__entry( ++ __field(u64, fd_addr) ++ __field(u32, fd_len) ++ __field(u16, fd_offset) ++ __string(name, netdev->name) ++ ), ++ ++ /* The function that assigns values to the above declared ++ * fields ++ */ ++ TP_fast_assign( ++ __entry->fd_addr = dpaa2_fd_get_addr(fd); ++ __entry->fd_len = dpaa2_fd_get_len(fd); ++ __entry->fd_offset = dpaa2_fd_get_offset(fd); ++ __assign_str(name, netdev->name); ++ ), ++ ++ /* This is what gets printed when the trace event is ++ * triggered. ++ */ ++ TP_printk(TR_FMT, ++ __get_str(name), ++ __entry->fd_addr, ++ __entry->fd_len, ++ __entry->fd_offset) ++); ++ ++/* Now declare events of the above type. Format is: ++ * DEFINE_EVENT(class, name, proto, args), with proto and args same as for class ++ */ ++ ++/* Tx (egress) fd */ ++DEFINE_EVENT(dpaa2_eth_fd, dpaa2_tx_fd, ++ TP_PROTO(struct net_device *netdev, ++ const struct dpaa2_fd *fd), ++ ++ TP_ARGS(netdev, fd) ++); ++ ++/* Rx fd */ ++DEFINE_EVENT(dpaa2_eth_fd, dpaa2_rx_fd, ++ TP_PROTO(struct net_device *netdev, ++ const struct dpaa2_fd *fd), ++ ++ TP_ARGS(netdev, fd) ++); ++ ++/* Tx confirmation fd */ ++DEFINE_EVENT(dpaa2_eth_fd, dpaa2_tx_conf_fd, ++ TP_PROTO(struct net_device *netdev, ++ const struct dpaa2_fd *fd), ++ ++ TP_ARGS(netdev, fd) ++); ++ ++/* Log data about raw buffers. Useful for tracing DPBP content. */ ++TRACE_EVENT(dpaa2_eth_buf_seed, ++ /* Trace function prototype */ ++ TP_PROTO(struct net_device *netdev, ++ /* virtual address and size */ ++ void *vaddr, ++ size_t size, ++ /* dma map address and size */ ++ dma_addr_t dma_addr, ++ size_t map_size, ++ /* buffer pool id, if relevant */ ++ u16 bpid), ++ ++ /* Repeat argument list here */ ++ TP_ARGS(netdev, vaddr, size, dma_addr, map_size, bpid), ++ ++ /* A structure containing the relevant information we want ++ * to record. Declare name and type for each normal element, ++ * name, type and size for arrays. Use __string for variable ++ * length strings. ++ */ ++ TP_STRUCT__entry( ++ __field(void *, vaddr) ++ __field(size_t, size) ++ __field(dma_addr_t, dma_addr) ++ __field(size_t, map_size) ++ __field(u16, bpid) ++ __string(name, netdev->name) ++ ), ++ ++ /* The function that assigns values to the above declared ++ * fields ++ */ ++ TP_fast_assign( ++ __entry->vaddr = vaddr; ++ __entry->size = size; ++ __entry->dma_addr = dma_addr; ++ __entry->map_size = map_size; ++ __entry->bpid = bpid; ++ __assign_str(name, netdev->name); ++ ), ++ ++ /* This is what gets printed when the trace event is ++ * triggered. ++ */ ++ TP_printk(TR_BUF_FMT, ++ __get_str(name), ++ __entry->vaddr, ++ __entry->size, ++ &__entry->dma_addr, ++ __entry->map_size, ++ __entry->bpid) ++); ++ ++/* If only one event of a certain type needs to be declared, use TRACE_EVENT(). ++ * The syntax is the same as for DECLARE_EVENT_CLASS(). ++ */ ++ ++#endif /* _DPAA2_ETH_TRACE_H */ ++ ++/* This must be outside ifdef _DPAA2_ETH_TRACE_H */ ++#undef TRACE_INCLUDE_PATH ++#define TRACE_INCLUDE_PATH . ++#undef TRACE_INCLUDE_FILE ++#define TRACE_INCLUDE_FILE dpaa2-eth-trace ++#include +diff --git a/drivers/staging/fsl-dpaa2/ethernet/dpaa2-eth.c b/drivers/staging/fsl-dpaa2/ethernet/dpaa2-eth.c +new file mode 100644 +index 0000000..cb52ede +--- /dev/null ++++ b/drivers/staging/fsl-dpaa2/ethernet/dpaa2-eth.c +@@ -0,0 +1,2957 @@ ++/* Copyright 2014-2015 Freescale Semiconductor Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * * Neither the name of Freescale Semiconductor nor the ++ * names of its contributors may be used to endorse or promote products ++ * derived from this software without specific prior written permission. ++ * ++ * ++ * ALTERNATIVELY, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") as published by the Free Software ++ * Foundation, either version 2 of that License or (at your option) any ++ * later version. ++ * ++ * THIS SOFTWARE IS PROVIDED BY Freescale Semiconductor ``AS IS'' AND ANY ++ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ++ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE ++ * DISCLAIMED. IN NO EVENT SHALL Freescale Semiconductor BE LIABLE FOR ANY ++ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ++ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; ++ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ++ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ++ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS ++ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ++ */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "../../fsl-mc/include/mc.h" ++#include "../../fsl-mc/include/mc-sys.h" ++#include "dpaa2-eth.h" ++ ++/* CREATE_TRACE_POINTS only needs to be defined once. Other dpa files ++ * using trace events only need to #include ++ */ ++#define CREATE_TRACE_POINTS ++#include "dpaa2-eth-trace.h" ++ ++MODULE_LICENSE("Dual BSD/GPL"); ++MODULE_AUTHOR("Freescale Semiconductor, Inc"); ++MODULE_DESCRIPTION("Freescale DPAA2 Ethernet Driver"); ++ ++static void validate_rx_csum(struct dpaa2_eth_priv *priv, ++ u32 fd_status, ++ struct sk_buff *skb) ++{ ++ skb_checksum_none_assert(skb); ++ ++ /* HW checksum validation is disabled, nothing to do here */ ++ if (!(priv->net_dev->features & NETIF_F_RXCSUM)) ++ return; ++ ++ /* Read checksum validation bits */ ++ if (!((fd_status & DPAA2_FAS_L3CV) && ++ (fd_status & DPAA2_FAS_L4CV))) ++ return; ++ ++ /* Inform the stack there's no need to compute L3/L4 csum anymore */ ++ skb->ip_summed = CHECKSUM_UNNECESSARY; ++} ++ ++/* Free a received FD. ++ * Not to be used for Tx conf FDs or on any other paths. ++ */ ++static void free_rx_fd(struct dpaa2_eth_priv *priv, ++ const struct dpaa2_fd *fd, ++ void *vaddr) ++{ ++ struct device *dev = priv->net_dev->dev.parent; ++ dma_addr_t addr = dpaa2_fd_get_addr(fd); ++ u8 fd_format = dpaa2_fd_get_format(fd); ++ struct dpaa2_sg_entry *sgt; ++ void *sg_vaddr; ++ int i; ++ ++ /* If single buffer frame, just free the data buffer */ ++ if (fd_format == dpaa2_fd_single) ++ goto free_buf; ++ ++ /* For S/G frames, we first need to free all SG entries */ ++ sgt = vaddr + dpaa2_fd_get_offset(fd); ++ for (i = 0; i < DPAA2_ETH_MAX_SG_ENTRIES; i++) { ++ dpaa2_sg_le_to_cpu(&sgt[i]); ++ ++ addr = dpaa2_sg_get_addr(&sgt[i]); ++ dma_unmap_single(dev, addr, DPAA2_ETH_RX_BUF_SIZE, ++ DMA_FROM_DEVICE); ++ ++ sg_vaddr = phys_to_virt(addr); ++ put_page(virt_to_head_page(sg_vaddr)); ++ ++ if (dpaa2_sg_is_final(&sgt[i])) ++ break; ++ } ++ ++free_buf: ++ put_page(virt_to_head_page(vaddr)); ++} ++ ++/* Build a linear skb based on a single-buffer frame descriptor */ ++static struct sk_buff *build_linear_skb(struct dpaa2_eth_priv *priv, ++ struct dpaa2_eth_channel *ch, ++ const struct dpaa2_fd *fd, ++ void *fd_vaddr) ++{ ++ struct sk_buff *skb = NULL; ++ u16 fd_offset = dpaa2_fd_get_offset(fd); ++ u32 fd_length = dpaa2_fd_get_len(fd); ++ ++ skb = build_skb(fd_vaddr, DPAA2_ETH_RX_BUF_SIZE + ++ SKB_DATA_ALIGN(sizeof(struct skb_shared_info))); ++ if (unlikely(!skb)) ++ return NULL; ++ ++ skb_reserve(skb, fd_offset); ++ skb_put(skb, fd_length); ++ ++ ch->buf_count--; ++ ++ return skb; ++} ++ ++/* Build a non linear (fragmented) skb based on a S/G table */ ++static struct sk_buff *build_frag_skb(struct dpaa2_eth_priv *priv, ++ struct dpaa2_eth_channel *ch, ++ struct dpaa2_sg_entry *sgt) ++{ ++ struct sk_buff *skb = NULL; ++ struct device *dev = priv->net_dev->dev.parent; ++ void *sg_vaddr; ++ dma_addr_t sg_addr; ++ u16 sg_offset; ++ u32 sg_length; ++ struct page *page, *head_page; ++ int page_offset; ++ int i; ++ ++ for (i = 0; i < DPAA2_ETH_MAX_SG_ENTRIES; i++) { ++ struct dpaa2_sg_entry *sge = &sgt[i]; ++ ++ dpaa2_sg_le_to_cpu(sge); ++ ++ /* NOTE: We only support SG entries in dpaa2_sg_single format, ++ * but this is the only format we may receive from HW anyway ++ */ ++ ++ /* Get the address and length from the S/G entry */ ++ sg_addr = dpaa2_sg_get_addr(sge); ++ dma_unmap_single(dev, sg_addr, DPAA2_ETH_RX_BUF_SIZE, ++ DMA_FROM_DEVICE); ++ ++ sg_vaddr = phys_to_virt(sg_addr); ++ sg_length = dpaa2_sg_get_len(sge); ++ ++ if (i == 0) { ++ /* We build the skb around the first data buffer */ ++ skb = build_skb(sg_vaddr, DPAA2_ETH_RX_BUF_SIZE + ++ SKB_DATA_ALIGN(sizeof(struct skb_shared_info))); ++ if (unlikely(!skb)) ++ return NULL; ++ ++ sg_offset = dpaa2_sg_get_offset(sge); ++ skb_reserve(skb, sg_offset); ++ skb_put(skb, sg_length); ++ } else { ++ /* Rest of the data buffers are stored as skb frags */ ++ page = virt_to_page(sg_vaddr); ++ head_page = virt_to_head_page(sg_vaddr); ++ ++ /* Offset in page (which may be compound). ++ * Data in subsequent SG entries is stored from the ++ * beginning of the buffer, so we don't need to add the ++ * sg_offset. ++ */ ++ page_offset = ((unsigned long)sg_vaddr & ++ (PAGE_SIZE - 1)) + ++ (page_address(page) - page_address(head_page)); ++ ++ skb_add_rx_frag(skb, i - 1, head_page, page_offset, ++ sg_length, DPAA2_ETH_RX_BUF_SIZE); ++ } ++ ++ if (dpaa2_sg_is_final(sge)) ++ break; ++ } ++ ++ /* Count all data buffers + SG table buffer */ ++ ch->buf_count -= i + 2; ++ ++ return skb; ++} ++ ++/* Main Rx frame processing routine */ ++static void dpaa2_eth_rx(struct dpaa2_eth_priv *priv, ++ struct dpaa2_eth_channel *ch, ++ const struct dpaa2_fd *fd, ++ struct napi_struct *napi) ++{ ++ dma_addr_t addr = dpaa2_fd_get_addr(fd); ++ u8 fd_format = dpaa2_fd_get_format(fd); ++ void *vaddr; ++ struct sk_buff *skb; ++ struct rtnl_link_stats64 *percpu_stats; ++ struct dpaa2_eth_drv_stats *percpu_extras; ++ struct device *dev = priv->net_dev->dev.parent; ++ struct dpaa2_fas *fas; ++ u32 status = 0; ++ ++ /* Tracing point */ ++ trace_dpaa2_rx_fd(priv->net_dev, fd); ++ ++ dma_unmap_single(dev, addr, DPAA2_ETH_RX_BUF_SIZE, DMA_FROM_DEVICE); ++ vaddr = phys_to_virt(addr); ++ ++ prefetch(vaddr + priv->buf_layout.private_data_size); ++ prefetch(vaddr + dpaa2_fd_get_offset(fd)); ++ ++ percpu_stats = this_cpu_ptr(priv->percpu_stats); ++ percpu_extras = this_cpu_ptr(priv->percpu_extras); ++ ++ if (fd_format == dpaa2_fd_single) { ++ skb = build_linear_skb(priv, ch, fd, vaddr); ++ } else if (fd_format == dpaa2_fd_sg) { ++ struct dpaa2_sg_entry *sgt = ++ vaddr + dpaa2_fd_get_offset(fd); ++ skb = build_frag_skb(priv, ch, sgt); ++ put_page(virt_to_head_page(vaddr)); ++ percpu_extras->rx_sg_frames++; ++ percpu_extras->rx_sg_bytes += dpaa2_fd_get_len(fd); ++ } else { ++ /* We don't support any other format */ ++ goto err_frame_format; ++ } ++ ++ if (unlikely(!skb)) ++ goto err_build_skb; ++ ++ prefetch(skb->data); ++ ++ /* Get the timestamp value */ ++ if (priv->ts_rx_en) { ++ struct skb_shared_hwtstamps *shhwtstamps = skb_hwtstamps(skb); ++ u64 *ns = (u64 *)(vaddr + ++ priv->buf_layout.private_data_size + ++ sizeof(struct dpaa2_fas)); ++ ++ *ns = DPAA2_PTP_NOMINAL_FREQ_PERIOD_NS * le64_to_cpup(ns); ++ memset(shhwtstamps, 0, sizeof(*shhwtstamps)); ++ shhwtstamps->hwtstamp = ns_to_ktime(*ns); ++ } ++ ++ /* Check if we need to validate the L4 csum */ ++ if (likely(fd->simple.frc & DPAA2_FD_FRC_FASV)) { ++ fas = (struct dpaa2_fas *) ++ (vaddr + priv->buf_layout.private_data_size); ++ status = le32_to_cpu(fas->status); ++ validate_rx_csum(priv, status, skb); ++ } ++ ++ skb->protocol = eth_type_trans(skb, priv->net_dev); ++ ++ percpu_stats->rx_packets++; ++ percpu_stats->rx_bytes += skb->len; ++ ++ if (priv->net_dev->features & NETIF_F_GRO) ++ napi_gro_receive(napi, skb); ++ else ++ netif_receive_skb(skb); ++ ++ return; ++err_frame_format: ++err_build_skb: ++ free_rx_fd(priv, fd, vaddr); ++ percpu_stats->rx_dropped++; ++} ++ ++#ifdef CONFIG_FSL_DPAA2_ETH_USE_ERR_QUEUE ++/* Processing of Rx frames received on the error FQ ++ * We check and print the error bits and then free the frame ++ */ ++static void dpaa2_eth_rx_err(struct dpaa2_eth_priv *priv, ++ struct dpaa2_eth_channel *ch, ++ const struct dpaa2_fd *fd, ++ struct napi_struct *napi __always_unused) ++{ ++ struct device *dev = priv->net_dev->dev.parent; ++ dma_addr_t addr = dpaa2_fd_get_addr(fd); ++ void *vaddr; ++ struct rtnl_link_stats64 *percpu_stats; ++ struct dpaa2_fas *fas; ++ u32 status = 0; ++ ++ dma_unmap_single(dev, addr, DPAA2_ETH_RX_BUF_SIZE, DMA_FROM_DEVICE); ++ vaddr = phys_to_virt(addr); ++ ++ if (fd->simple.frc & DPAA2_FD_FRC_FASV) { ++ fas = (struct dpaa2_fas *) ++ (vaddr + priv->buf_layout.private_data_size); ++ status = le32_to_cpu(fas->status); ++ if (net_ratelimit()) ++ netdev_warn(priv->net_dev, "Rx frame error: 0x%08x\n", ++ status & DPAA2_ETH_RX_ERR_MASK); ++ } ++ free_rx_fd(priv, fd, vaddr); ++ ++ percpu_stats = this_cpu_ptr(priv->percpu_stats); ++ percpu_stats->rx_errors++; ++} ++#endif ++ ++/* Consume all frames pull-dequeued into the store. This is the simplest way to ++ * make sure we don't accidentally issue another volatile dequeue which would ++ * overwrite (leak) frames already in the store. ++ * ++ * Observance of NAPI budget is not our concern, leaving that to the caller. ++ */ ++static int consume_frames(struct dpaa2_eth_channel *ch) ++{ ++ struct dpaa2_eth_priv *priv = ch->priv; ++ struct dpaa2_eth_fq *fq; ++ struct dpaa2_dq *dq; ++ const struct dpaa2_fd *fd; ++ int cleaned = 0; ++ int is_last; ++ ++ do { ++ dq = dpaa2_io_store_next(ch->store, &is_last); ++ if (unlikely(!dq)) { ++ /* If we're here, we *must* have placed a ++ * volatile dequeue comnmand, so keep reading through ++ * the store until we get some sort of valid response ++ * token (either a valid frame or an "empty dequeue") ++ */ ++ continue; ++ } ++ ++ fd = dpaa2_dq_fd(dq); ++ fq = (struct dpaa2_eth_fq *)dpaa2_dq_fqd_ctx(dq); ++ fq->stats.frames++; ++ ++ fq->consume(priv, ch, fd, &ch->napi); ++ cleaned++; ++ } while (!is_last); ++ ++ return cleaned; ++} ++ ++/* Configure the egress frame annotation for timestamp update */ ++static void enable_tx_tstamp(struct dpaa2_fd *fd, void *hwa_start) ++{ ++ struct dpaa2_faead *faead; ++ u32 ctrl; ++ u32 frc; ++ ++ /* Mark the egress frame annotation area as valid */ ++ frc = dpaa2_fd_get_frc(fd); ++ dpaa2_fd_set_frc(fd, frc | DPAA2_FD_FRC_FAEADV); ++ ++ /* enable UPD (update prepanded data) bit in FAEAD field of ++ * hardware frame annotation area ++ */ ++ ctrl = DPAA2_FAEAD_A2V | DPAA2_FAEAD_UPDV | DPAA2_FAEAD_UPD; ++ faead = hwa_start + DPAA2_FAEAD_OFFSET; ++ faead->ctrl = cpu_to_le32(ctrl); ++} ++ ++/* Create a frame descriptor based on a fragmented skb */ ++static int build_sg_fd(struct dpaa2_eth_priv *priv, ++ struct sk_buff *skb, ++ struct dpaa2_fd *fd) ++{ ++ struct device *dev = priv->net_dev->dev.parent; ++ void *sgt_buf = NULL; ++ void *hwa; ++ dma_addr_t addr; ++ int nr_frags = skb_shinfo(skb)->nr_frags; ++ struct dpaa2_sg_entry *sgt; ++ int i, j, err; ++ int sgt_buf_size; ++ struct scatterlist *scl, *crt_scl; ++ int num_sg; ++ int num_dma_bufs; ++ struct dpaa2_eth_swa *swa; ++ ++ /* Create and map scatterlist. ++ * We don't advertise NETIF_F_FRAGLIST, so skb_to_sgvec() will not have ++ * to go beyond nr_frags+1. ++ * Note: We don't support chained scatterlists ++ */ ++ if (unlikely(PAGE_SIZE / sizeof(struct scatterlist) < nr_frags + 1)) ++ return -EINVAL; ++ ++ scl = kcalloc(nr_frags + 1, sizeof(struct scatterlist), GFP_ATOMIC); ++ if (unlikely(!scl)) ++ return -ENOMEM; ++ ++ sg_init_table(scl, nr_frags + 1); ++ num_sg = skb_to_sgvec(skb, scl, 0, skb->len); ++ num_dma_bufs = dma_map_sg(dev, scl, num_sg, DMA_TO_DEVICE); ++ if (unlikely(!num_dma_bufs)) { ++ err = -ENOMEM; ++ goto dma_map_sg_failed; ++ } ++ ++ /* Prepare the HW SGT structure */ ++ sgt_buf_size = priv->tx_data_offset + ++ sizeof(struct dpaa2_sg_entry) * (1 + num_dma_bufs); ++ sgt_buf = kzalloc(sgt_buf_size + DPAA2_ETH_TX_BUF_ALIGN, GFP_ATOMIC); ++ if (unlikely(!sgt_buf)) { ++ err = -ENOMEM; ++ goto sgt_buf_alloc_failed; ++ } ++ sgt_buf = PTR_ALIGN(sgt_buf, DPAA2_ETH_TX_BUF_ALIGN); ++ ++ /* PTA from egress side is passed as is to the confirmation side so ++ * we need to clear some fields here in order to find consistent values ++ * on TX confirmation. We are clearing FAS (Frame Annotation Status) ++ * field here. ++ */ ++ hwa = sgt_buf + priv->buf_layout.private_data_size; ++ memset(hwa, 0, 8); ++ ++ sgt = (struct dpaa2_sg_entry *)(sgt_buf + priv->tx_data_offset); ++ ++ /* Fill in the HW SGT structure. ++ * ++ * sgt_buf is zeroed out, so the following fields are implicit ++ * in all sgt entries: ++ * - offset is 0 ++ * - format is 'dpaa2_sg_single' ++ */ ++ for_each_sg(scl, crt_scl, num_dma_bufs, i) { ++ dpaa2_sg_set_addr(&sgt[i], sg_dma_address(crt_scl)); ++ dpaa2_sg_set_len(&sgt[i], sg_dma_len(crt_scl)); ++ } ++ dpaa2_sg_set_final(&sgt[i - 1], true); ++ ++ /* Store the skb backpointer in the SGT buffer. ++ * Fit the scatterlist and the number of buffers alongside the ++ * skb backpointer in the SWA. We'll need all of them on Tx Conf. ++ */ ++ swa = (struct dpaa2_eth_swa *)sgt_buf; ++ swa->skb = skb; ++ swa->scl = scl; ++ swa->num_sg = num_sg; ++ swa->num_dma_bufs = num_dma_bufs; ++ ++ /* Hardware expects the SG table to be in little endian format */ ++ for (j = 0; j < i; j++) ++ dpaa2_sg_cpu_to_le(&sgt[j]); ++ ++ /* Separately map the SGT buffer */ ++ addr = dma_map_single(dev, sgt_buf, sgt_buf_size, DMA_TO_DEVICE); ++ if (unlikely(dma_mapping_error(dev, addr))) { ++ err = -ENOMEM; ++ goto dma_map_single_failed; ++ } ++ dpaa2_fd_set_offset(fd, priv->tx_data_offset); ++ dpaa2_fd_set_format(fd, dpaa2_fd_sg); ++ dpaa2_fd_set_addr(fd, addr); ++ dpaa2_fd_set_len(fd, skb->len); ++ ++ fd->simple.ctrl = DPAA2_FD_CTRL_ASAL | DPAA2_FD_CTRL_PTA | ++ DPAA2_FD_CTRL_PTV1; ++ ++ if (priv->ts_tx_en && skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) ++ enable_tx_tstamp(fd, hwa); ++ ++ return 0; ++ ++dma_map_single_failed: ++ kfree(sgt_buf); ++sgt_buf_alloc_failed: ++ dma_unmap_sg(dev, scl, num_sg, DMA_TO_DEVICE); ++dma_map_sg_failed: ++ kfree(scl); ++ return err; ++} ++ ++/* Create a frame descriptor based on a linear skb */ ++static int build_single_fd(struct dpaa2_eth_priv *priv, ++ struct sk_buff *skb, ++ struct dpaa2_fd *fd) ++{ ++ struct device *dev = priv->net_dev->dev.parent; ++ u8 *buffer_start; ++ struct sk_buff **skbh; ++ dma_addr_t addr; ++ void *hwa; ++ ++ buffer_start = PTR_ALIGN(skb->data - priv->tx_data_offset - ++ DPAA2_ETH_TX_BUF_ALIGN, ++ DPAA2_ETH_TX_BUF_ALIGN); ++ ++ /* PTA from egress side is passed as is to the confirmation side so ++ * we need to clear some fields here in order to find consistent values ++ * on TX confirmation. We are clearing FAS (Frame Annotation Status) ++ * field here ++ */ ++ hwa = buffer_start + priv->buf_layout.private_data_size; ++ memset(hwa, 0, 8); ++ ++ /* Store a backpointer to the skb at the beginning of the buffer ++ * (in the private data area) such that we can release it ++ * on Tx confirm ++ */ ++ skbh = (struct sk_buff **)buffer_start; ++ *skbh = skb; ++ ++ addr = dma_map_single(dev, buffer_start, ++ skb_tail_pointer(skb) - buffer_start, ++ DMA_TO_DEVICE); ++ if (unlikely(dma_mapping_error(dev, addr))) ++ return -ENOMEM; ++ ++ dpaa2_fd_set_addr(fd, addr); ++ dpaa2_fd_set_offset(fd, (u16)(skb->data - buffer_start)); ++ dpaa2_fd_set_len(fd, skb->len); ++ dpaa2_fd_set_format(fd, dpaa2_fd_single); ++ ++ fd->simple.ctrl = DPAA2_FD_CTRL_ASAL | DPAA2_FD_CTRL_PTA | ++ DPAA2_FD_CTRL_PTV1; ++ ++ if (priv->ts_tx_en && skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) ++ enable_tx_tstamp(fd, hwa); ++ ++ return 0; ++} ++ ++/* FD freeing routine on the Tx path ++ * ++ * DMA-unmap and free FD and possibly SGT buffer allocated on Tx. The skb ++ * back-pointed to is also freed. ++ * This can be called either from dpaa2_eth_tx_conf() or on the error path of ++ * dpaa2_eth_tx(). ++ * Optionally, return the frame annotation status word (FAS), which needs ++ * to be checked if we're on the confirmation path. ++ */ ++static void free_tx_fd(const struct dpaa2_eth_priv *priv, ++ const struct dpaa2_fd *fd, ++ u32 *status) ++{ ++ struct device *dev = priv->net_dev->dev.parent; ++ dma_addr_t fd_addr; ++ struct sk_buff **skbh, *skb; ++ unsigned char *buffer_start; ++ int unmap_size; ++ struct scatterlist *scl; ++ int num_sg, num_dma_bufs; ++ struct dpaa2_eth_swa *swa; ++ bool fd_single; ++ struct dpaa2_fas *fas; ++ ++ fd_addr = dpaa2_fd_get_addr(fd); ++ skbh = phys_to_virt(fd_addr); ++ fd_single = (dpaa2_fd_get_format(fd) == dpaa2_fd_single); ++ ++ if (fd_single) { ++ skb = *skbh; ++ buffer_start = (unsigned char *)skbh; ++ /* Accessing the skb buffer is safe before dma unmap, because ++ * we didn't map the actual skb shell. ++ */ ++ dma_unmap_single(dev, fd_addr, ++ skb_tail_pointer(skb) - buffer_start, ++ DMA_TO_DEVICE); ++ } else { ++ swa = (struct dpaa2_eth_swa *)skbh; ++ skb = swa->skb; ++ scl = swa->scl; ++ num_sg = swa->num_sg; ++ num_dma_bufs = swa->num_dma_bufs; ++ ++ /* Unmap the scatterlist */ ++ dma_unmap_sg(dev, scl, num_sg, DMA_TO_DEVICE); ++ kfree(scl); ++ ++ /* Unmap the SGT buffer */ ++ unmap_size = priv->tx_data_offset + ++ sizeof(struct dpaa2_sg_entry) * (1 + num_dma_bufs); ++ dma_unmap_single(dev, fd_addr, unmap_size, DMA_TO_DEVICE); ++ } ++ ++ /* Get the timestamp value */ ++ if (priv->ts_tx_en && skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) { ++ struct skb_shared_hwtstamps shhwtstamps; ++ u64 *ns; ++ ++ memset(&shhwtstamps, 0, sizeof(shhwtstamps)); ++ ++ ns = (u64 *)((void *)skbh + ++ priv->buf_layout.private_data_size + ++ sizeof(struct dpaa2_fas)); ++ *ns = DPAA2_PTP_NOMINAL_FREQ_PERIOD_NS * le64_to_cpup(ns); ++ shhwtstamps.hwtstamp = ns_to_ktime(*ns); ++ skb_tstamp_tx(skb, &shhwtstamps); ++ } ++ ++ /* Read the status from the Frame Annotation after we unmap the first ++ * buffer but before we free it. The caller function is responsible ++ * for checking the status value. ++ */ ++ if (status && (fd->simple.frc & DPAA2_FD_FRC_FASV)) { ++ fas = (struct dpaa2_fas *) ++ ((void *)skbh + priv->buf_layout.private_data_size); ++ *status = le32_to_cpu(fas->status); ++ } ++ ++ /* Free SGT buffer kmalloc'ed on tx */ ++ if (!fd_single) ++ kfree(skbh); ++ ++ /* Move on with skb release */ ++ dev_kfree_skb(skb); ++} ++ ++static int dpaa2_eth_tx(struct sk_buff *skb, struct net_device *net_dev) ++{ ++ struct dpaa2_eth_priv *priv = netdev_priv(net_dev); ++ struct dpaa2_fd fd; ++ struct rtnl_link_stats64 *percpu_stats; ++ struct dpaa2_eth_drv_stats *percpu_extras; ++ u16 queue_mapping, flow_id; ++ int err, i; ++ ++ percpu_stats = this_cpu_ptr(priv->percpu_stats); ++ percpu_extras = this_cpu_ptr(priv->percpu_extras); ++ ++ if (unlikely(skb_headroom(skb) < DPAA2_ETH_NEEDED_HEADROOM(priv))) { ++ struct sk_buff *ns; ++ ++ ns = skb_realloc_headroom(skb, DPAA2_ETH_NEEDED_HEADROOM(priv)); ++ if (unlikely(!ns)) { ++ percpu_stats->tx_dropped++; ++ goto err_alloc_headroom; ++ } ++ dev_kfree_skb(skb); ++ skb = ns; ++ } ++ ++ /* We'll be holding a back-reference to the skb until Tx Confirmation; ++ * we don't want that overwritten by a concurrent Tx with a cloned skb. ++ */ ++ skb = skb_unshare(skb, GFP_ATOMIC); ++ if (unlikely(!skb)) { ++ /* skb_unshare() has already freed the skb */ ++ percpu_stats->tx_dropped++; ++ return NETDEV_TX_OK; ++ } ++ ++ /* Setup the FD fields */ ++ memset(&fd, 0, sizeof(fd)); ++ ++ if (skb_is_nonlinear(skb)) { ++ err = build_sg_fd(priv, skb, &fd); ++ percpu_extras->tx_sg_frames++; ++ percpu_extras->tx_sg_bytes += skb->len; ++ } else { ++ err = build_single_fd(priv, skb, &fd); ++ } ++ ++ if (unlikely(err)) { ++ percpu_stats->tx_dropped++; ++ goto err_build_fd; ++ } ++ ++ /* Tracing point */ ++ trace_dpaa2_tx_fd(net_dev, &fd); ++ ++ /* TxConf FQ selection primarily based on cpu affinity; this is ++ * non-migratable context, so it's safe to call smp_processor_id(). ++ */ ++ queue_mapping = smp_processor_id() % priv->dpni_attrs.max_senders; ++ flow_id = priv->fq[queue_mapping].flowid; ++ for (i = 0; i < (DPAA2_ETH_MAX_TX_QUEUES << 1); i++) { ++ err = dpaa2_io_service_enqueue_qd(NULL, priv->tx_qdid, 0, ++ flow_id, &fd); ++ if (err != -EBUSY) ++ break; ++ } ++ percpu_extras->tx_portal_busy += i; ++ if (unlikely(err < 0)) { ++ percpu_stats->tx_errors++; ++ /* Clean up everything, including freeing the skb */ ++ free_tx_fd(priv, &fd, NULL); ++ } else { ++ percpu_stats->tx_packets++; ++ percpu_stats->tx_bytes += skb->len; ++ } ++ ++ return NETDEV_TX_OK; ++ ++err_build_fd: ++err_alloc_headroom: ++ dev_kfree_skb(skb); ++ ++ return NETDEV_TX_OK; ++} ++ ++/* Tx confirmation frame processing routine */ ++static void dpaa2_eth_tx_conf(struct dpaa2_eth_priv *priv, ++ struct dpaa2_eth_channel *ch, ++ const struct dpaa2_fd *fd, ++ struct napi_struct *napi __always_unused) ++{ ++ struct rtnl_link_stats64 *percpu_stats; ++ struct dpaa2_eth_drv_stats *percpu_extras; ++ u32 status = 0; ++ ++ /* Tracing point */ ++ trace_dpaa2_tx_conf_fd(priv->net_dev, fd); ++ ++ percpu_extras = this_cpu_ptr(priv->percpu_extras); ++ percpu_extras->tx_conf_frames++; ++ percpu_extras->tx_conf_bytes += dpaa2_fd_get_len(fd); ++ ++ free_tx_fd(priv, fd, &status); ++ ++ if (unlikely(status & DPAA2_ETH_TXCONF_ERR_MASK)) { ++ percpu_stats = this_cpu_ptr(priv->percpu_stats); ++ /* Tx-conf logically pertains to the egress path. */ ++ percpu_stats->tx_errors++; ++ } ++} ++ ++static int set_rx_csum(struct dpaa2_eth_priv *priv, bool enable) ++{ ++ int err; ++ ++ err = dpni_set_l3_chksum_validation(priv->mc_io, 0, priv->mc_token, ++ enable); ++ if (err) { ++ netdev_err(priv->net_dev, ++ "dpni_set_l3_chksum_validation() failed\n"); ++ return err; ++ } ++ ++ err = dpni_set_l4_chksum_validation(priv->mc_io, 0, priv->mc_token, ++ enable); ++ if (err) { ++ netdev_err(priv->net_dev, ++ "dpni_set_l4_chksum_validation failed\n"); ++ return err; ++ } ++ ++ return 0; ++} ++ ++static int set_tx_csum(struct dpaa2_eth_priv *priv, bool enable) ++{ ++ struct dpaa2_eth_fq *fq; ++ struct dpni_tx_flow_cfg tx_flow_cfg; ++ int err; ++ int i; ++ ++ memset(&tx_flow_cfg, 0, sizeof(tx_flow_cfg)); ++ tx_flow_cfg.options = DPNI_TX_FLOW_OPT_L3_CHKSUM_GEN | ++ DPNI_TX_FLOW_OPT_L4_CHKSUM_GEN; ++ tx_flow_cfg.l3_chksum_gen = enable; ++ tx_flow_cfg.l4_chksum_gen = enable; ++ ++ for (i = 0; i < priv->num_fqs; i++) { ++ fq = &priv->fq[i]; ++ if (fq->type != DPAA2_TX_CONF_FQ) ++ continue; ++ ++ /* The Tx flowid is kept in the corresponding TxConf FQ. */ ++ err = dpni_set_tx_flow(priv->mc_io, 0, priv->mc_token, ++ &fq->flowid, &tx_flow_cfg); ++ if (err) { ++ netdev_err(priv->net_dev, "dpni_set_tx_flow failed\n"); ++ return err; ++ } ++ } ++ ++ return 0; ++} ++ ++/* Perform a single release command to add buffers ++ * to the specified buffer pool ++ */ ++static int add_bufs(struct dpaa2_eth_priv *priv, u16 bpid) ++{ ++ struct device *dev = priv->net_dev->dev.parent; ++ u64 buf_array[DPAA2_ETH_BUFS_PER_CMD]; ++ void *buf; ++ dma_addr_t addr; ++ int i; ++ ++ for (i = 0; i < DPAA2_ETH_BUFS_PER_CMD; i++) { ++ /* Allocate buffer visible to WRIOP + skb shared info + ++ * alignment padding ++ */ ++ buf = netdev_alloc_frag(DPAA2_ETH_BUF_RAW_SIZE); ++ if (unlikely(!buf)) ++ goto err_alloc; ++ ++ buf = PTR_ALIGN(buf, DPAA2_ETH_RX_BUF_ALIGN); ++ ++ addr = dma_map_single(dev, buf, DPAA2_ETH_RX_BUF_SIZE, ++ DMA_FROM_DEVICE); ++ if (unlikely(dma_mapping_error(dev, addr))) ++ goto err_map; ++ ++ buf_array[i] = addr; ++ ++ /* tracing point */ ++ trace_dpaa2_eth_buf_seed(priv->net_dev, ++ buf, DPAA2_ETH_BUF_RAW_SIZE, ++ addr, DPAA2_ETH_RX_BUF_SIZE, ++ bpid); ++ } ++ ++release_bufs: ++ /* In case the portal is busy, retry until successful. ++ * The buffer release function would only fail if the QBMan portal ++ * was busy, which implies portal contention (i.e. more CPUs than ++ * portals, i.e. GPPs w/o affine DPIOs). For all practical purposes, ++ * there is little we can realistically do, short of giving up - ++ * in which case we'd risk depleting the buffer pool and never again ++ * receiving the Rx interrupt which would kick-start the refill logic. ++ * So just keep retrying, at the risk of being moved to ksoftirqd. ++ */ ++ while (dpaa2_io_service_release(NULL, bpid, buf_array, i)) ++ cpu_relax(); ++ return i; ++ ++err_map: ++ put_page(virt_to_head_page(buf)); ++err_alloc: ++ if (i) ++ goto release_bufs; ++ ++ return 0; ++} ++ ++static int seed_pool(struct dpaa2_eth_priv *priv, u16 bpid) ++{ ++ int i, j; ++ int new_count; ++ ++ /* This is the lazy seeding of Rx buffer pools. ++ * dpaa2_add_bufs() is also used on the Rx hotpath and calls ++ * napi_alloc_frag(). The trouble with that is that it in turn ends up ++ * calling this_cpu_ptr(), which mandates execution in atomic context. ++ * Rather than splitting up the code, do a one-off preempt disable. ++ */ ++ preempt_disable(); ++ for (j = 0; j < priv->num_channels; j++) { ++ for (i = 0; i < DPAA2_ETH_NUM_BUFS; ++ i += DPAA2_ETH_BUFS_PER_CMD) { ++ new_count = add_bufs(priv, bpid); ++ priv->channel[j]->buf_count += new_count; ++ ++ if (new_count < DPAA2_ETH_BUFS_PER_CMD) { ++ preempt_enable(); ++ return -ENOMEM; ++ } ++ } ++ } ++ preempt_enable(); ++ ++ return 0; ++} ++ ++/** ++ * Drain the specified number of buffers from the DPNI's private buffer pool. ++ * @count must not exceeed DPAA2_ETH_BUFS_PER_CMD ++ */ ++static void drain_bufs(struct dpaa2_eth_priv *priv, int count) ++{ ++ struct device *dev = priv->net_dev->dev.parent; ++ u64 buf_array[DPAA2_ETH_BUFS_PER_CMD]; ++ void *vaddr; ++ int ret, i; ++ ++ do { ++ ret = dpaa2_io_service_acquire(NULL, priv->dpbp_attrs.bpid, ++ buf_array, count); ++ if (ret < 0) { ++ netdev_err(priv->net_dev, "dpaa2_io_service_acquire() failed\n"); ++ return; ++ } ++ for (i = 0; i < ret; i++) { ++ /* Same logic as on regular Rx path */ ++ dma_unmap_single(dev, buf_array[i], ++ DPAA2_ETH_RX_BUF_SIZE, ++ DMA_FROM_DEVICE); ++ vaddr = phys_to_virt(buf_array[i]); ++ put_page(virt_to_head_page(vaddr)); ++ } ++ } while (ret); ++} ++ ++static void drain_pool(struct dpaa2_eth_priv *priv) ++{ ++ int i; ++ ++ drain_bufs(priv, DPAA2_ETH_BUFS_PER_CMD); ++ drain_bufs(priv, 1); ++ ++ for (i = 0; i < priv->num_channels; i++) ++ priv->channel[i]->buf_count = 0; ++} ++ ++/* Function is called from softirq context only, so we don't need to guard ++ * the access to percpu count ++ */ ++static int refill_pool(struct dpaa2_eth_priv *priv, ++ struct dpaa2_eth_channel *ch, ++ u16 bpid) ++{ ++ int new_count; ++ ++ if (likely(ch->buf_count >= DPAA2_ETH_REFILL_THRESH)) ++ return 0; ++ ++ do { ++ new_count = add_bufs(priv, bpid); ++ if (unlikely(!new_count)) { ++ /* Out of memory; abort for now, we'll try later on */ ++ break; ++ } ++ ch->buf_count += new_count; ++ } while (ch->buf_count < DPAA2_ETH_NUM_BUFS); ++ ++ if (unlikely(ch->buf_count < DPAA2_ETH_NUM_BUFS)) ++ return -ENOMEM; ++ ++ return 0; ++} ++ ++static int pull_channel(struct dpaa2_eth_channel *ch) ++{ ++ int err; ++ int dequeues = -1; ++ ++ /* Retry while portal is busy */ ++ do { ++ err = dpaa2_io_service_pull_channel(NULL, ch->ch_id, ch->store); ++ dequeues++; ++ cpu_relax(); ++ } while (err == -EBUSY); ++ ++ ch->stats.dequeue_portal_busy += dequeues; ++ if (unlikely(err)) ++ ch->stats.pull_err++; ++ ++ return err; ++} ++ ++/* NAPI poll routine ++ * ++ * Frames are dequeued from the QMan channel associated with this NAPI context. ++ * Rx, Tx confirmation and (if configured) Rx error frames all count ++ * towards the NAPI budget. ++ */ ++static int dpaa2_eth_poll(struct napi_struct *napi, int budget) ++{ ++ struct dpaa2_eth_channel *ch; ++ int cleaned = 0, store_cleaned; ++ struct dpaa2_eth_priv *priv; ++ int err; ++ ++ ch = container_of(napi, struct dpaa2_eth_channel, napi); ++ priv = ch->priv; ++ ++ while (cleaned < budget) { ++ err = pull_channel(ch); ++ if (unlikely(err)) ++ break; ++ ++ /* Refill pool if appropriate */ ++ refill_pool(priv, ch, priv->dpbp_attrs.bpid); ++ ++ store_cleaned = consume_frames(ch); ++ cleaned += store_cleaned; ++ ++ /* If we have enough budget left for a full store, ++ * try a new pull dequeue, otherwise we're done here ++ */ ++ if (store_cleaned == 0 || ++ cleaned > budget - DPAA2_ETH_STORE_SIZE) ++ break; ++ } ++ ++ if (cleaned < budget) { ++ napi_complete(napi); ++ /* Re-enable data available notifications */ ++ do { ++ err = dpaa2_io_service_rearm(NULL, &ch->nctx); ++ cpu_relax(); ++ } while (err == -EBUSY); ++ } ++ ++ ch->stats.frames += cleaned; ++ ++ return cleaned; ++} ++ ++static void enable_ch_napi(struct dpaa2_eth_priv *priv) ++{ ++ struct dpaa2_eth_channel *ch; ++ int i; ++ ++ for (i = 0; i < priv->num_channels; i++) { ++ ch = priv->channel[i]; ++ napi_enable(&ch->napi); ++ } ++} ++ ++static void disable_ch_napi(struct dpaa2_eth_priv *priv) ++{ ++ struct dpaa2_eth_channel *ch; ++ int i; ++ ++ for (i = 0; i < priv->num_channels; i++) { ++ ch = priv->channel[i]; ++ napi_disable(&ch->napi); ++ } ++} ++ ++static int link_state_update(struct dpaa2_eth_priv *priv) ++{ ++ struct dpni_link_state state; ++ int err; ++ ++ err = dpni_get_link_state(priv->mc_io, 0, priv->mc_token, &state); ++ if (unlikely(err)) { ++ netdev_err(priv->net_dev, ++ "dpni_get_link_state() failed\n"); ++ return err; ++ } ++ ++ /* Chech link state; speed / duplex changes are not treated yet */ ++ if (priv->link_state.up == state.up) ++ return 0; ++ ++ priv->link_state = state; ++ if (state.up) { ++ netif_carrier_on(priv->net_dev); ++ netif_tx_start_all_queues(priv->net_dev); ++ } else { ++ netif_tx_stop_all_queues(priv->net_dev); ++ netif_carrier_off(priv->net_dev); ++ } ++ ++ netdev_info(priv->net_dev, "Link Event: state %s", ++ state.up ? "up" : "down"); ++ ++ return 0; ++} ++ ++static int dpaa2_eth_open(struct net_device *net_dev) ++{ ++ struct dpaa2_eth_priv *priv = netdev_priv(net_dev); ++ int err; ++ ++ err = seed_pool(priv, priv->dpbp_attrs.bpid); ++ if (err) { ++ /* Not much to do; the buffer pool, though not filled up, ++ * may still contain some buffers which would enable us ++ * to limp on. ++ */ ++ netdev_err(net_dev, "Buffer seeding failed for DPBP %d (bpid=%d)\n", ++ priv->dpbp_dev->obj_desc.id, priv->dpbp_attrs.bpid); ++ } ++ ++ /* We'll only start the txqs when the link is actually ready; make sure ++ * we don't race against the link up notification, which may come ++ * immediately after dpni_enable(); ++ */ ++ netif_tx_stop_all_queues(net_dev); ++ enable_ch_napi(priv); ++ /* Also, explicitly set carrier off, otherwise netif_carrier_ok() will ++ * return true and cause 'ip link show' to report the LOWER_UP flag, ++ * even though the link notification wasn't even received. ++ */ ++ netif_carrier_off(net_dev); ++ ++ err = dpni_enable(priv->mc_io, 0, priv->mc_token); ++ if (err < 0) { ++ netdev_err(net_dev, "dpni_enable() failed\n"); ++ goto enable_err; ++ } ++ ++ /* If the DPMAC object has already processed the link up interrupt, ++ * we have to learn the link state ourselves. ++ */ ++ err = link_state_update(priv); ++ if (err < 0) { ++ netdev_err(net_dev, "Can't update link state\n"); ++ goto link_state_err; ++ } ++ ++ return 0; ++ ++link_state_err: ++enable_err: ++ disable_ch_napi(priv); ++ drain_pool(priv); ++ return err; ++} ++ ++/* The DPIO store must be empty when we call this, ++ * at the end of every NAPI cycle. ++ */ ++static u32 drain_channel(struct dpaa2_eth_priv *priv, ++ struct dpaa2_eth_channel *ch) ++{ ++ u32 drained = 0, total = 0; ++ ++ do { ++ pull_channel(ch); ++ drained = consume_frames(ch); ++ total += drained; ++ } while (drained); ++ ++ return total; ++} ++ ++static u32 drain_ingress_frames(struct dpaa2_eth_priv *priv) ++{ ++ struct dpaa2_eth_channel *ch; ++ int i; ++ u32 drained = 0; ++ ++ for (i = 0; i < priv->num_channels; i++) { ++ ch = priv->channel[i]; ++ drained += drain_channel(priv, ch); ++ } ++ ++ return drained; ++} ++ ++static int dpaa2_eth_stop(struct net_device *net_dev) ++{ ++ struct dpaa2_eth_priv *priv = netdev_priv(net_dev); ++ int dpni_enabled; ++ int retries = 10; ++ u32 drained; ++ ++ netif_tx_stop_all_queues(net_dev); ++ netif_carrier_off(net_dev); ++ ++ /* Loop while dpni_disable() attempts to drain the egress FQs ++ * and confirm them back to us. ++ */ ++ do { ++ dpni_disable(priv->mc_io, 0, priv->mc_token); ++ dpni_is_enabled(priv->mc_io, 0, priv->mc_token, &dpni_enabled); ++ if (dpni_enabled) ++ /* Allow the MC some slack */ ++ msleep(100); ++ } while (dpni_enabled && --retries); ++ if (!retries) { ++ netdev_warn(net_dev, "Retry count exceeded disabling DPNI\n"); ++ /* Must go on and disable NAPI nonetheless, so we don't crash at ++ * the next "ifconfig up" ++ */ ++ } ++ ++ /* Wait for NAPI to complete on every core and disable it. ++ * In particular, this will also prevent NAPI from being rescheduled if ++ * a new CDAN is serviced, effectively discarding the CDAN. We therefore ++ * don't even need to disarm the channels, except perhaps for the case ++ * of a huge coalescing value. ++ */ ++ disable_ch_napi(priv); ++ ++ /* Manually drain the Rx and TxConf queues */ ++ drained = drain_ingress_frames(priv); ++ if (drained) ++ netdev_dbg(net_dev, "Drained %d frames.\n", drained); ++ ++ /* Empty the buffer pool */ ++ drain_pool(priv); ++ ++ return 0; ++} ++ ++static int dpaa2_eth_init(struct net_device *net_dev) ++{ ++ u64 supported = 0; ++ u64 not_supported = 0; ++ struct dpaa2_eth_priv *priv = netdev_priv(net_dev); ++ u32 options = priv->dpni_attrs.options; ++ ++ /* Capabilities listing */ ++ supported |= IFF_LIVE_ADDR_CHANGE | IFF_PROMISC | IFF_ALLMULTI; ++ ++ if (options & DPNI_OPT_UNICAST_FILTER) ++ supported |= IFF_UNICAST_FLT; ++ else ++ not_supported |= IFF_UNICAST_FLT; ++ ++ if (options & DPNI_OPT_MULTICAST_FILTER) ++ supported |= IFF_MULTICAST; ++ else ++ not_supported |= IFF_MULTICAST; ++ ++ net_dev->priv_flags |= supported; ++ net_dev->priv_flags &= ~not_supported; ++ ++ /* Features */ ++ net_dev->features = NETIF_F_RXCSUM | ++ NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM | ++ NETIF_F_SG | NETIF_F_HIGHDMA | ++ NETIF_F_LLTX; ++ net_dev->hw_features = net_dev->features; ++ ++ return 0; ++} ++ ++static int dpaa2_eth_set_addr(struct net_device *net_dev, void *addr) ++{ ++ struct dpaa2_eth_priv *priv = netdev_priv(net_dev); ++ struct device *dev = net_dev->dev.parent; ++ int err; ++ ++ err = eth_mac_addr(net_dev, addr); ++ if (err < 0) { ++ dev_err(dev, "eth_mac_addr() failed with error %d\n", err); ++ return err; ++ } ++ ++ err = dpni_set_primary_mac_addr(priv->mc_io, 0, priv->mc_token, ++ net_dev->dev_addr); ++ if (err) { ++ dev_err(dev, "dpni_set_primary_mac_addr() failed (%d)\n", err); ++ return err; ++ } ++ ++ return 0; ++} ++ ++/** Fill in counters maintained by the GPP driver. These may be different from ++ * the hardware counters obtained by ethtool. ++ */ ++static struct rtnl_link_stats64 ++*dpaa2_eth_get_stats(struct net_device *net_dev, ++ struct rtnl_link_stats64 *stats) ++{ ++ struct dpaa2_eth_priv *priv = netdev_priv(net_dev); ++ struct rtnl_link_stats64 *percpu_stats; ++ u64 *cpustats; ++ u64 *netstats = (u64 *)stats; ++ int i, j; ++ int num = sizeof(struct rtnl_link_stats64) / sizeof(u64); ++ ++ for_each_possible_cpu(i) { ++ percpu_stats = per_cpu_ptr(priv->percpu_stats, i); ++ cpustats = (u64 *)percpu_stats; ++ for (j = 0; j < num; j++) ++ netstats[j] += cpustats[j]; ++ } ++ ++ return stats; ++} ++ ++static int dpaa2_eth_change_mtu(struct net_device *net_dev, int mtu) ++{ ++ struct dpaa2_eth_priv *priv = netdev_priv(net_dev); ++ int err; ++ ++ if (mtu < 68 || mtu > DPAA2_ETH_MAX_MTU) { ++ netdev_err(net_dev, "Invalid MTU %d. Valid range is: 68..%d\n", ++ mtu, DPAA2_ETH_MAX_MTU); ++ return -EINVAL; ++ } ++ ++ /* Set the maximum Rx frame length to match the transmit side; ++ * account for L2 headers when computing the MFL ++ */ ++ err = dpni_set_max_frame_length(priv->mc_io, 0, priv->mc_token, ++ (u16)DPAA2_ETH_L2_MAX_FRM(mtu)); ++ if (err) { ++ netdev_err(net_dev, "dpni_set_max_frame_length() failed\n"); ++ return err; ++ } ++ ++ net_dev->mtu = mtu; ++ return 0; ++} ++ ++/* Copy mac unicast addresses from @net_dev to @priv. ++ * Its sole purpose is to make dpaa2_eth_set_rx_mode() more readable. ++ */ ++static void add_uc_hw_addr(const struct net_device *net_dev, ++ struct dpaa2_eth_priv *priv) ++{ ++ struct netdev_hw_addr *ha; ++ int err; ++ ++ netdev_for_each_uc_addr(ha, net_dev) { ++ err = dpni_add_mac_addr(priv->mc_io, 0, priv->mc_token, ++ ha->addr); ++ if (err) ++ netdev_warn(priv->net_dev, ++ "Could not add ucast MAC %pM to the filtering table (err %d)\n", ++ ha->addr, err); ++ } ++} ++ ++/* Copy mac multicast addresses from @net_dev to @priv ++ * Its sole purpose is to make dpaa2_eth_set_rx_mode() more readable. ++ */ ++static void add_mc_hw_addr(const struct net_device *net_dev, ++ struct dpaa2_eth_priv *priv) ++{ ++ struct netdev_hw_addr *ha; ++ int err; ++ ++ netdev_for_each_mc_addr(ha, net_dev) { ++ err = dpni_add_mac_addr(priv->mc_io, 0, priv->mc_token, ++ ha->addr); ++ if (err) ++ netdev_warn(priv->net_dev, ++ "Could not add mcast MAC %pM to the filtering table (err %d)\n", ++ ha->addr, err); ++ } ++} ++ ++static void dpaa2_eth_set_rx_mode(struct net_device *net_dev) ++{ ++ struct dpaa2_eth_priv *priv = netdev_priv(net_dev); ++ int uc_count = netdev_uc_count(net_dev); ++ int mc_count = netdev_mc_count(net_dev); ++ u8 max_uc = priv->dpni_attrs.max_unicast_filters; ++ u8 max_mc = priv->dpni_attrs.max_multicast_filters; ++ u32 options = priv->dpni_attrs.options; ++ u16 mc_token = priv->mc_token; ++ struct fsl_mc_io *mc_io = priv->mc_io; ++ int err; ++ ++ /* Basic sanity checks; these probably indicate a misconfiguration */ ++ if (!(options & DPNI_OPT_UNICAST_FILTER) && max_uc != 0) ++ netdev_info(net_dev, ++ "max_unicast_filters=%d, DPNI_OPT_UNICAST_FILTER option must be enabled\n", ++ max_uc); ++ if (!(options & DPNI_OPT_MULTICAST_FILTER) && max_mc != 0) ++ netdev_info(net_dev, ++ "max_multicast_filters=%d, DPNI_OPT_MULTICAST_FILTER option must be enabled\n", ++ max_mc); ++ ++ /* Force promiscuous if the uc or mc counts exceed our capabilities. */ ++ if (uc_count > max_uc) { ++ netdev_info(net_dev, ++ "Unicast addr count reached %d, max allowed is %d; forcing promisc\n", ++ uc_count, max_uc); ++ goto force_promisc; ++ } ++ if (mc_count > max_mc) { ++ netdev_info(net_dev, ++ "Multicast addr count reached %d, max allowed is %d; forcing promisc\n", ++ mc_count, max_mc); ++ goto force_mc_promisc; ++ } ++ ++ /* Adjust promisc settings due to flag combinations */ ++ if (net_dev->flags & IFF_PROMISC) ++ goto force_promisc; ++ if (net_dev->flags & IFF_ALLMULTI) { ++ /* First, rebuild unicast filtering table. This should be done ++ * in promisc mode, in order to avoid frame loss while we ++ * progressively add entries to the table. ++ * We don't know whether we had been in promisc already, and ++ * making an MC call to find out is expensive; so set uc promisc ++ * nonetheless. ++ */ ++ err = dpni_set_unicast_promisc(mc_io, 0, mc_token, 1); ++ if (err) ++ netdev_warn(net_dev, "Can't set uc promisc\n"); ++ ++ /* Actual uc table reconstruction. */ ++ err = dpni_clear_mac_filters(mc_io, 0, mc_token, 1, 0); ++ if (err) ++ netdev_warn(net_dev, "Can't clear uc filters\n"); ++ add_uc_hw_addr(net_dev, priv); ++ ++ /* Finally, clear uc promisc and set mc promisc as requested. */ ++ err = dpni_set_unicast_promisc(mc_io, 0, mc_token, 0); ++ if (err) ++ netdev_warn(net_dev, "Can't clear uc promisc\n"); ++ goto force_mc_promisc; ++ } ++ ++ /* Neither unicast, nor multicast promisc will be on... eventually. ++ * For now, rebuild mac filtering tables while forcing both of them on. ++ */ ++ err = dpni_set_unicast_promisc(mc_io, 0, mc_token, 1); ++ if (err) ++ netdev_warn(net_dev, "Can't set uc promisc (%d)\n", err); ++ err = dpni_set_multicast_promisc(mc_io, 0, mc_token, 1); ++ if (err) ++ netdev_warn(net_dev, "Can't set mc promisc (%d)\n", err); ++ ++ /* Actual mac filtering tables reconstruction */ ++ err = dpni_clear_mac_filters(mc_io, 0, mc_token, 1, 1); ++ if (err) ++ netdev_warn(net_dev, "Can't clear mac filters\n"); ++ add_mc_hw_addr(net_dev, priv); ++ add_uc_hw_addr(net_dev, priv); ++ ++ /* Now we can clear both ucast and mcast promisc, without risking ++ * to drop legitimate frames anymore. ++ */ ++ err = dpni_set_unicast_promisc(mc_io, 0, mc_token, 0); ++ if (err) ++ netdev_warn(net_dev, "Can't clear ucast promisc\n"); ++ err = dpni_set_multicast_promisc(mc_io, 0, mc_token, 0); ++ if (err) ++ netdev_warn(net_dev, "Can't clear mcast promisc\n"); ++ ++ return; ++ ++force_promisc: ++ err = dpni_set_unicast_promisc(mc_io, 0, mc_token, 1); ++ if (err) ++ netdev_warn(net_dev, "Can't set ucast promisc\n"); ++force_mc_promisc: ++ err = dpni_set_multicast_promisc(mc_io, 0, mc_token, 1); ++ if (err) ++ netdev_warn(net_dev, "Can't set mcast promisc\n"); ++} ++ ++static int dpaa2_eth_set_features(struct net_device *net_dev, ++ netdev_features_t features) ++{ ++ struct dpaa2_eth_priv *priv = netdev_priv(net_dev); ++ netdev_features_t changed = features ^ net_dev->features; ++ bool enable; ++ int err; ++ ++ if (changed & NETIF_F_RXCSUM) { ++ enable = !!(features & NETIF_F_RXCSUM); ++ err = set_rx_csum(priv, enable); ++ if (err) ++ return err; ++ } ++ ++ if (changed & (NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM)) { ++ enable = !!(features & (NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM)); ++ err = set_tx_csum(priv, enable); ++ if (err) ++ return err; ++ } ++ ++ return 0; ++} ++ ++static int dpaa2_eth_ts_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) ++{ ++ struct dpaa2_eth_priv *priv = netdev_priv(dev); ++ struct hwtstamp_config config; ++ ++ if (copy_from_user(&config, rq->ifr_data, sizeof(config))) ++ return -EFAULT; ++ ++ switch (config.tx_type) { ++ case HWTSTAMP_TX_OFF: ++ priv->ts_tx_en = false; ++ break; ++ case HWTSTAMP_TX_ON: ++ priv->ts_tx_en = true; ++ break; ++ default: ++ return -ERANGE; ++ } ++ ++ if (config.rx_filter == HWTSTAMP_FILTER_NONE) { ++ priv->ts_rx_en = false; ++ } else { ++ priv->ts_rx_en = true; ++ /* TS is set for all frame types, not only those requested */ ++ config.rx_filter = HWTSTAMP_FILTER_ALL; ++ } ++ ++ return copy_to_user(rq->ifr_data, &config, sizeof(config)) ? ++ -EFAULT : 0; ++} ++ ++static int dpaa2_eth_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) ++{ ++ if (cmd == SIOCSHWTSTAMP) ++ return dpaa2_eth_ts_ioctl(dev, rq, cmd); ++ ++ return -EINVAL; ++} ++ ++static const struct net_device_ops dpaa2_eth_ops = { ++ .ndo_open = dpaa2_eth_open, ++ .ndo_start_xmit = dpaa2_eth_tx, ++ .ndo_stop = dpaa2_eth_stop, ++ .ndo_init = dpaa2_eth_init, ++ .ndo_set_mac_address = dpaa2_eth_set_addr, ++ .ndo_get_stats64 = dpaa2_eth_get_stats, ++ .ndo_change_mtu = dpaa2_eth_change_mtu, ++ .ndo_set_rx_mode = dpaa2_eth_set_rx_mode, ++ .ndo_set_features = dpaa2_eth_set_features, ++ .ndo_do_ioctl = dpaa2_eth_ioctl, ++}; ++ ++static void cdan_cb(struct dpaa2_io_notification_ctx *ctx) ++{ ++ struct dpaa2_eth_channel *ch; ++ ++ ch = container_of(ctx, struct dpaa2_eth_channel, nctx); ++ ++ /* Update NAPI statistics */ ++ ch->stats.cdan++; ++ ++ napi_schedule(&ch->napi); ++} ++ ++/* Allocate and configure a DPCON object */ ++static struct fsl_mc_device *setup_dpcon(struct dpaa2_eth_priv *priv) ++{ ++ struct fsl_mc_device *dpcon; ++ struct device *dev = priv->net_dev->dev.parent; ++ struct dpcon_attr attrs; ++ int err; ++ ++ err = fsl_mc_object_allocate(to_fsl_mc_device(dev), ++ FSL_MC_POOL_DPCON, &dpcon); ++ if (err) { ++ dev_info(dev, "Not enough DPCONs, will go on as-is\n"); ++ return NULL; ++ } ++ ++ err = dpcon_open(priv->mc_io, 0, dpcon->obj_desc.id, &dpcon->mc_handle); ++ if (err) { ++ dev_err(dev, "dpcon_open() failed\n"); ++ goto err_open; ++ } ++ ++ err = dpcon_reset(priv->mc_io, 0, dpcon->mc_handle); ++ if (err) { ++ dev_err(dev, "dpcon_reset() failed\n"); ++ goto err_reset; ++ } ++ ++ err = dpcon_get_attributes(priv->mc_io, 0, dpcon->mc_handle, &attrs); ++ if (err) { ++ dev_err(dev, "dpcon_get_attributes() failed\n"); ++ goto err_get_attr; ++ } ++ ++ err = dpcon_enable(priv->mc_io, 0, dpcon->mc_handle); ++ if (err) { ++ dev_err(dev, "dpcon_enable() failed\n"); ++ goto err_enable; ++ } ++ ++ return dpcon; ++ ++err_enable: ++err_get_attr: ++err_reset: ++ dpcon_close(priv->mc_io, 0, dpcon->mc_handle); ++err_open: ++ fsl_mc_object_free(dpcon); ++ ++ return NULL; ++} ++ ++static void free_dpcon(struct dpaa2_eth_priv *priv, ++ struct fsl_mc_device *dpcon) ++{ ++ dpcon_disable(priv->mc_io, 0, dpcon->mc_handle); ++ dpcon_close(priv->mc_io, 0, dpcon->mc_handle); ++ fsl_mc_object_free(dpcon); ++} ++ ++static struct dpaa2_eth_channel * ++alloc_channel(struct dpaa2_eth_priv *priv) ++{ ++ struct dpaa2_eth_channel *channel; ++ struct dpcon_attr attr; ++ struct device *dev = priv->net_dev->dev.parent; ++ int err; ++ ++ channel = kzalloc(sizeof(*channel), GFP_ATOMIC); ++ if (!channel) ++ return NULL; ++ ++ channel->dpcon = setup_dpcon(priv); ++ if (!channel->dpcon) ++ goto err_setup; ++ ++ err = dpcon_get_attributes(priv->mc_io, 0, channel->dpcon->mc_handle, ++ &attr); ++ if (err) { ++ dev_err(dev, "dpcon_get_attributes() failed\n"); ++ goto err_get_attr; ++ } ++ ++ channel->dpcon_id = attr.id; ++ channel->ch_id = attr.qbman_ch_id; ++ channel->priv = priv; ++ ++ return channel; ++ ++err_get_attr: ++ free_dpcon(priv, channel->dpcon); ++err_setup: ++ kfree(channel); ++ return NULL; ++} ++ ++static void free_channel(struct dpaa2_eth_priv *priv, ++ struct dpaa2_eth_channel *channel) ++{ ++ free_dpcon(priv, channel->dpcon); ++ kfree(channel); ++} ++ ++/* DPIO setup: allocate and configure QBMan channels, setup core affinity ++ * and register data availability notifications ++ */ ++static int setup_dpio(struct dpaa2_eth_priv *priv) ++{ ++ struct dpaa2_io_notification_ctx *nctx; ++ struct dpaa2_eth_channel *channel; ++ struct dpcon_notification_cfg dpcon_notif_cfg; ++ struct device *dev = priv->net_dev->dev.parent; ++ int i, err; ++ ++ /* Don't allocate more channels than strictly necessary and assign ++ * them to cores starting from the first one available in ++ * cpu_online_mask. ++ * If the number of channels is lower than the number of cores, ++ * there will be no rx/tx conf processing on the last cores in the mask. ++ */ ++ cpumask_clear(&priv->dpio_cpumask); ++ for_each_online_cpu(i) { ++ /* Try to allocate a channel */ ++ channel = alloc_channel(priv); ++ if (!channel) ++ goto err_alloc_ch; ++ ++ priv->channel[priv->num_channels] = channel; ++ ++ nctx = &channel->nctx; ++ nctx->is_cdan = 1; ++ nctx->cb = cdan_cb; ++ nctx->id = channel->ch_id; ++ nctx->desired_cpu = i; ++ ++ /* Register the new context */ ++ err = dpaa2_io_service_register(NULL, nctx); ++ if (err) { ++ dev_info(dev, "No affine DPIO for core %d\n", i); ++ /* This core doesn't have an affine DPIO, but there's ++ * a chance another one does, so keep trying ++ */ ++ free_channel(priv, channel); ++ continue; ++ } ++ ++ /* Register DPCON notification with MC */ ++ dpcon_notif_cfg.dpio_id = nctx->dpio_id; ++ dpcon_notif_cfg.priority = 0; ++ dpcon_notif_cfg.user_ctx = nctx->qman64; ++ err = dpcon_set_notification(priv->mc_io, 0, ++ channel->dpcon->mc_handle, ++ &dpcon_notif_cfg); ++ if (err) { ++ dev_err(dev, "dpcon_set_notification failed()\n"); ++ goto err_set_cdan; ++ } ++ ++ /* If we managed to allocate a channel and also found an affine ++ * DPIO for this core, add it to the final mask ++ */ ++ cpumask_set_cpu(i, &priv->dpio_cpumask); ++ priv->num_channels++; ++ ++ if (priv->num_channels == dpaa2_eth_max_channels(priv)) ++ break; ++ } ++ ++ /* Tx confirmation queues can only be serviced by cpus ++ * with an affine DPIO/channel ++ */ ++ cpumask_copy(&priv->txconf_cpumask, &priv->dpio_cpumask); ++ ++ return 0; ++ ++err_set_cdan: ++ dpaa2_io_service_deregister(NULL, nctx); ++ free_channel(priv, channel); ++err_alloc_ch: ++ if (cpumask_empty(&priv->dpio_cpumask)) { ++ dev_err(dev, "No cpu with an affine DPIO/DPCON\n"); ++ return -ENODEV; ++ } ++ cpumask_copy(&priv->txconf_cpumask, &priv->dpio_cpumask); ++ ++ return 0; ++} ++ ++static void free_dpio(struct dpaa2_eth_priv *priv) ++{ ++ int i; ++ struct dpaa2_eth_channel *ch; ++ ++ /* deregister CDAN notifications and free channels */ ++ for (i = 0; i < priv->num_channels; i++) { ++ ch = priv->channel[i]; ++ dpaa2_io_service_deregister(NULL, &ch->nctx); ++ free_channel(priv, ch); ++ } ++} ++ ++static struct dpaa2_eth_channel *get_affine_channel(struct dpaa2_eth_priv *priv, ++ int cpu) ++{ ++ struct device *dev = priv->net_dev->dev.parent; ++ int i; ++ ++ for (i = 0; i < priv->num_channels; i++) ++ if (priv->channel[i]->nctx.desired_cpu == cpu) ++ return priv->channel[i]; ++ ++ /* We should never get here. Issue a warning and return ++ * the first channel, because it's still better than nothing ++ */ ++ dev_warn(dev, "No affine channel found for cpu %d\n", cpu); ++ ++ return priv->channel[0]; ++} ++ ++static void set_fq_affinity(struct dpaa2_eth_priv *priv) ++{ ++ struct device *dev = priv->net_dev->dev.parent; ++ struct dpaa2_eth_fq *fq; ++ int rx_cpu, txc_cpu; ++ int i; ++ ++ /* For each FQ, pick one channel/CPU to deliver frames to. ++ * This may well change at runtime, either through irqbalance or ++ * through direct user intervention. ++ */ ++ rx_cpu = cpumask_first(&priv->dpio_cpumask); ++ txc_cpu = cpumask_first(&priv->txconf_cpumask); ++ ++ for (i = 0; i < priv->num_fqs; i++) { ++ fq = &priv->fq[i]; ++ switch (fq->type) { ++ case DPAA2_RX_FQ: ++ case DPAA2_RX_ERR_FQ: ++ fq->target_cpu = rx_cpu; ++ rx_cpu = cpumask_next(rx_cpu, &priv->dpio_cpumask); ++ if (rx_cpu >= nr_cpu_ids) ++ rx_cpu = cpumask_first(&priv->dpio_cpumask); ++ break; ++ case DPAA2_TX_CONF_FQ: ++ fq->target_cpu = txc_cpu; ++ txc_cpu = cpumask_next(txc_cpu, &priv->txconf_cpumask); ++ if (txc_cpu >= nr_cpu_ids) ++ txc_cpu = cpumask_first(&priv->txconf_cpumask); ++ break; ++ default: ++ dev_err(dev, "Unknown FQ type: %d\n", fq->type); ++ } ++ fq->channel = get_affine_channel(priv, fq->target_cpu); ++ } ++} ++ ++static void setup_fqs(struct dpaa2_eth_priv *priv) ++{ ++ int i; ++ ++ /* We have one TxConf FQ per Tx flow */ ++ for (i = 0; i < priv->dpni_attrs.max_senders; i++) { ++ priv->fq[priv->num_fqs].type = DPAA2_TX_CONF_FQ; ++ priv->fq[priv->num_fqs].consume = dpaa2_eth_tx_conf; ++ priv->fq[priv->num_fqs++].flowid = DPNI_NEW_FLOW_ID; ++ } ++ ++ /* The number of Rx queues (Rx distribution width) may be different from ++ * the number of cores. ++ * We only support one traffic class for now. ++ */ ++ for (i = 0; i < dpaa2_eth_queue_count(priv); i++) { ++ priv->fq[priv->num_fqs].type = DPAA2_RX_FQ; ++ priv->fq[priv->num_fqs].consume = dpaa2_eth_rx; ++ priv->fq[priv->num_fqs++].flowid = (u16)i; ++ } ++ ++#ifdef CONFIG_FSL_DPAA2_ETH_USE_ERR_QUEUE ++ /* We have exactly one Rx error queue per DPNI */ ++ priv->fq[priv->num_fqs].type = DPAA2_RX_ERR_FQ; ++ priv->fq[priv->num_fqs++].consume = dpaa2_eth_rx_err; ++#endif ++ ++ /* For each FQ, decide on which core to process incoming frames */ ++ set_fq_affinity(priv); ++} ++ ++/* Allocate and configure one buffer pool for each interface */ ++static int setup_dpbp(struct dpaa2_eth_priv *priv) ++{ ++ int err; ++ struct fsl_mc_device *dpbp_dev; ++ struct device *dev = priv->net_dev->dev.parent; ++ ++ err = fsl_mc_object_allocate(to_fsl_mc_device(dev), FSL_MC_POOL_DPBP, ++ &dpbp_dev); ++ if (err) { ++ dev_err(dev, "DPBP device allocation failed\n"); ++ return err; ++ } ++ ++ priv->dpbp_dev = dpbp_dev; ++ ++ err = dpbp_open(priv->mc_io, 0, priv->dpbp_dev->obj_desc.id, ++ &dpbp_dev->mc_handle); ++ if (err) { ++ dev_err(dev, "dpbp_open() failed\n"); ++ goto err_open; ++ } ++ ++ err = dpbp_reset(priv->mc_io, 0, dpbp_dev->mc_handle); ++ if (err) { ++ dev_err(dev, "dpbp_reset() failed\n"); ++ goto err_reset; ++ } ++ ++ err = dpbp_enable(priv->mc_io, 0, dpbp_dev->mc_handle); ++ if (err) { ++ dev_err(dev, "dpbp_enable() failed\n"); ++ goto err_enable; ++ } ++ ++ err = dpbp_get_attributes(priv->mc_io, 0, dpbp_dev->mc_handle, ++ &priv->dpbp_attrs); ++ if (err) { ++ dev_err(dev, "dpbp_get_attributes() failed\n"); ++ goto err_get_attr; ++ } ++ ++ return 0; ++ ++err_get_attr: ++ dpbp_disable(priv->mc_io, 0, dpbp_dev->mc_handle); ++err_enable: ++err_reset: ++ dpbp_close(priv->mc_io, 0, dpbp_dev->mc_handle); ++err_open: ++ fsl_mc_object_free(dpbp_dev); ++ ++ return err; ++} ++ ++static void free_dpbp(struct dpaa2_eth_priv *priv) ++{ ++ drain_pool(priv); ++ dpbp_disable(priv->mc_io, 0, priv->dpbp_dev->mc_handle); ++ dpbp_close(priv->mc_io, 0, priv->dpbp_dev->mc_handle); ++ fsl_mc_object_free(priv->dpbp_dev); ++} ++ ++/* Configure the DPNI object this interface is associated with */ ++static int setup_dpni(struct fsl_mc_device *ls_dev) ++{ ++ struct device *dev = &ls_dev->dev; ++ struct dpaa2_eth_priv *priv; ++ struct net_device *net_dev; ++ void *dma_mem; ++ int err; ++ ++ net_dev = dev_get_drvdata(dev); ++ priv = netdev_priv(net_dev); ++ ++ priv->dpni_id = ls_dev->obj_desc.id; ++ ++ /* get a handle for the DPNI object */ ++ err = dpni_open(priv->mc_io, 0, priv->dpni_id, &priv->mc_token); ++ if (err) { ++ dev_err(dev, "dpni_open() failed\n"); ++ goto err_open; ++ } ++ ++ ls_dev->mc_io = priv->mc_io; ++ ls_dev->mc_handle = priv->mc_token; ++ ++ err = dpni_reset(priv->mc_io, 0, priv->mc_token); ++ if (err) { ++ dev_err(dev, "dpni_reset() failed\n"); ++ goto err_reset; ++ } ++ ++ /* Map a memory region which will be used by MC to pass us an ++ * attribute structure ++ */ ++ dma_mem = kzalloc(DPAA2_EXT_CFG_SIZE, GFP_DMA | GFP_KERNEL); ++ if (!dma_mem) ++ goto err_alloc; ++ ++ priv->dpni_attrs.ext_cfg_iova = dma_map_single(dev, dma_mem, ++ DPAA2_EXT_CFG_SIZE, ++ DMA_FROM_DEVICE); ++ if (dma_mapping_error(dev, priv->dpni_attrs.ext_cfg_iova)) { ++ dev_err(dev, "dma mapping for dpni_ext_cfg failed\n"); ++ goto err_dma_map; ++ } ++ ++ err = dpni_get_attributes(priv->mc_io, 0, priv->mc_token, ++ &priv->dpni_attrs); ++ ++ /* We'll check the return code after unmapping, as we need to ++ * do this anyway ++ */ ++ dma_unmap_single(dev, priv->dpni_attrs.ext_cfg_iova, ++ DPAA2_EXT_CFG_SIZE, DMA_FROM_DEVICE); ++ ++ if (err) { ++ dev_err(dev, "dpni_get_attributes() failed (err=%d)\n", err); ++ goto err_get_attr; ++ } ++ ++ memset(&priv->dpni_ext_cfg, 0, sizeof(priv->dpni_ext_cfg)); ++ err = dpni_extract_extended_cfg(&priv->dpni_ext_cfg, dma_mem); ++ if (err) { ++ dev_err(dev, "dpni_extract_extended_cfg() failed\n"); ++ goto err_extract; ++ } ++ ++ /* Configure our buffers' layout */ ++ priv->buf_layout.options = DPNI_BUF_LAYOUT_OPT_PARSER_RESULT | ++ DPNI_BUF_LAYOUT_OPT_FRAME_STATUS | ++ DPNI_BUF_LAYOUT_OPT_PRIVATE_DATA_SIZE | ++ DPNI_BUF_LAYOUT_OPT_DATA_ALIGN; ++ priv->buf_layout.pass_parser_result = true; ++ priv->buf_layout.pass_frame_status = true; ++ priv->buf_layout.private_data_size = DPAA2_ETH_SWA_SIZE; ++ /* HW erratum mandates data alignment in multiples of 256 */ ++ priv->buf_layout.data_align = DPAA2_ETH_RX_BUF_ALIGN; ++ ++ /* rx buffer */ ++ err = dpni_set_rx_buffer_layout(priv->mc_io, 0, priv->mc_token, ++ &priv->buf_layout); ++ if (err) { ++ dev_err(dev, "dpni_set_rx_buffer_layout() failed"); ++ goto err_buf_layout; ++ } ++ /* tx buffer: remove Rx-only options */ ++ priv->buf_layout.options &= ~(DPNI_BUF_LAYOUT_OPT_DATA_ALIGN | ++ DPNI_BUF_LAYOUT_OPT_PARSER_RESULT); ++ err = dpni_set_tx_buffer_layout(priv->mc_io, 0, priv->mc_token, ++ &priv->buf_layout); ++ if (err) { ++ dev_err(dev, "dpni_set_tx_buffer_layout() failed"); ++ goto err_buf_layout; ++ } ++ /* tx-confirm: same options as tx */ ++ priv->buf_layout.options &= ~DPNI_BUF_LAYOUT_OPT_PRIVATE_DATA_SIZE; ++ priv->buf_layout.options |= DPNI_BUF_LAYOUT_OPT_TIMESTAMP; ++ priv->buf_layout.pass_timestamp = 1; ++ err = dpni_set_tx_conf_buffer_layout(priv->mc_io, 0, priv->mc_token, ++ &priv->buf_layout); ++ if (err) { ++ dev_err(dev, "dpni_set_tx_conf_buffer_layout() failed"); ++ goto err_buf_layout; ++ } ++ /* Now that we've set our tx buffer layout, retrieve the minimum ++ * required tx data offset. ++ */ ++ err = dpni_get_tx_data_offset(priv->mc_io, 0, priv->mc_token, ++ &priv->tx_data_offset); ++ if (err) { ++ dev_err(dev, "dpni_get_tx_data_offset() failed\n"); ++ goto err_data_offset; ++ } ++ ++ if ((priv->tx_data_offset % 64) != 0) ++ dev_warn(dev, "Tx data offset (%d) not a multiple of 64B", ++ priv->tx_data_offset); ++ ++ /* Accommodate SWA space. */ ++ priv->tx_data_offset += DPAA2_ETH_SWA_SIZE; ++ ++ /* allocate classification rule space */ ++ priv->cls_rule = kzalloc(sizeof(*priv->cls_rule) * ++ DPAA2_CLASSIFIER_ENTRY_COUNT, GFP_KERNEL); ++ if (!priv->cls_rule) ++ goto err_cls_rule; ++ ++ kfree(dma_mem); ++ ++ return 0; ++ ++err_cls_rule: ++err_data_offset: ++err_buf_layout: ++err_extract: ++err_get_attr: ++err_dma_map: ++ kfree(dma_mem); ++err_alloc: ++err_reset: ++ dpni_close(priv->mc_io, 0, priv->mc_token); ++err_open: ++ return err; ++} ++ ++static void free_dpni(struct dpaa2_eth_priv *priv) ++{ ++ int err; ++ ++ err = dpni_reset(priv->mc_io, 0, priv->mc_token); ++ if (err) ++ netdev_warn(priv->net_dev, "dpni_reset() failed (err %d)\n", ++ err); ++ ++ dpni_close(priv->mc_io, 0, priv->mc_token); ++} ++ ++static int setup_rx_flow(struct dpaa2_eth_priv *priv, ++ struct dpaa2_eth_fq *fq) ++{ ++ struct device *dev = priv->net_dev->dev.parent; ++ struct dpni_queue_attr rx_queue_attr; ++ struct dpni_queue_cfg queue_cfg; ++ int err; ++ ++ memset(&queue_cfg, 0, sizeof(queue_cfg)); ++ queue_cfg.options = DPNI_QUEUE_OPT_USER_CTX | DPNI_QUEUE_OPT_DEST | ++ DPNI_QUEUE_OPT_TAILDROP_THRESHOLD; ++ queue_cfg.dest_cfg.dest_type = DPNI_DEST_DPCON; ++ queue_cfg.dest_cfg.priority = 1; ++ queue_cfg.user_ctx = (u64)fq; ++ queue_cfg.dest_cfg.dest_id = fq->channel->dpcon_id; ++ queue_cfg.tail_drop_threshold = DPAA2_ETH_TAILDROP_THRESH; ++ err = dpni_set_rx_flow(priv->mc_io, 0, priv->mc_token, 0, fq->flowid, ++ &queue_cfg); ++ if (err) { ++ dev_err(dev, "dpni_set_rx_flow() failed\n"); ++ return err; ++ } ++ ++ /* Get the actual FQID that was assigned by MC */ ++ err = dpni_get_rx_flow(priv->mc_io, 0, priv->mc_token, 0, fq->flowid, ++ &rx_queue_attr); ++ if (err) { ++ dev_err(dev, "dpni_get_rx_flow() failed\n"); ++ return err; ++ } ++ fq->fqid = rx_queue_attr.fqid; ++ ++ return 0; ++} ++ ++static int setup_tx_flow(struct dpaa2_eth_priv *priv, ++ struct dpaa2_eth_fq *fq) ++{ ++ struct device *dev = priv->net_dev->dev.parent; ++ struct dpni_tx_flow_cfg tx_flow_cfg; ++ struct dpni_tx_conf_cfg tx_conf_cfg; ++ struct dpni_tx_conf_attr tx_conf_attr; ++ int err; ++ ++ memset(&tx_flow_cfg, 0, sizeof(tx_flow_cfg)); ++ tx_flow_cfg.options = DPNI_TX_FLOW_OPT_TX_CONF_ERROR; ++ tx_flow_cfg.use_common_tx_conf_queue = 0; ++ err = dpni_set_tx_flow(priv->mc_io, 0, priv->mc_token, ++ &fq->flowid, &tx_flow_cfg); ++ if (err) { ++ dev_err(dev, "dpni_set_tx_flow() failed\n"); ++ return err; ++ } ++ ++ tx_conf_cfg.errors_only = 0; ++ tx_conf_cfg.queue_cfg.options = DPNI_QUEUE_OPT_USER_CTX | ++ DPNI_QUEUE_OPT_DEST; ++ tx_conf_cfg.queue_cfg.user_ctx = (u64)fq; ++ tx_conf_cfg.queue_cfg.dest_cfg.dest_type = DPNI_DEST_DPCON; ++ tx_conf_cfg.queue_cfg.dest_cfg.dest_id = fq->channel->dpcon_id; ++ tx_conf_cfg.queue_cfg.dest_cfg.priority = 0; ++ ++ err = dpni_set_tx_conf(priv->mc_io, 0, priv->mc_token, fq->flowid, ++ &tx_conf_cfg); ++ if (err) { ++ dev_err(dev, "dpni_set_tx_conf() failed\n"); ++ return err; ++ } ++ ++ err = dpni_get_tx_conf(priv->mc_io, 0, priv->mc_token, fq->flowid, ++ &tx_conf_attr); ++ if (err) { ++ dev_err(dev, "dpni_get_tx_conf() failed\n"); ++ return err; ++ } ++ ++ fq->fqid = tx_conf_attr.queue_attr.fqid; ++ ++ return 0; ++} ++ ++#ifdef CONFIG_FSL_DPAA2_ETH_USE_ERR_QUEUE ++static int setup_rx_err_flow(struct dpaa2_eth_priv *priv, ++ struct dpaa2_eth_fq *fq) ++{ ++ struct dpni_queue_attr queue_attr; ++ struct dpni_queue_cfg queue_cfg; ++ int err; ++ ++ /* Configure the Rx error queue to generate CDANs, ++ * just like the Rx queues ++ */ ++ queue_cfg.options = DPNI_QUEUE_OPT_USER_CTX | DPNI_QUEUE_OPT_DEST; ++ queue_cfg.dest_cfg.dest_type = DPNI_DEST_DPCON; ++ queue_cfg.dest_cfg.priority = 1; ++ queue_cfg.user_ctx = (u64)fq; ++ queue_cfg.dest_cfg.dest_id = fq->channel->dpcon_id; ++ err = dpni_set_rx_err_queue(priv->mc_io, 0, priv->mc_token, &queue_cfg); ++ if (err) { ++ netdev_err(priv->net_dev, "dpni_set_rx_err_queue() failed\n"); ++ return err; ++ } ++ ++ /* Get the FQID */ ++ err = dpni_get_rx_err_queue(priv->mc_io, 0, priv->mc_token, ++ &queue_attr); ++ if (err) { ++ netdev_err(priv->net_dev, "dpni_get_rx_err_queue() failed\n"); ++ return err; ++ } ++ fq->fqid = queue_attr.fqid; ++ ++ return 0; ++} ++#endif ++ ++/* default hash key fields */ ++static struct dpaa2_eth_hash_fields default_hash_fields[] = { ++ { ++ /* L2 header */ ++ .rxnfc_field = RXH_L2DA, ++ .cls_prot = NET_PROT_ETH, ++ .cls_field = NH_FLD_ETH_DA, ++ .size = 6, ++ }, { ++ .cls_prot = NET_PROT_ETH, ++ .cls_field = NH_FLD_ETH_SA, ++ .size = 6, ++ }, { ++ /* This is the last ethertype field parsed: ++ * depending on frame format, it can be the MAC ethertype ++ * or the VLAN etype. ++ */ ++ .cls_prot = NET_PROT_ETH, ++ .cls_field = NH_FLD_ETH_TYPE, ++ .size = 2, ++ }, { ++ /* VLAN header */ ++ .rxnfc_field = RXH_VLAN, ++ .cls_prot = NET_PROT_VLAN, ++ .cls_field = NH_FLD_VLAN_TCI, ++ .size = 2, ++ }, { ++ /* IP header */ ++ .rxnfc_field = RXH_IP_SRC, ++ .cls_prot = NET_PROT_IP, ++ .cls_field = NH_FLD_IP_SRC, ++ .size = 4, ++ }, { ++ .rxnfc_field = RXH_IP_DST, ++ .cls_prot = NET_PROT_IP, ++ .cls_field = NH_FLD_IP_DST, ++ .size = 4, ++ }, { ++ .rxnfc_field = RXH_L3_PROTO, ++ .cls_prot = NET_PROT_IP, ++ .cls_field = NH_FLD_IP_PROTO, ++ .size = 1, ++ }, { ++ /* Using UDP ports, this is functionally equivalent to raw ++ * byte pairs from L4 header. ++ */ ++ .rxnfc_field = RXH_L4_B_0_1, ++ .cls_prot = NET_PROT_UDP, ++ .cls_field = NH_FLD_UDP_PORT_SRC, ++ .size = 2, ++ }, { ++ .rxnfc_field = RXH_L4_B_2_3, ++ .cls_prot = NET_PROT_UDP, ++ .cls_field = NH_FLD_UDP_PORT_DST, ++ .size = 2, ++ }, ++}; ++ ++/* Set RX hash options */ ++int set_hash(struct dpaa2_eth_priv *priv) ++{ ++ struct device *dev = priv->net_dev->dev.parent; ++ struct dpkg_profile_cfg cls_cfg; ++ struct dpni_rx_tc_dist_cfg dist_cfg; ++ u8 *dma_mem; ++ int i; ++ int err = 0; ++ ++ memset(&cls_cfg, 0, sizeof(cls_cfg)); ++ ++ for (i = 0; i < priv->num_hash_fields; i++) { ++ struct dpkg_extract *key = ++ &cls_cfg.extracts[cls_cfg.num_extracts]; ++ ++ key->type = DPKG_EXTRACT_FROM_HDR; ++ key->extract.from_hdr.prot = priv->hash_fields[i].cls_prot; ++ key->extract.from_hdr.type = DPKG_FULL_FIELD; ++ key->extract.from_hdr.field = priv->hash_fields[i].cls_field; ++ cls_cfg.num_extracts++; ++ ++ priv->rx_flow_hash |= priv->hash_fields[i].rxnfc_field; ++ } ++ ++ dma_mem = kzalloc(DPAA2_CLASSIFIER_DMA_SIZE, GFP_DMA | GFP_KERNEL); ++ if (!dma_mem) ++ return -ENOMEM; ++ ++ err = dpni_prepare_key_cfg(&cls_cfg, dma_mem); ++ if (err) { ++ dev_err(dev, "dpni_prepare_key_cfg error %d", err); ++ return err; ++ } ++ ++ memset(&dist_cfg, 0, sizeof(dist_cfg)); ++ ++ /* Prepare for setting the rx dist */ ++ dist_cfg.key_cfg_iova = dma_map_single(dev, dma_mem, ++ DPAA2_CLASSIFIER_DMA_SIZE, ++ DMA_TO_DEVICE); ++ if (dma_mapping_error(dev, dist_cfg.key_cfg_iova)) { ++ dev_err(dev, "DMA mapping failed\n"); ++ kfree(dma_mem); ++ return -ENOMEM; ++ } ++ ++ dist_cfg.dist_size = dpaa2_eth_queue_count(priv); ++ if (dpaa2_eth_fs_enabled(priv)) { ++ dist_cfg.dist_mode = DPNI_DIST_MODE_FS; ++ dist_cfg.fs_cfg.miss_action = DPNI_FS_MISS_HASH; ++ } else { ++ dist_cfg.dist_mode = DPNI_DIST_MODE_HASH; ++ } ++ ++ err = dpni_set_rx_tc_dist(priv->mc_io, 0, priv->mc_token, 0, &dist_cfg); ++ dma_unmap_single(dev, dist_cfg.key_cfg_iova, ++ DPAA2_CLASSIFIER_DMA_SIZE, DMA_TO_DEVICE); ++ kfree(dma_mem); ++ if (err) { ++ dev_err(dev, "dpni_set_rx_tc_dist() error %d\n", err); ++ return err; ++ } ++ ++ return 0; ++} ++ ++/* Bind the DPNI to its needed objects and resources: buffer pool, DPIOs, ++ * frame queues and channels ++ */ ++static int bind_dpni(struct dpaa2_eth_priv *priv) ++{ ++ struct net_device *net_dev = priv->net_dev; ++ struct device *dev = net_dev->dev.parent; ++ struct dpni_pools_cfg pools_params; ++ struct dpni_error_cfg err_cfg; ++ int err = 0; ++ int i; ++ ++ pools_params.num_dpbp = 1; ++ pools_params.pools[0].dpbp_id = priv->dpbp_dev->obj_desc.id; ++ pools_params.pools[0].backup_pool = 0; ++ pools_params.pools[0].buffer_size = DPAA2_ETH_RX_BUF_SIZE; ++ err = dpni_set_pools(priv->mc_io, 0, priv->mc_token, &pools_params); ++ if (err) { ++ dev_err(dev, "dpni_set_pools() failed\n"); ++ return err; ++ } ++ ++ /* Verify classification options and disable hashing and/or ++ * flow steering support in case of invalid configuration values ++ */ ++ check_cls_support(priv); ++ ++ /* have the interface implicitly distribute traffic based on ++ * a static hash key ++ */ ++ if (dpaa2_eth_hash_enabled(priv)) { ++ priv->hash_fields = default_hash_fields; ++ priv->num_hash_fields = ARRAY_SIZE(default_hash_fields); ++ err = set_hash(priv); ++ if (err) { ++ dev_err(dev, "Hashing configuration failed\n"); ++ return err; ++ } ++ } ++ ++ /* Configure handling of error frames */ ++ err_cfg.errors = DPAA2_ETH_RX_ERR_MASK; ++ err_cfg.set_frame_annotation = 1; ++#ifdef CONFIG_FSL_DPAA2_ETH_USE_ERR_QUEUE ++ err_cfg.error_action = DPNI_ERROR_ACTION_SEND_TO_ERROR_QUEUE; ++#else ++ err_cfg.error_action = DPNI_ERROR_ACTION_DISCARD; ++#endif ++ err = dpni_set_errors_behavior(priv->mc_io, 0, priv->mc_token, ++ &err_cfg); ++ if (err) { ++ dev_err(dev, "dpni_set_errors_behavior failed\n"); ++ return err; ++ } ++ ++ /* Configure Rx and Tx conf queues to generate CDANs */ ++ for (i = 0; i < priv->num_fqs; i++) { ++ switch (priv->fq[i].type) { ++ case DPAA2_RX_FQ: ++ err = setup_rx_flow(priv, &priv->fq[i]); ++ break; ++ case DPAA2_TX_CONF_FQ: ++ err = setup_tx_flow(priv, &priv->fq[i]); ++ break; ++#ifdef CONFIG_FSL_DPAA2_ETH_USE_ERR_QUEUE ++ case DPAA2_RX_ERR_FQ: ++ err = setup_rx_err_flow(priv, &priv->fq[i]); ++ break; ++#endif ++ default: ++ dev_err(dev, "Invalid FQ type %d\n", priv->fq[i].type); ++ return -EINVAL; ++ } ++ if (err) ++ return err; ++ } ++ ++ err = dpni_get_qdid(priv->mc_io, 0, priv->mc_token, &priv->tx_qdid); ++ if (err) { ++ dev_err(dev, "dpni_get_qdid() failed\n"); ++ return err; ++ } ++ ++ return 0; ++} ++ ++/* Allocate rings for storing incoming frame descriptors */ ++static int alloc_rings(struct dpaa2_eth_priv *priv) ++{ ++ struct net_device *net_dev = priv->net_dev; ++ struct device *dev = net_dev->dev.parent; ++ int i; ++ ++ for (i = 0; i < priv->num_channels; i++) { ++ priv->channel[i]->store = ++ dpaa2_io_store_create(DPAA2_ETH_STORE_SIZE, dev); ++ if (!priv->channel[i]->store) { ++ netdev_err(net_dev, "dpaa2_io_store_create() failed\n"); ++ goto err_ring; ++ } ++ } ++ ++ return 0; ++ ++err_ring: ++ for (i = 0; i < priv->num_channels; i++) { ++ if (!priv->channel[i]->store) ++ break; ++ dpaa2_io_store_destroy(priv->channel[i]->store); ++ } ++ ++ return -ENOMEM; ++} ++ ++static void free_rings(struct dpaa2_eth_priv *priv) ++{ ++ int i; ++ ++ for (i = 0; i < priv->num_channels; i++) ++ dpaa2_io_store_destroy(priv->channel[i]->store); ++} ++ ++static int netdev_init(struct net_device *net_dev) ++{ ++ int err; ++ struct device *dev = net_dev->dev.parent; ++ struct dpaa2_eth_priv *priv = netdev_priv(net_dev); ++ u8 mac_addr[ETH_ALEN]; ++ u8 bcast_addr[ETH_ALEN]; ++ ++ net_dev->netdev_ops = &dpaa2_eth_ops; ++ ++ /* If the DPNI attributes contain an all-0 mac_addr, ++ * set a random hardware address ++ */ ++ err = dpni_get_primary_mac_addr(priv->mc_io, 0, priv->mc_token, ++ mac_addr); ++ if (err) { ++ dev_err(dev, "dpni_get_primary_mac_addr() failed (%d)", err); ++ return err; ++ } ++ if (is_zero_ether_addr(mac_addr)) { ++ /* Fills in net_dev->dev_addr, as required by ++ * register_netdevice() ++ */ ++ eth_hw_addr_random(net_dev); ++ /* Make the user aware, without cluttering the boot log */ ++ pr_info_once(KBUILD_MODNAME " device(s) have all-zero hwaddr, replaced with random"); ++ err = dpni_set_primary_mac_addr(priv->mc_io, 0, priv->mc_token, ++ net_dev->dev_addr); ++ if (err) { ++ dev_err(dev, "dpni_set_primary_mac_addr(): %d\n", err); ++ return err; ++ } ++ /* Override NET_ADDR_RANDOM set by eth_hw_addr_random(); for all ++ * practical purposes, this will be our "permanent" mac address, ++ * at least until the next reboot. This move will also permit ++ * register_netdevice() to properly fill up net_dev->perm_addr. ++ */ ++ net_dev->addr_assign_type = NET_ADDR_PERM; ++ } else { ++ /* NET_ADDR_PERM is default, all we have to do is ++ * fill in the device addr. ++ */ ++ memcpy(net_dev->dev_addr, mac_addr, net_dev->addr_len); ++ } ++ ++ /* Explicitly add the broadcast address to the MAC filtering table; ++ * the MC won't do that for us. ++ */ ++ eth_broadcast_addr(bcast_addr); ++ err = dpni_add_mac_addr(priv->mc_io, 0, priv->mc_token, bcast_addr); ++ if (err) { ++ dev_warn(dev, "dpni_add_mac_addr() failed (%d)\n", err); ++ /* Won't return an error; at least, we'd have egress traffic */ ++ } ++ ++ /* Reserve enough space to align buffer as per hardware requirement; ++ * NOTE: priv->tx_data_offset MUST be initialized at this point. ++ */ ++ net_dev->needed_headroom = DPAA2_ETH_NEEDED_HEADROOM(priv); ++ ++ /* Our .ndo_init will be called herein */ ++ err = register_netdev(net_dev); ++ if (err < 0) { ++ dev_err(dev, "register_netdev() = %d\n", err); ++ return err; ++ } ++ ++ return 0; ++} ++ ++static int poll_link_state(void *arg) ++{ ++ struct dpaa2_eth_priv *priv = (struct dpaa2_eth_priv *)arg; ++ int err; ++ ++ while (!kthread_should_stop()) { ++ err = link_state_update(priv); ++ if (unlikely(err)) ++ return err; ++ ++ msleep(DPAA2_ETH_LINK_STATE_REFRESH); ++ } ++ ++ return 0; ++} ++ ++static irqreturn_t dpni_irq0_handler(int irq_num, void *arg) ++{ ++ return IRQ_WAKE_THREAD; ++} ++ ++static irqreturn_t dpni_irq0_handler_thread(int irq_num, void *arg) ++{ ++ u8 irq_index = DPNI_IRQ_INDEX; ++ u32 status, clear = 0; ++ struct device *dev = (struct device *)arg; ++ struct fsl_mc_device *dpni_dev = to_fsl_mc_device(dev); ++ struct net_device *net_dev = dev_get_drvdata(dev); ++ int err; ++ ++ err = dpni_get_irq_status(dpni_dev->mc_io, 0, dpni_dev->mc_handle, ++ irq_index, &status); ++ if (unlikely(err)) { ++ netdev_err(net_dev, "Can't get irq status (err %d)", err); ++ clear = 0xffffffff; ++ goto out; ++ } ++ ++ if (status & DPNI_IRQ_EVENT_LINK_CHANGED) { ++ clear |= DPNI_IRQ_EVENT_LINK_CHANGED; ++ link_state_update(netdev_priv(net_dev)); ++ } ++ ++out: ++ dpni_clear_irq_status(dpni_dev->mc_io, 0, dpni_dev->mc_handle, ++ irq_index, clear); ++ return IRQ_HANDLED; ++} ++ ++static int setup_irqs(struct fsl_mc_device *ls_dev) ++{ ++ int err = 0; ++ struct fsl_mc_device_irq *irq; ++ u8 irq_index = DPNI_IRQ_INDEX; ++ u32 mask = DPNI_IRQ_EVENT_LINK_CHANGED; ++ ++ err = fsl_mc_allocate_irqs(ls_dev); ++ if (err) { ++ dev_err(&ls_dev->dev, "MC irqs allocation failed\n"); ++ return err; ++ } ++ ++ irq = ls_dev->irqs[0]; ++ err = devm_request_threaded_irq(&ls_dev->dev, irq->irq_number, ++ dpni_irq0_handler, ++ dpni_irq0_handler_thread, ++ IRQF_NO_SUSPEND | IRQF_ONESHOT, ++ dev_name(&ls_dev->dev), &ls_dev->dev); ++ if (err < 0) { ++ dev_err(&ls_dev->dev, "devm_request_threaded_irq(): %d", err); ++ goto free_mc_irq; ++ } ++ ++ err = dpni_set_irq_mask(ls_dev->mc_io, 0, ls_dev->mc_handle, ++ irq_index, mask); ++ if (err < 0) { ++ dev_err(&ls_dev->dev, "dpni_set_irq_mask(): %d", err); ++ goto free_irq; ++ } ++ ++ err = dpni_set_irq_enable(ls_dev->mc_io, 0, ls_dev->mc_handle, ++ irq_index, 1); ++ if (err < 0) { ++ dev_err(&ls_dev->dev, "dpni_set_irq_enable(): %d", err); ++ goto free_irq; ++ } ++ ++ return 0; ++ ++free_irq: ++ devm_free_irq(&ls_dev->dev, irq->irq_number, &ls_dev->dev); ++free_mc_irq: ++ fsl_mc_free_irqs(ls_dev); ++ ++ return err; ++} ++ ++static void add_ch_napi(struct dpaa2_eth_priv *priv) ++{ ++ int i; ++ struct dpaa2_eth_channel *ch; ++ ++ for (i = 0; i < priv->num_channels; i++) { ++ ch = priv->channel[i]; ++ /* NAPI weight *MUST* be a multiple of DPAA2_ETH_STORE_SIZE */ ++ netif_napi_add(priv->net_dev, &ch->napi, dpaa2_eth_poll, ++ NAPI_POLL_WEIGHT); ++ } ++} ++ ++static void del_ch_napi(struct dpaa2_eth_priv *priv) ++{ ++ int i; ++ struct dpaa2_eth_channel *ch; ++ ++ for (i = 0; i < priv->num_channels; i++) { ++ ch = priv->channel[i]; ++ netif_napi_del(&ch->napi); ++ } ++} ++ ++/* SysFS support */ ++static ssize_t dpaa2_eth_show_tx_shaping(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ struct dpaa2_eth_priv *priv = netdev_priv(to_net_dev(dev)); ++ /* No MC API for getting the shaping config. We're stateful. */ ++ struct dpni_tx_shaping_cfg *scfg = &priv->shaping_cfg; ++ ++ return sprintf(buf, "%u %hu\n", scfg->rate_limit, scfg->max_burst_size); ++} ++ ++static ssize_t dpaa2_eth_write_tx_shaping(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, ++ size_t count) ++{ ++ int err, items; ++ struct dpaa2_eth_priv *priv = netdev_priv(to_net_dev(dev)); ++ struct dpni_tx_shaping_cfg scfg; ++ ++ items = sscanf(buf, "%u %hu", &scfg.rate_limit, &scfg.max_burst_size); ++ if (items != 2) { ++ pr_err("Expected format: \"rate_limit(Mbps) max_burst_size(bytes)\"\n"); ++ return -EINVAL; ++ } ++ /* Size restriction as per MC API documentation */ ++ if (scfg.max_burst_size > 64000) { ++ pr_err("max_burst_size must be <= 64000, thanks.\n"); ++ return -EINVAL; ++ } ++ ++ err = dpni_set_tx_shaping(priv->mc_io, 0, priv->mc_token, &scfg); ++ if (err) { ++ dev_err(dev, "dpni_set_tx_shaping() failed\n"); ++ return -EPERM; ++ } ++ /* If successful, save the current configuration for future inquiries */ ++ priv->shaping_cfg = scfg; ++ ++ return count; ++} ++ ++static ssize_t dpaa2_eth_show_txconf_cpumask(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ struct dpaa2_eth_priv *priv = netdev_priv(to_net_dev(dev)); ++ ++ return cpumask_scnprintf(buf, PAGE_SIZE, &priv->txconf_cpumask); ++} ++ ++static ssize_t dpaa2_eth_write_txconf_cpumask(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, ++ size_t count) ++{ ++ struct dpaa2_eth_priv *priv = netdev_priv(to_net_dev(dev)); ++ struct dpaa2_eth_fq *fq; ++ bool running = netif_running(priv->net_dev); ++ int i, err; ++ ++ err = cpulist_parse(buf, &priv->txconf_cpumask); ++ if (err) ++ return err; ++ ++ /* Only accept CPUs that have an affine DPIO */ ++ if (!cpumask_subset(&priv->txconf_cpumask, &priv->dpio_cpumask)) { ++ netdev_info(priv->net_dev, ++ "cpumask must be a subset of 0x%lx\n", ++ *cpumask_bits(&priv->dpio_cpumask)); ++ cpumask_and(&priv->txconf_cpumask, &priv->dpio_cpumask, ++ &priv->txconf_cpumask); ++ } ++ ++ /* Rewiring the TxConf FQs requires interface shutdown. ++ */ ++ if (running) { ++ err = dpaa2_eth_stop(priv->net_dev); ++ if (err) ++ return -ENODEV; ++ } ++ ++ /* Set the new TxConf FQ affinities */ ++ set_fq_affinity(priv); ++ ++ /* dpaa2_eth_open() below will *stop* the Tx queues until an explicit ++ * link up notification is received. Give the polling thread enough time ++ * to detect the link state change, or else we'll end up with the ++ * transmission side forever shut down. ++ */ ++ if (priv->do_link_poll) ++ msleep(2 * DPAA2_ETH_LINK_STATE_REFRESH); ++ ++ for (i = 0; i < priv->num_fqs; i++) { ++ fq = &priv->fq[i]; ++ if (fq->type != DPAA2_TX_CONF_FQ) ++ continue; ++ setup_tx_flow(priv, fq); ++ } ++ ++ if (running) { ++ err = dpaa2_eth_open(priv->net_dev); ++ if (err) ++ return -ENODEV; ++ } ++ ++ return count; ++} ++ ++static struct device_attribute dpaa2_eth_attrs[] = { ++ __ATTR(txconf_cpumask, ++ S_IRUSR | S_IWUSR, ++ dpaa2_eth_show_txconf_cpumask, ++ dpaa2_eth_write_txconf_cpumask), ++ ++ __ATTR(tx_shaping, ++ S_IRUSR | S_IWUSR, ++ dpaa2_eth_show_tx_shaping, ++ dpaa2_eth_write_tx_shaping), ++}; ++ ++void dpaa2_eth_sysfs_init(struct device *dev) ++{ ++ int i, err; ++ ++ for (i = 0; i < ARRAY_SIZE(dpaa2_eth_attrs); i++) { ++ err = device_create_file(dev, &dpaa2_eth_attrs[i]); ++ if (err) { ++ dev_err(dev, "ERROR creating sysfs file\n"); ++ goto undo; ++ } ++ } ++ return; ++ ++undo: ++ while (i > 0) ++ device_remove_file(dev, &dpaa2_eth_attrs[--i]); ++} ++ ++void dpaa2_eth_sysfs_remove(struct device *dev) ++{ ++ int i; ++ ++ for (i = 0; i < ARRAY_SIZE(dpaa2_eth_attrs); i++) ++ device_remove_file(dev, &dpaa2_eth_attrs[i]); ++} ++ ++static int dpaa2_eth_probe(struct fsl_mc_device *dpni_dev) ++{ ++ struct device *dev; ++ struct net_device *net_dev = NULL; ++ struct dpaa2_eth_priv *priv = NULL; ++ int err = 0; ++ ++ dev = &dpni_dev->dev; ++ ++ /* Net device */ ++ net_dev = alloc_etherdev_mq(sizeof(*priv), DPAA2_ETH_MAX_TX_QUEUES); ++ if (!net_dev) { ++ dev_err(dev, "alloc_etherdev_mq() failed\n"); ++ return -ENOMEM; ++ } ++ ++ SET_NETDEV_DEV(net_dev, dev); ++ dev_set_drvdata(dev, net_dev); ++ ++ priv = netdev_priv(net_dev); ++ priv->net_dev = net_dev; ++ ++ /* Obtain a MC portal */ ++ err = fsl_mc_portal_allocate(dpni_dev, FSL_MC_IO_ATOMIC_CONTEXT_PORTAL, ++ &priv->mc_io); ++ if (err) { ++ dev_err(dev, "MC portal allocation failed\n"); ++ goto err_portal_alloc; ++ } ++ ++ /* MC objects initialization and configuration */ ++ err = setup_dpni(dpni_dev); ++ if (err) ++ goto err_dpni_setup; ++ ++ err = setup_dpio(priv); ++ if (err) ++ goto err_dpio_setup; ++ ++ setup_fqs(priv); ++ ++ err = setup_dpbp(priv); ++ if (err) ++ goto err_dpbp_setup; ++ ++ err = bind_dpni(priv); ++ if (err) ++ goto err_bind; ++ ++ /* Add a NAPI context for each channel */ ++ add_ch_napi(priv); ++ ++ /* Percpu statistics */ ++ priv->percpu_stats = alloc_percpu(*priv->percpu_stats); ++ if (!priv->percpu_stats) { ++ dev_err(dev, "alloc_percpu(percpu_stats) failed\n"); ++ err = -ENOMEM; ++ goto err_alloc_percpu_stats; ++ } ++ priv->percpu_extras = alloc_percpu(*priv->percpu_extras); ++ if (!priv->percpu_extras) { ++ dev_err(dev, "alloc_percpu(percpu_extras) failed\n"); ++ err = -ENOMEM; ++ goto err_alloc_percpu_extras; ++ } ++ ++ snprintf(net_dev->name, IFNAMSIZ, "ni%d", dpni_dev->obj_desc.id); ++ if (!dev_valid_name(net_dev->name)) { ++ dev_warn(&net_dev->dev, ++ "netdevice name \"%s\" cannot be used, reverting to default..\n", ++ net_dev->name); ++ dev_alloc_name(net_dev, "eth%d"); ++ dev_warn(&net_dev->dev, "using name \"%s\"\n", net_dev->name); ++ } ++ ++ err = netdev_init(net_dev); ++ if (err) ++ goto err_netdev_init; ++ ++ /* Configure checksum offload based on current interface flags */ ++ err = set_rx_csum(priv, !!(net_dev->features & NETIF_F_RXCSUM)); ++ if (err) ++ goto err_csum; ++ ++ err = set_tx_csum(priv, !!(net_dev->features & ++ (NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM))); ++ if (err) ++ goto err_csum; ++ ++ err = alloc_rings(priv); ++ if (err) ++ goto err_alloc_rings; ++ ++ net_dev->ethtool_ops = &dpaa2_ethtool_ops; ++ ++ err = setup_irqs(dpni_dev); ++ if (err) { ++ netdev_warn(net_dev, "Failed to set link interrupt, fall back to polling\n"); ++ priv->poll_thread = kthread_run(poll_link_state, priv, ++ "%s_poll_link", net_dev->name); ++ if (IS_ERR(priv->poll_thread)) { ++ netdev_err(net_dev, "Error starting polling thread\n"); ++ goto err_poll_thread; ++ } ++ priv->do_link_poll = true; ++ } ++ ++ dpaa2_eth_sysfs_init(&net_dev->dev); ++ dpaa2_dbg_add(priv); ++ ++ dev_info(dev, "Probed interface %s\n", net_dev->name); ++ return 0; ++ ++err_poll_thread: ++ free_rings(priv); ++err_alloc_rings: ++err_csum: ++ unregister_netdev(net_dev); ++err_netdev_init: ++ free_percpu(priv->percpu_extras); ++err_alloc_percpu_extras: ++ free_percpu(priv->percpu_stats); ++err_alloc_percpu_stats: ++ del_ch_napi(priv); ++err_bind: ++ free_dpbp(priv); ++err_dpbp_setup: ++ free_dpio(priv); ++err_dpio_setup: ++ kfree(priv->cls_rule); ++ dpni_close(priv->mc_io, 0, priv->mc_token); ++err_dpni_setup: ++ fsl_mc_portal_free(priv->mc_io); ++err_portal_alloc: ++ dev_set_drvdata(dev, NULL); ++ free_netdev(net_dev); ++ ++ return err; ++} ++ ++static int dpaa2_eth_remove(struct fsl_mc_device *ls_dev) ++{ ++ struct device *dev; ++ struct net_device *net_dev; ++ struct dpaa2_eth_priv *priv; ++ ++ dev = &ls_dev->dev; ++ net_dev = dev_get_drvdata(dev); ++ priv = netdev_priv(net_dev); ++ ++ dpaa2_dbg_remove(priv); ++ dpaa2_eth_sysfs_remove(&net_dev->dev); ++ ++ unregister_netdev(net_dev); ++ dev_info(net_dev->dev.parent, "Removed interface %s\n", net_dev->name); ++ ++ free_dpio(priv); ++ free_rings(priv); ++ del_ch_napi(priv); ++ free_dpbp(priv); ++ free_dpni(priv); ++ ++ fsl_mc_portal_free(priv->mc_io); ++ ++ free_percpu(priv->percpu_stats); ++ free_percpu(priv->percpu_extras); ++ ++ if (priv->do_link_poll) ++ kthread_stop(priv->poll_thread); ++ else ++ fsl_mc_free_irqs(ls_dev); ++ ++ kfree(priv->cls_rule); ++ ++ dev_set_drvdata(dev, NULL); ++ free_netdev(net_dev); ++ ++ return 0; ++} ++ ++static const struct fsl_mc_device_match_id dpaa2_eth_match_id_table[] = { ++ { ++ .vendor = FSL_MC_VENDOR_FREESCALE, ++ .obj_type = "dpni", ++ .ver_major = DPNI_VER_MAJOR, ++ .ver_minor = DPNI_VER_MINOR ++ }, ++ { .vendor = 0x0 } ++}; ++ ++static struct fsl_mc_driver dpaa2_eth_driver = { ++ .driver = { ++ .name = KBUILD_MODNAME, ++ .owner = THIS_MODULE, ++ }, ++ .probe = dpaa2_eth_probe, ++ .remove = dpaa2_eth_remove, ++ .match_id_table = dpaa2_eth_match_id_table ++}; ++ ++static int __init dpaa2_eth_driver_init(void) ++{ ++ int err; ++ ++ dpaa2_eth_dbg_init(); ++ ++ err = fsl_mc_driver_register(&dpaa2_eth_driver); ++ if (err) { ++ dpaa2_eth_dbg_exit(); ++ return err; ++ } ++ ++ return 0; ++} ++ ++static void __exit dpaa2_eth_driver_exit(void) ++{ ++ fsl_mc_driver_unregister(&dpaa2_eth_driver); ++ dpaa2_eth_dbg_exit(); ++} ++ ++module_init(dpaa2_eth_driver_init); ++module_exit(dpaa2_eth_driver_exit); +diff --git a/drivers/staging/fsl-dpaa2/ethernet/dpaa2-eth.h b/drivers/staging/fsl-dpaa2/ethernet/dpaa2-eth.h +new file mode 100644 +index 0000000..bdcdbd6 +--- /dev/null ++++ b/drivers/staging/fsl-dpaa2/ethernet/dpaa2-eth.h +@@ -0,0 +1,397 @@ ++/* Copyright 2014-2015 Freescale Semiconductor Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * * Neither the name of Freescale Semiconductor nor the ++ * names of its contributors may be used to endorse or promote products ++ * derived from this software without specific prior written permission. ++ * ++ * ++ * ALTERNATIVELY, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") as published by the Free Software ++ * Foundation, either version 2 of that License or (at your option) any ++ * later version. ++ * ++ * THIS SOFTWARE IS PROVIDED BY Freescale Semiconductor ``AS IS'' AND ANY ++ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ++ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE ++ * DISCLAIMED. IN NO EVENT SHALL Freescale Semiconductor BE LIABLE FOR ANY ++ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ++ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; ++ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ++ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ++ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS ++ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++#ifndef __DPAA2_ETH_H ++#define __DPAA2_ETH_H ++ ++#include ++#include ++#include "../../fsl-mc/include/fsl_dpaa2_io.h" ++#include "../../fsl-mc/include/fsl_dpaa2_fd.h" ++#include "../../fsl-mc/include/dpbp.h" ++#include "../../fsl-mc/include/dpbp-cmd.h" ++#include "../../fsl-mc/include/dpcon.h" ++#include "../../fsl-mc/include/dpcon-cmd.h" ++#include "dpni.h" ++#include "dpni-cmd.h" ++ ++#include "dpaa2-eth-trace.h" ++#include "dpaa2-eth-debugfs.h" ++ ++#define DPAA2_ETH_STORE_SIZE 16 ++ ++/* Maximum number of scatter-gather entries in an ingress frame, ++ * considering the maximum receive frame size is 64K ++ */ ++#define DPAA2_ETH_MAX_SG_ENTRIES ((64 * 1024) / DPAA2_ETH_RX_BUF_SIZE) ++ ++/* Maximum acceptable MTU value. It is in direct relation with the hardware ++ * enforced Max Frame Length (currently 10k). ++ */ ++#define DPAA2_ETH_MFL (10 * 1024) ++#define DPAA2_ETH_MAX_MTU (DPAA2_ETH_MFL - VLAN_ETH_HLEN) ++/* Convert L3 MTU to L2 MFL */ ++#define DPAA2_ETH_L2_MAX_FRM(mtu) (mtu + VLAN_ETH_HLEN) ++ ++/* Set the taildrop threshold (in bytes) to allow the enqueue of several jumbo ++ * frames in the Rx queues (length of the current frame is not ++ * taken into account when making the taildrop decision) ++ */ ++#define DPAA2_ETH_TAILDROP_THRESH (64 * 1024) ++ ++/* Buffer quota per queue. Must be large enough such that for minimum sized ++ * frames taildrop kicks in before the bpool gets depleted, so we compute ++ * how many 64B frames fit inside the taildrop threshold and add a margin ++ * to accommodate the buffer refill delay. ++ */ ++#define DPAA2_ETH_MAX_FRAMES_PER_QUEUE (DPAA2_ETH_TAILDROP_THRESH / 64) ++#define DPAA2_ETH_NUM_BUFS (DPAA2_ETH_MAX_FRAMES_PER_QUEUE + 256) ++#define DPAA2_ETH_REFILL_THRESH DPAA2_ETH_MAX_FRAMES_PER_QUEUE ++ ++/* Maximum number of buffers that can be acquired/released through a single ++ * QBMan command ++ */ ++#define DPAA2_ETH_BUFS_PER_CMD 7 ++ ++/* Hardware requires alignment for ingress/egress buffer addresses ++ * and ingress buffer lengths. ++ */ ++#define DPAA2_ETH_RX_BUF_SIZE 2048 ++#define DPAA2_ETH_TX_BUF_ALIGN 64 ++#define DPAA2_ETH_RX_BUF_ALIGN 256 ++#define DPAA2_ETH_NEEDED_HEADROOM(p_priv) \ ++ ((p_priv)->tx_data_offset + DPAA2_ETH_TX_BUF_ALIGN) ++ ++/* Hardware only sees DPAA2_ETH_RX_BUF_SIZE, but we need to allocate ingress ++ * buffers large enough to allow building an skb around them and also account ++ * for alignment restrictions ++ */ ++#define DPAA2_ETH_BUF_RAW_SIZE \ ++ (DPAA2_ETH_RX_BUF_SIZE + \ ++ SKB_DATA_ALIGN(sizeof(struct skb_shared_info)) + \ ++ DPAA2_ETH_RX_BUF_ALIGN) ++ ++/* PTP nominal frequency 1GHz */ ++#define DPAA2_PTP_NOMINAL_FREQ_PERIOD_NS 1 ++ ++/* We are accommodating a skb backpointer and some S/G info ++ * in the frame's software annotation. The hardware ++ * options are either 0 or 64, so we choose the latter. ++ */ ++#define DPAA2_ETH_SWA_SIZE 64 ++ ++/* Must keep this struct smaller than DPAA2_ETH_SWA_SIZE */ ++struct dpaa2_eth_swa { ++ struct sk_buff *skb; ++ struct scatterlist *scl; ++ int num_sg; ++ int num_dma_bufs; ++}; ++ ++/* Annotation valid bits in FD FRC */ ++#define DPAA2_FD_FRC_FASV 0x8000 ++#define DPAA2_FD_FRC_FAEADV 0x4000 ++#define DPAA2_FD_FRC_FAPRV 0x2000 ++#define DPAA2_FD_FRC_FAIADV 0x1000 ++#define DPAA2_FD_FRC_FASWOV 0x0800 ++#define DPAA2_FD_FRC_FAICFDV 0x0400 ++ ++/* Annotation bits in FD CTRL */ ++#define DPAA2_FD_CTRL_ASAL 0x00020000 /* ASAL = 128 */ ++#define DPAA2_FD_CTRL_PTA 0x00800000 ++#define DPAA2_FD_CTRL_PTV1 0x00400000 ++ ++/* Frame annotation status */ ++struct dpaa2_fas { ++ u8 reserved; ++ u8 ppid; ++ __le16 ifpid; ++ __le32 status; ++} __packed; ++ ++/* Frame annotation egress action descriptor */ ++#define DPAA2_FAEAD_OFFSET 0x58 ++ ++struct dpaa2_faead { ++ __le32 conf_fqid; ++ __le32 ctrl; ++}; ++ ++#define DPAA2_FAEAD_A2V 0x20000000 ++#define DPAA2_FAEAD_UPDV 0x00001000 ++#define DPAA2_FAEAD_UPD 0x00000010 ++ ++/* Error and status bits in the frame annotation status word */ ++/* Debug frame, otherwise supposed to be discarded */ ++#define DPAA2_FAS_DISC 0x80000000 ++/* MACSEC frame */ ++#define DPAA2_FAS_MS 0x40000000 ++#define DPAA2_FAS_PTP 0x08000000 ++/* Ethernet multicast frame */ ++#define DPAA2_FAS_MC 0x04000000 ++/* Ethernet broadcast frame */ ++#define DPAA2_FAS_BC 0x02000000 ++#define DPAA2_FAS_KSE 0x00040000 ++#define DPAA2_FAS_EOFHE 0x00020000 ++#define DPAA2_FAS_MNLE 0x00010000 ++#define DPAA2_FAS_TIDE 0x00008000 ++#define DPAA2_FAS_PIEE 0x00004000 ++/* Frame length error */ ++#define DPAA2_FAS_FLE 0x00002000 ++/* Frame physical error */ ++#define DPAA2_FAS_FPE 0x00001000 ++#define DPAA2_FAS_PTE 0x00000080 ++#define DPAA2_FAS_ISP 0x00000040 ++#define DPAA2_FAS_PHE 0x00000020 ++#define DPAA2_FAS_BLE 0x00000010 ++/* L3 csum validation performed */ ++#define DPAA2_FAS_L3CV 0x00000008 ++/* L3 csum error */ ++#define DPAA2_FAS_L3CE 0x00000004 ++/* L4 csum validation performed */ ++#define DPAA2_FAS_L4CV 0x00000002 ++/* L4 csum error */ ++#define DPAA2_FAS_L4CE 0x00000001 ++/* Possible errors on the ingress path */ ++#define DPAA2_ETH_RX_ERR_MASK (DPAA2_FAS_KSE | \ ++ DPAA2_FAS_EOFHE | \ ++ DPAA2_FAS_MNLE | \ ++ DPAA2_FAS_TIDE | \ ++ DPAA2_FAS_PIEE | \ ++ DPAA2_FAS_FLE | \ ++ DPAA2_FAS_FPE | \ ++ DPAA2_FAS_PTE | \ ++ DPAA2_FAS_ISP | \ ++ DPAA2_FAS_PHE | \ ++ DPAA2_FAS_BLE | \ ++ DPAA2_FAS_L3CE | \ ++ DPAA2_FAS_L4CE) ++/* Tx errors */ ++#define DPAA2_ETH_TXCONF_ERR_MASK (DPAA2_FAS_KSE | \ ++ DPAA2_FAS_EOFHE | \ ++ DPAA2_FAS_MNLE | \ ++ DPAA2_FAS_TIDE) ++ ++/* Time in milliseconds between link state updates */ ++#define DPAA2_ETH_LINK_STATE_REFRESH 1000 ++ ++/* Driver statistics, other than those in struct rtnl_link_stats64. ++ * These are usually collected per-CPU and aggregated by ethtool. ++ */ ++struct dpaa2_eth_drv_stats { ++ __u64 tx_conf_frames; ++ __u64 tx_conf_bytes; ++ __u64 tx_sg_frames; ++ __u64 tx_sg_bytes; ++ __u64 rx_sg_frames; ++ __u64 rx_sg_bytes; ++ /* Enqueues retried due to portal busy */ ++ __u64 tx_portal_busy; ++}; ++ ++/* Per-FQ statistics */ ++struct dpaa2_eth_fq_stats { ++ /* Number of frames received on this queue */ ++ __u64 frames; ++}; ++ ++/* Per-channel statistics */ ++struct dpaa2_eth_ch_stats { ++ /* Volatile dequeues retried due to portal busy */ ++ __u64 dequeue_portal_busy; ++ /* Number of CDANs; useful to estimate avg NAPI len */ ++ __u64 cdan; ++ /* Number of frames received on queues from this channel */ ++ __u64 frames; ++ /* Pull errors */ ++ __u64 pull_err; ++}; ++ ++/* Maximum number of queues associated with a DPNI */ ++#define DPAA2_ETH_MAX_RX_QUEUES 16 ++#define DPAA2_ETH_MAX_TX_QUEUES NR_CPUS ++#define DPAA2_ETH_MAX_RX_ERR_QUEUES 1 ++#define DPAA2_ETH_MAX_QUEUES (DPAA2_ETH_MAX_RX_QUEUES + \ ++ DPAA2_ETH_MAX_TX_QUEUES + \ ++ DPAA2_ETH_MAX_RX_ERR_QUEUES) ++ ++#define DPAA2_ETH_MAX_DPCONS NR_CPUS ++ ++enum dpaa2_eth_fq_type { ++ DPAA2_RX_FQ = 0, ++ DPAA2_TX_CONF_FQ, ++ DPAA2_RX_ERR_FQ ++}; ++ ++struct dpaa2_eth_priv; ++ ++struct dpaa2_eth_fq { ++ u32 fqid; ++ u16 flowid; ++ int target_cpu; ++ struct dpaa2_eth_channel *channel; ++ enum dpaa2_eth_fq_type type; ++ ++ void (*consume)(struct dpaa2_eth_priv *, ++ struct dpaa2_eth_channel *, ++ const struct dpaa2_fd *, ++ struct napi_struct *); ++ struct dpaa2_eth_fq_stats stats; ++}; ++ ++struct dpaa2_eth_channel { ++ struct dpaa2_io_notification_ctx nctx; ++ struct fsl_mc_device *dpcon; ++ int dpcon_id; ++ int ch_id; ++ int dpio_id; ++ struct napi_struct napi; ++ struct dpaa2_io_store *store; ++ struct dpaa2_eth_priv *priv; ++ int buf_count; ++ struct dpaa2_eth_ch_stats stats; ++}; ++ ++struct dpaa2_eth_cls_rule { ++ struct ethtool_rx_flow_spec fs; ++ bool in_use; ++}; ++ ++struct dpaa2_eth_hash_fields { ++ u64 rxnfc_field; ++ enum net_prot cls_prot; ++ int cls_field; ++ int offset; ++ int size; ++}; ++ ++/* Driver private data */ ++struct dpaa2_eth_priv { ++ struct net_device *net_dev; ++ ++ u8 num_fqs; ++ struct dpaa2_eth_fq fq[DPAA2_ETH_MAX_QUEUES]; ++ ++ u8 num_channels; ++ struct dpaa2_eth_channel *channel[DPAA2_ETH_MAX_DPCONS]; ++ ++ int dpni_id; ++ struct dpni_attr dpni_attrs; ++ struct dpni_extended_cfg dpni_ext_cfg; ++ /* Insofar as the MC is concerned, we're using one layout on all 3 types ++ * of buffers (Rx, Tx, Tx-Conf). ++ */ ++ struct dpni_buffer_layout buf_layout; ++ u16 tx_data_offset; ++ ++ struct fsl_mc_device *dpbp_dev; ++ struct dpbp_attr dpbp_attrs; ++ ++ u16 tx_qdid; ++ struct fsl_mc_io *mc_io; ++ /* SysFS-controlled affinity mask for TxConf FQs */ ++ struct cpumask txconf_cpumask; ++ /* Cores which have an affine DPIO/DPCON. ++ * This is the cpu set on which Rx frames are processed; ++ * Tx confirmation frames are processed on a subset of this, ++ * depending on user settings. ++ */ ++ struct cpumask dpio_cpumask; ++ ++ /* Standard statistics */ ++ struct rtnl_link_stats64 __percpu *percpu_stats; ++ /* Extra stats, in addition to the ones known by the kernel */ ++ struct dpaa2_eth_drv_stats __percpu *percpu_extras; ++ ++ u16 mc_token; ++ ++ struct dpni_link_state link_state; ++ bool do_link_poll; ++ struct task_struct *poll_thread; ++ ++ struct dpaa2_eth_hash_fields *hash_fields; ++ u8 num_hash_fields; ++ /* enabled ethtool hashing bits */ ++ u64 rx_flow_hash; ++ ++#ifdef CONFIG_FSL_DPAA2_ETH_DEBUGFS ++ struct dpaa2_debugfs dbg; ++#endif ++ ++ /* array of classification rules */ ++ struct dpaa2_eth_cls_rule *cls_rule; ++ ++ struct dpni_tx_shaping_cfg shaping_cfg; ++ ++ bool ts_tx_en; /* Tx timestamping enabled */ ++ bool ts_rx_en; /* Rx timestamping enabled */ ++}; ++ ++#define dpaa2_eth_hash_enabled(priv) \ ++ ((priv)->dpni_attrs.options & DPNI_OPT_DIST_HASH) ++ ++#define dpaa2_eth_fs_enabled(priv) \ ++ ((priv)->dpni_attrs.options & DPNI_OPT_DIST_FS) ++ ++#define dpaa2_eth_fs_mask_enabled(priv) \ ++ ((priv)->dpni_attrs.options & DPNI_OPT_FS_MASK_SUPPORT) ++ ++#define DPAA2_CLASSIFIER_ENTRY_COUNT 16 ++ ++/* Required by struct dpni_attr::ext_cfg_iova */ ++#define DPAA2_EXT_CFG_SIZE 256 ++ ++/* size of DMA memory used to pass configuration to classifier, in bytes */ ++#define DPAA2_CLASSIFIER_DMA_SIZE 256 ++ ++extern const struct ethtool_ops dpaa2_ethtool_ops; ++ ++static int dpaa2_eth_queue_count(struct dpaa2_eth_priv *priv) ++{ ++ if (!dpaa2_eth_hash_enabled(priv)) ++ return 1; ++ ++ return priv->dpni_ext_cfg.tc_cfg[0].max_dist; ++} ++ ++static inline int dpaa2_eth_max_channels(struct dpaa2_eth_priv *priv) ++{ ++ /* Ideally, we want a number of channels large enough ++ * to accommodate both the Rx distribution size ++ * and the max number of Tx confirmation queues ++ */ ++ return max_t(int, dpaa2_eth_queue_count(priv), ++ priv->dpni_attrs.max_senders); ++} ++ ++void check_cls_support(struct dpaa2_eth_priv *priv); ++ ++#endif /* __DPAA2_H */ +diff --git a/drivers/staging/fsl-dpaa2/ethernet/dpaa2-ethtool.c b/drivers/staging/fsl-dpaa2/ethernet/dpaa2-ethtool.c +new file mode 100644 +index 0000000..1d792cd +--- /dev/null ++++ b/drivers/staging/fsl-dpaa2/ethernet/dpaa2-ethtool.c +@@ -0,0 +1,732 @@ ++/* Copyright 2014-2015 Freescale Semiconductor Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * * Neither the name of Freescale Semiconductor nor the ++ * names of its contributors may be used to endorse or promote products ++ * derived from this software without specific prior written permission. ++ * ++ * ++ * ALTERNATIVELY, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") as published by the Free Software ++ * Foundation, either version 2 of that License or (at your option) any ++ * later version. ++ * ++ * THIS SOFTWARE IS PROVIDED BY Freescale Semiconductor ``AS IS'' AND ANY ++ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ++ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE ++ * DISCLAIMED. IN NO EVENT SHALL Freescale Semiconductor BE LIABLE FOR ANY ++ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ++ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; ++ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ++ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ++ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS ++ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++#include "dpni.h" /* DPNI_LINK_OPT_* */ ++#include "dpaa2-eth.h" ++ ++/* To be kept in sync with 'enum dpni_counter' */ ++char dpaa2_ethtool_stats[][ETH_GSTRING_LEN] = { ++ "rx frames", ++ "rx bytes", ++ /* rx frames filtered/policed */ ++ "rx filtered frames", ++ /* rx frames dropped with errors */ ++ "rx discarded frames", ++ "rx mcast frames", ++ "rx mcast bytes", ++ "rx bcast frames", ++ "rx bcast bytes", ++ "tx frames", ++ "tx bytes", ++ /* tx frames dropped with errors */ ++ "tx discarded frames", ++}; ++ ++#define DPAA2_ETH_NUM_STATS ARRAY_SIZE(dpaa2_ethtool_stats) ++ ++/* To be kept in sync with 'struct dpaa2_eth_drv_stats' */ ++char dpaa2_ethtool_extras[][ETH_GSTRING_LEN] = { ++ /* per-cpu stats */ ++ ++ "tx conf frames", ++ "tx conf bytes", ++ "tx sg frames", ++ "tx sg bytes", ++ "rx sg frames", ++ "rx sg bytes", ++ /* how many times we had to retry the enqueue command */ ++ "enqueue portal busy", ++ ++ /* Channel stats */ ++ /* How many times we had to retry the volatile dequeue command */ ++ "dequeue portal busy", ++ "channel pull errors", ++ /* Number of notifications received */ ++ "cdan", ++#ifdef CONFIG_FSL_QBMAN_DEBUG ++ /* FQ stats */ ++ "rx pending frames", ++ "rx pending bytes", ++ "tx conf pending frames", ++ "tx conf pending bytes", ++ "buffer count" ++#endif ++}; ++ ++#define DPAA2_ETH_NUM_EXTRA_STATS ARRAY_SIZE(dpaa2_ethtool_extras) ++ ++static void dpaa2_eth_get_drvinfo(struct net_device *net_dev, ++ struct ethtool_drvinfo *drvinfo) ++{ ++ strlcpy(drvinfo->driver, KBUILD_MODNAME, sizeof(drvinfo->driver)); ++ strlcpy(drvinfo->version, VERSION, sizeof(drvinfo->version)); ++ strlcpy(drvinfo->fw_version, "N/A", sizeof(drvinfo->fw_version)); ++ strlcpy(drvinfo->bus_info, dev_name(net_dev->dev.parent->parent), ++ sizeof(drvinfo->bus_info)); ++} ++ ++static int dpaa2_eth_get_settings(struct net_device *net_dev, ++ struct ethtool_cmd *cmd) ++{ ++ struct dpni_link_state state = {0}; ++ int err = 0; ++ struct dpaa2_eth_priv *priv = netdev_priv(net_dev); ++ ++ err = dpni_get_link_state(priv->mc_io, 0, priv->mc_token, &state); ++ if (err) { ++ netdev_err(net_dev, "ERROR %d getting link state", err); ++ goto out; ++ } ++ ++ /* At the moment, we have no way of interrogating the DPMAC ++ * from the DPNI side - and for that matter there may exist ++ * no DPMAC at all. So for now we just don't report anything ++ * beyond the DPNI attributes. ++ */ ++ if (state.options & DPNI_LINK_OPT_AUTONEG) ++ cmd->autoneg = AUTONEG_ENABLE; ++ if (!(state.options & DPNI_LINK_OPT_HALF_DUPLEX)) ++ cmd->duplex = DUPLEX_FULL; ++ ethtool_cmd_speed_set(cmd, state.rate); ++ ++out: ++ return err; ++} ++ ++static int dpaa2_eth_set_settings(struct net_device *net_dev, ++ struct ethtool_cmd *cmd) ++{ ++ struct dpni_link_cfg cfg = {0}; ++ struct dpaa2_eth_priv *priv = netdev_priv(net_dev); ++ int err = 0; ++ ++ netdev_dbg(net_dev, "Setting link parameters..."); ++ ++ /* Due to a temporary MC limitation, the DPNI must be down ++ * in order to be able to change link settings. Taking steps to let ++ * the user know that. ++ */ ++ if (netif_running(net_dev)) { ++ netdev_info(net_dev, "Sorry, interface must be brought down first.\n"); ++ return -EACCES; ++ } ++ ++ cfg.rate = ethtool_cmd_speed(cmd); ++ if (cmd->autoneg == AUTONEG_ENABLE) ++ cfg.options |= DPNI_LINK_OPT_AUTONEG; ++ else ++ cfg.options &= ~DPNI_LINK_OPT_AUTONEG; ++ if (cmd->duplex == DUPLEX_HALF) ++ cfg.options |= DPNI_LINK_OPT_HALF_DUPLEX; ++ else ++ cfg.options &= ~DPNI_LINK_OPT_HALF_DUPLEX; ++ ++ err = dpni_set_link_cfg(priv->mc_io, 0, priv->mc_token, &cfg); ++ if (err) ++ /* ethtool will be loud enough if we return an error; no point ++ * in putting our own error message on the console by default ++ */ ++ netdev_dbg(net_dev, "ERROR %d setting link cfg", err); ++ ++ return err; ++} ++ ++static void dpaa2_eth_get_strings(struct net_device *netdev, u32 stringset, ++ u8 *data) ++{ ++ u8 *p = data; ++ int i; ++ ++ switch (stringset) { ++ case ETH_SS_STATS: ++ for (i = 0; i < DPAA2_ETH_NUM_STATS; i++) { ++ strlcpy(p, dpaa2_ethtool_stats[i], ETH_GSTRING_LEN); ++ p += ETH_GSTRING_LEN; ++ } ++ for (i = 0; i < DPAA2_ETH_NUM_EXTRA_STATS; i++) { ++ strlcpy(p, dpaa2_ethtool_extras[i], ETH_GSTRING_LEN); ++ p += ETH_GSTRING_LEN; ++ } ++ break; ++ } ++} ++ ++static int dpaa2_eth_get_sset_count(struct net_device *net_dev, int sset) ++{ ++ switch (sset) { ++ case ETH_SS_STATS: /* ethtool_get_stats(), ethtool_get_drvinfo() */ ++ return DPAA2_ETH_NUM_STATS + DPAA2_ETH_NUM_EXTRA_STATS; ++ default: ++ return -EOPNOTSUPP; ++ } ++} ++ ++/** Fill in hardware counters, as returned by MC. ++ */ ++static void dpaa2_eth_get_ethtool_stats(struct net_device *net_dev, ++ struct ethtool_stats *stats, ++ u64 *data) ++{ ++ int i; /* Current index in the data array */ ++ int j, k, err; ++ ++#ifdef CONFIG_FSL_QBMAN_DEBUG ++ u32 fcnt, bcnt; ++ u32 fcnt_rx_total = 0, fcnt_tx_total = 0; ++ u32 bcnt_rx_total = 0, bcnt_tx_total = 0; ++ u32 buf_cnt; ++#endif ++ u64 cdan = 0; ++ u64 portal_busy = 0, pull_err = 0; ++ struct dpaa2_eth_priv *priv = netdev_priv(net_dev); ++ struct dpaa2_eth_drv_stats *extras; ++ struct dpaa2_eth_ch_stats *ch_stats; ++ ++ memset(data, 0, ++ sizeof(u64) * (DPAA2_ETH_NUM_STATS + DPAA2_ETH_NUM_EXTRA_STATS)); ++ ++ /* Print standard counters, from DPNI statistics */ ++ for (i = 0; i < DPAA2_ETH_NUM_STATS; i++) { ++ err = dpni_get_counter(priv->mc_io, 0, priv->mc_token, i, ++ data + i); ++ if (err != 0) ++ netdev_warn(net_dev, "Err %d getting DPNI counter %d", ++ err, i); ++ } ++ ++ /* Print per-cpu extra stats */ ++ for_each_online_cpu(k) { ++ extras = per_cpu_ptr(priv->percpu_extras, k); ++ for (j = 0; j < sizeof(*extras) / sizeof(__u64); j++) ++ *((__u64 *)data + i + j) += *((__u64 *)extras + j); ++ } ++ i += j; ++ ++ /* We may be using fewer DPIOs than actual CPUs */ ++ for_each_cpu(j, &priv->dpio_cpumask) { ++ ch_stats = &priv->channel[j]->stats; ++ cdan += ch_stats->cdan; ++ portal_busy += ch_stats->dequeue_portal_busy; ++ pull_err += ch_stats->pull_err; ++ } ++ ++ *(data + i++) = portal_busy; ++ *(data + i++) = pull_err; ++ *(data + i++) = cdan; ++ ++#ifdef CONFIG_FSL_QBMAN_DEBUG ++ for (j = 0; j < priv->num_fqs; j++) { ++ /* Print FQ instantaneous counts */ ++ err = dpaa2_io_query_fq_count(NULL, priv->fq[j].fqid, ++ &fcnt, &bcnt); ++ if (err) { ++ netdev_warn(net_dev, "FQ query error %d", err); ++ return; ++ } ++ ++ if (priv->fq[j].type == DPAA2_TX_CONF_FQ) { ++ fcnt_tx_total += fcnt; ++ bcnt_tx_total += bcnt; ++ } else { ++ fcnt_rx_total += fcnt; ++ bcnt_rx_total += bcnt; ++ } ++ } ++ *(data + i++) = fcnt_rx_total; ++ *(data + i++) = bcnt_rx_total; ++ *(data + i++) = fcnt_tx_total; ++ *(data + i++) = bcnt_tx_total; ++ ++ err = dpaa2_io_query_bp_count(NULL, priv->dpbp_attrs.bpid, &buf_cnt); ++ if (err) { ++ netdev_warn(net_dev, "Buffer count query error %d\n", err); ++ return; ++ } ++ *(data + i++) = buf_cnt; ++#endif ++} ++ ++static int cls_key_off(struct dpaa2_eth_priv *priv, int prot, int field) ++{ ++ int i, off = 0; ++ ++ for (i = 0; i < priv->num_hash_fields; i++) { ++ if (priv->hash_fields[i].cls_prot == prot && ++ priv->hash_fields[i].cls_field == field) ++ return off; ++ off += priv->hash_fields[i].size; ++ } ++ ++ return -1; ++} ++ ++static u8 cls_key_size(struct dpaa2_eth_priv *priv) ++{ ++ u8 i, size = 0; ++ ++ for (i = 0; i < priv->num_hash_fields; i++) ++ size += priv->hash_fields[i].size; ++ ++ return size; ++} ++ ++void check_cls_support(struct dpaa2_eth_priv *priv) ++{ ++ u8 key_size = cls_key_size(priv); ++ struct device *dev = priv->net_dev->dev.parent; ++ ++ if (dpaa2_eth_hash_enabled(priv)) { ++ if (priv->dpni_attrs.max_dist_key_size < key_size) { ++ dev_dbg(dev, "max_dist_key_size = %d, expected %d. Hashing and steering are disabled\n", ++ priv->dpni_attrs.max_dist_key_size, ++ key_size); ++ goto disable_cls; ++ } ++ if (priv->num_hash_fields > DPKG_MAX_NUM_OF_EXTRACTS) { ++ dev_dbg(dev, "Too many key fields (max = %d). Hashing and steering are disabled\n", ++ DPKG_MAX_NUM_OF_EXTRACTS); ++ goto disable_cls; ++ } ++ } ++ ++ if (dpaa2_eth_fs_enabled(priv)) { ++ if (!dpaa2_eth_hash_enabled(priv)) { ++ dev_dbg(dev, "DPNI_OPT_DIST_HASH option missing. Steering is disabled\n"); ++ goto disable_cls; ++ } ++ if (!dpaa2_eth_fs_mask_enabled(priv)) { ++ dev_dbg(dev, "Key masks not supported. Steering is disabled\n"); ++ goto disable_fs; ++ } ++ } ++ ++ return; ++ ++disable_cls: ++ priv->dpni_attrs.options &= ~DPNI_OPT_DIST_HASH; ++disable_fs: ++ priv->dpni_attrs.options &= ~(DPNI_OPT_DIST_FS | ++ DPNI_OPT_FS_MASK_SUPPORT); ++} ++ ++static int prep_l4_rule(struct dpaa2_eth_priv *priv, ++ struct ethtool_tcpip4_spec *l4_value, ++ struct ethtool_tcpip4_spec *l4_mask, ++ void *key, void *mask, u8 l4_proto) ++{ ++ int offset; ++ ++ if (l4_mask->tos) { ++ netdev_err(priv->net_dev, "ToS is not supported for IPv4 L4\n"); ++ return -EOPNOTSUPP; ++ } ++ ++ if (l4_mask->ip4src) { ++ offset = cls_key_off(priv, NET_PROT_IP, NH_FLD_IP_SRC); ++ *(u32 *)(key + offset) = l4_value->ip4src; ++ *(u32 *)(mask + offset) = l4_mask->ip4src; ++ } ++ ++ if (l4_mask->ip4dst) { ++ offset = cls_key_off(priv, NET_PROT_IP, NH_FLD_IP_DST); ++ *(u32 *)(key + offset) = l4_value->ip4dst; ++ *(u32 *)(mask + offset) = l4_mask->ip4dst; ++ } ++ ++ if (l4_mask->psrc) { ++ offset = cls_key_off(priv, NET_PROT_UDP, NH_FLD_UDP_PORT_SRC); ++ *(u32 *)(key + offset) = l4_value->psrc; ++ *(u32 *)(mask + offset) = l4_mask->psrc; ++ } ++ ++ if (l4_mask->pdst) { ++ offset = cls_key_off(priv, NET_PROT_UDP, NH_FLD_UDP_PORT_DST); ++ *(u32 *)(key + offset) = l4_value->pdst; ++ *(u32 *)(mask + offset) = l4_mask->pdst; ++ } ++ ++ /* Only apply the rule for the user-specified L4 protocol ++ * and if ethertype matches IPv4 ++ */ ++ offset = cls_key_off(priv, NET_PROT_ETH, NH_FLD_ETH_TYPE); ++ *(u16 *)(key + offset) = htons(ETH_P_IP); ++ *(u16 *)(mask + offset) = 0xFFFF; ++ ++ offset = cls_key_off(priv, NET_PROT_IP, NH_FLD_IP_PROTO); ++ *(u8 *)(key + offset) = l4_proto; ++ *(u8 *)(mask + offset) = 0xFF; ++ ++ /* TODO: check IP version */ ++ ++ return 0; ++} ++ ++static int prep_eth_rule(struct dpaa2_eth_priv *priv, ++ struct ethhdr *eth_value, struct ethhdr *eth_mask, ++ void *key, void *mask) ++{ ++ int offset; ++ ++ if (eth_mask->h_proto) { ++ netdev_err(priv->net_dev, "Ethertype is not supported!\n"); ++ return -EOPNOTSUPP; ++ } ++ ++ if (!is_zero_ether_addr(eth_mask->h_source)) { ++ offset = cls_key_off(priv, NET_PROT_ETH, NH_FLD_ETH_SA); ++ ether_addr_copy(key + offset, eth_value->h_source); ++ ether_addr_copy(mask + offset, eth_mask->h_source); ++ } ++ ++ if (!is_zero_ether_addr(eth_mask->h_dest)) { ++ offset = cls_key_off(priv, NET_PROT_ETH, NH_FLD_ETH_DA); ++ ether_addr_copy(key + offset, eth_value->h_dest); ++ ether_addr_copy(mask + offset, eth_mask->h_dest); ++ } ++ ++ return 0; ++} ++ ++static int prep_user_ip_rule(struct dpaa2_eth_priv *priv, ++ struct ethtool_usrip4_spec *uip_value, ++ struct ethtool_usrip4_spec *uip_mask, ++ void *key, void *mask) ++{ ++ int offset; ++ ++ if (uip_mask->tos) ++ return -EOPNOTSUPP; ++ ++ if (uip_mask->ip4src) { ++ offset = cls_key_off(priv, NET_PROT_IP, NH_FLD_IP_SRC); ++ *(u32 *)(key + offset) = uip_value->ip4src; ++ *(u32 *)(mask + offset) = uip_mask->ip4src; ++ } ++ ++ if (uip_mask->ip4dst) { ++ offset = cls_key_off(priv, NET_PROT_IP, NH_FLD_IP_DST); ++ *(u32 *)(key + offset) = uip_value->ip4dst; ++ *(u32 *)(mask + offset) = uip_mask->ip4dst; ++ } ++ ++ if (uip_mask->proto) { ++ offset = cls_key_off(priv, NET_PROT_IP, NH_FLD_IP_PROTO); ++ *(u32 *)(key + offset) = uip_value->proto; ++ *(u32 *)(mask + offset) = uip_mask->proto; ++ } ++ if (uip_mask->l4_4_bytes) { ++ offset = cls_key_off(priv, NET_PROT_UDP, NH_FLD_UDP_PORT_SRC); ++ *(u16 *)(key + offset) = uip_value->l4_4_bytes << 16; ++ *(u16 *)(mask + offset) = uip_mask->l4_4_bytes << 16; ++ ++ offset = cls_key_off(priv, NET_PROT_UDP, NH_FLD_UDP_PORT_DST); ++ *(u16 *)(key + offset) = uip_value->l4_4_bytes & 0xFFFF; ++ *(u16 *)(mask + offset) = uip_mask->l4_4_bytes & 0xFFFF; ++ } ++ ++ /* Ethertype must be IP */ ++ offset = cls_key_off(priv, NET_PROT_ETH, NH_FLD_ETH_TYPE); ++ *(u16 *)(key + offset) = htons(ETH_P_IP); ++ *(u16 *)(mask + offset) = 0xFFFF; ++ ++ return 0; ++} ++ ++static int prep_ext_rule(struct dpaa2_eth_priv *priv, ++ struct ethtool_flow_ext *ext_value, ++ struct ethtool_flow_ext *ext_mask, ++ void *key, void *mask) ++{ ++ int offset; ++ ++ if (ext_mask->vlan_etype) ++ return -EOPNOTSUPP; ++ ++ if (ext_mask->vlan_tci) { ++ offset = cls_key_off(priv, NET_PROT_VLAN, NH_FLD_VLAN_TCI); ++ *(u16 *)(key + offset) = ext_value->vlan_tci; ++ *(u16 *)(mask + offset) = ext_mask->vlan_tci; ++ } ++ ++ return 0; ++} ++ ++static int prep_mac_ext_rule(struct dpaa2_eth_priv *priv, ++ struct ethtool_flow_ext *ext_value, ++ struct ethtool_flow_ext *ext_mask, ++ void *key, void *mask) ++{ ++ int offset; ++ ++ if (!is_zero_ether_addr(ext_mask->h_dest)) { ++ offset = cls_key_off(priv, NET_PROT_ETH, NH_FLD_ETH_DA); ++ ether_addr_copy(key + offset, ext_value->h_dest); ++ ether_addr_copy(mask + offset, ext_mask->h_dest); ++ } ++ ++ return 0; ++} ++ ++static int prep_cls_rule(struct net_device *net_dev, ++ struct ethtool_rx_flow_spec *fs, ++ void *key) ++{ ++ struct dpaa2_eth_priv *priv = netdev_priv(net_dev); ++ const u8 key_size = cls_key_size(priv); ++ void *msk = key + key_size; ++ int err; ++ ++ memset(key, 0, key_size * 2); ++ ++ switch (fs->flow_type & 0xff) { ++ case TCP_V4_FLOW: ++ err = prep_l4_rule(priv, &fs->h_u.tcp_ip4_spec, ++ &fs->m_u.tcp_ip4_spec, key, msk, ++ IPPROTO_TCP); ++ break; ++ case UDP_V4_FLOW: ++ err = prep_l4_rule(priv, &fs->h_u.udp_ip4_spec, ++ &fs->m_u.udp_ip4_spec, key, msk, ++ IPPROTO_UDP); ++ break; ++ case SCTP_V4_FLOW: ++ err = prep_l4_rule(priv, &fs->h_u.sctp_ip4_spec, ++ &fs->m_u.sctp_ip4_spec, key, msk, ++ IPPROTO_SCTP); ++ break; ++ case ETHER_FLOW: ++ err = prep_eth_rule(priv, &fs->h_u.ether_spec, ++ &fs->m_u.ether_spec, key, msk); ++ break; ++ case IP_USER_FLOW: ++ err = prep_user_ip_rule(priv, &fs->h_u.usr_ip4_spec, ++ &fs->m_u.usr_ip4_spec, key, msk); ++ break; ++ default: ++ /* TODO: AH, ESP */ ++ return -EOPNOTSUPP; ++ } ++ if (err) ++ return err; ++ ++ if (fs->flow_type & FLOW_EXT) { ++ err = prep_ext_rule(priv, &fs->h_ext, &fs->m_ext, key, msk); ++ if (err) ++ return err; ++ } ++ ++ if (fs->flow_type & FLOW_MAC_EXT) { ++ err = prep_mac_ext_rule(priv, &fs->h_ext, &fs->m_ext, key, msk); ++ if (err) ++ return err; ++ } ++ ++ return 0; ++} ++ ++static int do_cls(struct net_device *net_dev, ++ struct ethtool_rx_flow_spec *fs, ++ bool add) ++{ ++ struct dpaa2_eth_priv *priv = netdev_priv(net_dev); ++ struct device *dev = net_dev->dev.parent; ++ const int rule_cnt = DPAA2_CLASSIFIER_ENTRY_COUNT; ++ struct dpni_rule_cfg rule_cfg; ++ void *dma_mem; ++ int err = 0; ++ ++ if (!dpaa2_eth_fs_enabled(priv)) { ++ netdev_err(net_dev, "dev does not support steering!\n"); ++ /* dev doesn't support steering */ ++ return -EOPNOTSUPP; ++ } ++ ++ if ((fs->ring_cookie != RX_CLS_FLOW_DISC && ++ fs->ring_cookie >= dpaa2_eth_queue_count(priv)) || ++ fs->location >= rule_cnt) ++ return -EINVAL; ++ ++ memset(&rule_cfg, 0, sizeof(rule_cfg)); ++ rule_cfg.key_size = cls_key_size(priv); ++ ++ /* allocate twice the key size, for the actual key and for mask */ ++ dma_mem = kzalloc(rule_cfg.key_size * 2, GFP_DMA | GFP_KERNEL); ++ if (!dma_mem) ++ return -ENOMEM; ++ ++ err = prep_cls_rule(net_dev, fs, dma_mem); ++ if (err) ++ goto err_free_mem; ++ ++ rule_cfg.key_iova = dma_map_single(dev, dma_mem, ++ rule_cfg.key_size * 2, ++ DMA_TO_DEVICE); ++ ++ rule_cfg.mask_iova = rule_cfg.key_iova + rule_cfg.key_size; ++ ++ /* No way to control rule order in firmware */ ++ if (add) ++ err = dpni_add_fs_entry(priv->mc_io, 0, priv->mc_token, 0, ++ &rule_cfg, (u16)fs->ring_cookie); ++ else ++ err = dpni_remove_fs_entry(priv->mc_io, 0, priv->mc_token, 0, ++ &rule_cfg); ++ ++ dma_unmap_single(dev, rule_cfg.key_iova, ++ rule_cfg.key_size * 2, DMA_TO_DEVICE); ++ if (err) { ++ netdev_err(net_dev, "dpaa2_add/remove_cls() error %d\n", err); ++ goto err_free_mem; ++ } ++ ++ priv->cls_rule[fs->location].fs = *fs; ++ priv->cls_rule[fs->location].in_use = true; ++ ++err_free_mem: ++ kfree(dma_mem); ++ ++ return err; ++} ++ ++static int add_cls(struct net_device *net_dev, ++ struct ethtool_rx_flow_spec *fs) ++{ ++ struct dpaa2_eth_priv *priv = netdev_priv(net_dev); ++ int err; ++ ++ err = do_cls(net_dev, fs, true); ++ if (err) ++ return err; ++ ++ priv->cls_rule[fs->location].in_use = true; ++ priv->cls_rule[fs->location].fs = *fs; ++ ++ return 0; ++} ++ ++static int del_cls(struct net_device *net_dev, int location) ++{ ++ struct dpaa2_eth_priv *priv = netdev_priv(net_dev); ++ int err; ++ ++ err = do_cls(net_dev, &priv->cls_rule[location].fs, false); ++ if (err) ++ return err; ++ ++ priv->cls_rule[location].in_use = false; ++ ++ return 0; ++} ++ ++static int dpaa2_eth_set_rxnfc(struct net_device *net_dev, ++ struct ethtool_rxnfc *rxnfc) ++{ ++ int err = 0; ++ ++ switch (rxnfc->cmd) { ++ case ETHTOOL_SRXCLSRLINS: ++ err = add_cls(net_dev, &rxnfc->fs); ++ break; ++ ++ case ETHTOOL_SRXCLSRLDEL: ++ err = del_cls(net_dev, rxnfc->fs.location); ++ break; ++ ++ default: ++ err = -EOPNOTSUPP; ++ } ++ ++ return err; ++} ++ ++static int dpaa2_eth_get_rxnfc(struct net_device *net_dev, ++ struct ethtool_rxnfc *rxnfc, u32 *rule_locs) ++{ ++ struct dpaa2_eth_priv *priv = netdev_priv(net_dev); ++ const int rule_cnt = DPAA2_CLASSIFIER_ENTRY_COUNT; ++ int i, j; ++ ++ switch (rxnfc->cmd) { ++ case ETHTOOL_GRXFH: ++ /* we purposely ignore cmd->flow_type, because the hashing key ++ * is the same (and fixed) for all protocols ++ */ ++ rxnfc->data = priv->rx_flow_hash; ++ break; ++ ++ case ETHTOOL_GRXRINGS: ++ rxnfc->data = dpaa2_eth_queue_count(priv); ++ break; ++ ++ case ETHTOOL_GRXCLSRLCNT: ++ for (i = 0, rxnfc->rule_cnt = 0; i < rule_cnt; i++) ++ if (priv->cls_rule[i].in_use) ++ rxnfc->rule_cnt++; ++ rxnfc->data = rule_cnt; ++ break; ++ ++ case ETHTOOL_GRXCLSRULE: ++ if (!priv->cls_rule[rxnfc->fs.location].in_use) ++ return -EINVAL; ++ ++ rxnfc->fs = priv->cls_rule[rxnfc->fs.location].fs; ++ break; ++ ++ case ETHTOOL_GRXCLSRLALL: ++ for (i = 0, j = 0; i < rule_cnt; i++) { ++ if (!priv->cls_rule[i].in_use) ++ continue; ++ if (j == rxnfc->rule_cnt) ++ return -EMSGSIZE; ++ rule_locs[j++] = i; ++ } ++ rxnfc->rule_cnt = j; ++ rxnfc->data = rule_cnt; ++ break; ++ ++ default: ++ return -EOPNOTSUPP; ++ } ++ ++ return 0; ++} ++ ++const struct ethtool_ops dpaa2_ethtool_ops = { ++ .get_drvinfo = dpaa2_eth_get_drvinfo, ++ .get_link = ethtool_op_get_link, ++ .get_settings = dpaa2_eth_get_settings, ++ .set_settings = dpaa2_eth_set_settings, ++ .get_sset_count = dpaa2_eth_get_sset_count, ++ .get_ethtool_stats = dpaa2_eth_get_ethtool_stats, ++ .get_strings = dpaa2_eth_get_strings, ++ .get_rxnfc = dpaa2_eth_get_rxnfc, ++ .set_rxnfc = dpaa2_eth_set_rxnfc, ++}; +diff --git a/drivers/staging/fsl-dpaa2/ethernet/dpkg.h b/drivers/staging/fsl-dpaa2/ethernet/dpkg.h +new file mode 100644 +index 0000000..92ec12b +--- /dev/null ++++ b/drivers/staging/fsl-dpaa2/ethernet/dpkg.h +@@ -0,0 +1,175 @@ ++/* Copyright 2013-2015 Freescale Semiconductor Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * * Neither the name of the above-listed copyright holders nor the ++ * names of any contributors may be used to endorse or promote products ++ * derived from this software without specific prior written permission. ++ * ++ * ++ * ALTERNATIVELY, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") as published by the Free Software ++ * Foundation, either version 2 of that License or (at your option) any ++ * later version. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE ++ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++#ifndef __FSL_DPKG_H_ ++#define __FSL_DPKG_H_ ++ ++#include ++#include "../../fsl-mc/include/net.h" ++ ++/* Data Path Key Generator API ++ * Contains initialization APIs and runtime APIs for the Key Generator ++ */ ++ ++/** Key Generator properties */ ++ ++/** ++ * Number of masks per key extraction ++ */ ++#define DPKG_NUM_OF_MASKS 4 ++/** ++ * Number of extractions per key profile ++ */ ++#define DPKG_MAX_NUM_OF_EXTRACTS 10 ++ ++/** ++ * enum dpkg_extract_from_hdr_type - Selecting extraction by header types ++ * @DPKG_FROM_HDR: Extract selected bytes from header, by offset ++ * @DPKG_FROM_FIELD: Extract selected bytes from header, by offset from field ++ * @DPKG_FULL_FIELD: Extract a full field ++ */ ++enum dpkg_extract_from_hdr_type { ++ DPKG_FROM_HDR = 0, ++ DPKG_FROM_FIELD = 1, ++ DPKG_FULL_FIELD = 2 ++}; ++ ++/** ++ * enum dpkg_extract_type - Enumeration for selecting extraction type ++ * @DPKG_EXTRACT_FROM_HDR: Extract from the header ++ * @DPKG_EXTRACT_FROM_DATA: Extract from data not in specific header ++ * @DPKG_EXTRACT_FROM_PARSE: Extract from parser-result; ++ * e.g. can be used to extract header existence; ++ * please refer to 'Parse Result definition' section in the parser BG ++ */ ++enum dpkg_extract_type { ++ DPKG_EXTRACT_FROM_HDR = 0, ++ DPKG_EXTRACT_FROM_DATA = 1, ++ DPKG_EXTRACT_FROM_PARSE = 3 ++}; ++ ++/** ++ * struct dpkg_mask - A structure for defining a single extraction mask ++ * @mask: Byte mask for the extracted content ++ * @offset: Offset within the extracted content ++ */ ++struct dpkg_mask { ++ uint8_t mask; ++ uint8_t offset; ++}; ++ ++/** ++ * struct dpkg_extract - A structure for defining a single extraction ++ * @type: Determines how the union below is interpreted: ++ * DPKG_EXTRACT_FROM_HDR: selects 'from_hdr'; ++ * DPKG_EXTRACT_FROM_DATA: selects 'from_data'; ++ * DPKG_EXTRACT_FROM_PARSE: selects 'from_parse' ++ * @extract: Selects extraction method ++ * @num_of_byte_masks: Defines the number of valid entries in the array below; ++ * This is also the number of bytes to be used as masks ++ * @masks: Masks parameters ++ */ ++struct dpkg_extract { ++ enum dpkg_extract_type type; ++ /** ++ * union extract - Selects extraction method ++ * @from_hdr - Used when 'type = DPKG_EXTRACT_FROM_HDR' ++ * @from_data - Used when 'type = DPKG_EXTRACT_FROM_DATA' ++ * @from_parse - Used when 'type = DPKG_EXTRACT_FROM_PARSE' ++ */ ++ union { ++ /** ++ * struct from_hdr - Used when 'type = DPKG_EXTRACT_FROM_HDR' ++ * @prot: Any of the supported headers ++ * @type: Defines the type of header extraction: ++ * DPKG_FROM_HDR: use size & offset below; ++ * DPKG_FROM_FIELD: use field, size and offset below; ++ * DPKG_FULL_FIELD: use field below ++ * @field: One of the supported fields (NH_FLD_) ++ * ++ * @size: Size in bytes ++ * @offset: Byte offset ++ * @hdr_index: Clear for cases not listed below; ++ * Used for protocols that may have more than a single ++ * header, 0 indicates an outer header; ++ * Supported protocols (possible values): ++ * NET_PROT_VLAN (0, HDR_INDEX_LAST); ++ * NET_PROT_MPLS (0, 1, HDR_INDEX_LAST); ++ * NET_PROT_IP(0, HDR_INDEX_LAST); ++ * NET_PROT_IPv4(0, HDR_INDEX_LAST); ++ * NET_PROT_IPv6(0, HDR_INDEX_LAST); ++ */ ++ ++ struct { ++ enum net_prot prot; ++ enum dpkg_extract_from_hdr_type type; ++ uint32_t field; ++ uint8_t size; ++ uint8_t offset; ++ uint8_t hdr_index; ++ } from_hdr; ++ /** ++ * struct from_data - Used when 'type = DPKG_EXTRACT_FROM_DATA' ++ * @size: Size in bytes ++ * @offset: Byte offset ++ */ ++ struct { ++ uint8_t size; ++ uint8_t offset; ++ } from_data; ++ ++ /** ++ * struct from_parse - Used when 'type = DPKG_EXTRACT_FROM_PARSE' ++ * @size: Size in bytes ++ * @offset: Byte offset ++ */ ++ struct { ++ uint8_t size; ++ uint8_t offset; ++ } from_parse; ++ } extract; ++ ++ uint8_t num_of_byte_masks; ++ struct dpkg_mask masks[DPKG_NUM_OF_MASKS]; ++}; ++ ++/** ++ * struct dpkg_profile_cfg - A structure for defining a full Key Generation ++ * profile (rule) ++ * @num_extracts: Defines the number of valid entries in the array below ++ * @extracts: Array of required extractions ++ */ ++struct dpkg_profile_cfg { ++ uint8_t num_extracts; ++ struct dpkg_extract extracts[DPKG_MAX_NUM_OF_EXTRACTS]; ++}; ++ ++#endif /* __FSL_DPKG_H_ */ +diff --git a/drivers/staging/fsl-dpaa2/ethernet/dpni-cmd.h b/drivers/staging/fsl-dpaa2/ethernet/dpni-cmd.h +new file mode 100644 +index 0000000..c0f8af0 +--- /dev/null ++++ b/drivers/staging/fsl-dpaa2/ethernet/dpni-cmd.h +@@ -0,0 +1,1058 @@ ++/* Copyright 2013-2015 Freescale Semiconductor Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * * Neither the name of the above-listed copyright holders nor the ++ * names of any contributors may be used to endorse or promote products ++ * derived from this software without specific prior written permission. ++ * ++ * ++ * ALTERNATIVELY, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") as published by the Free Software ++ * Foundation, either version 2 of that License or (at your option) any ++ * later version. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE ++ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++#ifndef _FSL_DPNI_CMD_H ++#define _FSL_DPNI_CMD_H ++ ++/* DPNI Version */ ++#define DPNI_VER_MAJOR 6 ++#define DPNI_VER_MINOR 0 ++ ++/* Command IDs */ ++#define DPNI_CMDID_OPEN 0x801 ++#define DPNI_CMDID_CLOSE 0x800 ++#define DPNI_CMDID_CREATE 0x901 ++#define DPNI_CMDID_DESTROY 0x900 ++ ++#define DPNI_CMDID_ENABLE 0x002 ++#define DPNI_CMDID_DISABLE 0x003 ++#define DPNI_CMDID_GET_ATTR 0x004 ++#define DPNI_CMDID_RESET 0x005 ++#define DPNI_CMDID_IS_ENABLED 0x006 ++ ++#define DPNI_CMDID_SET_IRQ 0x010 ++#define DPNI_CMDID_GET_IRQ 0x011 ++#define DPNI_CMDID_SET_IRQ_ENABLE 0x012 ++#define DPNI_CMDID_GET_IRQ_ENABLE 0x013 ++#define DPNI_CMDID_SET_IRQ_MASK 0x014 ++#define DPNI_CMDID_GET_IRQ_MASK 0x015 ++#define DPNI_CMDID_GET_IRQ_STATUS 0x016 ++#define DPNI_CMDID_CLEAR_IRQ_STATUS 0x017 ++ ++#define DPNI_CMDID_SET_POOLS 0x200 ++#define DPNI_CMDID_GET_RX_BUFFER_LAYOUT 0x201 ++#define DPNI_CMDID_SET_RX_BUFFER_LAYOUT 0x202 ++#define DPNI_CMDID_GET_TX_BUFFER_LAYOUT 0x203 ++#define DPNI_CMDID_SET_TX_BUFFER_LAYOUT 0x204 ++#define DPNI_CMDID_SET_TX_CONF_BUFFER_LAYOUT 0x205 ++#define DPNI_CMDID_GET_TX_CONF_BUFFER_LAYOUT 0x206 ++#define DPNI_CMDID_SET_L3_CHKSUM_VALIDATION 0x207 ++#define DPNI_CMDID_GET_L3_CHKSUM_VALIDATION 0x208 ++#define DPNI_CMDID_SET_L4_CHKSUM_VALIDATION 0x209 ++#define DPNI_CMDID_GET_L4_CHKSUM_VALIDATION 0x20A ++#define DPNI_CMDID_SET_ERRORS_BEHAVIOR 0x20B ++#define DPNI_CMDID_SET_TX_CONF_REVOKE 0x20C ++ ++#define DPNI_CMDID_GET_QDID 0x210 ++#define DPNI_CMDID_GET_SP_INFO 0x211 ++#define DPNI_CMDID_GET_TX_DATA_OFFSET 0x212 ++#define DPNI_CMDID_GET_COUNTER 0x213 ++#define DPNI_CMDID_SET_COUNTER 0x214 ++#define DPNI_CMDID_GET_LINK_STATE 0x215 ++#define DPNI_CMDID_SET_MAX_FRAME_LENGTH 0x216 ++#define DPNI_CMDID_GET_MAX_FRAME_LENGTH 0x217 ++#define DPNI_CMDID_SET_MTU 0x218 ++#define DPNI_CMDID_GET_MTU 0x219 ++#define DPNI_CMDID_SET_LINK_CFG 0x21A ++#define DPNI_CMDID_SET_TX_SHAPING 0x21B ++ ++#define DPNI_CMDID_SET_MCAST_PROMISC 0x220 ++#define DPNI_CMDID_GET_MCAST_PROMISC 0x221 ++#define DPNI_CMDID_SET_UNICAST_PROMISC 0x222 ++#define DPNI_CMDID_GET_UNICAST_PROMISC 0x223 ++#define DPNI_CMDID_SET_PRIM_MAC 0x224 ++#define DPNI_CMDID_GET_PRIM_MAC 0x225 ++#define DPNI_CMDID_ADD_MAC_ADDR 0x226 ++#define DPNI_CMDID_REMOVE_MAC_ADDR 0x227 ++#define DPNI_CMDID_CLR_MAC_FILTERS 0x228 ++ ++#define DPNI_CMDID_SET_VLAN_FILTERS 0x230 ++#define DPNI_CMDID_ADD_VLAN_ID 0x231 ++#define DPNI_CMDID_REMOVE_VLAN_ID 0x232 ++#define DPNI_CMDID_CLR_VLAN_FILTERS 0x233 ++ ++#define DPNI_CMDID_SET_RX_TC_DIST 0x235 ++#define DPNI_CMDID_SET_TX_FLOW 0x236 ++#define DPNI_CMDID_GET_TX_FLOW 0x237 ++#define DPNI_CMDID_SET_RX_FLOW 0x238 ++#define DPNI_CMDID_GET_RX_FLOW 0x239 ++#define DPNI_CMDID_SET_RX_ERR_QUEUE 0x23A ++#define DPNI_CMDID_GET_RX_ERR_QUEUE 0x23B ++ ++#define DPNI_CMDID_SET_RX_TC_POLICING 0x23E ++#define DPNI_CMDID_SET_RX_TC_EARLY_DROP 0x23F ++ ++#define DPNI_CMDID_SET_QOS_TBL 0x240 ++#define DPNI_CMDID_ADD_QOS_ENT 0x241 ++#define DPNI_CMDID_REMOVE_QOS_ENT 0x242 ++#define DPNI_CMDID_CLR_QOS_TBL 0x243 ++#define DPNI_CMDID_ADD_FS_ENT 0x244 ++#define DPNI_CMDID_REMOVE_FS_ENT 0x245 ++#define DPNI_CMDID_CLR_FS_ENT 0x246 ++#define DPNI_CMDID_SET_VLAN_INSERTION 0x247 ++#define DPNI_CMDID_SET_VLAN_REMOVAL 0x248 ++#define DPNI_CMDID_SET_IPR 0x249 ++#define DPNI_CMDID_SET_IPF 0x24A ++ ++#define DPNI_CMDID_SET_TX_SELECTION 0x250 ++#define DPNI_CMDID_GET_RX_TC_POLICING 0x251 ++#define DPNI_CMDID_GET_RX_TC_EARLY_DROP 0x252 ++#define DPNI_CMDID_SET_RX_TC_CONGESTION_NOTIFICATION 0x253 ++#define DPNI_CMDID_GET_RX_TC_CONGESTION_NOTIFICATION 0x254 ++#define DPNI_CMDID_SET_TX_TC_CONGESTION_NOTIFICATION 0x255 ++#define DPNI_CMDID_GET_TX_TC_CONGESTION_NOTIFICATION 0x256 ++#define DPNI_CMDID_SET_TX_CONF 0x257 ++#define DPNI_CMDID_GET_TX_CONF 0x258 ++#define DPNI_CMDID_SET_TX_CONF_CONGESTION_NOTIFICATION 0x259 ++#define DPNI_CMDID_GET_TX_CONF_CONGESTION_NOTIFICATION 0x25A ++#define DPNI_CMDID_SET_TX_TC_EARLY_DROP 0x25B ++#define DPNI_CMDID_GET_TX_TC_EARLY_DROP 0x25C ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_CMD_OPEN(cmd, dpni_id) \ ++ MC_CMD_OP(cmd, 0, 0, 32, int, dpni_id) ++ ++#define DPNI_PREP_EXTENDED_CFG(ext, cfg) \ ++do { \ ++ MC_PREP_OP(ext, 0, 0, 16, uint16_t, cfg->tc_cfg[0].max_dist); \ ++ MC_PREP_OP(ext, 0, 16, 16, uint16_t, cfg->tc_cfg[0].max_fs_entries); \ ++ MC_PREP_OP(ext, 0, 32, 16, uint16_t, cfg->tc_cfg[1].max_dist); \ ++ MC_PREP_OP(ext, 0, 48, 16, uint16_t, cfg->tc_cfg[1].max_fs_entries); \ ++ MC_PREP_OP(ext, 1, 0, 16, uint16_t, cfg->tc_cfg[2].max_dist); \ ++ MC_PREP_OP(ext, 1, 16, 16, uint16_t, cfg->tc_cfg[2].max_fs_entries); \ ++ MC_PREP_OP(ext, 1, 32, 16, uint16_t, cfg->tc_cfg[3].max_dist); \ ++ MC_PREP_OP(ext, 1, 48, 16, uint16_t, cfg->tc_cfg[3].max_fs_entries); \ ++ MC_PREP_OP(ext, 2, 0, 16, uint16_t, cfg->tc_cfg[4].max_dist); \ ++ MC_PREP_OP(ext, 2, 16, 16, uint16_t, cfg->tc_cfg[4].max_fs_entries); \ ++ MC_PREP_OP(ext, 2, 32, 16, uint16_t, cfg->tc_cfg[5].max_dist); \ ++ MC_PREP_OP(ext, 2, 48, 16, uint16_t, cfg->tc_cfg[5].max_fs_entries); \ ++ MC_PREP_OP(ext, 3, 0, 16, uint16_t, cfg->tc_cfg[6].max_dist); \ ++ MC_PREP_OP(ext, 3, 16, 16, uint16_t, cfg->tc_cfg[6].max_fs_entries); \ ++ MC_PREP_OP(ext, 3, 32, 16, uint16_t, cfg->tc_cfg[7].max_dist); \ ++ MC_PREP_OP(ext, 3, 48, 16, uint16_t, cfg->tc_cfg[7].max_fs_entries); \ ++ MC_PREP_OP(ext, 4, 0, 16, uint16_t, \ ++ cfg->ipr_cfg.max_open_frames_ipv4); \ ++ MC_PREP_OP(ext, 4, 16, 16, uint16_t, \ ++ cfg->ipr_cfg.max_open_frames_ipv6); \ ++ MC_PREP_OP(ext, 4, 32, 16, uint16_t, \ ++ cfg->ipr_cfg.max_reass_frm_size); \ ++ MC_PREP_OP(ext, 5, 0, 16, uint16_t, \ ++ cfg->ipr_cfg.min_frag_size_ipv4); \ ++ MC_PREP_OP(ext, 5, 16, 16, uint16_t, \ ++ cfg->ipr_cfg.min_frag_size_ipv6); \ ++} while (0) ++ ++#define DPNI_EXT_EXTENDED_CFG(ext, cfg) \ ++do { \ ++ MC_EXT_OP(ext, 0, 0, 16, uint16_t, cfg->tc_cfg[0].max_dist); \ ++ MC_EXT_OP(ext, 0, 16, 16, uint16_t, cfg->tc_cfg[0].max_fs_entries); \ ++ MC_EXT_OP(ext, 0, 32, 16, uint16_t, cfg->tc_cfg[1].max_dist); \ ++ MC_EXT_OP(ext, 0, 48, 16, uint16_t, cfg->tc_cfg[1].max_fs_entries); \ ++ MC_EXT_OP(ext, 1, 0, 16, uint16_t, cfg->tc_cfg[2].max_dist); \ ++ MC_EXT_OP(ext, 1, 16, 16, uint16_t, cfg->tc_cfg[2].max_fs_entries); \ ++ MC_EXT_OP(ext, 1, 32, 16, uint16_t, cfg->tc_cfg[3].max_dist); \ ++ MC_EXT_OP(ext, 1, 48, 16, uint16_t, cfg->tc_cfg[3].max_fs_entries); \ ++ MC_EXT_OP(ext, 2, 0, 16, uint16_t, cfg->tc_cfg[4].max_dist); \ ++ MC_EXT_OP(ext, 2, 16, 16, uint16_t, cfg->tc_cfg[4].max_fs_entries); \ ++ MC_EXT_OP(ext, 2, 32, 16, uint16_t, cfg->tc_cfg[5].max_dist); \ ++ MC_EXT_OP(ext, 2, 48, 16, uint16_t, cfg->tc_cfg[5].max_fs_entries); \ ++ MC_EXT_OP(ext, 3, 0, 16, uint16_t, cfg->tc_cfg[6].max_dist); \ ++ MC_EXT_OP(ext, 3, 16, 16, uint16_t, cfg->tc_cfg[6].max_fs_entries); \ ++ MC_EXT_OP(ext, 3, 32, 16, uint16_t, cfg->tc_cfg[7].max_dist); \ ++ MC_EXT_OP(ext, 3, 48, 16, uint16_t, cfg->tc_cfg[7].max_fs_entries); \ ++ MC_EXT_OP(ext, 4, 0, 16, uint16_t, \ ++ cfg->ipr_cfg.max_open_frames_ipv4); \ ++ MC_EXT_OP(ext, 4, 16, 16, uint16_t, \ ++ cfg->ipr_cfg.max_open_frames_ipv6); \ ++ MC_EXT_OP(ext, 4, 32, 16, uint16_t, \ ++ cfg->ipr_cfg.max_reass_frm_size); \ ++ MC_EXT_OP(ext, 5, 0, 16, uint16_t, \ ++ cfg->ipr_cfg.min_frag_size_ipv4); \ ++ MC_EXT_OP(ext, 5, 16, 16, uint16_t, \ ++ cfg->ipr_cfg.min_frag_size_ipv6); \ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_CMD_CREATE(cmd, cfg) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 0, 8, uint8_t, cfg->adv.max_tcs); \ ++ MC_CMD_OP(cmd, 0, 8, 8, uint8_t, cfg->adv.max_senders); \ ++ MC_CMD_OP(cmd, 0, 16, 8, uint8_t, cfg->mac_addr[5]); \ ++ MC_CMD_OP(cmd, 0, 24, 8, uint8_t, cfg->mac_addr[4]); \ ++ MC_CMD_OP(cmd, 0, 32, 8, uint8_t, cfg->mac_addr[3]); \ ++ MC_CMD_OP(cmd, 0, 40, 8, uint8_t, cfg->mac_addr[2]); \ ++ MC_CMD_OP(cmd, 0, 48, 8, uint8_t, cfg->mac_addr[1]); \ ++ MC_CMD_OP(cmd, 0, 56, 8, uint8_t, cfg->mac_addr[0]); \ ++ MC_CMD_OP(cmd, 1, 0, 32, uint32_t, cfg->adv.options); \ ++ MC_CMD_OP(cmd, 2, 0, 8, uint8_t, cfg->adv.max_unicast_filters); \ ++ MC_CMD_OP(cmd, 2, 8, 8, uint8_t, cfg->adv.max_multicast_filters); \ ++ MC_CMD_OP(cmd, 2, 16, 8, uint8_t, cfg->adv.max_vlan_filters); \ ++ MC_CMD_OP(cmd, 2, 24, 8, uint8_t, cfg->adv.max_qos_entries); \ ++ MC_CMD_OP(cmd, 2, 32, 8, uint8_t, cfg->adv.max_qos_key_size); \ ++ MC_CMD_OP(cmd, 2, 48, 8, uint8_t, cfg->adv.max_dist_key_size); \ ++ MC_CMD_OP(cmd, 2, 56, 8, enum net_prot, cfg->adv.start_hdr); \ ++ MC_CMD_OP(cmd, 4, 48, 8, uint8_t, cfg->adv.max_policers); \ ++ MC_CMD_OP(cmd, 4, 56, 8, uint8_t, cfg->adv.max_congestion_ctrl); \ ++ MC_CMD_OP(cmd, 5, 0, 64, uint64_t, cfg->adv.ext_cfg_iova); \ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_CMD_SET_POOLS(cmd, cfg) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 0, 8, uint8_t, cfg->num_dpbp); \ ++ MC_CMD_OP(cmd, 0, 8, 1, int, cfg->pools[0].backup_pool); \ ++ MC_CMD_OP(cmd, 0, 9, 1, int, cfg->pools[1].backup_pool); \ ++ MC_CMD_OP(cmd, 0, 10, 1, int, cfg->pools[2].backup_pool); \ ++ MC_CMD_OP(cmd, 0, 11, 1, int, cfg->pools[3].backup_pool); \ ++ MC_CMD_OP(cmd, 0, 12, 1, int, cfg->pools[4].backup_pool); \ ++ MC_CMD_OP(cmd, 0, 13, 1, int, cfg->pools[5].backup_pool); \ ++ MC_CMD_OP(cmd, 0, 14, 1, int, cfg->pools[6].backup_pool); \ ++ MC_CMD_OP(cmd, 0, 15, 1, int, cfg->pools[7].backup_pool); \ ++ MC_CMD_OP(cmd, 0, 32, 32, int, cfg->pools[0].dpbp_id); \ ++ MC_CMD_OP(cmd, 4, 32, 16, uint16_t, cfg->pools[0].buffer_size);\ ++ MC_CMD_OP(cmd, 1, 0, 32, int, cfg->pools[1].dpbp_id); \ ++ MC_CMD_OP(cmd, 4, 48, 16, uint16_t, cfg->pools[1].buffer_size);\ ++ MC_CMD_OP(cmd, 1, 32, 32, int, cfg->pools[2].dpbp_id); \ ++ MC_CMD_OP(cmd, 5, 0, 16, uint16_t, cfg->pools[2].buffer_size);\ ++ MC_CMD_OP(cmd, 2, 0, 32, int, cfg->pools[3].dpbp_id); \ ++ MC_CMD_OP(cmd, 5, 16, 16, uint16_t, cfg->pools[3].buffer_size);\ ++ MC_CMD_OP(cmd, 2, 32, 32, int, cfg->pools[4].dpbp_id); \ ++ MC_CMD_OP(cmd, 5, 32, 16, uint16_t, cfg->pools[4].buffer_size);\ ++ MC_CMD_OP(cmd, 3, 0, 32, int, cfg->pools[5].dpbp_id); \ ++ MC_CMD_OP(cmd, 5, 48, 16, uint16_t, cfg->pools[5].buffer_size);\ ++ MC_CMD_OP(cmd, 3, 32, 32, int, cfg->pools[6].dpbp_id); \ ++ MC_CMD_OP(cmd, 6, 0, 16, uint16_t, cfg->pools[6].buffer_size);\ ++ MC_CMD_OP(cmd, 4, 0, 32, int, cfg->pools[7].dpbp_id); \ ++ MC_CMD_OP(cmd, 6, 16, 16, uint16_t, cfg->pools[7].buffer_size);\ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_RSP_IS_ENABLED(cmd, en) \ ++ MC_RSP_OP(cmd, 0, 0, 1, int, en) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_CMD_SET_IRQ(cmd, irq_index, irq_cfg) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 0, 32, uint32_t, irq_cfg->val); \ ++ MC_CMD_OP(cmd, 0, 32, 8, uint8_t, irq_index); \ ++ MC_CMD_OP(cmd, 1, 0, 64, uint64_t, irq_cfg->addr); \ ++ MC_CMD_OP(cmd, 2, 0, 32, int, irq_cfg->irq_num); \ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_CMD_GET_IRQ(cmd, irq_index) \ ++ MC_CMD_OP(cmd, 0, 32, 8, uint8_t, irq_index) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_RSP_GET_IRQ(cmd, type, irq_cfg) \ ++do { \ ++ MC_RSP_OP(cmd, 0, 0, 32, uint32_t, irq_cfg->val); \ ++ MC_RSP_OP(cmd, 1, 0, 64, uint64_t, irq_cfg->addr); \ ++ MC_RSP_OP(cmd, 2, 0, 32, int, irq_cfg->irq_num); \ ++ MC_RSP_OP(cmd, 2, 32, 32, int, type); \ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_CMD_SET_IRQ_ENABLE(cmd, irq_index, en) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 0, 8, uint8_t, en); \ ++ MC_CMD_OP(cmd, 0, 32, 8, uint8_t, irq_index);\ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_CMD_GET_IRQ_ENABLE(cmd, irq_index) \ ++ MC_CMD_OP(cmd, 0, 32, 8, uint8_t, irq_index) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_RSP_GET_IRQ_ENABLE(cmd, en) \ ++ MC_RSP_OP(cmd, 0, 0, 8, uint8_t, en) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_CMD_SET_IRQ_MASK(cmd, irq_index, mask) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 0, 32, uint32_t, mask); \ ++ MC_CMD_OP(cmd, 0, 32, 8, uint8_t, irq_index);\ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_CMD_GET_IRQ_MASK(cmd, irq_index) \ ++ MC_CMD_OP(cmd, 0, 32, 8, uint8_t, irq_index) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_RSP_GET_IRQ_MASK(cmd, mask) \ ++ MC_RSP_OP(cmd, 0, 0, 32, uint32_t, mask) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_CMD_GET_IRQ_STATUS(cmd, irq_index, status) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 0, 32, uint32_t, status);\ ++ MC_CMD_OP(cmd, 0, 32, 8, uint8_t, irq_index);\ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_RSP_GET_IRQ_STATUS(cmd, status) \ ++ MC_RSP_OP(cmd, 0, 0, 32, uint32_t, status) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_CMD_CLEAR_IRQ_STATUS(cmd, irq_index, status) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 0, 32, uint32_t, status); \ ++ MC_CMD_OP(cmd, 0, 32, 8, uint8_t, irq_index);\ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_CMD_GET_ATTR(cmd, attr) \ ++ MC_CMD_OP(cmd, 6, 0, 64, uint64_t, attr->ext_cfg_iova) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_RSP_GET_ATTR(cmd, attr) \ ++do { \ ++ MC_RSP_OP(cmd, 0, 0, 32, int, attr->id);\ ++ MC_RSP_OP(cmd, 0, 32, 8, uint8_t, attr->max_tcs); \ ++ MC_RSP_OP(cmd, 0, 40, 8, uint8_t, attr->max_senders); \ ++ MC_RSP_OP(cmd, 0, 48, 8, enum net_prot, attr->start_hdr); \ ++ MC_RSP_OP(cmd, 1, 0, 32, uint32_t, attr->options); \ ++ MC_RSP_OP(cmd, 2, 0, 8, uint8_t, attr->max_unicast_filters); \ ++ MC_RSP_OP(cmd, 2, 8, 8, uint8_t, attr->max_multicast_filters);\ ++ MC_RSP_OP(cmd, 2, 16, 8, uint8_t, attr->max_vlan_filters); \ ++ MC_RSP_OP(cmd, 2, 24, 8, uint8_t, attr->max_qos_entries); \ ++ MC_RSP_OP(cmd, 2, 32, 8, uint8_t, attr->max_qos_key_size); \ ++ MC_RSP_OP(cmd, 2, 40, 8, uint8_t, attr->max_dist_key_size); \ ++ MC_RSP_OP(cmd, 4, 48, 8, uint8_t, attr->max_policers); \ ++ MC_RSP_OP(cmd, 4, 56, 8, uint8_t, attr->max_congestion_ctrl); \ ++ MC_RSP_OP(cmd, 5, 32, 16, uint16_t, attr->version.major);\ ++ MC_RSP_OP(cmd, 5, 48, 16, uint16_t, attr->version.minor);\ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_CMD_SET_ERRORS_BEHAVIOR(cmd, cfg) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 0, 32, uint32_t, cfg->errors); \ ++ MC_CMD_OP(cmd, 0, 32, 4, enum dpni_error_action, cfg->error_action); \ ++ MC_CMD_OP(cmd, 0, 36, 1, int, cfg->set_frame_annotation); \ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_RSP_GET_RX_BUFFER_LAYOUT(cmd, layout) \ ++do { \ ++ MC_RSP_OP(cmd, 0, 0, 16, uint16_t, layout->private_data_size); \ ++ MC_RSP_OP(cmd, 0, 16, 16, uint16_t, layout->data_align); \ ++ MC_RSP_OP(cmd, 1, 0, 1, int, layout->pass_timestamp); \ ++ MC_RSP_OP(cmd, 1, 1, 1, int, layout->pass_parser_result); \ ++ MC_RSP_OP(cmd, 1, 2, 1, int, layout->pass_frame_status); \ ++ MC_RSP_OP(cmd, 1, 16, 16, uint16_t, layout->data_head_room); \ ++ MC_RSP_OP(cmd, 1, 32, 16, uint16_t, layout->data_tail_room); \ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_CMD_SET_RX_BUFFER_LAYOUT(cmd, layout) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 0, 16, uint16_t, layout->private_data_size); \ ++ MC_CMD_OP(cmd, 0, 16, 16, uint16_t, layout->data_align); \ ++ MC_CMD_OP(cmd, 0, 32, 32, uint32_t, layout->options); \ ++ MC_CMD_OP(cmd, 1, 0, 1, int, layout->pass_timestamp); \ ++ MC_CMD_OP(cmd, 1, 1, 1, int, layout->pass_parser_result); \ ++ MC_CMD_OP(cmd, 1, 2, 1, int, layout->pass_frame_status); \ ++ MC_CMD_OP(cmd, 1, 16, 16, uint16_t, layout->data_head_room); \ ++ MC_CMD_OP(cmd, 1, 32, 16, uint16_t, layout->data_tail_room); \ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_RSP_GET_TX_BUFFER_LAYOUT(cmd, layout) \ ++do { \ ++ MC_RSP_OP(cmd, 0, 0, 16, uint16_t, layout->private_data_size); \ ++ MC_RSP_OP(cmd, 0, 16, 16, uint16_t, layout->data_align); \ ++ MC_RSP_OP(cmd, 1, 0, 1, int, layout->pass_timestamp); \ ++ MC_RSP_OP(cmd, 1, 1, 1, int, layout->pass_parser_result); \ ++ MC_RSP_OP(cmd, 1, 2, 1, int, layout->pass_frame_status); \ ++ MC_RSP_OP(cmd, 1, 16, 16, uint16_t, layout->data_head_room); \ ++ MC_RSP_OP(cmd, 1, 32, 16, uint16_t, layout->data_tail_room); \ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_CMD_SET_TX_BUFFER_LAYOUT(cmd, layout) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 0, 16, uint16_t, layout->private_data_size); \ ++ MC_CMD_OP(cmd, 0, 16, 16, uint16_t, layout->data_align); \ ++ MC_CMD_OP(cmd, 0, 32, 32, uint32_t, layout->options); \ ++ MC_CMD_OP(cmd, 1, 0, 1, int, layout->pass_timestamp); \ ++ MC_CMD_OP(cmd, 1, 1, 1, int, layout->pass_parser_result); \ ++ MC_CMD_OP(cmd, 1, 2, 1, int, layout->pass_frame_status); \ ++ MC_CMD_OP(cmd, 1, 16, 16, uint16_t, layout->data_head_room); \ ++ MC_CMD_OP(cmd, 1, 32, 16, uint16_t, layout->data_tail_room); \ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_RSP_GET_TX_CONF_BUFFER_LAYOUT(cmd, layout) \ ++do { \ ++ MC_RSP_OP(cmd, 0, 0, 16, uint16_t, layout->private_data_size); \ ++ MC_RSP_OP(cmd, 0, 16, 16, uint16_t, layout->data_align); \ ++ MC_RSP_OP(cmd, 1, 0, 1, int, layout->pass_timestamp); \ ++ MC_RSP_OP(cmd, 1, 1, 1, int, layout->pass_parser_result); \ ++ MC_RSP_OP(cmd, 1, 2, 1, int, layout->pass_frame_status); \ ++ MC_RSP_OP(cmd, 1, 16, 16, uint16_t, layout->data_head_room); \ ++ MC_RSP_OP(cmd, 1, 32, 16, uint16_t, layout->data_tail_room); \ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_CMD_SET_TX_CONF_BUFFER_LAYOUT(cmd, layout) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 0, 16, uint16_t, layout->private_data_size); \ ++ MC_CMD_OP(cmd, 0, 16, 16, uint16_t, layout->data_align); \ ++ MC_CMD_OP(cmd, 0, 32, 32, uint32_t, layout->options); \ ++ MC_CMD_OP(cmd, 1, 0, 1, int, layout->pass_timestamp); \ ++ MC_CMD_OP(cmd, 1, 1, 1, int, layout->pass_parser_result); \ ++ MC_CMD_OP(cmd, 1, 2, 1, int, layout->pass_frame_status); \ ++ MC_CMD_OP(cmd, 1, 16, 16, uint16_t, layout->data_head_room); \ ++ MC_CMD_OP(cmd, 1, 32, 16, uint16_t, layout->data_tail_room); \ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_CMD_SET_L3_CHKSUM_VALIDATION(cmd, en) \ ++ MC_CMD_OP(cmd, 0, 0, 1, int, en) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_RSP_GET_L3_CHKSUM_VALIDATION(cmd, en) \ ++ MC_RSP_OP(cmd, 0, 0, 1, int, en) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_CMD_SET_L4_CHKSUM_VALIDATION(cmd, en) \ ++ MC_CMD_OP(cmd, 0, 0, 1, int, en) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_RSP_GET_L4_CHKSUM_VALIDATION(cmd, en) \ ++ MC_RSP_OP(cmd, 0, 0, 1, int, en) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_RSP_GET_QDID(cmd, qdid) \ ++ MC_RSP_OP(cmd, 0, 0, 16, uint16_t, qdid) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_RSP_GET_SP_INFO(cmd, sp_info) \ ++do { \ ++ MC_RSP_OP(cmd, 0, 0, 16, uint16_t, sp_info->spids[0]); \ ++ MC_RSP_OP(cmd, 0, 16, 16, uint16_t, sp_info->spids[1]); \ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_RSP_GET_TX_DATA_OFFSET(cmd, data_offset) \ ++ MC_RSP_OP(cmd, 0, 0, 16, uint16_t, data_offset) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_CMD_GET_COUNTER(cmd, counter) \ ++ MC_CMD_OP(cmd, 0, 0, 16, enum dpni_counter, counter) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_RSP_GET_COUNTER(cmd, value) \ ++ MC_RSP_OP(cmd, 1, 0, 64, uint64_t, value) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_CMD_SET_COUNTER(cmd, counter, value) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 0, 16, enum dpni_counter, counter); \ ++ MC_CMD_OP(cmd, 1, 0, 64, uint64_t, value); \ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_CMD_SET_LINK_CFG(cmd, cfg) \ ++do { \ ++ MC_CMD_OP(cmd, 1, 0, 32, uint32_t, cfg->rate);\ ++ MC_CMD_OP(cmd, 2, 0, 64, uint64_t, cfg->options);\ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_RSP_GET_LINK_STATE(cmd, state) \ ++do { \ ++ MC_RSP_OP(cmd, 0, 32, 1, int, state->up);\ ++ MC_RSP_OP(cmd, 1, 0, 32, uint32_t, state->rate);\ ++ MC_RSP_OP(cmd, 2, 0, 64, uint64_t, state->options);\ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_CMD_SET_TX_SHAPING(cmd, tx_shaper) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 0, 16, uint16_t, tx_shaper->max_burst_size);\ ++ MC_CMD_OP(cmd, 1, 0, 32, uint32_t, tx_shaper->rate_limit);\ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_CMD_SET_MAX_FRAME_LENGTH(cmd, max_frame_length) \ ++ MC_CMD_OP(cmd, 0, 0, 16, uint16_t, max_frame_length) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_RSP_GET_MAX_FRAME_LENGTH(cmd, max_frame_length) \ ++ MC_RSP_OP(cmd, 0, 0, 16, uint16_t, max_frame_length) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_CMD_SET_MTU(cmd, mtu) \ ++ MC_CMD_OP(cmd, 0, 0, 16, uint16_t, mtu) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_RSP_GET_MTU(cmd, mtu) \ ++ MC_RSP_OP(cmd, 0, 0, 16, uint16_t, mtu) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_CMD_SET_MULTICAST_PROMISC(cmd, en) \ ++ MC_CMD_OP(cmd, 0, 0, 1, int, en) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_RSP_GET_MULTICAST_PROMISC(cmd, en) \ ++ MC_RSP_OP(cmd, 0, 0, 1, int, en) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_CMD_SET_UNICAST_PROMISC(cmd, en) \ ++ MC_CMD_OP(cmd, 0, 0, 1, int, en) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_RSP_GET_UNICAST_PROMISC(cmd, en) \ ++ MC_RSP_OP(cmd, 0, 0, 1, int, en) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_CMD_SET_PRIMARY_MAC_ADDR(cmd, mac_addr) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 16, 8, uint8_t, mac_addr[5]); \ ++ MC_CMD_OP(cmd, 0, 24, 8, uint8_t, mac_addr[4]); \ ++ MC_CMD_OP(cmd, 0, 32, 8, uint8_t, mac_addr[3]); \ ++ MC_CMD_OP(cmd, 0, 40, 8, uint8_t, mac_addr[2]); \ ++ MC_CMD_OP(cmd, 0, 48, 8, uint8_t, mac_addr[1]); \ ++ MC_CMD_OP(cmd, 0, 56, 8, uint8_t, mac_addr[0]); \ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_RSP_GET_PRIMARY_MAC_ADDR(cmd, mac_addr) \ ++do { \ ++ MC_RSP_OP(cmd, 0, 16, 8, uint8_t, mac_addr[5]); \ ++ MC_RSP_OP(cmd, 0, 24, 8, uint8_t, mac_addr[4]); \ ++ MC_RSP_OP(cmd, 0, 32, 8, uint8_t, mac_addr[3]); \ ++ MC_RSP_OP(cmd, 0, 40, 8, uint8_t, mac_addr[2]); \ ++ MC_RSP_OP(cmd, 0, 48, 8, uint8_t, mac_addr[1]); \ ++ MC_RSP_OP(cmd, 0, 56, 8, uint8_t, mac_addr[0]); \ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_CMD_ADD_MAC_ADDR(cmd, mac_addr) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 16, 8, uint8_t, mac_addr[5]); \ ++ MC_CMD_OP(cmd, 0, 24, 8, uint8_t, mac_addr[4]); \ ++ MC_CMD_OP(cmd, 0, 32, 8, uint8_t, mac_addr[3]); \ ++ MC_CMD_OP(cmd, 0, 40, 8, uint8_t, mac_addr[2]); \ ++ MC_CMD_OP(cmd, 0, 48, 8, uint8_t, mac_addr[1]); \ ++ MC_CMD_OP(cmd, 0, 56, 8, uint8_t, mac_addr[0]); \ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_CMD_REMOVE_MAC_ADDR(cmd, mac_addr) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 16, 8, uint8_t, mac_addr[5]); \ ++ MC_CMD_OP(cmd, 0, 24, 8, uint8_t, mac_addr[4]); \ ++ MC_CMD_OP(cmd, 0, 32, 8, uint8_t, mac_addr[3]); \ ++ MC_CMD_OP(cmd, 0, 40, 8, uint8_t, mac_addr[2]); \ ++ MC_CMD_OP(cmd, 0, 48, 8, uint8_t, mac_addr[1]); \ ++ MC_CMD_OP(cmd, 0, 56, 8, uint8_t, mac_addr[0]); \ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_CMD_CLEAR_MAC_FILTERS(cmd, unicast, multicast) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 0, 1, int, unicast); \ ++ MC_CMD_OP(cmd, 0, 1, 1, int, multicast); \ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_CMD_SET_VLAN_FILTERS(cmd, en) \ ++ MC_CMD_OP(cmd, 0, 0, 1, int, en) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_CMD_ADD_VLAN_ID(cmd, vlan_id) \ ++ MC_CMD_OP(cmd, 0, 32, 16, uint16_t, vlan_id) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_CMD_REMOVE_VLAN_ID(cmd, vlan_id) \ ++ MC_CMD_OP(cmd, 0, 32, 16, uint16_t, vlan_id) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_CMD_SET_TX_SELECTION(cmd, cfg) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 0, 16, uint16_t, cfg->tc_sched[0].delta_bandwidth);\ ++ MC_CMD_OP(cmd, 0, 16, 4, enum dpni_tx_schedule_mode, \ ++ cfg->tc_sched[0].mode); \ ++ MC_CMD_OP(cmd, 0, 32, 16, uint16_t, cfg->tc_sched[1].delta_bandwidth);\ ++ MC_CMD_OP(cmd, 0, 48, 4, enum dpni_tx_schedule_mode, \ ++ cfg->tc_sched[1].mode); \ ++ MC_CMD_OP(cmd, 1, 0, 16, uint16_t, cfg->tc_sched[2].delta_bandwidth);\ ++ MC_CMD_OP(cmd, 1, 16, 4, enum dpni_tx_schedule_mode, \ ++ cfg->tc_sched[2].mode); \ ++ MC_CMD_OP(cmd, 1, 32, 16, uint16_t, cfg->tc_sched[3].delta_bandwidth);\ ++ MC_CMD_OP(cmd, 1, 48, 4, enum dpni_tx_schedule_mode, \ ++ cfg->tc_sched[3].mode); \ ++ MC_CMD_OP(cmd, 2, 0, 16, uint16_t, cfg->tc_sched[4].delta_bandwidth);\ ++ MC_CMD_OP(cmd, 2, 16, 4, enum dpni_tx_schedule_mode, \ ++ cfg->tc_sched[4].mode); \ ++ MC_CMD_OP(cmd, 2, 32, 16, uint16_t, cfg->tc_sched[5].delta_bandwidth);\ ++ MC_CMD_OP(cmd, 2, 48, 4, enum dpni_tx_schedule_mode, \ ++ cfg->tc_sched[5].mode); \ ++ MC_CMD_OP(cmd, 3, 0, 16, uint16_t, cfg->tc_sched[6].delta_bandwidth);\ ++ MC_CMD_OP(cmd, 3, 16, 4, enum dpni_tx_schedule_mode, \ ++ cfg->tc_sched[6].mode); \ ++ MC_CMD_OP(cmd, 3, 32, 16, uint16_t, cfg->tc_sched[7].delta_bandwidth);\ ++ MC_CMD_OP(cmd, 3, 48, 4, enum dpni_tx_schedule_mode, \ ++ cfg->tc_sched[7].mode); \ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_CMD_SET_RX_TC_DIST(cmd, tc_id, cfg) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 0, 16, uint16_t, cfg->dist_size); \ ++ MC_CMD_OP(cmd, 0, 16, 8, uint8_t, tc_id); \ ++ MC_CMD_OP(cmd, 0, 24, 4, enum dpni_dist_mode, cfg->dist_mode); \ ++ MC_CMD_OP(cmd, 0, 28, 4, enum dpni_fs_miss_action, \ ++ cfg->fs_cfg.miss_action); \ ++ MC_CMD_OP(cmd, 0, 48, 16, uint16_t, cfg->fs_cfg.default_flow_id); \ ++ MC_CMD_OP(cmd, 6, 0, 64, uint64_t, cfg->key_cfg_iova); \ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_CMD_SET_TX_FLOW(cmd, flow_id, cfg) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 43, 1, int, cfg->l3_chksum_gen);\ ++ MC_CMD_OP(cmd, 0, 44, 1, int, cfg->l4_chksum_gen);\ ++ MC_CMD_OP(cmd, 0, 45, 1, int, cfg->use_common_tx_conf_queue);\ ++ MC_CMD_OP(cmd, 0, 48, 16, uint16_t, flow_id);\ ++ MC_CMD_OP(cmd, 2, 0, 32, uint32_t, cfg->options);\ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_RSP_SET_TX_FLOW(cmd, flow_id) \ ++ MC_RSP_OP(cmd, 0, 48, 16, uint16_t, flow_id) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_CMD_GET_TX_FLOW(cmd, flow_id) \ ++ MC_CMD_OP(cmd, 0, 48, 16, uint16_t, flow_id) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_RSP_GET_TX_FLOW(cmd, attr) \ ++do { \ ++ MC_RSP_OP(cmd, 0, 43, 1, int, attr->l3_chksum_gen);\ ++ MC_RSP_OP(cmd, 0, 44, 1, int, attr->l4_chksum_gen);\ ++ MC_RSP_OP(cmd, 0, 45, 1, int, attr->use_common_tx_conf_queue);\ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_CMD_SET_RX_FLOW(cmd, tc_id, flow_id, cfg) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 0, 32, int, cfg->dest_cfg.dest_id); \ ++ MC_CMD_OP(cmd, 0, 32, 8, uint8_t, cfg->dest_cfg.priority);\ ++ MC_CMD_OP(cmd, 0, 40, 2, enum dpni_dest, cfg->dest_cfg.dest_type);\ ++ MC_CMD_OP(cmd, 0, 42, 1, int, cfg->order_preservation_en);\ ++ MC_CMD_OP(cmd, 0, 48, 16, uint16_t, flow_id); \ ++ MC_CMD_OP(cmd, 1, 0, 64, uint64_t, cfg->user_ctx); \ ++ MC_CMD_OP(cmd, 2, 16, 8, uint8_t, tc_id); \ ++ MC_CMD_OP(cmd, 2, 32, 32, uint32_t, cfg->options); \ ++ MC_CMD_OP(cmd, 3, 0, 4, enum dpni_flc_type, cfg->flc_cfg.flc_type); \ ++ MC_CMD_OP(cmd, 3, 4, 4, enum dpni_stash_size, \ ++ cfg->flc_cfg.frame_data_size);\ ++ MC_CMD_OP(cmd, 3, 8, 4, enum dpni_stash_size, \ ++ cfg->flc_cfg.flow_context_size);\ ++ MC_CMD_OP(cmd, 3, 32, 32, uint32_t, cfg->flc_cfg.options);\ ++ MC_CMD_OP(cmd, 4, 0, 64, uint64_t, cfg->flc_cfg.flow_context);\ ++ MC_CMD_OP(cmd, 5, 0, 32, uint32_t, cfg->tail_drop_threshold); \ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_CMD_GET_RX_FLOW(cmd, tc_id, flow_id) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 16, 8, uint8_t, tc_id); \ ++ MC_CMD_OP(cmd, 0, 48, 16, uint16_t, flow_id); \ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_RSP_GET_RX_FLOW(cmd, attr) \ ++do { \ ++ MC_RSP_OP(cmd, 0, 0, 32, int, attr->dest_cfg.dest_id); \ ++ MC_RSP_OP(cmd, 0, 32, 8, uint8_t, attr->dest_cfg.priority);\ ++ MC_RSP_OP(cmd, 0, 40, 2, enum dpni_dest, attr->dest_cfg.dest_type); \ ++ MC_RSP_OP(cmd, 0, 42, 1, int, attr->order_preservation_en);\ ++ MC_RSP_OP(cmd, 1, 0, 64, uint64_t, attr->user_ctx); \ ++ MC_RSP_OP(cmd, 2, 0, 32, uint32_t, attr->tail_drop_threshold); \ ++ MC_RSP_OP(cmd, 2, 32, 32, uint32_t, attr->fqid); \ ++ MC_RSP_OP(cmd, 3, 0, 4, enum dpni_flc_type, attr->flc_cfg.flc_type); \ ++ MC_RSP_OP(cmd, 3, 4, 4, enum dpni_stash_size, \ ++ attr->flc_cfg.frame_data_size);\ ++ MC_RSP_OP(cmd, 3, 8, 4, enum dpni_stash_size, \ ++ attr->flc_cfg.flow_context_size);\ ++ MC_RSP_OP(cmd, 3, 32, 32, uint32_t, attr->flc_cfg.options);\ ++ MC_RSP_OP(cmd, 4, 0, 64, uint64_t, attr->flc_cfg.flow_context);\ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_CMD_SET_RX_ERR_QUEUE(cmd, cfg) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 0, 32, int, cfg->dest_cfg.dest_id); \ ++ MC_CMD_OP(cmd, 0, 32, 8, uint8_t, cfg->dest_cfg.priority);\ ++ MC_CMD_OP(cmd, 0, 40, 2, enum dpni_dest, cfg->dest_cfg.dest_type);\ ++ MC_CMD_OP(cmd, 0, 42, 1, int, cfg->order_preservation_en);\ ++ MC_CMD_OP(cmd, 1, 0, 64, uint64_t, cfg->user_ctx); \ ++ MC_CMD_OP(cmd, 2, 0, 32, uint32_t, cfg->options); \ ++ MC_CMD_OP(cmd, 2, 32, 32, uint32_t, cfg->tail_drop_threshold); \ ++ MC_CMD_OP(cmd, 3, 0, 4, enum dpni_flc_type, cfg->flc_cfg.flc_type); \ ++ MC_CMD_OP(cmd, 3, 4, 4, enum dpni_stash_size, \ ++ cfg->flc_cfg.frame_data_size);\ ++ MC_CMD_OP(cmd, 3, 8, 4, enum dpni_stash_size, \ ++ cfg->flc_cfg.flow_context_size);\ ++ MC_CMD_OP(cmd, 3, 32, 32, uint32_t, cfg->flc_cfg.options);\ ++ MC_CMD_OP(cmd, 4, 0, 64, uint64_t, cfg->flc_cfg.flow_context);\ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_RSP_GET_RX_ERR_QUEUE(cmd, attr) \ ++do { \ ++ MC_RSP_OP(cmd, 0, 0, 32, int, attr->dest_cfg.dest_id); \ ++ MC_RSP_OP(cmd, 0, 32, 8, uint8_t, attr->dest_cfg.priority);\ ++ MC_RSP_OP(cmd, 0, 40, 2, enum dpni_dest, attr->dest_cfg.dest_type);\ ++ MC_RSP_OP(cmd, 0, 42, 1, int, attr->order_preservation_en);\ ++ MC_RSP_OP(cmd, 1, 0, 64, uint64_t, attr->user_ctx); \ ++ MC_RSP_OP(cmd, 2, 0, 32, uint32_t, attr->tail_drop_threshold); \ ++ MC_RSP_OP(cmd, 2, 32, 32, uint32_t, attr->fqid); \ ++ MC_RSP_OP(cmd, 3, 0, 4, enum dpni_flc_type, attr->flc_cfg.flc_type); \ ++ MC_RSP_OP(cmd, 3, 4, 4, enum dpni_stash_size, \ ++ attr->flc_cfg.frame_data_size);\ ++ MC_RSP_OP(cmd, 3, 8, 4, enum dpni_stash_size, \ ++ attr->flc_cfg.flow_context_size);\ ++ MC_RSP_OP(cmd, 3, 32, 32, uint32_t, attr->flc_cfg.options);\ ++ MC_RSP_OP(cmd, 4, 0, 64, uint64_t, attr->flc_cfg.flow_context);\ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_CMD_SET_TX_CONF_REVOKE(cmd, revoke) \ ++ MC_CMD_OP(cmd, 0, 0, 1, int, revoke) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_CMD_SET_QOS_TABLE(cmd, cfg) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 32, 8, uint8_t, cfg->default_tc); \ ++ MC_CMD_OP(cmd, 0, 40, 1, int, cfg->discard_on_miss); \ ++ MC_CMD_OP(cmd, 6, 0, 64, uint64_t, cfg->key_cfg_iova); \ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_CMD_ADD_QOS_ENTRY(cmd, cfg, tc_id) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 16, 8, uint8_t, tc_id); \ ++ MC_CMD_OP(cmd, 0, 24, 8, uint8_t, cfg->key_size); \ ++ MC_CMD_OP(cmd, 1, 0, 64, uint64_t, cfg->key_iova); \ ++ MC_CMD_OP(cmd, 2, 0, 64, uint64_t, cfg->mask_iova); \ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_CMD_REMOVE_QOS_ENTRY(cmd, cfg) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 24, 8, uint8_t, cfg->key_size); \ ++ MC_CMD_OP(cmd, 1, 0, 64, uint64_t, cfg->key_iova); \ ++ MC_CMD_OP(cmd, 2, 0, 64, uint64_t, cfg->mask_iova); \ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_CMD_ADD_FS_ENTRY(cmd, tc_id, cfg, flow_id) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 16, 8, uint8_t, tc_id); \ ++ MC_CMD_OP(cmd, 0, 48, 16, uint16_t, flow_id); \ ++ MC_CMD_OP(cmd, 0, 24, 8, uint8_t, cfg->key_size); \ ++ MC_CMD_OP(cmd, 1, 0, 64, uint64_t, cfg->key_iova); \ ++ MC_CMD_OP(cmd, 2, 0, 64, uint64_t, cfg->mask_iova); \ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_CMD_REMOVE_FS_ENTRY(cmd, tc_id, cfg) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 16, 8, uint8_t, tc_id); \ ++ MC_CMD_OP(cmd, 0, 24, 8, uint8_t, cfg->key_size); \ ++ MC_CMD_OP(cmd, 1, 0, 64, uint64_t, cfg->key_iova); \ ++ MC_CMD_OP(cmd, 2, 0, 64, uint64_t, cfg->mask_iova); \ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_CMD_CLEAR_FS_ENTRIES(cmd, tc_id) \ ++ MC_CMD_OP(cmd, 0, 16, 8, uint8_t, tc_id) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_CMD_SET_VLAN_INSERTION(cmd, en) \ ++ MC_CMD_OP(cmd, 0, 0, 1, int, en) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_CMD_SET_VLAN_REMOVAL(cmd, en) \ ++ MC_CMD_OP(cmd, 0, 0, 1, int, en) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_CMD_SET_IPR(cmd, en) \ ++ MC_CMD_OP(cmd, 0, 0, 1, int, en) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_CMD_SET_IPF(cmd, en) \ ++ MC_CMD_OP(cmd, 0, 0, 1, int, en) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_CMD_SET_RX_TC_POLICING(cmd, tc_id, cfg) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 0, 4, enum dpni_policer_mode, cfg->mode); \ ++ MC_CMD_OP(cmd, 0, 4, 4, enum dpni_policer_color, cfg->default_color); \ ++ MC_CMD_OP(cmd, 0, 8, 4, enum dpni_policer_unit, cfg->units); \ ++ MC_CMD_OP(cmd, 0, 16, 8, uint8_t, tc_id); \ ++ MC_CMD_OP(cmd, 0, 32, 32, uint32_t, cfg->options); \ ++ MC_CMD_OP(cmd, 1, 0, 32, uint32_t, cfg->cir); \ ++ MC_CMD_OP(cmd, 1, 32, 32, uint32_t, cfg->cbs); \ ++ MC_CMD_OP(cmd, 2, 0, 32, uint32_t, cfg->eir); \ ++ MC_CMD_OP(cmd, 2, 32, 32, uint32_t, cfg->ebs);\ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_CMD_GET_RX_TC_POLICING(cmd, tc_id) \ ++ MC_CMD_OP(cmd, 0, 16, 8, uint8_t, tc_id) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_RSP_GET_RX_TC_POLICING(cmd, cfg) \ ++do { \ ++ MC_RSP_OP(cmd, 0, 0, 4, enum dpni_policer_mode, cfg->mode); \ ++ MC_RSP_OP(cmd, 0, 4, 4, enum dpni_policer_color, cfg->default_color); \ ++ MC_RSP_OP(cmd, 0, 8, 4, enum dpni_policer_unit, cfg->units); \ ++ MC_RSP_OP(cmd, 0, 32, 32, uint32_t, cfg->options); \ ++ MC_RSP_OP(cmd, 1, 0, 32, uint32_t, cfg->cir); \ ++ MC_RSP_OP(cmd, 1, 32, 32, uint32_t, cfg->cbs); \ ++ MC_RSP_OP(cmd, 2, 0, 32, uint32_t, cfg->eir); \ ++ MC_RSP_OP(cmd, 2, 32, 32, uint32_t, cfg->ebs);\ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_PREP_EARLY_DROP(ext, cfg) \ ++do { \ ++ MC_PREP_OP(ext, 0, 0, 2, enum dpni_early_drop_mode, cfg->mode); \ ++ MC_PREP_OP(ext, 0, 2, 2, \ ++ enum dpni_congestion_unit, cfg->units); \ ++ MC_PREP_OP(ext, 0, 32, 32, uint32_t, cfg->tail_drop_threshold); \ ++ MC_PREP_OP(ext, 1, 0, 8, uint8_t, cfg->green.drop_probability); \ ++ MC_PREP_OP(ext, 2, 0, 64, uint64_t, cfg->green.max_threshold); \ ++ MC_PREP_OP(ext, 3, 0, 64, uint64_t, cfg->green.min_threshold); \ ++ MC_PREP_OP(ext, 5, 0, 8, uint8_t, cfg->yellow.drop_probability);\ ++ MC_PREP_OP(ext, 6, 0, 64, uint64_t, cfg->yellow.max_threshold); \ ++ MC_PREP_OP(ext, 7, 0, 64, uint64_t, cfg->yellow.min_threshold); \ ++ MC_PREP_OP(ext, 9, 0, 8, uint8_t, cfg->red.drop_probability); \ ++ MC_PREP_OP(ext, 10, 0, 64, uint64_t, cfg->red.max_threshold); \ ++ MC_PREP_OP(ext, 11, 0, 64, uint64_t, cfg->red.min_threshold); \ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_EXT_EARLY_DROP(ext, cfg) \ ++do { \ ++ MC_EXT_OP(ext, 0, 0, 2, enum dpni_early_drop_mode, cfg->mode); \ ++ MC_EXT_OP(ext, 0, 2, 2, \ ++ enum dpni_congestion_unit, cfg->units); \ ++ MC_EXT_OP(ext, 0, 32, 32, uint32_t, cfg->tail_drop_threshold); \ ++ MC_EXT_OP(ext, 1, 0, 8, uint8_t, cfg->green.drop_probability); \ ++ MC_EXT_OP(ext, 2, 0, 64, uint64_t, cfg->green.max_threshold); \ ++ MC_EXT_OP(ext, 3, 0, 64, uint64_t, cfg->green.min_threshold); \ ++ MC_EXT_OP(ext, 5, 0, 8, uint8_t, cfg->yellow.drop_probability);\ ++ MC_EXT_OP(ext, 6, 0, 64, uint64_t, cfg->yellow.max_threshold); \ ++ MC_EXT_OP(ext, 7, 0, 64, uint64_t, cfg->yellow.min_threshold); \ ++ MC_EXT_OP(ext, 9, 0, 8, uint8_t, cfg->red.drop_probability); \ ++ MC_EXT_OP(ext, 10, 0, 64, uint64_t, cfg->red.max_threshold); \ ++ MC_EXT_OP(ext, 11, 0, 64, uint64_t, cfg->red.min_threshold); \ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_CMD_SET_RX_TC_EARLY_DROP(cmd, tc_id, early_drop_iova) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 8, 8, uint8_t, tc_id); \ ++ MC_CMD_OP(cmd, 1, 0, 64, uint64_t, early_drop_iova); \ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_CMD_GET_RX_TC_EARLY_DROP(cmd, tc_id, early_drop_iova) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 8, 8, uint8_t, tc_id); \ ++ MC_CMD_OP(cmd, 1, 0, 64, uint64_t, early_drop_iova); \ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_CMD_SET_TX_TC_EARLY_DROP(cmd, tc_id, early_drop_iova) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 8, 8, uint8_t, tc_id); \ ++ MC_CMD_OP(cmd, 1, 0, 64, uint64_t, early_drop_iova); \ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPNI_CMD_GET_TX_TC_EARLY_DROP(cmd, tc_id, early_drop_iova) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 8, 8, uint8_t, tc_id); \ ++ MC_CMD_OP(cmd, 1, 0, 64, uint64_t, early_drop_iova); \ ++} while (0) ++ ++#define DPNI_CMD_SET_RX_TC_CONGESTION_NOTIFICATION(cmd, tc_id, cfg) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 0, 2, enum dpni_congestion_unit, cfg->units); \ ++ MC_CMD_OP(cmd, 0, 4, 4, enum dpni_dest, cfg->dest_cfg.dest_type); \ ++ MC_CMD_OP(cmd, 0, 8, 8, uint8_t, tc_id); \ ++ MC_CMD_OP(cmd, 0, 16, 8, uint8_t, cfg->dest_cfg.priority); \ ++ MC_CMD_OP(cmd, 1, 0, 32, uint32_t, cfg->threshold_entry); \ ++ MC_CMD_OP(cmd, 1, 32, 32, uint32_t, cfg->threshold_exit); \ ++ MC_CMD_OP(cmd, 2, 0, 16, uint16_t, cfg->options); \ ++ MC_CMD_OP(cmd, 2, 32, 32, int, cfg->dest_cfg.dest_id); \ ++ MC_CMD_OP(cmd, 3, 0, 64, uint64_t, cfg->message_ctx); \ ++ MC_CMD_OP(cmd, 4, 0, 64, uint64_t, cfg->message_iova); \ ++} while (0) ++ ++#define DPNI_CMD_GET_RX_TC_CONGESTION_NOTIFICATION(cmd, tc_id) \ ++ MC_CMD_OP(cmd, 0, 8, 8, uint8_t, tc_id) ++ ++#define DPNI_RSP_GET_RX_TC_CONGESTION_NOTIFICATION(cmd, cfg) \ ++do { \ ++ MC_RSP_OP(cmd, 0, 0, 2, enum dpni_congestion_unit, cfg->units); \ ++ MC_RSP_OP(cmd, 0, 4, 4, enum dpni_dest, cfg->dest_cfg.dest_type); \ ++ MC_RSP_OP(cmd, 0, 16, 8, uint8_t, cfg->dest_cfg.priority); \ ++ MC_RSP_OP(cmd, 1, 0, 32, uint32_t, cfg->threshold_entry); \ ++ MC_RSP_OP(cmd, 1, 32, 32, uint32_t, cfg->threshold_exit); \ ++ MC_RSP_OP(cmd, 2, 0, 16, uint16_t, cfg->options); \ ++ MC_RSP_OP(cmd, 2, 32, 32, int, cfg->dest_cfg.dest_id); \ ++ MC_RSP_OP(cmd, 3, 0, 64, uint64_t, cfg->message_ctx); \ ++ MC_RSP_OP(cmd, 4, 0, 64, uint64_t, cfg->message_iova); \ ++} while (0) ++ ++#define DPNI_CMD_SET_TX_TC_CONGESTION_NOTIFICATION(cmd, tc_id, cfg) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 0, 2, enum dpni_congestion_unit, cfg->units); \ ++ MC_CMD_OP(cmd, 0, 4, 4, enum dpni_dest, cfg->dest_cfg.dest_type); \ ++ MC_CMD_OP(cmd, 0, 8, 8, uint8_t, tc_id); \ ++ MC_CMD_OP(cmd, 0, 16, 8, uint8_t, cfg->dest_cfg.priority); \ ++ MC_CMD_OP(cmd, 1, 0, 32, uint32_t, cfg->threshold_entry); \ ++ MC_CMD_OP(cmd, 1, 32, 32, uint32_t, cfg->threshold_exit); \ ++ MC_CMD_OP(cmd, 2, 0, 16, uint16_t, cfg->options); \ ++ MC_CMD_OP(cmd, 2, 32, 32, int, cfg->dest_cfg.dest_id); \ ++ MC_CMD_OP(cmd, 3, 0, 64, uint64_t, cfg->message_ctx); \ ++ MC_CMD_OP(cmd, 4, 0, 64, uint64_t, cfg->message_iova); \ ++} while (0) ++ ++#define DPNI_CMD_GET_TX_TC_CONGESTION_NOTIFICATION(cmd, tc_id) \ ++ MC_CMD_OP(cmd, 0, 8, 8, uint8_t, tc_id) ++ ++#define DPNI_RSP_GET_TX_TC_CONGESTION_NOTIFICATION(cmd, cfg) \ ++do { \ ++ MC_RSP_OP(cmd, 0, 0, 2, enum dpni_congestion_unit, cfg->units); \ ++ MC_RSP_OP(cmd, 0, 4, 4, enum dpni_dest, cfg->dest_cfg.dest_type); \ ++ MC_RSP_OP(cmd, 0, 16, 8, uint8_t, cfg->dest_cfg.priority); \ ++ MC_RSP_OP(cmd, 1, 0, 32, uint32_t, cfg->threshold_entry); \ ++ MC_RSP_OP(cmd, 1, 32, 32, uint32_t, cfg->threshold_exit); \ ++ MC_RSP_OP(cmd, 2, 0, 16, uint16_t, cfg->options); \ ++ MC_RSP_OP(cmd, 2, 32, 32, int, cfg->dest_cfg.dest_id); \ ++ MC_RSP_OP(cmd, 3, 0, 64, uint64_t, cfg->message_ctx); \ ++ MC_RSP_OP(cmd, 4, 0, 64, uint64_t, cfg->message_iova); \ ++} while (0) ++ ++#define DPNI_CMD_SET_TX_CONF(cmd, flow_id, cfg) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 32, 8, uint8_t, cfg->queue_cfg.dest_cfg.priority); \ ++ MC_CMD_OP(cmd, 0, 40, 2, enum dpni_dest, \ ++ cfg->queue_cfg.dest_cfg.dest_type); \ ++ MC_CMD_OP(cmd, 0, 42, 1, int, cfg->errors_only); \ ++ MC_CMD_OP(cmd, 0, 46, 1, int, cfg->queue_cfg.order_preservation_en); \ ++ MC_CMD_OP(cmd, 0, 48, 16, uint16_t, flow_id); \ ++ MC_CMD_OP(cmd, 1, 0, 64, uint64_t, cfg->queue_cfg.user_ctx); \ ++ MC_CMD_OP(cmd, 2, 0, 32, uint32_t, cfg->queue_cfg.options); \ ++ MC_CMD_OP(cmd, 2, 32, 32, int, cfg->queue_cfg.dest_cfg.dest_id); \ ++ MC_CMD_OP(cmd, 3, 0, 32, uint32_t, \ ++ cfg->queue_cfg.tail_drop_threshold); \ ++ MC_CMD_OP(cmd, 4, 0, 4, enum dpni_flc_type, \ ++ cfg->queue_cfg.flc_cfg.flc_type); \ ++ MC_CMD_OP(cmd, 4, 4, 4, enum dpni_stash_size, \ ++ cfg->queue_cfg.flc_cfg.frame_data_size); \ ++ MC_CMD_OP(cmd, 4, 8, 4, enum dpni_stash_size, \ ++ cfg->queue_cfg.flc_cfg.flow_context_size); \ ++ MC_CMD_OP(cmd, 4, 32, 32, uint32_t, cfg->queue_cfg.flc_cfg.options); \ ++ MC_CMD_OP(cmd, 5, 0, 64, uint64_t, \ ++ cfg->queue_cfg.flc_cfg.flow_context); \ ++} while (0) ++ ++#define DPNI_CMD_GET_TX_CONF(cmd, flow_id) \ ++ MC_CMD_OP(cmd, 0, 48, 16, uint16_t, flow_id) ++ ++#define DPNI_RSP_GET_TX_CONF(cmd, attr) \ ++do { \ ++ MC_RSP_OP(cmd, 0, 32, 8, uint8_t, \ ++ attr->queue_attr.dest_cfg.priority); \ ++ MC_RSP_OP(cmd, 0, 40, 2, enum dpni_dest, \ ++ attr->queue_attr.dest_cfg.dest_type); \ ++ MC_RSP_OP(cmd, 0, 42, 1, int, attr->errors_only); \ ++ MC_RSP_OP(cmd, 0, 46, 1, int, \ ++ attr->queue_attr.order_preservation_en); \ ++ MC_RSP_OP(cmd, 1, 0, 64, uint64_t, attr->queue_attr.user_ctx); \ ++ MC_RSP_OP(cmd, 2, 32, 32, int, attr->queue_attr.dest_cfg.dest_id); \ ++ MC_RSP_OP(cmd, 3, 0, 32, uint32_t, \ ++ attr->queue_attr.tail_drop_threshold); \ ++ MC_RSP_OP(cmd, 3, 32, 32, uint32_t, attr->queue_attr.fqid); \ ++ MC_RSP_OP(cmd, 4, 0, 4, enum dpni_flc_type, \ ++ attr->queue_attr.flc_cfg.flc_type); \ ++ MC_RSP_OP(cmd, 4, 4, 4, enum dpni_stash_size, \ ++ attr->queue_attr.flc_cfg.frame_data_size); \ ++ MC_RSP_OP(cmd, 4, 8, 4, enum dpni_stash_size, \ ++ attr->queue_attr.flc_cfg.flow_context_size); \ ++ MC_RSP_OP(cmd, 4, 32, 32, uint32_t, attr->queue_attr.flc_cfg.options); \ ++ MC_RSP_OP(cmd, 5, 0, 64, uint64_t, \ ++ attr->queue_attr.flc_cfg.flow_context); \ ++} while (0) ++ ++#define DPNI_CMD_SET_TX_CONF_CONGESTION_NOTIFICATION(cmd, flow_id, cfg) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 0, 2, enum dpni_congestion_unit, cfg->units); \ ++ MC_CMD_OP(cmd, 0, 4, 4, enum dpni_dest, cfg->dest_cfg.dest_type); \ ++ MC_CMD_OP(cmd, 0, 16, 8, uint8_t, cfg->dest_cfg.priority); \ ++ MC_CMD_OP(cmd, 0, 48, 16, uint16_t, flow_id); \ ++ MC_CMD_OP(cmd, 1, 0, 32, uint32_t, cfg->threshold_entry); \ ++ MC_CMD_OP(cmd, 1, 32, 32, uint32_t, cfg->threshold_exit); \ ++ MC_CMD_OP(cmd, 2, 0, 16, uint16_t, cfg->options); \ ++ MC_CMD_OP(cmd, 2, 32, 32, int, cfg->dest_cfg.dest_id); \ ++ MC_CMD_OP(cmd, 3, 0, 64, uint64_t, cfg->message_ctx); \ ++ MC_CMD_OP(cmd, 4, 0, 64, uint64_t, cfg->message_iova); \ ++} while (0) ++ ++#define DPNI_CMD_GET_TX_CONF_CONGESTION_NOTIFICATION(cmd, flow_id) \ ++ MC_CMD_OP(cmd, 0, 48, 16, uint16_t, flow_id) ++ ++#define DPNI_RSP_GET_TX_CONF_CONGESTION_NOTIFICATION(cmd, cfg) \ ++do { \ ++ MC_RSP_OP(cmd, 0, 0, 2, enum dpni_congestion_unit, cfg->units); \ ++ MC_RSP_OP(cmd, 0, 4, 4, enum dpni_dest, cfg->dest_cfg.dest_type); \ ++ MC_RSP_OP(cmd, 0, 16, 8, uint8_t, cfg->dest_cfg.priority); \ ++ MC_RSP_OP(cmd, 1, 0, 32, uint32_t, cfg->threshold_entry); \ ++ MC_RSP_OP(cmd, 1, 32, 32, uint32_t, cfg->threshold_exit); \ ++ MC_RSP_OP(cmd, 2, 0, 16, uint16_t, cfg->options); \ ++ MC_RSP_OP(cmd, 2, 32, 32, int, cfg->dest_cfg.dest_id); \ ++ MC_RSP_OP(cmd, 3, 0, 64, uint64_t, cfg->message_ctx); \ ++ MC_RSP_OP(cmd, 4, 0, 64, uint64_t, cfg->message_iova); \ ++} while (0) ++ ++#endif /* _FSL_DPNI_CMD_H */ +diff --git a/drivers/staging/fsl-dpaa2/ethernet/dpni.c b/drivers/staging/fsl-dpaa2/ethernet/dpni.c +new file mode 100644 +index 0000000..c228ce5 +--- /dev/null ++++ b/drivers/staging/fsl-dpaa2/ethernet/dpni.c +@@ -0,0 +1,1907 @@ ++/* Copyright 2013-2015 Freescale Semiconductor Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * * Neither the name of the above-listed copyright holders nor the ++ * names of any contributors may be used to endorse or promote products ++ * derived from this software without specific prior written permission. ++ * ++ * ++ * ALTERNATIVELY, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") as published by the Free Software ++ * Foundation, either version 2 of that License or (at your option) any ++ * later version. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE ++ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++#include "../../fsl-mc/include/mc-sys.h" ++#include "../../fsl-mc/include/mc-cmd.h" ++#include "dpni.h" ++#include "dpni-cmd.h" ++ ++int dpni_prepare_key_cfg(const struct dpkg_profile_cfg *cfg, ++ uint8_t *key_cfg_buf) ++{ ++ int i, j; ++ int offset = 0; ++ int param = 1; ++ uint64_t *params = (uint64_t *)key_cfg_buf; ++ ++ if (!key_cfg_buf || !cfg) ++ return -EINVAL; ++ ++ params[0] |= mc_enc(0, 8, cfg->num_extracts); ++ params[0] = cpu_to_le64(params[0]); ++ ++ if (cfg->num_extracts >= DPKG_MAX_NUM_OF_EXTRACTS) ++ return -EINVAL; ++ ++ for (i = 0; i < cfg->num_extracts; i++) { ++ switch (cfg->extracts[i].type) { ++ case DPKG_EXTRACT_FROM_HDR: ++ params[param] |= mc_enc(0, 8, ++ cfg->extracts[i].extract.from_hdr.prot); ++ params[param] |= mc_enc(8, 4, ++ cfg->extracts[i].extract.from_hdr.type); ++ params[param] |= mc_enc(16, 8, ++ cfg->extracts[i].extract.from_hdr.size); ++ params[param] |= mc_enc(24, 8, ++ cfg->extracts[i].extract. ++ from_hdr.offset); ++ params[param] |= mc_enc(32, 32, ++ cfg->extracts[i].extract. ++ from_hdr.field); ++ params[param] = cpu_to_le64(params[param]); ++ param++; ++ params[param] |= mc_enc(0, 8, ++ cfg->extracts[i].extract. ++ from_hdr.hdr_index); ++ break; ++ case DPKG_EXTRACT_FROM_DATA: ++ params[param] |= mc_enc(16, 8, ++ cfg->extracts[i].extract. ++ from_data.size); ++ params[param] |= mc_enc(24, 8, ++ cfg->extracts[i].extract. ++ from_data.offset); ++ params[param] = cpu_to_le64(params[param]); ++ param++; ++ break; ++ case DPKG_EXTRACT_FROM_PARSE: ++ params[param] |= mc_enc(16, 8, ++ cfg->extracts[i].extract. ++ from_parse.size); ++ params[param] |= mc_enc(24, 8, ++ cfg->extracts[i].extract. ++ from_parse.offset); ++ params[param] = cpu_to_le64(params[param]); ++ param++; ++ break; ++ default: ++ return -EINVAL; ++ } ++ params[param] |= mc_enc( ++ 24, 8, cfg->extracts[i].num_of_byte_masks); ++ params[param] |= mc_enc(32, 4, cfg->extracts[i].type); ++ params[param] = cpu_to_le64(params[param]); ++ param++; ++ for (offset = 0, j = 0; ++ j < DPKG_NUM_OF_MASKS; ++ offset += 16, j++) { ++ params[param] |= mc_enc( ++ (offset), 8, cfg->extracts[i].masks[j].mask); ++ params[param] |= mc_enc( ++ (offset + 8), 8, ++ cfg->extracts[i].masks[j].offset); ++ } ++ params[param] = cpu_to_le64(params[param]); ++ param++; ++ } ++ return 0; ++} ++ ++int dpni_prepare_extended_cfg(const struct dpni_extended_cfg *cfg, ++ uint8_t *ext_cfg_buf) ++{ ++ uint64_t *ext_params = (uint64_t *)ext_cfg_buf; ++ ++ DPNI_PREP_EXTENDED_CFG(ext_params, cfg); ++ ++ return 0; ++} ++ ++int dpni_extract_extended_cfg(struct dpni_extended_cfg *cfg, ++ const uint8_t *ext_cfg_buf) ++{ ++ const uint64_t *ext_params = (const uint64_t *)ext_cfg_buf; ++ ++ DPNI_EXT_EXTENDED_CFG(ext_params, cfg); ++ ++ return 0; ++} ++ ++int dpni_open(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ int dpni_id, ++ uint16_t *token) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_OPEN, ++ cmd_flags, ++ 0); ++ DPNI_CMD_OPEN(cmd, dpni_id); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ *token = MC_CMD_HDR_READ_TOKEN(cmd.header); ++ ++ return 0; ++} ++ ++int dpni_close(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_CLOSE, ++ cmd_flags, ++ token); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpni_create(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ const struct dpni_cfg *cfg, ++ uint16_t *token) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_CREATE, ++ cmd_flags, ++ 0); ++ DPNI_CMD_CREATE(cmd, cfg); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ *token = MC_CMD_HDR_READ_TOKEN(cmd.header); ++ ++ return 0; ++} ++ ++int dpni_destroy(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_DESTROY, ++ cmd_flags, ++ token); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpni_set_pools(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ const struct dpni_pools_cfg *cfg) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_SET_POOLS, ++ cmd_flags, ++ token); ++ DPNI_CMD_SET_POOLS(cmd, cfg); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpni_enable(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_ENABLE, ++ cmd_flags, ++ token); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpni_disable(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_DISABLE, ++ cmd_flags, ++ token); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpni_is_enabled(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int *en) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_IS_ENABLED, cmd_flags, ++ token); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ DPNI_RSP_IS_ENABLED(cmd, *en); ++ ++ return 0; ++} ++ ++int dpni_reset(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_RESET, ++ cmd_flags, ++ token); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpni_set_irq(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ struct dpni_irq_cfg *irq_cfg) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_SET_IRQ, ++ cmd_flags, ++ token); ++ DPNI_CMD_SET_IRQ(cmd, irq_index, irq_cfg); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpni_get_irq(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ int *type, ++ struct dpni_irq_cfg *irq_cfg) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_GET_IRQ, ++ cmd_flags, ++ token); ++ DPNI_CMD_GET_IRQ(cmd, irq_index); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ DPNI_RSP_GET_IRQ(cmd, *type, irq_cfg); ++ ++ return 0; ++} ++ ++int dpni_set_irq_enable(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint8_t en) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_SET_IRQ_ENABLE, ++ cmd_flags, ++ token); ++ DPNI_CMD_SET_IRQ_ENABLE(cmd, irq_index, en); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpni_get_irq_enable(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint8_t *en) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_GET_IRQ_ENABLE, ++ cmd_flags, ++ token); ++ DPNI_CMD_GET_IRQ_ENABLE(cmd, irq_index); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ DPNI_RSP_GET_IRQ_ENABLE(cmd, *en); ++ ++ return 0; ++} ++ ++int dpni_set_irq_mask(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint32_t mask) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_SET_IRQ_MASK, ++ cmd_flags, ++ token); ++ DPNI_CMD_SET_IRQ_MASK(cmd, irq_index, mask); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpni_get_irq_mask(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint32_t *mask) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_GET_IRQ_MASK, ++ cmd_flags, ++ token); ++ DPNI_CMD_GET_IRQ_MASK(cmd, irq_index); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ DPNI_RSP_GET_IRQ_MASK(cmd, *mask); ++ ++ return 0; ++} ++ ++int dpni_get_irq_status(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint32_t *status) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_GET_IRQ_STATUS, ++ cmd_flags, ++ token); ++ DPNI_CMD_GET_IRQ_STATUS(cmd, irq_index, *status); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ DPNI_RSP_GET_IRQ_STATUS(cmd, *status); ++ ++ return 0; ++} ++ ++int dpni_clear_irq_status(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint32_t status) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_CLEAR_IRQ_STATUS, ++ cmd_flags, ++ token); ++ DPNI_CMD_CLEAR_IRQ_STATUS(cmd, irq_index, status); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpni_get_attributes(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ struct dpni_attr *attr) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_GET_ATTR, ++ cmd_flags, ++ token); ++ DPNI_CMD_GET_ATTR(cmd, attr); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ DPNI_RSP_GET_ATTR(cmd, attr); ++ ++ return 0; ++} ++ ++int dpni_set_errors_behavior(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ struct dpni_error_cfg *cfg) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_SET_ERRORS_BEHAVIOR, ++ cmd_flags, ++ token); ++ DPNI_CMD_SET_ERRORS_BEHAVIOR(cmd, cfg); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpni_get_rx_buffer_layout(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ struct dpni_buffer_layout *layout) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_GET_RX_BUFFER_LAYOUT, ++ cmd_flags, ++ token); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ DPNI_RSP_GET_RX_BUFFER_LAYOUT(cmd, layout); ++ ++ return 0; ++} ++ ++int dpni_set_rx_buffer_layout(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ const struct dpni_buffer_layout *layout) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_SET_RX_BUFFER_LAYOUT, ++ cmd_flags, ++ token); ++ DPNI_CMD_SET_RX_BUFFER_LAYOUT(cmd, layout); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpni_get_tx_buffer_layout(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ struct dpni_buffer_layout *layout) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_GET_TX_BUFFER_LAYOUT, ++ cmd_flags, ++ token); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ DPNI_RSP_GET_TX_BUFFER_LAYOUT(cmd, layout); ++ ++ return 0; ++} ++ ++int dpni_set_tx_buffer_layout(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ const struct dpni_buffer_layout *layout) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_SET_TX_BUFFER_LAYOUT, ++ cmd_flags, ++ token); ++ DPNI_CMD_SET_TX_BUFFER_LAYOUT(cmd, layout); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpni_get_tx_conf_buffer_layout(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ struct dpni_buffer_layout *layout) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_GET_TX_CONF_BUFFER_LAYOUT, ++ cmd_flags, ++ token); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ DPNI_RSP_GET_TX_CONF_BUFFER_LAYOUT(cmd, layout); ++ ++ return 0; ++} ++ ++int dpni_set_tx_conf_buffer_layout(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ const struct dpni_buffer_layout *layout) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_SET_TX_CONF_BUFFER_LAYOUT, ++ cmd_flags, ++ token); ++ DPNI_CMD_SET_TX_CONF_BUFFER_LAYOUT(cmd, layout); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpni_get_l3_chksum_validation(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int *en) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_GET_L3_CHKSUM_VALIDATION, ++ cmd_flags, ++ token); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ DPNI_RSP_GET_L3_CHKSUM_VALIDATION(cmd, *en); ++ ++ return 0; ++} ++ ++int dpni_set_l3_chksum_validation(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int en) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_SET_L3_CHKSUM_VALIDATION, ++ cmd_flags, ++ token); ++ DPNI_CMD_SET_L3_CHKSUM_VALIDATION(cmd, en); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpni_get_l4_chksum_validation(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int *en) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_GET_L4_CHKSUM_VALIDATION, ++ cmd_flags, ++ token); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ DPNI_RSP_GET_L4_CHKSUM_VALIDATION(cmd, *en); ++ ++ return 0; ++} ++ ++int dpni_set_l4_chksum_validation(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int en) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_SET_L4_CHKSUM_VALIDATION, ++ cmd_flags, ++ token); ++ DPNI_CMD_SET_L4_CHKSUM_VALIDATION(cmd, en); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpni_get_qdid(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint16_t *qdid) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_GET_QDID, ++ cmd_flags, ++ token); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ DPNI_RSP_GET_QDID(cmd, *qdid); ++ ++ return 0; ++} ++ ++int dpni_get_sp_info(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ struct dpni_sp_info *sp_info) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_GET_SP_INFO, ++ cmd_flags, ++ token); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ DPNI_RSP_GET_SP_INFO(cmd, sp_info); ++ ++ return 0; ++} ++ ++int dpni_get_tx_data_offset(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint16_t *data_offset) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_GET_TX_DATA_OFFSET, ++ cmd_flags, ++ token); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ DPNI_RSP_GET_TX_DATA_OFFSET(cmd, *data_offset); ++ ++ return 0; ++} ++ ++int dpni_get_counter(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ enum dpni_counter counter, ++ uint64_t *value) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_GET_COUNTER, ++ cmd_flags, ++ token); ++ DPNI_CMD_GET_COUNTER(cmd, counter); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ DPNI_RSP_GET_COUNTER(cmd, *value); ++ ++ return 0; ++} ++ ++int dpni_set_counter(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ enum dpni_counter counter, ++ uint64_t value) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_SET_COUNTER, ++ cmd_flags, ++ token); ++ DPNI_CMD_SET_COUNTER(cmd, counter, value); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpni_set_link_cfg(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ const struct dpni_link_cfg *cfg) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_SET_LINK_CFG, ++ cmd_flags, ++ token); ++ DPNI_CMD_SET_LINK_CFG(cmd, cfg); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpni_get_link_state(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ struct dpni_link_state *state) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_GET_LINK_STATE, ++ cmd_flags, ++ token); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ DPNI_RSP_GET_LINK_STATE(cmd, state); ++ ++ return 0; ++} ++ ++int dpni_set_tx_shaping(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ const struct dpni_tx_shaping_cfg *tx_shaper) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_SET_TX_SHAPING, ++ cmd_flags, ++ token); ++ DPNI_CMD_SET_TX_SHAPING(cmd, tx_shaper); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpni_set_max_frame_length(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint16_t max_frame_length) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_SET_MAX_FRAME_LENGTH, ++ cmd_flags, ++ token); ++ DPNI_CMD_SET_MAX_FRAME_LENGTH(cmd, max_frame_length); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpni_get_max_frame_length(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint16_t *max_frame_length) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_GET_MAX_FRAME_LENGTH, ++ cmd_flags, ++ token); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ DPNI_RSP_GET_MAX_FRAME_LENGTH(cmd, *max_frame_length); ++ ++ return 0; ++} ++ ++int dpni_set_mtu(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint16_t mtu) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_SET_MTU, ++ cmd_flags, ++ token); ++ DPNI_CMD_SET_MTU(cmd, mtu); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpni_get_mtu(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint16_t *mtu) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_GET_MTU, ++ cmd_flags, ++ token); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ DPNI_RSP_GET_MTU(cmd, *mtu); ++ ++ return 0; ++} ++ ++int dpni_set_multicast_promisc(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int en) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_SET_MCAST_PROMISC, ++ cmd_flags, ++ token); ++ DPNI_CMD_SET_MULTICAST_PROMISC(cmd, en); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpni_get_multicast_promisc(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int *en) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_GET_MCAST_PROMISC, ++ cmd_flags, ++ token); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ DPNI_RSP_GET_MULTICAST_PROMISC(cmd, *en); ++ ++ return 0; ++} ++ ++int dpni_set_unicast_promisc(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int en) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_SET_UNICAST_PROMISC, ++ cmd_flags, ++ token); ++ DPNI_CMD_SET_UNICAST_PROMISC(cmd, en); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpni_get_unicast_promisc(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int *en) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_GET_UNICAST_PROMISC, ++ cmd_flags, ++ token); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ DPNI_RSP_GET_UNICAST_PROMISC(cmd, *en); ++ ++ return 0; ++} ++ ++int dpni_set_primary_mac_addr(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ const uint8_t mac_addr[6]) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_SET_PRIM_MAC, ++ cmd_flags, ++ token); ++ DPNI_CMD_SET_PRIMARY_MAC_ADDR(cmd, mac_addr); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpni_get_primary_mac_addr(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t mac_addr[6]) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_GET_PRIM_MAC, ++ cmd_flags, ++ token); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ DPNI_RSP_GET_PRIMARY_MAC_ADDR(cmd, mac_addr); ++ ++ return 0; ++} ++ ++int dpni_add_mac_addr(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ const uint8_t mac_addr[6]) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_ADD_MAC_ADDR, ++ cmd_flags, ++ token); ++ DPNI_CMD_ADD_MAC_ADDR(cmd, mac_addr); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpni_remove_mac_addr(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ const uint8_t mac_addr[6]) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_REMOVE_MAC_ADDR, ++ cmd_flags, ++ token); ++ DPNI_CMD_REMOVE_MAC_ADDR(cmd, mac_addr); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpni_clear_mac_filters(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int unicast, ++ int multicast) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_CLR_MAC_FILTERS, ++ cmd_flags, ++ token); ++ DPNI_CMD_CLEAR_MAC_FILTERS(cmd, unicast, multicast); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpni_set_vlan_filters(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int en) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_SET_VLAN_FILTERS, ++ cmd_flags, ++ token); ++ DPNI_CMD_SET_VLAN_FILTERS(cmd, en); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpni_add_vlan_id(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint16_t vlan_id) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_ADD_VLAN_ID, ++ cmd_flags, ++ token); ++ DPNI_CMD_ADD_VLAN_ID(cmd, vlan_id); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpni_remove_vlan_id(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint16_t vlan_id) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_REMOVE_VLAN_ID, ++ cmd_flags, ++ token); ++ DPNI_CMD_REMOVE_VLAN_ID(cmd, vlan_id); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpni_clear_vlan_filters(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_CLR_VLAN_FILTERS, ++ cmd_flags, ++ token); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpni_set_tx_selection(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ const struct dpni_tx_selection_cfg *cfg) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_SET_TX_SELECTION, ++ cmd_flags, ++ token); ++ DPNI_CMD_SET_TX_SELECTION(cmd, cfg); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpni_set_rx_tc_dist(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t tc_id, ++ const struct dpni_rx_tc_dist_cfg *cfg) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_SET_RX_TC_DIST, ++ cmd_flags, ++ token); ++ DPNI_CMD_SET_RX_TC_DIST(cmd, tc_id, cfg); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpni_set_tx_flow(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint16_t *flow_id, ++ const struct dpni_tx_flow_cfg *cfg) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_SET_TX_FLOW, ++ cmd_flags, ++ token); ++ DPNI_CMD_SET_TX_FLOW(cmd, *flow_id, cfg); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ DPNI_RSP_SET_TX_FLOW(cmd, *flow_id); ++ ++ return 0; ++} ++ ++int dpni_get_tx_flow(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint16_t flow_id, ++ struct dpni_tx_flow_attr *attr) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_GET_TX_FLOW, ++ cmd_flags, ++ token); ++ DPNI_CMD_GET_TX_FLOW(cmd, flow_id); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ DPNI_RSP_GET_TX_FLOW(cmd, attr); ++ ++ return 0; ++} ++ ++int dpni_set_rx_flow(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t tc_id, ++ uint16_t flow_id, ++ const struct dpni_queue_cfg *cfg) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_SET_RX_FLOW, ++ cmd_flags, ++ token); ++ DPNI_CMD_SET_RX_FLOW(cmd, tc_id, flow_id, cfg); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpni_get_rx_flow(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t tc_id, ++ uint16_t flow_id, ++ struct dpni_queue_attr *attr) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_GET_RX_FLOW, ++ cmd_flags, ++ token); ++ DPNI_CMD_GET_RX_FLOW(cmd, tc_id, flow_id); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ DPNI_RSP_GET_RX_FLOW(cmd, attr); ++ ++ return 0; ++} ++ ++int dpni_set_rx_err_queue(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ const struct dpni_queue_cfg *cfg) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_SET_RX_ERR_QUEUE, ++ cmd_flags, ++ token); ++ DPNI_CMD_SET_RX_ERR_QUEUE(cmd, cfg); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpni_get_rx_err_queue(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ struct dpni_queue_attr *attr) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_GET_RX_ERR_QUEUE, ++ cmd_flags, ++ token); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ DPNI_RSP_GET_RX_ERR_QUEUE(cmd, attr); ++ ++ return 0; ++} ++ ++int dpni_set_tx_conf_revoke(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int revoke) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_SET_TX_CONF_REVOKE, ++ cmd_flags, ++ token); ++ DPNI_CMD_SET_TX_CONF_REVOKE(cmd, revoke); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpni_set_qos_table(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ const struct dpni_qos_tbl_cfg *cfg) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_SET_QOS_TBL, ++ cmd_flags, ++ token); ++ DPNI_CMD_SET_QOS_TABLE(cmd, cfg); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpni_add_qos_entry(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ const struct dpni_rule_cfg *cfg, ++ uint8_t tc_id) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_ADD_QOS_ENT, ++ cmd_flags, ++ token); ++ DPNI_CMD_ADD_QOS_ENTRY(cmd, cfg, tc_id); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpni_remove_qos_entry(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ const struct dpni_rule_cfg *cfg) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_REMOVE_QOS_ENT, ++ cmd_flags, ++ token); ++ DPNI_CMD_REMOVE_QOS_ENTRY(cmd, cfg); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpni_clear_qos_table(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_CLR_QOS_TBL, ++ cmd_flags, ++ token); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpni_add_fs_entry(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t tc_id, ++ const struct dpni_rule_cfg *cfg, ++ uint16_t flow_id) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_ADD_FS_ENT, ++ cmd_flags, ++ token); ++ DPNI_CMD_ADD_FS_ENTRY(cmd, tc_id, cfg, flow_id); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpni_remove_fs_entry(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t tc_id, ++ const struct dpni_rule_cfg *cfg) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_REMOVE_FS_ENT, ++ cmd_flags, ++ token); ++ DPNI_CMD_REMOVE_FS_ENTRY(cmd, tc_id, cfg); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpni_clear_fs_entries(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t tc_id) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_CLR_FS_ENT, ++ cmd_flags, ++ token); ++ DPNI_CMD_CLEAR_FS_ENTRIES(cmd, tc_id); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpni_set_vlan_insertion(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int en) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_SET_VLAN_INSERTION, ++ cmd_flags, token); ++ DPNI_CMD_SET_VLAN_INSERTION(cmd, en); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpni_set_vlan_removal(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int en) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_SET_VLAN_REMOVAL, ++ cmd_flags, token); ++ DPNI_CMD_SET_VLAN_REMOVAL(cmd, en); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpni_set_ipr(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int en) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_SET_IPR, ++ cmd_flags, ++ token); ++ DPNI_CMD_SET_IPR(cmd, en); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpni_set_ipf(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int en) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_SET_IPF, ++ cmd_flags, ++ token); ++ DPNI_CMD_SET_IPF(cmd, en); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpni_set_rx_tc_policing(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t tc_id, ++ const struct dpni_rx_tc_policing_cfg *cfg) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_SET_RX_TC_POLICING, ++ cmd_flags, ++ token); ++ DPNI_CMD_SET_RX_TC_POLICING(cmd, tc_id, cfg); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpni_get_rx_tc_policing(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t tc_id, ++ struct dpni_rx_tc_policing_cfg *cfg) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_GET_RX_TC_POLICING, ++ cmd_flags, ++ token); ++ DPNI_CMD_GET_RX_TC_POLICING(cmd, tc_id); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ DPNI_RSP_GET_RX_TC_POLICING(cmd, cfg); ++ ++ return 0; ++} ++ ++void dpni_prepare_early_drop(const struct dpni_early_drop_cfg *cfg, ++ uint8_t *early_drop_buf) ++{ ++ uint64_t *ext_params = (uint64_t *)early_drop_buf; ++ ++ DPNI_PREP_EARLY_DROP(ext_params, cfg); ++} ++ ++void dpni_extract_early_drop(struct dpni_early_drop_cfg *cfg, ++ const uint8_t *early_drop_buf) ++{ ++ const uint64_t *ext_params = (const uint64_t *)early_drop_buf; ++ ++ DPNI_EXT_EARLY_DROP(ext_params, cfg); ++} ++ ++int dpni_set_rx_tc_early_drop(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t tc_id, ++ uint64_t early_drop_iova) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_SET_RX_TC_EARLY_DROP, ++ cmd_flags, ++ token); ++ DPNI_CMD_SET_RX_TC_EARLY_DROP(cmd, tc_id, early_drop_iova); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpni_get_rx_tc_early_drop(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t tc_id, ++ uint64_t early_drop_iova) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_GET_RX_TC_EARLY_DROP, ++ cmd_flags, ++ token); ++ DPNI_CMD_GET_RX_TC_EARLY_DROP(cmd, tc_id, early_drop_iova); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpni_set_tx_tc_early_drop(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t tc_id, ++ uint64_t early_drop_iova) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_SET_TX_TC_EARLY_DROP, ++ cmd_flags, ++ token); ++ DPNI_CMD_SET_TX_TC_EARLY_DROP(cmd, tc_id, early_drop_iova); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpni_get_tx_tc_early_drop(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t tc_id, ++ uint64_t early_drop_iova) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_GET_TX_TC_EARLY_DROP, ++ cmd_flags, ++ token); ++ DPNI_CMD_GET_TX_TC_EARLY_DROP(cmd, tc_id, early_drop_iova); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpni_set_rx_tc_congestion_notification(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t tc_id, ++ const struct dpni_congestion_notification_cfg *cfg) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header( ++ DPNI_CMDID_SET_RX_TC_CONGESTION_NOTIFICATION, ++ cmd_flags, ++ token); ++ DPNI_CMD_SET_RX_TC_CONGESTION_NOTIFICATION(cmd, tc_id, cfg); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpni_get_rx_tc_congestion_notification(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t tc_id, ++ struct dpni_congestion_notification_cfg *cfg) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header( ++ DPNI_CMDID_GET_RX_TC_CONGESTION_NOTIFICATION, ++ cmd_flags, ++ token); ++ DPNI_CMD_GET_RX_TC_CONGESTION_NOTIFICATION(cmd, tc_id); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ DPNI_RSP_GET_RX_TC_CONGESTION_NOTIFICATION(cmd, cfg); ++ ++ return 0; ++} ++ ++int dpni_set_tx_tc_congestion_notification(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t tc_id, ++ const struct dpni_congestion_notification_cfg *cfg) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header( ++ DPNI_CMDID_SET_TX_TC_CONGESTION_NOTIFICATION, ++ cmd_flags, ++ token); ++ DPNI_CMD_SET_TX_TC_CONGESTION_NOTIFICATION(cmd, tc_id, cfg); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpni_get_tx_tc_congestion_notification(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t tc_id, ++ struct dpni_congestion_notification_cfg *cfg) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header( ++ DPNI_CMDID_GET_TX_TC_CONGESTION_NOTIFICATION, ++ cmd_flags, ++ token); ++ DPNI_CMD_GET_TX_TC_CONGESTION_NOTIFICATION(cmd, tc_id); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ DPNI_RSP_GET_TX_TC_CONGESTION_NOTIFICATION(cmd, cfg); ++ ++ return 0; ++} ++ ++int dpni_set_tx_conf(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint16_t flow_id, ++ const struct dpni_tx_conf_cfg *cfg) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_SET_TX_CONF, ++ cmd_flags, ++ token); ++ DPNI_CMD_SET_TX_CONF(cmd, flow_id, cfg); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpni_get_tx_conf(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint16_t flow_id, ++ struct dpni_tx_conf_attr *attr) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPNI_CMDID_GET_TX_CONF, ++ cmd_flags, ++ token); ++ DPNI_CMD_GET_TX_CONF(cmd, flow_id); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ DPNI_RSP_GET_TX_CONF(cmd, attr); ++ ++ return 0; ++} ++ ++int dpni_set_tx_conf_congestion_notification(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint16_t flow_id, ++ const struct dpni_congestion_notification_cfg *cfg) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header( ++ DPNI_CMDID_SET_TX_CONF_CONGESTION_NOTIFICATION, ++ cmd_flags, ++ token); ++ DPNI_CMD_SET_TX_CONF_CONGESTION_NOTIFICATION(cmd, flow_id, cfg); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpni_get_tx_conf_congestion_notification(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint16_t flow_id, ++ struct dpni_congestion_notification_cfg *cfg) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header( ++ DPNI_CMDID_GET_TX_CONF_CONGESTION_NOTIFICATION, ++ cmd_flags, ++ token); ++ DPNI_CMD_GET_TX_CONF_CONGESTION_NOTIFICATION(cmd, flow_id); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ DPNI_RSP_GET_TX_CONF_CONGESTION_NOTIFICATION(cmd, cfg); ++ ++ return 0; ++} +diff --git a/drivers/staging/fsl-dpaa2/ethernet/dpni.h b/drivers/staging/fsl-dpaa2/ethernet/dpni.h +new file mode 100644 +index 0000000..fca426d +--- /dev/null ++++ b/drivers/staging/fsl-dpaa2/ethernet/dpni.h +@@ -0,0 +1,2581 @@ ++/* Copyright 2013-2015 Freescale Semiconductor Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * * Neither the name of the above-listed copyright holders nor the ++ * names of any contributors may be used to endorse or promote products ++ * derived from this software without specific prior written permission. ++ * ++ * ++ * ALTERNATIVELY, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") as published by the Free Software ++ * Foundation, either version 2 of that License or (at your option) any ++ * later version. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE ++ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++#ifndef __FSL_DPNI_H ++#define __FSL_DPNI_H ++ ++#include "dpkg.h" ++ ++struct fsl_mc_io; ++ ++/** ++ * Data Path Network Interface API ++ * Contains initialization APIs and runtime control APIs for DPNI ++ */ ++ ++/** General DPNI macros */ ++ ++/** ++ * Maximum number of traffic classes ++ */ ++#define DPNI_MAX_TC 8 ++/** ++ * Maximum number of buffer pools per DPNI ++ */ ++#define DPNI_MAX_DPBP 8 ++/** ++ * Maximum number of storage-profiles per DPNI ++ */ ++#define DPNI_MAX_SP 2 ++ ++/** ++ * All traffic classes considered; see dpni_set_rx_flow() ++ */ ++#define DPNI_ALL_TCS (uint8_t)(-1) ++/** ++ * All flows within traffic class considered; see dpni_set_rx_flow() ++ */ ++#define DPNI_ALL_TC_FLOWS (uint16_t)(-1) ++/** ++ * Generate new flow ID; see dpni_set_tx_flow() ++ */ ++#define DPNI_NEW_FLOW_ID (uint16_t)(-1) ++/* use for common tx-conf queue; see dpni_set_tx_conf_() */ ++#define DPNI_COMMON_TX_CONF (uint16_t)(-1) ++ ++/** ++ * dpni_open() - Open a control session for the specified object ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @dpni_id: DPNI unique ID ++ * @token: Returned token; use in subsequent API calls ++ * ++ * This function can be used to open a control session for an ++ * already created object; an object may have been declared in ++ * the DPL or by calling the dpni_create() function. ++ * This function returns a unique authentication token, ++ * associated with the specific object ID and the specific MC ++ * portal; this token must be used in all subsequent commands for ++ * this specific object. ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_open(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ int dpni_id, ++ uint16_t *token); ++ ++/** ++ * dpni_close() - Close the control session of the object ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * ++ * After this function is called, no further operations are ++ * allowed on the object without opening a new control session. ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_close(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token); ++ ++/* DPNI configuration options */ ++ ++/** ++ * Allow different distribution key profiles for different traffic classes; ++ * if not set, a single key profile is assumed ++ */ ++#define DPNI_OPT_ALLOW_DIST_KEY_PER_TC 0x00000001 ++ ++/** ++ * Disable all non-error transmit confirmation; error frames are reported ++ * back to a common Tx error queue ++ */ ++#define DPNI_OPT_TX_CONF_DISABLED 0x00000002 ++ ++/** ++ * Disable per-sender private Tx confirmation/error queue ++ */ ++#define DPNI_OPT_PRIVATE_TX_CONF_ERROR_DISABLED 0x00000004 ++ ++/** ++ * Support distribution based on hashed key; ++ * allows statistical distribution over receive queues in a traffic class ++ */ ++#define DPNI_OPT_DIST_HASH 0x00000010 ++ ++/** ++ * DEPRECATED - if this flag is selected and and all new 'max_fs_entries' are ++ * '0' then backward compatibility is preserved; ++ * Support distribution based on flow steering; ++ * allows explicit control of distribution over receive queues in a traffic ++ * class ++ */ ++#define DPNI_OPT_DIST_FS 0x00000020 ++ ++/** ++ * Unicast filtering support ++ */ ++#define DPNI_OPT_UNICAST_FILTER 0x00000080 ++/** ++ * Multicast filtering support ++ */ ++#define DPNI_OPT_MULTICAST_FILTER 0x00000100 ++/** ++ * VLAN filtering support ++ */ ++#define DPNI_OPT_VLAN_FILTER 0x00000200 ++/** ++ * Support IP reassembly on received packets ++ */ ++#define DPNI_OPT_IPR 0x00000800 ++/** ++ * Support IP fragmentation on transmitted packets ++ */ ++#define DPNI_OPT_IPF 0x00001000 ++/** ++ * VLAN manipulation support ++ */ ++#define DPNI_OPT_VLAN_MANIPULATION 0x00010000 ++/** ++ * Support masking of QoS lookup keys ++ */ ++#define DPNI_OPT_QOS_MASK_SUPPORT 0x00020000 ++/** ++ * Support masking of Flow Steering lookup keys ++ */ ++#define DPNI_OPT_FS_MASK_SUPPORT 0x00040000 ++ ++/** ++ * struct dpni_extended_cfg - Structure representing extended DPNI configuration ++ * @tc_cfg: TCs configuration ++ * @ipr_cfg: IP reassembly configuration ++ */ ++struct dpni_extended_cfg { ++ /** ++ * struct tc_cfg - TC configuration ++ * @max_dist: Maximum distribution size for Rx traffic class; ++ * supported values: 1,2,3,4,6,7,8,12,14,16,24,28,32,48,56,64,96, ++ * 112,128,192,224,256,384,448,512,768,896,1024; ++ * value '0' will be treated as '1'. ++ * other unsupported values will be round down to the nearest ++ * supported value. ++ * @max_fs_entries: Maximum FS entries for Rx traffic class; ++ * '0' means no support for this TC; ++ */ ++ struct { ++ uint16_t max_dist; ++ uint16_t max_fs_entries; ++ } tc_cfg[DPNI_MAX_TC]; ++ /** ++ * struct ipr_cfg - Structure representing IP reassembly configuration ++ * @max_reass_frm_size: Maximum size of the reassembled frame ++ * @min_frag_size_ipv4: Minimum fragment size of IPv4 fragments ++ * @min_frag_size_ipv6: Minimum fragment size of IPv6 fragments ++ * @max_open_frames_ipv4: Maximum concurrent IPv4 packets in reassembly ++ * process ++ * @max_open_frames_ipv6: Maximum concurrent IPv6 packets in reassembly ++ * process ++ */ ++ struct { ++ uint16_t max_reass_frm_size; ++ uint16_t min_frag_size_ipv4; ++ uint16_t min_frag_size_ipv6; ++ uint16_t max_open_frames_ipv4; ++ uint16_t max_open_frames_ipv6; ++ } ipr_cfg; ++}; ++ ++/** ++ * dpni_prepare_extended_cfg() - function prepare extended parameters ++ * @cfg: extended structure ++ * @ext_cfg_buf: Zeroed 256 bytes of memory before mapping it to DMA ++ * ++ * This function has to be called before dpni_create() ++ */ ++int dpni_prepare_extended_cfg(const struct dpni_extended_cfg *cfg, ++ uint8_t *ext_cfg_buf); ++ ++/** ++ * struct dpni_cfg - Structure representing DPNI configuration ++ * @mac_addr: Primary MAC address ++ * @adv: Advanced parameters; default is all zeros; ++ * use this structure to change default settings ++ */ ++struct dpni_cfg { ++ uint8_t mac_addr[6]; ++ /** ++ * struct adv - Advanced parameters ++ * @options: Mask of available options; use 'DPNI_OPT_' values ++ * @start_hdr: Selects the packet starting header for parsing; ++ * 'NET_PROT_NONE' is treated as default: 'NET_PROT_ETH' ++ * @max_senders: Maximum number of different senders; used as the number ++ * of dedicated Tx flows; Non-power-of-2 values are rounded ++ * up to the next power-of-2 value as hardware demands it; ++ * '0' will be treated as '1' ++ * @max_tcs: Maximum number of traffic classes (for both Tx and Rx); ++ * '0' will e treated as '1' ++ * @max_unicast_filters: Maximum number of unicast filters; ++ * '0' is treated as '16' ++ * @max_multicast_filters: Maximum number of multicast filters; ++ * '0' is treated as '64' ++ * @max_qos_entries: if 'max_tcs > 1', declares the maximum entries in ++ * the QoS table; '0' is treated as '64' ++ * @max_qos_key_size: Maximum key size for the QoS look-up; ++ * '0' is treated as '24' which is enough for IPv4 ++ * 5-tuple ++ * @max_dist_key_size: Maximum key size for the distribution; ++ * '0' is treated as '24' which is enough for IPv4 5-tuple ++ * @max_policers: Maximum number of policers; ++ * should be between '0' and max_tcs ++ * @max_congestion_ctrl: Maximum number of congestion control groups ++ * (CGs); covers early drop and congestion notification ++ * requirements; ++ * should be between '0' and ('max_tcs' + 'max_senders') ++ * @ext_cfg_iova: I/O virtual address of 256 bytes DMA-able memory ++ * filled with the extended configuration by calling ++ * dpni_prepare_extended_cfg() ++ */ ++ struct { ++ uint32_t options; ++ enum net_prot start_hdr; ++ uint8_t max_senders; ++ uint8_t max_tcs; ++ uint8_t max_unicast_filters; ++ uint8_t max_multicast_filters; ++ uint8_t max_vlan_filters; ++ uint8_t max_qos_entries; ++ uint8_t max_qos_key_size; ++ uint8_t max_dist_key_size; ++ uint8_t max_policers; ++ uint8_t max_congestion_ctrl; ++ uint64_t ext_cfg_iova; ++ } adv; ++}; ++ ++/** ++ * dpni_create() - Create the DPNI object ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @cfg: Configuration structure ++ * @token: Returned token; use in subsequent API calls ++ * ++ * Create the DPNI object, allocate required resources and ++ * perform required initialization. ++ * ++ * The object can be created either by declaring it in the ++ * DPL file, or by calling this function. ++ * ++ * This function returns a unique authentication token, ++ * associated with the specific object ID and the specific MC ++ * portal; this token must be used in all subsequent calls to ++ * this specific object. For objects that are created using the ++ * DPL file, call dpni_open() function to get an authentication ++ * token first. ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_create(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ const struct dpni_cfg *cfg, ++ uint16_t *token); ++ ++/** ++ * dpni_destroy() - Destroy the DPNI object and release all its resources. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * ++ * Return: '0' on Success; error code otherwise. ++ */ ++int dpni_destroy(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token); ++ ++/** ++ * struct dpni_pools_cfg - Structure representing buffer pools configuration ++ * @num_dpbp: Number of DPBPs ++ * @pools: Array of buffer pools parameters; The number of valid entries ++ * must match 'num_dpbp' value ++ */ ++struct dpni_pools_cfg { ++ uint8_t num_dpbp; ++ /** ++ * struct pools - Buffer pools parameters ++ * @dpbp_id: DPBP object ID ++ * @buffer_size: Buffer size ++ * @backup_pool: Backup pool ++ */ ++ struct { ++ int dpbp_id; ++ uint16_t buffer_size; ++ int backup_pool; ++ } pools[DPNI_MAX_DPBP]; ++}; ++ ++/** ++ * dpni_set_pools() - Set buffer pools configuration ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @cfg: Buffer pools configuration ++ * ++ * mandatory for DPNI operation ++ * warning:Allowed only when DPNI is disabled ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_set_pools(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ const struct dpni_pools_cfg *cfg); ++ ++/** ++ * dpni_enable() - Enable the DPNI, allow sending and receiving frames. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_enable(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token); ++ ++/** ++ * dpni_disable() - Disable the DPNI, stop sending and receiving frames. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_disable(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token); ++ ++/** ++ * dpni_is_enabled() - Check if the DPNI is enabled. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @en: Returns '1' if object is enabled; '0' otherwise ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_is_enabled(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int *en); ++ ++/** ++ * dpni_reset() - Reset the DPNI, returns the object to initial state. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_reset(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token); ++ ++/** ++ * DPNI IRQ Index and Events ++ */ ++ ++/** ++ * IRQ index ++ */ ++#define DPNI_IRQ_INDEX 0 ++/** ++ * IRQ event - indicates a change in link state ++ */ ++#define DPNI_IRQ_EVENT_LINK_CHANGED 0x00000001 ++ ++/** ++ * struct dpni_irq_cfg - IRQ configuration ++ * @addr: Address that must be written to signal a message-based interrupt ++ * @val: Value to write into irq_addr address ++ * @irq_num: A user defined number associated with this IRQ ++ */ ++struct dpni_irq_cfg { ++ uint64_t addr; ++ uint32_t val; ++ int irq_num; ++}; ++ ++/** ++ * dpni_set_irq() - Set IRQ information for the DPNI to trigger an interrupt. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @irq_index: Identifies the interrupt index to configure ++ * @irq_cfg: IRQ configuration ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_set_irq(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ struct dpni_irq_cfg *irq_cfg); ++ ++/** ++ * dpni_get_irq() - Get IRQ information from the DPNI. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @irq_index: The interrupt index to configure ++ * @type: Interrupt type: 0 represents message interrupt ++ * type (both irq_addr and irq_val are valid) ++ * @irq_cfg: IRQ attributes ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_get_irq(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ int *type, ++ struct dpni_irq_cfg *irq_cfg); ++ ++/** ++ * dpni_set_irq_enable() - Set overall interrupt state. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @irq_index: The interrupt index to configure ++ * @en: Interrupt state: - enable = 1, disable = 0 ++ * ++ * Allows GPP software to control when interrupts are generated. ++ * Each interrupt can have up to 32 causes. The enable/disable control's the ++ * overall interrupt state. if the interrupt is disabled no causes will cause ++ * an interrupt. ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_set_irq_enable(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint8_t en); ++ ++/** ++ * dpni_get_irq_enable() - Get overall interrupt state ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @irq_index: The interrupt index to configure ++ * @en: Returned interrupt state - enable = 1, disable = 0 ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_get_irq_enable(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint8_t *en); ++ ++/** ++ * dpni_set_irq_mask() - Set interrupt mask. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @irq_index: The interrupt index to configure ++ * @mask: event mask to trigger interrupt; ++ * each bit: ++ * 0 = ignore event ++ * 1 = consider event for asserting IRQ ++ * ++ * Every interrupt can have up to 32 causes and the interrupt model supports ++ * masking/unmasking each cause independently ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_set_irq_mask(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint32_t mask); ++ ++/** ++ * dpni_get_irq_mask() - Get interrupt mask. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @irq_index: The interrupt index to configure ++ * @mask: Returned event mask to trigger interrupt ++ * ++ * Every interrupt can have up to 32 causes and the interrupt model supports ++ * masking/unmasking each cause independently ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_get_irq_mask(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint32_t *mask); ++ ++/** ++ * dpni_get_irq_status() - Get the current status of any pending interrupts. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @irq_index: The interrupt index to configure ++ * @status: Returned interrupts status - one bit per cause: ++ * 0 = no interrupt pending ++ * 1 = interrupt pending ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_get_irq_status(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint32_t *status); ++ ++/** ++ * dpni_clear_irq_status() - Clear a pending interrupt's status ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @irq_index: The interrupt index to configure ++ * @status: bits to clear (W1C) - one bit per cause: ++ * 0 = don't change ++ * 1 = clear status bit ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_clear_irq_status(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint32_t status); ++ ++/** ++ * struct dpni_attr - Structure representing DPNI attributes ++ * @id: DPNI object ID ++ * @version: DPNI version ++ * @start_hdr: Indicates the packet starting header for parsing ++ * @options: Mask of available options; reflects the value as was given in ++ * object's creation ++ * @max_senders: Maximum number of different senders; used as the number ++ * of dedicated Tx flows; ++ * @max_tcs: Maximum number of traffic classes (for both Tx and Rx) ++ * @max_unicast_filters: Maximum number of unicast filters ++ * @max_multicast_filters: Maximum number of multicast filters ++ * @max_vlan_filters: Maximum number of VLAN filters ++ * @max_qos_entries: if 'max_tcs > 1', declares the maximum entries in QoS table ++ * @max_qos_key_size: Maximum key size for the QoS look-up ++ * @max_dist_key_size: Maximum key size for the distribution look-up ++ * @max_policers: Maximum number of policers; ++ * @max_congestion_ctrl: Maximum number of congestion control groups (CGs); ++ * @ext_cfg_iova: I/O virtual address of 256 bytes DMA-able memory; ++ * call dpni_extract_extended_cfg() to extract the extended configuration ++ */ ++struct dpni_attr { ++ int id; ++ /** ++ * struct version - DPNI version ++ * @major: DPNI major version ++ * @minor: DPNI minor version ++ */ ++ struct { ++ uint16_t major; ++ uint16_t minor; ++ } version; ++ enum net_prot start_hdr; ++ uint32_t options; ++ uint8_t max_senders; ++ uint8_t max_tcs; ++ uint8_t max_unicast_filters; ++ uint8_t max_multicast_filters; ++ uint8_t max_vlan_filters; ++ uint8_t max_qos_entries; ++ uint8_t max_qos_key_size; ++ uint8_t max_dist_key_size; ++ uint8_t max_policers; ++ uint8_t max_congestion_ctrl; ++ uint64_t ext_cfg_iova; ++}; ++ ++/** ++ * dpni_get_attributes() - Retrieve DPNI attributes. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @attr: Object's attributes ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_get_attributes(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ struct dpni_attr *attr); ++ ++/** ++ * dpni_extract_extended_cfg() - extract the extended parameters ++ * @cfg: extended structure ++ * @ext_cfg_buf: 256 bytes of DMA-able memory ++ * ++ * This function has to be called after dpni_get_attributes() ++ */ ++int dpni_extract_extended_cfg(struct dpni_extended_cfg *cfg, ++ const uint8_t *ext_cfg_buf); ++ ++/** ++ * DPNI errors ++ */ ++ ++/** ++ * Extract out of frame header error ++ */ ++#define DPNI_ERROR_EOFHE 0x00020000 ++/** ++ * Frame length error ++ */ ++#define DPNI_ERROR_FLE 0x00002000 ++/** ++ * Frame physical error ++ */ ++#define DPNI_ERROR_FPE 0x00001000 ++/** ++ * Parsing header error ++ */ ++#define DPNI_ERROR_PHE 0x00000020 ++/** ++ * Parser L3 checksum error ++ */ ++#define DPNI_ERROR_L3CE 0x00000004 ++/** ++ * Parser L3 checksum error ++ */ ++#define DPNI_ERROR_L4CE 0x00000001 ++ ++/** ++ * enum dpni_error_action - Defines DPNI behavior for errors ++ * @DPNI_ERROR_ACTION_DISCARD: Discard the frame ++ * @DPNI_ERROR_ACTION_CONTINUE: Continue with the normal flow ++ * @DPNI_ERROR_ACTION_SEND_TO_ERROR_QUEUE: Send the frame to the error queue ++ */ ++enum dpni_error_action { ++ DPNI_ERROR_ACTION_DISCARD = 0, ++ DPNI_ERROR_ACTION_CONTINUE = 1, ++ DPNI_ERROR_ACTION_SEND_TO_ERROR_QUEUE = 2 ++}; ++ ++/** ++ * struct dpni_error_cfg - Structure representing DPNI errors treatment ++ * @errors: Errors mask; use 'DPNI_ERROR__ ++ * @error_action: The desired action for the errors mask ++ * @set_frame_annotation: Set to '1' to mark the errors in frame annotation ++ * status (FAS); relevant only for the non-discard action ++ */ ++struct dpni_error_cfg { ++ uint32_t errors; ++ enum dpni_error_action error_action; ++ int set_frame_annotation; ++}; ++ ++/** ++ * dpni_set_errors_behavior() - Set errors behavior ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @cfg: Errors configuration ++ * ++ * this function may be called numerous times with different ++ * error masks ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_set_errors_behavior(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ struct dpni_error_cfg *cfg); ++ ++/** ++ * DPNI buffer layout modification options ++ */ ++ ++/** ++ * Select to modify the time-stamp setting ++ */ ++#define DPNI_BUF_LAYOUT_OPT_TIMESTAMP 0x00000001 ++/** ++ * Select to modify the parser-result setting; not applicable for Tx ++ */ ++#define DPNI_BUF_LAYOUT_OPT_PARSER_RESULT 0x00000002 ++/** ++ * Select to modify the frame-status setting ++ */ ++#define DPNI_BUF_LAYOUT_OPT_FRAME_STATUS 0x00000004 ++/** ++ * Select to modify the private-data-size setting ++ */ ++#define DPNI_BUF_LAYOUT_OPT_PRIVATE_DATA_SIZE 0x00000008 ++/** ++ * Select to modify the data-alignment setting ++ */ ++#define DPNI_BUF_LAYOUT_OPT_DATA_ALIGN 0x00000010 ++/** ++ * Select to modify the data-head-room setting ++ */ ++#define DPNI_BUF_LAYOUT_OPT_DATA_HEAD_ROOM 0x00000020 ++/** ++ * Select to modify the data-tail-room setting ++ */ ++#define DPNI_BUF_LAYOUT_OPT_DATA_TAIL_ROOM 0x00000040 ++ ++/** ++ * struct dpni_buffer_layout - Structure representing DPNI buffer layout ++ * @options: Flags representing the suggested modifications to the buffer ++ * layout; Use any combination of 'DPNI_BUF_LAYOUT_OPT_' flags ++ * @pass_timestamp: Pass timestamp value ++ * @pass_parser_result: Pass parser results ++ * @pass_frame_status: Pass frame status ++ * @private_data_size: Size kept for private data (in bytes) ++ * @data_align: Data alignment ++ * @data_head_room: Data head room ++ * @data_tail_room: Data tail room ++ */ ++struct dpni_buffer_layout { ++ uint32_t options; ++ int pass_timestamp; ++ int pass_parser_result; ++ int pass_frame_status; ++ uint16_t private_data_size; ++ uint16_t data_align; ++ uint16_t data_head_room; ++ uint16_t data_tail_room; ++}; ++ ++/** ++ * dpni_get_rx_buffer_layout() - Retrieve Rx buffer layout attributes. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @layout: Returns buffer layout attributes ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_get_rx_buffer_layout(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ struct dpni_buffer_layout *layout); ++ ++/** ++ * dpni_set_rx_buffer_layout() - Set Rx buffer layout configuration. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @layout: Buffer layout configuration ++ * ++ * Return: '0' on Success; Error code otherwise. ++ * ++ * @warning Allowed only when DPNI is disabled ++ */ ++int dpni_set_rx_buffer_layout(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ const struct dpni_buffer_layout *layout); ++ ++/** ++ * dpni_get_tx_buffer_layout() - Retrieve Tx buffer layout attributes. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @layout: Returns buffer layout attributes ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_get_tx_buffer_layout(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ struct dpni_buffer_layout *layout); ++ ++/** ++ * dpni_set_tx_buffer_layout() - Set Tx buffer layout configuration. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @layout: Buffer layout configuration ++ * ++ * Return: '0' on Success; Error code otherwise. ++ * ++ * @warning Allowed only when DPNI is disabled ++ */ ++int dpni_set_tx_buffer_layout(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ const struct dpni_buffer_layout *layout); ++ ++/** ++ * dpni_get_tx_conf_buffer_layout() - Retrieve Tx confirmation buffer layout ++ * attributes. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @layout: Returns buffer layout attributes ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_get_tx_conf_buffer_layout(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ struct dpni_buffer_layout *layout); ++ ++/** ++ * dpni_set_tx_conf_buffer_layout() - Set Tx confirmation buffer layout ++ * configuration. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @layout: Buffer layout configuration ++ * ++ * Return: '0' on Success; Error code otherwise. ++ * ++ * @warning Allowed only when DPNI is disabled ++ */ ++int dpni_set_tx_conf_buffer_layout(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ const struct dpni_buffer_layout *layout); ++ ++/** ++ * dpni_set_l3_chksum_validation() - Enable/disable L3 checksum validation ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @en: Set to '1' to enable; '0' to disable ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_set_l3_chksum_validation(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int en); ++ ++/** ++ * dpni_get_l3_chksum_validation() - Get L3 checksum validation mode ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @en: Returns '1' if enabled; '0' otherwise ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_get_l3_chksum_validation(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int *en); ++ ++/** ++ * dpni_set_l4_chksum_validation() - Enable/disable L4 checksum validation ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @en: Set to '1' to enable; '0' to disable ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_set_l4_chksum_validation(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int en); ++ ++/** ++ * dpni_get_l4_chksum_validation() - Get L4 checksum validation mode ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @en: Returns '1' if enabled; '0' otherwise ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_get_l4_chksum_validation(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int *en); ++ ++/** ++ * dpni_get_qdid() - Get the Queuing Destination ID (QDID) that should be used ++ * for enqueue operations ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @qdid: Returned virtual QDID value that should be used as an argument ++ * in all enqueue operations ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_get_qdid(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint16_t *qdid); ++ ++/** ++ * struct dpni_sp_info - Structure representing DPNI storage-profile information ++ * (relevant only for DPNI owned by AIOP) ++ * @spids: array of storage-profiles ++ */ ++struct dpni_sp_info { ++ uint16_t spids[DPNI_MAX_SP]; ++}; ++ ++/** ++ * dpni_get_spids() - Get the AIOP storage profile IDs associated with the DPNI ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @sp_info: Returned AIOP storage-profile information ++ * ++ * Return: '0' on Success; Error code otherwise. ++ * ++ * @warning Only relevant for DPNI that belongs to AIOP container. ++ */ ++int dpni_get_sp_info(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ struct dpni_sp_info *sp_info); ++ ++/** ++ * dpni_get_tx_data_offset() - Get the Tx data offset (from start of buffer) ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @data_offset: Tx data offset (from start of buffer) ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_get_tx_data_offset(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint16_t *data_offset); ++ ++/** ++ * enum dpni_counter - DPNI counter types ++ * @DPNI_CNT_ING_FRAME: Counts ingress frames ++ * @DPNI_CNT_ING_BYTE: Counts ingress bytes ++ * @DPNI_CNT_ING_FRAME_DROP: Counts ingress frames dropped due to explicit ++ * 'drop' setting ++ * @DPNI_CNT_ING_FRAME_DISCARD: Counts ingress frames discarded due to errors ++ * @DPNI_CNT_ING_MCAST_FRAME: Counts ingress multicast frames ++ * @DPNI_CNT_ING_MCAST_BYTE: Counts ingress multicast bytes ++ * @DPNI_CNT_ING_BCAST_FRAME: Counts ingress broadcast frames ++ * @DPNI_CNT_ING_BCAST_BYTES: Counts ingress broadcast bytes ++ * @DPNI_CNT_EGR_FRAME: Counts egress frames ++ * @DPNI_CNT_EGR_BYTE: Counts egress bytes ++ * @DPNI_CNT_EGR_FRAME_DISCARD: Counts egress frames discarded due to errors ++ */ ++enum dpni_counter { ++ DPNI_CNT_ING_FRAME = 0x0, ++ DPNI_CNT_ING_BYTE = 0x1, ++ DPNI_CNT_ING_FRAME_DROP = 0x2, ++ DPNI_CNT_ING_FRAME_DISCARD = 0x3, ++ DPNI_CNT_ING_MCAST_FRAME = 0x4, ++ DPNI_CNT_ING_MCAST_BYTE = 0x5, ++ DPNI_CNT_ING_BCAST_FRAME = 0x6, ++ DPNI_CNT_ING_BCAST_BYTES = 0x7, ++ DPNI_CNT_EGR_FRAME = 0x8, ++ DPNI_CNT_EGR_BYTE = 0x9, ++ DPNI_CNT_EGR_FRAME_DISCARD = 0xa ++}; ++ ++/** ++ * dpni_get_counter() - Read a specific DPNI counter ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @counter: The requested counter ++ * @value: Returned counter's current value ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_get_counter(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ enum dpni_counter counter, ++ uint64_t *value); ++ ++/** ++ * dpni_set_counter() - Set (or clear) a specific DPNI counter ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @counter: The requested counter ++ * @value: New counter value; typically pass '0' for resetting ++ * the counter. ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_set_counter(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ enum dpni_counter counter, ++ uint64_t value); ++ ++/** ++ * Enable auto-negotiation ++ */ ++#define DPNI_LINK_OPT_AUTONEG 0x0000000000000001ULL ++/** ++ * Enable half-duplex mode ++ */ ++#define DPNI_LINK_OPT_HALF_DUPLEX 0x0000000000000002ULL ++/** ++ * Enable pause frames ++ */ ++#define DPNI_LINK_OPT_PAUSE 0x0000000000000004ULL ++/** ++ * Enable a-symmetric pause frames ++ */ ++#define DPNI_LINK_OPT_ASYM_PAUSE 0x0000000000000008ULL ++ ++/** ++ * struct - Structure representing DPNI link configuration ++ * @rate: Rate ++ * @options: Mask of available options; use 'DPNI_LINK_OPT_' values ++ */ ++struct dpni_link_cfg { ++ uint32_t rate; ++ uint64_t options; ++}; ++ ++/** ++ * dpni_set_link_cfg() - set the link configuration. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @cfg: Link configuration ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_set_link_cfg(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ const struct dpni_link_cfg *cfg); ++ ++/** ++ * struct dpni_link_state - Structure representing DPNI link state ++ * @rate: Rate ++ * @options: Mask of available options; use 'DPNI_LINK_OPT_' values ++ * @up: Link state; '0' for down, '1' for up ++ */ ++struct dpni_link_state { ++ uint32_t rate; ++ uint64_t options; ++ int up; ++}; ++ ++/** ++ * dpni_get_link_state() - Return the link state (either up or down) ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @state: Returned link state; ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_get_link_state(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ struct dpni_link_state *state); ++ ++/** ++ * struct dpni_tx_shaping - Structure representing DPNI tx shaping configuration ++ * @rate_limit: rate in Mbps ++ * @max_burst_size: burst size in bytes (up to 64KB) ++ */ ++struct dpni_tx_shaping_cfg { ++ uint32_t rate_limit; ++ uint16_t max_burst_size; ++}; ++ ++/** ++ * dpni_set_tx_shaping() - Set the transmit shaping ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @tx_shaper: tx shaping configuration ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_set_tx_shaping(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ const struct dpni_tx_shaping_cfg *tx_shaper); ++ ++/** ++ * dpni_set_max_frame_length() - Set the maximum received frame length. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @max_frame_length: Maximum received frame length (in ++ * bytes); frame is discarded if its ++ * length exceeds this value ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_set_max_frame_length(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint16_t max_frame_length); ++ ++/** ++ * dpni_get_max_frame_length() - Get the maximum received frame length. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @max_frame_length: Maximum received frame length (in ++ * bytes); frame is discarded if its ++ * length exceeds this value ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_get_max_frame_length(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint16_t *max_frame_length); ++ ++/** ++ * dpni_set_mtu() - Set the MTU for the interface. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @mtu: MTU length (in bytes) ++ * ++ * MTU determines the maximum fragment size for performing IP ++ * fragmentation on egress packets. ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_set_mtu(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint16_t mtu); ++ ++/** ++ * dpni_get_mtu() - Get the MTU. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @mtu: Returned MTU length (in bytes) ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_get_mtu(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint16_t *mtu); ++ ++/** ++ * dpni_set_multicast_promisc() - Enable/disable multicast promiscuous mode ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @en: Set to '1' to enable; '0' to disable ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_set_multicast_promisc(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int en); ++ ++/** ++ * dpni_get_multicast_promisc() - Get multicast promiscuous mode ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @en: Returns '1' if enabled; '0' otherwise ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_get_multicast_promisc(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int *en); ++ ++/** ++ * dpni_set_unicast_promisc() - Enable/disable unicast promiscuous mode ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @en: Set to '1' to enable; '0' to disable ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_set_unicast_promisc(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int en); ++ ++/** ++ * dpni_get_unicast_promisc() - Get unicast promiscuous mode ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @en: Returns '1' if enabled; '0' otherwise ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_get_unicast_promisc(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int *en); ++ ++/** ++ * dpni_set_primary_mac_addr() - Set the primary MAC address ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @mac_addr: MAC address to set as primary address ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_set_primary_mac_addr(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ const uint8_t mac_addr[6]); ++ ++/** ++ * dpni_get_primary_mac_addr() - Get the primary MAC address ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @mac_addr: Returned MAC address ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_get_primary_mac_addr(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t mac_addr[6]); ++ ++/** ++ * dpni_add_mac_addr() - Add MAC address filter ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @mac_addr: MAC address to add ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_add_mac_addr(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ const uint8_t mac_addr[6]); ++ ++/** ++ * dpni_remove_mac_addr() - Remove MAC address filter ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @mac_addr: MAC address to remove ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_remove_mac_addr(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ const uint8_t mac_addr[6]); ++ ++/** ++ * dpni_clear_mac_filters() - Clear all unicast and/or multicast MAC filters ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @unicast: Set to '1' to clear unicast addresses ++ * @multicast: Set to '1' to clear multicast addresses ++ * ++ * The primary MAC address is not cleared by this operation. ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_clear_mac_filters(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int unicast, ++ int multicast); ++ ++/** ++ * dpni_set_vlan_filters() - Enable/disable VLAN filtering mode ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @en: Set to '1' to enable; '0' to disable ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_set_vlan_filters(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int en); ++ ++/** ++ * dpni_add_vlan_id() - Add VLAN ID filter ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @vlan_id: VLAN ID to add ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_add_vlan_id(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint16_t vlan_id); ++ ++/** ++ * dpni_remove_vlan_id() - Remove VLAN ID filter ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @vlan_id: VLAN ID to remove ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_remove_vlan_id(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint16_t vlan_id); ++ ++/** ++ * dpni_clear_vlan_filters() - Clear all VLAN filters ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_clear_vlan_filters(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token); ++ ++/** ++ * enum dpni_tx_schedule_mode - DPNI Tx scheduling mode ++ * @DPNI_TX_SCHED_STRICT_PRIORITY: strict priority ++ * @DPNI_TX_SCHED_WEIGHTED: weighted based scheduling ++ */ ++enum dpni_tx_schedule_mode { ++ DPNI_TX_SCHED_STRICT_PRIORITY, ++ DPNI_TX_SCHED_WEIGHTED, ++}; ++ ++/** ++ * struct dpni_tx_schedule_cfg - Structure representing Tx ++ * scheduling configuration ++ * @mode: scheduling mode ++ * @delta_bandwidth: Bandwidth represented in weights from 100 to 10000; ++ * not applicable for 'strict-priority' mode; ++ */ ++struct dpni_tx_schedule_cfg { ++ enum dpni_tx_schedule_mode mode; ++ uint16_t delta_bandwidth; ++}; ++ ++/** ++ * struct dpni_tx_selection_cfg - Structure representing transmission ++ * selection configuration ++ * @tc_sched: an array of traffic-classes ++ */ ++struct dpni_tx_selection_cfg { ++ struct dpni_tx_schedule_cfg tc_sched[DPNI_MAX_TC]; ++}; ++ ++/** ++ * dpni_set_tx_selection() - Set transmission selection configuration ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @cfg: transmission selection configuration ++ * ++ * warning: Allowed only when DPNI is disabled ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_set_tx_selection(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ const struct dpni_tx_selection_cfg *cfg); ++ ++/** ++ * enum dpni_dist_mode - DPNI distribution mode ++ * @DPNI_DIST_MODE_NONE: No distribution ++ * @DPNI_DIST_MODE_HASH: Use hash distribution; only relevant if ++ * the 'DPNI_OPT_DIST_HASH' option was set at DPNI creation ++ * @DPNI_DIST_MODE_FS: Use explicit flow steering; only relevant if ++ * the 'DPNI_OPT_DIST_FS' option was set at DPNI creation ++ */ ++enum dpni_dist_mode { ++ DPNI_DIST_MODE_NONE = 0, ++ DPNI_DIST_MODE_HASH = 1, ++ DPNI_DIST_MODE_FS = 2 ++}; ++ ++/** ++ * enum dpni_fs_miss_action - DPNI Flow Steering miss action ++ * @DPNI_FS_MISS_DROP: In case of no-match, drop the frame ++ * @DPNI_FS_MISS_EXPLICIT_FLOWID: In case of no-match, use explicit flow-id ++ * @DPNI_FS_MISS_HASH: In case of no-match, distribute using hash ++ */ ++enum dpni_fs_miss_action { ++ DPNI_FS_MISS_DROP = 0, ++ DPNI_FS_MISS_EXPLICIT_FLOWID = 1, ++ DPNI_FS_MISS_HASH = 2 ++}; ++ ++/** ++ * struct dpni_fs_tbl_cfg - Flow Steering table configuration ++ * @miss_action: Miss action selection ++ * @default_flow_id: Used when 'miss_action = DPNI_FS_MISS_EXPLICIT_FLOWID' ++ */ ++struct dpni_fs_tbl_cfg { ++ enum dpni_fs_miss_action miss_action; ++ uint16_t default_flow_id; ++}; ++ ++/** ++ * dpni_prepare_key_cfg() - function prepare extract parameters ++ * @cfg: defining a full Key Generation profile (rule) ++ * @key_cfg_buf: Zeroed 256 bytes of memory before mapping it to DMA ++ * ++ * This function has to be called before the following functions: ++ * - dpni_set_rx_tc_dist() ++ * - dpni_set_qos_table() ++ */ ++int dpni_prepare_key_cfg(const struct dpkg_profile_cfg *cfg, ++ uint8_t *key_cfg_buf); ++ ++/** ++ * struct dpni_rx_tc_dist_cfg - Rx traffic class distribution configuration ++ * @dist_size: Set the distribution size; ++ * supported values: 1,2,3,4,6,7,8,12,14,16,24,28,32,48,56,64,96, ++ * 112,128,192,224,256,384,448,512,768,896,1024 ++ * @dist_mode: Distribution mode ++ * @key_cfg_iova: I/O virtual address of 256 bytes DMA-able memory filled with ++ * the extractions to be used for the distribution key by calling ++ * dpni_prepare_key_cfg() relevant only when ++ * 'dist_mode != DPNI_DIST_MODE_NONE', otherwise it can be '0' ++ * @fs_cfg: Flow Steering table configuration; only relevant if ++ * 'dist_mode = DPNI_DIST_MODE_FS' ++ */ ++struct dpni_rx_tc_dist_cfg { ++ uint16_t dist_size; ++ enum dpni_dist_mode dist_mode; ++ uint64_t key_cfg_iova; ++ struct dpni_fs_tbl_cfg fs_cfg; ++}; ++ ++/** ++ * dpni_set_rx_tc_dist() - Set Rx traffic class distribution configuration ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @tc_id: Traffic class selection (0-7) ++ * @cfg: Traffic class distribution configuration ++ * ++ * warning: if 'dist_mode != DPNI_DIST_MODE_NONE', call dpni_prepare_key_cfg() ++ * first to prepare the key_cfg_iova parameter ++ * ++ * Return: '0' on Success; error code otherwise. ++ */ ++int dpni_set_rx_tc_dist(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t tc_id, ++ const struct dpni_rx_tc_dist_cfg *cfg); ++ ++/** ++ * Set to select color aware mode (otherwise - color blind) ++ */ ++#define DPNI_POLICER_OPT_COLOR_AWARE 0x00000001 ++/** ++ * Set to discard frame with RED color ++ */ ++#define DPNI_POLICER_OPT_DISCARD_RED 0x00000002 ++ ++/** ++ * enum dpni_policer_mode - selecting the policer mode ++ * @DPNI_POLICER_MODE_NONE: Policer is disabled ++ * @DPNI_POLICER_MODE_PASS_THROUGH: Policer pass through ++ * @DPNI_POLICER_MODE_RFC_2698: Policer algorithm RFC 2698 ++ * @DPNI_POLICER_MODE_RFC_4115: Policer algorithm RFC 4115 ++ */ ++enum dpni_policer_mode { ++ DPNI_POLICER_MODE_NONE = 0, ++ DPNI_POLICER_MODE_PASS_THROUGH, ++ DPNI_POLICER_MODE_RFC_2698, ++ DPNI_POLICER_MODE_RFC_4115 ++}; ++ ++/** ++ * enum dpni_policer_unit - DPNI policer units ++ * @DPNI_POLICER_UNIT_BYTES: bytes units ++ * @DPNI_POLICER_UNIT_FRAMES: frames units ++ */ ++enum dpni_policer_unit { ++ DPNI_POLICER_UNIT_BYTES = 0, ++ DPNI_POLICER_UNIT_FRAMES ++}; ++ ++/** ++ * enum dpni_policer_color - selecting the policer color ++ * @DPNI_POLICER_COLOR_GREEN: Green color ++ * @DPNI_POLICER_COLOR_YELLOW: Yellow color ++ * @DPNI_POLICER_COLOR_RED: Red color ++ */ ++enum dpni_policer_color { ++ DPNI_POLICER_COLOR_GREEN = 0, ++ DPNI_POLICER_COLOR_YELLOW, ++ DPNI_POLICER_COLOR_RED ++}; ++ ++/** ++ * struct dpni_rx_tc_policing_cfg - Policer configuration ++ * @options: Mask of available options; use 'DPNI_POLICER_OPT_' values ++ * @mode: policer mode ++ * @default_color: For pass-through mode the policer re-colors with this ++ * color any incoming packets. For Color aware non-pass-through mode: ++ * policer re-colors with this color all packets with FD[DROPP]>2. ++ * @units: Bytes or Packets ++ * @cir: Committed information rate (CIR) in Kbps or packets/second ++ * @cbs: Committed burst size (CBS) in bytes or packets ++ * @eir: Peak information rate (PIR, rfc2698) in Kbps or packets/second ++ * Excess information rate (EIR, rfc4115) in Kbps or packets/second ++ * @ebs: Peak burst size (PBS, rfc2698) in bytes or packets ++ * Excess burst size (EBS, rfc4115) in bytes or packets ++ */ ++struct dpni_rx_tc_policing_cfg { ++ uint32_t options; ++ enum dpni_policer_mode mode; ++ enum dpni_policer_unit units; ++ enum dpni_policer_color default_color; ++ uint32_t cir; ++ uint32_t cbs; ++ uint32_t eir; ++ uint32_t ebs; ++}; ++ ++/** ++ * dpni_set_rx_tc_policing() - Set Rx traffic class policing configuration ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @tc_id: Traffic class selection (0-7) ++ * @cfg: Traffic class policing configuration ++ * ++ * Return: '0' on Success; error code otherwise. ++ */ ++int dpni_set_rx_tc_policing(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t tc_id, ++ const struct dpni_rx_tc_policing_cfg *cfg); ++ ++/** ++ * dpni_get_rx_tc_policing() - Get Rx traffic class policing configuration ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @tc_id: Traffic class selection (0-7) ++ * @cfg: Traffic class policing configuration ++ * ++ * Return: '0' on Success; error code otherwise. ++ */ ++int dpni_get_rx_tc_policing(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t tc_id, ++ struct dpni_rx_tc_policing_cfg *cfg); ++ ++/** ++ * enum dpni_congestion_unit - DPNI congestion units ++ * @DPNI_CONGESTION_UNIT_BYTES: bytes units ++ * @DPNI_CONGESTION_UNIT_FRAMES: frames units ++ */ ++enum dpni_congestion_unit { ++ DPNI_CONGESTION_UNIT_BYTES = 0, ++ DPNI_CONGESTION_UNIT_FRAMES ++}; ++ ++/** ++ * enum dpni_early_drop_mode - DPNI early drop mode ++ * @DPNI_EARLY_DROP_MODE_NONE: early drop is disabled ++ * @DPNI_EARLY_DROP_MODE_TAIL: early drop in taildrop mode ++ * @DPNI_EARLY_DROP_MODE_WRED: early drop in WRED mode ++ */ ++enum dpni_early_drop_mode { ++ DPNI_EARLY_DROP_MODE_NONE = 0, ++ DPNI_EARLY_DROP_MODE_TAIL, ++ DPNI_EARLY_DROP_MODE_WRED ++}; ++ ++/** ++ * struct dpni_wred_cfg - WRED configuration ++ * @max_threshold: maximum threshold that packets may be discarded. Above this ++ * threshold all packets are discarded; must be less than 2^39; ++ * approximated to be expressed as (x+256)*2^(y-1) due to HW ++ * implementation. ++ * @min_threshold: minimum threshold that packets may be discarded at ++ * @drop_probability: probability that a packet will be discarded (1-100, ++ * associated with the max_threshold). ++ */ ++struct dpni_wred_cfg { ++ uint64_t max_threshold; ++ uint64_t min_threshold; ++ uint8_t drop_probability; ++}; ++ ++/** ++ * struct dpni_early_drop_cfg - early-drop configuration ++ * @mode: drop mode ++ * @units: units type ++ * @green: WRED - 'green' configuration ++ * @yellow: WRED - 'yellow' configuration ++ * @red: WRED - 'red' configuration ++ * @tail_drop_threshold: tail drop threshold ++ */ ++struct dpni_early_drop_cfg { ++ enum dpni_early_drop_mode mode; ++ enum dpni_congestion_unit units; ++ ++ struct dpni_wred_cfg green; ++ struct dpni_wred_cfg yellow; ++ struct dpni_wred_cfg red; ++ ++ uint32_t tail_drop_threshold; ++}; ++ ++/** ++ * dpni_prepare_early_drop() - prepare an early drop. ++ * @cfg: Early-drop configuration ++ * @early_drop_buf: Zeroed 256 bytes of memory before mapping it to DMA ++ * ++ * This function has to be called before dpni_set_rx_tc_early_drop or ++ * dpni_set_tx_tc_early_drop ++ * ++ */ ++void dpni_prepare_early_drop(const struct dpni_early_drop_cfg *cfg, ++ uint8_t *early_drop_buf); ++ ++/** ++ * dpni_extract_early_drop() - extract the early drop configuration. ++ * @cfg: Early-drop configuration ++ * @early_drop_buf: Zeroed 256 bytes of memory before mapping it to DMA ++ * ++ * This function has to be called after dpni_get_rx_tc_early_drop or ++ * dpni_get_tx_tc_early_drop ++ * ++ */ ++void dpni_extract_early_drop(struct dpni_early_drop_cfg *cfg, ++ const uint8_t *early_drop_buf); ++ ++/** ++ * dpni_set_rx_tc_early_drop() - Set Rx traffic class early-drop configuration ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @tc_id: Traffic class selection (0-7) ++ * @early_drop_iova: I/O virtual address of 256 bytes DMA-able memory filled ++ * with the early-drop configuration by calling dpni_prepare_early_drop() ++ * ++ * warning: Before calling this function, call dpni_prepare_early_drop() to ++ * prepare the early_drop_iova parameter ++ * ++ * Return: '0' on Success; error code otherwise. ++ */ ++int dpni_set_rx_tc_early_drop(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t tc_id, ++ uint64_t early_drop_iova); ++ ++/** ++ * dpni_get_rx_tc_early_drop() - Get Rx traffic class early-drop configuration ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @tc_id: Traffic class selection (0-7) ++ * @early_drop_iova: I/O virtual address of 256 bytes DMA-able memory ++ * ++ * warning: After calling this function, call dpni_extract_early_drop() to ++ * get the early drop configuration ++ * ++ * Return: '0' on Success; error code otherwise. ++ */ ++int dpni_get_rx_tc_early_drop(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t tc_id, ++ uint64_t early_drop_iova); ++ ++/** ++ * dpni_set_tx_tc_early_drop() - Set Tx traffic class early-drop configuration ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @tc_id: Traffic class selection (0-7) ++ * @early_drop_iova: I/O virtual address of 256 bytes DMA-able memory filled ++ * with the early-drop configuration by calling dpni_prepare_early_drop() ++ * ++ * warning: Before calling this function, call dpni_prepare_early_drop() to ++ * prepare the early_drop_iova parameter ++ * ++ * Return: '0' on Success; error code otherwise. ++ */ ++int dpni_set_tx_tc_early_drop(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t tc_id, ++ uint64_t early_drop_iova); ++ ++/** ++ * dpni_get_tx_tc_early_drop() - Get Tx traffic class early-drop configuration ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @tc_id: Traffic class selection (0-7) ++ * @early_drop_iova: I/O virtual address of 256 bytes DMA-able memory ++ * ++ * warning: After calling this function, call dpni_extract_early_drop() to ++ * get the early drop configuration ++ * ++ * Return: '0' on Success; error code otherwise. ++ */ ++int dpni_get_tx_tc_early_drop(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t tc_id, ++ uint64_t early_drop_iova); ++ ++/** ++ * enum dpni_dest - DPNI destination types ++ * @DPNI_DEST_NONE: Unassigned destination; The queue is set in parked mode and ++ * does not generate FQDAN notifications; user is expected to ++ * dequeue from the queue based on polling or other user-defined ++ * method ++ * @DPNI_DEST_DPIO: The queue is set in schedule mode and generates FQDAN ++ * notifications to the specified DPIO; user is expected to dequeue ++ * from the queue only after notification is received ++ * @DPNI_DEST_DPCON: The queue is set in schedule mode and does not generate ++ * FQDAN notifications, but is connected to the specified DPCON ++ * object; user is expected to dequeue from the DPCON channel ++ */ ++enum dpni_dest { ++ DPNI_DEST_NONE = 0, ++ DPNI_DEST_DPIO = 1, ++ DPNI_DEST_DPCON = 2 ++}; ++ ++/** ++ * struct dpni_dest_cfg - Structure representing DPNI destination parameters ++ * @dest_type: Destination type ++ * @dest_id: Either DPIO ID or DPCON ID, depending on the destination type ++ * @priority: Priority selection within the DPIO or DPCON channel; valid values ++ * are 0-1 or 0-7, depending on the number of priorities in that ++ * channel; not relevant for 'DPNI_DEST_NONE' option ++ */ ++struct dpni_dest_cfg { ++ enum dpni_dest dest_type; ++ int dest_id; ++ uint8_t priority; ++}; ++ ++/* DPNI congestion options */ ++ ++/** ++ * CSCN message is written to message_iova once entering a ++ * congestion state (see 'threshold_entry') ++ */ ++#define DPNI_CONG_OPT_WRITE_MEM_ON_ENTER 0x00000001 ++/** ++ * CSCN message is written to message_iova once exiting a ++ * congestion state (see 'threshold_exit') ++ */ ++#define DPNI_CONG_OPT_WRITE_MEM_ON_EXIT 0x00000002 ++/** ++ * CSCN write will attempt to allocate into a cache (coherent write); ++ * valid only if 'DPNI_CONG_OPT_WRITE_MEM_' is selected ++ */ ++#define DPNI_CONG_OPT_COHERENT_WRITE 0x00000004 ++/** ++ * if 'dest_cfg.dest_type != DPNI_DEST_NONE' CSCN message is sent to ++ * DPIO/DPCON's WQ channel once entering a congestion state ++ * (see 'threshold_entry') ++ */ ++#define DPNI_CONG_OPT_NOTIFY_DEST_ON_ENTER 0x00000008 ++/** ++ * if 'dest_cfg.dest_type != DPNI_DEST_NONE' CSCN message is sent to ++ * DPIO/DPCON's WQ channel once exiting a congestion state ++ * (see 'threshold_exit') ++ */ ++#define DPNI_CONG_OPT_NOTIFY_DEST_ON_EXIT 0x00000010 ++/** ++ * if 'dest_cfg.dest_type != DPNI_DEST_NONE' when the CSCN is written to the ++ * sw-portal's DQRR, the DQRI interrupt is asserted immediately (if enabled) ++ */ ++#define DPNI_CONG_OPT_INTR_COALESCING_DISABLED 0x00000020 ++ ++/** ++ * struct dpni_congestion_notification_cfg - congestion notification ++ * configuration ++ * @units: units type ++ * @threshold_entry: above this threshold we enter a congestion state. ++ * set it to '0' to disable it ++ * @threshold_exit: below this threshold we exit the congestion state. ++ * @message_ctx: The context that will be part of the CSCN message ++ * @message_iova: I/O virtual address (must be in DMA-able memory), ++ * must be 16B aligned; valid only if 'DPNI_CONG_OPT_WRITE_MEM_' is ++ * contained in 'options' ++ * @dest_cfg: CSCN can be send to either DPIO or DPCON WQ channel ++ * @options: Mask of available options; use 'DPNI_CONG_OPT_' values ++ */ ++ ++struct dpni_congestion_notification_cfg { ++ enum dpni_congestion_unit units; ++ uint32_t threshold_entry; ++ uint32_t threshold_exit; ++ uint64_t message_ctx; ++ uint64_t message_iova; ++ struct dpni_dest_cfg dest_cfg; ++ uint16_t options; ++}; ++ ++/** ++ * dpni_set_rx_tc_congestion_notification() - Set Rx traffic class congestion ++ * notification configuration ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @tc_id: Traffic class selection (0-7) ++ * @cfg: congestion notification configuration ++ * ++ * Return: '0' on Success; error code otherwise. ++ */ ++int dpni_set_rx_tc_congestion_notification(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t tc_id, ++ const struct dpni_congestion_notification_cfg *cfg); ++ ++/** ++ * dpni_get_rx_tc_congestion_notification() - Get Rx traffic class congestion ++ * notification configuration ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @tc_id: Traffic class selection (0-7) ++ * @cfg: congestion notification configuration ++ * ++ * Return: '0' on Success; error code otherwise. ++ */ ++int dpni_get_rx_tc_congestion_notification(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t tc_id, ++ struct dpni_congestion_notification_cfg *cfg); ++ ++/** ++ * dpni_set_tx_tc_congestion_notification() - Set Tx traffic class congestion ++ * notification configuration ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @tc_id: Traffic class selection (0-7) ++ * @cfg: congestion notification configuration ++ * ++ * Return: '0' on Success; error code otherwise. ++ */ ++int dpni_set_tx_tc_congestion_notification(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t tc_id, ++ const struct dpni_congestion_notification_cfg *cfg); ++ ++/** ++ * dpni_get_tx_tc_congestion_notification() - Get Tx traffic class congestion ++ * notification configuration ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @tc_id: Traffic class selection (0-7) ++ * @cfg: congestion notification configuration ++ * ++ * Return: '0' on Success; error code otherwise. ++ */ ++int dpni_get_tx_tc_congestion_notification(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t tc_id, ++ struct dpni_congestion_notification_cfg *cfg); ++ ++/** ++ * enum dpni_flc_type - DPNI FLC types ++ * @DPNI_FLC_USER_DEFINED: select the FLC to be used for user defined value ++ * @DPNI_FLC_STASH: select the FLC to be used for stash control ++ */ ++enum dpni_flc_type { ++ DPNI_FLC_USER_DEFINED = 0, ++ DPNI_FLC_STASH = 1, ++}; ++ ++/** ++ * enum dpni_stash_size - DPNI FLC stashing size ++ * @DPNI_STASH_SIZE_0B: no stash ++ * @DPNI_STASH_SIZE_64B: stashes 64 bytes ++ * @DPNI_STASH_SIZE_128B: stashes 128 bytes ++ * @DPNI_STASH_SIZE_192B: stashes 192 bytes ++ */ ++enum dpni_stash_size { ++ DPNI_STASH_SIZE_0B = 0, ++ DPNI_STASH_SIZE_64B = 1, ++ DPNI_STASH_SIZE_128B = 2, ++ DPNI_STASH_SIZE_192B = 3, ++}; ++ ++/* DPNI FLC stash options */ ++ ++/** ++ * stashes the whole annotation area (up to 192 bytes) ++ */ ++#define DPNI_FLC_STASH_FRAME_ANNOTATION 0x00000001 ++ ++/** ++ * struct dpni_flc_cfg - Structure representing DPNI FLC configuration ++ * @flc_type: FLC type ++ * @options: Mask of available options; ++ * use 'DPNI_FLC_STASH_' values ++ * @frame_data_size: Size of frame data to be stashed ++ * @flow_context_size: Size of flow context to be stashed ++ * @flow_context: 1. In case flc_type is 'DPNI_FLC_USER_DEFINED': ++ * this value will be provided in the frame descriptor ++ * (FD[FLC]) ++ * 2. In case flc_type is 'DPNI_FLC_STASH': ++ * this value will be I/O virtual address of the ++ * flow-context; ++ * Must be cacheline-aligned and DMA-able memory ++ */ ++struct dpni_flc_cfg { ++ enum dpni_flc_type flc_type; ++ uint32_t options; ++ enum dpni_stash_size frame_data_size; ++ enum dpni_stash_size flow_context_size; ++ uint64_t flow_context; ++}; ++ ++/** ++ * DPNI queue modification options ++ */ ++ ++/** ++ * Select to modify the user's context associated with the queue ++ */ ++#define DPNI_QUEUE_OPT_USER_CTX 0x00000001 ++/** ++ * Select to modify the queue's destination ++ */ ++#define DPNI_QUEUE_OPT_DEST 0x00000002 ++/** Select to modify the flow-context parameters; ++ * not applicable for Tx-conf/Err queues as the FD comes from the user ++ */ ++#define DPNI_QUEUE_OPT_FLC 0x00000004 ++/** ++ * Select to modify the queue's order preservation ++ */ ++#define DPNI_QUEUE_OPT_ORDER_PRESERVATION 0x00000008 ++/* Select to modify the queue's tail-drop threshold */ ++#define DPNI_QUEUE_OPT_TAILDROP_THRESHOLD 0x00000010 ++ ++/** ++ * struct dpni_queue_cfg - Structure representing queue configuration ++ * @options: Flags representing the suggested modifications to the queue; ++ * Use any combination of 'DPNI_QUEUE_OPT_' flags ++ * @user_ctx: User context value provided in the frame descriptor of each ++ * dequeued frame; valid only if 'DPNI_QUEUE_OPT_USER_CTX' ++ * is contained in 'options' ++ * @dest_cfg: Queue destination parameters; ++ * valid only if 'DPNI_QUEUE_OPT_DEST' is contained in 'options' ++ * @flc_cfg: Flow context configuration; in case the TC's distribution ++ * is either NONE or HASH the FLC's settings of flow#0 are used. ++ * in the case of FS (flow-steering) the flow's FLC settings ++ * are used. ++ * valid only if 'DPNI_QUEUE_OPT_FLC' is contained in 'options' ++ * @order_preservation_en: enable/disable order preservation; ++ * valid only if 'DPNI_QUEUE_OPT_ORDER_PRESERVATION' is contained ++ * in 'options' ++ * @tail_drop_threshold: set the queue's tail drop threshold in bytes; ++ * '0' value disable the threshold; maximum value is 0xE000000; ++ * valid only if 'DPNI_QUEUE_OPT_TAILDROP_THRESHOLD' is contained ++ * in 'options' ++ */ ++struct dpni_queue_cfg { ++ uint32_t options; ++ uint64_t user_ctx; ++ struct dpni_dest_cfg dest_cfg; ++ struct dpni_flc_cfg flc_cfg; ++ int order_preservation_en; ++ uint32_t tail_drop_threshold; ++}; ++ ++/** ++ * struct dpni_queue_attr - Structure representing queue attributes ++ * @user_ctx: User context value provided in the frame descriptor of each ++ * dequeued frame ++ * @dest_cfg: Queue destination configuration ++ * @flc_cfg: Flow context configuration ++ * @order_preservation_en: enable/disable order preservation ++ * @tail_drop_threshold: queue's tail drop threshold in bytes; ++ * @fqid: Virtual fqid value to be used for dequeue operations ++ */ ++struct dpni_queue_attr { ++ uint64_t user_ctx; ++ struct dpni_dest_cfg dest_cfg; ++ struct dpni_flc_cfg flc_cfg; ++ int order_preservation_en; ++ uint32_t tail_drop_threshold; ++ ++ uint32_t fqid; ++}; ++ ++/** ++ * DPNI Tx flow modification options ++ */ ++ ++/** ++ * Select to modify the settings for dedicate Tx confirmation/error ++ */ ++#define DPNI_TX_FLOW_OPT_TX_CONF_ERROR 0x00000001 ++/** ++ * Select to modify the L3 checksum generation setting ++ */ ++#define DPNI_TX_FLOW_OPT_L3_CHKSUM_GEN 0x00000010 ++/** ++ * Select to modify the L4 checksum generation setting ++ */ ++#define DPNI_TX_FLOW_OPT_L4_CHKSUM_GEN 0x00000020 ++ ++/** ++ * struct dpni_tx_flow_cfg - Structure representing Tx flow configuration ++ * @options: Flags representing the suggested modifications to the Tx flow; ++ * Use any combination 'DPNI_TX_FLOW_OPT_' flags ++ * @use_common_tx_conf_queue: Set to '1' to use the common (default) Tx ++ * confirmation and error queue; Set to '0' to use the private ++ * Tx confirmation and error queue; valid only if ++ * 'DPNI_OPT_PRIVATE_TX_CONF_ERROR_DISABLED' wasn't set at DPNI creation ++ * and 'DPNI_TX_FLOW_OPT_TX_CONF_ERROR' is contained in 'options' ++ * @l3_chksum_gen: Set to '1' to enable L3 checksum generation; '0' to disable; ++ * valid only if 'DPNI_TX_FLOW_OPT_L3_CHKSUM_GEN' is contained in 'options' ++ * @l4_chksum_gen: Set to '1' to enable L4 checksum generation; '0' to disable; ++ * valid only if 'DPNI_TX_FLOW_OPT_L4_CHKSUM_GEN' is contained in 'options' ++ */ ++struct dpni_tx_flow_cfg { ++ uint32_t options; ++ int use_common_tx_conf_queue; ++ int l3_chksum_gen; ++ int l4_chksum_gen; ++}; ++ ++/** ++ * dpni_set_tx_flow() - Set Tx flow configuration ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @flow_id: Provides (or returns) the sender's flow ID; ++ * for each new sender set (*flow_id) to 'DPNI_NEW_FLOW_ID' to generate ++ * a new flow_id; this ID should be used as the QDBIN argument ++ * in enqueue operations ++ * @cfg: Tx flow configuration ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_set_tx_flow(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint16_t *flow_id, ++ const struct dpni_tx_flow_cfg *cfg); ++ ++/** ++ * struct dpni_tx_flow_attr - Structure representing Tx flow attributes ++ * @use_common_tx_conf_queue: '1' if using common (default) Tx confirmation and ++ * error queue; '0' if using private Tx confirmation and error queue ++ * @l3_chksum_gen: '1' if L3 checksum generation is enabled; '0' if disabled ++ * @l4_chksum_gen: '1' if L4 checksum generation is enabled; '0' if disabled ++ */ ++struct dpni_tx_flow_attr { ++ int use_common_tx_conf_queue; ++ int l3_chksum_gen; ++ int l4_chksum_gen; ++}; ++ ++/** ++ * dpni_get_tx_flow() - Get Tx flow attributes ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @flow_id: The sender's flow ID, as returned by the ++ * dpni_set_tx_flow() function ++ * @attr: Returned Tx flow attributes ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_get_tx_flow(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint16_t flow_id, ++ struct dpni_tx_flow_attr *attr); ++ ++/** ++ * struct dpni_tx_conf_cfg - Structure representing Tx conf configuration ++ * @errors_only: Set to '1' to report back only error frames; ++ * Set to '0' to confirm transmission/error for all transmitted frames; ++ * @queue_cfg: Queue configuration ++ */ ++struct dpni_tx_conf_cfg { ++ int errors_only; ++ struct dpni_queue_cfg queue_cfg; ++}; ++ ++/** ++ * dpni_set_tx_conf() - Set Tx confirmation and error queue configuration ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @flow_id: The sender's flow ID, as returned by the ++ * dpni_set_tx_flow() function; ++ * use 'DPNI_COMMON_TX_CONF' for common tx-conf ++ * @cfg: Queue configuration ++ * ++ * If either 'DPNI_OPT_TX_CONF_DISABLED' or ++ * 'DPNI_OPT_PRIVATE_TX_CONF_ERROR_DISABLED' were selected at DPNI creation, ++ * this function can ONLY be used with 'flow_id == DPNI_COMMON_TX_CONF'; ++ * i.e. only serve the common tx-conf-err queue; ++ * if 'DPNI_OPT_TX_CONF_DISABLED' was selected, only error frames are reported ++ * back - successfully transmitted frames are not confirmed. Otherwise, all ++ * transmitted frames are sent for confirmation. ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_set_tx_conf(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint16_t flow_id, ++ const struct dpni_tx_conf_cfg *cfg); ++ ++/** ++ * struct dpni_tx_conf_attr - Structure representing Tx conf attributes ++ * @errors_only: '1' if only error frames are reported back; '0' if all ++ * transmitted frames are confirmed ++ * @queue_attr: Queue attributes ++ */ ++struct dpni_tx_conf_attr { ++ int errors_only; ++ struct dpni_queue_attr queue_attr; ++}; ++ ++/** ++ * dpni_get_tx_conf() - Get Tx confirmation and error queue attributes ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @flow_id: The sender's flow ID, as returned by the ++ * dpni_set_tx_flow() function; ++ * use 'DPNI_COMMON_TX_CONF' for common tx-conf ++ * @attr: Returned tx-conf attributes ++ * ++ * If either 'DPNI_OPT_TX_CONF_DISABLED' or ++ * 'DPNI_OPT_PRIVATE_TX_CONF_ERROR_DISABLED' were selected at DPNI creation, ++ * this function can ONLY be used with 'flow_id == DPNI_COMMON_TX_CONF'; ++ * i.e. only serve the common tx-conf-err queue; ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_get_tx_conf(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint16_t flow_id, ++ struct dpni_tx_conf_attr *attr); ++ ++/** ++ * dpni_set_tx_conf_congestion_notification() - Set Tx conf congestion ++ * notification configuration ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @flow_id: The sender's flow ID, as returned by the ++ * dpni_set_tx_flow() function; ++ * use 'DPNI_COMMON_TX_CONF' for common tx-conf ++ * @cfg: congestion notification configuration ++ * ++ * If either 'DPNI_OPT_TX_CONF_DISABLED' or ++ * 'DPNI_OPT_PRIVATE_TX_CONF_ERROR_DISABLED' were selected at DPNI creation, ++ * this function can ONLY be used with 'flow_id == DPNI_COMMON_TX_CONF'; ++ * i.e. only serve the common tx-conf-err queue; ++ * ++ * Return: '0' on Success; error code otherwise. ++ */ ++int dpni_set_tx_conf_congestion_notification(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint16_t flow_id, ++ const struct dpni_congestion_notification_cfg *cfg); ++ ++/** ++ * dpni_get_tx_conf_congestion_notification() - Get Tx conf congestion ++ * notification configuration ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @flow_id: The sender's flow ID, as returned by the ++ * dpni_set_tx_flow() function; ++ * use 'DPNI_COMMON_TX_CONF' for common tx-conf ++ * @cfg: congestion notification ++ * ++ * If either 'DPNI_OPT_TX_CONF_DISABLED' or ++ * 'DPNI_OPT_PRIVATE_TX_CONF_ERROR_DISABLED' were selected at DPNI creation, ++ * this function can ONLY be used with 'flow_id == DPNI_COMMON_TX_CONF'; ++ * i.e. only serve the common tx-conf-err queue; ++ * ++ * Return: '0' on Success; error code otherwise. ++ */ ++int dpni_get_tx_conf_congestion_notification(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint16_t flow_id, ++ struct dpni_congestion_notification_cfg *cfg); ++ ++/** ++ * dpni_set_tx_conf_revoke() - Tx confirmation revocation ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @revoke: revoke or not ++ * ++ * This function is useful only when 'DPNI_OPT_TX_CONF_DISABLED' is not ++ * selected at DPNI creation. ++ * Calling this function with 'revoke' set to '1' disables all transmit ++ * confirmation (including the private confirmation queues), regardless of ++ * previous settings; Note that in this case, Tx error frames are still ++ * enqueued to the general transmit errors queue. ++ * Calling this function with 'revoke' set to '0' restores the previous ++ * settings for both general and private transmit confirmation. ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_set_tx_conf_revoke(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int revoke); ++ ++/** ++ * dpni_set_rx_flow() - Set Rx flow configuration ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @tc_id: Traffic class selection (0-7); ++ * use 'DPNI_ALL_TCS' to set all TCs and all flows ++ * @flow_id: Rx flow id within the traffic class; use ++ * 'DPNI_ALL_TC_FLOWS' to set all flows within ++ * this tc_id; ignored if tc_id is set to ++ * 'DPNI_ALL_TCS'; ++ * @cfg: Rx flow configuration ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_set_rx_flow(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t tc_id, ++ uint16_t flow_id, ++ const struct dpni_queue_cfg *cfg); ++ ++/** ++ * dpni_get_rx_flow() - Get Rx flow attributes ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @tc_id: Traffic class selection (0-7) ++ * @flow_id: Rx flow id within the traffic class ++ * @attr: Returned Rx flow attributes ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_get_rx_flow(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t tc_id, ++ uint16_t flow_id, ++ struct dpni_queue_attr *attr); ++ ++/** ++ * dpni_set_rx_err_queue() - Set Rx error queue configuration ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @cfg: Queue configuration ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_set_rx_err_queue(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ const struct dpni_queue_cfg *cfg); ++ ++/** ++ * dpni_get_rx_err_queue() - Get Rx error queue attributes ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @attr: Returned Queue attributes ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_get_rx_err_queue(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ struct dpni_queue_attr *attr); ++ ++/** ++ * struct dpni_qos_tbl_cfg - Structure representing QOS table configuration ++ * @key_cfg_iova: I/O virtual address of 256 bytes DMA-able memory filled with ++ * key extractions to be used as the QoS criteria by calling ++ * dpni_prepare_key_cfg() ++ * @discard_on_miss: Set to '1' to discard frames in case of no match (miss); ++ * '0' to use the 'default_tc' in such cases ++ * @default_tc: Used in case of no-match and 'discard_on_miss'= 0 ++ */ ++struct dpni_qos_tbl_cfg { ++ uint64_t key_cfg_iova; ++ int discard_on_miss; ++ uint8_t default_tc; ++}; ++ ++/** ++ * dpni_set_qos_table() - Set QoS mapping table ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @cfg: QoS table configuration ++ * ++ * This function and all QoS-related functions require that ++ *'max_tcs > 1' was set at DPNI creation. ++ * ++ * warning: Before calling this function, call dpni_prepare_key_cfg() to ++ * prepare the key_cfg_iova parameter ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_set_qos_table(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ const struct dpni_qos_tbl_cfg *cfg); ++ ++/** ++ * struct dpni_rule_cfg - Rule configuration for table lookup ++ * @key_iova: I/O virtual address of the key (must be in DMA-able memory) ++ * @mask_iova: I/O virtual address of the mask (must be in DMA-able memory) ++ * @key_size: key and mask size (in bytes) ++ */ ++struct dpni_rule_cfg { ++ uint64_t key_iova; ++ uint64_t mask_iova; ++ uint8_t key_size; ++}; ++ ++/** ++ * dpni_add_qos_entry() - Add QoS mapping entry (to select a traffic class) ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @cfg: QoS rule to add ++ * @tc_id: Traffic class selection (0-7) ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_add_qos_entry(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ const struct dpni_rule_cfg *cfg, ++ uint8_t tc_id); ++ ++/** ++ * dpni_remove_qos_entry() - Remove QoS mapping entry ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @cfg: QoS rule to remove ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_remove_qos_entry(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ const struct dpni_rule_cfg *cfg); ++ ++/** ++ * dpni_clear_qos_table() - Clear all QoS mapping entries ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * ++ * Following this function call, all frames are directed to ++ * the default traffic class (0) ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_clear_qos_table(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token); ++ ++/** ++ * dpni_add_fs_entry() - Add Flow Steering entry for a specific traffic class ++ * (to select a flow ID) ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @tc_id: Traffic class selection (0-7) ++ * @cfg: Flow steering rule to add ++ * @flow_id: Flow id selection (must be smaller than the ++ * distribution size of the traffic class) ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_add_fs_entry(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t tc_id, ++ const struct dpni_rule_cfg *cfg, ++ uint16_t flow_id); ++ ++/** ++ * dpni_remove_fs_entry() - Remove Flow Steering entry from a specific ++ * traffic class ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @tc_id: Traffic class selection (0-7) ++ * @cfg: Flow steering rule to remove ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_remove_fs_entry(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t tc_id, ++ const struct dpni_rule_cfg *cfg); ++ ++/** ++ * dpni_clear_fs_entries() - Clear all Flow Steering entries of a specific ++ * traffic class ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @tc_id: Traffic class selection (0-7) ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_clear_fs_entries(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t tc_id); ++ ++/** ++ * dpni_set_vlan_insertion() - Enable/disable VLAN insertion for egress frames ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @en: Set to '1' to enable; '0' to disable ++ * ++ * Requires that the 'DPNI_OPT_VLAN_MANIPULATION' option is set ++ * at DPNI creation. ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_set_vlan_insertion(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int en); ++ ++/** ++ * dpni_set_vlan_removal() - Enable/disable VLAN removal for ingress frames ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @en: Set to '1' to enable; '0' to disable ++ * ++ * Requires that the 'DPNI_OPT_VLAN_MANIPULATION' option is set ++ * at DPNI creation. ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_set_vlan_removal(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int en); ++ ++/** ++ * dpni_set_ipr() - Enable/disable IP reassembly of ingress frames ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @en: Set to '1' to enable; '0' to disable ++ * ++ * Requires that the 'DPNI_OPT_IPR' option is set at DPNI creation. ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_set_ipr(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int en); ++ ++/** ++ * dpni_set_ipf() - Enable/disable IP fragmentation of egress frames ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPNI object ++ * @en: Set to '1' to enable; '0' to disable ++ * ++ * Requires that the 'DPNI_OPT_IPF' option is set at DPNI ++ * creation. Fragmentation is performed according to MTU value ++ * set by dpni_set_mtu() function ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpni_set_ipf(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int en); ++ ++#endif /* __FSL_DPNI_H */ +diff --git a/drivers/staging/fsl-dpaa2/mac/Kconfig b/drivers/staging/fsl-dpaa2/mac/Kconfig +new file mode 100644 +index 0000000..174a9cd +--- /dev/null ++++ b/drivers/staging/fsl-dpaa2/mac/Kconfig +@@ -0,0 +1,24 @@ ++config FSL_DPAA2_MAC ++ tristate "DPAA2 MAC / PHY interface" ++ depends on FSL_MC_BUS && FSL_DPAA2 ++ select MDIO_BUS_MUX_MMIOREG ++ select FSL_XGMAC_MDIO ++ select FIXED_PHY ++ ---help--- ++ Prototype driver for DPAA2 MAC / PHY interface object. ++ This driver works as a proxy between phylib including phy drivers and ++ the MC firmware. It receives updates on link state changes from PHY ++ lib and forwards them to MC and receives interrupt from MC whenever ++ a request is made to change the link state. ++ ++ ++config FSL_DPAA2_MAC_NETDEVS ++ bool "Expose net interfaces for PHYs" ++ default n ++ depends on FSL_DPAA2_MAC ++ ---help--- ++ Exposes macX net interfaces which allow direct control over MACs and ++ PHYs. ++ . ++ Leave disabled if unsure. ++ +diff --git a/drivers/staging/fsl-dpaa2/mac/Makefile b/drivers/staging/fsl-dpaa2/mac/Makefile +new file mode 100644 +index 0000000..bda9410 +--- /dev/null ++++ b/drivers/staging/fsl-dpaa2/mac/Makefile +@@ -0,0 +1,10 @@ ++ ++obj-$(CONFIG_FSL_DPAA2_MAC) += dpaa2-mac.o ++ ++dpaa2-mac-objs := mac.o dpmac.o ++ ++all: ++ make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules ++ ++clean: ++ make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean +diff --git a/drivers/staging/fsl-dpaa2/mac/dpmac-cmd.h b/drivers/staging/fsl-dpaa2/mac/dpmac-cmd.h +new file mode 100644 +index 0000000..dc00590 +--- /dev/null ++++ b/drivers/staging/fsl-dpaa2/mac/dpmac-cmd.h +@@ -0,0 +1,195 @@ ++/* Copyright 2013-2015 Freescale Semiconductor Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * * Neither the name of the above-listed copyright holders nor the ++ * names of any contributors may be used to endorse or promote products ++ * derived from this software without specific prior written permission. ++ * ++ * ++ * ALTERNATIVELY, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") as published by the Free Software ++ * Foundation, either version 2 of that License or (at your option) any ++ * later version. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE ++ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++#ifndef _FSL_DPMAC_CMD_H ++#define _FSL_DPMAC_CMD_H ++ ++/* DPMAC Version */ ++#define DPMAC_VER_MAJOR 3 ++#define DPMAC_VER_MINOR 2 ++ ++/* Command IDs */ ++#define DPMAC_CMDID_CLOSE 0x800 ++#define DPMAC_CMDID_OPEN 0x80c ++#define DPMAC_CMDID_CREATE 0x90c ++#define DPMAC_CMDID_DESTROY 0x900 ++ ++#define DPMAC_CMDID_GET_ATTR 0x004 ++#define DPMAC_CMDID_RESET 0x005 ++ ++#define DPMAC_CMDID_SET_IRQ 0x010 ++#define DPMAC_CMDID_GET_IRQ 0x011 ++#define DPMAC_CMDID_SET_IRQ_ENABLE 0x012 ++#define DPMAC_CMDID_GET_IRQ_ENABLE 0x013 ++#define DPMAC_CMDID_SET_IRQ_MASK 0x014 ++#define DPMAC_CMDID_GET_IRQ_MASK 0x015 ++#define DPMAC_CMDID_GET_IRQ_STATUS 0x016 ++#define DPMAC_CMDID_CLEAR_IRQ_STATUS 0x017 ++ ++#define DPMAC_CMDID_MDIO_READ 0x0c0 ++#define DPMAC_CMDID_MDIO_WRITE 0x0c1 ++#define DPMAC_CMDID_GET_LINK_CFG 0x0c2 ++#define DPMAC_CMDID_SET_LINK_STATE 0x0c3 ++#define DPMAC_CMDID_GET_COUNTER 0x0c4 ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPMAC_CMD_CREATE(cmd, cfg) \ ++ MC_CMD_OP(cmd, 0, 0, 32, int, cfg->mac_id) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPMAC_CMD_OPEN(cmd, dpmac_id) \ ++ MC_CMD_OP(cmd, 0, 0, 32, int, dpmac_id) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPMAC_CMD_SET_IRQ(cmd, irq_index, irq_cfg) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 0, 8, uint8_t, irq_index);\ ++ MC_CMD_OP(cmd, 0, 32, 32, uint32_t, irq_cfg->val);\ ++ MC_CMD_OP(cmd, 1, 0, 64, uint64_t, irq_cfg->addr); \ ++ MC_CMD_OP(cmd, 2, 0, 32, int, irq_cfg->irq_num); \ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPMAC_CMD_GET_IRQ(cmd, irq_index) \ ++ MC_CMD_OP(cmd, 0, 32, 8, uint8_t, irq_index) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPMAC_RSP_GET_IRQ(cmd, type, irq_cfg) \ ++do { \ ++ MC_RSP_OP(cmd, 0, 0, 32, uint32_t, irq_cfg->val); \ ++ MC_RSP_OP(cmd, 1, 0, 64, uint64_t, irq_cfg->addr); \ ++ MC_RSP_OP(cmd, 2, 0, 32, int, irq_cfg->irq_num); \ ++ MC_RSP_OP(cmd, 2, 32, 32, int, type); \ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPMAC_CMD_SET_IRQ_ENABLE(cmd, irq_index, en) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 0, 8, uint8_t, en); \ ++ MC_CMD_OP(cmd, 0, 32, 8, uint8_t, irq_index);\ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPMAC_CMD_GET_IRQ_ENABLE(cmd, irq_index) \ ++ MC_CMD_OP(cmd, 0, 32, 8, uint8_t, irq_index) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPMAC_RSP_GET_IRQ_ENABLE(cmd, en) \ ++ MC_RSP_OP(cmd, 0, 0, 8, uint8_t, en) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPMAC_CMD_SET_IRQ_MASK(cmd, irq_index, mask) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 0, 32, uint32_t, mask);\ ++ MC_CMD_OP(cmd, 0, 32, 8, uint8_t, irq_index);\ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPMAC_CMD_GET_IRQ_MASK(cmd, irq_index) \ ++ MC_CMD_OP(cmd, 0, 32, 8, uint8_t, irq_index) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPMAC_RSP_GET_IRQ_MASK(cmd, mask) \ ++ MC_RSP_OP(cmd, 0, 0, 32, uint32_t, mask) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPMAC_CMD_GET_IRQ_STATUS(cmd, irq_index, status) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 0, 32, uint32_t, status);\ ++ MC_CMD_OP(cmd, 0, 32, 8, uint8_t, irq_index);\ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPMAC_RSP_GET_IRQ_STATUS(cmd, status) \ ++ MC_RSP_OP(cmd, 0, 0, 32, uint32_t, status) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPMAC_CMD_CLEAR_IRQ_STATUS(cmd, irq_index, status) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 0, 32, uint32_t, status); \ ++ MC_CMD_OP(cmd, 0, 32, 8, uint8_t, irq_index);\ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPMAC_RSP_GET_ATTRIBUTES(cmd, attr) \ ++do { \ ++ MC_RSP_OP(cmd, 0, 0, 32, int, attr->phy_id);\ ++ MC_RSP_OP(cmd, 0, 32, 32, int, attr->id);\ ++ MC_RSP_OP(cmd, 1, 0, 16, uint16_t, attr->version.major);\ ++ MC_RSP_OP(cmd, 1, 16, 16, uint16_t, attr->version.minor);\ ++ MC_RSP_OP(cmd, 1, 32, 8, enum dpmac_link_type, attr->link_type);\ ++ MC_RSP_OP(cmd, 1, 40, 8, enum dpmac_eth_if, attr->eth_if);\ ++ MC_RSP_OP(cmd, 2, 0, 32, uint32_t, attr->max_rate);\ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPMAC_CMD_MDIO_READ(cmd, cfg) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 0, 8, uint8_t, cfg->phy_addr); \ ++ MC_CMD_OP(cmd, 0, 8, 8, uint8_t, cfg->reg); \ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPMAC_RSP_MDIO_READ(cmd, data) \ ++ MC_RSP_OP(cmd, 0, 16, 16, uint16_t, data) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPMAC_CMD_MDIO_WRITE(cmd, cfg) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 0, 8, uint8_t, cfg->phy_addr); \ ++ MC_CMD_OP(cmd, 0, 8, 8, uint8_t, cfg->reg); \ ++ MC_CMD_OP(cmd, 0, 16, 16, uint16_t, cfg->data); \ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPMAC_RSP_GET_LINK_CFG(cmd, cfg) \ ++do { \ ++ MC_RSP_OP(cmd, 0, 0, 64, uint64_t, cfg->options); \ ++ MC_RSP_OP(cmd, 1, 0, 32, uint32_t, cfg->rate); \ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPMAC_CMD_SET_LINK_STATE(cmd, cfg) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 0, 64, uint64_t, cfg->options); \ ++ MC_CMD_OP(cmd, 1, 0, 32, uint32_t, cfg->rate); \ ++ MC_CMD_OP(cmd, 2, 0, 1, int, cfg->up); \ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPMAC_CMD_GET_COUNTER(cmd, type) \ ++ MC_CMD_OP(cmd, 0, 0, 8, enum dpmac_counter, type) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPMAC_RSP_GET_COUNTER(cmd, counter) \ ++ MC_RSP_OP(cmd, 1, 0, 64, uint64_t, counter) ++ ++#endif /* _FSL_DPMAC_CMD_H */ +diff --git a/drivers/staging/fsl-dpaa2/mac/dpmac.c b/drivers/staging/fsl-dpaa2/mac/dpmac.c +new file mode 100644 +index 0000000..fc23b40 +--- /dev/null ++++ b/drivers/staging/fsl-dpaa2/mac/dpmac.c +@@ -0,0 +1,422 @@ ++/* Copyright 2013-2015 Freescale Semiconductor Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * * Neither the name of the above-listed copyright holders nor the ++ * names of any contributors may be used to endorse or promote products ++ * derived from this software without specific prior written permission. ++ * ++ * ++ * ALTERNATIVELY, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") as published by the Free Software ++ * Foundation, either version 2 of that License or (at your option) any ++ * later version. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE ++ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++#include "../../fsl-mc/include/mc-sys.h" ++#include "../../fsl-mc/include/mc-cmd.h" ++#include "dpmac.h" ++#include "dpmac-cmd.h" ++ ++int dpmac_open(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ int dpmac_id, ++ uint16_t *token) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPMAC_CMDID_OPEN, ++ cmd_flags, ++ 0); ++ DPMAC_CMD_OPEN(cmd, dpmac_id); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ *token = MC_CMD_HDR_READ_TOKEN(cmd.header); ++ ++ return err; ++} ++ ++int dpmac_close(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPMAC_CMDID_CLOSE, cmd_flags, ++ token); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpmac_create(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ const struct dpmac_cfg *cfg, ++ uint16_t *token) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPMAC_CMDID_CREATE, ++ cmd_flags, ++ 0); ++ DPMAC_CMD_CREATE(cmd, cfg); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ *token = MC_CMD_HDR_READ_TOKEN(cmd.header); ++ ++ return 0; ++} ++ ++int dpmac_destroy(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPMAC_CMDID_DESTROY, ++ cmd_flags, ++ token); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpmac_set_irq(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ struct dpmac_irq_cfg *irq_cfg) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPMAC_CMDID_SET_IRQ, ++ cmd_flags, ++ token); ++ DPMAC_CMD_SET_IRQ(cmd, irq_index, irq_cfg); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpmac_get_irq(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ int *type, ++ struct dpmac_irq_cfg *irq_cfg) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPMAC_CMDID_GET_IRQ, ++ cmd_flags, ++ token); ++ DPMAC_CMD_GET_IRQ(cmd, irq_index); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ DPMAC_RSP_GET_IRQ(cmd, *type, irq_cfg); ++ ++ return 0; ++} ++ ++int dpmac_set_irq_enable(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint8_t en) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPMAC_CMDID_SET_IRQ_ENABLE, ++ cmd_flags, ++ token); ++ DPMAC_CMD_SET_IRQ_ENABLE(cmd, irq_index, en); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpmac_get_irq_enable(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint8_t *en) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPMAC_CMDID_GET_IRQ_ENABLE, ++ cmd_flags, ++ token); ++ DPMAC_CMD_GET_IRQ_ENABLE(cmd, irq_index); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ DPMAC_RSP_GET_IRQ_ENABLE(cmd, *en); ++ ++ return 0; ++} ++ ++int dpmac_set_irq_mask(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint32_t mask) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPMAC_CMDID_SET_IRQ_MASK, ++ cmd_flags, ++ token); ++ DPMAC_CMD_SET_IRQ_MASK(cmd, irq_index, mask); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpmac_get_irq_mask(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint32_t *mask) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPMAC_CMDID_GET_IRQ_MASK, ++ cmd_flags, ++ token); ++ DPMAC_CMD_GET_IRQ_MASK(cmd, irq_index); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ DPMAC_RSP_GET_IRQ_MASK(cmd, *mask); ++ ++ return 0; ++} ++ ++int dpmac_get_irq_status(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint32_t *status) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPMAC_CMDID_GET_IRQ_STATUS, ++ cmd_flags, ++ token); ++ DPMAC_CMD_GET_IRQ_STATUS(cmd, irq_index, *status); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ DPMAC_RSP_GET_IRQ_STATUS(cmd, *status); ++ ++ return 0; ++} ++ ++int dpmac_clear_irq_status(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint32_t status) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPMAC_CMDID_CLEAR_IRQ_STATUS, ++ cmd_flags, ++ token); ++ DPMAC_CMD_CLEAR_IRQ_STATUS(cmd, irq_index, status); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpmac_get_attributes(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ struct dpmac_attr *attr) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPMAC_CMDID_GET_ATTR, ++ cmd_flags, ++ token); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ DPMAC_RSP_GET_ATTRIBUTES(cmd, attr); ++ ++ return 0; ++} ++ ++int dpmac_mdio_read(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ struct dpmac_mdio_cfg *cfg) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPMAC_CMDID_MDIO_READ, ++ cmd_flags, ++ token); ++ DPMAC_CMD_MDIO_READ(cmd, cfg); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ DPMAC_RSP_MDIO_READ(cmd, cfg->data); ++ ++ return 0; ++} ++ ++int dpmac_mdio_write(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ struct dpmac_mdio_cfg *cfg) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPMAC_CMDID_MDIO_WRITE, ++ cmd_flags, ++ token); ++ DPMAC_CMD_MDIO_WRITE(cmd, cfg); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpmac_get_link_cfg(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ struct dpmac_link_cfg *cfg) ++{ ++ struct mc_command cmd = { 0 }; ++ int err = 0; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPMAC_CMDID_GET_LINK_CFG, ++ cmd_flags, ++ token); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ DPMAC_RSP_GET_LINK_CFG(cmd, cfg); ++ ++ return 0; ++} ++ ++int dpmac_set_link_state(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ struct dpmac_link_state *link_state) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPMAC_CMDID_SET_LINK_STATE, ++ cmd_flags, ++ token); ++ DPMAC_CMD_SET_LINK_STATE(cmd, link_state); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpmac_get_counter(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ enum dpmac_counter type, ++ uint64_t *counter) ++{ ++ struct mc_command cmd = { 0 }; ++ int err = 0; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPMAC_CMDID_GET_COUNTER, ++ cmd_flags, ++ token); ++ DPMAC_CMD_GET_COUNTER(cmd, type); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ DPMAC_RSP_GET_COUNTER(cmd, *counter); ++ ++ return 0; ++} +diff --git a/drivers/staging/fsl-dpaa2/mac/dpmac.h b/drivers/staging/fsl-dpaa2/mac/dpmac.h +new file mode 100644 +index 0000000..ad27772 +--- /dev/null ++++ b/drivers/staging/fsl-dpaa2/mac/dpmac.h +@@ -0,0 +1,593 @@ ++/* Copyright 2013-2015 Freescale Semiconductor Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * * Neither the name of the above-listed copyright holders nor the ++ * names of any contributors may be used to endorse or promote products ++ * derived from this software without specific prior written permission. ++ * ++ * ++ * ALTERNATIVELY, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") as published by the Free Software ++ * Foundation, either version 2 of that License or (at your option) any ++ * later version. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE ++ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++#ifndef __FSL_DPMAC_H ++#define __FSL_DPMAC_H ++ ++/* Data Path MAC API ++ * Contains initialization APIs and runtime control APIs for DPMAC ++ */ ++ ++struct fsl_mc_io; ++ ++/** ++ * dpmac_open() - Open a control session for the specified object. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @dpmac_id: DPMAC unique ID ++ * @token: Returned token; use in subsequent API calls ++ * ++ * This function can be used to open a control session for an ++ * already created object; an object may have been declared in ++ * the DPL or by calling the dpmac_create function. ++ * This function returns a unique authentication token, ++ * associated with the specific object ID and the specific MC ++ * portal; this token must be used in all subsequent commands for ++ * this specific object ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpmac_open(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ int dpmac_id, ++ uint16_t *token); ++ ++/** ++ * dpmac_close() - Close the control session of the object ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPMAC object ++ * ++ * After this function is called, no further operations are ++ * allowed on the object without opening a new control session. ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpmac_close(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token); ++ ++/** ++ * enum dpmac_link_type - DPMAC link type ++ * @DPMAC_LINK_TYPE_NONE: No link ++ * @DPMAC_LINK_TYPE_FIXED: Link is fixed type ++ * @DPMAC_LINK_TYPE_PHY: Link by PHY ID ++ * @DPMAC_LINK_TYPE_BACKPLANE: Backplane link type ++ */ ++enum dpmac_link_type { ++ DPMAC_LINK_TYPE_NONE, ++ DPMAC_LINK_TYPE_FIXED, ++ DPMAC_LINK_TYPE_PHY, ++ DPMAC_LINK_TYPE_BACKPLANE ++}; ++ ++/** ++ * enum dpmac_eth_if - DPMAC Ethrnet interface ++ * @DPMAC_ETH_IF_MII: MII interface ++ * @DPMAC_ETH_IF_RMII: RMII interface ++ * @DPMAC_ETH_IF_SMII: SMII interface ++ * @DPMAC_ETH_IF_GMII: GMII interface ++ * @DPMAC_ETH_IF_RGMII: RGMII interface ++ * @DPMAC_ETH_IF_SGMII: SGMII interface ++ * @DPMAC_ETH_IF_QSGMII: QSGMII interface ++ * @DPMAC_ETH_IF_XAUI: XAUI interface ++ * @DPMAC_ETH_IF_XFI: XFI interface ++ */ ++enum dpmac_eth_if { ++ DPMAC_ETH_IF_MII, ++ DPMAC_ETH_IF_RMII, ++ DPMAC_ETH_IF_SMII, ++ DPMAC_ETH_IF_GMII, ++ DPMAC_ETH_IF_RGMII, ++ DPMAC_ETH_IF_SGMII, ++ DPMAC_ETH_IF_QSGMII, ++ DPMAC_ETH_IF_XAUI, ++ DPMAC_ETH_IF_XFI ++}; ++ ++/** ++ * struct dpmac_cfg - Structure representing DPMAC configuration ++ * @mac_id: Represents the Hardware MAC ID; in case of multiple WRIOP, ++ * the MAC IDs are continuous. ++ * For example: 2 WRIOPs, 16 MACs in each: ++ * MAC IDs for the 1st WRIOP: 1-16, ++ * MAC IDs for the 2nd WRIOP: 17-32. ++ */ ++struct dpmac_cfg { ++ int mac_id; ++}; ++ ++/** ++ * dpmac_create() - Create the DPMAC object. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @cfg: Configuration structure ++ * @token: Returned token; use in subsequent API calls ++ * ++ * Create the DPMAC object, allocate required resources and ++ * perform required initialization. ++ * ++ * The object can be created either by declaring it in the ++ * DPL file, or by calling this function. ++ * This function returns a unique authentication token, ++ * associated with the specific object ID and the specific MC ++ * portal; this token must be used in all subsequent calls to ++ * this specific object. For objects that are created using the ++ * DPL file, call dpmac_open function to get an authentication ++ * token first. ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpmac_create(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ const struct dpmac_cfg *cfg, ++ uint16_t *token); ++ ++/** ++ * dpmac_destroy() - Destroy the DPMAC object and release all its resources. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPMAC object ++ * ++ * Return: '0' on Success; error code otherwise. ++ */ ++int dpmac_destroy(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token); ++ ++/** ++ * DPMAC IRQ Index and Events ++ */ ++ ++/** ++ * IRQ index ++ */ ++#define DPMAC_IRQ_INDEX 0 ++/** ++ * IRQ event - indicates a change in link state ++ */ ++#define DPMAC_IRQ_EVENT_LINK_CFG_REQ 0x00000001 ++/** ++ * IRQ event - Indicates that the link state changed ++ */ ++#define DPMAC_IRQ_EVENT_LINK_CHANGED 0x00000002 ++ ++/** ++ * struct dpmac_irq_cfg - IRQ configuration ++ * @addr: Address that must be written to signal a message-based interrupt ++ * @val: Value to write into irq_addr address ++ * @irq_num: A user defined number associated with this IRQ ++ */ ++struct dpmac_irq_cfg { ++ uint64_t addr; ++ uint32_t val; ++ int irq_num; ++}; ++ ++/** ++ * dpmac_set_irq() - Set IRQ information for the DPMAC to trigger an interrupt. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPMAC object ++ * @irq_index: Identifies the interrupt index to configure ++ * @irq_cfg: IRQ configuration ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpmac_set_irq(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ struct dpmac_irq_cfg *irq_cfg); ++ ++/** ++ * dpmac_get_irq() - Get IRQ information from the DPMAC. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPMAC object ++ * @irq_index: The interrupt index to configure ++ * @type: Interrupt type: 0 represents message interrupt ++ * type (both irq_addr and irq_val are valid) ++ * @irq_cfg: IRQ attributes ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpmac_get_irq(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ int *type, ++ struct dpmac_irq_cfg *irq_cfg); ++ ++/** ++ * dpmac_set_irq_enable() - Set overall interrupt state. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPMAC object ++ * @irq_index: The interrupt index to configure ++ * @en: Interrupt state - enable = 1, disable = 0 ++ * ++ * Allows GPP software to control when interrupts are generated. ++ * Each interrupt can have up to 32 causes. The enable/disable control's the ++ * overall interrupt state. if the interrupt is disabled no causes will cause ++ * an interrupt. ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpmac_set_irq_enable(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint8_t en); ++ ++/** ++ * dpmac_get_irq_enable() - Get overall interrupt state ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPMAC object ++ * @irq_index: The interrupt index to configure ++ * @en: Returned interrupt state - enable = 1, disable = 0 ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpmac_get_irq_enable(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint8_t *en); ++ ++/** ++ * dpmac_set_irq_mask() - Set interrupt mask. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPMAC object ++ * @irq_index: The interrupt index to configure ++ * @mask: Event mask to trigger interrupt; ++ * each bit: ++ * 0 = ignore event ++ * 1 = consider event for asserting IRQ ++ * ++ * Every interrupt can have up to 32 causes and the interrupt model supports ++ * masking/unmasking each cause independently ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpmac_set_irq_mask(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint32_t mask); ++ ++/** ++ * dpmac_get_irq_mask() - Get interrupt mask. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPMAC object ++ * @irq_index: The interrupt index to configure ++ * @mask: Returned event mask to trigger interrupt ++ * ++ * Every interrupt can have up to 32 causes and the interrupt model supports ++ * masking/unmasking each cause independently ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpmac_get_irq_mask(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint32_t *mask); ++ ++/** ++ * dpmac_get_irq_status() - Get the current status of any pending interrupts. ++ * ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPMAC object ++ * @irq_index: The interrupt index to configure ++ * @status: Returned interrupts status - one bit per cause: ++ * 0 = no interrupt pending ++ * 1 = interrupt pending ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpmac_get_irq_status(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint32_t *status); ++ ++/** ++ * dpmac_clear_irq_status() - Clear a pending interrupt's status ++ * ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPMAC object ++ * @irq_index: The interrupt index to configure ++ * @status: Bits to clear (W1C) - one bit per cause: ++ * 0 = don't change ++ * 1 = clear status bit ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpmac_clear_irq_status(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint32_t status); ++ ++/** ++ * struct dpmac_attr - Structure representing DPMAC attributes ++ * @id: DPMAC object ID ++ * @phy_id: PHY ID ++ * @link_type: link type ++ * @eth_if: Ethernet interface ++ * @max_rate: Maximum supported rate - in Mbps ++ * @version: DPMAC version ++ */ ++struct dpmac_attr { ++ int id; ++ int phy_id; ++ enum dpmac_link_type link_type; ++ enum dpmac_eth_if eth_if; ++ uint32_t max_rate; ++ /** ++ * struct version - Structure representing DPMAC version ++ * @major: DPMAC major version ++ * @minor: DPMAC minor version ++ */ ++ struct { ++ uint16_t major; ++ uint16_t minor; ++ } version; ++}; ++ ++/** ++ * dpmac_get_attributes - Retrieve DPMAC attributes. ++ * ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPMAC object ++ * @attr: Returned object's attributes ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpmac_get_attributes(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ struct dpmac_attr *attr); ++ ++/** ++ * struct dpmac_mdio_cfg - DPMAC MDIO read/write parameters ++ * @phy_addr: MDIO device address ++ * @reg: Address of the register within the Clause 45 PHY device from which data ++ * is to be read ++ * @data: Data read/write from/to MDIO ++ */ ++struct dpmac_mdio_cfg { ++ uint8_t phy_addr; ++ uint8_t reg; ++ uint16_t data; ++}; ++ ++/** ++ * dpmac_mdio_read() - Perform MDIO read transaction ++ * @mc_io: Pointer to opaque I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPMAC object ++ * @cfg: Structure with MDIO transaction parameters ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpmac_mdio_read(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ struct dpmac_mdio_cfg *cfg); ++ ++/** ++ * dpmac_mdio_write() - Perform MDIO write transaction ++ * @mc_io: Pointer to opaque I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPMAC object ++ * @cfg: Structure with MDIO transaction parameters ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpmac_mdio_write(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ struct dpmac_mdio_cfg *cfg); ++ ++/** ++ * DPMAC link configuration/state options ++ */ ++ ++/** ++ * Enable auto-negotiation ++ */ ++#define DPMAC_LINK_OPT_AUTONEG 0x0000000000000001ULL ++/** ++ * Enable half-duplex mode ++ */ ++#define DPMAC_LINK_OPT_HALF_DUPLEX 0x0000000000000002ULL ++/** ++ * Enable pause frames ++ */ ++#define DPMAC_LINK_OPT_PAUSE 0x0000000000000004ULL ++/** ++ * Enable a-symmetric pause frames ++ */ ++#define DPMAC_LINK_OPT_ASYM_PAUSE 0x0000000000000008ULL ++ ++/** ++ * struct dpmac_link_cfg - Structure representing DPMAC link configuration ++ * @rate: Link's rate - in Mbps ++ * @options: Enable/Disable DPMAC link cfg features (bitmap) ++ */ ++struct dpmac_link_cfg { ++ uint32_t rate; ++ uint64_t options; ++}; ++ ++/** ++ * dpmac_get_link_cfg() - Get Ethernet link configuration ++ * @mc_io: Pointer to opaque I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPMAC object ++ * @cfg: Returned structure with the link configuration ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpmac_get_link_cfg(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ struct dpmac_link_cfg *cfg); ++ ++/** ++ * struct dpmac_link_state - DPMAC link configuration request ++ * @rate: Rate in Mbps ++ * @options: Enable/Disable DPMAC link cfg features (bitmap) ++ * @up: Link state ++ */ ++struct dpmac_link_state { ++ uint32_t rate; ++ uint64_t options; ++ int up; ++}; ++ ++/** ++ * dpmac_set_link_state() - Set the Ethernet link status ++ * @mc_io: Pointer to opaque I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPMAC object ++ * @link_state: Link state configuration ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpmac_set_link_state(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ struct dpmac_link_state *link_state); ++ ++/** ++ * enum dpmac_counter - DPMAC counter types ++ * @DPMAC_CNT_ING_FRAME_64: counts 64-bytes frames, good or bad. ++ * @DPMAC_CNT_ING_FRAME_127: counts 65- to 127-bytes frames, good or bad. ++ * @DPMAC_CNT_ING_FRAME_255: counts 128- to 255-bytes frames, good or bad. ++ * @DPMAC_CNT_ING_FRAME_511: counts 256- to 511-bytes frames, good or bad. ++ * @DPMAC_CNT_ING_FRAME_1023: counts 512- to 1023-bytes frames, good or bad. ++ * @DPMAC_CNT_ING_FRAME_1518: counts 1024- to 1518-bytes frames, good or bad. ++ * @DPMAC_CNT_ING_FRAME_1519_MAX: counts 1519-bytes frames and larger ++ * (up to max frame length specified), ++ * good or bad. ++ * @DPMAC_CNT_ING_FRAG: counts frames which are shorter than 64 bytes received ++ * with a wrong CRC ++ * @DPMAC_CNT_ING_JABBER: counts frames longer than the maximum frame length ++ * specified, with a bad frame check sequence. ++ * @DPMAC_CNT_ING_FRAME_DISCARD: counts dropped frames due to internal errors. ++ * Occurs when a receive FIFO overflows. ++ * Includes also frames truncated as a result of ++ * the receive FIFO overflow. ++ * @DPMAC_CNT_ING_ALIGN_ERR: counts frames with an alignment error ++ * (optional used for wrong SFD). ++ * @DPMAC_CNT_EGR_UNDERSIZED: counts frames transmitted that was less than 64 ++ * bytes long with a good CRC. ++ * @DPMAC_CNT_ING_OVERSIZED: counts frames longer than the maximum frame length ++ * specified, with a good frame check sequence. ++ * @DPMAC_CNT_ING_VALID_PAUSE_FRAME: counts valid pause frames (regular and PFC) ++ * @DPMAC_CNT_EGR_VALID_PAUSE_FRAME: counts valid pause frames transmitted ++ * (regular and PFC). ++ * @DPMAC_CNT_ING_BYTE: counts bytes received except preamble for all valid ++ * frames and valid pause frames. ++ * @DPMAC_CNT_ING_MCAST_FRAME: counts received multicast frames. ++ * @DPMAC_CNT_ING_BCAST_FRAME: counts received broadcast frames. ++ * @DPMAC_CNT_ING_ALL_FRAME: counts each good or bad frames received. ++ * @DPMAC_CNT_ING_UCAST_FRAME: counts received unicast frames. ++ * @DPMAC_CNT_ING_ERR_FRAME: counts frames received with an error ++ * (except for undersized/fragment frame). ++ * @DPMAC_CNT_EGR_BYTE: counts bytes transmitted except preamble for all valid ++ * frames and valid pause frames transmitted. ++ * @DPMAC_CNT_EGR_MCAST_FRAME: counts transmitted multicast frames. ++ * @DPMAC_CNT_EGR_BCAST_FRAME: counts transmitted broadcast frames. ++ * @DPMAC_CNT_EGR_UCAST_FRAME: counts transmitted unicast frames. ++ * @DPMAC_CNT_EGR_ERR_FRAME: counts frames transmitted with an error. ++ * @DPMAC_CNT_ING_GOOD_FRAME: counts frames received without error, including ++ * pause frames. ++ * @DPMAC_CNT_ENG_GOOD_FRAME: counts frames transmitted without error, including ++ * pause frames. ++ */ ++enum dpmac_counter { ++ DPMAC_CNT_ING_FRAME_64, ++ DPMAC_CNT_ING_FRAME_127, ++ DPMAC_CNT_ING_FRAME_255, ++ DPMAC_CNT_ING_FRAME_511, ++ DPMAC_CNT_ING_FRAME_1023, ++ DPMAC_CNT_ING_FRAME_1518, ++ DPMAC_CNT_ING_FRAME_1519_MAX, ++ DPMAC_CNT_ING_FRAG, ++ DPMAC_CNT_ING_JABBER, ++ DPMAC_CNT_ING_FRAME_DISCARD, ++ DPMAC_CNT_ING_ALIGN_ERR, ++ DPMAC_CNT_EGR_UNDERSIZED, ++ DPMAC_CNT_ING_OVERSIZED, ++ DPMAC_CNT_ING_VALID_PAUSE_FRAME, ++ DPMAC_CNT_EGR_VALID_PAUSE_FRAME, ++ DPMAC_CNT_ING_BYTE, ++ DPMAC_CNT_ING_MCAST_FRAME, ++ DPMAC_CNT_ING_BCAST_FRAME, ++ DPMAC_CNT_ING_ALL_FRAME, ++ DPMAC_CNT_ING_UCAST_FRAME, ++ DPMAC_CNT_ING_ERR_FRAME, ++ DPMAC_CNT_EGR_BYTE, ++ DPMAC_CNT_EGR_MCAST_FRAME, ++ DPMAC_CNT_EGR_BCAST_FRAME, ++ DPMAC_CNT_EGR_UCAST_FRAME, ++ DPMAC_CNT_EGR_ERR_FRAME, ++ DPMAC_CNT_ING_GOOD_FRAME, ++ DPMAC_CNT_ENG_GOOD_FRAME ++}; ++ ++/** ++ * dpmac_get_counter() - Read a specific DPMAC counter ++ * @mc_io: Pointer to opaque I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPMAC object ++ * @type: The requested counter ++ * @counter: Returned counter value ++ * ++ * Return: The requested counter; '0' otherwise. ++ */ ++int dpmac_get_counter(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ enum dpmac_counter type, ++ uint64_t *counter); ++ ++#endif /* __FSL_DPMAC_H */ +diff --git a/drivers/staging/fsl-dpaa2/mac/mac.c b/drivers/staging/fsl-dpaa2/mac/mac.c +new file mode 100644 +index 0000000..fe16b8b +--- /dev/null ++++ b/drivers/staging/fsl-dpaa2/mac/mac.c +@@ -0,0 +1,694 @@ ++/* Copyright 2015 Freescale Semiconductor Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * * Neither the name of Freescale Semiconductor nor the ++ * names of its contributors may be used to endorse or promote products ++ * derived from this software without specific prior written permission. ++ * ++ * ++ * ALTERNATIVELY, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") as published by the Free Software ++ * Foundation, either version 2 of that License or (at your option) any ++ * later version. ++ * ++ * THIS SOFTWARE IS PROVIDED BY Freescale Semiconductor ``AS IS'' AND ANY ++ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ++ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE ++ * DISCLAIMED. IN NO EVENT SHALL Freescale Semiconductor BE LIABLE FOR ANY ++ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ++ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; ++ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ++ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ++ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS ++ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++#include ++ ++#include ++#include ++#include ++#include ++ ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "../../fsl-mc/include/mc.h" ++#include "../../fsl-mc/include/mc-sys.h" ++ ++#include "dpmac.h" ++#include "dpmac-cmd.h" ++ ++#define DPAA2_SUPPORTED_DPMAC_VERSION 3 ++ ++struct dpaa2_mac_priv { ++ struct net_device *netdev; ++ struct fsl_mc_device *mc_dev; ++ struct dpmac_attr attr; ++ struct dpmac_link_state old_state; ++}; ++ ++/* TODO: fix the 10G modes, mapping can't be right: ++ * XGMII is paralel ++ * XAUI is serial, using 8b/10b encoding ++ * XFI is also serial but using 64b/66b encoding ++ * they can't all map to XGMII... ++ * ++ * This must be kept in sync with enum dpmac_eth_if. ++ */ ++static phy_interface_t dpaa2_mac_iface_mode[] = { ++ /* DPMAC_ETH_IF_MII */ ++ PHY_INTERFACE_MODE_MII, ++ /* DPMAC_ETH_IF_RMII */ ++ PHY_INTERFACE_MODE_RMII, ++ /* DPMAC_ETH_IF_SMII */ ++ PHY_INTERFACE_MODE_SMII, ++ /* DPMAC_ETH_IF_GMII */ ++ PHY_INTERFACE_MODE_GMII, ++ /* DPMAC_ETH_IF_RGMII */ ++ PHY_INTERFACE_MODE_RGMII, ++ /* DPMAC_ETH_IF_SGMII */ ++ PHY_INTERFACE_MODE_SGMII, ++ /* DPMAC_ETH_IF_QSGMII */ ++ PHY_INTERFACE_MODE_QSGMII, ++ /* DPMAC_ETH_IF_XAUI */ ++ PHY_INTERFACE_MODE_XGMII, ++ /* DPMAC_ETH_IF_XFI */ ++ PHY_INTERFACE_MODE_XGMII, ++}; ++ ++static void dpaa2_mac_link_changed(struct net_device *netdev) ++{ ++ struct phy_device *phydev; ++ struct dpmac_link_state state = { 0 }; ++ struct dpaa2_mac_priv *priv = netdev_priv(netdev); ++ int err; ++ ++ /* the PHY just notified us of link state change */ ++ phydev = netdev->phydev; ++ ++ state.up = !!phydev->link; ++ if (phydev->link) { ++ state.rate = phydev->speed; ++ ++ if (!phydev->duplex) ++ state.options |= DPMAC_LINK_OPT_HALF_DUPLEX; ++ if (phydev->autoneg) ++ state.options |= DPMAC_LINK_OPT_AUTONEG; ++ ++ netif_carrier_on(netdev); ++ } else { ++ netif_carrier_off(netdev); ++ } ++ ++ if (priv->old_state.up != state.up || ++ priv->old_state.rate != state.rate || ++ priv->old_state.options != state.options) { ++ priv->old_state = state; ++ phy_print_status(phydev); ++ } ++ ++ /* We must interrogate MC at all times, because we don't know ++ * when and whether a potential DPNI may have read the link state. ++ */ ++ err = dpmac_set_link_state(priv->mc_dev->mc_io, 0, ++ priv->mc_dev->mc_handle, &state); ++ if (unlikely(err)) ++ dev_err(&priv->mc_dev->dev, "dpmac_set_link_state: %d\n", err); ++} ++ ++/* IRQ bits that we handle */ ++static const u32 dpmac_irq_mask = DPMAC_IRQ_EVENT_LINK_CFG_REQ; ++ ++#ifdef CONFIG_FSL_DPAA2_MAC_NETDEVS ++static netdev_tx_t dpaa2_mac_drop_frame(struct sk_buff *skb, ++ struct net_device *dev) ++{ ++ /* we don't support I/O for now, drop the frame */ ++ dev_kfree_skb_any(skb); ++ return NETDEV_TX_OK; ++} ++ ++static int dpaa2_mac_open(struct net_device *netdev) ++{ ++ /* start PHY state machine */ ++ phy_start(netdev->phydev); ++ ++ return 0; ++} ++ ++static int dpaa2_mac_stop(struct net_device *netdev) ++{ ++ if (!netdev->phydev) ++ goto done; ++ ++ /* stop PHY state machine */ ++ phy_stop(netdev->phydev); ++ ++ /* signal link down to firmware */ ++ netdev->phydev->link = 0; ++ dpaa2_mac_link_changed(netdev); ++ ++done: ++ return 0; ++} ++ ++static int dpaa2_mac_get_settings(struct net_device *netdev, ++ struct ethtool_cmd *cmd) ++{ ++ return phy_ethtool_gset(netdev->phydev, cmd); ++} ++ ++static int dpaa2_mac_set_settings(struct net_device *netdev, ++ struct ethtool_cmd *cmd) ++{ ++ return phy_ethtool_sset(netdev->phydev, cmd); ++} ++ ++static struct rtnl_link_stats64 ++*dpaa2_mac_get_stats(struct net_device *netdev, ++ struct rtnl_link_stats64 *storage) ++{ ++ struct dpaa2_mac_priv *priv = netdev_priv(netdev); ++ u64 tmp; ++ int err; ++ ++ err = dpmac_get_counter(priv->mc_dev->mc_io, 0, priv->mc_dev->mc_handle, ++ DPMAC_CNT_EGR_MCAST_FRAME, ++ &storage->tx_packets); ++ if (err) ++ goto error; ++ err = dpmac_get_counter(priv->mc_dev->mc_io, 0, priv->mc_dev->mc_handle, ++ DPMAC_CNT_EGR_BCAST_FRAME, &tmp); ++ if (err) ++ goto error; ++ storage->tx_packets += tmp; ++ err = dpmac_get_counter(priv->mc_dev->mc_io, 0, priv->mc_dev->mc_handle, ++ DPMAC_CNT_EGR_UCAST_FRAME, &tmp); ++ if (err) ++ goto error; ++ storage->tx_packets += tmp; ++ ++ err = dpmac_get_counter(priv->mc_dev->mc_io, 0, priv->mc_dev->mc_handle, ++ DPMAC_CNT_EGR_UNDERSIZED, &storage->tx_dropped); ++ if (err) ++ goto error; ++ err = dpmac_get_counter(priv->mc_dev->mc_io, 0, priv->mc_dev->mc_handle, ++ DPMAC_CNT_EGR_BYTE, &storage->tx_bytes); ++ if (err) ++ goto error; ++ err = dpmac_get_counter(priv->mc_dev->mc_io, 0, priv->mc_dev->mc_handle, ++ DPMAC_CNT_EGR_ERR_FRAME, &storage->tx_errors); ++ if (err) ++ goto error; ++ ++ err = dpmac_get_counter(priv->mc_dev->mc_io, 0, priv->mc_dev->mc_handle, ++ DPMAC_CNT_ING_ALL_FRAME, &storage->rx_packets); ++ if (err) ++ goto error; ++ err = dpmac_get_counter(priv->mc_dev->mc_io, 0, priv->mc_dev->mc_handle, ++ DPMAC_CNT_ING_MCAST_FRAME, &storage->multicast); ++ if (err) ++ goto error; ++ err = dpmac_get_counter(priv->mc_dev->mc_io, 0, priv->mc_dev->mc_handle, ++ DPMAC_CNT_ING_FRAME_DISCARD, ++ &storage->rx_dropped); ++ if (err) ++ goto error; ++ err = dpmac_get_counter(priv->mc_dev->mc_io, 0, priv->mc_dev->mc_handle, ++ DPMAC_CNT_ING_ALIGN_ERR, &storage->rx_errors); ++ if (err) ++ goto error; ++ err = dpmac_get_counter(priv->mc_dev->mc_io, 0, priv->mc_dev->mc_handle, ++ DPMAC_CNT_ING_OVERSIZED, &tmp); ++ if (err) ++ goto error; ++ storage->rx_errors += tmp; ++ err = dpmac_get_counter(priv->mc_dev->mc_io, 0, priv->mc_dev->mc_handle, ++ DPMAC_CNT_ING_BYTE, &storage->rx_bytes); ++ if (err) ++ goto error; ++ ++ return storage; ++ ++error: ++ netdev_err(netdev, "dpmac_get_counter err %d\n", err); ++ return storage; ++} ++ ++static struct { ++ enum dpmac_counter id; ++ char name[ETH_GSTRING_LEN]; ++} dpaa2_mac_counters[] = { ++ {DPMAC_CNT_ING_ALL_FRAME, "rx all frames"}, ++ {DPMAC_CNT_ING_GOOD_FRAME, "rx frames ok"}, ++ {DPMAC_CNT_ING_ERR_FRAME, "rx frame errors"}, ++ {DPMAC_CNT_ING_FRAME_DISCARD, "rx frame discards"}, ++ {DPMAC_CNT_ING_UCAST_FRAME, "rx u-cast"}, ++ {DPMAC_CNT_ING_BCAST_FRAME, "rx b-cast"}, ++ {DPMAC_CNT_ING_MCAST_FRAME, "rx m-cast"}, ++ {DPMAC_CNT_ING_FRAME_64, "rx 64 bytes"}, ++ {DPMAC_CNT_ING_FRAME_127, "rx 65-127 bytes"}, ++ {DPMAC_CNT_ING_FRAME_255, "rx 128-255 bytes"}, ++ {DPMAC_CNT_ING_FRAME_511, "rx 256-511 bytes"}, ++ {DPMAC_CNT_ING_FRAME_1023, "rx 512-1023 bytes"}, ++ {DPMAC_CNT_ING_FRAME_1518, "rx 1024-1518 bytes"}, ++ {DPMAC_CNT_ING_FRAME_1519_MAX, "rx 1519-max bytes"}, ++ {DPMAC_CNT_ING_FRAG, "rx frags"}, ++ {DPMAC_CNT_ING_JABBER, "rx jabber"}, ++ {DPMAC_CNT_ING_ALIGN_ERR, "rx align errors"}, ++ {DPMAC_CNT_ING_OVERSIZED, "rx oversized"}, ++ {DPMAC_CNT_ING_VALID_PAUSE_FRAME, "rx pause"}, ++ {DPMAC_CNT_ING_BYTE, "rx bytes"}, ++ {DPMAC_CNT_ENG_GOOD_FRAME, "tx frames ok"}, ++ {DPMAC_CNT_EGR_UCAST_FRAME, "tx u-cast"}, ++ {DPMAC_CNT_EGR_MCAST_FRAME, "tx m-cast"}, ++ {DPMAC_CNT_EGR_BCAST_FRAME, "tx b-cast"}, ++ {DPMAC_CNT_EGR_ERR_FRAME, "tx frame errors"}, ++ {DPMAC_CNT_EGR_UNDERSIZED, "tx undersized"}, ++ {DPMAC_CNT_EGR_VALID_PAUSE_FRAME, "tx b-pause"}, ++ {DPMAC_CNT_EGR_BYTE, "tx bytes"}, ++ ++}; ++ ++static void dpaa2_mac_get_strings(struct net_device *netdev, ++ u32 stringset, u8 *data) ++{ ++ int i; ++ ++ switch (stringset) { ++ case ETH_SS_STATS: ++ for (i = 0; i < ARRAY_SIZE(dpaa2_mac_counters); i++) ++ memcpy(data + i * ETH_GSTRING_LEN, ++ dpaa2_mac_counters[i].name, ++ ETH_GSTRING_LEN); ++ break; ++ } ++} ++ ++static void dpaa2_mac_get_ethtool_stats(struct net_device *netdev, ++ struct ethtool_stats *stats, ++ u64 *data) ++{ ++ struct dpaa2_mac_priv *priv = netdev_priv(netdev); ++ int i; ++ int err; ++ ++ for (i = 0; i < ARRAY_SIZE(dpaa2_mac_counters); i++) { ++ err = dpmac_get_counter(priv->mc_dev->mc_io, ++ 0, ++ priv->mc_dev->mc_handle, ++ dpaa2_mac_counters[i].id, &data[i]); ++ if (err) ++ netdev_err(netdev, "dpmac_get_counter[%s] err %d\n", ++ dpaa2_mac_counters[i].name, err); ++ } ++} ++ ++static int dpaa2_mac_get_sset_count(struct net_device *dev, int sset) ++{ ++ switch (sset) { ++ case ETH_SS_STATS: ++ return ARRAY_SIZE(dpaa2_mac_counters); ++ default: ++ return -EOPNOTSUPP; ++ } ++} ++ ++static const struct net_device_ops dpaa2_mac_ndo_ops = { ++ .ndo_start_xmit = &dpaa2_mac_drop_frame, ++ .ndo_open = &dpaa2_mac_open, ++ .ndo_stop = &dpaa2_mac_stop, ++ .ndo_get_stats64 = &dpaa2_mac_get_stats, ++}; ++ ++static const struct ethtool_ops dpaa2_mac_ethtool_ops = { ++ .get_settings = &dpaa2_mac_get_settings, ++ .set_settings = &dpaa2_mac_set_settings, ++ .get_strings = &dpaa2_mac_get_strings, ++ .get_ethtool_stats = &dpaa2_mac_get_ethtool_stats, ++ .get_sset_count = &dpaa2_mac_get_sset_count, ++}; ++#endif /* CONFIG_FSL_DPAA2_MAC_NETDEVS */ ++ ++static void configure_link(struct dpaa2_mac_priv *priv, ++ struct dpmac_link_cfg *cfg) ++{ ++ struct phy_device *phydev = priv->netdev->phydev; ++ ++ if (unlikely(!phydev)) ++ return; ++ ++ phydev->speed = cfg->rate; ++ phydev->duplex = !!(cfg->options & DPMAC_LINK_OPT_HALF_DUPLEX); ++ ++ if (cfg->options & DPMAC_LINK_OPT_AUTONEG) { ++ phydev->autoneg = 1; ++ phydev->advertising |= ADVERTISED_Autoneg; ++ } else { ++ phydev->autoneg = 0; ++ phydev->advertising &= ~ADVERTISED_Autoneg; ++ } ++ ++ phy_start_aneg(phydev); ++} ++ ++static irqreturn_t dpaa2_mac_irq_handler(int irq_num, void *arg) ++{ ++ struct device *dev = (struct device *)arg; ++ struct fsl_mc_device *mc_dev = to_fsl_mc_device(dev); ++ struct dpaa2_mac_priv *priv = dev_get_drvdata(dev); ++ struct dpmac_link_cfg link_cfg; ++ u32 status; ++ int err; ++ ++ err = dpmac_get_irq_status(mc_dev->mc_io, 0, mc_dev->mc_handle, ++ DPMAC_IRQ_INDEX, &status); ++ if (unlikely(err || !status)) ++ return IRQ_NONE; ++ ++ /* DPNI-initiated link configuration; 'ifconfig up' also calls this */ ++ if (status & DPMAC_IRQ_EVENT_LINK_CFG_REQ) { ++ err = dpmac_get_link_cfg(mc_dev->mc_io, 0, mc_dev->mc_handle, ++ &link_cfg); ++ if (unlikely(err)) ++ goto out; ++ ++ configure_link(priv, &link_cfg); ++ } ++ ++out: ++ dpmac_clear_irq_status(mc_dev->mc_io, 0, mc_dev->mc_handle, ++ DPMAC_IRQ_INDEX, status); ++ ++ return IRQ_HANDLED; ++} ++ ++static int setup_irqs(struct fsl_mc_device *mc_dev) ++{ ++ int err; ++ ++ err = fsl_mc_allocate_irqs(mc_dev); ++ if (err) { ++ dev_err(&mc_dev->dev, "fsl_mc_allocate_irqs err %d\n", err); ++ return err; ++ } ++ ++ err = devm_request_threaded_irq(&mc_dev->dev, ++ mc_dev->irqs[0]->irq_number, ++ NULL, &dpaa2_mac_irq_handler, ++ IRQF_NO_SUSPEND | IRQF_ONESHOT, ++ dev_name(&mc_dev->dev), &mc_dev->dev); ++ if (err) { ++ dev_err(&mc_dev->dev, "devm_request_threaded_irq err %d\n", ++ err); ++ goto free_irq; ++ } ++ ++ err = dpmac_set_irq_mask(mc_dev->mc_io, 0, mc_dev->mc_handle, ++ DPMAC_IRQ_INDEX, dpmac_irq_mask); ++ if (err) { ++ dev_err(&mc_dev->dev, "dpmac_set_irq_mask err %d\n", err); ++ goto free_irq; ++ } ++ err = dpmac_set_irq_enable(mc_dev->mc_io, 0, mc_dev->mc_handle, ++ DPMAC_IRQ_INDEX, 1); ++ if (err) { ++ dev_err(&mc_dev->dev, "dpmac_set_irq_enable err %d\n", err); ++ goto free_irq; ++ } ++ ++ return 0; ++ ++free_irq: ++ fsl_mc_free_irqs(mc_dev); ++ ++ return err; ++} ++ ++static void teardown_irqs(struct fsl_mc_device *mc_dev) ++{ ++ int err; ++ ++ err = dpmac_set_irq_mask(mc_dev->mc_io, 0, mc_dev->mc_handle, ++ DPMAC_IRQ_INDEX, dpmac_irq_mask); ++ if (err) ++ dev_err(&mc_dev->dev, "dpmac_set_irq_mask err %d\n", err); ++ ++ err = dpmac_set_irq_enable(mc_dev->mc_io, 0, mc_dev->mc_handle, ++ DPMAC_IRQ_INDEX, 0); ++ if (err) ++ dev_err(&mc_dev->dev, "dpmac_set_irq_enable err %d\n", err); ++ ++ fsl_mc_free_irqs(mc_dev); ++} ++ ++static struct device_node *lookup_node(struct device *dev, int dpmac_id) ++{ ++ struct device_node *dpmacs, *dpmac = NULL; ++ struct device_node *mc_node = dev->of_node; ++ u32 id; ++ int err; ++ ++ dpmacs = of_find_node_by_name(mc_node, "dpmacs"); ++ if (!dpmacs) { ++ dev_err(dev, "No dpmacs subnode in device-tree\n"); ++ return NULL; ++ } ++ ++ while ((dpmac = of_get_next_child(dpmacs, dpmac))) { ++ err = of_property_read_u32(dpmac, "reg", &id); ++ if (err) ++ continue; ++ if (id == dpmac_id) ++ return dpmac; ++ } ++ ++ return NULL; ++} ++ ++static int dpaa2_mac_probe(struct fsl_mc_device *mc_dev) ++{ ++ struct device *dev; ++ struct dpaa2_mac_priv *priv = NULL; ++ struct device_node *phy_node, *dpmac_node; ++ struct net_device *netdev; ++ phy_interface_t if_mode; ++ int err = 0; ++ ++ dev = &mc_dev->dev; ++ ++ /* prepare a net_dev structure to make the phy lib API happy */ ++ netdev = alloc_etherdev(sizeof(*priv)); ++ if (!netdev) { ++ dev_err(dev, "alloc_etherdev error\n"); ++ err = -ENOMEM; ++ goto err_exit; ++ } ++ priv = netdev_priv(netdev); ++ priv->mc_dev = mc_dev; ++ priv->netdev = netdev; ++ ++ SET_NETDEV_DEV(netdev, dev); ++ ++#ifdef CONFIG_FSL_DPAA2_MAC_NETDEVS ++ snprintf(netdev->name, IFNAMSIZ, "mac%d", mc_dev->obj_desc.id); ++#endif ++ ++ dev_set_drvdata(dev, priv); ++ ++ err = fsl_mc_portal_allocate(mc_dev, 0, &mc_dev->mc_io); ++ if (err || !mc_dev->mc_io) { ++ dev_err(dev, "fsl_mc_portal_allocate error: %d\n", err); ++ err = -ENODEV; ++ goto err_free_netdev; ++ } ++ ++ err = dpmac_open(mc_dev->mc_io, 0, mc_dev->obj_desc.id, ++ &mc_dev->mc_handle); ++ if (err || !mc_dev->mc_handle) { ++ dev_err(dev, "dpmac_open error: %d\n", err); ++ err = -ENODEV; ++ goto err_free_mcp; ++ } ++ ++ err = dpmac_get_attributes(mc_dev->mc_io, 0, ++ mc_dev->mc_handle, &priv->attr); ++ if (err) { ++ dev_err(dev, "dpmac_get_attributes err %d\n", err); ++ err = -EINVAL; ++ goto err_close; ++ } ++ ++ dev_warn(dev, "Using DPMAC API %d.%d\n", ++ priv->attr.version.major, priv->attr.version.minor); ++ ++ /* Look up the DPMAC node in the device-tree. */ ++ dpmac_node = lookup_node(dev, priv->attr.id); ++ if (!dpmac_node) { ++ dev_err(dev, "No dpmac@%d subnode found.\n", priv->attr.id); ++ err = -ENODEV; ++ goto err_close; ++ } ++ ++ err = setup_irqs(mc_dev); ++ if (err) { ++ err = -EFAULT; ++ goto err_close; ++ } ++ ++#ifdef CONFIG_FSL_DPAA2_MAC_NETDEVS ++ /* OPTIONAL, register netdev just to make it visible to the user */ ++ netdev->netdev_ops = &dpaa2_mac_ndo_ops; ++ netdev->ethtool_ops = &dpaa2_mac_ethtool_ops; ++ ++ /* phy starts up enabled so netdev should be up too */ ++ netdev->flags |= IFF_UP; ++ ++ err = register_netdev(priv->netdev); ++ if (err < 0) { ++ dev_err(dev, "register_netdev error %d\n", err); ++ err = -ENODEV; ++ goto err_free_irq; ++ } ++#endif /* CONFIG_FSL_DPAA2_MAC_NETDEVS */ ++ ++ /* probe the PHY as a fixed-link if the link type declared in DPC ++ * explicitly mandates this ++ */ ++ if (priv->attr.link_type == DPMAC_LINK_TYPE_FIXED) ++ goto probe_fixed_link; ++ ++ if (priv->attr.eth_if < ARRAY_SIZE(dpaa2_mac_iface_mode)) { ++ if_mode = dpaa2_mac_iface_mode[priv->attr.eth_if]; ++ dev_dbg(dev, "\tusing if mode %s for eth_if %d\n", ++ phy_modes(if_mode), priv->attr.eth_if); ++ } else { ++ dev_warn(dev, "Unexpected interface mode %d, will probe as fixed link\n", ++ priv->attr.eth_if); ++ goto probe_fixed_link; ++ } ++ ++ /* try to connect to the PHY */ ++ phy_node = of_parse_phandle(dpmac_node, "phy-handle", 0); ++ if (!phy_node) { ++ if (!phy_node) { ++ dev_err(dev, "dpmac node has no phy-handle property\n"); ++ err = -ENODEV; ++ goto err_no_phy; ++ } ++ } ++ netdev->phydev = of_phy_connect(netdev, phy_node, ++ &dpaa2_mac_link_changed, 0, if_mode); ++ if (!netdev->phydev) { ++ /* No need for dev_err(); the kernel's loud enough as it is. */ ++ dev_dbg(dev, "Can't of_phy_connect() now.\n"); ++ /* We might be waiting for the MDIO MUX to probe, so defer ++ * our own probing. ++ */ ++ err = -EPROBE_DEFER; ++ goto err_defer; ++ } ++ dev_info(dev, "Connected to %s PHY.\n", phy_modes(if_mode)); ++ ++probe_fixed_link: ++ if (!netdev->phydev) { ++ struct fixed_phy_status status = { ++ .link = 1, ++ /* fixed-phys don't support 10Gbps speed for now */ ++ .speed = 1000, ++ .duplex = 1, ++ }; ++ ++ /* try to register a fixed link phy */ ++ netdev->phydev = fixed_phy_register(PHY_POLL, &status, NULL); ++ if (!netdev->phydev || IS_ERR(netdev->phydev)) { ++ dev_err(dev, "error trying to register fixed PHY\n"); ++ /* So we don't crash unregister_netdev() later on */ ++ netdev->phydev = NULL; ++ err = -EFAULT; ++ goto err_no_phy; ++ } ++ dev_info(dev, "Registered fixed PHY.\n"); ++ } ++ ++ /* start PHY state machine */ ++#ifdef CONFIG_FSL_DPAA2_MAC_NETDEVS ++ dpaa2_mac_open(netdev); ++#else /* CONFIG_FSL_DPAA2_MAC_NETDEVS */ ++ phy_start(netdev->phydev); ++#endif /* CONFIG_FSL_DPAA2_MAC_NETDEVS */ ++ return 0; ++ ++err_defer: ++err_no_phy: ++#ifdef CONFIG_FSL_DPAA2_MAC_NETDEVS ++ unregister_netdev(netdev); ++err_free_irq: ++#endif ++ teardown_irqs(mc_dev); ++err_close: ++ dpmac_close(mc_dev->mc_io, 0, mc_dev->mc_handle); ++err_free_mcp: ++ fsl_mc_portal_free(mc_dev->mc_io); ++err_free_netdev: ++ free_netdev(netdev); ++err_exit: ++ return err; ++} ++ ++static int dpaa2_mac_remove(struct fsl_mc_device *mc_dev) ++{ ++ struct device *dev = &mc_dev->dev; ++ struct dpaa2_mac_priv *priv = dev_get_drvdata(dev); ++ ++#ifdef CONFIG_FSL_DPAA2_MAC_NETDEVS ++ unregister_netdev(priv->netdev); ++#endif ++ teardown_irqs(priv->mc_dev); ++ dpmac_close(priv->mc_dev->mc_io, 0, priv->mc_dev->mc_handle); ++ fsl_mc_portal_free(priv->mc_dev->mc_io); ++ free_netdev(priv->netdev); ++ ++ dev_set_drvdata(dev, NULL); ++ kfree(priv); ++ ++ return 0; ++} ++ ++static const struct fsl_mc_device_match_id dpaa2_mac_match_id_table[] = { ++ { ++ .vendor = FSL_MC_VENDOR_FREESCALE, ++ .obj_type = "dpmac", ++ .ver_major = DPMAC_VER_MAJOR, ++ .ver_minor = DPMAC_VER_MINOR, ++ }, ++ {} ++}; ++ ++static struct fsl_mc_driver dpaa2_mac_drv = { ++ .driver = { ++ .name = KBUILD_MODNAME, ++ .owner = THIS_MODULE, ++ }, ++ .probe = dpaa2_mac_probe, ++ .remove = dpaa2_mac_remove, ++ .match_id_table = dpaa2_mac_match_id_table, ++}; ++ ++module_fsl_mc_driver(dpaa2_mac_drv); ++ ++MODULE_LICENSE("GPL"); ++MODULE_DESCRIPTION("DPAA2 PHY proxy interface driver"); +diff --git a/drivers/staging/fsl-mc/Kconfig b/drivers/staging/fsl-mc/Kconfig +new file mode 100644 +index 0000000..32df07b +--- /dev/null ++++ b/drivers/staging/fsl-mc/Kconfig +@@ -0,0 +1 @@ ++source "drivers/staging/fsl-mc/bus/Kconfig" +diff --git a/drivers/staging/fsl-mc/Makefile b/drivers/staging/fsl-mc/Makefile +new file mode 100644 +index 0000000..9c6a001 +--- /dev/null ++++ b/drivers/staging/fsl-mc/Makefile +@@ -0,0 +1,2 @@ ++# Freescale Management Complex (MC) bus drivers ++obj-$(CONFIG_FSL_MC_BUS) += bus/ +diff --git a/drivers/staging/fsl-mc/TODO b/drivers/staging/fsl-mc/TODO +new file mode 100644 +index 0000000..d78288b +--- /dev/null ++++ b/drivers/staging/fsl-mc/TODO +@@ -0,0 +1,13 @@ ++* Add README file (with ASCII art) describing relationships between ++ DPAA2 objects and how combine them to make a NIC, an LS2 switch, etc. ++ Also, define all acronyms used. ++ ++* Decide if multiple root fsl-mc buses will be supported per Linux instance, ++ and if so add support for this. ++ ++* Add at least one device driver for a DPAA2 object (child device of the ++ fsl-mc bus). ++ ++Please send any patches to Greg Kroah-Hartman , ++german.rivera@freescale.com, devel@driverdev.osuosl.org, ++linux-kernel@vger.kernel.org +diff --git a/drivers/staging/fsl-mc/bus/Kconfig b/drivers/staging/fsl-mc/bus/Kconfig +new file mode 100644 +index 0000000..8bef5b8 +--- /dev/null ++++ b/drivers/staging/fsl-mc/bus/Kconfig +@@ -0,0 +1,45 @@ ++# ++# Freescale Management Complex (MC) bus drivers ++# ++# Copyright (C) 2014 Freescale Semiconductor, Inc. ++# ++# This file is released under the GPLv2 ++# ++ ++config FSL_MC_BUS ++ tristate "Freescale Management Complex (MC) bus driver" ++ depends on OF && ARM64 ++ help ++ Driver to enable the bus infrastructure for the Freescale ++ QorIQ Management Complex (fsl-mc). The fsl-mc is a hardware ++ module of the QorIQ LS2 SoCs, that does resource management ++ for hardware building-blocks in the SoC that can be used ++ to dynamically create networking hardware objects such as ++ network interfaces (NICs), crypto accelerator instances, ++ or L2 switches. ++ ++ Only enable this option when building the kernel for ++ Freescale QorQIQ LS2xxxx SoCs. ++ ++config FSL_MC_RESTOOL ++ tristate "Freescale Management Complex (MC) restool driver" ++ depends on FSL_MC_BUS ++ help ++ Driver that provides kernel support for the Freescale Management ++ Complex resource manager user-space tool. ++ ++config FSL_MC_DPIO ++ tristate "Freescale Data Path I/O (DPIO) driver" ++ depends on FSL_MC_BUS ++ help ++ Driver for Freescale Data Path I/O (DPIO) devices. ++ A DPIO device provides queue and buffer management facilities ++ for software to interact with other Data Path devices. This ++ driver does not expose the DPIO device individually, but ++ groups them under a service layer API. ++ ++config FSL_QBMAN_DEBUG ++ tristate "Freescale QBMAN Debug APIs" ++ depends on FSL_MC_DPIO ++ help ++ QBMan debug assistant APIs. +diff --git a/drivers/staging/fsl-mc/bus/Makefile b/drivers/staging/fsl-mc/bus/Makefile +new file mode 100644 +index 0000000..f29399c +--- /dev/null ++++ b/drivers/staging/fsl-mc/bus/Makefile +@@ -0,0 +1,24 @@ ++# ++# Freescale Management Complex (MC) bus drivers ++# ++# Copyright (C) 2014 Freescale Semiconductor, Inc. ++# ++# This file is released under the GPLv2 ++# ++obj-$(CONFIG_FSL_MC_BUS) += mc-bus-driver.o ++ ++mc-bus-driver-objs := mc-bus.o \ ++ mc-sys.o \ ++ dprc.o \ ++ dpmng.o \ ++ dprc-driver.o \ ++ mc-allocator.o \ ++ dpmcp.o \ ++ dpbp.o \ ++ dpcon.o ++ ++# MC restool kernel support ++obj-$(CONFIG_FSL_MC_RESTOOL) += mc-restool.o ++ ++# MC DPIO driver ++obj-$(CONFIG_FSL_MC_DPIO) += dpio/ +diff --git a/drivers/staging/fsl-mc/bus/dpbp.c b/drivers/staging/fsl-mc/bus/dpbp.c +new file mode 100644 +index 0000000..f183121 +--- /dev/null ++++ b/drivers/staging/fsl-mc/bus/dpbp.c +@@ -0,0 +1,459 @@ ++/* Copyright 2013-2014 Freescale Semiconductor Inc. ++* ++* Redistribution and use in source and binary forms, with or without ++* modification, are permitted provided that the following conditions are met: ++* * Redistributions of source code must retain the above copyright ++* notice, this list of conditions and the following disclaimer. ++* * Redistributions in binary form must reproduce the above copyright ++* notice, this list of conditions and the following disclaimer in the ++* documentation and/or other materials provided with the distribution. ++* * Neither the name of the above-listed copyright holders nor the ++* names of any contributors may be used to endorse or promote products ++* derived from this software without specific prior written permission. ++* ++* ++* ALTERNATIVELY, this software may be distributed under the terms of the ++* GNU General Public License ("GPL") as published by the Free Software ++* Foundation, either version 2 of that License or (at your option) any ++* later version. ++* ++* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE ++* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++* POSSIBILITY OF SUCH DAMAGE. ++*/ ++#include "../include/mc-sys.h" ++#include "../include/mc-cmd.h" ++#include "../include/dpbp.h" ++#include "../include/dpbp-cmd.h" ++ ++int dpbp_open(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ int dpbp_id, ++ uint16_t *token) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPBP_CMDID_OPEN, ++ cmd_flags, ++ 0); ++ ++ cmd.params[0] |= mc_enc(0, 32, dpbp_id); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ *token = MC_CMD_HDR_READ_TOKEN(cmd.header); ++ ++ return err; ++} ++EXPORT_SYMBOL(dpbp_open); ++ ++int dpbp_close(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPBP_CMDID_CLOSE, cmd_flags, ++ token); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++EXPORT_SYMBOL(dpbp_close); ++ ++int dpbp_create(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ const struct dpbp_cfg *cfg, ++ uint16_t *token) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ (void)(cfg); /* unused */ ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPBP_CMDID_CREATE, ++ cmd_flags, ++ 0); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ *token = MC_CMD_HDR_READ_TOKEN(cmd.header); ++ ++ return 0; ++} ++ ++int dpbp_destroy(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPBP_CMDID_DESTROY, ++ cmd_flags, ++ token); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpbp_enable(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPBP_CMDID_ENABLE, cmd_flags, ++ token); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++EXPORT_SYMBOL(dpbp_enable); ++ ++int dpbp_disable(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPBP_CMDID_DISABLE, ++ cmd_flags, ++ token); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++EXPORT_SYMBOL(dpbp_disable); ++ ++int dpbp_is_enabled(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int *en) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPBP_CMDID_IS_ENABLED, cmd_flags, ++ token); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ *en = (int)mc_dec(cmd.params[0], 0, 1); ++ ++ return 0; ++} ++ ++int dpbp_reset(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPBP_CMDID_RESET, ++ cmd_flags, ++ token); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpbp_set_irq(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ struct dpbp_irq_cfg *irq_cfg) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPBP_CMDID_SET_IRQ, ++ cmd_flags, ++ token); ++ ++ cmd.params[0] |= mc_enc(0, 8, irq_index); ++ cmd.params[0] |= mc_enc(32, 32, irq_cfg->val); ++ cmd.params[1] |= mc_enc(0, 64, irq_cfg->addr); ++ cmd.params[2] |= mc_enc(0, 32, irq_cfg->irq_num); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpbp_get_irq(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ int *type, ++ struct dpbp_irq_cfg *irq_cfg) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPBP_CMDID_GET_IRQ, ++ cmd_flags, ++ token); ++ ++ cmd.params[0] |= mc_enc(32, 8, irq_index); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ irq_cfg->val = (uint32_t)mc_dec(cmd.params[0], 0, 32); ++ irq_cfg->addr = (uint64_t)mc_dec(cmd.params[1], 0, 64); ++ irq_cfg->irq_num = (int)mc_dec(cmd.params[2], 0, 32); ++ *type = (int)mc_dec(cmd.params[2], 32, 32); ++ ++ return 0; ++} ++ ++int dpbp_set_irq_enable(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint8_t en) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPBP_CMDID_SET_IRQ_ENABLE, ++ cmd_flags, ++ token); ++ ++ cmd.params[0] |= mc_enc(0, 8, en); ++ cmd.params[0] |= mc_enc(32, 8, irq_index); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpbp_get_irq_enable(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint8_t *en) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPBP_CMDID_GET_IRQ_ENABLE, ++ cmd_flags, ++ token); ++ ++ cmd.params[0] |= mc_enc(32, 8, irq_index); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ *en = (uint8_t)mc_dec(cmd.params[0], 0, 8); ++ return 0; ++} ++ ++int dpbp_set_irq_mask(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint32_t mask) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPBP_CMDID_SET_IRQ_MASK, ++ cmd_flags, ++ token); ++ ++ cmd.params[0] |= mc_enc(0, 32, mask); ++ cmd.params[0] |= mc_enc(32, 8, irq_index); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpbp_get_irq_mask(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint32_t *mask) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPBP_CMDID_GET_IRQ_MASK, ++ cmd_flags, ++ token); ++ ++ cmd.params[0] |= mc_enc(32, 8, irq_index); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ *mask = (uint32_t)mc_dec(cmd.params[0], 0, 32); ++ return 0; ++} ++ ++int dpbp_get_irq_status(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint32_t *status) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPBP_CMDID_GET_IRQ_STATUS, ++ cmd_flags, ++ token); ++ ++ cmd.params[0] |= mc_enc(0, 32, *status); ++ cmd.params[0] |= mc_enc(32, 8, irq_index); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ *status = (uint32_t)mc_dec(cmd.params[0], 0, 32); ++ return 0; ++} ++ ++int dpbp_clear_irq_status(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint32_t status) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPBP_CMDID_CLEAR_IRQ_STATUS, ++ cmd_flags, ++ token); ++ ++ cmd.params[0] |= mc_enc(0, 32, status); ++ cmd.params[0] |= mc_enc(32, 8, irq_index); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpbp_get_attributes(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ struct dpbp_attr *attr) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPBP_CMDID_GET_ATTR, ++ cmd_flags, ++ token); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ attr->bpid = (uint16_t)mc_dec(cmd.params[0], 16, 16); ++ attr->id = (int)mc_dec(cmd.params[0], 32, 32); ++ attr->version.major = (uint16_t)mc_dec(cmd.params[1], 0, 16); ++ attr->version.minor = (uint16_t)mc_dec(cmd.params[1], 16, 16); ++ return 0; ++} ++EXPORT_SYMBOL(dpbp_get_attributes); ++ ++int dpbp_set_notifications(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ struct dpbp_notification_cfg *cfg) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPBP_CMDID_SET_NOTIFICATIONS, ++ cmd_flags, ++ token); ++ ++ cmd.params[0] |= mc_enc(0, 32, cfg->depletion_entry); ++ cmd.params[0] |= mc_enc(32, 32, cfg->depletion_exit); ++ cmd.params[1] |= mc_enc(0, 32, cfg->surplus_entry); ++ cmd.params[1] |= mc_enc(32, 32, cfg->surplus_exit); ++ cmd.params[2] |= mc_enc(0, 16, cfg->options); ++ cmd.params[3] |= mc_enc(0, 64, cfg->message_ctx); ++ cmd.params[4] |= mc_enc(0, 64, cfg->message_iova); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpbp_get_notifications(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ struct dpbp_notification_cfg *cfg) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPBP_CMDID_GET_NOTIFICATIONS, ++ cmd_flags, ++ token); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ cfg->depletion_entry = (uint32_t)mc_dec(cmd.params[0], 0, 32); ++ cfg->depletion_exit = (uint32_t)mc_dec(cmd.params[0], 32, 32); ++ cfg->surplus_entry = (uint32_t)mc_dec(cmd.params[1], 0, 32); ++ cfg->surplus_exit = (uint32_t)mc_dec(cmd.params[1], 32, 32); ++ cfg->options = (uint16_t)mc_dec(cmd.params[2], 0, 16); ++ cfg->message_ctx = (uint64_t)mc_dec(cmd.params[3], 0, 64); ++ cfg->message_iova = (uint64_t)mc_dec(cmd.params[4], 0, 64); ++ ++ return 0; ++} +diff --git a/drivers/staging/fsl-mc/bus/dpcon.c b/drivers/staging/fsl-mc/bus/dpcon.c +new file mode 100644 +index 0000000..7965284 +--- /dev/null ++++ b/drivers/staging/fsl-mc/bus/dpcon.c +@@ -0,0 +1,407 @@ ++/* Copyright 2013-2015 Freescale Semiconductor Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * * Neither the name of the above-listed copyright holders nor the ++ * names of any contributors may be used to endorse or promote products ++ * derived from this software without specific prior written permission. ++ * ++ * ++ * ALTERNATIVELY, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") as published by the Free Software ++ * Foundation, either version 2 of that License or (at your option) any ++ * later version. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE ++ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++#include "../include/mc-sys.h" ++#include "../include/mc-cmd.h" ++#include "../include/dpcon.h" ++#include "../include/dpcon-cmd.h" ++ ++int dpcon_open(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ int dpcon_id, ++ uint16_t *token) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPCON_CMDID_OPEN, ++ cmd_flags, ++ 0); ++ DPCON_CMD_OPEN(cmd, dpcon_id); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ *token = MC_CMD_HDR_READ_TOKEN(cmd.header); ++ ++ return 0; ++} ++EXPORT_SYMBOL(dpcon_open); ++ ++int dpcon_close(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPCON_CMDID_CLOSE, ++ cmd_flags, ++ token); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++EXPORT_SYMBOL(dpcon_close); ++ ++int dpcon_create(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ const struct dpcon_cfg *cfg, ++ uint16_t *token) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPCON_CMDID_CREATE, ++ cmd_flags, ++ 0); ++ DPCON_CMD_CREATE(cmd, cfg); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ *token = MC_CMD_HDR_READ_TOKEN(cmd.header); ++ ++ return 0; ++} ++ ++int dpcon_destroy(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPCON_CMDID_DESTROY, ++ cmd_flags, ++ token); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpcon_enable(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPCON_CMDID_ENABLE, ++ cmd_flags, ++ token); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++EXPORT_SYMBOL(dpcon_enable); ++ ++int dpcon_disable(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPCON_CMDID_DISABLE, ++ cmd_flags, ++ token); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++EXPORT_SYMBOL(dpcon_disable); ++ ++int dpcon_is_enabled(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int *en) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPCON_CMDID_IS_ENABLED, ++ cmd_flags, ++ token); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ DPCON_RSP_IS_ENABLED(cmd, *en); ++ ++ return 0; ++} ++ ++int dpcon_reset(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPCON_CMDID_RESET, ++ cmd_flags, token); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpcon_set_irq(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ struct dpcon_irq_cfg *irq_cfg) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPCON_CMDID_SET_IRQ, ++ cmd_flags, ++ token); ++ DPCON_CMD_SET_IRQ(cmd, irq_index, irq_cfg); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpcon_get_irq(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ int *type, ++ struct dpcon_irq_cfg *irq_cfg) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPCON_CMDID_GET_IRQ, ++ cmd_flags, ++ token); ++ DPCON_CMD_GET_IRQ(cmd, irq_index); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ DPCON_RSP_GET_IRQ(cmd, *type, irq_cfg); ++ ++ return 0; ++} ++ ++int dpcon_set_irq_enable(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint8_t en) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPCON_CMDID_SET_IRQ_ENABLE, ++ cmd_flags, ++ token); ++ DPCON_CMD_SET_IRQ_ENABLE(cmd, irq_index, en); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpcon_get_irq_enable(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint8_t *en) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPCON_CMDID_GET_IRQ_ENABLE, ++ cmd_flags, ++ token); ++ DPCON_CMD_GET_IRQ_ENABLE(cmd, irq_index); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ DPCON_RSP_GET_IRQ_ENABLE(cmd, *en); ++ ++ return 0; ++} ++ ++int dpcon_set_irq_mask(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint32_t mask) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPCON_CMDID_SET_IRQ_MASK, ++ cmd_flags, ++ token); ++ DPCON_CMD_SET_IRQ_MASK(cmd, irq_index, mask); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpcon_get_irq_mask(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint32_t *mask) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPCON_CMDID_GET_IRQ_MASK, ++ cmd_flags, ++ token); ++ DPCON_CMD_GET_IRQ_MASK(cmd, irq_index); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ DPCON_RSP_GET_IRQ_MASK(cmd, *mask); ++ ++ return 0; ++} ++ ++int dpcon_get_irq_status(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint32_t *status) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPCON_CMDID_GET_IRQ_STATUS, ++ cmd_flags, ++ token); ++ DPCON_CMD_GET_IRQ_STATUS(cmd, irq_index, *status); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ DPCON_RSP_GET_IRQ_STATUS(cmd, *status); ++ ++ return 0; ++} ++ ++int dpcon_clear_irq_status(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint32_t status) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPCON_CMDID_CLEAR_IRQ_STATUS, ++ cmd_flags, ++ token); ++ DPCON_CMD_CLEAR_IRQ_STATUS(cmd, irq_index, status); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpcon_get_attributes(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ struct dpcon_attr *attr) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPCON_CMDID_GET_ATTR, ++ cmd_flags, ++ token); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ DPCON_RSP_GET_ATTR(cmd, attr); ++ ++ return 0; ++} ++EXPORT_SYMBOL(dpcon_get_attributes); ++ ++int dpcon_set_notification(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ struct dpcon_notification_cfg *cfg) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPCON_CMDID_SET_NOTIFICATION, ++ cmd_flags, ++ token); ++ DPCON_CMD_SET_NOTIFICATION(cmd, cfg); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++EXPORT_SYMBOL(dpcon_set_notification); ++ +diff --git a/drivers/staging/fsl-mc/bus/dpio/Makefile b/drivers/staging/fsl-mc/bus/dpio/Makefile +new file mode 100644 +index 0000000..c20356b +--- /dev/null ++++ b/drivers/staging/fsl-mc/bus/dpio/Makefile +@@ -0,0 +1,9 @@ ++# ++# Freescale DPIO driver ++# ++ ++obj-$(CONFIG_FSL_MC_BUS) += fsl-dpio-drv.o ++ ++fsl-dpio-drv-objs := dpio-drv.o dpio_service.o dpio.o qbman_portal.o ++ ++obj-$(CONFIG_FSL_QBMAN_DEBUG) += qbman_debug.o +diff --git a/drivers/staging/fsl-mc/bus/dpio/dpio-drv.c b/drivers/staging/fsl-mc/bus/dpio/dpio-drv.c +new file mode 100644 +index 0000000..80add27 +--- /dev/null ++++ b/drivers/staging/fsl-mc/bus/dpio/dpio-drv.c +@@ -0,0 +1,401 @@ ++/* Copyright 2014 Freescale Semiconductor Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * * Neither the name of Freescale Semiconductor nor the ++ * names of its contributors may be used to endorse or promote products ++ * derived from this software without specific prior written permission. ++ * ++ * ++ * ALTERNATIVELY, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") as published by the Free Software ++ * Foundation, either version 2 of that License or (at your option) any ++ * later version. ++ * ++ * THIS SOFTWARE IS PROVIDED BY Freescale Semiconductor ``AS IS'' AND ANY ++ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ++ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE ++ * DISCLAIMED. IN NO EVENT SHALL Freescale Semiconductor BE LIABLE FOR ANY ++ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ++ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; ++ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ++ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ++ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS ++ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "../../include/mc.h" ++#include "../../include/fsl_dpaa2_io.h" ++ ++#include "fsl_qbman_portal.h" ++#include "fsl_dpio.h" ++#include "fsl_dpio_cmd.h" ++ ++#include "dpio-drv.h" ++ ++#define DPIO_DESCRIPTION "DPIO Driver" ++ ++MODULE_LICENSE("Dual BSD/GPL"); ++MODULE_AUTHOR("Freescale Semiconductor, Inc"); ++MODULE_DESCRIPTION(DPIO_DESCRIPTION); ++ ++#define MAX_DPIO_IRQ_NAME 16 /* Big enough for "FSL DPIO %d" */ ++ ++struct dpio_priv { ++ struct dpaa2_io *io; ++ char irq_name[MAX_DPIO_IRQ_NAME]; ++ struct task_struct *thread; ++}; ++ ++static int dpio_thread(void *data) ++{ ++ struct dpaa2_io *io = data; ++ ++ while (!kthread_should_stop()) { ++ int err = dpaa2_io_poll(io); ++ ++ if (err) { ++ pr_err("dpaa2_io_poll() failed\n"); ++ return err; ++ } ++ msleep(50); ++ } ++ return 0; ++} ++ ++static irqreturn_t dpio_irq_handler(int irq_num, void *arg) ++{ ++ struct device *dev = (struct device *)arg; ++ struct dpio_priv *priv = dev_get_drvdata(dev); ++ ++ return dpaa2_io_irq(priv->io); ++} ++ ++static void unregister_dpio_irq_handlers(struct fsl_mc_device *ls_dev) ++{ ++ int i; ++ struct fsl_mc_device_irq *irq; ++ int irq_count = ls_dev->obj_desc.irq_count; ++ ++ for (i = 0; i < irq_count; i++) { ++ irq = ls_dev->irqs[i]; ++ devm_free_irq(&ls_dev->dev, irq->irq_number, &ls_dev->dev); ++ } ++} ++ ++static int register_dpio_irq_handlers(struct fsl_mc_device *ls_dev, int cpu) ++{ ++ struct dpio_priv *priv; ++ unsigned int i; ++ int error; ++ struct fsl_mc_device_irq *irq; ++ unsigned int num_irq_handlers_registered = 0; ++ int irq_count = ls_dev->obj_desc.irq_count; ++ cpumask_t mask; ++ ++ priv = dev_get_drvdata(&ls_dev->dev); ++ ++ if (WARN_ON(irq_count != 1)) ++ return -EINVAL; ++ ++ for (i = 0; i < irq_count; i++) { ++ irq = ls_dev->irqs[i]; ++ error = devm_request_irq(&ls_dev->dev, ++ irq->irq_number, ++ dpio_irq_handler, ++ 0, ++ priv->irq_name, ++ &ls_dev->dev); ++ if (error < 0) { ++ dev_err(&ls_dev->dev, ++ "devm_request_irq() failed: %d\n", ++ error); ++ goto error_unregister_irq_handlers; ++ } ++ ++ /* Set the IRQ affinity */ ++ cpumask_clear(&mask); ++ cpumask_set_cpu(cpu, &mask); ++ if (irq_set_affinity(irq->irq_number, &mask)) ++ pr_err("irq_set_affinity failed irq %d cpu %d\n", ++ irq->irq_number, cpu); ++ ++ num_irq_handlers_registered++; ++ } ++ ++ return 0; ++ ++error_unregister_irq_handlers: ++ for (i = 0; i < num_irq_handlers_registered; i++) { ++ irq = ls_dev->irqs[i]; ++ devm_free_irq(&ls_dev->dev, irq->irq_number, ++ &ls_dev->dev); ++ } ++ ++ return error; ++} ++ ++static int __cold ++dpaa2_dpio_probe(struct fsl_mc_device *ls_dev) ++{ ++ struct dpio_attr dpio_attrs; ++ struct dpaa2_io_desc desc; ++ struct dpio_priv *priv; ++ int err = -ENOMEM; ++ struct device *dev = &ls_dev->dev; ++ struct dpaa2_io *defservice; ++ bool irq_allocated = false; ++ static int next_cpu; ++ ++ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); ++ if (!priv) ++ goto err_priv_alloc; ++ ++ dev_set_drvdata(dev, priv); ++ ++ err = fsl_mc_portal_allocate(ls_dev, 0, &ls_dev->mc_io); ++ if (err) { ++ dev_err(dev, "MC portal allocation failed\n"); ++ err = -EPROBE_DEFER; ++ goto err_mcportal; ++ } ++ ++ err = dpio_open(ls_dev->mc_io, 0, ls_dev->obj_desc.id, ++ &ls_dev->mc_handle); ++ if (err) { ++ dev_err(dev, "dpio_open() failed\n"); ++ goto err_open; ++ } ++ ++ err = dpio_get_attributes(ls_dev->mc_io, 0, ls_dev->mc_handle, ++ &dpio_attrs); ++ if (err) { ++ dev_err(dev, "dpio_get_attributes() failed %d\n", err); ++ goto err_get_attr; ++ } ++ err = dpio_enable(ls_dev->mc_io, 0, ls_dev->mc_handle); ++ if (err) { ++ dev_err(dev, "dpio_enable() failed %d\n", err); ++ goto err_get_attr; ++ } ++ pr_info("ce_paddr=0x%llx, ci_paddr=0x%llx, portalid=%d, prios=%d\n", ++ ls_dev->regions[0].start, ++ ls_dev->regions[1].start, ++ dpio_attrs.qbman_portal_id, ++ dpio_attrs.num_priorities); ++ ++ pr_info("ce_size=0x%llx, ci_size=0x%llx\n", ++ resource_size(&ls_dev->regions[0]), ++ resource_size(&ls_dev->regions[1])); ++ ++ desc.qman_version = dpio_attrs.qbman_version; ++ /* Build DPIO driver object out of raw MC object */ ++ desc.receives_notifications = dpio_attrs.num_priorities ? 1 : 0; ++ desc.has_irq = 1; ++ desc.will_poll = 1; ++ desc.has_8prio = dpio_attrs.num_priorities == 8 ? 1 : 0; ++ desc.cpu = next_cpu; ++ desc.stash_affinity = next_cpu; ++ next_cpu = (next_cpu + 1) % num_active_cpus(); ++ desc.dpio_id = ls_dev->obj_desc.id; ++ desc.regs_cena = ioremap_cache_ns(ls_dev->regions[0].start, ++ resource_size(&ls_dev->regions[0])); ++ desc.regs_cinh = ioremap(ls_dev->regions[1].start, ++ resource_size(&ls_dev->regions[1])); ++ ++ err = fsl_mc_allocate_irqs(ls_dev); ++ if (err) { ++ dev_err(dev, "DPIO fsl_mc_allocate_irqs failed\n"); ++ desc.has_irq = 0; ++ } else { ++ irq_allocated = true; ++ ++ snprintf(priv->irq_name, MAX_DPIO_IRQ_NAME, "FSL DPIO %d", ++ desc.dpio_id); ++ ++ err = register_dpio_irq_handlers(ls_dev, desc.cpu); ++ if (err) ++ desc.has_irq = 0; ++ } ++ ++ priv->io = dpaa2_io_create(&desc); ++ if (!priv->io) { ++ dev_err(dev, "DPIO setup failed\n"); ++ goto err_dpaa2_io_create; ++ } ++ ++ /* If no irq then go to poll mode */ ++ if (desc.has_irq == 0) { ++ dev_info(dev, "Using polling mode for DPIO %d\n", ++ desc.dpio_id); ++ /* goto err_register_dpio_irq; */ ++ /* TEMP: Start polling if IRQ could not ++ be registered. This will go away once ++ KVM support for MSI is present */ ++ if (irq_allocated == true) ++ fsl_mc_free_irqs(ls_dev); ++ ++ if (desc.stash_affinity) ++ priv->thread = kthread_create_on_cpu(dpio_thread, ++ priv->io, ++ desc.cpu, ++ "dpio_aff%u"); ++ else ++ priv->thread = ++ kthread_create(dpio_thread, ++ priv->io, ++ "dpio_non%u", ++ dpio_attrs.qbman_portal_id); ++ if (IS_ERR(priv->thread)) { ++ dev_err(dev, "DPIO thread failure\n"); ++ err = PTR_ERR(priv->thread); ++ goto err_dpaa_thread; ++ } ++ wake_up_process(priv->thread); ++ } ++ ++ defservice = dpaa2_io_default_service(); ++ err = dpaa2_io_service_add(defservice, priv->io); ++ dpaa2_io_down(defservice); ++ if (err) { ++ dev_err(dev, "DPIO add-to-service failed\n"); ++ goto err_dpaa2_io_add; ++ } ++ ++ dev_info(dev, "dpio: probed object %d\n", ls_dev->obj_desc.id); ++ dev_info(dev, " receives_notifications = %d\n", ++ desc.receives_notifications); ++ dev_info(dev, " has_irq = %d\n", desc.has_irq); ++ dpio_close(ls_dev->mc_io, 0, ls_dev->mc_handle); ++ fsl_mc_portal_free(ls_dev->mc_io); ++ return 0; ++ ++err_dpaa2_io_add: ++ unregister_dpio_irq_handlers(ls_dev); ++/* TEMP: To be restored once polling is removed ++ err_register_dpio_irq: ++ fsl_mc_free_irqs(ls_dev); ++*/ ++err_dpaa_thread: ++err_dpaa2_io_create: ++ dpio_disable(ls_dev->mc_io, 0, ls_dev->mc_handle); ++err_get_attr: ++ dpio_close(ls_dev->mc_io, 0, ls_dev->mc_handle); ++err_open: ++ fsl_mc_portal_free(ls_dev->mc_io); ++err_mcportal: ++ dev_set_drvdata(dev, NULL); ++ devm_kfree(dev, priv); ++err_priv_alloc: ++ return err; ++} ++ ++/* ++ * Tear down interrupts for a given DPIO object ++ */ ++static void dpio_teardown_irqs(struct fsl_mc_device *ls_dev) ++{ ++ /* (void)disable_dpio_irqs(ls_dev); */ ++ unregister_dpio_irq_handlers(ls_dev); ++ fsl_mc_free_irqs(ls_dev); ++} ++ ++static int __cold ++dpaa2_dpio_remove(struct fsl_mc_device *ls_dev) ++{ ++ struct device *dev; ++ struct dpio_priv *priv; ++ int err; ++ ++ dev = &ls_dev->dev; ++ priv = dev_get_drvdata(dev); ++ ++ /* there is no implementation yet for pulling a DPIO object out of a ++ * running service (and they're currently always running). ++ */ ++ dev_crit(dev, "DPIO unplugging is broken, the service holds onto it\n"); ++ ++ if (priv->thread) ++ kthread_stop(priv->thread); ++ else ++ dpio_teardown_irqs(ls_dev); ++ ++ err = fsl_mc_portal_allocate(ls_dev, 0, &ls_dev->mc_io); ++ if (err) { ++ dev_err(dev, "MC portal allocation failed\n"); ++ goto err_mcportal; ++ } ++ ++ err = dpio_open(ls_dev->mc_io, 0, ls_dev->obj_desc.id, ++ &ls_dev->mc_handle); ++ if (err) { ++ dev_err(dev, "dpio_open() failed\n"); ++ goto err_open; ++ } ++ ++ dev_set_drvdata(dev, NULL); ++ dpaa2_io_down(priv->io); ++ ++ err = 0; ++ ++ dpio_disable(ls_dev->mc_io, 0, ls_dev->mc_handle); ++ dpio_close(ls_dev->mc_io, 0, ls_dev->mc_handle); ++err_open: ++ fsl_mc_portal_free(ls_dev->mc_io); ++err_mcportal: ++ return err; ++} ++ ++static const struct fsl_mc_device_match_id dpaa2_dpio_match_id_table[] = { ++ { ++ .vendor = FSL_MC_VENDOR_FREESCALE, ++ .obj_type = "dpio", ++ .ver_major = DPIO_VER_MAJOR, ++ .ver_minor = DPIO_VER_MINOR ++ }, ++ { .vendor = 0x0 } ++}; ++ ++static struct fsl_mc_driver dpaa2_dpio_driver = { ++ .driver = { ++ .name = KBUILD_MODNAME, ++ .owner = THIS_MODULE, ++ }, ++ .probe = dpaa2_dpio_probe, ++ .remove = dpaa2_dpio_remove, ++ .match_id_table = dpaa2_dpio_match_id_table ++}; ++ ++static int dpio_driver_init(void) ++{ ++ int err; ++ ++ err = dpaa2_io_service_driver_init(); ++ if (!err) { ++ err = fsl_mc_driver_register(&dpaa2_dpio_driver); ++ if (err) ++ dpaa2_io_service_driver_exit(); ++ } ++ return err; ++} ++static void dpio_driver_exit(void) ++{ ++ fsl_mc_driver_unregister(&dpaa2_dpio_driver); ++ dpaa2_io_service_driver_exit(); ++} ++module_init(dpio_driver_init); ++module_exit(dpio_driver_exit); +diff --git a/drivers/staging/fsl-mc/bus/dpio/dpio-drv.h b/drivers/staging/fsl-mc/bus/dpio/dpio-drv.h +new file mode 100644 +index 0000000..fe8d40b +--- /dev/null ++++ b/drivers/staging/fsl-mc/bus/dpio/dpio-drv.h +@@ -0,0 +1,33 @@ ++/* Copyright 2014 Freescale Semiconductor Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * * Neither the name of Freescale Semiconductor nor the ++ * names of its contributors may be used to endorse or promote products ++ * derived from this software without specific prior written permission. ++ * ++ * ++ * ALTERNATIVELY, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") as published by the Free Software ++ * Foundation, either version 2 of that License or (at your option) any ++ * later version. ++ * ++ * THIS SOFTWARE IS PROVIDED BY Freescale Semiconductor ``AS IS'' AND ANY ++ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ++ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE ++ * DISCLAIMED. IN NO EVENT SHALL Freescale Semiconductor BE LIABLE FOR ANY ++ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ++ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; ++ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ++ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ++ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS ++ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++int dpaa2_io_service_driver_init(void); ++void dpaa2_io_service_driver_exit(void); +diff --git a/drivers/staging/fsl-mc/bus/dpio/dpio.c b/drivers/staging/fsl-mc/bus/dpio/dpio.c +new file mode 100644 +index 0000000..b63edd6 +--- /dev/null ++++ b/drivers/staging/fsl-mc/bus/dpio/dpio.c +@@ -0,0 +1,468 @@ ++/* Copyright 2013-2015 Freescale Semiconductor Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * * Neither the name of the above-listed copyright holders nor the ++ * names of any contributors may be used to endorse or promote products ++ * derived from this software without specific prior written permission. ++ * ++ * ++ * ALTERNATIVELY, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") as published by the Free Software ++ * Foundation, either version 2 of that License or (at your option) any ++ * later version. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE ++ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++#include "../../include/mc-sys.h" ++#include "../../include/mc-cmd.h" ++#include "fsl_dpio.h" ++#include "fsl_dpio_cmd.h" ++ ++int dpio_open(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ int dpio_id, ++ uint16_t *token) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPIO_CMDID_OPEN, ++ cmd_flags, ++ 0); ++ DPIO_CMD_OPEN(cmd, dpio_id); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ *token = MC_CMD_HDR_READ_TOKEN(cmd.header); ++ ++ return 0; ++} ++ ++int dpio_close(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPIO_CMDID_CLOSE, ++ cmd_flags, ++ token); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpio_create(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ const struct dpio_cfg *cfg, ++ uint16_t *token) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPIO_CMDID_CREATE, ++ cmd_flags, ++ 0); ++ DPIO_CMD_CREATE(cmd, cfg); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ *token = MC_CMD_HDR_READ_TOKEN(cmd.header); ++ ++ return 0; ++} ++ ++int dpio_destroy(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPIO_CMDID_DESTROY, ++ cmd_flags, ++ token); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpio_enable(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPIO_CMDID_ENABLE, ++ cmd_flags, ++ token); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpio_disable(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPIO_CMDID_DISABLE, ++ cmd_flags, ++ token); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpio_is_enabled(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int *en) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPIO_CMDID_IS_ENABLED, cmd_flags, ++ token); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ DPIO_RSP_IS_ENABLED(cmd, *en); ++ ++ return 0; ++} ++ ++int dpio_reset(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPIO_CMDID_RESET, ++ cmd_flags, ++ token); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpio_set_irq(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ struct dpio_irq_cfg *irq_cfg) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPIO_CMDID_SET_IRQ, ++ cmd_flags, ++ token); ++ DPIO_CMD_SET_IRQ(cmd, irq_index, irq_cfg); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpio_get_irq(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ int *type, ++ struct dpio_irq_cfg *irq_cfg) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPIO_CMDID_GET_IRQ, ++ cmd_flags, ++ token); ++ DPIO_CMD_GET_IRQ(cmd, irq_index); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ DPIO_RSP_GET_IRQ(cmd, *type, irq_cfg); ++ ++ return 0; ++} ++ ++int dpio_set_irq_enable(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint8_t en) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPIO_CMDID_SET_IRQ_ENABLE, ++ cmd_flags, ++ token); ++ DPIO_CMD_SET_IRQ_ENABLE(cmd, irq_index, en); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpio_get_irq_enable(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint8_t *en) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPIO_CMDID_GET_IRQ_ENABLE, ++ cmd_flags, ++ token); ++ DPIO_CMD_GET_IRQ_ENABLE(cmd, irq_index); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ DPIO_RSP_GET_IRQ_ENABLE(cmd, *en); ++ ++ return 0; ++} ++ ++int dpio_set_irq_mask(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint32_t mask) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPIO_CMDID_SET_IRQ_MASK, ++ cmd_flags, ++ token); ++ DPIO_CMD_SET_IRQ_MASK(cmd, irq_index, mask); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpio_get_irq_mask(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint32_t *mask) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPIO_CMDID_GET_IRQ_MASK, ++ cmd_flags, ++ token); ++ DPIO_CMD_GET_IRQ_MASK(cmd, irq_index); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ DPIO_RSP_GET_IRQ_MASK(cmd, *mask); ++ ++ return 0; ++} ++ ++int dpio_get_irq_status(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint32_t *status) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPIO_CMDID_GET_IRQ_STATUS, ++ cmd_flags, ++ token); ++ DPIO_CMD_GET_IRQ_STATUS(cmd, irq_index, *status); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ DPIO_RSP_GET_IRQ_STATUS(cmd, *status); ++ ++ return 0; ++} ++ ++int dpio_clear_irq_status(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint32_t status) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPIO_CMDID_CLEAR_IRQ_STATUS, ++ cmd_flags, ++ token); ++ DPIO_CMD_CLEAR_IRQ_STATUS(cmd, irq_index, status); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpio_get_attributes(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ struct dpio_attr *attr) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPIO_CMDID_GET_ATTR, ++ cmd_flags, ++ token); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ DPIO_RSP_GET_ATTR(cmd, attr); ++ ++ return 0; ++} ++ ++int dpio_set_stashing_destination(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t sdest) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPIO_CMDID_SET_STASHING_DEST, ++ cmd_flags, ++ token); ++ DPIO_CMD_SET_STASHING_DEST(cmd, sdest); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpio_get_stashing_destination(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t *sdest) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPIO_CMDID_GET_STASHING_DEST, ++ cmd_flags, ++ token); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ DPIO_RSP_GET_STASHING_DEST(cmd, *sdest); ++ ++ return 0; ++} ++ ++int dpio_add_static_dequeue_channel(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int dpcon_id, ++ uint8_t *channel_index) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPIO_CMDID_ADD_STATIC_DEQUEUE_CHANNEL, ++ cmd_flags, ++ token); ++ DPIO_CMD_ADD_STATIC_DEQUEUE_CHANNEL(cmd, dpcon_id); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ DPIO_RSP_ADD_STATIC_DEQUEUE_CHANNEL(cmd, *channel_index); ++ ++ return 0; ++} ++ ++int dpio_remove_static_dequeue_channel(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int dpcon_id) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header( ++ DPIO_CMDID_REMOVE_STATIC_DEQUEUE_CHANNEL, ++ cmd_flags, ++ token); ++ DPIO_CMD_REMOVE_STATIC_DEQUEUE_CHANNEL(cmd, dpcon_id); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} +diff --git a/drivers/staging/fsl-mc/bus/dpio/dpio_service.c b/drivers/staging/fsl-mc/bus/dpio/dpio_service.c +new file mode 100644 +index 0000000..ebcfd59 +--- /dev/null ++++ b/drivers/staging/fsl-mc/bus/dpio/dpio_service.c +@@ -0,0 +1,801 @@ ++/* Copyright 2014 Freescale Semiconductor Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * * Neither the name of Freescale Semiconductor nor the ++ * names of its contributors may be used to endorse or promote products ++ * derived from this software without specific prior written permission. ++ * ++ * ++ * ALTERNATIVELY, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") as published by the Free Software ++ * Foundation, either version 2 of that License or (at your option) any ++ * later version. ++ * ++ * THIS SOFTWARE IS PROVIDED BY Freescale Semiconductor ``AS IS'' AND ANY ++ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ++ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE ++ * DISCLAIMED. IN NO EVENT SHALL Freescale Semiconductor BE LIABLE FOR ANY ++ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ++ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; ++ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ++ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ++ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS ++ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ++ */ ++#include ++#include "fsl_qbman_portal.h" ++#include "../../include/mc.h" ++#include "../../include/fsl_dpaa2_io.h" ++#include "fsl_dpio.h" ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "dpio-drv.h" ++#include "qbman_debug.h" ++ ++#define UNIMPLEMENTED() pr_err("FOO: %s unimplemented!\n", __func__) ++ ++#define MAGIC_SERVICE 0xabcd9876 ++#define MAGIC_OBJECT 0x1234fedc ++ ++struct dpaa2_io { ++ /* If MAGIC_SERVICE, this is a group of objects, use the 'service' part ++ * of the union. If MAGIC_OBJECT, use the 'object' part of the union. If ++ * it's neither, something got corrupted. This is mainly to satisfy ++ * dpaa2_io_from_registration(), which dereferences a caller- ++ * instantiated struct and so warrants a bug-checking step - hence the ++ * magic rather than a boolean. ++ */ ++ unsigned int magic; ++ atomic_t refs; ++ union { ++ struct dpaa2_io_service { ++ spinlock_t lock; ++ struct list_head list; ++ /* for targeted dpaa2_io selection */ ++ struct dpaa2_io *objects_by_cpu[NR_CPUS]; ++ cpumask_t cpus_notifications; ++ cpumask_t cpus_stashing; ++ int has_nonaffine; ++ /* slight hack. record the special case of the ++ * "default service", because that's the case where we ++ * need to avoid a kfree() ... */ ++ int is_defservice; ++ } service; ++ struct dpaa2_io_object { ++ struct dpaa2_io_desc dpio_desc; ++ struct qbman_swp_desc swp_desc; ++ struct qbman_swp *swp; ++ /* If the object is part of a service, this is it (and ++ * 'node' is linked into the service's list) */ ++ struct dpaa2_io *service; ++ struct list_head node; ++ /* Interrupt mask, as used with ++ * qbman_swp_interrupt_[gs]et_vanish(). This isn't ++ * locked, because the higher layer is driving all ++ * "ingress" processing. */ ++ uint32_t irq_mask; ++ /* As part of simplifying assumptions, we provide an ++ * irq-safe lock for each type of DPIO operation that ++ * isn't innately lockless. The selection algorithms ++ * (which are simplified) require this, whereas ++ * eventually adherence to cpu-affinity will presumably ++ * relax the locking requirements. */ ++ spinlock_t lock_mgmt_cmd; ++ spinlock_t lock_notifications; ++ struct list_head notifications; ++ } object; ++ }; ++}; ++ ++struct dpaa2_io_store { ++ unsigned int max; ++ dma_addr_t paddr; ++ struct dpaa2_dq *vaddr; ++ void *alloced_addr; /* the actual return from kmalloc as it may ++ be adjusted for alignment purposes */ ++ unsigned int idx; /* position of the next-to-be-returned entry */ ++ struct qbman_swp *swp; /* portal used to issue VDQCR */ ++ struct device *dev; /* device used for DMA mapping */ ++}; ++ ++static struct dpaa2_io def_serv; ++ ++/**********************/ ++/* Internal functions */ ++/**********************/ ++ ++static void service_init(struct dpaa2_io *d, int is_defservice) ++{ ++ struct dpaa2_io_service *s = &d->service; ++ ++ d->magic = MAGIC_SERVICE; ++ atomic_set(&d->refs, 1); ++ spin_lock_init(&s->lock); ++ INIT_LIST_HEAD(&s->list); ++ cpumask_clear(&s->cpus_notifications); ++ cpumask_clear(&s->cpus_stashing); ++ s->has_nonaffine = 0; ++ s->is_defservice = is_defservice; ++} ++ ++/* Selection algorithms, stupid ones at that. These are to handle the case where ++ * the given dpaa2_io is a service, by choosing the non-service dpaa2_io within ++ * it to use. ++ */ ++static struct dpaa2_io *_service_select_by_cpu_slow(struct dpaa2_io_service *ss, ++ int cpu) ++{ ++ struct dpaa2_io *o; ++ unsigned long irqflags; ++ ++ spin_lock_irqsave(&ss->lock, irqflags); ++ /* TODO: this is about the dumbest and slowest selection algorithm you ++ * could imagine. (We're looking for something working first, and ++ * something efficient second...) ++ */ ++ list_for_each_entry(o, &ss->list, object.node) ++ if (o->object.dpio_desc.cpu == cpu) ++ goto found; ++ ++ /* No joy. Try the first nonaffine portal (bleurgh) */ ++ if (ss->has_nonaffine) ++ list_for_each_entry(o, &ss->list, object.node) ++ if (!o->object.dpio_desc.stash_affinity) ++ goto found; ++ ++ /* No joy. Try the first object. Told you it was horrible. */ ++ if (!list_empty(&ss->list)) ++ o = list_entry(ss->list.next, struct dpaa2_io, object.node); ++ else ++ o = NULL; ++ ++found: ++ spin_unlock_irqrestore(&ss->lock, irqflags); ++ return o; ++} ++ ++static struct dpaa2_io *service_select_by_cpu(struct dpaa2_io *d, int cpu) ++{ ++ struct dpaa2_io_service *ss; ++ unsigned long irqflags; ++ ++ if (!d) ++ d = &def_serv; ++ else if (d->magic == MAGIC_OBJECT) ++ return d; ++ BUG_ON(d->magic != MAGIC_SERVICE); ++ ++ ss = &d->service; ++ ++ /* If cpu==-1, choose the current cpu, with no guarantees about ++ * potentially being migrated away. ++ */ ++ if (unlikely(cpu < 0)) { ++ spin_lock_irqsave(&ss->lock, irqflags); ++ cpu = smp_processor_id(); ++ spin_unlock_irqrestore(&ss->lock, irqflags); ++ ++ return _service_select_by_cpu_slow(ss, cpu); ++ } ++ ++ /* If a specific cpu was requested, pick it up immediately */ ++ return ss->objects_by_cpu[cpu]; ++} ++ ++static inline struct dpaa2_io *service_select_any(struct dpaa2_io *d) ++{ ++ struct dpaa2_io_service *ss; ++ struct dpaa2_io *o; ++ unsigned long irqflags; ++ ++ if (!d) ++ d = &def_serv; ++ else if (d->magic == MAGIC_OBJECT) ++ return d; ++ BUG_ON(d->magic != MAGIC_SERVICE); ++ ++ /* ++ * Lock the service, looking for the first DPIO object in the list, ++ * ignore everything else about that DPIO, and choose it to do the ++ * operation! As a post-selection step, move the DPIO to the end of ++ * the list. It should improve load-balancing a little, although it ++ * might also incur a performance hit, given that the lock is *global* ++ * and this may be called on the fast-path... ++ */ ++ ss = &d->service; ++ spin_lock_irqsave(&ss->lock, irqflags); ++ if (!list_empty(&ss->list)) { ++ o = list_entry(ss->list.next, struct dpaa2_io, object.node); ++ list_del(&o->object.node); ++ list_add_tail(&o->object.node, &ss->list); ++ } else ++ o = NULL; ++ spin_unlock_irqrestore(&ss->lock, irqflags); ++ return o; ++} ++ ++/* If the context is not preemptible, select the service affine to the ++ * current cpu. Otherwise, "select any". ++ */ ++static inline struct dpaa2_io *_service_select(struct dpaa2_io *d) ++{ ++ struct dpaa2_io *temp = d; ++ ++ if (likely(!preemptible())) { ++ d = service_select_by_cpu(d, smp_processor_id()); ++ if (likely(d)) ++ return d; ++ } ++ return service_select_any(temp); ++} ++ ++/**********************/ ++/* Exported functions */ ++/**********************/ ++ ++struct dpaa2_io *dpaa2_io_create(const struct dpaa2_io_desc *desc) ++{ ++ struct dpaa2_io *ret = kmalloc(sizeof(*ret), GFP_KERNEL); ++ struct dpaa2_io_object *o = &ret->object; ++ ++ if (!ret) ++ return NULL; ++ ret->magic = MAGIC_OBJECT; ++ atomic_set(&ret->refs, 1); ++ o->dpio_desc = *desc; ++ o->swp_desc.cena_bar = o->dpio_desc.regs_cena; ++ o->swp_desc.cinh_bar = o->dpio_desc.regs_cinh; ++ o->swp_desc.qman_version = o->dpio_desc.qman_version; ++ o->swp = qbman_swp_init(&o->swp_desc); ++ o->service = NULL; ++ if (!o->swp) { ++ kfree(ret); ++ return NULL; ++ } ++ INIT_LIST_HEAD(&o->node); ++ spin_lock_init(&o->lock_mgmt_cmd); ++ spin_lock_init(&o->lock_notifications); ++ INIT_LIST_HEAD(&o->notifications); ++ if (!o->dpio_desc.has_irq) ++ qbman_swp_interrupt_set_vanish(o->swp, 0xffffffff); ++ else { ++ /* For now only enable DQRR interrupts */ ++ qbman_swp_interrupt_set_trigger(o->swp, ++ QBMAN_SWP_INTERRUPT_DQRI); ++ } ++ qbman_swp_interrupt_clear_status(o->swp, 0xffffffff); ++ if (o->dpio_desc.receives_notifications) ++ qbman_swp_push_set(o->swp, 0, 1); ++ return ret; ++} ++EXPORT_SYMBOL(dpaa2_io_create); ++ ++struct dpaa2_io *dpaa2_io_create_service(void) ++{ ++ struct dpaa2_io *ret = kmalloc(sizeof(*ret), GFP_KERNEL); ++ ++ if (ret) ++ service_init(ret, 0); ++ return ret; ++} ++EXPORT_SYMBOL(dpaa2_io_create_service); ++ ++struct dpaa2_io *dpaa2_io_default_service(void) ++{ ++ atomic_inc(&def_serv.refs); ++ return &def_serv; ++} ++EXPORT_SYMBOL(dpaa2_io_default_service); ++ ++void dpaa2_io_down(struct dpaa2_io *d) ++{ ++ if (!atomic_dec_and_test(&d->refs)) ++ return; ++ if (d->magic == MAGIC_SERVICE) { ++ BUG_ON(!list_empty(&d->service.list)); ++ if (d->service.is_defservice) ++ /* avoid the kfree()! */ ++ return; ++ } else { ++ BUG_ON(d->magic != MAGIC_OBJECT); ++ BUG_ON(d->object.service); ++ BUG_ON(!list_empty(&d->object.notifications)); ++ } ++ kfree(d); ++} ++EXPORT_SYMBOL(dpaa2_io_down); ++ ++int dpaa2_io_service_add(struct dpaa2_io *s, struct dpaa2_io *o) ++{ ++ struct dpaa2_io_service *ss = &s->service; ++ struct dpaa2_io_object *oo = &o->object; ++ int res = -EINVAL; ++ ++ if ((s->magic != MAGIC_SERVICE) || (o->magic != MAGIC_OBJECT)) ++ return res; ++ atomic_inc(&o->refs); ++ atomic_inc(&s->refs); ++ spin_lock(&ss->lock); ++ /* 'obj' must not already be associated with a service */ ++ if (!oo->service) { ++ oo->service = s; ++ list_add(&oo->node, &ss->list); ++ if (oo->dpio_desc.receives_notifications) { ++ cpumask_set_cpu(oo->dpio_desc.cpu, ++ &ss->cpus_notifications); ++ /* Update the fast-access array */ ++ ss->objects_by_cpu[oo->dpio_desc.cpu] = ++ container_of(oo, struct dpaa2_io, object); ++ } ++ if (oo->dpio_desc.stash_affinity) ++ cpumask_set_cpu(oo->dpio_desc.cpu, ++ &ss->cpus_stashing); ++ if (!oo->dpio_desc.stash_affinity) ++ ss->has_nonaffine = 1; ++ /* success */ ++ res = 0; ++ } ++ spin_unlock(&ss->lock); ++ if (res) { ++ dpaa2_io_down(s); ++ dpaa2_io_down(o); ++ } ++ return res; ++} ++EXPORT_SYMBOL(dpaa2_io_service_add); ++ ++int dpaa2_io_get_descriptor(struct dpaa2_io *obj, struct dpaa2_io_desc *desc) ++{ ++ if (obj->magic == MAGIC_SERVICE) ++ return -EINVAL; ++ BUG_ON(obj->magic != MAGIC_OBJECT); ++ *desc = obj->object.dpio_desc; ++ return 0; ++} ++EXPORT_SYMBOL(dpaa2_io_get_descriptor); ++ ++#define DPAA_POLL_MAX 32 ++ ++int dpaa2_io_poll(struct dpaa2_io *obj) ++{ ++ const struct dpaa2_dq *dq; ++ struct qbman_swp *swp; ++ int max = 0; ++ ++ if (obj->magic != MAGIC_OBJECT) ++ return -EINVAL; ++ swp = obj->object.swp; ++ dq = qbman_swp_dqrr_next(swp); ++ while (dq) { ++ if (qbman_result_is_SCN(dq)) { ++ struct dpaa2_io_notification_ctx *ctx; ++ uint64_t q64; ++ ++ q64 = qbman_result_SCN_ctx(dq); ++ ctx = (void *)q64; ++ ctx->cb(ctx); ++ } else ++ pr_crit("Unrecognised/ignored DQRR entry\n"); ++ qbman_swp_dqrr_consume(swp, dq); ++ ++max; ++ if (max > DPAA_POLL_MAX) ++ return 0; ++ dq = qbman_swp_dqrr_next(swp); ++ } ++ return 0; ++} ++EXPORT_SYMBOL(dpaa2_io_poll); ++ ++int dpaa2_io_irq(struct dpaa2_io *obj) ++{ ++ struct qbman_swp *swp; ++ uint32_t status; ++ ++ if (obj->magic != MAGIC_OBJECT) ++ return -EINVAL; ++ swp = obj->object.swp; ++ status = qbman_swp_interrupt_read_status(swp); ++ if (!status) ++ return IRQ_NONE; ++ dpaa2_io_poll(obj); ++ qbman_swp_interrupt_clear_status(swp, status); ++ qbman_swp_interrupt_set_inhibit(swp, 0); ++ return IRQ_HANDLED; ++} ++EXPORT_SYMBOL(dpaa2_io_irq); ++ ++int dpaa2_io_pause_poll(struct dpaa2_io *obj) ++{ ++ UNIMPLEMENTED(); ++ return -EINVAL; ++} ++EXPORT_SYMBOL(dpaa2_io_pause_poll); ++ ++int dpaa2_io_resume_poll(struct dpaa2_io *obj) ++{ ++ UNIMPLEMENTED(); ++ return -EINVAL; ++} ++EXPORT_SYMBOL(dpaa2_io_resume_poll); ++ ++void dpaa2_io_service_notifications(struct dpaa2_io *s, cpumask_t *mask) ++{ ++ struct dpaa2_io_service *ss = &s->service; ++ ++ BUG_ON(s->magic != MAGIC_SERVICE); ++ cpumask_copy(mask, &ss->cpus_notifications); ++} ++EXPORT_SYMBOL(dpaa2_io_service_notifications); ++ ++void dpaa2_io_service_stashing(struct dpaa2_io *s, cpumask_t *mask) ++{ ++ struct dpaa2_io_service *ss = &s->service; ++ ++ BUG_ON(s->magic != MAGIC_SERVICE); ++ cpumask_copy(mask, &ss->cpus_stashing); ++} ++EXPORT_SYMBOL(dpaa2_io_service_stashing); ++ ++int dpaa2_io_service_has_nonaffine(struct dpaa2_io *s) ++{ ++ struct dpaa2_io_service *ss = &s->service; ++ ++ BUG_ON(s->magic != MAGIC_SERVICE); ++ return ss->has_nonaffine; ++} ++EXPORT_SYMBOL(dpaa2_io_service_has_nonaffine); ++ ++int dpaa2_io_service_register(struct dpaa2_io *d, ++ struct dpaa2_io_notification_ctx *ctx) ++{ ++ unsigned long irqflags; ++ ++ d = service_select_by_cpu(d, ctx->desired_cpu); ++ if (!d) ++ return -ENODEV; ++ ctx->dpio_id = d->object.dpio_desc.dpio_id; ++ ctx->qman64 = (uint64_t)ctx; ++ ctx->dpio_private = d; ++ spin_lock_irqsave(&d->object.lock_notifications, irqflags); ++ list_add(&ctx->node, &d->object.notifications); ++ spin_unlock_irqrestore(&d->object.lock_notifications, irqflags); ++ if (ctx->is_cdan) ++ /* Enable the generation of CDAN notifications */ ++ qbman_swp_CDAN_set_context_enable(d->object.swp, ++ (uint16_t)ctx->id, ++ ctx->qman64); ++ return 0; ++} ++EXPORT_SYMBOL(dpaa2_io_service_register); ++ ++int dpaa2_io_service_deregister(struct dpaa2_io *service, ++ struct dpaa2_io_notification_ctx *ctx) ++{ ++ struct dpaa2_io *d = ctx->dpio_private; ++ unsigned long irqflags; ++ ++ if (!service) ++ service = &def_serv; ++ BUG_ON((service != d) && (service != d->object.service)); ++ if (ctx->is_cdan) ++ qbman_swp_CDAN_disable(d->object.swp, ++ (uint16_t)ctx->id); ++ spin_lock_irqsave(&d->object.lock_notifications, irqflags); ++ list_del(&ctx->node); ++ spin_unlock_irqrestore(&d->object.lock_notifications, irqflags); ++ return 0; ++} ++EXPORT_SYMBOL(dpaa2_io_service_deregister); ++ ++int dpaa2_io_service_rearm(struct dpaa2_io *d, ++ struct dpaa2_io_notification_ctx *ctx) ++{ ++ unsigned long irqflags; ++ int err; ++ ++ d = _service_select(d); ++ if (!d) ++ return -ENODEV; ++ spin_lock_irqsave(&d->object.lock_mgmt_cmd, irqflags); ++ if (ctx->is_cdan) ++ err = qbman_swp_CDAN_enable(d->object.swp, (uint16_t)ctx->id); ++ else ++ err = qbman_swp_fq_schedule(d->object.swp, ctx->id); ++ spin_unlock_irqrestore(&d->object.lock_mgmt_cmd, irqflags); ++ return err; ++} ++EXPORT_SYMBOL(dpaa2_io_service_rearm); ++ ++int dpaa2_io_from_registration(struct dpaa2_io_notification_ctx *ctx, ++ struct dpaa2_io **io) ++{ ++ struct dpaa2_io_notification_ctx *tmp; ++ struct dpaa2_io *d = ctx->dpio_private; ++ unsigned long irqflags; ++ int ret = 0; ++ ++ BUG_ON(d->magic != MAGIC_OBJECT); ++ /* Iterate the notifications associated with 'd' looking for a match. If ++ * not, we've been passed an unregistered ctx! */ ++ spin_lock_irqsave(&d->object.lock_notifications, irqflags); ++ list_for_each_entry(tmp, &d->object.notifications, node) ++ if (tmp == ctx) ++ goto found; ++ ret = -EINVAL; ++found: ++ spin_unlock_irqrestore(&d->object.lock_notifications, irqflags); ++ if (!ret) { ++ atomic_inc(&d->refs); ++ *io = d; ++ } ++ return ret; ++} ++EXPORT_SYMBOL(dpaa2_io_from_registration); ++ ++int dpaa2_io_service_get_persistent(struct dpaa2_io *service, int cpu, ++ struct dpaa2_io **ret) ++{ ++ if (cpu == -1) ++ *ret = service_select_any(service); ++ else ++ *ret = service_select_by_cpu(service, cpu); ++ if (*ret) { ++ atomic_inc(&(*ret)->refs); ++ return 0; ++ } ++ return -ENODEV; ++} ++EXPORT_SYMBOL(dpaa2_io_service_get_persistent); ++ ++int dpaa2_io_service_pull_fq(struct dpaa2_io *d, uint32_t fqid, ++ struct dpaa2_io_store *s) ++{ ++ struct qbman_pull_desc pd; ++ int err; ++ ++ qbman_pull_desc_clear(&pd); ++ qbman_pull_desc_set_storage(&pd, s->vaddr, s->paddr, 1); ++ qbman_pull_desc_set_numframes(&pd, (uint8_t)s->max); ++ qbman_pull_desc_set_fq(&pd, fqid); ++ d = _service_select(d); ++ if (!d) ++ return -ENODEV; ++ s->swp = d->object.swp; ++ err = qbman_swp_pull(d->object.swp, &pd); ++ if (err) ++ s->swp = NULL; ++ return err; ++} ++EXPORT_SYMBOL(dpaa2_io_service_pull_fq); ++ ++int dpaa2_io_service_pull_channel(struct dpaa2_io *d, uint32_t channelid, ++ struct dpaa2_io_store *s) ++{ ++ struct qbman_pull_desc pd; ++ int err; ++ ++ qbman_pull_desc_clear(&pd); ++ qbman_pull_desc_set_storage(&pd, s->vaddr, s->paddr, 1); ++ qbman_pull_desc_set_numframes(&pd, (uint8_t)s->max); ++ qbman_pull_desc_set_channel(&pd, channelid, qbman_pull_type_prio); ++ d = _service_select(d); ++ if (!d) ++ return -ENODEV; ++ s->swp = d->object.swp; ++ err = qbman_swp_pull(d->object.swp, &pd); ++ if (err) ++ s->swp = NULL; ++ return err; ++} ++EXPORT_SYMBOL(dpaa2_io_service_pull_channel); ++ ++int dpaa2_io_service_enqueue_fq(struct dpaa2_io *d, ++ uint32_t fqid, ++ const struct dpaa2_fd *fd) ++{ ++ struct qbman_eq_desc ed; ++ ++ d = _service_select(d); ++ if (!d) ++ return -ENODEV; ++ qbman_eq_desc_clear(&ed); ++ qbman_eq_desc_set_no_orp(&ed, 0); ++ qbman_eq_desc_set_fq(&ed, fqid); ++ return qbman_swp_enqueue(d->object.swp, &ed, ++ (const struct qbman_fd *)fd); ++} ++EXPORT_SYMBOL(dpaa2_io_service_enqueue_fq); ++ ++int dpaa2_io_service_enqueue_qd(struct dpaa2_io *d, ++ uint32_t qdid, uint8_t prio, uint16_t qdbin, ++ const struct dpaa2_fd *fd) ++{ ++ struct qbman_eq_desc ed; ++ ++ d = _service_select(d); ++ if (!d) ++ return -ENODEV; ++ qbman_eq_desc_clear(&ed); ++ qbman_eq_desc_set_no_orp(&ed, 0); ++ qbman_eq_desc_set_qd(&ed, qdid, qdbin, prio); ++ return qbman_swp_enqueue(d->object.swp, &ed, ++ (const struct qbman_fd *)fd); ++} ++EXPORT_SYMBOL(dpaa2_io_service_enqueue_qd); ++ ++int dpaa2_io_service_release(struct dpaa2_io *d, ++ uint32_t bpid, ++ const uint64_t *buffers, ++ unsigned int num_buffers) ++{ ++ struct qbman_release_desc rd; ++ ++ d = _service_select(d); ++ if (!d) ++ return -ENODEV; ++ qbman_release_desc_clear(&rd); ++ qbman_release_desc_set_bpid(&rd, bpid); ++ return qbman_swp_release(d->object.swp, &rd, buffers, num_buffers); ++} ++EXPORT_SYMBOL(dpaa2_io_service_release); ++ ++int dpaa2_io_service_acquire(struct dpaa2_io *d, ++ uint32_t bpid, ++ uint64_t *buffers, ++ unsigned int num_buffers) ++{ ++ unsigned long irqflags; ++ int err; ++ ++ d = _service_select(d); ++ if (!d) ++ return -ENODEV; ++ spin_lock_irqsave(&d->object.lock_mgmt_cmd, irqflags); ++ err = qbman_swp_acquire(d->object.swp, bpid, buffers, num_buffers); ++ spin_unlock_irqrestore(&d->object.lock_mgmt_cmd, irqflags); ++ return err; ++} ++EXPORT_SYMBOL(dpaa2_io_service_acquire); ++ ++struct dpaa2_io_store *dpaa2_io_store_create(unsigned int max_frames, ++ struct device *dev) ++{ ++ struct dpaa2_io_store *ret = kmalloc(sizeof(*ret), GFP_KERNEL); ++ size_t size; ++ ++ BUG_ON(!max_frames || (max_frames > 16)); ++ if (!ret) ++ return NULL; ++ ret->max = max_frames; ++ size = max_frames * sizeof(struct dpaa2_dq) + 64; ++ ret->alloced_addr = kmalloc(size, GFP_KERNEL); ++ if (!ret->alloced_addr) { ++ kfree(ret); ++ return NULL; ++ } ++ ret->vaddr = PTR_ALIGN(ret->alloced_addr, 64); ++ ret->paddr = dma_map_single(dev, ret->vaddr, ++ sizeof(struct dpaa2_dq) * max_frames, ++ DMA_FROM_DEVICE); ++ if (dma_mapping_error(dev, ret->paddr)) { ++ kfree(ret->alloced_addr); ++ kfree(ret); ++ return NULL; ++ } ++ ret->idx = 0; ++ ret->dev = dev; ++ return ret; ++} ++EXPORT_SYMBOL(dpaa2_io_store_create); ++ ++void dpaa2_io_store_destroy(struct dpaa2_io_store *s) ++{ ++ dma_unmap_single(s->dev, s->paddr, sizeof(struct dpaa2_dq) * s->max, ++ DMA_FROM_DEVICE); ++ kfree(s->alloced_addr); ++ kfree(s); ++} ++EXPORT_SYMBOL(dpaa2_io_store_destroy); ++ ++struct dpaa2_dq *dpaa2_io_store_next(struct dpaa2_io_store *s, int *is_last) ++{ ++ int match; ++ struct dpaa2_dq *ret = &s->vaddr[s->idx]; ++ ++ match = qbman_result_has_new_result(s->swp, ret); ++ if (!match) { ++ *is_last = 0; ++ return NULL; ++ } ++ BUG_ON(!qbman_result_is_DQ(ret)); ++ s->idx++; ++ if (dpaa2_dq_is_pull_complete(ret)) { ++ *is_last = 1; ++ s->idx = 0; ++ /* If we get an empty dequeue result to terminate a zero-results ++ * vdqcr, return NULL to the caller rather than expecting him to ++ * check non-NULL results every time. */ ++ if (!(dpaa2_dq_flags(ret) & DPAA2_DQ_STAT_VALIDFRAME)) ++ ret = NULL; ++ } else ++ *is_last = 0; ++ return ret; ++} ++EXPORT_SYMBOL(dpaa2_io_store_next); ++ ++#ifdef CONFIG_FSL_QBMAN_DEBUG ++int dpaa2_io_query_fq_count(struct dpaa2_io *d, uint32_t fqid, ++ uint32_t *fcnt, uint32_t *bcnt) ++{ ++ struct qbman_attr state; ++ struct qbman_swp *swp; ++ unsigned long irqflags; ++ int ret; ++ ++ d = service_select_any(d); ++ if (!d) ++ return -ENODEV; ++ ++ swp = d->object.swp; ++ spin_lock_irqsave(&d->object.lock_mgmt_cmd, irqflags); ++ ret = qbman_fq_query_state(swp, fqid, &state); ++ spin_unlock_irqrestore(&d->object.lock_mgmt_cmd, irqflags); ++ if (ret) ++ return ret; ++ *fcnt = qbman_fq_state_frame_count(&state); ++ *bcnt = qbman_fq_state_byte_count(&state); ++ ++ return 0; ++} ++EXPORT_SYMBOL(dpaa2_io_query_fq_count); ++ ++int dpaa2_io_query_bp_count(struct dpaa2_io *d, uint32_t bpid, ++ uint32_t *num) ++{ ++ struct qbman_attr state; ++ struct qbman_swp *swp; ++ unsigned long irqflags; ++ int ret; ++ ++ d = service_select_any(d); ++ if (!d) ++ return -ENODEV; ++ ++ swp = d->object.swp; ++ spin_lock_irqsave(&d->object.lock_mgmt_cmd, irqflags); ++ ret = qbman_bp_query(swp, bpid, &state); ++ spin_unlock_irqrestore(&d->object.lock_mgmt_cmd, irqflags); ++ if (ret) ++ return ret; ++ *num = qbman_bp_info_num_free_bufs(&state); ++ return 0; ++} ++EXPORT_SYMBOL(dpaa2_io_query_bp_count); ++ ++#endif ++ ++/* module init/exit hooks called from dpio-drv.c. These are declared in ++ * dpio-drv.h. ++ */ ++int dpaa2_io_service_driver_init(void) ++{ ++ service_init(&def_serv, 1); ++ return 0; ++} ++ ++void dpaa2_io_service_driver_exit(void) ++{ ++ if (atomic_read(&def_serv.refs) != 1) ++ pr_err("default DPIO service leaves dangling DPIO objects!\n"); ++} +diff --git a/drivers/staging/fsl-mc/bus/dpio/fsl_dpio.h b/drivers/staging/fsl-mc/bus/dpio/fsl_dpio.h +new file mode 100644 +index 0000000..88a492f +--- /dev/null ++++ b/drivers/staging/fsl-mc/bus/dpio/fsl_dpio.h +@@ -0,0 +1,460 @@ ++/* Copyright 2013-2015 Freescale Semiconductor Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * * Neither the name of the above-listed copyright holders nor the ++ * names of any contributors may be used to endorse or promote products ++ * derived from this software without specific prior written permission. ++ * ++ * ++ * ALTERNATIVELY, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") as published by the Free Software ++ * Foundation, either version 2 of that License or (at your option) any ++ * later version. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE ++ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++#ifndef __FSL_DPIO_H ++#define __FSL_DPIO_H ++ ++/* Data Path I/O Portal API ++ * Contains initialization APIs and runtime control APIs for DPIO ++ */ ++ ++struct fsl_mc_io; ++ ++/** ++ * dpio_open() - Open a control session for the specified object ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @dpio_id: DPIO unique ID ++ * @token: Returned token; use in subsequent API calls ++ * ++ * This function can be used to open a control session for an ++ * already created object; an object may have been declared in ++ * the DPL or by calling the dpio_create() function. ++ * This function returns a unique authentication token, ++ * associated with the specific object ID and the specific MC ++ * portal; this token must be used in all subsequent commands for ++ * this specific object. ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpio_open(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ int dpio_id, ++ uint16_t *token); ++ ++/** ++ * dpio_close() - Close the control session of the object ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPIO object ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpio_close(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token); ++ ++/** ++ * enum dpio_channel_mode - DPIO notification channel mode ++ * @DPIO_NO_CHANNEL: No support for notification channel ++ * @DPIO_LOCAL_CHANNEL: Notifications on data availability can be received by a ++ * dedicated channel in the DPIO; user should point the queue's ++ * destination in the relevant interface to this DPIO ++ */ ++enum dpio_channel_mode { ++ DPIO_NO_CHANNEL = 0, ++ DPIO_LOCAL_CHANNEL = 1, ++}; ++ ++/** ++ * struct dpio_cfg - Structure representing DPIO configuration ++ * @channel_mode: Notification channel mode ++ * @num_priorities: Number of priorities for the notification channel (1-8); ++ * relevant only if 'channel_mode = DPIO_LOCAL_CHANNEL' ++ */ ++struct dpio_cfg { ++ enum dpio_channel_mode channel_mode; ++ uint8_t num_priorities; ++}; ++ ++/** ++ * dpio_create() - Create the DPIO object. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @cfg: Configuration structure ++ * @token: Returned token; use in subsequent API calls ++ * ++ * Create the DPIO object, allocate required resources and ++ * perform required initialization. ++ * ++ * The object can be created either by declaring it in the ++ * DPL file, or by calling this function. ++ * ++ * This function returns a unique authentication token, ++ * associated with the specific object ID and the specific MC ++ * portal; this token must be used in all subsequent calls to ++ * this specific object. For objects that are created using the ++ * DPL file, call dpio_open() function to get an authentication ++ * token first. ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpio_create(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ const struct dpio_cfg *cfg, ++ uint16_t *token); ++ ++/** ++ * dpio_destroy() - Destroy the DPIO object and release all its resources. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPIO object ++ * ++ * Return: '0' on Success; Error code otherwise ++ */ ++int dpio_destroy(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token); ++ ++/** ++ * dpio_enable() - Enable the DPIO, allow I/O portal operations. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPIO object ++ * ++ * Return: '0' on Success; Error code otherwise ++ */ ++int dpio_enable(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token); ++ ++/** ++ * dpio_disable() - Disable the DPIO, stop any I/O portal operation. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPIO object ++ * ++ * Return: '0' on Success; Error code otherwise ++ */ ++int dpio_disable(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token); ++ ++/** ++ * dpio_is_enabled() - Check if the DPIO is enabled. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPIO object ++ * @en: Returns '1' if object is enabled; '0' otherwise ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpio_is_enabled(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int *en); ++ ++/** ++ * dpio_reset() - Reset the DPIO, returns the object to initial state. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPIO object ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpio_reset(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token); ++ ++/** ++ * dpio_set_stashing_destination() - Set the stashing destination. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPIO object ++ * @sdest: stashing destination value ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpio_set_stashing_destination(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t sdest); ++ ++/** ++ * dpio_get_stashing_destination() - Get the stashing destination.. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPIO object ++ * @sdest: Returns the stashing destination value ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpio_get_stashing_destination(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t *sdest); ++ ++/** ++ * dpio_add_static_dequeue_channel() - Add a static dequeue channel. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPIO object ++ * @dpcon_id: DPCON object ID ++ * @channel_index: Returned channel index to be used in qbman API ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpio_add_static_dequeue_channel(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int dpcon_id, ++ uint8_t *channel_index); ++ ++/** ++ * dpio_remove_static_dequeue_channel() - Remove a static dequeue channel. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPIO object ++ * @dpcon_id: DPCON object ID ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpio_remove_static_dequeue_channel(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int dpcon_id); ++ ++/** ++ * DPIO IRQ Index and Events ++ */ ++ ++/** ++ * Irq software-portal index ++ */ ++#define DPIO_IRQ_SWP_INDEX 0 ++ ++/** ++ * struct dpio_irq_cfg - IRQ configuration ++ * @addr: Address that must be written to signal a message-based interrupt ++ * @val: Value to write into irq_addr address ++ * @irq_num: A user defined number associated with this IRQ ++ */ ++struct dpio_irq_cfg { ++ uint64_t addr; ++ uint32_t val; ++ int irq_num; ++}; ++ ++/** ++ * dpio_set_irq() - Set IRQ information for the DPIO to trigger an interrupt. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPIO object ++ * @irq_index: Identifies the interrupt index to configure ++ * @irq_cfg: IRQ configuration ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpio_set_irq(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ struct dpio_irq_cfg *irq_cfg); ++ ++/** ++ * dpio_get_irq() - Get IRQ information from the DPIO. ++ * ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPIO object ++ * @irq_index: The interrupt index to configure ++ * @type: Interrupt type: 0 represents message interrupt ++ * type (both irq_addr and irq_val are valid) ++ * @irq_cfg: IRQ attributes ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpio_get_irq(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ int *type, ++ struct dpio_irq_cfg *irq_cfg); ++ ++/** ++ * dpio_set_irq_enable() - Set overall interrupt state. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPIO object ++ * @irq_index: The interrupt index to configure ++ * @en: Interrupt state - enable = 1, disable = 0 ++ * ++ * Allows GPP software to control when interrupts are generated. ++ * Each interrupt can have up to 32 causes. The enable/disable control's the ++ * overall interrupt state. if the interrupt is disabled no causes will cause ++ * an interrupt. ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpio_set_irq_enable(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint8_t en); ++ ++/** ++ * dpio_get_irq_enable() - Get overall interrupt state ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPIO object ++ * @irq_index: The interrupt index to configure ++ * @en: Returned interrupt state - enable = 1, disable = 0 ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpio_get_irq_enable(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint8_t *en); ++ ++/** ++ * dpio_set_irq_mask() - Set interrupt mask. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPIO object ++ * @irq_index: The interrupt index to configure ++ * @mask: event mask to trigger interrupt; ++ * each bit: ++ * 0 = ignore event ++ * 1 = consider event for asserting IRQ ++ * ++ * Every interrupt can have up to 32 causes and the interrupt model supports ++ * masking/unmasking each cause independently ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpio_set_irq_mask(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint32_t mask); ++ ++/** ++ * dpio_get_irq_mask() - Get interrupt mask. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPIO object ++ * @irq_index: The interrupt index to configure ++ * @mask: Returned event mask to trigger interrupt ++ * ++ * Every interrupt can have up to 32 causes and the interrupt model supports ++ * masking/unmasking each cause independently ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpio_get_irq_mask(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint32_t *mask); ++ ++/** ++ * dpio_get_irq_status() - Get the current status of any pending interrupts. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPIO object ++ * @irq_index: The interrupt index to configure ++ * @status: Returned interrupts status - one bit per cause: ++ * 0 = no interrupt pending ++ * 1 = interrupt pending ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpio_get_irq_status(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint32_t *status); ++ ++/** ++ * dpio_clear_irq_status() - Clear a pending interrupt's status ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPIO object ++ * @irq_index: The interrupt index to configure ++ * @status: bits to clear (W1C) - one bit per cause: ++ * 0 = don't change ++ * 1 = clear status bit ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpio_clear_irq_status(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint32_t status); ++ ++/** ++ * struct dpio_attr - Structure representing DPIO attributes ++ * @id: DPIO object ID ++ * @version: DPIO version ++ * @qbman_portal_ce_offset: offset of the software portal cache-enabled area ++ * @qbman_portal_ci_offset: offset of the software portal cache-inhibited area ++ * @qbman_portal_id: Software portal ID ++ * @channel_mode: Notification channel mode ++ * @num_priorities: Number of priorities for the notification channel (1-8); ++ * relevant only if 'channel_mode = DPIO_LOCAL_CHANNEL' ++ * @qbman_version: QBMAN version ++ */ ++struct dpio_attr { ++ int id; ++ /** ++ * struct version - DPIO version ++ * @major: DPIO major version ++ * @minor: DPIO minor version ++ */ ++ struct { ++ uint16_t major; ++ uint16_t minor; ++ } version; ++ uint64_t qbman_portal_ce_offset; ++ uint64_t qbman_portal_ci_offset; ++ uint16_t qbman_portal_id; ++ enum dpio_channel_mode channel_mode; ++ uint8_t num_priorities; ++ uint32_t qbman_version; ++}; ++ ++/** ++ * dpio_get_attributes() - Retrieve DPIO attributes ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPIO object ++ * @attr: Returned object's attributes ++ * ++ * Return: '0' on Success; Error code otherwise ++ */ ++int dpio_get_attributes(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ struct dpio_attr *attr); ++#endif /* __FSL_DPIO_H */ +diff --git a/drivers/staging/fsl-mc/bus/dpio/fsl_dpio_cmd.h b/drivers/staging/fsl-mc/bus/dpio/fsl_dpio_cmd.h +new file mode 100644 +index 0000000..f339cd6 +--- /dev/null ++++ b/drivers/staging/fsl-mc/bus/dpio/fsl_dpio_cmd.h +@@ -0,0 +1,184 @@ ++/* Copyright 2013-2015 Freescale Semiconductor Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * * Neither the name of the above-listed copyright holders nor the ++ * names of any contributors may be used to endorse or promote products ++ * derived from this software without specific prior written permission. ++ * ++ * ++ * ALTERNATIVELY, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") as published by the Free Software ++ * Foundation, either version 2 of that License or (at your option) any ++ * later version. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE ++ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++#ifndef _FSL_DPIO_CMD_H ++#define _FSL_DPIO_CMD_H ++ ++/* DPIO Version */ ++#define DPIO_VER_MAJOR 3 ++#define DPIO_VER_MINOR 2 ++ ++/* Command IDs */ ++#define DPIO_CMDID_CLOSE 0x800 ++#define DPIO_CMDID_OPEN 0x803 ++#define DPIO_CMDID_CREATE 0x903 ++#define DPIO_CMDID_DESTROY 0x900 ++ ++#define DPIO_CMDID_ENABLE 0x002 ++#define DPIO_CMDID_DISABLE 0x003 ++#define DPIO_CMDID_GET_ATTR 0x004 ++#define DPIO_CMDID_RESET 0x005 ++#define DPIO_CMDID_IS_ENABLED 0x006 ++ ++#define DPIO_CMDID_SET_IRQ 0x010 ++#define DPIO_CMDID_GET_IRQ 0x011 ++#define DPIO_CMDID_SET_IRQ_ENABLE 0x012 ++#define DPIO_CMDID_GET_IRQ_ENABLE 0x013 ++#define DPIO_CMDID_SET_IRQ_MASK 0x014 ++#define DPIO_CMDID_GET_IRQ_MASK 0x015 ++#define DPIO_CMDID_GET_IRQ_STATUS 0x016 ++#define DPIO_CMDID_CLEAR_IRQ_STATUS 0x017 ++ ++#define DPIO_CMDID_SET_STASHING_DEST 0x120 ++#define DPIO_CMDID_GET_STASHING_DEST 0x121 ++#define DPIO_CMDID_ADD_STATIC_DEQUEUE_CHANNEL 0x122 ++#define DPIO_CMDID_REMOVE_STATIC_DEQUEUE_CHANNEL 0x123 ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPIO_CMD_OPEN(cmd, dpio_id) \ ++ MC_CMD_OP(cmd, 0, 0, 32, int, dpio_id) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPIO_CMD_CREATE(cmd, cfg) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 16, 2, enum dpio_channel_mode, \ ++ cfg->channel_mode);\ ++ MC_CMD_OP(cmd, 0, 32, 8, uint8_t, cfg->num_priorities);\ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPIO_RSP_IS_ENABLED(cmd, en) \ ++ MC_RSP_OP(cmd, 0, 0, 1, int, en) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPIO_CMD_SET_IRQ(cmd, irq_index, irq_cfg) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 0, 8, uint8_t, irq_index);\ ++ MC_CMD_OP(cmd, 0, 32, 32, uint32_t, irq_cfg->val);\ ++ MC_CMD_OP(cmd, 1, 0, 64, uint64_t, irq_cfg->addr);\ ++ MC_CMD_OP(cmd, 2, 0, 32, int, irq_cfg->irq_num); \ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPIO_CMD_GET_IRQ(cmd, irq_index) \ ++ MC_CMD_OP(cmd, 0, 32, 8, uint8_t, irq_index) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPIO_RSP_GET_IRQ(cmd, type, irq_cfg) \ ++do { \ ++ MC_RSP_OP(cmd, 0, 0, 32, uint32_t, irq_cfg->val); \ ++ MC_RSP_OP(cmd, 1, 0, 64, uint64_t, irq_cfg->addr); \ ++ MC_RSP_OP(cmd, 2, 0, 32, int, irq_cfg->irq_num); \ ++ MC_RSP_OP(cmd, 2, 32, 32, int, type); \ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPIO_CMD_SET_IRQ_ENABLE(cmd, irq_index, en) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 0, 8, uint8_t, en); \ ++ MC_CMD_OP(cmd, 0, 32, 8, uint8_t, irq_index);\ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPIO_CMD_GET_IRQ_ENABLE(cmd, irq_index) \ ++ MC_CMD_OP(cmd, 0, 32, 8, uint8_t, irq_index) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPIO_RSP_GET_IRQ_ENABLE(cmd, en) \ ++ MC_RSP_OP(cmd, 0, 0, 8, uint8_t, en) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPIO_CMD_SET_IRQ_MASK(cmd, irq_index, mask) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 0, 32, uint32_t, mask); \ ++ MC_CMD_OP(cmd, 0, 32, 8, uint8_t, irq_index);\ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPIO_CMD_GET_IRQ_MASK(cmd, irq_index) \ ++ MC_CMD_OP(cmd, 0, 32, 8, uint8_t, irq_index) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPIO_RSP_GET_IRQ_MASK(cmd, mask) \ ++ MC_RSP_OP(cmd, 0, 0, 32, uint32_t, mask) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPIO_CMD_GET_IRQ_STATUS(cmd, irq_index, status) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 0, 32, uint32_t, status);\ ++ MC_CMD_OP(cmd, 0, 32, 8, uint8_t, irq_index);\ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPIO_RSP_GET_IRQ_STATUS(cmd, status) \ ++ MC_RSP_OP(cmd, 0, 0, 32, uint32_t, status) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPIO_CMD_CLEAR_IRQ_STATUS(cmd, irq_index, status) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 0, 32, uint32_t, status); \ ++ MC_CMD_OP(cmd, 0, 32, 8, uint8_t, irq_index);\ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPIO_RSP_GET_ATTR(cmd, attr) \ ++do { \ ++ MC_RSP_OP(cmd, 0, 0, 32, int, attr->id);\ ++ MC_RSP_OP(cmd, 0, 32, 16, uint16_t, attr->qbman_portal_id);\ ++ MC_RSP_OP(cmd, 0, 48, 8, uint8_t, attr->num_priorities);\ ++ MC_RSP_OP(cmd, 0, 56, 4, enum dpio_channel_mode, attr->channel_mode);\ ++ MC_RSP_OP(cmd, 1, 0, 64, uint64_t, attr->qbman_portal_ce_offset);\ ++ MC_RSP_OP(cmd, 2, 0, 64, uint64_t, attr->qbman_portal_ci_offset);\ ++ MC_RSP_OP(cmd, 3, 0, 16, uint16_t, attr->version.major);\ ++ MC_RSP_OP(cmd, 3, 16, 16, uint16_t, attr->version.minor);\ ++ MC_RSP_OP(cmd, 3, 32, 32, uint32_t, attr->qbman_version);\ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPIO_CMD_SET_STASHING_DEST(cmd, sdest) \ ++ MC_CMD_OP(cmd, 0, 0, 8, uint8_t, sdest) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPIO_RSP_GET_STASHING_DEST(cmd, sdest) \ ++ MC_RSP_OP(cmd, 0, 0, 8, uint8_t, sdest) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPIO_CMD_ADD_STATIC_DEQUEUE_CHANNEL(cmd, dpcon_id) \ ++ MC_CMD_OP(cmd, 0, 0, 32, int, dpcon_id) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPIO_RSP_ADD_STATIC_DEQUEUE_CHANNEL(cmd, channel_index) \ ++ MC_RSP_OP(cmd, 0, 0, 8, uint8_t, channel_index) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPIO_CMD_REMOVE_STATIC_DEQUEUE_CHANNEL(cmd, dpcon_id) \ ++ MC_CMD_OP(cmd, 0, 0, 32, int, dpcon_id) ++#endif /* _FSL_DPIO_CMD_H */ +diff --git a/drivers/staging/fsl-mc/bus/dpio/fsl_qbman_base.h b/drivers/staging/fsl-mc/bus/dpio/fsl_qbman_base.h +new file mode 100644 +index 0000000..2874ff8 +--- /dev/null ++++ b/drivers/staging/fsl-mc/bus/dpio/fsl_qbman_base.h +@@ -0,0 +1,123 @@ ++/* Copyright (C) 2014 Freescale Semiconductor, Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * * Neither the name of Freescale Semiconductor nor the ++ * names of its contributors may be used to endorse or promote products ++ * derived from this software without specific prior written permission. ++ * ++ * ++ * ALTERNATIVELY, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") as published by the Free Software ++ * Foundation, either version 2 of that License or (at your option) any ++ * later version. ++ * ++ * THIS SOFTWARE IS PROVIDED BY Freescale Semiconductor ``AS IS'' AND ANY ++ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ++ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE ++ * DISCLAIMED. IN NO EVENT SHALL Freescale Semiconductor BE LIABLE FOR ANY ++ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ++ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; ++ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ++ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ++ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS ++ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ++ */ ++#ifndef _FSL_QBMAN_BASE_H ++#define _FSL_QBMAN_BASE_H ++ ++/** ++ * struct qbman_block_desc - qbman block descriptor structure ++ * ++ * Descriptor for a QBMan instance on the SoC. On partitions/targets that do not ++ * control this QBMan instance, these values may simply be place-holders. The ++ * idea is simply that we be able to distinguish between them, eg. so that SWP ++ * descriptors can identify which QBMan instance they belong to. ++ */ ++struct qbman_block_desc { ++ void *ccsr_reg_bar; /* CCSR register map */ ++ int irq_rerr; /* Recoverable error interrupt line */ ++ int irq_nrerr; /* Non-recoverable error interrupt line */ ++}; ++ ++/** ++ * struct qbman_swp_desc - qbman software portal descriptor structure ++ * ++ * Descriptor for a QBMan software portal, expressed in terms that make sense to ++ * the user context. Ie. on MC, this information is likely to be true-physical, ++ * and instantiated statically at compile-time. On GPP, this information is ++ * likely to be obtained via "discovery" over a partition's "layerscape bus" ++ * (ie. in response to a MC portal command), and would take into account any ++ * virtualisation of the GPP user's address space and/or interrupt numbering. ++ */ ++struct qbman_swp_desc { ++ const struct qbman_block_desc *block; /* The QBMan instance */ ++ void *cena_bar; /* Cache-enabled portal register map */ ++ void *cinh_bar; /* Cache-inhibited portal register map */ ++ uint32_t qman_version; ++}; ++ ++/* Driver object for managing a QBMan portal */ ++struct qbman_swp; ++ ++/** ++ * struct qbman_fd - basci structure for qbman frame descriptor ++ * ++ * Place-holder for FDs, we represent it via the simplest form that we need for ++ * now. Different overlays may be needed to support different options, etc. (It ++ * is impractical to define One True Struct, because the resulting encoding ++ * routines (lots of read-modify-writes) would be worst-case performance whether ++ * or not circumstances required them.) ++ * ++ * Note, as with all data-structures exchanged between software and hardware (be ++ * they located in the portal register map or DMA'd to and from main-memory), ++ * the driver ensures that the caller of the driver API sees the data-structures ++ * in host-endianness. "struct qbman_fd" is no exception. The 32-bit words ++ * contained within this structure are represented in host-endianness, even if ++ * hardware always treats them as little-endian. As such, if any of these fields ++ * are interpreted in a binary (rather than numerical) fashion by hardware ++ * blocks (eg. accelerators), then the user should be careful. We illustrate ++ * with an example; ++ * ++ * Suppose the desired behaviour of an accelerator is controlled by the "frc" ++ * field of the FDs that are sent to it. Suppose also that the behaviour desired ++ * by the user corresponds to an "frc" value which is expressed as the literal ++ * sequence of bytes 0xfe, 0xed, 0xab, and 0xba. So "frc" should be the 32-bit ++ * value in which 0xfe is the first byte and 0xba is the last byte, and as ++ * hardware is little-endian, this amounts to a 32-bit "value" of 0xbaabedfe. If ++ * the software is little-endian also, this can simply be achieved by setting ++ * frc=0xbaabedfe. On the other hand, if software is big-endian, it should set ++ * frc=0xfeedabba! The best away of avoiding trouble with this sort of thing is ++ * to treat the 32-bit words as numerical values, in which the offset of a field ++ * from the beginning of the first byte (as required or generated by hardware) ++ * is numerically encoded by a left-shift (ie. by raising the field to a ++ * corresponding power of 2). Ie. in the current example, software could set ++ * "frc" in the following way, and it would work correctly on both little-endian ++ * and big-endian operation; ++ * fd.frc = (0xfe << 0) | (0xed << 8) | (0xab << 16) | (0xba << 24); ++ */ ++struct qbman_fd { ++ union { ++ uint32_t words[8]; ++ struct qbman_fd_simple { ++ uint32_t addr_lo; ++ uint32_t addr_hi; ++ uint32_t len; ++ /* offset in the MS 16 bits, BPID in the LS 16 bits */ ++ uint32_t bpid_offset; ++ uint32_t frc; /* frame context */ ++ /* "err", "va", "cbmt", "asal", [...] */ ++ uint32_t ctrl; ++ /* flow context */ ++ uint32_t flc_lo; ++ uint32_t flc_hi; ++ } simple; ++ }; ++}; ++ ++#endif /* !_FSL_QBMAN_BASE_H */ +diff --git a/drivers/staging/fsl-mc/bus/dpio/fsl_qbman_portal.h b/drivers/staging/fsl-mc/bus/dpio/fsl_qbman_portal.h +new file mode 100644 +index 0000000..c9e543e +--- /dev/null ++++ b/drivers/staging/fsl-mc/bus/dpio/fsl_qbman_portal.h +@@ -0,0 +1,753 @@ ++/* Copyright (C) 2014 Freescale Semiconductor, Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * * Neither the name of Freescale Semiconductor nor the ++ * names of its contributors may be used to endorse or promote products ++ * derived from this software without specific prior written permission. ++ * ++ * ++ * ALTERNATIVELY, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") as published by the Free Software ++ * Foundation, either version 2 of that License or (at your option) any ++ * later version. ++ * ++ * THIS SOFTWARE IS PROVIDED BY Freescale Semiconductor ``AS IS'' AND ANY ++ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ++ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE ++ * DISCLAIMED. IN NO EVENT SHALL Freescale Semiconductor BE LIABLE FOR ANY ++ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ++ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; ++ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ++ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ++ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS ++ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ++ */ ++#ifndef _FSL_QBMAN_PORTAL_H ++#define _FSL_QBMAN_PORTAL_H ++ ++#include "fsl_qbman_base.h" ++ ++/** ++ * qbman_swp_init() - Create a functional object representing the given ++ * QBMan portal descriptor. ++ * @d: the given qbman swp descriptor ++ * ++ * Return qbman_swp portal object for success, NULL if the object cannot ++ * be created. ++ */ ++struct qbman_swp *qbman_swp_init(const struct qbman_swp_desc *d); ++/** ++ * qbman_swp_finish() - Create and destroy a functional object representing ++ * the given QBMan portal descriptor. ++ * @p: the qbman_swp object to be destroyed. ++ * ++ */ ++void qbman_swp_finish(struct qbman_swp *p); ++ ++/** ++ * qbman_swp_get_desc() - Get the descriptor of the given portal object. ++ * @p: the given portal object. ++ * ++ * Return the descriptor for this portal. ++ */ ++const struct qbman_swp_desc *qbman_swp_get_desc(struct qbman_swp *p); ++ ++ /**************/ ++ /* Interrupts */ ++ /**************/ ++ ++/* See the QBMan driver API documentation for details on the interrupt ++ * mechanisms. */ ++#define QBMAN_SWP_INTERRUPT_EQRI ((uint32_t)0x00000001) ++#define QBMAN_SWP_INTERRUPT_EQDI ((uint32_t)0x00000002) ++#define QBMAN_SWP_INTERRUPT_DQRI ((uint32_t)0x00000004) ++#define QBMAN_SWP_INTERRUPT_RCRI ((uint32_t)0x00000008) ++#define QBMAN_SWP_INTERRUPT_RCDI ((uint32_t)0x00000010) ++#define QBMAN_SWP_INTERRUPT_VDCI ((uint32_t)0x00000020) ++ ++/** ++ * qbman_swp_interrupt_get_vanish() ++ * qbman_swp_interrupt_set_vanish() - Get/Set the data in software portal ++ * interrupt status disable register. ++ * @p: the given software portal object. ++ * @mask: The mask to set in SWP_IDSR register. ++ * ++ * Return the settings in SWP_ISDR register for Get function. ++ */ ++uint32_t qbman_swp_interrupt_get_vanish(struct qbman_swp *p); ++void qbman_swp_interrupt_set_vanish(struct qbman_swp *p, uint32_t mask); ++ ++/** ++ * qbman_swp_interrupt_read_status() ++ * qbman_swp_interrupt_clear_status() - Get/Set the data in software portal ++ * interrupt status register. ++ * @p: the given software portal object. ++ * @mask: The mask to set in SWP_ISR register. ++ * ++ * Return the settings in SWP_ISR register for Get function. ++ * ++ */ ++uint32_t qbman_swp_interrupt_read_status(struct qbman_swp *p); ++void qbman_swp_interrupt_clear_status(struct qbman_swp *p, uint32_t mask); ++ ++/** ++ * qbman_swp_interrupt_get_trigger() ++ * qbman_swp_interrupt_set_trigger() - Get/Set the data in software portal ++ * interrupt enable register. ++ * @p: the given software portal object. ++ * @mask: The mask to set in SWP_IER register. ++ * ++ * Return the settings in SWP_IER register for Get function. ++ */ ++uint32_t qbman_swp_interrupt_get_trigger(struct qbman_swp *p); ++void qbman_swp_interrupt_set_trigger(struct qbman_swp *p, uint32_t mask); ++ ++/** ++ * qbman_swp_interrupt_get_inhibit() ++ * qbman_swp_interrupt_set_inhibit() - Set/Set the data in software portal ++ * interrupt inhibit register. ++ * @p: the given software portal object. ++ * @mask: The mask to set in SWP_IIR register. ++ * ++ * Return the settings in SWP_IIR register for Get function. ++ */ ++int qbman_swp_interrupt_get_inhibit(struct qbman_swp *p); ++void qbman_swp_interrupt_set_inhibit(struct qbman_swp *p, int inhibit); ++ ++ /************/ ++ /* Dequeues */ ++ /************/ ++ ++/* See the QBMan driver API documentation for details on the enqueue ++ * mechanisms. NB: the use of a 'dpaa2_' prefix for this type is because it is ++ * primarily used by the "DPIO" layer that sits above (and hides) the QBMan ++ * driver. The structure is defined in the DPIO interface, but to avoid circular ++ * dependencies we just pre/re-declare it here opaquely. */ ++struct dpaa2_dq; ++ ++/* ------------------- */ ++/* Push-mode dequeuing */ ++/* ------------------- */ ++ ++/** ++ * qbman_swp_push_get() - Get the push dequeue setup. ++ * @p: the software portal object. ++ * @channel_idx: the channel index to query. ++ * @enabled: returned boolean to show whether the push dequeue is enabled for ++ * the given channel. ++ */ ++void qbman_swp_push_get(struct qbman_swp *, uint8_t channel_idx, int *enabled); ++/** ++ * qbman_swp_push_set() - Enable or disable push dequeue. ++ * @p: the software portal object. ++ * @channel_idx: the channel index.. ++ * @enable: enable or disable push dequeue. ++ * ++ * The user of a portal can enable and disable push-mode dequeuing of up to 16 ++ * channels independently. It does not specify this toggling by channel IDs, but ++ * rather by specifying the index (from 0 to 15) that has been mapped to the ++ * desired channel. ++ */ ++void qbman_swp_push_set(struct qbman_swp *, uint8_t channel_idx, int enable); ++ ++/* ------------------- */ ++/* Pull-mode dequeuing */ ++/* ------------------- */ ++ ++/** ++ * struct qbman_pull_desc - the structure for pull dequeue descriptor ++ */ ++struct qbman_pull_desc { ++ uint32_t dont_manipulate_directly[6]; ++}; ++ ++enum qbman_pull_type_e { ++ /* dequeue with priority precedence, respect intra-class scheduling */ ++ qbman_pull_type_prio = 1, ++ /* dequeue with active FQ precedence, respect ICS */ ++ qbman_pull_type_active, ++ /* dequeue with active FQ precedence, no ICS */ ++ qbman_pull_type_active_noics ++}; ++ ++/** ++ * qbman_pull_desc_clear() - Clear the contents of a descriptor to ++ * default/starting state. ++ * @d: the pull dequeue descriptor to be cleared. ++ */ ++void qbman_pull_desc_clear(struct qbman_pull_desc *d); ++ ++/** ++ * qbman_pull_desc_set_storage()- Set the pull dequeue storage ++ * @d: the pull dequeue descriptor to be set. ++ * @storage: the pointer of the memory to store the dequeue result. ++ * @storage_phys: the physical address of the storage memory. ++ * @stash: to indicate whether write allocate is enabled. ++ * ++ * If not called, or if called with 'storage' as NULL, the result pull dequeues ++ * will produce results to DQRR. If 'storage' is non-NULL, then results are ++ * produced to the given memory location (using the physical/DMA address which ++ * the caller provides in 'storage_phys'), and 'stash' controls whether or not ++ * those writes to main-memory express a cache-warming attribute. ++ */ ++void qbman_pull_desc_set_storage(struct qbman_pull_desc *d, ++ struct dpaa2_dq *storage, ++ dma_addr_t storage_phys, ++ int stash); ++/** ++ * qbman_pull_desc_set_numframes() - Set the number of frames to be dequeued. ++ * @d: the pull dequeue descriptor to be set. ++ * @numframes: number of frames to be set, must be between 1 and 16, inclusive. ++ */ ++void qbman_pull_desc_set_numframes(struct qbman_pull_desc *, uint8_t numframes); ++ ++/** ++ * qbman_pull_desc_set_fq() - Set fqid from which the dequeue command dequeues. ++ * @fqid: the frame queue index of the given FQ. ++ * ++ * qbman_pull_desc_set_wq() - Set wqid from which the dequeue command dequeues. ++ * @wqid: composed of channel id and wqid within the channel. ++ * @dct: the dequeue command type. ++ * ++ * qbman_pull_desc_set_channel() - Set channelid from which the dequeue command ++ * dequeues. ++ * @chid: the channel id to be dequeued. ++ * @dct: the dequeue command type. ++ * ++ * Exactly one of the following descriptor "actions" should be set. (Calling any ++ * one of these will replace the effect of any prior call to one of these.) ++ * - pull dequeue from the given frame queue (FQ) ++ * - pull dequeue from any FQ in the given work queue (WQ) ++ * - pull dequeue from any FQ in any WQ in the given channel ++ */ ++void qbman_pull_desc_set_fq(struct qbman_pull_desc *, uint32_t fqid); ++void qbman_pull_desc_set_wq(struct qbman_pull_desc *, uint32_t wqid, ++ enum qbman_pull_type_e dct); ++void qbman_pull_desc_set_channel(struct qbman_pull_desc *, uint32_t chid, ++ enum qbman_pull_type_e dct); ++ ++/** ++ * qbman_swp_pull() - Issue the pull dequeue command ++ * @s: the software portal object. ++ * @d: the software portal descriptor which has been configured with ++ * the set of qbman_pull_desc_set_*() calls. ++ * ++ * Return 0 for success, and -EBUSY if the software portal is not ready ++ * to do pull dequeue. ++ */ ++int qbman_swp_pull(struct qbman_swp *, struct qbman_pull_desc *d); ++ ++/* -------------------------------- */ ++/* Polling DQRR for dequeue results */ ++/* -------------------------------- */ ++ ++/** ++ * qbman_swp_dqrr_next() - Get an valid DQRR entry. ++ * @s: the software portal object. ++ * ++ * Return NULL if there are no unconsumed DQRR entries. Return a DQRR entry ++ * only once, so repeated calls can return a sequence of DQRR entries, without ++ * requiring they be consumed immediately or in any particular order. ++ */ ++const struct dpaa2_dq *qbman_swp_dqrr_next(struct qbman_swp *s); ++ ++/** ++ * qbman_swp_dqrr_consume() - Consume DQRR entries previously returned from ++ * qbman_swp_dqrr_next(). ++ * @s: the software portal object. ++ * @dq: the DQRR entry to be consumed. ++ */ ++void qbman_swp_dqrr_consume(struct qbman_swp *s, const struct dpaa2_dq *dq); ++ ++/* ------------------------------------------------- */ ++/* Polling user-provided storage for dequeue results */ ++/* ------------------------------------------------- */ ++/** ++ * qbman_result_has_new_result() - Check and get the dequeue response from the ++ * dq storage memory set in pull dequeue command ++ * @s: the software portal object. ++ * @dq: the dequeue result read from the memory. ++ * ++ * Only used for user-provided storage of dequeue results, not DQRR. For ++ * efficiency purposes, the driver will perform any required endianness ++ * conversion to ensure that the user's dequeue result storage is in host-endian ++ * format (whether or not that is the same as the little-endian format that ++ * hardware DMA'd to the user's storage). As such, once the user has called ++ * qbman_result_has_new_result() and been returned a valid dequeue result, ++ * they should not call it again on the same memory location (except of course ++ * if another dequeue command has been executed to produce a new result to that ++ * location). ++ * ++ * Return 1 for getting a valid dequeue result, or 0 for not getting a valid ++ * dequeue result. ++ */ ++int qbman_result_has_new_result(struct qbman_swp *, ++ const struct dpaa2_dq *); ++ ++/* -------------------------------------------------------- */ ++/* Parsing dequeue entries (DQRR and user-provided storage) */ ++/* -------------------------------------------------------- */ ++ ++/** ++ * qbman_result_is_DQ() - check the dequeue result is a dequeue response or not ++ * @dq: the dequeue result to be checked. ++ * ++ * DQRR entries may contain non-dequeue results, ie. notifications ++ */ ++int qbman_result_is_DQ(const struct dpaa2_dq *); ++ ++/** ++ * qbman_result_is_SCN() - Check the dequeue result is notification or not ++ * @dq: the dequeue result to be checked. ++ * ++ * All the non-dequeue results (FQDAN/CDAN/CSCN/...) are "state change ++ * notifications" of one type or another. Some APIs apply to all of them, of the ++ * form qbman_result_SCN_***(). ++ */ ++static inline int qbman_result_is_SCN(const struct dpaa2_dq *dq) ++{ ++ return !qbman_result_is_DQ(dq); ++} ++ ++/** ++ * Recognise different notification types, only required if the user allows for ++ * these to occur, and cares about them when they do. ++ */ ++int qbman_result_is_FQDAN(const struct dpaa2_dq *); ++ /* FQ Data Availability */ ++int qbman_result_is_CDAN(const struct dpaa2_dq *); ++ /* Channel Data Availability */ ++int qbman_result_is_CSCN(const struct dpaa2_dq *); ++ /* Congestion State Change */ ++int qbman_result_is_BPSCN(const struct dpaa2_dq *); ++ /* Buffer Pool State Change */ ++int qbman_result_is_CGCU(const struct dpaa2_dq *); ++ /* Congestion Group Count Update */ ++/* Frame queue state change notifications; (FQDAN in theory counts too as it ++ * leaves a FQ parked, but it is primarily a data availability notification) */ ++int qbman_result_is_FQRN(const struct dpaa2_dq *); /* Retirement */ ++int qbman_result_is_FQRNI(const struct dpaa2_dq *); ++ /* Retirement Immediate */ ++int qbman_result_is_FQPN(const struct dpaa2_dq *); /* Park */ ++ ++/* NB: for parsing dequeue results (when "is_DQ" is TRUE), use the higher-layer ++ * dpaa2_dq_*() functions. */ ++ ++/* State-change notifications (FQDAN/CDAN/CSCN/...). */ ++/** ++ * qbman_result_SCN_state() - Get the state field in State-change notification ++ */ ++uint8_t qbman_result_SCN_state(const struct dpaa2_dq *); ++/** ++ * qbman_result_SCN_rid() - Get the resource id in State-change notification ++ */ ++uint32_t qbman_result_SCN_rid(const struct dpaa2_dq *); ++/** ++ * qbman_result_SCN_ctx() - Get the context data in State-change notification ++ */ ++uint64_t qbman_result_SCN_ctx(const struct dpaa2_dq *); ++/** ++ * qbman_result_SCN_state_in_mem() - Get the state field in State-change ++ * notification which is written to memory instead of DQRR. ++ */ ++uint8_t qbman_result_SCN_state_in_mem(const struct dpaa2_dq *); ++/** ++ * qbman_result_SCN_rid_in_mem() - Get the resource id in State-change ++ * notification which is written to memory instead of DQRR. ++ */ ++uint32_t qbman_result_SCN_rid_in_mem(const struct dpaa2_dq *); ++ ++/* Type-specific "resource IDs". Mainly for illustration purposes, though it ++ * also gives the appropriate type widths. */ ++#define qbman_result_FQDAN_fqid(dq) qbman_result_SCN_rid(dq) ++#define qbman_result_FQRN_fqid(dq) qbman_result_SCN_rid(dq) ++#define qbman_result_FQRNI_fqid(dq) qbman_result_SCN_rid(dq) ++#define qbman_result_FQPN_fqid(dq) qbman_result_SCN_rid(dq) ++#define qbman_result_CDAN_cid(dq) ((uint16_t)qbman_result_SCN_rid(dq)) ++#define qbman_result_CSCN_cgid(dq) ((uint16_t)qbman_result_SCN_rid(dq)) ++ ++/** ++ * qbman_result_bpscn_bpid() - Get the bpid from BPSCN ++ * ++ * Return the buffer pool id. ++ */ ++uint16_t qbman_result_bpscn_bpid(const struct dpaa2_dq *); ++/** ++ * qbman_result_bpscn_has_free_bufs() - Check whether there are free ++ * buffers in the pool from BPSCN. ++ * ++ * Return the number of free buffers. ++ */ ++int qbman_result_bpscn_has_free_bufs(const struct dpaa2_dq *); ++/** ++ * qbman_result_bpscn_is_depleted() - Check BPSCN to see whether the ++ * buffer pool is depleted. ++ * ++ * Return the status of buffer pool depletion. ++ */ ++int qbman_result_bpscn_is_depleted(const struct dpaa2_dq *); ++/** ++ * qbman_result_bpscn_is_surplus() - Check BPSCN to see whether the buffer ++ * pool is surplus or not. ++ * ++ * Return the status of buffer pool surplus. ++ */ ++int qbman_result_bpscn_is_surplus(const struct dpaa2_dq *); ++/** ++ * qbman_result_bpscn_ctx() - Get the BPSCN CTX from BPSCN message ++ * ++ * Return the BPSCN context. ++ */ ++uint64_t qbman_result_bpscn_ctx(const struct dpaa2_dq *); ++ ++/* Parsing CGCU */ ++/** ++ * qbman_result_cgcu_cgid() - Check CGCU resouce id, i.e. cgid ++ * ++ * Return the CGCU resource id. ++ */ ++uint16_t qbman_result_cgcu_cgid(const struct dpaa2_dq *); ++/** ++ * qbman_result_cgcu_icnt() - Get the I_CNT from CGCU ++ * ++ * Return instantaneous count in the CGCU notification. ++ */ ++uint64_t qbman_result_cgcu_icnt(const struct dpaa2_dq *); ++ ++ /************/ ++ /* Enqueues */ ++ /************/ ++/** ++ * struct qbman_eq_desc - structure of enqueue descriptor ++ */ ++struct qbman_eq_desc { ++ uint32_t dont_manipulate_directly[8]; ++}; ++ ++/** ++ * struct qbman_eq_response - structure of enqueue response ++ */ ++struct qbman_eq_response { ++ uint32_t dont_manipulate_directly[16]; ++}; ++ ++/** ++ * qbman_eq_desc_clear() - Clear the contents of a descriptor to ++ * default/starting state. ++ */ ++void qbman_eq_desc_clear(struct qbman_eq_desc *); ++ ++/* Exactly one of the following descriptor "actions" should be set. (Calling ++ * any one of these will replace the effect of any prior call to one of these.) ++ * - enqueue without order-restoration ++ * - enqueue with order-restoration ++ * - fill a hole in the order-restoration sequence, without any enqueue ++ * - advance NESN (Next Expected Sequence Number), without any enqueue ++ * 'respond_success' indicates whether an enqueue response should be DMA'd ++ * after success (otherwise a response is DMA'd only after failure). ++ * 'incomplete' indicates that other fragments of the same 'seqnum' are yet to ++ * be enqueued. ++ */ ++/** ++ * qbman_eq_desc_set_no_orp() - Set enqueue descriptor without orp ++ * @d: the enqueue descriptor. ++ * @response_success: 1 = enqueue with response always; 0 = enqueue with ++ * rejections returned on a FQ. ++ */ ++void qbman_eq_desc_set_no_orp(struct qbman_eq_desc *d, int respond_success); ++ ++/** ++ * qbman_eq_desc_set_orp() - Set order-resotration in the enqueue descriptor ++ * @d: the enqueue descriptor. ++ * @response_success: 1 = enqueue with response always; 0 = enqueue with ++ * rejections returned on a FQ. ++ * @opr_id: the order point record id. ++ * @seqnum: the order restoration sequence number. ++ * @incomplete: indiates whether this is the last fragments using the same ++ * sequeue number. ++ */ ++void qbman_eq_desc_set_orp(struct qbman_eq_desc *d, int respond_success, ++ uint32_t opr_id, uint32_t seqnum, int incomplete); ++ ++/** ++ * qbman_eq_desc_set_orp_hole() - fill a hole in the order-restoration sequence ++ * without any enqueue ++ * @d: the enqueue descriptor. ++ * @opr_id: the order point record id. ++ * @seqnum: the order restoration sequence number. ++ */ ++void qbman_eq_desc_set_orp_hole(struct qbman_eq_desc *d, uint32_t opr_id, ++ uint32_t seqnum); ++ ++/** ++ * qbman_eq_desc_set_orp_nesn() - advance NESN (Next Expected Sequence Number) ++ * without any enqueue ++ * @d: the enqueue descriptor. ++ * @opr_id: the order point record id. ++ * @seqnum: the order restoration sequence number. ++ */ ++void qbman_eq_desc_set_orp_nesn(struct qbman_eq_desc *d, uint32_t opr_id, ++ uint32_t seqnum); ++ ++/** ++ * qbman_eq_desc_set_response() - Set the enqueue response info. ++ * @d: the enqueue descriptor ++ * @storage_phys: the physical address of the enqueue response in memory. ++ * @stash: indicate that the write allocation enabled or not. ++ * ++ * In the case where an enqueue response is DMA'd, this determines where that ++ * response should go. (The physical/DMA address is given for hardware's ++ * benefit, but software should interpret it as a "struct qbman_eq_response" ++ * data structure.) 'stash' controls whether or not the write to main-memory ++ * expresses a cache-warming attribute. ++ */ ++void qbman_eq_desc_set_response(struct qbman_eq_desc *d, ++ dma_addr_t storage_phys, ++ int stash); ++/** ++ * qbman_eq_desc_set_token() - Set token for the enqueue command ++ * @d: the enqueue descriptor ++ * @token: the token to be set. ++ * ++ * token is the value that shows up in an enqueue response that can be used to ++ * detect when the results have been published. The easiest technique is to zero ++ * result "storage" before issuing an enqueue, and use any non-zero 'token' ++ * value. ++ */ ++void qbman_eq_desc_set_token(struct qbman_eq_desc *d, uint8_t token); ++ ++/** ++ * qbman_eq_desc_set_fq() ++ * qbman_eq_desc_set_qd() - Set eithe FQ or Queuing Destination for the enqueue ++ * command. ++ * @d: the enqueue descriptor ++ * @fqid: the id of the frame queue to be enqueued. ++ * @qdid: the id of the queuing destination to be enqueued. ++ * @qd_bin: the queuing destination bin ++ * @qd_prio: the queuing destination priority. ++ * ++ * Exactly one of the following descriptor "targets" should be set. (Calling any ++ * one of these will replace the effect of any prior call to one of these.) ++ * - enqueue to a frame queue ++ * - enqueue to a queuing destination ++ * Note, that none of these will have any affect if the "action" type has been ++ * set to "orp_hole" or "orp_nesn". ++ */ ++void qbman_eq_desc_set_fq(struct qbman_eq_desc *, uint32_t fqid); ++void qbman_eq_desc_set_qd(struct qbman_eq_desc *, uint32_t qdid, ++ uint32_t qd_bin, uint32_t qd_prio); ++ ++/** ++ * qbman_eq_desc_set_eqdi() - enable/disable EQDI interrupt ++ * @d: the enqueue descriptor ++ * @enable: boolean to enable/disable EQDI ++ * ++ * Determines whether or not the portal's EQDI interrupt source should be ++ * asserted after the enqueue command is completed. ++ */ ++void qbman_eq_desc_set_eqdi(struct qbman_eq_desc *, int enable); ++ ++/** ++ * qbman_eq_desc_set_dca() - Set DCA mode in the enqueue command. ++ * @d: the enqueue descriptor. ++ * @enable: enabled/disable DCA mode. ++ * @dqrr_idx: DCAP_CI, the DCAP consumer index. ++ * @park: determine the whether park the FQ or not ++ * ++ * Determines whether or not a portal DQRR entry should be consumed once the ++ * enqueue command is completed. (And if so, and the DQRR entry corresponds ++ * to a held-active (order-preserving) FQ, whether the FQ should be parked ++ * instead of being rescheduled.) ++ */ ++void qbman_eq_desc_set_dca(struct qbman_eq_desc *, int enable, ++ uint32_t dqrr_idx, int park); ++ ++/** ++ * qbman_swp_enqueue() - Issue an enqueue command. ++ * @s: the software portal used for enqueue. ++ * @d: the enqueue descriptor. ++ * @fd: the frame descriptor to be enqueued. ++ * ++ * Please note that 'fd' should only be NULL if the "action" of the ++ * descriptor is "orp_hole" or "orp_nesn". ++ * ++ * Return 0 for successful enqueue, -EBUSY if the EQCR is not ready. ++ */ ++int qbman_swp_enqueue(struct qbman_swp *, const struct qbman_eq_desc *, ++ const struct qbman_fd *fd); ++ ++/** ++ * qbman_swp_enqueue_thresh() - Set the threshold for EQRI interrupt. ++ * ++ * An EQRI interrupt can be generated when the fill-level of EQCR falls below ++ * the 'thresh' value set here. Setting thresh==0 (the default) disables. ++ */ ++int qbman_swp_enqueue_thresh(struct qbman_swp *, unsigned int thresh); ++ ++ /*******************/ ++ /* Buffer releases */ ++ /*******************/ ++/** ++ * struct qbman_release_desc - The structure for buffer release descriptor ++ */ ++struct qbman_release_desc { ++ uint32_t dont_manipulate_directly[1]; ++}; ++ ++/** ++ * qbman_release_desc_clear() - Clear the contents of a descriptor to ++ * default/starting state. ++ */ ++void qbman_release_desc_clear(struct qbman_release_desc *); ++ ++/** ++ * qbman_release_desc_set_bpid() - Set the ID of the buffer pool to release to ++ */ ++void qbman_release_desc_set_bpid(struct qbman_release_desc *, uint32_t bpid); ++ ++/** ++ * qbman_release_desc_set_rcdi() - Determines whether or not the portal's RCDI ++ * interrupt source should be asserted after the release command is completed. ++ */ ++void qbman_release_desc_set_rcdi(struct qbman_release_desc *, int enable); ++ ++/** ++ * qbman_swp_release() - Issue a buffer release command. ++ * @s: the software portal object. ++ * @d: the release descriptor. ++ * @buffers: a pointer pointing to the buffer address to be released. ++ * @num_buffers: number of buffers to be released, must be less than 8. ++ * ++ * Return 0 for success, -EBUSY if the release command ring is not ready. ++ */ ++int qbman_swp_release(struct qbman_swp *s, const struct qbman_release_desc *d, ++ const uint64_t *buffers, unsigned int num_buffers); ++ ++ /*******************/ ++ /* Buffer acquires */ ++ /*******************/ ++ ++/** ++ * qbman_swp_acquire() - Issue a buffer acquire command. ++ * @s: the software portal object. ++ * @bpid: the buffer pool index. ++ * @buffers: a pointer pointing to the acquired buffer address|es. ++ * @num_buffers: number of buffers to be acquired, must be less than 8. ++ * ++ * Return 0 for success, or negative error code if the acquire command ++ * fails. ++ */ ++int qbman_swp_acquire(struct qbman_swp *, uint32_t bpid, uint64_t *buffers, ++ unsigned int num_buffers); ++ ++ /*****************/ ++ /* FQ management */ ++ /*****************/ ++ ++/** ++ * qbman_swp_fq_schedule() - Move the fq to the scheduled state. ++ * @s: the software portal object. ++ * @fqid: the index of frame queue to be scheduled. ++ * ++ * There are a couple of different ways that a FQ can end up parked state, ++ * This schedules it. ++ * ++ * Return 0 for success, or negative error code for failure. ++ */ ++int qbman_swp_fq_schedule(struct qbman_swp *s, uint32_t fqid); ++ ++/** ++ * qbman_swp_fq_force() - Force the FQ to fully scheduled state. ++ * @s: the software portal object. ++ * @fqid: the index of frame queue to be forced. ++ * ++ * Force eligible will force a tentatively-scheduled FQ to be fully-scheduled ++ * and thus be available for selection by any channel-dequeuing behaviour (push ++ * or pull). If the FQ is subsequently "dequeued" from the channel and is still ++ * empty at the time this happens, the resulting dq_entry will have no FD. ++ * (qbman_result_DQ_fd() will return NULL.) ++ * ++ * Return 0 for success, or negative error code for failure. ++ */ ++int qbman_swp_fq_force(struct qbman_swp *s, uint32_t fqid); ++ ++/** ++ * qbman_swp_fq_xon() ++ * qbman_swp_fq_xoff() - XON/XOFF the frame queue. ++ * @s: the software portal object. ++ * @fqid: the index of frame queue. ++ * ++ * These functions change the FQ flow-control stuff between XON/XOFF. (The ++ * default is XON.) This setting doesn't affect enqueues to the FQ, just ++ * dequeues. XOFF FQs will remain in the tenatively-scheduled state, even when ++ * non-empty, meaning they won't be selected for scheduled dequeuing. If a FQ is ++ * changed to XOFF after it had already become truly-scheduled to a channel, and ++ * a pull dequeue of that channel occurs that selects that FQ for dequeuing, ++ * then the resulting dq_entry will have no FD. (qbman_result_DQ_fd() will ++ * return NULL.) ++ * ++ * Return 0 for success, or negative error code for failure. ++ */ ++int qbman_swp_fq_xon(struct qbman_swp *s, uint32_t fqid); ++int qbman_swp_fq_xoff(struct qbman_swp *s, uint32_t fqid); ++ ++ /**********************/ ++ /* Channel management */ ++ /**********************/ ++ ++/* If the user has been allocated a channel object that is going to generate ++ * CDANs to another channel, then these functions will be necessary. ++ * CDAN-enabled channels only generate a single CDAN notification, after which ++ * it they need to be reenabled before they'll generate another. (The idea is ++ * that pull dequeuing will occur in reaction to the CDAN, followed by a ++ * reenable step.) Each function generates a distinct command to hardware, so a ++ * combination function is provided if the user wishes to modify the "context" ++ * (which shows up in each CDAN message) each time they reenable, as a single ++ * command to hardware. */ ++/** ++ * qbman_swp_CDAN_set_context() - Set CDAN context ++ * @s: the software portal object. ++ * @channelid: the channel index. ++ * @ctx: the context to be set in CDAN. ++ * ++ * Return 0 for success, or negative error code for failure. ++ */ ++int qbman_swp_CDAN_set_context(struct qbman_swp *, uint16_t channelid, ++ uint64_t ctx); ++ ++/** ++ * qbman_swp_CDAN_enable() - Enable CDAN for the channel. ++ * @s: the software portal object. ++ * @channelid: the index of the channel to generate CDAN. ++ * ++ * Return 0 for success, or negative error code for failure. ++ */ ++int qbman_swp_CDAN_enable(struct qbman_swp *, uint16_t channelid); ++ ++/** ++ * qbman_swp_CDAN_disable() - disable CDAN for the channel. ++ * @s: the software portal object. ++ * @channelid: the index of the channel to generate CDAN. ++ * ++ * Return 0 for success, or negative error code for failure. ++ */ ++int qbman_swp_CDAN_disable(struct qbman_swp *, uint16_t channelid); ++ ++/** ++ * qbman_swp_CDAN_set_context_enable() - Set CDAN contest and enable CDAN ++ * @s: the software portal object. ++ * @channelid: the index of the channel to generate CDAN. ++ * @ctx: the context set in CDAN. ++ * ++ * Return 0 for success, or negative error code for failure. ++ */ ++int qbman_swp_CDAN_set_context_enable(struct qbman_swp *, uint16_t channelid, ++ uint64_t ctx); ++ ++#endif /* !_FSL_QBMAN_PORTAL_H */ +diff --git a/drivers/staging/fsl-mc/bus/dpio/qbman_debug.c b/drivers/staging/fsl-mc/bus/dpio/qbman_debug.c +new file mode 100644 +index 0000000..12e33d3 +--- /dev/null ++++ b/drivers/staging/fsl-mc/bus/dpio/qbman_debug.c +@@ -0,0 +1,846 @@ ++/* Copyright (C) 2015 Freescale Semiconductor, Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * * Neither the name of Freescale Semiconductor nor the ++ * names of its contributors may be used to endorse or promote products ++ * derived from this software without specific prior written permission. ++ * ++ * ++ * ALTERNATIVELY, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") as published by the Free Software ++ * Foundation, either version 2 of that License or (at your option) any ++ * later version. ++ * ++ * THIS SOFTWARE IS PROVIDED BY Freescale Semiconductor ``AS IS'' AND ANY ++ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ++ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE ++ * DISCLAIMED. IN NO EVENT SHALL Freescale Semiconductor BE LIABLE FOR ANY ++ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ++ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; ++ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ++ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ++ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS ++ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++#include "qbman_portal.h" ++#include "qbman_debug.h" ++#include "fsl_qbman_portal.h" ++ ++/* QBMan portal management command code */ ++#define QBMAN_BP_QUERY 0x32 ++#define QBMAN_FQ_QUERY 0x44 ++#define QBMAN_FQ_QUERY_NP 0x45 ++#define QBMAN_CGR_QUERY 0x51 ++#define QBMAN_WRED_QUERY 0x54 ++#define QBMAN_CGR_STAT_QUERY 0x55 ++#define QBMAN_CGR_STAT_QUERY_CLR 0x56 ++ ++enum qbman_attr_usage_e { ++ qbman_attr_usage_fq, ++ qbman_attr_usage_bpool, ++ qbman_attr_usage_cgr, ++}; ++ ++struct int_qbman_attr { ++ uint32_t words[32]; ++ enum qbman_attr_usage_e usage; ++}; ++ ++#define attr_type_set(a, e) \ ++{ \ ++ struct qbman_attr *__attr = a; \ ++ enum qbman_attr_usage_e __usage = e; \ ++ ((struct int_qbman_attr *)__attr)->usage = __usage; \ ++} ++ ++#define ATTR32(d) (&(d)->dont_manipulate_directly[0]) ++#define ATTR32_1(d) (&(d)->dont_manipulate_directly[16]) ++ ++static struct qb_attr_code code_bp_bpid = QB_CODE(0, 16, 16); ++static struct qb_attr_code code_bp_bdi = QB_CODE(1, 16, 1); ++static struct qb_attr_code code_bp_va = QB_CODE(1, 17, 1); ++static struct qb_attr_code code_bp_wae = QB_CODE(1, 18, 1); ++static struct qb_attr_code code_bp_swdet = QB_CODE(4, 0, 16); ++static struct qb_attr_code code_bp_swdxt = QB_CODE(4, 16, 16); ++static struct qb_attr_code code_bp_hwdet = QB_CODE(5, 0, 16); ++static struct qb_attr_code code_bp_hwdxt = QB_CODE(5, 16, 16); ++static struct qb_attr_code code_bp_swset = QB_CODE(6, 0, 16); ++static struct qb_attr_code code_bp_swsxt = QB_CODE(6, 16, 16); ++static struct qb_attr_code code_bp_vbpid = QB_CODE(7, 0, 14); ++static struct qb_attr_code code_bp_icid = QB_CODE(7, 16, 15); ++static struct qb_attr_code code_bp_pl = QB_CODE(7, 31, 1); ++static struct qb_attr_code code_bp_bpscn_addr_lo = QB_CODE(8, 0, 32); ++static struct qb_attr_code code_bp_bpscn_addr_hi = QB_CODE(9, 0, 32); ++static struct qb_attr_code code_bp_bpscn_ctx_lo = QB_CODE(10, 0, 32); ++static struct qb_attr_code code_bp_bpscn_ctx_hi = QB_CODE(11, 0, 32); ++static struct qb_attr_code code_bp_hw_targ = QB_CODE(12, 0, 16); ++static struct qb_attr_code code_bp_state = QB_CODE(1, 24, 3); ++static struct qb_attr_code code_bp_fill = QB_CODE(2, 0, 32); ++static struct qb_attr_code code_bp_hdptr = QB_CODE(3, 0, 32); ++static struct qb_attr_code code_bp_sdcnt = QB_CODE(13, 0, 8); ++static struct qb_attr_code code_bp_hdcnt = QB_CODE(13, 1, 8); ++static struct qb_attr_code code_bp_sscnt = QB_CODE(13, 2, 8); ++ ++void qbman_bp_attr_clear(struct qbman_attr *a) ++{ ++ memset(a, 0, sizeof(*a)); ++ attr_type_set(a, qbman_attr_usage_bpool); ++} ++ ++int qbman_bp_query(struct qbman_swp *s, uint32_t bpid, ++ struct qbman_attr *a) ++{ ++ uint32_t *p; ++ uint32_t verb, rslt; ++ uint32_t *attr = ATTR32(a); ++ ++ qbman_bp_attr_clear(a); ++ ++ /* Start the management command */ ++ p = qbman_swp_mc_start(s); ++ if (!p) ++ return -EBUSY; ++ ++ /* Encode the caller-provided attributes */ ++ qb_attr_code_encode(&code_bp_bpid, p, bpid); ++ ++ /* Complete the management command */ ++ p = qbman_swp_mc_complete(s, p, p[0] | QBMAN_BP_QUERY); ++ ++ /* Decode the outcome */ ++ verb = qb_attr_code_decode(&code_generic_verb, p); ++ rslt = qb_attr_code_decode(&code_generic_rslt, p); ++ BUG_ON(verb != QBMAN_BP_QUERY); ++ ++ /* Determine success or failure */ ++ if (unlikely(rslt != QBMAN_MC_RSLT_OK)) { ++ pr_err("Query of BPID 0x%x failed, code=0x%02x\n", bpid, rslt); ++ return -EIO; ++ } ++ ++ /* For the query, word[0] of the result contains only the ++ * verb/rslt fields, so skip word[0]. ++ */ ++ word_copy(&attr[1], &p[1], 15); ++ return 0; ++} ++ ++void qbman_bp_attr_get_bdi(struct qbman_attr *a, int *bdi, int *va, int *wae) ++{ ++ uint32_t *p = ATTR32(a); ++ ++ *bdi = !!qb_attr_code_decode(&code_bp_bdi, p); ++ *va = !!qb_attr_code_decode(&code_bp_va, p); ++ *wae = !!qb_attr_code_decode(&code_bp_wae, p); ++} ++ ++static uint32_t qbman_bp_thresh_to_value(uint32_t val) ++{ ++ return (val & 0xff) << ((val & 0xf00) >> 8); ++} ++ ++void qbman_bp_attr_get_swdet(struct qbman_attr *a, uint32_t *swdet) ++{ ++ uint32_t *p = ATTR32(a); ++ ++ *swdet = qbman_bp_thresh_to_value(qb_attr_code_decode(&code_bp_swdet, ++ p)); ++} ++void qbman_bp_attr_get_swdxt(struct qbman_attr *a, uint32_t *swdxt) ++{ ++ uint32_t *p = ATTR32(a); ++ ++ *swdxt = qbman_bp_thresh_to_value(qb_attr_code_decode(&code_bp_swdxt, ++ p)); ++} ++void qbman_bp_attr_get_hwdet(struct qbman_attr *a, uint32_t *hwdet) ++{ ++ uint32_t *p = ATTR32(a); ++ ++ *hwdet = qbman_bp_thresh_to_value(qb_attr_code_decode(&code_bp_hwdet, ++ p)); ++} ++void qbman_bp_attr_get_hwdxt(struct qbman_attr *a, uint32_t *hwdxt) ++{ ++ uint32_t *p = ATTR32(a); ++ ++ *hwdxt = qbman_bp_thresh_to_value(qb_attr_code_decode(&code_bp_hwdxt, ++ p)); ++} ++ ++void qbman_bp_attr_get_swset(struct qbman_attr *a, uint32_t *swset) ++{ ++ uint32_t *p = ATTR32(a); ++ ++ *swset = qbman_bp_thresh_to_value(qb_attr_code_decode(&code_bp_swset, ++ p)); ++} ++ ++void qbman_bp_attr_get_swsxt(struct qbman_attr *a, uint32_t *swsxt) ++{ ++ uint32_t *p = ATTR32(a); ++ ++ *swsxt = qbman_bp_thresh_to_value(qb_attr_code_decode(&code_bp_swsxt, ++ p)); ++} ++ ++void qbman_bp_attr_get_vbpid(struct qbman_attr *a, uint32_t *vbpid) ++{ ++ uint32_t *p = ATTR32(a); ++ ++ *vbpid = qb_attr_code_decode(&code_bp_vbpid, p); ++} ++ ++void qbman_bp_attr_get_icid(struct qbman_attr *a, uint32_t *icid, int *pl) ++{ ++ uint32_t *p = ATTR32(a); ++ ++ *icid = qb_attr_code_decode(&code_bp_icid, p); ++ *pl = !!qb_attr_code_decode(&code_bp_pl, p); ++} ++ ++void qbman_bp_attr_get_bpscn_addr(struct qbman_attr *a, uint64_t *bpscn_addr) ++{ ++ uint32_t *p = ATTR32(a); ++ ++ *bpscn_addr = ((uint64_t)qb_attr_code_decode(&code_bp_bpscn_addr_hi, ++ p) << 32) | ++ (uint64_t)qb_attr_code_decode(&code_bp_bpscn_addr_lo, ++ p); ++} ++ ++void qbman_bp_attr_get_bpscn_ctx(struct qbman_attr *a, uint64_t *bpscn_ctx) ++{ ++ uint32_t *p = ATTR32(a); ++ ++ *bpscn_ctx = ((uint64_t)qb_attr_code_decode(&code_bp_bpscn_ctx_hi, p) ++ << 32) | ++ (uint64_t)qb_attr_code_decode(&code_bp_bpscn_ctx_lo, ++ p); ++} ++ ++void qbman_bp_attr_get_hw_targ(struct qbman_attr *a, uint32_t *hw_targ) ++{ ++ uint32_t *p = ATTR32(a); ++ ++ *hw_targ = qb_attr_code_decode(&code_bp_hw_targ, p); ++} ++ ++int qbman_bp_info_has_free_bufs(struct qbman_attr *a) ++{ ++ uint32_t *p = ATTR32(a); ++ ++ return !(int)(qb_attr_code_decode(&code_bp_state, p) & 0x1); ++} ++ ++int qbman_bp_info_is_depleted(struct qbman_attr *a) ++{ ++ uint32_t *p = ATTR32(a); ++ ++ return (int)(qb_attr_code_decode(&code_bp_state, p) & 0x2); ++} ++ ++int qbman_bp_info_is_surplus(struct qbman_attr *a) ++{ ++ uint32_t *p = ATTR32(a); ++ ++ return (int)(qb_attr_code_decode(&code_bp_state, p) & 0x4); ++} ++ ++uint32_t qbman_bp_info_num_free_bufs(struct qbman_attr *a) ++{ ++ uint32_t *p = ATTR32(a); ++ ++ return qb_attr_code_decode(&code_bp_fill, p); ++} ++ ++uint32_t qbman_bp_info_hdptr(struct qbman_attr *a) ++{ ++ uint32_t *p = ATTR32(a); ++ ++ return qb_attr_code_decode(&code_bp_hdptr, p); ++} ++ ++uint32_t qbman_bp_info_sdcnt(struct qbman_attr *a) ++{ ++ uint32_t *p = ATTR32(a); ++ ++ return qb_attr_code_decode(&code_bp_sdcnt, p); ++} ++ ++uint32_t qbman_bp_info_hdcnt(struct qbman_attr *a) ++{ ++ uint32_t *p = ATTR32(a); ++ ++ return qb_attr_code_decode(&code_bp_hdcnt, p); ++} ++ ++uint32_t qbman_bp_info_sscnt(struct qbman_attr *a) ++{ ++ uint32_t *p = ATTR32(a); ++ ++ return qb_attr_code_decode(&code_bp_sscnt, p); ++} ++ ++static struct qb_attr_code code_fq_fqid = QB_CODE(1, 0, 24); ++static struct qb_attr_code code_fq_cgrid = QB_CODE(2, 16, 16); ++static struct qb_attr_code code_fq_destwq = QB_CODE(3, 0, 15); ++static struct qb_attr_code code_fq_fqctrl = QB_CODE(3, 24, 8); ++static struct qb_attr_code code_fq_icscred = QB_CODE(4, 0, 15); ++static struct qb_attr_code code_fq_tdthresh = QB_CODE(4, 16, 13); ++static struct qb_attr_code code_fq_oa_len = QB_CODE(5, 0, 12); ++static struct qb_attr_code code_fq_oa_ics = QB_CODE(5, 14, 1); ++static struct qb_attr_code code_fq_oa_cgr = QB_CODE(5, 15, 1); ++static struct qb_attr_code code_fq_mctl_bdi = QB_CODE(5, 24, 1); ++static struct qb_attr_code code_fq_mctl_ff = QB_CODE(5, 25, 1); ++static struct qb_attr_code code_fq_mctl_va = QB_CODE(5, 26, 1); ++static struct qb_attr_code code_fq_mctl_ps = QB_CODE(5, 27, 1); ++static struct qb_attr_code code_fq_ctx_lower32 = QB_CODE(6, 0, 32); ++static struct qb_attr_code code_fq_ctx_upper32 = QB_CODE(7, 0, 32); ++static struct qb_attr_code code_fq_icid = QB_CODE(8, 0, 15); ++static struct qb_attr_code code_fq_pl = QB_CODE(8, 15, 1); ++static struct qb_attr_code code_fq_vfqid = QB_CODE(9, 0, 24); ++static struct qb_attr_code code_fq_erfqid = QB_CODE(10, 0, 24); ++ ++void qbman_fq_attr_clear(struct qbman_attr *a) ++{ ++ memset(a, 0, sizeof(*a)); ++ attr_type_set(a, qbman_attr_usage_fq); ++} ++ ++/* FQ query function for programmable fields */ ++int qbman_fq_query(struct qbman_swp *s, uint32_t fqid, struct qbman_attr *desc) ++{ ++ uint32_t *p; ++ uint32_t verb, rslt; ++ uint32_t *d = ATTR32(desc); ++ ++ qbman_fq_attr_clear(desc); ++ ++ p = qbman_swp_mc_start(s); ++ if (!p) ++ return -EBUSY; ++ qb_attr_code_encode(&code_fq_fqid, p, fqid); ++ p = qbman_swp_mc_complete(s, p, QBMAN_FQ_QUERY); ++ ++ /* Decode the outcome */ ++ verb = qb_attr_code_decode(&code_generic_verb, p); ++ rslt = qb_attr_code_decode(&code_generic_rslt, p); ++ BUG_ON(verb != QBMAN_FQ_QUERY); ++ ++ /* Determine success or failure */ ++ if (unlikely(rslt != QBMAN_MC_RSLT_OK)) { ++ pr_err("Query of FQID 0x%x failed, code=0x%02x\n", ++ fqid, rslt); ++ return -EIO; ++ } ++ /* For the configure, word[0] of the command contains only the WE-mask. ++ * For the query, word[0] of the result contains only the verb/rslt ++ * fields. Skip word[0] in the latter case. */ ++ word_copy(&d[1], &p[1], 15); ++ return 0; ++} ++ ++void qbman_fq_attr_get_fqctrl(struct qbman_attr *d, uint32_t *fqctrl) ++{ ++ uint32_t *p = ATTR32(d); ++ ++ *fqctrl = qb_attr_code_decode(&code_fq_fqctrl, p); ++} ++ ++void qbman_fq_attr_get_cgrid(struct qbman_attr *d, uint32_t *cgrid) ++{ ++ uint32_t *p = ATTR32(d); ++ ++ *cgrid = qb_attr_code_decode(&code_fq_cgrid, p); ++} ++ ++void qbman_fq_attr_get_destwq(struct qbman_attr *d, uint32_t *destwq) ++{ ++ uint32_t *p = ATTR32(d); ++ ++ *destwq = qb_attr_code_decode(&code_fq_destwq, p); ++} ++ ++void qbman_fq_attr_get_icscred(struct qbman_attr *d, uint32_t *icscred) ++{ ++ uint32_t *p = ATTR32(d); ++ ++ *icscred = qb_attr_code_decode(&code_fq_icscred, p); ++} ++ ++static struct qb_attr_code code_tdthresh_exp = QB_CODE(0, 0, 5); ++static struct qb_attr_code code_tdthresh_mant = QB_CODE(0, 5, 8); ++static uint32_t qbman_thresh_to_value(uint32_t val) ++{ ++ uint32_t m, e; ++ ++ m = qb_attr_code_decode(&code_tdthresh_mant, &val); ++ e = qb_attr_code_decode(&code_tdthresh_exp, &val); ++ return m << e; ++} ++ ++void qbman_fq_attr_get_tdthresh(struct qbman_attr *d, uint32_t *tdthresh) ++{ ++ uint32_t *p = ATTR32(d); ++ ++ *tdthresh = qbman_thresh_to_value(qb_attr_code_decode(&code_fq_tdthresh, ++ p)); ++} ++ ++void qbman_fq_attr_get_oa(struct qbman_attr *d, ++ int *oa_ics, int *oa_cgr, int32_t *oa_len) ++{ ++ uint32_t *p = ATTR32(d); ++ ++ *oa_ics = !!qb_attr_code_decode(&code_fq_oa_ics, p); ++ *oa_cgr = !!qb_attr_code_decode(&code_fq_oa_cgr, p); ++ *oa_len = qb_attr_code_makesigned(&code_fq_oa_len, ++ qb_attr_code_decode(&code_fq_oa_len, p)); ++} ++ ++void qbman_fq_attr_get_mctl(struct qbman_attr *d, ++ int *bdi, int *ff, int *va, int *ps) ++{ ++ uint32_t *p = ATTR32(d); ++ ++ *bdi = !!qb_attr_code_decode(&code_fq_mctl_bdi, p); ++ *ff = !!qb_attr_code_decode(&code_fq_mctl_ff, p); ++ *va = !!qb_attr_code_decode(&code_fq_mctl_va, p); ++ *ps = !!qb_attr_code_decode(&code_fq_mctl_ps, p); ++} ++ ++void qbman_fq_attr_get_ctx(struct qbman_attr *d, uint32_t *hi, uint32_t *lo) ++{ ++ uint32_t *p = ATTR32(d); ++ ++ *hi = qb_attr_code_decode(&code_fq_ctx_upper32, p); ++ *lo = qb_attr_code_decode(&code_fq_ctx_lower32, p); ++} ++ ++void qbman_fq_attr_get_icid(struct qbman_attr *d, uint32_t *icid, int *pl) ++{ ++ uint32_t *p = ATTR32(d); ++ ++ *icid = qb_attr_code_decode(&code_fq_icid, p); ++ *pl = !!qb_attr_code_decode(&code_fq_pl, p); ++} ++ ++void qbman_fq_attr_get_vfqid(struct qbman_attr *d, uint32_t *vfqid) ++{ ++ uint32_t *p = ATTR32(d); ++ ++ *vfqid = qb_attr_code_decode(&code_fq_vfqid, p); ++} ++ ++void qbman_fq_attr_get_erfqid(struct qbman_attr *d, uint32_t *erfqid) ++{ ++ uint32_t *p = ATTR32(d); ++ ++ *erfqid = qb_attr_code_decode(&code_fq_erfqid, p); ++} ++ ++/* Query FQ Non-Programmalbe Fields */ ++static struct qb_attr_code code_fq_np_state = QB_CODE(0, 16, 3); ++static struct qb_attr_code code_fq_np_fe = QB_CODE(0, 19, 1); ++static struct qb_attr_code code_fq_np_x = QB_CODE(0, 20, 1); ++static struct qb_attr_code code_fq_np_r = QB_CODE(0, 21, 1); ++static struct qb_attr_code code_fq_np_oe = QB_CODE(0, 22, 1); ++static struct qb_attr_code code_fq_np_frm_cnt = QB_CODE(6, 0, 24); ++static struct qb_attr_code code_fq_np_byte_cnt = QB_CODE(7, 0, 32); ++ ++int qbman_fq_query_state(struct qbman_swp *s, uint32_t fqid, ++ struct qbman_attr *state) ++{ ++ uint32_t *p; ++ uint32_t verb, rslt; ++ uint32_t *d = ATTR32(state); ++ ++ qbman_fq_attr_clear(state); ++ ++ p = qbman_swp_mc_start(s); ++ if (!p) ++ return -EBUSY; ++ qb_attr_code_encode(&code_fq_fqid, p, fqid); ++ p = qbman_swp_mc_complete(s, p, QBMAN_FQ_QUERY_NP); ++ ++ /* Decode the outcome */ ++ verb = qb_attr_code_decode(&code_generic_verb, p); ++ rslt = qb_attr_code_decode(&code_generic_rslt, p); ++ BUG_ON(verb != QBMAN_FQ_QUERY_NP); ++ ++ /* Determine success or failure */ ++ if (unlikely(rslt != QBMAN_MC_RSLT_OK)) { ++ pr_err("Query NP fields of FQID 0x%x failed, code=0x%02x\n", ++ fqid, rslt); ++ return -EIO; ++ } ++ word_copy(&d[0], &p[0], 16); ++ return 0; ++} ++ ++uint32_t qbman_fq_state_schedstate(const struct qbman_attr *state) ++{ ++ const uint32_t *p = ATTR32(state); ++ ++ return qb_attr_code_decode(&code_fq_np_state, p); ++} ++ ++int qbman_fq_state_force_eligible(const struct qbman_attr *state) ++{ ++ const uint32_t *p = ATTR32(state); ++ ++ return !!qb_attr_code_decode(&code_fq_np_fe, p); ++} ++ ++int qbman_fq_state_xoff(const struct qbman_attr *state) ++{ ++ const uint32_t *p = ATTR32(state); ++ ++ return !!qb_attr_code_decode(&code_fq_np_x, p); ++} ++ ++int qbman_fq_state_retirement_pending(const struct qbman_attr *state) ++{ ++ const uint32_t *p = ATTR32(state); ++ ++ return !!qb_attr_code_decode(&code_fq_np_r, p); ++} ++ ++int qbman_fq_state_overflow_error(const struct qbman_attr *state) ++{ ++ const uint32_t *p = ATTR32(state); ++ ++ return !!qb_attr_code_decode(&code_fq_np_oe, p); ++} ++ ++uint32_t qbman_fq_state_frame_count(const struct qbman_attr *state) ++{ ++ const uint32_t *p = ATTR32(state); ++ ++ return qb_attr_code_decode(&code_fq_np_frm_cnt, p); ++} ++ ++uint32_t qbman_fq_state_byte_count(const struct qbman_attr *state) ++{ ++ const uint32_t *p = ATTR32(state); ++ ++ return qb_attr_code_decode(&code_fq_np_byte_cnt, p); ++} ++ ++/* Query CGR */ ++static struct qb_attr_code code_cgr_cgid = QB_CODE(0, 16, 16); ++static struct qb_attr_code code_cgr_cscn_wq_en_enter = QB_CODE(2, 0, 1); ++static struct qb_attr_code code_cgr_cscn_wq_en_exit = QB_CODE(2, 1, 1); ++static struct qb_attr_code code_cgr_cscn_wq_icd = QB_CODE(2, 2, 1); ++static struct qb_attr_code code_cgr_mode = QB_CODE(3, 16, 2); ++static struct qb_attr_code code_cgr_rej_cnt_mode = QB_CODE(3, 18, 1); ++static struct qb_attr_code code_cgr_cscn_bdi = QB_CODE(3, 19, 1); ++static struct qb_attr_code code_cgr_cscn_wr_en_enter = QB_CODE(3, 24, 1); ++static struct qb_attr_code code_cgr_cscn_wr_en_exit = QB_CODE(3, 25, 1); ++static struct qb_attr_code code_cgr_cg_wr_ae = QB_CODE(3, 26, 1); ++static struct qb_attr_code code_cgr_cscn_dcp_en = QB_CODE(3, 27, 1); ++static struct qb_attr_code code_cgr_cg_wr_va = QB_CODE(3, 28, 1); ++static struct qb_attr_code code_cgr_i_cnt_wr_en = QB_CODE(4, 0, 1); ++static struct qb_attr_code code_cgr_i_cnt_wr_bnd = QB_CODE(4, 1, 5); ++static struct qb_attr_code code_cgr_td_en = QB_CODE(4, 8, 1); ++static struct qb_attr_code code_cgr_cs_thres = QB_CODE(4, 16, 13); ++static struct qb_attr_code code_cgr_cs_thres_x = QB_CODE(5, 0, 13); ++static struct qb_attr_code code_cgr_td_thres = QB_CODE(5, 16, 13); ++static struct qb_attr_code code_cgr_cscn_tdcp = QB_CODE(6, 0, 16); ++static struct qb_attr_code code_cgr_cscn_wqid = QB_CODE(6, 16, 16); ++static struct qb_attr_code code_cgr_cscn_vcgid = QB_CODE(7, 0, 16); ++static struct qb_attr_code code_cgr_cg_icid = QB_CODE(7, 16, 15); ++static struct qb_attr_code code_cgr_cg_pl = QB_CODE(7, 31, 1); ++static struct qb_attr_code code_cgr_cg_wr_addr_lo = QB_CODE(8, 0, 32); ++static struct qb_attr_code code_cgr_cg_wr_addr_hi = QB_CODE(9, 0, 32); ++static struct qb_attr_code code_cgr_cscn_ctx_lo = QB_CODE(10, 0, 32); ++static struct qb_attr_code code_cgr_cscn_ctx_hi = QB_CODE(11, 0, 32); ++ ++void qbman_cgr_attr_clear(struct qbman_attr *a) ++{ ++ memset(a, 0, sizeof(*a)); ++ attr_type_set(a, qbman_attr_usage_cgr); ++} ++ ++int qbman_cgr_query(struct qbman_swp *s, uint32_t cgid, struct qbman_attr *attr) ++{ ++ uint32_t *p; ++ uint32_t verb, rslt; ++ uint32_t *d[2]; ++ int i; ++ uint32_t query_verb; ++ ++ d[0] = ATTR32(attr); ++ d[1] = ATTR32_1(attr); ++ ++ qbman_cgr_attr_clear(attr); ++ ++ for (i = 0; i < 2; i++) { ++ p = qbman_swp_mc_start(s); ++ if (!p) ++ return -EBUSY; ++ query_verb = i ? QBMAN_WRED_QUERY : QBMAN_CGR_QUERY; ++ ++ qb_attr_code_encode(&code_cgr_cgid, p, cgid); ++ p = qbman_swp_mc_complete(s, p, p[0] | query_verb); ++ ++ /* Decode the outcome */ ++ verb = qb_attr_code_decode(&code_generic_verb, p); ++ rslt = qb_attr_code_decode(&code_generic_rslt, p); ++ BUG_ON(verb != query_verb); ++ ++ /* Determine success or failure */ ++ if (unlikely(rslt != QBMAN_MC_RSLT_OK)) { ++ pr_err("Query CGID 0x%x failed,", cgid); ++ pr_err(" verb=0x%02x, code=0x%02x\n", verb, rslt); ++ return -EIO; ++ } ++ /* For the configure, word[0] of the command contains only the ++ * verb/cgid. For the query, word[0] of the result contains ++ * only the verb/rslt fields. Skip word[0] in the latter case. ++ */ ++ word_copy(&d[i][1], &p[1], 15); ++ } ++ return 0; ++} ++ ++void qbman_cgr_attr_get_ctl1(struct qbman_attr *d, int *cscn_wq_en_enter, ++ int *cscn_wq_en_exit, int *cscn_wq_icd) ++ { ++ uint32_t *p = ATTR32(d); ++ *cscn_wq_en_enter = !!qb_attr_code_decode(&code_cgr_cscn_wq_en_enter, ++ p); ++ *cscn_wq_en_exit = !!qb_attr_code_decode(&code_cgr_cscn_wq_en_exit, p); ++ *cscn_wq_icd = !!qb_attr_code_decode(&code_cgr_cscn_wq_icd, p); ++} ++ ++void qbman_cgr_attr_get_mode(struct qbman_attr *d, uint32_t *mode, ++ int *rej_cnt_mode, int *cscn_bdi) ++{ ++ uint32_t *p = ATTR32(d); ++ *mode = qb_attr_code_decode(&code_cgr_mode, p); ++ *rej_cnt_mode = !!qb_attr_code_decode(&code_cgr_rej_cnt_mode, p); ++ *cscn_bdi = !!qb_attr_code_decode(&code_cgr_cscn_bdi, p); ++} ++ ++void qbman_cgr_attr_get_ctl2(struct qbman_attr *d, int *cscn_wr_en_enter, ++ int *cscn_wr_en_exit, int *cg_wr_ae, ++ int *cscn_dcp_en, int *cg_wr_va) ++{ ++ uint32_t *p = ATTR32(d); ++ *cscn_wr_en_enter = !!qb_attr_code_decode(&code_cgr_cscn_wr_en_enter, ++ p); ++ *cscn_wr_en_exit = !!qb_attr_code_decode(&code_cgr_cscn_wr_en_exit, p); ++ *cg_wr_ae = !!qb_attr_code_decode(&code_cgr_cg_wr_ae, p); ++ *cscn_dcp_en = !!qb_attr_code_decode(&code_cgr_cscn_dcp_en, p); ++ *cg_wr_va = !!qb_attr_code_decode(&code_cgr_cg_wr_va, p); ++} ++ ++void qbman_cgr_attr_get_iwc(struct qbman_attr *d, int *i_cnt_wr_en, ++ uint32_t *i_cnt_wr_bnd) ++{ ++ uint32_t *p = ATTR32(d); ++ *i_cnt_wr_en = !!qb_attr_code_decode(&code_cgr_i_cnt_wr_en, p); ++ *i_cnt_wr_bnd = qb_attr_code_decode(&code_cgr_i_cnt_wr_bnd, p); ++} ++ ++void qbman_cgr_attr_get_tdc(struct qbman_attr *d, int *td_en) ++{ ++ uint32_t *p = ATTR32(d); ++ *td_en = !!qb_attr_code_decode(&code_cgr_td_en, p); ++} ++ ++void qbman_cgr_attr_get_cs_thres(struct qbman_attr *d, uint32_t *cs_thres) ++{ ++ uint32_t *p = ATTR32(d); ++ *cs_thres = qbman_thresh_to_value(qb_attr_code_decode( ++ &code_cgr_cs_thres, p)); ++} ++ ++void qbman_cgr_attr_get_cs_thres_x(struct qbman_attr *d, ++ uint32_t *cs_thres_x) ++{ ++ uint32_t *p = ATTR32(d); ++ *cs_thres_x = qbman_thresh_to_value(qb_attr_code_decode( ++ &code_cgr_cs_thres_x, p)); ++} ++ ++void qbman_cgr_attr_get_td_thres(struct qbman_attr *d, uint32_t *td_thres) ++{ ++ uint32_t *p = ATTR32(d); ++ *td_thres = qbman_thresh_to_value(qb_attr_code_decode( ++ &code_cgr_td_thres, p)); ++} ++ ++void qbman_cgr_attr_get_cscn_tdcp(struct qbman_attr *d, uint32_t *cscn_tdcp) ++{ ++ uint32_t *p = ATTR32(d); ++ *cscn_tdcp = qb_attr_code_decode(&code_cgr_cscn_tdcp, p); ++} ++ ++void qbman_cgr_attr_get_cscn_wqid(struct qbman_attr *d, uint32_t *cscn_wqid) ++{ ++ uint32_t *p = ATTR32(d); ++ *cscn_wqid = qb_attr_code_decode(&code_cgr_cscn_wqid, p); ++} ++ ++void qbman_cgr_attr_get_cscn_vcgid(struct qbman_attr *d, ++ uint32_t *cscn_vcgid) ++{ ++ uint32_t *p = ATTR32(d); ++ *cscn_vcgid = qb_attr_code_decode(&code_cgr_cscn_vcgid, p); ++} ++ ++void qbman_cgr_attr_get_cg_icid(struct qbman_attr *d, uint32_t *icid, ++ int *pl) ++{ ++ uint32_t *p = ATTR32(d); ++ *icid = qb_attr_code_decode(&code_cgr_cg_icid, p); ++ *pl = !!qb_attr_code_decode(&code_cgr_cg_pl, p); ++} ++ ++void qbman_cgr_attr_get_cg_wr_addr(struct qbman_attr *d, ++ uint64_t *cg_wr_addr) ++{ ++ uint32_t *p = ATTR32(d); ++ *cg_wr_addr = ((uint64_t)qb_attr_code_decode(&code_cgr_cg_wr_addr_hi, ++ p) << 32) | ++ (uint64_t)qb_attr_code_decode(&code_cgr_cg_wr_addr_lo, ++ p); ++} ++ ++void qbman_cgr_attr_get_cscn_ctx(struct qbman_attr *d, uint64_t *cscn_ctx) ++{ ++ uint32_t *p = ATTR32(d); ++ *cscn_ctx = ((uint64_t)qb_attr_code_decode(&code_cgr_cscn_ctx_hi, p) ++ << 32) | ++ (uint64_t)qb_attr_code_decode(&code_cgr_cscn_ctx_lo, p); ++} ++ ++#define WRED_EDP_WORD(n) (18 + n/4) ++#define WRED_EDP_OFFSET(n) (8 * (n % 4)) ++#define WRED_PARM_DP_WORD(n) (n + 20) ++#define WRED_WE_EDP(n) (16 + n * 2) ++#define WRED_WE_PARM_DP(n) (17 + n * 2) ++void qbman_cgr_attr_wred_get_edp(struct qbman_attr *d, uint32_t idx, ++ int *edp) ++{ ++ uint32_t *p = ATTR32(d); ++ struct qb_attr_code code_wred_edp = QB_CODE(WRED_EDP_WORD(idx), ++ WRED_EDP_OFFSET(idx), 8); ++ *edp = (int)qb_attr_code_decode(&code_wred_edp, p); ++} ++ ++void qbman_cgr_attr_wred_dp_decompose(uint32_t dp, uint64_t *minth, ++ uint64_t *maxth, uint8_t *maxp) ++{ ++ uint8_t ma, mn, step_i, step_s, pn; ++ ++ ma = (uint8_t)(dp >> 24); ++ mn = (uint8_t)(dp >> 19) & 0x1f; ++ step_i = (uint8_t)(dp >> 11); ++ step_s = (uint8_t)(dp >> 6) & 0x1f; ++ pn = (uint8_t)dp & 0x3f; ++ ++ *maxp = ((pn<<2) * 100)/256; ++ ++ if (mn == 0) ++ *maxth = ma; ++ else ++ *maxth = ((ma+256) * (1<<(mn-1))); ++ ++ if (step_s == 0) ++ *minth = *maxth - step_i; ++ else ++ *minth = *maxth - (256 + step_i) * (1<<(step_s - 1)); ++} ++ ++void qbman_cgr_attr_wred_get_parm_dp(struct qbman_attr *d, uint32_t idx, ++ uint32_t *dp) ++{ ++ uint32_t *p = ATTR32(d); ++ struct qb_attr_code code_wred_parm_dp = QB_CODE(WRED_PARM_DP_WORD(idx), ++ 0, 8); ++ *dp = qb_attr_code_decode(&code_wred_parm_dp, p); ++} ++ ++/* Query CGR/CCGR/CQ statistics */ ++static struct qb_attr_code code_cgr_stat_ct = QB_CODE(4, 0, 32); ++static struct qb_attr_code code_cgr_stat_frame_cnt_lo = QB_CODE(4, 0, 32); ++static struct qb_attr_code code_cgr_stat_frame_cnt_hi = QB_CODE(5, 0, 8); ++static struct qb_attr_code code_cgr_stat_byte_cnt_lo = QB_CODE(6, 0, 32); ++static struct qb_attr_code code_cgr_stat_byte_cnt_hi = QB_CODE(7, 0, 16); ++static int qbman_cgr_statistics_query(struct qbman_swp *s, uint32_t cgid, ++ int clear, uint32_t command_type, ++ uint64_t *frame_cnt, uint64_t *byte_cnt) ++{ ++ uint32_t *p; ++ uint32_t verb, rslt; ++ uint32_t query_verb; ++ uint32_t hi, lo; ++ ++ p = qbman_swp_mc_start(s); ++ if (!p) ++ return -EBUSY; ++ ++ qb_attr_code_encode(&code_cgr_cgid, p, cgid); ++ if (command_type < 2) ++ qb_attr_code_encode(&code_cgr_stat_ct, p, command_type); ++ query_verb = clear ? ++ QBMAN_CGR_STAT_QUERY_CLR : QBMAN_CGR_STAT_QUERY; ++ p = qbman_swp_mc_complete(s, p, p[0] | query_verb); ++ ++ /* Decode the outcome */ ++ verb = qb_attr_code_decode(&code_generic_verb, p); ++ rslt = qb_attr_code_decode(&code_generic_rslt, p); ++ BUG_ON(verb != query_verb); ++ ++ /* Determine success or failure */ ++ if (unlikely(rslt != QBMAN_MC_RSLT_OK)) { ++ pr_err("Query statistics of CGID 0x%x failed,", cgid); ++ pr_err(" verb=0x%02x code=0x%02x\n", verb, rslt); ++ return -EIO; ++ } ++ ++ if (*frame_cnt) { ++ hi = qb_attr_code_decode(&code_cgr_stat_frame_cnt_hi, p); ++ lo = qb_attr_code_decode(&code_cgr_stat_frame_cnt_lo, p); ++ *frame_cnt = ((uint64_t)hi << 32) | (uint64_t)lo; ++ } ++ if (*byte_cnt) { ++ hi = qb_attr_code_decode(&code_cgr_stat_byte_cnt_hi, p); ++ lo = qb_attr_code_decode(&code_cgr_stat_byte_cnt_lo, p); ++ *byte_cnt = ((uint64_t)hi << 32) | (uint64_t)lo; ++ } ++ ++ return 0; ++} ++ ++int qbman_cgr_reject_statistics(struct qbman_swp *s, uint32_t cgid, int clear, ++ uint64_t *frame_cnt, uint64_t *byte_cnt) ++{ ++ return qbman_cgr_statistics_query(s, cgid, clear, 0xff, ++ frame_cnt, byte_cnt); ++} ++ ++int qbman_ccgr_reject_statistics(struct qbman_swp *s, uint32_t cgid, int clear, ++ uint64_t *frame_cnt, uint64_t *byte_cnt) ++{ ++ return qbman_cgr_statistics_query(s, cgid, clear, 1, ++ frame_cnt, byte_cnt); ++} ++ ++int qbman_cq_dequeue_statistics(struct qbman_swp *s, uint32_t cgid, int clear, ++ uint64_t *frame_cnt, uint64_t *byte_cnt) ++{ ++ return qbman_cgr_statistics_query(s, cgid, clear, 0, ++ frame_cnt, byte_cnt); ++} +diff --git a/drivers/staging/fsl-mc/bus/dpio/qbman_debug.h b/drivers/staging/fsl-mc/bus/dpio/qbman_debug.h +new file mode 100644 +index 0000000..1e6b002 +--- /dev/null ++++ b/drivers/staging/fsl-mc/bus/dpio/qbman_debug.h +@@ -0,0 +1,136 @@ ++/* Copyright (C) 2015 Freescale Semiconductor, Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * * Neither the name of Freescale Semiconductor nor the ++ * names of its contributors may be used to endorse or promote products ++ * derived from this software without specific prior written permission. ++ * ++ * ++ * ALTERNATIVELY, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") as published by the Free Software ++ * Foundation, either version 2 of that License or (at your option) any ++ * later version. ++ * ++ * THIS SOFTWARE IS PROVIDED BY Freescale Semiconductor ``AS IS'' AND ANY ++ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ++ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE ++ * DISCLAIMED. IN NO EVENT SHALL Freescale Semiconductor BE LIABLE FOR ANY ++ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ++ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; ++ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ++ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ++ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS ++ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++struct qbman_attr { ++ uint32_t dont_manipulate_directly[40]; ++}; ++ ++/* Buffer pool query commands */ ++int qbman_bp_query(struct qbman_swp *s, uint32_t bpid, ++ struct qbman_attr *a); ++void qbman_bp_attr_get_bdi(struct qbman_attr *a, int *bdi, int *va, int *wae); ++void qbman_bp_attr_get_swdet(struct qbman_attr *a, uint32_t *swdet); ++void qbman_bp_attr_get_swdxt(struct qbman_attr *a, uint32_t *swdxt); ++void qbman_bp_attr_get_hwdet(struct qbman_attr *a, uint32_t *hwdet); ++void qbman_bp_attr_get_hwdxt(struct qbman_attr *a, uint32_t *hwdxt); ++void qbman_bp_attr_get_swset(struct qbman_attr *a, uint32_t *swset); ++void qbman_bp_attr_get_swsxt(struct qbman_attr *a, uint32_t *swsxt); ++void qbman_bp_attr_get_vbpid(struct qbman_attr *a, uint32_t *vbpid); ++void qbman_bp_attr_get_icid(struct qbman_attr *a, uint32_t *icid, int *pl); ++void qbman_bp_attr_get_bpscn_addr(struct qbman_attr *a, uint64_t *bpscn_addr); ++void qbman_bp_attr_get_bpscn_ctx(struct qbman_attr *a, uint64_t *bpscn_ctx); ++void qbman_bp_attr_get_hw_targ(struct qbman_attr *a, uint32_t *hw_targ); ++int qbman_bp_info_has_free_bufs(struct qbman_attr *a); ++int qbman_bp_info_is_depleted(struct qbman_attr *a); ++int qbman_bp_info_is_surplus(struct qbman_attr *a); ++uint32_t qbman_bp_info_num_free_bufs(struct qbman_attr *a); ++uint32_t qbman_bp_info_hdptr(struct qbman_attr *a); ++uint32_t qbman_bp_info_sdcnt(struct qbman_attr *a); ++uint32_t qbman_bp_info_hdcnt(struct qbman_attr *a); ++uint32_t qbman_bp_info_sscnt(struct qbman_attr *a); ++ ++/* FQ query function for programmable fields */ ++int qbman_fq_query(struct qbman_swp *s, uint32_t fqid, ++ struct qbman_attr *desc); ++void qbman_fq_attr_get_fqctrl(struct qbman_attr *d, uint32_t *fqctrl); ++void qbman_fq_attr_get_cgrid(struct qbman_attr *d, uint32_t *cgrid); ++void qbman_fq_attr_get_destwq(struct qbman_attr *d, uint32_t *destwq); ++void qbman_fq_attr_get_icscred(struct qbman_attr *d, uint32_t *icscred); ++void qbman_fq_attr_get_tdthresh(struct qbman_attr *d, uint32_t *tdthresh); ++void qbman_fq_attr_get_oa(struct qbman_attr *d, ++ int *oa_ics, int *oa_cgr, int32_t *oa_len); ++void qbman_fq_attr_get_mctl(struct qbman_attr *d, ++ int *bdi, int *ff, int *va, int *ps); ++void qbman_fq_attr_get_ctx(struct qbman_attr *d, uint32_t *hi, uint32_t *lo); ++void qbman_fq_attr_get_icid(struct qbman_attr *d, uint32_t *icid, int *pl); ++void qbman_fq_attr_get_vfqid(struct qbman_attr *d, uint32_t *vfqid); ++void qbman_fq_attr_get_erfqid(struct qbman_attr *d, uint32_t *erfqid); ++ ++/* FQ query command for non-programmable fields*/ ++enum qbman_fq_schedstate_e { ++ qbman_fq_schedstate_oos = 0, ++ qbman_fq_schedstate_retired, ++ qbman_fq_schedstate_tentatively_scheduled, ++ qbman_fq_schedstate_truly_scheduled, ++ qbman_fq_schedstate_parked, ++ qbman_fq_schedstate_held_active, ++}; ++ ++int qbman_fq_query_state(struct qbman_swp *s, uint32_t fqid, ++ struct qbman_attr *state); ++uint32_t qbman_fq_state_schedstate(const struct qbman_attr *state); ++int qbman_fq_state_force_eligible(const struct qbman_attr *state); ++int qbman_fq_state_xoff(const struct qbman_attr *state); ++int qbman_fq_state_retirement_pending(const struct qbman_attr *state); ++int qbman_fq_state_overflow_error(const struct qbman_attr *state); ++uint32_t qbman_fq_state_frame_count(const struct qbman_attr *state); ++uint32_t qbman_fq_state_byte_count(const struct qbman_attr *state); ++ ++/* CGR query */ ++int qbman_cgr_query(struct qbman_swp *s, uint32_t cgid, ++ struct qbman_attr *attr); ++void qbman_cgr_attr_get_ctl1(struct qbman_attr *d, int *cscn_wq_en_enter, ++ int *cscn_wq_en_exit, int *cscn_wq_icd); ++void qbman_cgr_attr_get_mode(struct qbman_attr *d, uint32_t *mode, ++ int *rej_cnt_mode, int *cscn_bdi); ++void qbman_cgr_attr_get_ctl2(struct qbman_attr *d, int *cscn_wr_en_enter, ++ int *cscn_wr_en_exit, int *cg_wr_ae, ++ int *cscn_dcp_en, int *cg_wr_va); ++void qbman_cgr_attr_get_iwc(struct qbman_attr *d, int *i_cnt_wr_en, ++ uint32_t *i_cnt_wr_bnd); ++void qbman_cgr_attr_get_tdc(struct qbman_attr *d, int *td_en); ++void qbman_cgr_attr_get_cs_thres(struct qbman_attr *d, uint32_t *cs_thres); ++void qbman_cgr_attr_get_cs_thres_x(struct qbman_attr *d, ++ uint32_t *cs_thres_x); ++void qbman_cgr_attr_get_td_thres(struct qbman_attr *d, uint32_t *td_thres); ++void qbman_cgr_attr_get_cscn_tdcp(struct qbman_attr *d, uint32_t *cscn_tdcp); ++void qbman_cgr_attr_get_cscn_wqid(struct qbman_attr *d, uint32_t *cscn_wqid); ++void qbman_cgr_attr_get_cscn_vcgid(struct qbman_attr *d, ++ uint32_t *cscn_vcgid); ++void qbman_cgr_attr_get_cg_icid(struct qbman_attr *d, uint32_t *icid, ++ int *pl); ++void qbman_cgr_attr_get_cg_wr_addr(struct qbman_attr *d, ++ uint64_t *cg_wr_addr); ++void qbman_cgr_attr_get_cscn_ctx(struct qbman_attr *d, uint64_t *cscn_ctx); ++void qbman_cgr_attr_wred_get_edp(struct qbman_attr *d, uint32_t idx, ++ int *edp); ++void qbman_cgr_attr_wred_dp_decompose(uint32_t dp, uint64_t *minth, ++ uint64_t *maxth, uint8_t *maxp); ++void qbman_cgr_attr_wred_get_parm_dp(struct qbman_attr *d, uint32_t idx, ++ uint32_t *dp); ++ ++/* CGR/CCGR/CQ statistics query */ ++int qbman_cgr_reject_statistics(struct qbman_swp *s, uint32_t cgid, int clear, ++ uint64_t *frame_cnt, uint64_t *byte_cnt); ++int qbman_ccgr_reject_statistics(struct qbman_swp *s, uint32_t cgid, int clear, ++ uint64_t *frame_cnt, uint64_t *byte_cnt); ++int qbman_cq_dequeue_statistics(struct qbman_swp *s, uint32_t cgid, int clear, ++ uint64_t *frame_cnt, uint64_t *byte_cnt); +diff --git a/drivers/staging/fsl-mc/bus/dpio/qbman_portal.c b/drivers/staging/fsl-mc/bus/dpio/qbman_portal.c +new file mode 100644 +index 0000000..6c5638b +--- /dev/null ++++ b/drivers/staging/fsl-mc/bus/dpio/qbman_portal.c +@@ -0,0 +1,1212 @@ ++/* Copyright (C) 2014 Freescale Semiconductor, Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * * Neither the name of Freescale Semiconductor nor the ++ * names of its contributors may be used to endorse or promote products ++ * derived from this software without specific prior written permission. ++ * ++ * ++ * ALTERNATIVELY, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") as published by the Free Software ++ * Foundation, either version 2 of that License or (at your option) any ++ * later version. ++ * ++ * THIS SOFTWARE IS PROVIDED BY Freescale Semiconductor ``AS IS'' AND ANY ++ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ++ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE ++ * DISCLAIMED. IN NO EVENT SHALL Freescale Semiconductor BE LIABLE FOR ANY ++ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ++ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; ++ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ++ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ++ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS ++ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++#include "qbman_portal.h" ++ ++/* QBMan portal management command codes */ ++#define QBMAN_MC_ACQUIRE 0x30 ++#define QBMAN_WQCHAN_CONFIGURE 0x46 ++ ++/* CINH register offsets */ ++#define QBMAN_CINH_SWP_EQAR 0x8c0 ++#define QBMAN_CINH_SWP_DQPI 0xa00 ++#define QBMAN_CINH_SWP_DCAP 0xac0 ++#define QBMAN_CINH_SWP_SDQCR 0xb00 ++#define QBMAN_CINH_SWP_RAR 0xcc0 ++#define QBMAN_CINH_SWP_ISR 0xe00 ++#define QBMAN_CINH_SWP_IER 0xe40 ++#define QBMAN_CINH_SWP_ISDR 0xe80 ++#define QBMAN_CINH_SWP_IIR 0xec0 ++ ++/* CENA register offsets */ ++#define QBMAN_CENA_SWP_EQCR(n) (0x000 + ((uint32_t)(n) << 6)) ++#define QBMAN_CENA_SWP_DQRR(n) (0x200 + ((uint32_t)(n) << 6)) ++#define QBMAN_CENA_SWP_RCR(n) (0x400 + ((uint32_t)(n) << 6)) ++#define QBMAN_CENA_SWP_CR 0x600 ++#define QBMAN_CENA_SWP_RR(vb) (0x700 + ((uint32_t)(vb) >> 1)) ++#define QBMAN_CENA_SWP_VDQCR 0x780 ++ ++/* Reverse mapping of QBMAN_CENA_SWP_DQRR() */ ++#define QBMAN_IDX_FROM_DQRR(p) (((unsigned long)p & 0x1ff) >> 6) ++ ++/* QBMan FQ management command codes */ ++#define QBMAN_FQ_SCHEDULE 0x48 ++#define QBMAN_FQ_FORCE 0x49 ++#define QBMAN_FQ_XON 0x4d ++#define QBMAN_FQ_XOFF 0x4e ++ ++/*******************************/ ++/* Pre-defined attribute codes */ ++/*******************************/ ++ ++struct qb_attr_code code_generic_verb = QB_CODE(0, 0, 7); ++struct qb_attr_code code_generic_rslt = QB_CODE(0, 8, 8); ++ ++/*************************/ ++/* SDQCR attribute codes */ ++/*************************/ ++ ++/* we put these here because at least some of them are required by ++ * qbman_swp_init() */ ++struct qb_attr_code code_sdqcr_dct = QB_CODE(0, 24, 2); ++struct qb_attr_code code_sdqcr_fc = QB_CODE(0, 29, 1); ++struct qb_attr_code code_sdqcr_tok = QB_CODE(0, 16, 8); ++#define CODE_SDQCR_DQSRC(n) QB_CODE(0, n, 1) ++enum qbman_sdqcr_dct { ++ qbman_sdqcr_dct_null = 0, ++ qbman_sdqcr_dct_prio_ics, ++ qbman_sdqcr_dct_active_ics, ++ qbman_sdqcr_dct_active ++}; ++enum qbman_sdqcr_fc { ++ qbman_sdqcr_fc_one = 0, ++ qbman_sdqcr_fc_up_to_3 = 1 ++}; ++struct qb_attr_code code_sdqcr_dqsrc = QB_CODE(0, 0, 16); ++ ++/*********************************/ ++/* Portal constructor/destructor */ ++/*********************************/ ++ ++/* Software portals should always be in the power-on state when we initialise, ++ * due to the CCSR-based portal reset functionality that MC has. ++ * ++ * Erk! Turns out that QMan versions prior to 4.1 do not correctly reset DQRR ++ * valid-bits, so we need to support a workaround where we don't trust ++ * valid-bits when detecting new entries until any stale ring entries have been ++ * overwritten at least once. The idea is that we read PI for the first few ++ * entries, then switch to valid-bit after that. The trick is to clear the ++ * bug-work-around boolean once the PI wraps around the ring for the first time. ++ * ++ * Note: this still carries a slight additional cost once the decrementer hits ++ * zero, so ideally the workaround should only be compiled in if the compiled ++ * image needs to support affected chips. We use WORKAROUND_DQRR_RESET_BUG for ++ * this. ++ */ ++struct qbman_swp *qbman_swp_init(const struct qbman_swp_desc *d) ++{ ++ int ret; ++ struct qbman_swp *p = kmalloc(sizeof(*p), GFP_KERNEL); ++ ++ if (!p) ++ return NULL; ++ p->desc = d; ++#ifdef QBMAN_CHECKING ++ p->mc.check = swp_mc_can_start; ++#endif ++ p->mc.valid_bit = QB_VALID_BIT; ++ p->sdq = 0; ++ qb_attr_code_encode(&code_sdqcr_dct, &p->sdq, qbman_sdqcr_dct_prio_ics); ++ qb_attr_code_encode(&code_sdqcr_fc, &p->sdq, qbman_sdqcr_fc_up_to_3); ++ qb_attr_code_encode(&code_sdqcr_tok, &p->sdq, 0xbb); ++ atomic_set(&p->vdq.busy, 1); ++ p->vdq.valid_bit = QB_VALID_BIT; ++ p->dqrr.next_idx = 0; ++ p->dqrr.valid_bit = QB_VALID_BIT; ++ /* TODO: should also read PI/CI type registers and check that they're on ++ * PoR values. If we're asked to initialise portals that aren't in reset ++ * state, bad things will follow. */ ++#ifdef WORKAROUND_DQRR_RESET_BUG ++ p->dqrr.reset_bug = 1; ++#endif ++ if ((p->desc->qman_version & 0xFFFF0000) < QMAN_REV_4100) ++ p->dqrr.dqrr_size = 4; ++ else ++ p->dqrr.dqrr_size = 8; ++ ret = qbman_swp_sys_init(&p->sys, d, p->dqrr.dqrr_size); ++ if (ret) { ++ kfree(p); ++ pr_err("qbman_swp_sys_init() failed %d\n", ret); ++ return NULL; ++ } ++ /* SDQCR needs to be initialized to 0 when no channels are ++ being dequeued from or else the QMan HW will indicate an ++ error. The values that were calculated above will be ++ applied when dequeues from a specific channel are enabled */ ++ qbman_cinh_write(&p->sys, QBMAN_CINH_SWP_SDQCR, 0); ++ return p; ++} ++ ++void qbman_swp_finish(struct qbman_swp *p) ++{ ++#ifdef QBMAN_CHECKING ++ BUG_ON(p->mc.check != swp_mc_can_start); ++#endif ++ qbman_swp_sys_finish(&p->sys); ++ kfree(p); ++} ++ ++const struct qbman_swp_desc *qbman_swp_get_desc(struct qbman_swp *p) ++{ ++ return p->desc; ++} ++ ++/**************/ ++/* Interrupts */ ++/**************/ ++ ++uint32_t qbman_swp_interrupt_get_vanish(struct qbman_swp *p) ++{ ++ return qbman_cinh_read(&p->sys, QBMAN_CINH_SWP_ISDR); ++} ++ ++void qbman_swp_interrupt_set_vanish(struct qbman_swp *p, uint32_t mask) ++{ ++ qbman_cinh_write(&p->sys, QBMAN_CINH_SWP_ISDR, mask); ++} ++ ++uint32_t qbman_swp_interrupt_read_status(struct qbman_swp *p) ++{ ++ return qbman_cinh_read(&p->sys, QBMAN_CINH_SWP_ISR); ++} ++ ++void qbman_swp_interrupt_clear_status(struct qbman_swp *p, uint32_t mask) ++{ ++ qbman_cinh_write(&p->sys, QBMAN_CINH_SWP_ISR, mask); ++} ++ ++uint32_t qbman_swp_interrupt_get_trigger(struct qbman_swp *p) ++{ ++ return qbman_cinh_read(&p->sys, QBMAN_CINH_SWP_IER); ++} ++ ++void qbman_swp_interrupt_set_trigger(struct qbman_swp *p, uint32_t mask) ++{ ++ qbman_cinh_write(&p->sys, QBMAN_CINH_SWP_IER, mask); ++} ++ ++int qbman_swp_interrupt_get_inhibit(struct qbman_swp *p) ++{ ++ return qbman_cinh_read(&p->sys, QBMAN_CINH_SWP_IIR); ++} ++ ++void qbman_swp_interrupt_set_inhibit(struct qbman_swp *p, int inhibit) ++{ ++ qbman_cinh_write(&p->sys, QBMAN_CINH_SWP_IIR, inhibit ? 0xffffffff : 0); ++} ++ ++/***********************/ ++/* Management commands */ ++/***********************/ ++ ++/* ++ * Internal code common to all types of management commands. ++ */ ++ ++void *qbman_swp_mc_start(struct qbman_swp *p) ++{ ++ void *ret; ++#ifdef QBMAN_CHECKING ++ BUG_ON(p->mc.check != swp_mc_can_start); ++#endif ++ ret = qbman_cena_write_start(&p->sys, QBMAN_CENA_SWP_CR); ++#ifdef QBMAN_CHECKING ++ if (!ret) ++ p->mc.check = swp_mc_can_submit; ++#endif ++ return ret; ++} ++ ++void qbman_swp_mc_submit(struct qbman_swp *p, void *cmd, uint32_t cmd_verb) ++{ ++ uint32_t *v = cmd; ++#ifdef QBMAN_CHECKING ++ BUG_ON(!p->mc.check != swp_mc_can_submit); ++#endif ++ /* TBD: "|=" is going to hurt performance. Need to move as many fields ++ * out of word zero, and for those that remain, the "OR" needs to occur ++ * at the caller side. This debug check helps to catch cases where the ++ * caller wants to OR but has forgotten to do so. */ ++ BUG_ON((*v & cmd_verb) != *v); ++ *v = cmd_verb | p->mc.valid_bit; ++ qbman_cena_write_complete(&p->sys, QBMAN_CENA_SWP_CR, cmd); ++#ifdef QBMAN_CHECKING ++ p->mc.check = swp_mc_can_poll; ++#endif ++} ++ ++void *qbman_swp_mc_result(struct qbman_swp *p) ++{ ++ uint32_t *ret, verb; ++#ifdef QBMAN_CHECKING ++ BUG_ON(p->mc.check != swp_mc_can_poll); ++#endif ++ qbman_cena_invalidate_prefetch(&p->sys, ++ QBMAN_CENA_SWP_RR(p->mc.valid_bit)); ++ ret = qbman_cena_read(&p->sys, QBMAN_CENA_SWP_RR(p->mc.valid_bit)); ++ /* Remove the valid-bit - command completed iff the rest is non-zero */ ++ verb = ret[0] & ~QB_VALID_BIT; ++ if (!verb) ++ return NULL; ++#ifdef QBMAN_CHECKING ++ p->mc.check = swp_mc_can_start; ++#endif ++ p->mc.valid_bit ^= QB_VALID_BIT; ++ return ret; ++} ++ ++/***********/ ++/* Enqueue */ ++/***********/ ++ ++/* These should be const, eventually */ ++static struct qb_attr_code code_eq_cmd = QB_CODE(0, 0, 2); ++static struct qb_attr_code code_eq_eqdi = QB_CODE(0, 3, 1); ++static struct qb_attr_code code_eq_dca_en = QB_CODE(0, 15, 1); ++static struct qb_attr_code code_eq_dca_pk = QB_CODE(0, 14, 1); ++static struct qb_attr_code code_eq_dca_idx = QB_CODE(0, 8, 2); ++static struct qb_attr_code code_eq_orp_en = QB_CODE(0, 2, 1); ++static struct qb_attr_code code_eq_orp_is_nesn = QB_CODE(0, 31, 1); ++static struct qb_attr_code code_eq_orp_nlis = QB_CODE(0, 30, 1); ++static struct qb_attr_code code_eq_orp_seqnum = QB_CODE(0, 16, 14); ++static struct qb_attr_code code_eq_opr_id = QB_CODE(1, 0, 16); ++static struct qb_attr_code code_eq_tgt_id = QB_CODE(2, 0, 24); ++/* static struct qb_attr_code code_eq_tag = QB_CODE(3, 0, 32); */ ++static struct qb_attr_code code_eq_qd_en = QB_CODE(0, 4, 1); ++static struct qb_attr_code code_eq_qd_bin = QB_CODE(4, 0, 16); ++static struct qb_attr_code code_eq_qd_pri = QB_CODE(4, 16, 4); ++static struct qb_attr_code code_eq_rsp_stash = QB_CODE(5, 16, 1); ++static struct qb_attr_code code_eq_rsp_id = QB_CODE(5, 24, 8); ++static struct qb_attr_code code_eq_rsp_lo = QB_CODE(6, 0, 32); ++ ++enum qbman_eq_cmd_e { ++ /* No enqueue, primarily for plugging ORP gaps for dropped frames */ ++ qbman_eq_cmd_empty, ++ /* DMA an enqueue response once complete */ ++ qbman_eq_cmd_respond, ++ /* DMA an enqueue response only if the enqueue fails */ ++ qbman_eq_cmd_respond_reject ++}; ++ ++void qbman_eq_desc_clear(struct qbman_eq_desc *d) ++{ ++ memset(d, 0, sizeof(*d)); ++} ++ ++void qbman_eq_desc_set_no_orp(struct qbman_eq_desc *d, int respond_success) ++{ ++ uint32_t *cl = qb_cl(d); ++ ++ qb_attr_code_encode(&code_eq_orp_en, cl, 0); ++ qb_attr_code_encode(&code_eq_cmd, cl, ++ respond_success ? qbman_eq_cmd_respond : ++ qbman_eq_cmd_respond_reject); ++} ++ ++void qbman_eq_desc_set_orp(struct qbman_eq_desc *d, int respond_success, ++ uint32_t opr_id, uint32_t seqnum, int incomplete) ++{ ++ uint32_t *cl = qb_cl(d); ++ ++ qb_attr_code_encode(&code_eq_orp_en, cl, 1); ++ qb_attr_code_encode(&code_eq_cmd, cl, ++ respond_success ? qbman_eq_cmd_respond : ++ qbman_eq_cmd_respond_reject); ++ qb_attr_code_encode(&code_eq_opr_id, cl, opr_id); ++ qb_attr_code_encode(&code_eq_orp_seqnum, cl, seqnum); ++ qb_attr_code_encode(&code_eq_orp_nlis, cl, !!incomplete); ++} ++ ++void qbman_eq_desc_set_orp_hole(struct qbman_eq_desc *d, uint32_t opr_id, ++ uint32_t seqnum) ++{ ++ uint32_t *cl = qb_cl(d); ++ ++ qb_attr_code_encode(&code_eq_orp_en, cl, 1); ++ qb_attr_code_encode(&code_eq_cmd, cl, qbman_eq_cmd_empty); ++ qb_attr_code_encode(&code_eq_opr_id, cl, opr_id); ++ qb_attr_code_encode(&code_eq_orp_seqnum, cl, seqnum); ++ qb_attr_code_encode(&code_eq_orp_nlis, cl, 0); ++ qb_attr_code_encode(&code_eq_orp_is_nesn, cl, 0); ++} ++ ++void qbman_eq_desc_set_orp_nesn(struct qbman_eq_desc *d, uint32_t opr_id, ++ uint32_t seqnum) ++{ ++ uint32_t *cl = qb_cl(d); ++ ++ qb_attr_code_encode(&code_eq_orp_en, cl, 1); ++ qb_attr_code_encode(&code_eq_cmd, cl, qbman_eq_cmd_empty); ++ qb_attr_code_encode(&code_eq_opr_id, cl, opr_id); ++ qb_attr_code_encode(&code_eq_orp_seqnum, cl, seqnum); ++ qb_attr_code_encode(&code_eq_orp_nlis, cl, 0); ++ qb_attr_code_encode(&code_eq_orp_is_nesn, cl, 1); ++} ++ ++void qbman_eq_desc_set_response(struct qbman_eq_desc *d, ++ dma_addr_t storage_phys, ++ int stash) ++{ ++ uint32_t *cl = qb_cl(d); ++ ++ qb_attr_code_encode_64(&code_eq_rsp_lo, (uint64_t *)cl, storage_phys); ++ qb_attr_code_encode(&code_eq_rsp_stash, cl, !!stash); ++} ++ ++void qbman_eq_desc_set_token(struct qbman_eq_desc *d, uint8_t token) ++{ ++ uint32_t *cl = qb_cl(d); ++ ++ qb_attr_code_encode(&code_eq_rsp_id, cl, (uint32_t)token); ++} ++ ++void qbman_eq_desc_set_fq(struct qbman_eq_desc *d, uint32_t fqid) ++{ ++ uint32_t *cl = qb_cl(d); ++ ++ qb_attr_code_encode(&code_eq_qd_en, cl, 0); ++ qb_attr_code_encode(&code_eq_tgt_id, cl, fqid); ++} ++ ++void qbman_eq_desc_set_qd(struct qbman_eq_desc *d, uint32_t qdid, ++ uint32_t qd_bin, uint32_t qd_prio) ++{ ++ uint32_t *cl = qb_cl(d); ++ ++ qb_attr_code_encode(&code_eq_qd_en, cl, 1); ++ qb_attr_code_encode(&code_eq_tgt_id, cl, qdid); ++ qb_attr_code_encode(&code_eq_qd_bin, cl, qd_bin); ++ qb_attr_code_encode(&code_eq_qd_pri, cl, qd_prio); ++} ++ ++void qbman_eq_desc_set_eqdi(struct qbman_eq_desc *d, int enable) ++{ ++ uint32_t *cl = qb_cl(d); ++ ++ qb_attr_code_encode(&code_eq_eqdi, cl, !!enable); ++} ++ ++void qbman_eq_desc_set_dca(struct qbman_eq_desc *d, int enable, ++ uint32_t dqrr_idx, int park) ++{ ++ uint32_t *cl = qb_cl(d); ++ ++ qb_attr_code_encode(&code_eq_dca_en, cl, !!enable); ++ if (enable) { ++ qb_attr_code_encode(&code_eq_dca_pk, cl, !!park); ++ qb_attr_code_encode(&code_eq_dca_idx, cl, dqrr_idx); ++ } ++} ++ ++#define EQAR_IDX(eqar) ((eqar) & 0x7) ++#define EQAR_VB(eqar) ((eqar) & 0x80) ++#define EQAR_SUCCESS(eqar) ((eqar) & 0x100) ++ ++int qbman_swp_enqueue(struct qbman_swp *s, const struct qbman_eq_desc *d, ++ const struct qbman_fd *fd) ++{ ++ uint32_t *p; ++ const uint32_t *cl = qb_cl(d); ++ uint32_t eqar = qbman_cinh_read(&s->sys, QBMAN_CINH_SWP_EQAR); ++ ++ pr_debug("EQAR=%08x\n", eqar); ++ if (!EQAR_SUCCESS(eqar)) ++ return -EBUSY; ++ p = qbman_cena_write_start(&s->sys, ++ QBMAN_CENA_SWP_EQCR(EQAR_IDX(eqar))); ++ word_copy(&p[1], &cl[1], 7); ++ word_copy(&p[8], fd, sizeof(*fd) >> 2); ++ /* Set the verb byte, have to substitute in the valid-bit */ ++ p[0] = cl[0] | EQAR_VB(eqar); ++ qbman_cena_write_complete(&s->sys, ++ QBMAN_CENA_SWP_EQCR(EQAR_IDX(eqar)), ++ p); ++ return 0; ++} ++ ++/*************************/ ++/* Static (push) dequeue */ ++/*************************/ ++ ++void qbman_swp_push_get(struct qbman_swp *s, uint8_t channel_idx, int *enabled) ++{ ++ struct qb_attr_code code = CODE_SDQCR_DQSRC(channel_idx); ++ ++ BUG_ON(channel_idx > 15); ++ *enabled = (int)qb_attr_code_decode(&code, &s->sdq); ++} ++ ++void qbman_swp_push_set(struct qbman_swp *s, uint8_t channel_idx, int enable) ++{ ++ uint16_t dqsrc; ++ struct qb_attr_code code = CODE_SDQCR_DQSRC(channel_idx); ++ ++ BUG_ON(channel_idx > 15); ++ qb_attr_code_encode(&code, &s->sdq, !!enable); ++ /* Read make the complete src map. If no channels are enabled ++ the SDQCR must be 0 or else QMan will assert errors */ ++ dqsrc = (uint16_t)qb_attr_code_decode(&code_sdqcr_dqsrc, &s->sdq); ++ if (dqsrc != 0) ++ qbman_cinh_write(&s->sys, QBMAN_CINH_SWP_SDQCR, s->sdq); ++ else ++ qbman_cinh_write(&s->sys, QBMAN_CINH_SWP_SDQCR, 0); ++} ++ ++/***************************/ ++/* Volatile (pull) dequeue */ ++/***************************/ ++ ++/* These should be const, eventually */ ++static struct qb_attr_code code_pull_dct = QB_CODE(0, 0, 2); ++static struct qb_attr_code code_pull_dt = QB_CODE(0, 2, 2); ++static struct qb_attr_code code_pull_rls = QB_CODE(0, 4, 1); ++static struct qb_attr_code code_pull_stash = QB_CODE(0, 5, 1); ++static struct qb_attr_code code_pull_numframes = QB_CODE(0, 8, 4); ++static struct qb_attr_code code_pull_token = QB_CODE(0, 16, 8); ++static struct qb_attr_code code_pull_dqsource = QB_CODE(1, 0, 24); ++static struct qb_attr_code code_pull_rsp_lo = QB_CODE(2, 0, 32); ++ ++enum qb_pull_dt_e { ++ qb_pull_dt_channel, ++ qb_pull_dt_workqueue, ++ qb_pull_dt_framequeue ++}; ++ ++void qbman_pull_desc_clear(struct qbman_pull_desc *d) ++{ ++ memset(d, 0, sizeof(*d)); ++} ++ ++void qbman_pull_desc_set_storage(struct qbman_pull_desc *d, ++ struct dpaa2_dq *storage, ++ dma_addr_t storage_phys, ++ int stash) ++{ ++ uint32_t *cl = qb_cl(d); ++ ++ /* Squiggle the pointer 'storage' into the extra 2 words of the ++ * descriptor (which aren't copied to the hw command) */ ++ *(void **)&cl[4] = storage; ++ if (!storage) { ++ qb_attr_code_encode(&code_pull_rls, cl, 0); ++ return; ++ } ++ qb_attr_code_encode(&code_pull_rls, cl, 1); ++ qb_attr_code_encode(&code_pull_stash, cl, !!stash); ++ qb_attr_code_encode_64(&code_pull_rsp_lo, (uint64_t *)cl, storage_phys); ++} ++ ++void qbman_pull_desc_set_numframes(struct qbman_pull_desc *d, uint8_t numframes) ++{ ++ uint32_t *cl = qb_cl(d); ++ ++ BUG_ON(!numframes || (numframes > 16)); ++ qb_attr_code_encode(&code_pull_numframes, cl, ++ (uint32_t)(numframes - 1)); ++} ++ ++void qbman_pull_desc_set_token(struct qbman_pull_desc *d, uint8_t token) ++{ ++ uint32_t *cl = qb_cl(d); ++ ++ qb_attr_code_encode(&code_pull_token, cl, token); ++} ++ ++void qbman_pull_desc_set_fq(struct qbman_pull_desc *d, uint32_t fqid) ++{ ++ uint32_t *cl = qb_cl(d); ++ ++ qb_attr_code_encode(&code_pull_dct, cl, 1); ++ qb_attr_code_encode(&code_pull_dt, cl, qb_pull_dt_framequeue); ++ qb_attr_code_encode(&code_pull_dqsource, cl, fqid); ++} ++ ++void qbman_pull_desc_set_wq(struct qbman_pull_desc *d, uint32_t wqid, ++ enum qbman_pull_type_e dct) ++{ ++ uint32_t *cl = qb_cl(d); ++ ++ qb_attr_code_encode(&code_pull_dct, cl, dct); ++ qb_attr_code_encode(&code_pull_dt, cl, qb_pull_dt_workqueue); ++ qb_attr_code_encode(&code_pull_dqsource, cl, wqid); ++} ++ ++void qbman_pull_desc_set_channel(struct qbman_pull_desc *d, uint32_t chid, ++ enum qbman_pull_type_e dct) ++{ ++ uint32_t *cl = qb_cl(d); ++ ++ qb_attr_code_encode(&code_pull_dct, cl, dct); ++ qb_attr_code_encode(&code_pull_dt, cl, qb_pull_dt_channel); ++ qb_attr_code_encode(&code_pull_dqsource, cl, chid); ++} ++ ++int qbman_swp_pull(struct qbman_swp *s, struct qbman_pull_desc *d) ++{ ++ uint32_t *p; ++ uint32_t *cl = qb_cl(d); ++ ++ if (!atomic_dec_and_test(&s->vdq.busy)) { ++ atomic_inc(&s->vdq.busy); ++ return -EBUSY; ++ } ++ s->vdq.storage = *(void **)&cl[4]; ++ qb_attr_code_encode(&code_pull_token, cl, 1); ++ p = qbman_cena_write_start(&s->sys, QBMAN_CENA_SWP_VDQCR); ++ word_copy(&p[1], &cl[1], 3); ++ /* Set the verb byte, have to substitute in the valid-bit */ ++ p[0] = cl[0] | s->vdq.valid_bit; ++ s->vdq.valid_bit ^= QB_VALID_BIT; ++ qbman_cena_write_complete(&s->sys, QBMAN_CENA_SWP_VDQCR, p); ++ return 0; ++} ++ ++/****************/ ++/* Polling DQRR */ ++/****************/ ++ ++static struct qb_attr_code code_dqrr_verb = QB_CODE(0, 0, 8); ++static struct qb_attr_code code_dqrr_response = QB_CODE(0, 0, 7); ++static struct qb_attr_code code_dqrr_stat = QB_CODE(0, 8, 8); ++static struct qb_attr_code code_dqrr_seqnum = QB_CODE(0, 16, 14); ++static struct qb_attr_code code_dqrr_odpid = QB_CODE(1, 0, 16); ++/* static struct qb_attr_code code_dqrr_tok = QB_CODE(1, 24, 8); */ ++static struct qb_attr_code code_dqrr_fqid = QB_CODE(2, 0, 24); ++static struct qb_attr_code code_dqrr_byte_count = QB_CODE(4, 0, 32); ++static struct qb_attr_code code_dqrr_frame_count = QB_CODE(5, 0, 24); ++static struct qb_attr_code code_dqrr_ctx_lo = QB_CODE(6, 0, 32); ++ ++#define QBMAN_RESULT_DQ 0x60 ++#define QBMAN_RESULT_FQRN 0x21 ++#define QBMAN_RESULT_FQRNI 0x22 ++#define QBMAN_RESULT_FQPN 0x24 ++#define QBMAN_RESULT_FQDAN 0x25 ++#define QBMAN_RESULT_CDAN 0x26 ++#define QBMAN_RESULT_CSCN_MEM 0x27 ++#define QBMAN_RESULT_CGCU 0x28 ++#define QBMAN_RESULT_BPSCN 0x29 ++#define QBMAN_RESULT_CSCN_WQ 0x2a ++ ++static struct qb_attr_code code_dqpi_pi = QB_CODE(0, 0, 4); ++ ++/* NULL return if there are no unconsumed DQRR entries. Returns a DQRR entry ++ * only once, so repeated calls can return a sequence of DQRR entries, without ++ * requiring they be consumed immediately or in any particular order. */ ++const struct dpaa2_dq *qbman_swp_dqrr_next(struct qbman_swp *s) ++{ ++ uint32_t verb; ++ uint32_t response_verb; ++ uint32_t flags; ++ const struct dpaa2_dq *dq; ++ const uint32_t *p; ++ ++ /* Before using valid-bit to detect if something is there, we have to ++ * handle the case of the DQRR reset bug... */ ++#ifdef WORKAROUND_DQRR_RESET_BUG ++ if (unlikely(s->dqrr.reset_bug)) { ++ /* We pick up new entries by cache-inhibited producer index, ++ * which means that a non-coherent mapping would require us to ++ * invalidate and read *only* once that PI has indicated that ++ * there's an entry here. The first trip around the DQRR ring ++ * will be much less efficient than all subsequent trips around ++ * it... ++ */ ++ uint32_t dqpi = qbman_cinh_read(&s->sys, QBMAN_CINH_SWP_DQPI); ++ uint32_t pi = qb_attr_code_decode(&code_dqpi_pi, &dqpi); ++ /* there are new entries iff pi != next_idx */ ++ if (pi == s->dqrr.next_idx) ++ return NULL; ++ /* if next_idx is/was the last ring index, and 'pi' is ++ * different, we can disable the workaround as all the ring ++ * entries have now been DMA'd to so valid-bit checking is ++ * repaired. Note: this logic needs to be based on next_idx ++ * (which increments one at a time), rather than on pi (which ++ * can burst and wrap-around between our snapshots of it). ++ */ ++ if (s->dqrr.next_idx == (s->dqrr.dqrr_size - 1)) { ++ pr_debug("DEBUG: next_idx=%d, pi=%d, clear reset bug\n", ++ s->dqrr.next_idx, pi); ++ s->dqrr.reset_bug = 0; ++ } ++ qbman_cena_invalidate_prefetch(&s->sys, ++ QBMAN_CENA_SWP_DQRR(s->dqrr.next_idx)); ++ } ++#endif ++ ++ dq = qbman_cena_read(&s->sys, QBMAN_CENA_SWP_DQRR(s->dqrr.next_idx)); ++ p = qb_cl(dq); ++ verb = qb_attr_code_decode(&code_dqrr_verb, p); ++ ++ /* If the valid-bit isn't of the expected polarity, nothing there. Note, ++ * in the DQRR reset bug workaround, we shouldn't need to skip these ++ * check, because we've already determined that a new entry is available ++ * and we've invalidated the cacheline before reading it, so the ++ * valid-bit behaviour is repaired and should tell us what we already ++ * knew from reading PI. ++ */ ++ if ((verb & QB_VALID_BIT) != s->dqrr.valid_bit) { ++ qbman_cena_invalidate_prefetch(&s->sys, ++ QBMAN_CENA_SWP_DQRR(s->dqrr.next_idx)); ++ return NULL; ++ } ++ /* There's something there. Move "next_idx" attention to the next ring ++ * entry (and prefetch it) before returning what we found. */ ++ s->dqrr.next_idx++; ++ s->dqrr.next_idx &= s->dqrr.dqrr_size - 1; /* Wrap around */ ++ /* TODO: it's possible to do all this without conditionals, optimise it ++ * later. */ ++ if (!s->dqrr.next_idx) ++ s->dqrr.valid_bit ^= QB_VALID_BIT; ++ ++ /* If this is the final response to a volatile dequeue command ++ indicate that the vdq is no longer busy */ ++ flags = dpaa2_dq_flags(dq); ++ response_verb = qb_attr_code_decode(&code_dqrr_response, &verb); ++ if ((response_verb == QBMAN_RESULT_DQ) && ++ (flags & DPAA2_DQ_STAT_VOLATILE) && ++ (flags & DPAA2_DQ_STAT_EXPIRED)) ++ atomic_inc(&s->vdq.busy); ++ ++ qbman_cena_invalidate_prefetch(&s->sys, ++ QBMAN_CENA_SWP_DQRR(s->dqrr.next_idx)); ++ return dq; ++} ++ ++/* Consume DQRR entries previously returned from qbman_swp_dqrr_next(). */ ++void qbman_swp_dqrr_consume(struct qbman_swp *s, const struct dpaa2_dq *dq) ++{ ++ qbman_cinh_write(&s->sys, QBMAN_CINH_SWP_DCAP, QBMAN_IDX_FROM_DQRR(dq)); ++} ++ ++/*********************************/ ++/* Polling user-provided storage */ ++/*********************************/ ++ ++int qbman_result_has_new_result(struct qbman_swp *s, ++ const struct dpaa2_dq *dq) ++{ ++ /* To avoid converting the little-endian DQ entry to host-endian prior ++ * to us knowing whether there is a valid entry or not (and run the ++ * risk of corrupting the incoming hardware LE write), we detect in ++ * hardware endianness rather than host. This means we need a different ++ * "code" depending on whether we are BE or LE in software, which is ++ * where DQRR_TOK_OFFSET comes in... */ ++ static struct qb_attr_code code_dqrr_tok_detect = ++ QB_CODE(0, DQRR_TOK_OFFSET, 8); ++ /* The user trying to poll for a result treats "dq" as const. It is ++ * however the same address that was provided to us non-const in the ++ * first place, for directing hardware DMA to. So we can cast away the ++ * const because it is mutable from our perspective. */ ++ uint32_t *p = qb_cl((struct dpaa2_dq *)dq); ++ uint32_t token; ++ ++ token = qb_attr_code_decode(&code_dqrr_tok_detect, &p[1]); ++ if (token != 1) ++ return 0; ++ qb_attr_code_encode(&code_dqrr_tok_detect, &p[1], 0); ++ ++ /* Only now do we convert from hardware to host endianness. Also, as we ++ * are returning success, the user has promised not to call us again, so ++ * there's no risk of us converting the endianness twice... */ ++ make_le32_n(p, 16); ++ ++ /* VDQCR "no longer busy" hook - not quite the same as DQRR, because the ++ * fact "VDQCR" shows busy doesn't mean that the result we're looking at ++ * is from the same command. Eg. we may be looking at our 10th dequeue ++ * result from our first VDQCR command, yet the second dequeue command ++ * could have been kicked off already, after seeing the 1st result. Ie. ++ * the result we're looking at is not necessarily proof that we can ++ * reset "busy". We instead base the decision on whether the current ++ * result is sitting at the first 'storage' location of the busy ++ * command. */ ++ if (s->vdq.storage == dq) { ++ s->vdq.storage = NULL; ++ atomic_inc(&s->vdq.busy); ++ } ++ return 1; ++} ++ ++/********************************/ ++/* Categorising qbman_result */ ++/********************************/ ++ ++static struct qb_attr_code code_result_in_mem = ++ QB_CODE(0, QBMAN_RESULT_VERB_OFFSET_IN_MEM, 7); ++ ++static inline int __qbman_result_is_x(const struct dpaa2_dq *dq, uint32_t x) ++{ ++ const uint32_t *p = qb_cl(dq); ++ uint32_t response_verb = qb_attr_code_decode(&code_dqrr_response, p); ++ ++ return response_verb == x; ++} ++ ++static inline int __qbman_result_is_x_in_mem(const struct dpaa2_dq *dq, ++ uint32_t x) ++{ ++ const uint32_t *p = qb_cl(dq); ++ uint32_t response_verb = qb_attr_code_decode(&code_result_in_mem, p); ++ ++ return (response_verb == x); ++} ++ ++int qbman_result_is_DQ(const struct dpaa2_dq *dq) ++{ ++ return __qbman_result_is_x(dq, QBMAN_RESULT_DQ); ++} ++ ++int qbman_result_is_FQDAN(const struct dpaa2_dq *dq) ++{ ++ return __qbman_result_is_x(dq, QBMAN_RESULT_FQDAN); ++} ++ ++int qbman_result_is_CDAN(const struct dpaa2_dq *dq) ++{ ++ return __qbman_result_is_x(dq, QBMAN_RESULT_CDAN); ++} ++ ++int qbman_result_is_CSCN(const struct dpaa2_dq *dq) ++{ ++ return __qbman_result_is_x_in_mem(dq, QBMAN_RESULT_CSCN_MEM) || ++ __qbman_result_is_x(dq, QBMAN_RESULT_CSCN_WQ); ++} ++ ++int qbman_result_is_BPSCN(const struct dpaa2_dq *dq) ++{ ++ return __qbman_result_is_x_in_mem(dq, QBMAN_RESULT_BPSCN); ++} ++ ++int qbman_result_is_CGCU(const struct dpaa2_dq *dq) ++{ ++ return __qbman_result_is_x_in_mem(dq, QBMAN_RESULT_CGCU); ++} ++ ++int qbman_result_is_FQRN(const struct dpaa2_dq *dq) ++{ ++ return __qbman_result_is_x_in_mem(dq, QBMAN_RESULT_FQRN); ++} ++ ++int qbman_result_is_FQRNI(const struct dpaa2_dq *dq) ++{ ++ return __qbman_result_is_x_in_mem(dq, QBMAN_RESULT_FQRNI); ++} ++ ++int qbman_result_is_FQPN(const struct dpaa2_dq *dq) ++{ ++ return __qbman_result_is_x(dq, QBMAN_RESULT_FQPN); ++} ++ ++/*********************************/ ++/* Parsing frame dequeue results */ ++/*********************************/ ++ ++/* These APIs assume qbman_result_is_DQ() is TRUE */ ++ ++uint32_t dpaa2_dq_flags(const struct dpaa2_dq *dq) ++{ ++ const uint32_t *p = qb_cl(dq); ++ ++ return qb_attr_code_decode(&code_dqrr_stat, p); ++} ++ ++uint16_t dpaa2_dq_seqnum(const struct dpaa2_dq *dq) ++{ ++ const uint32_t *p = qb_cl(dq); ++ ++ return (uint16_t)qb_attr_code_decode(&code_dqrr_seqnum, p); ++} ++ ++uint16_t dpaa2_dq_odpid(const struct dpaa2_dq *dq) ++{ ++ const uint32_t *p = qb_cl(dq); ++ ++ return (uint16_t)qb_attr_code_decode(&code_dqrr_odpid, p); ++} ++ ++uint32_t dpaa2_dq_fqid(const struct dpaa2_dq *dq) ++{ ++ const uint32_t *p = qb_cl(dq); ++ ++ return qb_attr_code_decode(&code_dqrr_fqid, p); ++} ++ ++uint32_t dpaa2_dq_byte_count(const struct dpaa2_dq *dq) ++{ ++ const uint32_t *p = qb_cl(dq); ++ ++ return qb_attr_code_decode(&code_dqrr_byte_count, p); ++} ++ ++uint32_t dpaa2_dq_frame_count(const struct dpaa2_dq *dq) ++{ ++ const uint32_t *p = qb_cl(dq); ++ ++ return qb_attr_code_decode(&code_dqrr_frame_count, p); ++} ++ ++uint64_t dpaa2_dq_fqd_ctx(const struct dpaa2_dq *dq) ++{ ++ const uint64_t *p = (uint64_t *)qb_cl(dq); ++ ++ return qb_attr_code_decode_64(&code_dqrr_ctx_lo, p); ++} ++EXPORT_SYMBOL(dpaa2_dq_fqd_ctx); ++ ++const struct dpaa2_fd *dpaa2_dq_fd(const struct dpaa2_dq *dq) ++{ ++ const uint32_t *p = qb_cl(dq); ++ ++ return (const struct dpaa2_fd *)&p[8]; ++} ++EXPORT_SYMBOL(dpaa2_dq_fd); ++ ++/**************************************/ ++/* Parsing state-change notifications */ ++/**************************************/ ++ ++static struct qb_attr_code code_scn_state = QB_CODE(0, 16, 8); ++static struct qb_attr_code code_scn_rid = QB_CODE(1, 0, 24); ++static struct qb_attr_code code_scn_state_in_mem = ++ QB_CODE(0, SCN_STATE_OFFSET_IN_MEM, 8); ++static struct qb_attr_code code_scn_rid_in_mem = ++ QB_CODE(1, SCN_RID_OFFSET_IN_MEM, 24); ++static struct qb_attr_code code_scn_ctx_lo = QB_CODE(2, 0, 32); ++ ++uint8_t qbman_result_SCN_state(const struct dpaa2_dq *scn) ++{ ++ const uint32_t *p = qb_cl(scn); ++ ++ return (uint8_t)qb_attr_code_decode(&code_scn_state, p); ++} ++ ++uint32_t qbman_result_SCN_rid(const struct dpaa2_dq *scn) ++{ ++ const uint32_t *p = qb_cl(scn); ++ ++ return qb_attr_code_decode(&code_scn_rid, p); ++} ++ ++uint64_t qbman_result_SCN_ctx(const struct dpaa2_dq *scn) ++{ ++ const uint64_t *p = (uint64_t *)qb_cl(scn); ++ ++ return qb_attr_code_decode_64(&code_scn_ctx_lo, p); ++} ++ ++uint8_t qbman_result_SCN_state_in_mem(const struct dpaa2_dq *scn) ++{ ++ const uint32_t *p = qb_cl(scn); ++ ++ return (uint8_t)qb_attr_code_decode(&code_scn_state_in_mem, p); ++} ++ ++uint32_t qbman_result_SCN_rid_in_mem(const struct dpaa2_dq *scn) ++{ ++ const uint32_t *p = qb_cl(scn); ++ uint32_t result_rid; ++ ++ result_rid = qb_attr_code_decode(&code_scn_rid_in_mem, p); ++ return make_le24(result_rid); ++} ++ ++/*****************/ ++/* Parsing BPSCN */ ++/*****************/ ++uint16_t qbman_result_bpscn_bpid(const struct dpaa2_dq *scn) ++{ ++ return (uint16_t)qbman_result_SCN_rid_in_mem(scn) & 0x3FFF; ++} ++ ++int qbman_result_bpscn_has_free_bufs(const struct dpaa2_dq *scn) ++{ ++ return !(int)(qbman_result_SCN_state_in_mem(scn) & 0x1); ++} ++ ++int qbman_result_bpscn_is_depleted(const struct dpaa2_dq *scn) ++{ ++ return (int)(qbman_result_SCN_state_in_mem(scn) & 0x2); ++} ++ ++int qbman_result_bpscn_is_surplus(const struct dpaa2_dq *scn) ++{ ++ return (int)(qbman_result_SCN_state_in_mem(scn) & 0x4); ++} ++ ++uint64_t qbman_result_bpscn_ctx(const struct dpaa2_dq *scn) ++{ ++ return qbman_result_SCN_ctx(scn); ++} ++ ++/*****************/ ++/* Parsing CGCU */ ++/*****************/ ++uint16_t qbman_result_cgcu_cgid(const struct dpaa2_dq *scn) ++{ ++ return (uint16_t)qbman_result_SCN_rid_in_mem(scn) & 0xFFFF; ++} ++ ++uint64_t qbman_result_cgcu_icnt(const struct dpaa2_dq *scn) ++{ ++ return qbman_result_SCN_ctx(scn) & 0xFFFFFFFFFF; ++} ++ ++/******************/ ++/* Buffer release */ ++/******************/ ++ ++/* These should be const, eventually */ ++/* static struct qb_attr_code code_release_num = QB_CODE(0, 0, 3); */ ++static struct qb_attr_code code_release_set_me = QB_CODE(0, 5, 1); ++static struct qb_attr_code code_release_rcdi = QB_CODE(0, 6, 1); ++static struct qb_attr_code code_release_bpid = QB_CODE(0, 16, 16); ++ ++void qbman_release_desc_clear(struct qbman_release_desc *d) ++{ ++ uint32_t *cl; ++ ++ memset(d, 0, sizeof(*d)); ++ cl = qb_cl(d); ++ qb_attr_code_encode(&code_release_set_me, cl, 1); ++} ++ ++void qbman_release_desc_set_bpid(struct qbman_release_desc *d, uint32_t bpid) ++{ ++ uint32_t *cl = qb_cl(d); ++ ++ qb_attr_code_encode(&code_release_bpid, cl, bpid); ++} ++ ++void qbman_release_desc_set_rcdi(struct qbman_release_desc *d, int enable) ++{ ++ uint32_t *cl = qb_cl(d); ++ ++ qb_attr_code_encode(&code_release_rcdi, cl, !!enable); ++} ++ ++#define RAR_IDX(rar) ((rar) & 0x7) ++#define RAR_VB(rar) ((rar) & 0x80) ++#define RAR_SUCCESS(rar) ((rar) & 0x100) ++ ++int qbman_swp_release(struct qbman_swp *s, const struct qbman_release_desc *d, ++ const uint64_t *buffers, unsigned int num_buffers) ++{ ++ uint32_t *p; ++ const uint32_t *cl = qb_cl(d); ++ uint32_t rar = qbman_cinh_read(&s->sys, QBMAN_CINH_SWP_RAR); ++ ++ pr_debug("RAR=%08x\n", rar); ++ if (!RAR_SUCCESS(rar)) ++ return -EBUSY; ++ BUG_ON(!num_buffers || (num_buffers > 7)); ++ /* Start the release command */ ++ p = qbman_cena_write_start(&s->sys, ++ QBMAN_CENA_SWP_RCR(RAR_IDX(rar))); ++ /* Copy the caller's buffer pointers to the command */ ++ u64_to_le32_copy(&p[2], buffers, num_buffers); ++ /* Set the verb byte, have to substitute in the valid-bit and the number ++ * of buffers. */ ++ p[0] = cl[0] | RAR_VB(rar) | num_buffers; ++ qbman_cena_write_complete(&s->sys, ++ QBMAN_CENA_SWP_RCR(RAR_IDX(rar)), ++ p); ++ return 0; ++} ++ ++/*******************/ ++/* Buffer acquires */ ++/*******************/ ++ ++/* These should be const, eventually */ ++static struct qb_attr_code code_acquire_bpid = QB_CODE(0, 16, 16); ++static struct qb_attr_code code_acquire_num = QB_CODE(1, 0, 3); ++static struct qb_attr_code code_acquire_r_num = QB_CODE(1, 0, 3); ++ ++int qbman_swp_acquire(struct qbman_swp *s, uint32_t bpid, uint64_t *buffers, ++ unsigned int num_buffers) ++{ ++ uint32_t *p; ++ uint32_t verb, rslt, num; ++ ++ BUG_ON(!num_buffers || (num_buffers > 7)); ++ ++ /* Start the management command */ ++ p = qbman_swp_mc_start(s); ++ ++ if (!p) ++ return -EBUSY; ++ ++ /* Encode the caller-provided attributes */ ++ qb_attr_code_encode(&code_acquire_bpid, p, bpid); ++ qb_attr_code_encode(&code_acquire_num, p, num_buffers); ++ ++ /* Complete the management command */ ++ p = qbman_swp_mc_complete(s, p, p[0] | QBMAN_MC_ACQUIRE); ++ ++ /* Decode the outcome */ ++ verb = qb_attr_code_decode(&code_generic_verb, p); ++ rslt = qb_attr_code_decode(&code_generic_rslt, p); ++ num = qb_attr_code_decode(&code_acquire_r_num, p); ++ BUG_ON(verb != QBMAN_MC_ACQUIRE); ++ ++ /* Determine success or failure */ ++ if (unlikely(rslt != QBMAN_MC_RSLT_OK)) { ++ pr_err("Acquire buffers from BPID 0x%x failed, code=0x%02x\n", ++ bpid, rslt); ++ return -EIO; ++ } ++ BUG_ON(num > num_buffers); ++ /* Copy the acquired buffers to the caller's array */ ++ u64_from_le32_copy(buffers, &p[2], num); ++ return (int)num; ++} ++ ++/*****************/ ++/* FQ management */ ++/*****************/ ++ ++static struct qb_attr_code code_fqalt_fqid = QB_CODE(1, 0, 32); ++ ++static int qbman_swp_alt_fq_state(struct qbman_swp *s, uint32_t fqid, ++ uint8_t alt_fq_verb) ++{ ++ uint32_t *p; ++ uint32_t verb, rslt; ++ ++ /* Start the management command */ ++ p = qbman_swp_mc_start(s); ++ if (!p) ++ return -EBUSY; ++ ++ qb_attr_code_encode(&code_fqalt_fqid, p, fqid); ++ /* Complete the management command */ ++ p = qbman_swp_mc_complete(s, p, p[0] | alt_fq_verb); ++ ++ /* Decode the outcome */ ++ verb = qb_attr_code_decode(&code_generic_verb, p); ++ rslt = qb_attr_code_decode(&code_generic_rslt, p); ++ BUG_ON(verb != alt_fq_verb); ++ ++ /* Determine success or failure */ ++ if (unlikely(rslt != QBMAN_MC_RSLT_OK)) { ++ pr_err("ALT FQID %d failed: verb = 0x%08x, code = 0x%02x\n", ++ fqid, alt_fq_verb, rslt); ++ return -EIO; ++ } ++ ++ return 0; ++} ++ ++int qbman_swp_fq_schedule(struct qbman_swp *s, uint32_t fqid) ++{ ++ return qbman_swp_alt_fq_state(s, fqid, QBMAN_FQ_SCHEDULE); ++} ++ ++int qbman_swp_fq_force(struct qbman_swp *s, uint32_t fqid) ++{ ++ return qbman_swp_alt_fq_state(s, fqid, QBMAN_FQ_FORCE); ++} ++ ++int qbman_swp_fq_xon(struct qbman_swp *s, uint32_t fqid) ++{ ++ return qbman_swp_alt_fq_state(s, fqid, QBMAN_FQ_XON); ++} ++ ++int qbman_swp_fq_xoff(struct qbman_swp *s, uint32_t fqid) ++{ ++ return qbman_swp_alt_fq_state(s, fqid, QBMAN_FQ_XOFF); ++} ++ ++/**********************/ ++/* Channel management */ ++/**********************/ ++ ++static struct qb_attr_code code_cdan_cid = QB_CODE(0, 16, 12); ++static struct qb_attr_code code_cdan_we = QB_CODE(1, 0, 8); ++static struct qb_attr_code code_cdan_en = QB_CODE(1, 8, 1); ++static struct qb_attr_code code_cdan_ctx_lo = QB_CODE(2, 0, 32); ++ ++/* Hide "ICD" for now as we don't use it, don't set it, and don't test it, so it ++ * would be irresponsible to expose it. */ ++#define CODE_CDAN_WE_EN 0x1 ++#define CODE_CDAN_WE_CTX 0x4 ++ ++static int qbman_swp_CDAN_set(struct qbman_swp *s, uint16_t channelid, ++ uint8_t we_mask, uint8_t cdan_en, ++ uint64_t ctx) ++{ ++ uint32_t *p; ++ uint32_t verb, rslt; ++ ++ /* Start the management command */ ++ p = qbman_swp_mc_start(s); ++ if (!p) ++ return -EBUSY; ++ ++ /* Encode the caller-provided attributes */ ++ qb_attr_code_encode(&code_cdan_cid, p, channelid); ++ qb_attr_code_encode(&code_cdan_we, p, we_mask); ++ qb_attr_code_encode(&code_cdan_en, p, cdan_en); ++ qb_attr_code_encode_64(&code_cdan_ctx_lo, (uint64_t *)p, ctx); ++ /* Complete the management command */ ++ p = qbman_swp_mc_complete(s, p, p[0] | QBMAN_WQCHAN_CONFIGURE); ++ ++ /* Decode the outcome */ ++ verb = qb_attr_code_decode(&code_generic_verb, p); ++ rslt = qb_attr_code_decode(&code_generic_rslt, p); ++ BUG_ON(verb != QBMAN_WQCHAN_CONFIGURE); ++ ++ /* Determine success or failure */ ++ if (unlikely(rslt != QBMAN_MC_RSLT_OK)) { ++ pr_err("CDAN cQID %d failed: code = 0x%02x\n", ++ channelid, rslt); ++ return -EIO; ++ } ++ ++ return 0; ++} ++ ++int qbman_swp_CDAN_set_context(struct qbman_swp *s, uint16_t channelid, ++ uint64_t ctx) ++{ ++ return qbman_swp_CDAN_set(s, channelid, ++ CODE_CDAN_WE_CTX, ++ 0, ctx); ++} ++ ++int qbman_swp_CDAN_enable(struct qbman_swp *s, uint16_t channelid) ++{ ++ return qbman_swp_CDAN_set(s, channelid, ++ CODE_CDAN_WE_EN, ++ 1, 0); ++} ++int qbman_swp_CDAN_disable(struct qbman_swp *s, uint16_t channelid) ++{ ++ return qbman_swp_CDAN_set(s, channelid, ++ CODE_CDAN_WE_EN, ++ 0, 0); ++} ++int qbman_swp_CDAN_set_context_enable(struct qbman_swp *s, uint16_t channelid, ++ uint64_t ctx) ++{ ++ return qbman_swp_CDAN_set(s, channelid, ++ CODE_CDAN_WE_EN | CODE_CDAN_WE_CTX, ++ 1, ctx); ++} +diff --git a/drivers/staging/fsl-mc/bus/dpio/qbman_portal.h b/drivers/staging/fsl-mc/bus/dpio/qbman_portal.h +new file mode 100644 +index 0000000..65ebf3f +--- /dev/null ++++ b/drivers/staging/fsl-mc/bus/dpio/qbman_portal.h +@@ -0,0 +1,261 @@ ++/* Copyright (C) 2014 Freescale Semiconductor, Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * * Neither the name of Freescale Semiconductor nor the ++ * names of its contributors may be used to endorse or promote products ++ * derived from this software without specific prior written permission. ++ * ++ * ++ * ALTERNATIVELY, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") as published by the Free Software ++ * Foundation, either version 2 of that License or (at your option) any ++ * later version. ++ * ++ * THIS SOFTWARE IS PROVIDED BY Freescale Semiconductor ``AS IS'' AND ANY ++ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ++ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE ++ * DISCLAIMED. IN NO EVENT SHALL Freescale Semiconductor BE LIABLE FOR ANY ++ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ++ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; ++ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ++ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ++ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS ++ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++#include "qbman_private.h" ++#include "fsl_qbman_portal.h" ++#include "../../include/fsl_dpaa2_fd.h" ++ ++/* All QBMan command and result structures use this "valid bit" encoding */ ++#define QB_VALID_BIT ((uint32_t)0x80) ++ ++/* Management command result codes */ ++#define QBMAN_MC_RSLT_OK 0xf0 ++ ++/* TBD: as of QBMan 4.1, DQRR will be 8 rather than 4! */ ++#define QBMAN_DQRR_SIZE 4 ++ ++/* DQRR valid-bit reset bug. See qbman_portal.c::qbman_swp_init(). */ ++#define WORKAROUND_DQRR_RESET_BUG ++ ++/* --------------------- */ ++/* portal data structure */ ++/* --------------------- */ ++ ++struct qbman_swp { ++ const struct qbman_swp_desc *desc; ++ /* The qbman_sys (ie. arch/OS-specific) support code can put anything it ++ * needs in here. */ ++ struct qbman_swp_sys sys; ++ /* Management commands */ ++ struct { ++#ifdef QBMAN_CHECKING ++ enum swp_mc_check { ++ swp_mc_can_start, /* call __qbman_swp_mc_start() */ ++ swp_mc_can_submit, /* call __qbman_swp_mc_submit() */ ++ swp_mc_can_poll, /* call __qbman_swp_mc_result() */ ++ } check; ++#endif ++ uint32_t valid_bit; /* 0x00 or 0x80 */ ++ } mc; ++ /* Push dequeues */ ++ uint32_t sdq; ++ /* Volatile dequeues */ ++ struct { ++ /* VDQCR supports a "1 deep pipeline", meaning that if you know ++ * the last-submitted command is already executing in the ++ * hardware (as evidenced by at least 1 valid dequeue result), ++ * you can write another dequeue command to the register, the ++ * hardware will start executing it as soon as the ++ * already-executing command terminates. (This minimises latency ++ * and stalls.) With that in mind, this "busy" variable refers ++ * to whether or not a command can be submitted, not whether or ++ * not a previously-submitted command is still executing. In ++ * other words, once proof is seen that the previously-submitted ++ * command is executing, "vdq" is no longer "busy". ++ */ ++ atomic_t busy; ++ uint32_t valid_bit; /* 0x00 or 0x80 */ ++ /* We need to determine when vdq is no longer busy. This depends ++ * on whether the "busy" (last-submitted) dequeue command is ++ * targeting DQRR or main-memory, and detected is based on the ++ * presence of the dequeue command's "token" showing up in ++ * dequeue entries in DQRR or main-memory (respectively). */ ++ struct dpaa2_dq *storage; /* NULL if DQRR */ ++ } vdq; ++ /* DQRR */ ++ struct { ++ uint32_t next_idx; ++ uint32_t valid_bit; ++ uint8_t dqrr_size; ++#ifdef WORKAROUND_DQRR_RESET_BUG ++ int reset_bug; ++#endif ++ } dqrr; ++}; ++ ++/* -------------------------- */ ++/* portal management commands */ ++/* -------------------------- */ ++ ++/* Different management commands all use this common base layer of code to issue ++ * commands and poll for results. The first function returns a pointer to where ++ * the caller should fill in their MC command (though they should ignore the ++ * verb byte), the second function commits merges in the caller-supplied command ++ * verb (which should not include the valid-bit) and submits the command to ++ * hardware, and the third function checks for a completed response (returns ++ * non-NULL if only if the response is complete). */ ++void *qbman_swp_mc_start(struct qbman_swp *p); ++void qbman_swp_mc_submit(struct qbman_swp *p, void *cmd, uint32_t cmd_verb); ++void *qbman_swp_mc_result(struct qbman_swp *p); ++ ++/* Wraps up submit + poll-for-result */ ++static inline void *qbman_swp_mc_complete(struct qbman_swp *swp, void *cmd, ++ uint32_t cmd_verb) ++{ ++ int loopvar; ++ ++ qbman_swp_mc_submit(swp, cmd, cmd_verb); ++ DBG_POLL_START(loopvar); ++ do { ++ DBG_POLL_CHECK(loopvar); ++ cmd = qbman_swp_mc_result(swp); ++ } while (!cmd); ++ return cmd; ++} ++ ++/* ------------ */ ++/* qb_attr_code */ ++/* ------------ */ ++ ++/* This struct locates a sub-field within a QBMan portal (CENA) cacheline which ++ * is either serving as a configuration command or a query result. The ++ * representation is inherently little-endian, as the indexing of the words is ++ * itself little-endian in nature and layerscape is little endian for anything ++ * that crosses a word boundary too (64-bit fields are the obvious examples). ++ */ ++struct qb_attr_code { ++ unsigned int word; /* which uint32_t[] array member encodes the field */ ++ unsigned int lsoffset; /* encoding offset from ls-bit */ ++ unsigned int width; /* encoding width. (bool must be 1.) */ ++}; ++ ++/* Some pre-defined codes */ ++extern struct qb_attr_code code_generic_verb; ++extern struct qb_attr_code code_generic_rslt; ++ ++/* Macros to define codes */ ++#define QB_CODE(a, b, c) { a, b, c} ++#define QB_CODE_NULL \ ++ QB_CODE((unsigned int)-1, (unsigned int)-1, (unsigned int)-1) ++ ++/* Rotate a code "ms", meaning that it moves from less-significant bytes to ++ * more-significant, from less-significant words to more-significant, etc. The ++ * "ls" version does the inverse, from more-significant towards ++ * less-significant. ++ */ ++static inline void qb_attr_code_rotate_ms(struct qb_attr_code *code, ++ unsigned int bits) ++{ ++ code->lsoffset += bits; ++ while (code->lsoffset > 31) { ++ code->word++; ++ code->lsoffset -= 32; ++ } ++} ++static inline void qb_attr_code_rotate_ls(struct qb_attr_code *code, ++ unsigned int bits) ++{ ++ /* Don't be fooled, this trick should work because the types are ++ * unsigned. So the case that interests the while loop (the rotate has ++ * gone too far and the word count needs to compensate for it), is ++ * manifested when lsoffset is negative. But that equates to a really ++ * large unsigned value, starting with lots of "F"s. As such, we can ++ * continue adding 32 back to it until it wraps back round above zero, ++ * to a value of 31 or less... ++ */ ++ code->lsoffset -= bits; ++ while (code->lsoffset > 31) { ++ code->word--; ++ code->lsoffset += 32; ++ } ++} ++/* Implement a loop of code rotations until 'expr' evaluates to FALSE (0). */ ++#define qb_attr_code_for_ms(code, bits, expr) \ ++ for (; expr; qb_attr_code_rotate_ms(code, bits)) ++#define qb_attr_code_for_ls(code, bits, expr) \ ++ for (; expr; qb_attr_code_rotate_ls(code, bits)) ++ ++/* decode a field from a cacheline */ ++static inline uint32_t qb_attr_code_decode(const struct qb_attr_code *code, ++ const uint32_t *cacheline) ++{ ++ return d32_uint32_t(code->lsoffset, code->width, cacheline[code->word]); ++} ++static inline uint64_t qb_attr_code_decode_64(const struct qb_attr_code *code, ++ const uint64_t *cacheline) ++{ ++ uint64_t res; ++ u64_from_le32_copy(&res, &cacheline[code->word/2], 1); ++ return res; ++} ++ ++/* encode a field to a cacheline */ ++static inline void qb_attr_code_encode(const struct qb_attr_code *code, ++ uint32_t *cacheline, uint32_t val) ++{ ++ cacheline[code->word] = ++ r32_uint32_t(code->lsoffset, code->width, cacheline[code->word]) ++ | e32_uint32_t(code->lsoffset, code->width, val); ++} ++static inline void qb_attr_code_encode_64(const struct qb_attr_code *code, ++ uint64_t *cacheline, uint64_t val) ++{ ++ u64_to_le32_copy(&cacheline[code->word/2], &val, 1); ++} ++ ++/* Small-width signed values (two's-complement) will decode into medium-width ++ * positives. (Eg. for an 8-bit signed field, which stores values from -128 to ++ * +127, a setting of -7 would appear to decode to the 32-bit unsigned value ++ * 249. Likewise -120 would decode as 136.) This function allows the caller to ++ * "re-sign" such fields to 32-bit signed. (Eg. -7, which was 249 with an 8-bit ++ * encoding, will become 0xfffffff9 if you cast the return value to uint32_t). ++ */ ++static inline int32_t qb_attr_code_makesigned(const struct qb_attr_code *code, ++ uint32_t val) ++{ ++ BUG_ON(val >= (1 << code->width)); ++ /* If the high bit was set, it was encoding a negative */ ++ if (val >= (1 << (code->width - 1))) ++ return (int32_t)0 - (int32_t)(((uint32_t)1 << code->width) - ++ val); ++ /* Otherwise, it was encoding a positive */ ++ return (int32_t)val; ++} ++ ++/* ---------------------- */ ++/* Descriptors/cachelines */ ++/* ---------------------- */ ++ ++/* To avoid needless dynamic allocation, the driver API often gives the caller ++ * a "descriptor" type that the caller can instantiate however they like. ++ * Ultimately though, it is just a cacheline of binary storage (or something ++ * smaller when it is known that the descriptor doesn't need all 64 bytes) for ++ * holding pre-formatted pieces of hardware commands. The performance-critical ++ * code can then copy these descriptors directly into hardware command ++ * registers more efficiently than trying to construct/format commands ++ * on-the-fly. The API user sees the descriptor as an array of 32-bit words in ++ * order for the compiler to know its size, but the internal details are not ++ * exposed. The following macro is used within the driver for converting *any* ++ * descriptor pointer to a usable array pointer. The use of a macro (instead of ++ * an inline) is necessary to work with different descriptor types and to work ++ * correctly with const and non-const inputs (and similarly-qualified outputs). ++ */ ++#define qb_cl(d) (&(d)->dont_manipulate_directly[0]) +diff --git a/drivers/staging/fsl-mc/bus/dpio/qbman_private.h b/drivers/staging/fsl-mc/bus/dpio/qbman_private.h +new file mode 100644 +index 0000000..e376b80 +--- /dev/null ++++ b/drivers/staging/fsl-mc/bus/dpio/qbman_private.h +@@ -0,0 +1,173 @@ ++/* Copyright (C) 2014 Freescale Semiconductor, Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * * Neither the name of Freescale Semiconductor nor the ++ * names of its contributors may be used to endorse or promote products ++ * derived from this software without specific prior written permission. ++ * ++ * ++ * ALTERNATIVELY, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") as published by the Free Software ++ * Foundation, either version 2 of that License or (at your option) any ++ * later version. ++ * ++ * THIS SOFTWARE IS PROVIDED BY Freescale Semiconductor ``AS IS'' AND ANY ++ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ++ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE ++ * DISCLAIMED. IN NO EVENT SHALL Freescale Semiconductor BE LIABLE FOR ANY ++ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ++ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; ++ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ++ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ++ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS ++ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ++*/ ++ ++/* Perform extra checking */ ++#define QBMAN_CHECKING ++ ++/* To maximise the amount of logic that is common between the Linux driver and ++ * other targets (such as the embedded MC firmware), we pivot here between the ++ * inclusion of two platform-specific headers. ++ * ++ * The first, qbman_sys_decl.h, includes any and all required system headers as ++ * well as providing any definitions for the purposes of compatibility. The ++ * second, qbman_sys.h, is where platform-specific routines go. ++ * ++ * The point of the split is that the platform-independent code (including this ++ * header) may depend on platform-specific declarations, yet other ++ * platform-specific routines may depend on platform-independent definitions. ++ */ ++ ++#include "qbman_sys_decl.h" ++ ++#define QMAN_REV_4000 0x04000000 ++#define QMAN_REV_4100 0x04010000 ++#define QMAN_REV_4101 0x04010001 ++ ++/* When things go wrong, it is a convenient trick to insert a few FOO() ++ * statements in the code to trace progress. TODO: remove this once we are ++ * hacking the code less actively. ++ */ ++#define FOO() fsl_os_print("FOO: %s:%d\n", __FILE__, __LINE__) ++ ++/* Any time there is a register interface which we poll on, this provides a ++ * "break after x iterations" scheme for it. It's handy for debugging, eg. ++ * where you don't want millions of lines of log output from a polling loop ++ * that won't, because such things tend to drown out the earlier log output ++ * that might explain what caused the problem. (NB: put ";" after each macro!) ++ * TODO: we should probably remove this once we're done sanitising the ++ * simulator... ++ */ ++#define DBG_POLL_START(loopvar) (loopvar = 10) ++#define DBG_POLL_CHECK(loopvar) \ ++ do {if (!(loopvar--)) BUG_ON(1); } while (0) ++ ++/* For CCSR or portal-CINH registers that contain fields at arbitrary offsets ++ * and widths, these macro-generated encode/decode/isolate/remove inlines can ++ * be used. ++ * ++ * Eg. to "d"ecode a 14-bit field out of a register (into a "uint16_t" type), ++ * where the field is located 3 bits "up" from the least-significant bit of the ++ * register (ie. the field location within the 32-bit register corresponds to a ++ * mask of 0x0001fff8), you would do; ++ * uint16_t field = d32_uint16_t(3, 14, reg_value); ++ * ++ * Or to "e"ncode a 1-bit boolean value (input type is "int", zero is FALSE, ++ * non-zero is TRUE, so must convert all non-zero inputs to 1, hence the "!!" ++ * operator) into a register at bit location 0x00080000 (19 bits "in" from the ++ * LS bit), do; ++ * reg_value |= e32_int(19, 1, !!field); ++ * ++ * If you wish to read-modify-write a register, such that you leave the 14-bit ++ * field as-is but have all other fields set to zero, then "i"solate the 14-bit ++ * value using; ++ * reg_value = i32_uint16_t(3, 14, reg_value); ++ * ++ * Alternatively, you could "r"emove the 1-bit boolean field (setting it to ++ * zero) but leaving all other fields as-is; ++ * reg_val = r32_int(19, 1, reg_value); ++ * ++ */ ++#define MAKE_MASK32(width) (width == 32 ? 0xffffffff : \ ++ (uint32_t)((1 << width) - 1)) ++#define DECLARE_CODEC32(t) \ ++static inline uint32_t e32_##t(uint32_t lsoffset, uint32_t width, t val) \ ++{ \ ++ BUG_ON(width > (sizeof(t) * 8)); \ ++ return ((uint32_t)val & MAKE_MASK32(width)) << lsoffset; \ ++} \ ++static inline t d32_##t(uint32_t lsoffset, uint32_t width, uint32_t val) \ ++{ \ ++ BUG_ON(width > (sizeof(t) * 8)); \ ++ return (t)((val >> lsoffset) & MAKE_MASK32(width)); \ ++} \ ++static inline uint32_t i32_##t(uint32_t lsoffset, uint32_t width, \ ++ uint32_t val) \ ++{ \ ++ BUG_ON(width > (sizeof(t) * 8)); \ ++ return e32_##t(lsoffset, width, d32_##t(lsoffset, width, val)); \ ++} \ ++static inline uint32_t r32_##t(uint32_t lsoffset, uint32_t width, \ ++ uint32_t val) \ ++{ \ ++ BUG_ON(width > (sizeof(t) * 8)); \ ++ return ~(MAKE_MASK32(width) << lsoffset) & val; \ ++} ++DECLARE_CODEC32(uint32_t) ++DECLARE_CODEC32(uint16_t) ++DECLARE_CODEC32(uint8_t) ++DECLARE_CODEC32(int) ++ ++ /*********************/ ++ /* Debugging assists */ ++ /*********************/ ++ ++static inline void __hexdump(unsigned long start, unsigned long end, ++ unsigned long p, size_t sz, const unsigned char *c) ++{ ++ while (start < end) { ++ unsigned int pos = 0; ++ char buf[64]; ++ int nl = 0; ++ ++ pos += sprintf(buf + pos, "%08lx: ", start); ++ do { ++ if ((start < p) || (start >= (p + sz))) ++ pos += sprintf(buf + pos, ".."); ++ else ++ pos += sprintf(buf + pos, "%02x", *(c++)); ++ if (!(++start & 15)) { ++ buf[pos++] = '\n'; ++ nl = 1; ++ } else { ++ nl = 0; ++ if (!(start & 1)) ++ buf[pos++] = ' '; ++ if (!(start & 3)) ++ buf[pos++] = ' '; ++ } ++ } while (start & 15); ++ if (!nl) ++ buf[pos++] = '\n'; ++ buf[pos] = '\0'; ++ pr_info("%s", buf); ++ } ++} ++static inline void hexdump(const void *ptr, size_t sz) ++{ ++ unsigned long p = (unsigned long)ptr; ++ unsigned long start = p & ~(unsigned long)15; ++ unsigned long end = (p + sz + 15) & ~(unsigned long)15; ++ const unsigned char *c = ptr; ++ ++ __hexdump(start, end, p, sz, c); ++} ++ ++#include "qbman_sys.h" +diff --git a/drivers/staging/fsl-mc/bus/dpio/qbman_sys.h b/drivers/staging/fsl-mc/bus/dpio/qbman_sys.h +new file mode 100644 +index 0000000..4849212 +--- /dev/null ++++ b/drivers/staging/fsl-mc/bus/dpio/qbman_sys.h +@@ -0,0 +1,307 @@ ++/* Copyright (C) 2014 Freescale Semiconductor, Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * * Neither the name of Freescale Semiconductor nor the ++ * names of its contributors may be used to endorse or promote products ++ * derived from this software without specific prior written permission. ++ * ++ * ++ * ALTERNATIVELY, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") as published by the Free Software ++ * Foundation, either version 2 of that License or (at your option) any ++ * later version. ++ * ++ * THIS SOFTWARE IS PROVIDED BY Freescale Semiconductor ``AS IS'' AND ANY ++ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ++ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE ++ * DISCLAIMED. IN NO EVENT SHALL Freescale Semiconductor BE LIABLE FOR ANY ++ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ++ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; ++ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ++ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ++ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS ++ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ++ */ ++/* qbman_sys_decl.h and qbman_sys.h are the two platform-specific files in the ++ * driver. They are only included via qbman_private.h, which is itself a ++ * platform-independent file and is included by all the other driver source. ++ * ++ * qbman_sys_decl.h is included prior to all other declarations and logic, and ++ * it exists to provide compatibility with any linux interfaces our ++ * single-source driver code is dependent on (eg. kmalloc). Ie. this file ++ * provides linux compatibility. ++ * ++ * This qbman_sys.h header, on the other hand, is included *after* any common ++ * and platform-neutral declarations and logic in qbman_private.h, and exists to ++ * implement any platform-specific logic of the qbman driver itself. Ie. it is ++ * *not* to provide linux compatibility. ++ */ ++ ++/* Trace the 3 different classes of read/write access to QBMan. #undef as ++ * required. */ ++#undef QBMAN_CCSR_TRACE ++#undef QBMAN_CINH_TRACE ++#undef QBMAN_CENA_TRACE ++ ++static inline void word_copy(void *d, const void *s, unsigned int cnt) ++{ ++ uint32_t *dd = d; ++ const uint32_t *ss = s; ++ ++ while (cnt--) ++ *(dd++) = *(ss++); ++} ++ ++/* Currently, the CENA support code expects each 32-bit word to be written in ++ * host order, and these are converted to hardware (little-endian) order on ++ * command submission. However, 64-bit quantities are must be written (and read) ++ * as two 32-bit words with the least-significant word first, irrespective of ++ * host endianness. */ ++static inline void u64_to_le32_copy(void *d, const uint64_t *s, ++ unsigned int cnt) ++{ ++ uint32_t *dd = d; ++ const uint32_t *ss = (const uint32_t *)s; ++ ++ while (cnt--) { ++ /* TBD: the toolchain was choking on the use of 64-bit types up ++ * until recently so this works entirely with 32-bit variables. ++ * When 64-bit types become usable again, investigate better ++ * ways of doing this. */ ++#if defined(__BIG_ENDIAN) ++ *(dd++) = ss[1]; ++ *(dd++) = ss[0]; ++ ss += 2; ++#else ++ *(dd++) = *(ss++); ++ *(dd++) = *(ss++); ++#endif ++ } ++} ++static inline void u64_from_le32_copy(uint64_t *d, const void *s, ++ unsigned int cnt) ++{ ++ const uint32_t *ss = s; ++ uint32_t *dd = (uint32_t *)d; ++ ++ while (cnt--) { ++#if defined(__BIG_ENDIAN) ++ dd[1] = *(ss++); ++ dd[0] = *(ss++); ++ dd += 2; ++#else ++ *(dd++) = *(ss++); ++ *(dd++) = *(ss++); ++#endif ++ } ++} ++ ++/* Convert a host-native 32bit value into little endian */ ++#if defined(__BIG_ENDIAN) ++static inline uint32_t make_le32(uint32_t val) ++{ ++ return ((val & 0xff) << 24) | ((val & 0xff00) << 8) | ++ ((val & 0xff0000) >> 8) | ((val & 0xff000000) >> 24); ++} ++static inline uint32_t make_le24(uint32_t val) ++{ ++ return (((val & 0xff) << 16) | (val & 0xff00) | ++ ((val & 0xff0000) >> 16)); ++} ++#else ++#define make_le32(val) (val) ++#define make_le24(val) (val) ++#endif ++static inline void make_le32_n(uint32_t *val, unsigned int num) ++{ ++ while (num--) { ++ *val = make_le32(*val); ++ val++; ++ } ++} ++ ++ /******************/ ++ /* Portal access */ ++ /******************/ ++struct qbman_swp_sys { ++ /* On GPP, the sys support for qbman_swp is here. The CENA region isi ++ * not an mmap() of the real portal registers, but an allocated ++ * place-holder, because the actual writes/reads to/from the portal are ++ * marshalled from these allocated areas using QBMan's "MC access ++ * registers". CINH accesses are atomic so there's no need for a ++ * place-holder. */ ++ void *cena; ++ void __iomem *addr_cena; ++ void __iomem *addr_cinh; ++}; ++ ++/* P_OFFSET is (ACCESS_CMD,0,12) - offset within the portal ++ * C is (ACCESS_CMD,12,1) - is inhibited? (0==CENA, 1==CINH) ++ * SWP_IDX is (ACCESS_CMD,16,10) - Software portal index ++ * P is (ACCESS_CMD,28,1) - (0==special portal, 1==any portal) ++ * T is (ACCESS_CMD,29,1) - Command type (0==READ, 1==WRITE) ++ * E is (ACCESS_CMD,31,1) - Command execute (1 to issue, poll for 0==complete) ++ */ ++ ++static inline void qbman_cinh_write(struct qbman_swp_sys *s, uint32_t offset, ++ uint32_t val) ++{ ++ ++ writel_relaxed(val, s->addr_cinh + offset); ++#ifdef QBMAN_CINH_TRACE ++ pr_info("qbman_cinh_write(%p:0x%03x) 0x%08x\n", ++ s->addr_cinh, offset, val); ++#endif ++} ++ ++static inline uint32_t qbman_cinh_read(struct qbman_swp_sys *s, uint32_t offset) ++{ ++ uint32_t reg = readl_relaxed(s->addr_cinh + offset); ++ ++#ifdef QBMAN_CINH_TRACE ++ pr_info("qbman_cinh_read(%p:0x%03x) 0x%08x\n", ++ s->addr_cinh, offset, reg); ++#endif ++ return reg; ++} ++ ++static inline void *qbman_cena_write_start(struct qbman_swp_sys *s, ++ uint32_t offset) ++{ ++ void *shadow = s->cena + offset; ++ ++#ifdef QBMAN_CENA_TRACE ++ pr_info("qbman_cena_write_start(%p:0x%03x) %p\n", ++ s->addr_cena, offset, shadow); ++#endif ++ BUG_ON(offset & 63); ++ dcbz(shadow); ++ return shadow; ++} ++ ++static inline void qbman_cena_write_complete(struct qbman_swp_sys *s, ++ uint32_t offset, void *cmd) ++{ ++ const uint32_t *shadow = cmd; ++ int loop; ++ ++#ifdef QBMAN_CENA_TRACE ++ pr_info("qbman_cena_write_complete(%p:0x%03x) %p\n", ++ s->addr_cena, offset, shadow); ++ hexdump(cmd, 64); ++#endif ++ for (loop = 15; loop >= 1; loop--) ++ writel_relaxed(shadow[loop], s->addr_cena + ++ offset + loop * 4); ++ lwsync(); ++ writel_relaxed(shadow[0], s->addr_cena + offset); ++ dcbf(s->addr_cena + offset); ++} ++ ++static inline void *qbman_cena_read(struct qbman_swp_sys *s, uint32_t offset) ++{ ++ uint32_t *shadow = s->cena + offset; ++ unsigned int loop; ++ ++#ifdef QBMAN_CENA_TRACE ++ pr_info("qbman_cena_read(%p:0x%03x) %p\n", ++ s->addr_cena, offset, shadow); ++#endif ++ ++ for (loop = 0; loop < 16; loop++) ++ shadow[loop] = readl_relaxed(s->addr_cena + offset ++ + loop * 4); ++#ifdef QBMAN_CENA_TRACE ++ hexdump(shadow, 64); ++#endif ++ return shadow; ++} ++ ++static inline void qbman_cena_invalidate_prefetch(struct qbman_swp_sys *s, ++ uint32_t offset) ++{ ++ dcivac(s->addr_cena + offset); ++ prefetch_for_load(s->addr_cena + offset); ++} ++ ++ /******************/ ++ /* Portal support */ ++ /******************/ ++ ++/* The SWP_CFG portal register is special, in that it is used by the ++ * platform-specific code rather than the platform-independent code in ++ * qbman_portal.c. So use of it is declared locally here. */ ++#define QBMAN_CINH_SWP_CFG 0xd00 ++ ++/* For MC portal use, we always configure with ++ * DQRR_MF is (SWP_CFG,20,3) - DQRR max fill (<- 0x4) ++ * EST is (SWP_CFG,16,3) - EQCR_CI stashing threshold (<- 0x0) ++ * RPM is (SWP_CFG,12,2) - RCR production notification mode (<- 0x3) ++ * DCM is (SWP_CFG,10,2) - DQRR consumption notification mode (<- 0x2) ++ * EPM is (SWP_CFG,8,2) - EQCR production notification mode (<- 0x3) ++ * SD is (SWP_CFG,5,1) - memory stashing drop enable (<- FALSE) ++ * SP is (SWP_CFG,4,1) - memory stashing priority (<- TRUE) ++ * SE is (SWP_CFG,3,1) - memory stashing enable (<- 0x0) ++ * DP is (SWP_CFG,2,1) - dequeue stashing priority (<- TRUE) ++ * DE is (SWP_CFG,1,1) - dequeue stashing enable (<- 0x0) ++ * EP is (SWP_CFG,0,1) - EQCR_CI stashing priority (<- FALSE) ++ */ ++static inline uint32_t qbman_set_swp_cfg(uint8_t max_fill, uint8_t wn, ++ uint8_t est, uint8_t rpm, uint8_t dcm, ++ uint8_t epm, int sd, int sp, int se, ++ int dp, int de, int ep) ++{ ++ uint32_t reg; ++ ++ reg = e32_uint8_t(20, (uint32_t)(3 + (max_fill >> 3)), max_fill) | ++ e32_uint8_t(16, 3, est) | e32_uint8_t(12, 2, rpm) | ++ e32_uint8_t(10, 2, dcm) | e32_uint8_t(8, 2, epm) | ++ e32_int(5, 1, sd) | e32_int(4, 1, sp) | e32_int(3, 1, se) | ++ e32_int(2, 1, dp) | e32_int(1, 1, de) | e32_int(0, 1, ep) | ++ e32_uint8_t(14, 1, wn); ++ return reg; ++} ++ ++static inline int qbman_swp_sys_init(struct qbman_swp_sys *s, ++ const struct qbman_swp_desc *d, ++ uint8_t dqrr_size) ++{ ++ uint32_t reg; ++ ++ s->addr_cena = d->cena_bar; ++ s->addr_cinh = d->cinh_bar; ++ s->cena = (void *)get_zeroed_page(GFP_KERNEL); ++ if (!s->cena) { ++ pr_err("Could not allocate page for cena shadow\n"); ++ return -1; ++ } ++ ++#ifdef QBMAN_CHECKING ++ /* We should never be asked to initialise for a portal that isn't in ++ * the power-on state. (Ie. don't forget to reset portals when they are ++ * decommissioned!) ++ */ ++ reg = qbman_cinh_read(s, QBMAN_CINH_SWP_CFG); ++ BUG_ON(reg); ++#endif ++ reg = qbman_set_swp_cfg(dqrr_size, 0, 0, 3, 2, 3, 0, 1, 0, 1, 0, 0); ++ qbman_cinh_write(s, QBMAN_CINH_SWP_CFG, reg); ++ reg = qbman_cinh_read(s, QBMAN_CINH_SWP_CFG); ++ if (!reg) { ++ pr_err("The portal is not enabled!\n"); ++ kfree(s->cena); ++ return -1; ++ } ++ return 0; ++} ++ ++static inline void qbman_swp_sys_finish(struct qbman_swp_sys *s) ++{ ++ free_page((unsigned long)s->cena); ++} +diff --git a/drivers/staging/fsl-mc/bus/dpio/qbman_sys_decl.h b/drivers/staging/fsl-mc/bus/dpio/qbman_sys_decl.h +new file mode 100644 +index 0000000..5b3a224 +--- /dev/null ++++ b/drivers/staging/fsl-mc/bus/dpio/qbman_sys_decl.h +@@ -0,0 +1,86 @@ ++/* Copyright (C) 2014 Freescale Semiconductor, Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * * Neither the name of Freescale Semiconductor nor the ++ * names of its contributors may be used to endorse or promote products ++ * derived from this software without specific prior written permission. ++ * ++ * ++ * ALTERNATIVELY, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") as published by the Free Software ++ * Foundation, either version 2 of that License or (at your option) any ++ * later version. ++ * ++ * THIS SOFTWARE IS PROVIDED BY Freescale Semiconductor ``AS IS'' AND ANY ++ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ++ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE ++ * DISCLAIMED. IN NO EVENT SHALL Freescale Semiconductor BE LIABLE FOR ANY ++ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ++ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; ++ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ++ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ++ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS ++ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ++ */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "fsl_qbman_base.h" ++ ++/* The platform-independent code shouldn't need endianness, except for ++ * weird/fast-path cases like qbman_result_has_token(), which needs to ++ * perform a passive and endianness-specific test on a read-only data structure ++ * very quickly. It's an exception, and this symbol is used for that case. */ ++#if defined(__BIG_ENDIAN) ++#define DQRR_TOK_OFFSET 0 ++#define QBMAN_RESULT_VERB_OFFSET_IN_MEM 24 ++#define SCN_STATE_OFFSET_IN_MEM 8 ++#define SCN_RID_OFFSET_IN_MEM 8 ++#else ++#define DQRR_TOK_OFFSET 24 ++#define QBMAN_RESULT_VERB_OFFSET_IN_MEM 0 ++#define SCN_STATE_OFFSET_IN_MEM 16 ++#define SCN_RID_OFFSET_IN_MEM 0 ++#endif ++ ++/* Similarly-named functions */ ++#define upper32(a) upper_32_bits(a) ++#define lower32(a) lower_32_bits(a) ++ ++ /****************/ ++ /* arch assists */ ++ /****************/ ++ ++#define dcbz(p) { asm volatile("dc zva, %0" : : "r" (p) : "memory"); } ++#define lwsync() { asm volatile("dmb st" : : : "memory"); } ++#define dcbf(p) { asm volatile("dc cvac, %0;" : : "r" (p) : "memory"); } ++#define dcivac(p) { asm volatile("dc ivac, %0" : : "r"(p) : "memory"); } ++static inline void prefetch_for_load(void *p) ++{ ++ asm volatile("prfm pldl1keep, [%0, #64]" : : "r" (p)); ++} ++static inline void prefetch_for_store(void *p) ++{ ++ asm volatile("prfm pstl1keep, [%0, #64]" : : "r" (p)); ++} +diff --git a/drivers/staging/fsl-mc/bus/dpio/qbman_test.c b/drivers/staging/fsl-mc/bus/dpio/qbman_test.c +new file mode 100644 +index 0000000..28396e7 +--- /dev/null ++++ b/drivers/staging/fsl-mc/bus/dpio/qbman_test.c +@@ -0,0 +1,664 @@ ++/* Copyright (C) 2014 Freescale Semiconductor, Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * * Neither the name of Freescale Semiconductor nor the ++ * names of its contributors may be used to endorse or promote products ++ * derived from this software without specific prior written permission. ++ * ++ * ++ * ALTERNATIVELY, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") as published by the Free Software ++ * Foundation, either version 2 of that License or (at your option) any ++ * later version. ++ * ++ * THIS SOFTWARE IS PROVIDED BY Freescale Semiconductor ``AS IS'' AND ANY ++ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ++ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE ++ * DISCLAIMED. IN NO EVENT SHALL Freescale Semiconductor BE LIABLE FOR ANY ++ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ++ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; ++ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ++ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ++ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS ++ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++#include ++#include ++#include ++ ++#include "qbman_private.h" ++#include "fsl_qbman_portal.h" ++#include "qbman_debug.h" ++#include "../../include/fsl_dpaa2_fd.h" ++ ++#define QBMAN_SWP_CENA_BASE 0x818000000 ++#define QBMAN_SWP_CINH_BASE 0x81c000000 ++ ++#define QBMAN_PORTAL_IDX 2 ++#define QBMAN_TEST_FQID 19 ++#define QBMAN_TEST_BPID 23 ++#define QBMAN_USE_QD ++#ifdef QBMAN_USE_QD ++#define QBMAN_TEST_QDID 1 ++#endif ++#define QBMAN_TEST_LFQID 0xf00010 ++ ++#define NUM_EQ_FRAME 10 ++#define NUM_DQ_FRAME 10 ++#define NUM_DQ_IN_DQRR 5 ++#define NUM_DQ_IN_MEM (NUM_DQ_FRAME - NUM_DQ_IN_DQRR) ++ ++static struct qbman_swp *swp; ++static struct qbman_eq_desc eqdesc; ++static struct qbman_pull_desc pulldesc; ++static struct qbman_release_desc releasedesc; ++static struct qbman_eq_response eq_storage[1]; ++static struct dpaa2_dq dq_storage[NUM_DQ_IN_MEM] __aligned(64); ++static dma_addr_t eq_storage_phys; ++static dma_addr_t dq_storage_phys; ++ ++/* FQ ctx attribute values for the test code. */ ++#define FQCTX_HI 0xabbaf00d ++#define FQCTX_LO 0x98765432 ++#define FQ_VFQID 0x123456 ++ ++/* Sample frame descriptor */ ++static struct qbman_fd_simple fd = { ++ .addr_lo = 0xbabaf33d, ++ .addr_hi = 0x01234567, ++ .len = 0x7777, ++ .frc = 0xdeadbeef, ++ .flc_lo = 0xcafecafe, ++ .flc_hi = 0xbeadabba ++}; ++ ++static void fd_inc(struct qbman_fd_simple *_fd) ++{ ++ _fd->addr_lo += _fd->len; ++ _fd->flc_lo += 0x100; ++ _fd->frc += 0x10; ++} ++ ++static int fd_cmp(struct qbman_fd *fda, struct qbman_fd *fdb) ++{ ++ int i; ++ ++ for (i = 0; i < 8; i++) ++ if (fda->words[i] - fdb->words[i]) ++ return 1; ++ return 0; ++} ++ ++struct qbman_fd fd_eq[NUM_EQ_FRAME]; ++struct qbman_fd fd_dq[NUM_DQ_FRAME]; ++ ++/* "Buffers" to be released (and storage for buffers to be acquired) */ ++static uint64_t rbufs[320]; ++static uint64_t abufs[320]; ++ ++static void do_enqueue(struct qbman_swp *swp) ++{ ++ int i, j, ret; ++ ++#ifdef QBMAN_USE_QD ++ pr_info("*****QBMan_test: Enqueue %d frames to QD %d\n", ++ NUM_EQ_FRAME, QBMAN_TEST_QDID); ++#else ++ pr_info("*****QBMan_test: Enqueue %d frames to FQ %d\n", ++ NUM_EQ_FRAME, QBMAN_TEST_FQID); ++#endif ++ for (i = 0; i < NUM_EQ_FRAME; i++) { ++ /*********************************/ ++ /* Prepare a enqueue descriptor */ ++ /*********************************/ ++ memset(eq_storage, 0, sizeof(eq_storage)); ++ eq_storage_phys = virt_to_phys(eq_storage); ++ qbman_eq_desc_clear(&eqdesc); ++ qbman_eq_desc_set_no_orp(&eqdesc, 0); ++ qbman_eq_desc_set_response(&eqdesc, eq_storage_phys, 0); ++ qbman_eq_desc_set_token(&eqdesc, 0x99); ++#ifdef QBMAN_USE_QD ++ /**********************************/ ++ /* Prepare a Queueing Destination */ ++ /**********************************/ ++ qbman_eq_desc_set_qd(&eqdesc, QBMAN_TEST_QDID, 0, 3); ++#else ++ qbman_eq_desc_set_fq(&eqdesc, QBMAN_TEST_FQID); ++#endif ++ ++ /******************/ ++ /* Try an enqueue */ ++ /******************/ ++ ret = qbman_swp_enqueue(swp, &eqdesc, ++ (const struct qbman_fd *)&fd); ++ BUG_ON(ret); ++ for (j = 0; j < 8; j++) ++ fd_eq[i].words[j] = *((uint32_t *)&fd + j); ++ fd_inc(&fd); ++ } ++} ++ ++static void do_push_dequeue(struct qbman_swp *swp) ++{ ++ int i, j; ++ const struct dpaa2_dq *dq_storage1; ++ const struct qbman_fd *__fd; ++ int loopvar; ++ ++ pr_info("*****QBMan_test: Start push dequeue\n"); ++ for (i = 0; i < NUM_DQ_FRAME; i++) { ++ DBG_POLL_START(loopvar); ++ do { ++ DBG_POLL_CHECK(loopvar); ++ dq_storage1 = qbman_swp_dqrr_next(swp); ++ } while (!dq_storage1); ++ if (dq_storage1) { ++ __fd = (const struct qbman_fd *) ++ dpaa2_dq_fd(dq_storage1); ++ for (j = 0; j < 8; j++) ++ fd_dq[i].words[j] = __fd->words[j]; ++ if (fd_cmp(&fd_eq[i], &fd_dq[i])) { ++ pr_info("enqueue FD is\n"); ++ hexdump(&fd_eq[i], 32); ++ pr_info("dequeue FD is\n"); ++ hexdump(&fd_dq[i], 32); ++ } ++ qbman_swp_dqrr_consume(swp, dq_storage1); ++ } else { ++ pr_info("The push dequeue fails\n"); ++ } ++ } ++} ++ ++static void do_pull_dequeue(struct qbman_swp *swp) ++{ ++ int i, j, ret; ++ const struct dpaa2_dq *dq_storage1; ++ const struct qbman_fd *__fd; ++ int loopvar; ++ ++ pr_info("*****QBMan_test: Dequeue %d frames with dq entry in DQRR\n", ++ NUM_DQ_IN_DQRR); ++ for (i = 0; i < NUM_DQ_IN_DQRR; i++) { ++ qbman_pull_desc_clear(&pulldesc); ++ qbman_pull_desc_set_storage(&pulldesc, NULL, 0, 0); ++ qbman_pull_desc_set_numframes(&pulldesc, 1); ++ qbman_pull_desc_set_fq(&pulldesc, QBMAN_TEST_FQID); ++ ++ ret = qbman_swp_pull(swp, &pulldesc); ++ BUG_ON(ret); ++ DBG_POLL_START(loopvar); ++ do { ++ DBG_POLL_CHECK(loopvar); ++ dq_storage1 = qbman_swp_dqrr_next(swp); ++ } while (!dq_storage1); ++ ++ if (dq_storage1) { ++ __fd = (const struct qbman_fd *) ++ dpaa2_dq_fd(dq_storage1); ++ for (j = 0; j < 8; j++) ++ fd_dq[i].words[j] = __fd->words[j]; ++ if (fd_cmp(&fd_eq[i], &fd_dq[i])) { ++ pr_info("enqueue FD is\n"); ++ hexdump(&fd_eq[i], 32); ++ pr_info("dequeue FD is\n"); ++ hexdump(&fd_dq[i], 32); ++ } ++ qbman_swp_dqrr_consume(swp, dq_storage1); ++ } else { ++ pr_info("Dequeue with dq entry in DQRR fails\n"); ++ } ++ } ++ ++ pr_info("*****QBMan_test: Dequeue %d frames with dq entry in memory\n", ++ NUM_DQ_IN_MEM); ++ for (i = 0; i < NUM_DQ_IN_MEM; i++) { ++ dq_storage_phys = virt_to_phys(&dq_storage[i]); ++ qbman_pull_desc_clear(&pulldesc); ++ qbman_pull_desc_set_storage(&pulldesc, &dq_storage[i], ++ dq_storage_phys, 1); ++ qbman_pull_desc_set_numframes(&pulldesc, 1); ++ qbman_pull_desc_set_fq(&pulldesc, QBMAN_TEST_FQID); ++ ret = qbman_swp_pull(swp, &pulldesc); ++ BUG_ON(ret); ++ ++ DBG_POLL_START(loopvar); ++ do { ++ DBG_POLL_CHECK(loopvar); ++ ret = qbman_result_has_new_result(swp, ++ &dq_storage[i]); ++ } while (!ret); ++ ++ if (ret) { ++ for (j = 0; j < 8; j++) ++ fd_dq[i + NUM_DQ_IN_DQRR].words[j] = ++ dq_storage[i].dont_manipulate_directly[j + 8]; ++ j = i + NUM_DQ_IN_DQRR; ++ if (fd_cmp(&fd_eq[j], &fd_dq[j])) { ++ pr_info("enqueue FD is\n"); ++ hexdump(&fd_eq[i + NUM_DQ_IN_DQRR], 32); ++ pr_info("dequeue FD is\n"); ++ hexdump(&fd_dq[i + NUM_DQ_IN_DQRR], 32); ++ hexdump(&dq_storage[i], 64); ++ } ++ } else { ++ pr_info("Dequeue with dq entry in memory fails\n"); ++ } ++ } ++} ++ ++static void release_buffer(struct qbman_swp *swp, unsigned int num) ++{ ++ int ret; ++ unsigned int i, j; ++ ++ qbman_release_desc_clear(&releasedesc); ++ qbman_release_desc_set_bpid(&releasedesc, QBMAN_TEST_BPID); ++ pr_info("*****QBMan_test: Release %d buffers to BP %d\n", ++ num, QBMAN_TEST_BPID); ++ for (i = 0; i < (num / 7 + 1); i++) { ++ j = ((num - i * 7) > 7) ? 7 : (num - i * 7); ++ ret = qbman_swp_release(swp, &releasedesc, &rbufs[i * 7], j); ++ BUG_ON(ret); ++ } ++} ++ ++static void acquire_buffer(struct qbman_swp *swp, unsigned int num) ++{ ++ int ret; ++ unsigned int i, j; ++ ++ pr_info("*****QBMan_test: Acquire %d buffers from BP %d\n", ++ num, QBMAN_TEST_BPID); ++ ++ for (i = 0; i < (num / 7 + 1); i++) { ++ j = ((num - i * 7) > 7) ? 7 : (num - i * 7); ++ ret = qbman_swp_acquire(swp, QBMAN_TEST_BPID, &abufs[i * 7], j); ++ BUG_ON(ret != j); ++ } ++} ++ ++static void buffer_pool_test(struct qbman_swp *swp) ++{ ++ struct qbman_attr info; ++ struct dpaa2_dq *bpscn_message; ++ dma_addr_t bpscn_phys; ++ uint64_t bpscn_ctx; ++ uint64_t ctx = 0xbbccddaadeadbeefull; ++ int i, ret; ++ uint32_t hw_targ; ++ ++ pr_info("*****QBMan_test: test buffer pool management\n"); ++ ret = qbman_bp_query(swp, QBMAN_TEST_BPID, &info); ++ qbman_bp_attr_get_bpscn_addr(&info, &bpscn_phys); ++ pr_info("The bpscn is %llx, info_phys is %llx\n", bpscn_phys, ++ virt_to_phys(&info)); ++ bpscn_message = phys_to_virt(bpscn_phys); ++ ++ for (i = 0; i < 320; i++) ++ rbufs[i] = 0xf00dabba01234567ull + i * 0x40; ++ ++ release_buffer(swp, 320); ++ ++ pr_info("QBMan_test: query the buffer pool\n"); ++ qbman_bp_query(swp, QBMAN_TEST_BPID, &info); ++ hexdump(&info, 64); ++ qbman_bp_attr_get_hw_targ(&info, &hw_targ); ++ pr_info("hw_targ is %d\n", hw_targ); ++ ++ /* Acquire buffers to trigger BPSCN */ ++ acquire_buffer(swp, 300); ++ /* BPSCN should be written to the memory */ ++ qbman_bp_query(swp, QBMAN_TEST_BPID, &info); ++ hexdump(&info, 64); ++ hexdump(bpscn_message, 64); ++ BUG_ON(!qbman_result_is_BPSCN(bpscn_message)); ++ /* There should be free buffers in the pool */ ++ BUG_ON(!(qbman_result_bpscn_has_free_bufs(bpscn_message))); ++ /* Buffer pool is depleted */ ++ BUG_ON(!qbman_result_bpscn_is_depleted(bpscn_message)); ++ /* The ctx should match */ ++ bpscn_ctx = qbman_result_bpscn_ctx(bpscn_message); ++ pr_info("BPSCN test: ctx %llx, bpscn_ctx %llx\n", ctx, bpscn_ctx); ++ BUG_ON(ctx != bpscn_ctx); ++ memset(bpscn_message, 0, sizeof(struct dpaa2_dq)); ++ ++ /* Re-seed the buffer pool to trigger BPSCN */ ++ release_buffer(swp, 240); ++ /* BPSCN should be written to the memory */ ++ BUG_ON(!qbman_result_is_BPSCN(bpscn_message)); ++ /* There should be free buffers in the pool */ ++ BUG_ON(!(qbman_result_bpscn_has_free_bufs(bpscn_message))); ++ /* Buffer pool is not depleted */ ++ BUG_ON(qbman_result_bpscn_is_depleted(bpscn_message)); ++ memset(bpscn_message, 0, sizeof(struct dpaa2_dq)); ++ ++ acquire_buffer(swp, 260); ++ /* BPSCN should be written to the memory */ ++ BUG_ON(!qbman_result_is_BPSCN(bpscn_message)); ++ /* There should be free buffers in the pool while BPSCN generated */ ++ BUG_ON(!(qbman_result_bpscn_has_free_bufs(bpscn_message))); ++ /* Buffer pool is depletion */ ++ BUG_ON(!qbman_result_bpscn_is_depleted(bpscn_message)); ++} ++ ++static void ceetm_test(struct qbman_swp *swp) ++{ ++ int i, j, ret; ++ ++ qbman_eq_desc_clear(&eqdesc); ++ qbman_eq_desc_set_no_orp(&eqdesc, 0); ++ qbman_eq_desc_set_fq(&eqdesc, QBMAN_TEST_LFQID); ++ pr_info("*****QBMan_test: Enqueue to LFQID %x\n", ++ QBMAN_TEST_LFQID); ++ for (i = 0; i < NUM_EQ_FRAME; i++) { ++ ret = qbman_swp_enqueue(swp, &eqdesc, ++ (const struct qbman_fd *)&fd); ++ BUG_ON(ret); ++ for (j = 0; j < 8; j++) ++ fd_eq[i].words[j] = *((uint32_t *)&fd + j); ++ fd_inc(&fd); ++ } ++} ++ ++int qbman_test(void) ++{ ++ struct qbman_swp_desc pd; ++ uint32_t reg; ++ ++ pd.cena_bar = ioremap_cache_ns(QBMAN_SWP_CENA_BASE + ++ QBMAN_PORTAL_IDX * 0x10000, 0x10000); ++ pd.cinh_bar = ioremap(QBMAN_SWP_CINH_BASE + ++ QBMAN_PORTAL_IDX * 0x10000, 0x10000); ++ ++ /* Detect whether the mc image is the test image with GPP setup */ ++ reg = readl_relaxed(pd.cena_bar + 0x4); ++ if (reg != 0xdeadbeef) { ++ pr_err("The MC image doesn't have GPP test setup, stop!\n"); ++ iounmap(pd.cena_bar); ++ iounmap(pd.cinh_bar); ++ return -1; ++ } ++ ++ pr_info("*****QBMan_test: Init QBMan SWP %d\n", QBMAN_PORTAL_IDX); ++ swp = qbman_swp_init(&pd); ++ if (!swp) { ++ iounmap(pd.cena_bar); ++ iounmap(pd.cinh_bar); ++ return -1; ++ } ++ ++ /*******************/ ++ /* Enqueue frames */ ++ /*******************/ ++ do_enqueue(swp); ++ ++ /*******************/ ++ /* Do pull dequeue */ ++ /*******************/ ++ do_pull_dequeue(swp); ++ ++ /*******************/ ++ /* Enqueue frames */ ++ /*******************/ ++ qbman_swp_push_set(swp, 0, 1); ++ qbman_swp_fq_schedule(swp, QBMAN_TEST_FQID); ++ do_enqueue(swp); ++ ++ /*******************/ ++ /* Do push dequeue */ ++ /*******************/ ++ do_push_dequeue(swp); ++ ++ /**************************/ ++ /* Test buffer pool funcs */ ++ /**************************/ ++ buffer_pool_test(swp); ++ ++ /******************/ ++ /* CEETM test */ ++ /******************/ ++ ceetm_test(swp); ++ ++ qbman_swp_finish(swp); ++ pr_info("*****QBMan_test: Kernel test Passed\n"); ++ return 0; ++} ++ ++/* user-space test-case, definitions: ++ * ++ * 1 portal only, using portal index 3. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define QBMAN_TEST_US_SWP 3 /* portal index for user space */ ++ ++#define QBMAN_TEST_MAGIC 'q' ++struct qbman_test_swp_ioctl { ++ unsigned long portal1_cinh; ++ unsigned long portal1_cena; ++}; ++struct qbman_test_dma_ioctl { ++ unsigned long ptr; ++ uint64_t phys_addr; ++}; ++ ++struct qbman_test_priv { ++ int has_swp_map; ++ int has_dma_map; ++ unsigned long pgoff; ++}; ++ ++#define QBMAN_TEST_SWP_MAP \ ++ _IOR(QBMAN_TEST_MAGIC, 0x01, struct qbman_test_swp_ioctl) ++#define QBMAN_TEST_SWP_UNMAP \ ++ _IOR(QBMAN_TEST_MAGIC, 0x02, struct qbman_test_swp_ioctl) ++#define QBMAN_TEST_DMA_MAP \ ++ _IOR(QBMAN_TEST_MAGIC, 0x03, struct qbman_test_dma_ioctl) ++#define QBMAN_TEST_DMA_UNMAP \ ++ _IOR(QBMAN_TEST_MAGIC, 0x04, struct qbman_test_dma_ioctl) ++ ++#define TEST_PORTAL1_CENA_PGOFF ((QBMAN_SWP_CENA_BASE + QBMAN_TEST_US_SWP * \ ++ 0x10000) >> PAGE_SHIFT) ++#define TEST_PORTAL1_CINH_PGOFF ((QBMAN_SWP_CINH_BASE + QBMAN_TEST_US_SWP * \ ++ 0x10000) >> PAGE_SHIFT) ++ ++static int qbman_test_open(struct inode *inode, struct file *filp) ++{ ++ struct qbman_test_priv *priv; ++ ++ priv = kmalloc(sizeof(struct qbman_test_priv), GFP_KERNEL); ++ if (!priv) ++ return -EIO; ++ filp->private_data = priv; ++ priv->has_swp_map = 0; ++ priv->has_dma_map = 0; ++ priv->pgoff = 0; ++ return 0; ++} ++ ++static int qbman_test_mmap(struct file *filp, struct vm_area_struct *vma) ++{ ++ int ret; ++ struct qbman_test_priv *priv = filp->private_data; ++ ++ BUG_ON(!priv); ++ ++ if (vma->vm_pgoff == TEST_PORTAL1_CINH_PGOFF) ++ vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); ++ else if (vma->vm_pgoff == TEST_PORTAL1_CENA_PGOFF) ++ vma->vm_page_prot = pgprot_cached_ns(vma->vm_page_prot); ++ else if (vma->vm_pgoff == priv->pgoff) ++ vma->vm_page_prot = pgprot_cached(vma->vm_page_prot); ++ else { ++ pr_err("Damn, unrecognised pg_off!!\n"); ++ return -EINVAL; ++ } ++ ret = remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, ++ vma->vm_end - vma->vm_start, ++ vma->vm_page_prot); ++ return ret; ++} ++ ++static long qbman_test_ioctl(struct file *fp, unsigned int cmd, ++ unsigned long arg) ++{ ++ void __user *a = (void __user *)arg; ++ unsigned long longret, populate; ++ int ret = 0; ++ struct qbman_test_priv *priv = fp->private_data; ++ ++ BUG_ON(!priv); ++ ++ switch (cmd) { ++ case QBMAN_TEST_SWP_MAP: ++ { ++ struct qbman_test_swp_ioctl params; ++ ++ if (priv->has_swp_map) ++ return -EINVAL; ++ down_write(¤t->mm->mmap_sem); ++ /* Map portal1 CINH */ ++ longret = do_mmap_pgoff(fp, PAGE_SIZE, 0x10000, ++ PROT_READ | PROT_WRITE, MAP_SHARED, ++ TEST_PORTAL1_CINH_PGOFF, &populate); ++ if (longret & ~PAGE_MASK) { ++ ret = (int)longret; ++ goto out; ++ } ++ params.portal1_cinh = longret; ++ /* Map portal1 CENA */ ++ longret = do_mmap_pgoff(fp, PAGE_SIZE, 0x10000, ++ PROT_READ | PROT_WRITE, MAP_SHARED, ++ TEST_PORTAL1_CENA_PGOFF, &populate); ++ if (longret & ~PAGE_MASK) { ++ ret = (int)longret; ++ goto out; ++ } ++ params.portal1_cena = longret; ++ priv->has_swp_map = 1; ++out: ++ up_write(¤t->mm->mmap_sem); ++ if (!ret && copy_to_user(a, ¶ms, sizeof(params))) ++ return -EFAULT; ++ return ret; ++ } ++ case QBMAN_TEST_SWP_UNMAP: ++ { ++ struct qbman_test_swp_ioctl params; ++ ++ if (!priv->has_swp_map) ++ return -EINVAL; ++ ++ if (copy_from_user(¶ms, a, sizeof(params))) ++ return -EFAULT; ++ down_write(¤t->mm->mmap_sem); ++ do_munmap(current->mm, params.portal1_cena, 0x10000); ++ do_munmap(current->mm, params.portal1_cinh, 0x10000); ++ up_write(¤t->mm->mmap_sem); ++ priv->has_swp_map = 0; ++ return 0; ++ } ++ case QBMAN_TEST_DMA_MAP: ++ { ++ struct qbman_test_dma_ioctl params; ++ void *vaddr; ++ ++ if (priv->has_dma_map) ++ return -EINVAL; ++ vaddr = (void *)get_zeroed_page(GFP_KERNEL); ++ params.phys_addr = virt_to_phys(vaddr); ++ priv->pgoff = (unsigned long)params.phys_addr >> PAGE_SHIFT; ++ down_write(¤t->mm->mmap_sem); ++ longret = do_mmap_pgoff(fp, PAGE_SIZE, PAGE_SIZE, ++ PROT_READ | PROT_WRITE, MAP_SHARED, ++ priv->pgoff, &populate); ++ if (longret & ~PAGE_MASK) { ++ ret = (int)longret; ++ return ret; ++ } ++ params.ptr = longret; ++ priv->has_dma_map = 1; ++ up_write(¤t->mm->mmap_sem); ++ if (copy_to_user(a, ¶ms, sizeof(params))) ++ return -EFAULT; ++ return 0; ++ } ++ case QBMAN_TEST_DMA_UNMAP: ++ { ++ struct qbman_test_dma_ioctl params; ++ ++ if (!priv->has_dma_map) ++ return -EINVAL; ++ if (copy_from_user(¶ms, a, sizeof(params))) ++ return -EFAULT; ++ down_write(¤t->mm->mmap_sem); ++ do_munmap(current->mm, params.ptr, PAGE_SIZE); ++ up_write(¤t->mm->mmap_sem); ++ free_page((unsigned long)phys_to_virt(params.phys_addr)); ++ priv->has_dma_map = 0; ++ return 0; ++ } ++ default: ++ pr_err("Bad ioctl cmd!\n"); ++ } ++ return -EINVAL; ++} ++ ++static const struct file_operations qbman_fops = { ++ .open = qbman_test_open, ++ .mmap = qbman_test_mmap, ++ .unlocked_ioctl = qbman_test_ioctl ++}; ++ ++static struct miscdevice qbman_miscdev = { ++ .name = "qbman-test", ++ .fops = &qbman_fops, ++ .minor = MISC_DYNAMIC_MINOR, ++}; ++ ++static int qbman_miscdev_init; ++ ++static int test_init(void) ++{ ++ int ret = qbman_test(); ++ ++ if (!ret) { ++ /* MC image supports the test cases, so instantiate the ++ * character devic that the user-space test case will use to do ++ * its memory mappings. */ ++ ret = misc_register(&qbman_miscdev); ++ if (ret) { ++ pr_err("qbman-test: failed to register misc device\n"); ++ return ret; ++ } ++ pr_info("qbman-test: misc device registered!\n"); ++ qbman_miscdev_init = 1; ++ } ++ return 0; ++} ++ ++static void test_exit(void) ++{ ++ if (qbman_miscdev_init) { ++ misc_deregister(&qbman_miscdev); ++ qbman_miscdev_init = 0; ++ } ++} ++ ++module_init(test_init); ++module_exit(test_exit); +diff --git a/drivers/staging/fsl-mc/bus/dpmcp-cmd.h b/drivers/staging/fsl-mc/bus/dpmcp-cmd.h +new file mode 100644 +index 0000000..c9b52dd +--- /dev/null ++++ b/drivers/staging/fsl-mc/bus/dpmcp-cmd.h +@@ -0,0 +1,56 @@ ++/* Copyright 2013-2015 Freescale Semiconductor Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * * Neither the name of the above-listed copyright holders nor the ++ * names of any contributors may be used to endorse or promote products ++ * derived from this software without specific prior written permission. ++ * ++ * ++ * ALTERNATIVELY, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") as published by the Free Software ++ * Foundation, either version 2 of that License or (at your option) any ++ * later version. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE ++ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++#ifndef _FSL_DPMCP_CMD_H ++#define _FSL_DPMCP_CMD_H ++ ++/* Minimal supported DPMCP Version */ ++#define DPMCP_MIN_VER_MAJOR 3 ++#define DPMCP_MIN_VER_MINOR 0 ++ ++/* Command IDs */ ++#define DPMCP_CMDID_CLOSE 0x800 ++#define DPMCP_CMDID_OPEN 0x80b ++#define DPMCP_CMDID_CREATE 0x90b ++#define DPMCP_CMDID_DESTROY 0x900 ++ ++#define DPMCP_CMDID_GET_ATTR 0x004 ++#define DPMCP_CMDID_RESET 0x005 ++ ++#define DPMCP_CMDID_SET_IRQ 0x010 ++#define DPMCP_CMDID_GET_IRQ 0x011 ++#define DPMCP_CMDID_SET_IRQ_ENABLE 0x012 ++#define DPMCP_CMDID_GET_IRQ_ENABLE 0x013 ++#define DPMCP_CMDID_SET_IRQ_MASK 0x014 ++#define DPMCP_CMDID_GET_IRQ_MASK 0x015 ++#define DPMCP_CMDID_GET_IRQ_STATUS 0x016 ++ ++#endif /* _FSL_DPMCP_CMD_H */ +diff --git a/drivers/staging/fsl-mc/bus/dpmcp.c b/drivers/staging/fsl-mc/bus/dpmcp.c +new file mode 100644 +index 0000000..e23592a +--- /dev/null ++++ b/drivers/staging/fsl-mc/bus/dpmcp.c +@@ -0,0 +1,318 @@ ++/* Copyright 2013-2015 Freescale Semiconductor Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * * Neither the name of the above-listed copyright holders nor the ++ * names of any contributors may be used to endorse or promote products ++ * derived from this software without specific prior written permission. ++ * ++ * ++ * ALTERNATIVELY, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") as published by the Free Software ++ * Foundation, either version 2 of that License or (at your option) any ++ * later version. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE ++ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++#include "../include/mc-sys.h" ++#include "../include/mc-cmd.h" ++#include "dpmcp.h" ++#include "dpmcp-cmd.h" ++ ++int dpmcp_open(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ int dpmcp_id, ++ uint16_t *token) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPMCP_CMDID_OPEN, ++ cmd_flags, ++ 0); ++ cmd.params[0] |= mc_enc(0, 32, dpmcp_id); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ *token = MC_CMD_HDR_READ_TOKEN(cmd.header); ++ ++ return err; ++} ++ ++int dpmcp_close(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPMCP_CMDID_CLOSE, ++ cmd_flags, ++ token); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpmcp_create(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ const struct dpmcp_cfg *cfg, ++ uint16_t *token) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPMCP_CMDID_CREATE, ++ cmd_flags, ++ 0); ++ cmd.params[0] |= mc_enc(0, 32, cfg->portal_id); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ *token = MC_CMD_HDR_READ_TOKEN(cmd.header); ++ ++ return 0; ++} ++ ++int dpmcp_destroy(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPMCP_CMDID_DESTROY, ++ cmd_flags, ++ token); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpmcp_reset(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPMCP_CMDID_RESET, ++ cmd_flags, ++ token); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpmcp_set_irq(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ struct dpmcp_irq_cfg *irq_cfg) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPMCP_CMDID_SET_IRQ, ++ cmd_flags, ++ token); ++ cmd.params[0] |= mc_enc(0, 8, irq_index); ++ cmd.params[0] |= mc_enc(32, 32, irq_cfg->val); ++ cmd.params[1] |= mc_enc(0, 64, irq_cfg->paddr); ++ cmd.params[2] |= mc_enc(0, 32, irq_cfg->irq_num); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpmcp_get_irq(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ int *type, ++ struct dpmcp_irq_cfg *irq_cfg) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPMCP_CMDID_GET_IRQ, ++ cmd_flags, ++ token); ++ cmd.params[0] |= mc_enc(32, 8, irq_index); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ irq_cfg->val = (uint32_t)mc_dec(cmd.params[0], 0, 32); ++ irq_cfg->paddr = (uint64_t)mc_dec(cmd.params[1], 0, 64); ++ irq_cfg->irq_num = (int)mc_dec(cmd.params[2], 0, 32); ++ *type = (int)mc_dec(cmd.params[2], 32, 32); ++ return 0; ++} ++ ++int dpmcp_set_irq_enable(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint8_t en) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPMCP_CMDID_SET_IRQ_ENABLE, ++ cmd_flags, ++ token); ++ cmd.params[0] |= mc_enc(0, 8, en); ++ cmd.params[0] |= mc_enc(32, 8, irq_index); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpmcp_get_irq_enable(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint8_t *en) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPMCP_CMDID_GET_IRQ_ENABLE, ++ cmd_flags, ++ token); ++ cmd.params[0] |= mc_enc(32, 8, irq_index); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ *en = (uint8_t)mc_dec(cmd.params[0], 0, 8); ++ return 0; ++} ++ ++int dpmcp_set_irq_mask(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint32_t mask) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPMCP_CMDID_SET_IRQ_MASK, ++ cmd_flags, ++ token); ++ cmd.params[0] |= mc_enc(0, 32, mask); ++ cmd.params[0] |= mc_enc(32, 8, irq_index); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++ ++int dpmcp_get_irq_mask(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint32_t *mask) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPMCP_CMDID_GET_IRQ_MASK, ++ cmd_flags, ++ token); ++ cmd.params[0] |= mc_enc(32, 8, irq_index); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ *mask = (uint32_t)mc_dec(cmd.params[0], 0, 32); ++ return 0; ++} ++ ++int dpmcp_get_irq_status(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint32_t *status) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPMCP_CMDID_GET_IRQ_STATUS, ++ cmd_flags, ++ token); ++ cmd.params[0] |= mc_enc(32, 8, irq_index); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ *status = (uint32_t)mc_dec(cmd.params[0], 0, 32); ++ return 0; ++} ++ ++int dpmcp_get_attributes(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ struct dpmcp_attr *attr) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPMCP_CMDID_GET_ATTR, ++ cmd_flags, ++ token); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ attr->id = (int)mc_dec(cmd.params[0], 32, 32); ++ attr->version.major = (uint16_t)mc_dec(cmd.params[1], 0, 16); ++ attr->version.minor = (uint16_t)mc_dec(cmd.params[1], 16, 16); ++ return 0; ++} +diff --git a/drivers/staging/fsl-mc/bus/dpmcp.h b/drivers/staging/fsl-mc/bus/dpmcp.h +new file mode 100644 +index 0000000..e434a24 +--- /dev/null ++++ b/drivers/staging/fsl-mc/bus/dpmcp.h +@@ -0,0 +1,323 @@ ++/* Copyright 2013-2015 Freescale Semiconductor Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * * Neither the name of the above-listed copyright holders nor the ++ * names of any contributors may be used to endorse or promote products ++ * derived from this software without specific prior written permission. ++ * ++ * ++ * ALTERNATIVELY, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") as published by the Free Software ++ * Foundation, either version 2 of that License or (at your option) any ++ * later version. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE ++ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++#ifndef __FSL_DPMCP_H ++#define __FSL_DPMCP_H ++ ++/* Data Path Management Command Portal API ++ * Contains initialization APIs and runtime control APIs for DPMCP ++ */ ++ ++struct fsl_mc_io; ++ ++/** ++ * dpmcp_open() - Open a control session for the specified object. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @dpmcp_id: DPMCP unique ID ++ * @token: Returned token; use in subsequent API calls ++ * ++ * This function can be used to open a control session for an ++ * already created object; an object may have been declared in ++ * the DPL or by calling the dpmcp_create function. ++ * This function returns a unique authentication token, ++ * associated with the specific object ID and the specific MC ++ * portal; this token must be used in all subsequent commands for ++ * this specific object ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpmcp_open(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ int dpmcp_id, ++ uint16_t *token); ++ ++/* Get portal ID from pool */ ++#define DPMCP_GET_PORTAL_ID_FROM_POOL (-1) ++ ++/** ++ * dpmcp_close() - Close the control session of the object ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPMCP object ++ * ++ * After this function is called, no further operations are ++ * allowed on the object without opening a new control session. ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpmcp_close(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token); ++ ++/** ++ * struct dpmcp_cfg - Structure representing DPMCP configuration ++ * @portal_id: Portal ID; 'DPMCP_GET_PORTAL_ID_FROM_POOL' to get the portal ID ++ * from pool ++ */ ++struct dpmcp_cfg { ++ int portal_id; ++}; ++ ++/** ++ * dpmcp_create() - Create the DPMCP object. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @cfg: Configuration structure ++ * @token: Returned token; use in subsequent API calls ++ * ++ * Create the DPMCP object, allocate required resources and ++ * perform required initialization. ++ * ++ * The object can be created either by declaring it in the ++ * DPL file, or by calling this function. ++ * This function returns a unique authentication token, ++ * associated with the specific object ID and the specific MC ++ * portal; this token must be used in all subsequent calls to ++ * this specific object. For objects that are created using the ++ * DPL file, call dpmcp_open function to get an authentication ++ * token first. ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpmcp_create(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ const struct dpmcp_cfg *cfg, ++ uint16_t *token); ++ ++/** ++ * dpmcp_destroy() - Destroy the DPMCP object and release all its resources. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPMCP object ++ * ++ * Return: '0' on Success; error code otherwise. ++ */ ++int dpmcp_destroy(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token); ++ ++/** ++ * dpmcp_reset() - Reset the DPMCP, returns the object to initial state. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPMCP object ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpmcp_reset(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token); ++ ++/* IRQ */ ++/* IRQ Index */ ++#define DPMCP_IRQ_INDEX 0 ++/* irq event - Indicates that the link state changed */ ++#define DPMCP_IRQ_EVENT_CMD_DONE 0x00000001 ++ ++/** ++ * struct dpmcp_irq_cfg - IRQ configuration ++ * @paddr: Address that must be written to signal a message-based interrupt ++ * @val: Value to write into irq_addr address ++ * @irq_num: A user defined number associated with this IRQ ++ */ ++struct dpmcp_irq_cfg { ++ uint64_t paddr; ++ uint32_t val; ++ int irq_num; ++}; ++ ++/** ++ * dpmcp_set_irq() - Set IRQ information for the DPMCP to trigger an interrupt. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPMCP object ++ * @irq_index: Identifies the interrupt index to configure ++ * @irq_cfg: IRQ configuration ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpmcp_set_irq(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ struct dpmcp_irq_cfg *irq_cfg); ++ ++/** ++ * dpmcp_get_irq() - Get IRQ information from the DPMCP. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPMCP object ++ * @irq_index: The interrupt index to configure ++ * @type: Interrupt type: 0 represents message interrupt ++ * type (both irq_addr and irq_val are valid) ++ * @irq_cfg: IRQ attributes ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpmcp_get_irq(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ int *type, ++ struct dpmcp_irq_cfg *irq_cfg); ++ ++/** ++ * dpmcp_set_irq_enable() - Set overall interrupt state. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPMCP object ++ * @irq_index: The interrupt index to configure ++ * @en: Interrupt state - enable = 1, disable = 0 ++ * ++ * Allows GPP software to control when interrupts are generated. ++ * Each interrupt can have up to 32 causes. The enable/disable control's the ++ * overall interrupt state. if the interrupt is disabled no causes will cause ++ * an interrupt. ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpmcp_set_irq_enable(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint8_t en); ++ ++/** ++ * dpmcp_get_irq_enable() - Get overall interrupt state ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPMCP object ++ * @irq_index: The interrupt index to configure ++ * @en: Returned interrupt state - enable = 1, disable = 0 ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpmcp_get_irq_enable(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint8_t *en); ++ ++/** ++ * dpmcp_set_irq_mask() - Set interrupt mask. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPMCP object ++ * @irq_index: The interrupt index to configure ++ * @mask: Event mask to trigger interrupt; ++ * each bit: ++ * 0 = ignore event ++ * 1 = consider event for asserting IRQ ++ * ++ * Every interrupt can have up to 32 causes and the interrupt model supports ++ * masking/unmasking each cause independently ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpmcp_set_irq_mask(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint32_t mask); ++ ++/** ++ * dpmcp_get_irq_mask() - Get interrupt mask. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPMCP object ++ * @irq_index: The interrupt index to configure ++ * @mask: Returned event mask to trigger interrupt ++ * ++ * Every interrupt can have up to 32 causes and the interrupt model supports ++ * masking/unmasking each cause independently ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpmcp_get_irq_mask(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint32_t *mask); ++ ++/** ++ * dpmcp_get_irq_status() - Get the current status of any pending interrupts. ++ * ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPMCP object ++ * @irq_index: The interrupt index to configure ++ * @status: Returned interrupts status - one bit per cause: ++ * 0 = no interrupt pending ++ * 1 = interrupt pending ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpmcp_get_irq_status(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint32_t *status); ++ ++/** ++ * struct dpmcp_attr - Structure representing DPMCP attributes ++ * @id: DPMCP object ID ++ * @version: DPMCP version ++ */ ++struct dpmcp_attr { ++ int id; ++ /** ++ * struct version - Structure representing DPMCP version ++ * @major: DPMCP major version ++ * @minor: DPMCP minor version ++ */ ++ struct { ++ uint16_t major; ++ uint16_t minor; ++ } version; ++}; ++ ++/** ++ * dpmcp_get_attributes - Retrieve DPMCP attributes. ++ * ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPMCP object ++ * @attr: Returned object's attributes ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpmcp_get_attributes(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ struct dpmcp_attr *attr); ++ ++#endif /* __FSL_DPMCP_H */ +diff --git a/drivers/staging/fsl-mc/bus/dpmng-cmd.h b/drivers/staging/fsl-mc/bus/dpmng-cmd.h +new file mode 100644 +index 0000000..ba8cfa9 +--- /dev/null ++++ b/drivers/staging/fsl-mc/bus/dpmng-cmd.h +@@ -0,0 +1,47 @@ ++/* Copyright 2013-2014 Freescale Semiconductor Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * * Neither the name of the above-listed copyright holders nor the ++ * names of any contributors may be used to endorse or promote products ++ * derived from this software without specific prior written permission. ++ * ++ * ++ * ALTERNATIVELY, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") as published by the Free Software ++ * Foundation, either version 2 of that License or (at your option) any ++ * later version. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE ++ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++/*************************************************************************//* ++ dpmng-cmd.h ++ ++ defines portal commands ++ ++ *//**************************************************************************/ ++ ++#ifndef __FSL_DPMNG_CMD_H ++#define __FSL_DPMNG_CMD_H ++ ++/* Command IDs */ ++#define DPMNG_CMDID_GET_CONT_ID 0x830 ++#define DPMNG_CMDID_GET_VERSION 0x831 ++ ++#endif /* __FSL_DPMNG_CMD_H */ +diff --git a/drivers/staging/fsl-mc/bus/dpmng.c b/drivers/staging/fsl-mc/bus/dpmng.c +new file mode 100644 +index 0000000..387390b +--- /dev/null ++++ b/drivers/staging/fsl-mc/bus/dpmng.c +@@ -0,0 +1,85 @@ ++/* Copyright 2013-2014 Freescale Semiconductor Inc. ++* ++* Redistribution and use in source and binary forms, with or without ++* modification, are permitted provided that the following conditions are met: ++* * Redistributions of source code must retain the above copyright ++* notice, this list of conditions and the following disclaimer. ++* * Redistributions in binary form must reproduce the above copyright ++* notice, this list of conditions and the following disclaimer in the ++* documentation and/or other materials provided with the distribution. ++* * Neither the name of the above-listed copyright holders nor the ++* names of any contributors may be used to endorse or promote products ++* derived from this software without specific prior written permission. ++* ++* ++* ALTERNATIVELY, this software may be distributed under the terms of the ++* GNU General Public License ("GPL") as published by the Free Software ++* Foundation, either version 2 of that License or (at your option) any ++* later version. ++* ++* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE ++* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++* POSSIBILITY OF SUCH DAMAGE. ++*/ ++#include "../include/mc-sys.h" ++#include "../include/mc-cmd.h" ++#include "../include/dpmng.h" ++#include "dpmng-cmd.h" ++ ++int mc_get_version(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ struct mc_version *mc_ver_info) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPMNG_CMDID_GET_VERSION, ++ cmd_flags, ++ 0); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ mc_ver_info->revision = mc_dec(cmd.params[0], 0, 32); ++ mc_ver_info->major = mc_dec(cmd.params[0], 32, 32); ++ mc_ver_info->minor = mc_dec(cmd.params[1], 0, 32); ++ ++ return 0; ++} ++EXPORT_SYMBOL(mc_get_version); ++ ++int dpmng_get_container_id(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ int *container_id) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPMNG_CMDID_GET_CONT_ID, ++ cmd_flags, ++ 0); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ *container_id = mc_dec(cmd.params[0], 0, 32); ++ ++ return 0; ++} ++ +diff --git a/drivers/staging/fsl-mc/bus/dprc-cmd.h b/drivers/staging/fsl-mc/bus/dprc-cmd.h +new file mode 100644 +index 0000000..9b854fa +--- /dev/null ++++ b/drivers/staging/fsl-mc/bus/dprc-cmd.h +@@ -0,0 +1,87 @@ ++/* Copyright 2013-2014 Freescale Semiconductor Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * * Neither the name of the above-listed copyright holders nor the ++ * names of any contributors may be used to endorse or promote products ++ * derived from this software without specific prior written permission. ++ * ++ * ++ * ALTERNATIVELY, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") as published by the Free Software ++ * Foundation, either version 2 of that License or (at your option) any ++ * later version. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE ++ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++/*************************************************************************//* ++ dprc-cmd.h ++ ++ defines dprc portal commands ++ ++ *//**************************************************************************/ ++ ++#ifndef _FSL_DPRC_CMD_H ++#define _FSL_DPRC_CMD_H ++ ++/* Minimal supported DPRC Version */ ++#define DPRC_MIN_VER_MAJOR 5 ++#define DPRC_MIN_VER_MINOR 0 ++ ++/* Command IDs */ ++#define DPRC_CMDID_CLOSE 0x800 ++#define DPRC_CMDID_OPEN 0x805 ++#define DPRC_CMDID_CREATE 0x905 ++ ++#define DPRC_CMDID_GET_ATTR 0x004 ++#define DPRC_CMDID_RESET_CONT 0x005 ++ ++#define DPRC_CMDID_SET_IRQ 0x010 ++#define DPRC_CMDID_GET_IRQ 0x011 ++#define DPRC_CMDID_SET_IRQ_ENABLE 0x012 ++#define DPRC_CMDID_GET_IRQ_ENABLE 0x013 ++#define DPRC_CMDID_SET_IRQ_MASK 0x014 ++#define DPRC_CMDID_GET_IRQ_MASK 0x015 ++#define DPRC_CMDID_GET_IRQ_STATUS 0x016 ++#define DPRC_CMDID_CLEAR_IRQ_STATUS 0x017 ++ ++#define DPRC_CMDID_CREATE_CONT 0x151 ++#define DPRC_CMDID_DESTROY_CONT 0x152 ++#define DPRC_CMDID_SET_RES_QUOTA 0x155 ++#define DPRC_CMDID_GET_RES_QUOTA 0x156 ++#define DPRC_CMDID_ASSIGN 0x157 ++#define DPRC_CMDID_UNASSIGN 0x158 ++#define DPRC_CMDID_GET_OBJ_COUNT 0x159 ++#define DPRC_CMDID_GET_OBJ 0x15A ++#define DPRC_CMDID_GET_RES_COUNT 0x15B ++#define DPRC_CMDID_GET_RES_IDS 0x15C ++#define DPRC_CMDID_GET_OBJ_REG 0x15E ++#define DPRC_CMDID_SET_OBJ_IRQ 0x15F ++#define DPRC_CMDID_GET_OBJ_IRQ 0x160 ++#define DPRC_CMDID_SET_OBJ_LABEL 0x161 ++#define DPRC_CMDID_GET_OBJ_DESC 0x162 ++ ++#define DPRC_CMDID_CONNECT 0x167 ++#define DPRC_CMDID_DISCONNECT 0x168 ++#define DPRC_CMDID_GET_POOL 0x169 ++#define DPRC_CMDID_GET_POOL_COUNT 0x16A ++ ++#define DPRC_CMDID_GET_CONNECTION 0x16C ++ ++#endif /* _FSL_DPRC_CMD_H */ +diff --git a/drivers/staging/fsl-mc/bus/dprc-driver.c b/drivers/staging/fsl-mc/bus/dprc-driver.c +new file mode 100644 +index 0000000..5b6fa1c +--- /dev/null ++++ b/drivers/staging/fsl-mc/bus/dprc-driver.c +@@ -0,0 +1,1084 @@ ++/* ++ * Freescale data path resource container (DPRC) driver ++ * ++ * Copyright (C) 2014 Freescale Semiconductor, Inc. ++ * Author: German Rivera ++ * ++ * This file is licensed under the terms of the GNU General Public ++ * License version 2. This program is licensed "as is" without any ++ * warranty of any kind, whether express or implied. ++ */ ++ ++#include "../include/mc-private.h" ++#include "../include/mc-sys.h" ++#include ++#include ++#include ++#include "dprc-cmd.h" ++#include "dpmcp.h" ++ ++struct dprc_child_objs { ++ int child_count; ++ struct dprc_obj_desc *child_array; ++}; ++ ++static int __fsl_mc_device_remove_if_not_in_mc(struct device *dev, void *data) ++{ ++ int i; ++ struct dprc_child_objs *objs; ++ struct fsl_mc_device *mc_dev; ++ ++ WARN_ON(!dev); ++ WARN_ON(!data); ++ mc_dev = to_fsl_mc_device(dev); ++ objs = data; ++ ++ for (i = 0; i < objs->child_count; i++) { ++ struct dprc_obj_desc *obj_desc = &objs->child_array[i]; ++ ++ if (strlen(obj_desc->type) != 0 && ++ FSL_MC_DEVICE_MATCH(mc_dev, obj_desc)) ++ break; ++ } ++ ++ if (i == objs->child_count) ++ fsl_mc_device_remove(mc_dev); ++ ++ return 0; ++} ++ ++static int __fsl_mc_device_remove(struct device *dev, void *data) ++{ ++ WARN_ON(!dev); ++ WARN_ON(data); ++ fsl_mc_device_remove(to_fsl_mc_device(dev)); ++ return 0; ++} ++ ++/** ++ * dprc_remove_devices - Removes devices for objects removed from a DPRC ++ * ++ * @mc_bus_dev: pointer to the fsl-mc device that represents a DPRC object ++ * @obj_desc_array: array of object descriptors for child objects currently ++ * present in the DPRC in the MC. ++ * @num_child_objects_in_mc: number of entries in obj_desc_array ++ * ++ * Synchronizes the state of the Linux bus driver with the actual state of ++ * the MC by removing devices that represent MC objects that have ++ * been dynamically removed in the physical DPRC. ++ */ ++static void dprc_remove_devices(struct fsl_mc_device *mc_bus_dev, ++ struct dprc_obj_desc *obj_desc_array, ++ int num_child_objects_in_mc) ++{ ++ if (num_child_objects_in_mc != 0) { ++ /* ++ * Remove child objects that are in the DPRC in Linux, ++ * but not in the MC: ++ */ ++ struct dprc_child_objs objs; ++ ++ objs.child_count = num_child_objects_in_mc; ++ objs.child_array = obj_desc_array; ++ device_for_each_child(&mc_bus_dev->dev, &objs, ++ __fsl_mc_device_remove_if_not_in_mc); ++ } else { ++ /* ++ * There are no child objects for this DPRC in the MC. ++ * So, remove all the child devices from Linux: ++ */ ++ device_for_each_child(&mc_bus_dev->dev, NULL, ++ __fsl_mc_device_remove); ++ } ++} ++ ++static int __fsl_mc_device_match(struct device *dev, void *data) ++{ ++ struct dprc_obj_desc *obj_desc = data; ++ struct fsl_mc_device *mc_dev = to_fsl_mc_device(dev); ++ ++ return FSL_MC_DEVICE_MATCH(mc_dev, obj_desc); ++} ++ ++static struct fsl_mc_device *fsl_mc_device_lookup(struct dprc_obj_desc ++ *obj_desc, ++ struct fsl_mc_device ++ *mc_bus_dev) ++{ ++ struct device *dev; ++ ++ dev = device_find_child(&mc_bus_dev->dev, obj_desc, ++ __fsl_mc_device_match); ++ ++ return dev ? to_fsl_mc_device(dev) : NULL; ++} ++ ++/** ++ * check_plugged_state_change - Check change in an MC object's plugged state ++ * ++ * @mc_dev: pointer to the fsl-mc device for a given MC object ++ * @obj_desc: pointer to the MC object's descriptor in the MC ++ * ++ * If the plugged state has changed from unplugged to plugged, the fsl-mc ++ * device is bound to the corresponding device driver. ++ * If the plugged state has changed from plugged to unplugged, the fsl-mc ++ * device is unbound from the corresponding device driver. ++ */ ++static void check_plugged_state_change(struct fsl_mc_device *mc_dev, ++ struct dprc_obj_desc *obj_desc) ++{ ++ int error; ++ uint32_t plugged_flag_at_mc = ++ (obj_desc->state & DPRC_OBJ_STATE_PLUGGED); ++ ++ if (plugged_flag_at_mc != ++ (mc_dev->obj_desc.state & DPRC_OBJ_STATE_PLUGGED)) { ++ if (plugged_flag_at_mc) { ++ mc_dev->obj_desc.state |= DPRC_OBJ_STATE_PLUGGED; ++ error = device_attach(&mc_dev->dev); ++ if (error < 0) { ++ dev_err(&mc_dev->dev, ++ "device_attach() failed: %d\n", ++ error); ++ } ++ } else { ++ mc_dev->obj_desc.state &= ~DPRC_OBJ_STATE_PLUGGED; ++ device_release_driver(&mc_dev->dev); ++ } ++ } ++} ++ ++/** ++ * dprc_add_new_devices - Adds devices to the logical bus for a DPRC ++ * ++ * @mc_bus_dev: pointer to the fsl-mc device that represents a DPRC object ++ * @driver_override: driver override to apply to new objects found in the DPRC, ++ * or NULL, if none. ++ * @obj_desc_array: array of device descriptors for child devices currently ++ * present in the physical DPRC. ++ * @num_child_objects_in_mc: number of entries in obj_desc_array ++ * ++ * Synchronizes the state of the Linux bus driver with the actual ++ * state of the MC by adding objects that have been newly discovered ++ * in the physical DPRC. ++ */ ++static void dprc_add_new_devices(struct fsl_mc_device *mc_bus_dev, ++ const char *driver_override, ++ struct dprc_obj_desc *obj_desc_array, ++ int num_child_objects_in_mc) ++{ ++ int error; ++ int i; ++ ++ for (i = 0; i < num_child_objects_in_mc; i++) { ++ struct fsl_mc_device *child_dev; ++ struct dprc_obj_desc *obj_desc = &obj_desc_array[i]; ++ ++ if (strlen(obj_desc->type) == 0) ++ continue; ++ ++ /* ++ * Check if device is already known to Linux: ++ */ ++ child_dev = fsl_mc_device_lookup(obj_desc, mc_bus_dev); ++ if (child_dev) { ++ check_plugged_state_change(child_dev, obj_desc); ++ continue; ++ } ++ ++ error = fsl_mc_device_add(obj_desc, NULL, &mc_bus_dev->dev, ++ driver_override, &child_dev); ++ if (error < 0) ++ continue; ++ } ++} ++ ++void dprc_init_all_resource_pools(struct fsl_mc_device *mc_bus_dev) ++{ ++ int pool_type; ++ struct fsl_mc_bus *mc_bus = to_fsl_mc_bus(mc_bus_dev); ++ ++ for (pool_type = 0; pool_type < FSL_MC_NUM_POOL_TYPES; pool_type++) { ++ struct fsl_mc_resource_pool *res_pool = ++ &mc_bus->resource_pools[pool_type]; ++ ++ res_pool->type = pool_type; ++ res_pool->max_count = 0; ++ res_pool->free_count = 0; ++ res_pool->mc_bus = mc_bus; ++ INIT_LIST_HEAD(&res_pool->free_list); ++ mutex_init(&res_pool->mutex); ++ } ++} ++ ++static void dprc_cleanup_resource_pool(struct fsl_mc_device *mc_bus_dev, ++ enum fsl_mc_pool_type pool_type) ++{ ++ struct fsl_mc_resource *resource; ++ struct fsl_mc_resource *next; ++ struct fsl_mc_bus *mc_bus = to_fsl_mc_bus(mc_bus_dev); ++ struct fsl_mc_resource_pool *res_pool = ++ &mc_bus->resource_pools[pool_type]; ++ int free_count = 0; ++ ++ WARN_ON(res_pool->type != pool_type); ++ WARN_ON(res_pool->free_count != res_pool->max_count); ++ ++ list_for_each_entry_safe(resource, next, &res_pool->free_list, node) { ++ free_count++; ++ WARN_ON(resource->type != res_pool->type); ++ WARN_ON(resource->parent_pool != res_pool); ++ devm_kfree(&mc_bus_dev->dev, resource); ++ } ++ ++ WARN_ON(free_count != res_pool->free_count); ++} ++ ++/* ++ * Clean up all resource pools other than the IRQ pool ++ */ ++void dprc_cleanup_all_resource_pools(struct fsl_mc_device *mc_bus_dev) ++{ ++ int pool_type; ++ ++ for (pool_type = 0; pool_type < FSL_MC_NUM_POOL_TYPES; pool_type++) { ++ if (pool_type != FSL_MC_POOL_IRQ) ++ dprc_cleanup_resource_pool(mc_bus_dev, pool_type); ++ } ++} ++ ++/** ++ * dprc_scan_objects - Discover objects in a DPRC ++ * ++ * @mc_bus_dev: pointer to the fsl-mc device that represents a DPRC object ++ * @driver_override: driver override to apply to new objects found in the DPRC, ++ * or NULL, if none. ++ * @total_irq_count: total number of IRQs needed by objects in the DPRC. ++ * ++ * Detects objects added and removed from a DPRC and synchronizes the ++ * state of the Linux bus driver, MC by adding and removing ++ * devices accordingly. ++ * Two types of devices can be found in a DPRC: allocatable objects (e.g., ++ * dpbp, dpmcp) and non-allocatable devices (e.g., dprc, dpni). ++ * All allocatable devices needed to be probed before all non-allocatable ++ * devices, to ensure that device drivers for non-allocatable ++ * devices can allocate any type of allocatable devices. ++ * That is, we need to ensure that the corresponding resource pools are ++ * populated before they can get allocation requests from probe callbacks ++ * of the device drivers for the non-allocatable devices. ++ */ ++int dprc_scan_objects(struct fsl_mc_device *mc_bus_dev, ++ const char *driver_override, ++ unsigned int *total_irq_count) ++{ ++ int num_child_objects; ++ int dprc_get_obj_failures; ++ int error; ++ unsigned int irq_count = mc_bus_dev->obj_desc.irq_count; ++ struct dprc_obj_desc *child_obj_desc_array = NULL; ++ ++ error = dprc_get_obj_count(mc_bus_dev->mc_io, ++ 0, ++ mc_bus_dev->mc_handle, ++ &num_child_objects); ++ if (error < 0) { ++ dev_err(&mc_bus_dev->dev, "dprc_get_obj_count() failed: %d\n", ++ error); ++ return error; ++ } ++ ++ if (num_child_objects != 0) { ++ int i; ++ ++ child_obj_desc_array = ++ devm_kmalloc_array(&mc_bus_dev->dev, num_child_objects, ++ sizeof(*child_obj_desc_array), ++ GFP_KERNEL); ++ if (!child_obj_desc_array) ++ return -ENOMEM; ++ ++ /* ++ * Discover objects currently present in the physical DPRC: ++ */ ++ dprc_get_obj_failures = 0; ++ for (i = 0; i < num_child_objects; i++) { ++ struct dprc_obj_desc *obj_desc = ++ &child_obj_desc_array[i]; ++ ++ error = dprc_get_obj(mc_bus_dev->mc_io, ++ 0, ++ mc_bus_dev->mc_handle, ++ i, obj_desc); ++ ++ /* ++ * -ENXIO means object index was invalid. ++ * This is caused when the DPRC was changed at ++ * the MC during the scan. In this case, ++ * abort the current scan. ++ */ ++ if (error == -ENXIO) ++ return error; ++ ++ if (error < 0) { ++ dev_err(&mc_bus_dev->dev, ++ "dprc_get_obj(i=%d) failed: %d\n", ++ i, error); ++ /* ++ * Mark the obj entry as "invalid", by using the ++ * empty string as obj type: ++ */ ++ obj_desc->type[0] = '\0'; ++ obj_desc->id = error; ++ dprc_get_obj_failures++; ++ continue; ++ } ++ ++ /* ++ * for DPRC versions that do not support the ++ * shareability attribute, make simplifying assumption ++ * that only SEC is not shareable. ++ */ ++ if ((strcmp(obj_desc->type, "dpseci") == 0) && ++ (obj_desc->ver_major < 4)) ++ obj_desc->flags |= ++ DPRC_OBJ_FLAG_NO_MEM_SHAREABILITY; ++ ++ irq_count += obj_desc->irq_count; ++ dev_dbg(&mc_bus_dev->dev, ++ "Discovered object: type %s, id %d\n", ++ obj_desc->type, obj_desc->id); ++ } ++ ++ if (dprc_get_obj_failures != 0) { ++ dev_err(&mc_bus_dev->dev, ++ "%d out of %d devices could not be retrieved\n", ++ dprc_get_obj_failures, num_child_objects); ++ } ++ } ++ ++ *total_irq_count = irq_count; ++ dprc_remove_devices(mc_bus_dev, child_obj_desc_array, ++ num_child_objects); ++ ++ dprc_add_new_devices(mc_bus_dev, driver_override, child_obj_desc_array, ++ num_child_objects); ++ ++ if (child_obj_desc_array) ++ devm_kfree(&mc_bus_dev->dev, child_obj_desc_array); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(dprc_scan_objects); ++ ++/** ++ * dprc_scan_container - Scans a physical DPRC and synchronizes Linux bus state ++ * ++ * @mc_bus_dev: pointer to the fsl-mc device that represents a DPRC object ++ * ++ * Scans the physical DPRC and synchronizes the state of the Linux ++ * bus driver with the actual state of the MC by adding and removing ++ * devices as appropriate. ++ */ ++static int dprc_scan_container(struct fsl_mc_device *mc_bus_dev) ++{ ++ int error; ++ unsigned int irq_count; ++ struct fsl_mc_bus *mc_bus = to_fsl_mc_bus(mc_bus_dev); ++ ++ dprc_init_all_resource_pools(mc_bus_dev); ++ ++ /* ++ * Discover objects in the DPRC: ++ */ ++ mutex_lock(&mc_bus->scan_mutex); ++ error = dprc_scan_objects(mc_bus_dev, NULL, &irq_count); ++ mutex_unlock(&mc_bus->scan_mutex); ++ if (error < 0) ++ goto error; ++ ++ if (fsl_mc_interrupts_supported() && !mc_bus->irq_resources) { ++ irq_count += FSL_MC_IRQ_POOL_MAX_EXTRA_IRQS; ++ error = fsl_mc_populate_irq_pool(mc_bus, irq_count); ++ if (error < 0) ++ goto error; ++ } ++ ++ return 0; ++error: ++ device_for_each_child(&mc_bus_dev->dev, NULL, __fsl_mc_device_remove); ++ dprc_cleanup_all_resource_pools(mc_bus_dev); ++ return error; ++} ++ ++/** ++ * dprc_irq0_handler - Regular ISR for DPRC interrupt 0 ++ * ++ * @irq: IRQ number of the interrupt being handled ++ * @arg: Pointer to device structure ++ */ ++static irqreturn_t dprc_irq0_handler(int irq_num, void *arg) ++{ ++ return IRQ_WAKE_THREAD; ++} ++ ++/** ++ * dprc_irq0_handler_thread - Handler thread function for DPRC interrupt 0 ++ * ++ * @irq: IRQ number of the interrupt being handled ++ * @arg: Pointer to device structure ++ */ ++static irqreturn_t dprc_irq0_handler_thread(int irq_num, void *arg) ++{ ++ int error; ++ uint32_t status; ++ struct device *dev = (struct device *)arg; ++ struct fsl_mc_device *mc_dev = to_fsl_mc_device(dev); ++ struct fsl_mc_bus *mc_bus = to_fsl_mc_bus(mc_dev); ++ struct fsl_mc_io *mc_io = mc_dev->mc_io; ++ int irq_index = 0; ++ ++ dev_dbg(dev, "DPRC IRQ %d triggered on CPU %u\n", ++ irq_num, smp_processor_id()); ++ if (WARN_ON(!(mc_dev->flags & FSL_MC_IS_DPRC))) ++ return IRQ_HANDLED; ++ ++ mutex_lock(&mc_bus->scan_mutex); ++ if (WARN_ON(mc_dev->irqs[irq_index]->irq_number != (uint32_t)irq_num)) ++ goto out; ++ ++ status = 0; ++ error = dprc_get_irq_status(mc_io, 0, mc_dev->mc_handle, irq_index, ++ &status); ++ if (error < 0) { ++ dev_err(dev, ++ "dprc_get_irq_status() failed: %d\n", error); ++ goto out; ++ } ++ ++ error = dprc_clear_irq_status(mc_io, 0, mc_dev->mc_handle, irq_index, ++ status); ++ if (error < 0) { ++ dev_err(dev, ++ "dprc_clear_irq_status() failed: %d\n", error); ++ goto out; ++ } ++ ++ if (status & (DPRC_IRQ_EVENT_OBJ_ADDED | ++ DPRC_IRQ_EVENT_OBJ_REMOVED | ++ DPRC_IRQ_EVENT_CONTAINER_DESTROYED | ++ DPRC_IRQ_EVENT_OBJ_DESTROYED | ++ DPRC_IRQ_EVENT_OBJ_CREATED)) { ++ unsigned int irq_count; ++ ++ error = dprc_scan_objects(mc_dev, NULL, &irq_count); ++ if (error < 0) { ++ if (error != -ENXIO) /* don't need to report aborted scan */ ++ dev_err(dev, "dprc_scan_objects() failed: %d\n", error); ++ goto out; ++ } ++ ++ WARN_ON((int16_t)irq_count < 0); ++ ++ if ((int16_t)irq_count > ++ mc_bus->resource_pools[FSL_MC_POOL_IRQ].max_count) { ++ dev_warn(dev, ++ "IRQs needed (%u) exceed IRQs preallocated (%u)\n", ++ irq_count, ++ mc_bus->resource_pools[FSL_MC_POOL_IRQ]. ++ max_count); ++ } ++ } ++ ++out: ++ mutex_unlock(&mc_bus->scan_mutex); ++ return IRQ_HANDLED; ++} ++ ++/* ++ * Disable and clear interrupts for a given DPRC object ++ */ ++static int disable_dprc_irqs(struct fsl_mc_device *mc_dev) ++{ ++ int i; ++ int error; ++ struct fsl_mc_io *mc_io = mc_dev->mc_io; ++ int irq_count = mc_dev->obj_desc.irq_count; ++ ++ if (WARN_ON(irq_count == 0)) ++ return -EINVAL; ++ ++ for (i = 0; i < irq_count; i++) { ++ /* ++ * Disable generation of interrupt i, while we configure it: ++ */ ++ error = dprc_set_irq_enable(mc_io, 0, mc_dev->mc_handle, i, 0); ++ if (error < 0) { ++ dev_err(&mc_dev->dev, ++ "Disabling DPRC IRQ %d failed: dprc_set_irq_enable() failed: %d\n", ++ i, error); ++ ++ return error; ++ } ++ ++ /* ++ * Disable all interrupt causes for interrupt i: ++ */ ++ error = dprc_set_irq_mask(mc_io, 0, mc_dev->mc_handle, i, 0x0); ++ if (error < 0) { ++ dev_err(&mc_dev->dev, ++ "Disabling DPRC IRQ %d failed: dprc_set_irq_mask() failed: %d\n", ++ i, error); ++ ++ return error; ++ } ++ ++ /* ++ * Clear any leftover interrupt i: ++ */ ++ error = dprc_clear_irq_status(mc_io, 0, mc_dev->mc_handle, i, ++ ~0x0U); ++ if (error < 0) { ++ dev_err(&mc_dev->dev, ++ "Disabling DPRC IRQ %d failed: dprc_clear_irq_status() failed: %d\n", ++ i, error); ++ ++ return error; ++ } ++ } ++ ++ return 0; ++} ++ ++static void unregister_dprc_irq_handlers(struct fsl_mc_device *mc_dev) ++{ ++ int i; ++ struct fsl_mc_device_irq *irq; ++ int irq_count = mc_dev->obj_desc.irq_count; ++ ++ for (i = 0; i < irq_count; i++) { ++ irq = mc_dev->irqs[i]; ++ devm_free_irq(&mc_dev->dev, irq->irq_number, ++ &mc_dev->dev); ++ } ++} ++ ++static int register_dprc_irq_handlers(struct fsl_mc_device *mc_dev) ++{ ++ static const struct irq_handler { ++ irq_handler_t irq_handler; ++ irq_handler_t irq_handler_thread; ++ const char *irq_name; ++ } irq_handlers[] = { ++ [0] = { ++ .irq_handler = dprc_irq0_handler, ++ .irq_handler_thread = dprc_irq0_handler_thread, ++ .irq_name = "FSL MC DPRC irq0", ++ }, ++ }; ++ ++ unsigned int i; ++ int error; ++ struct fsl_mc_device_irq *irq; ++ unsigned int num_irq_handlers_registered = 0; ++ int irq_count = mc_dev->obj_desc.irq_count; ++ ++ if (WARN_ON(irq_count != ARRAY_SIZE(irq_handlers))) ++ return -EINVAL; ++ ++ for (i = 0; i < ARRAY_SIZE(irq_handlers); i++) { ++ irq = mc_dev->irqs[i]; ++ ++ /* ++ * NOTE: devm_request_threaded_irq() invokes the device-specific ++ * function that programs the MSI physically in the device ++ */ ++ error = devm_request_threaded_irq(&mc_dev->dev, ++ irq->irq_number, ++ irq_handlers[i].irq_handler, ++ irq_handlers[i]. ++ irq_handler_thread, ++ IRQF_NO_SUSPEND | ++ IRQF_ONESHOT, ++ irq_handlers[i].irq_name, ++ &mc_dev->dev); ++ if (error < 0) { ++ dev_err(&mc_dev->dev, ++ "devm_request_threaded_irq() failed: %d\n", ++ error); ++ goto error_unregister_irq_handlers; ++ } ++ ++ num_irq_handlers_registered++; ++ } ++ ++ return 0; ++ ++error_unregister_irq_handlers: ++ for (i = 0; i < num_irq_handlers_registered; i++) { ++ irq = mc_dev->irqs[i]; ++ devm_free_irq(&mc_dev->dev, irq->irq_number, ++ &mc_dev->dev); ++ } ++ ++ return error; ++} ++ ++static int enable_dprc_irqs(struct fsl_mc_device *mc_dev) ++{ ++ int i; ++ int error; ++ int irq_count = mc_dev->obj_desc.irq_count; ++ ++ for (i = 0; i < irq_count; i++) { ++ /* ++ * Enable all interrupt causes for the interrupt: ++ */ ++ error = dprc_set_irq_mask(mc_dev->mc_io, ++ 0, ++ mc_dev->mc_handle, ++ i, ++ ~0x0u); ++ if (error < 0) { ++ dev_err(&mc_dev->dev, ++ "Enabling DPRC IRQ %d failed: dprc_set_irq_mask() failed: %d\n", ++ i, error); ++ ++ return error; ++ } ++ ++ /* ++ * Enable generation of the interrupt: ++ */ ++ error = dprc_set_irq_enable(mc_dev->mc_io, ++ 0, ++ mc_dev->mc_handle, ++ i, 1); ++ if (error < 0) { ++ dev_err(&mc_dev->dev, ++ "Enabling DPRC IRQ %d failed: dprc_set_irq_enable() failed: %d\n", ++ i, error); ++ ++ return error; ++ } ++ } ++ ++ return 0; ++} ++ ++/* ++ * Setup interrupts for a given DPRC device ++ */ ++static int dprc_setup_irqs(struct fsl_mc_device *mc_dev) ++{ ++ int error; ++ ++ error = fsl_mc_allocate_irqs(mc_dev); ++ if (error < 0) ++ return error; ++ ++ error = disable_dprc_irqs(mc_dev); ++ if (error < 0) ++ goto error_free_irqs; ++ ++ error = register_dprc_irq_handlers(mc_dev); ++ if (error < 0) ++ goto error_free_irqs; ++ ++ error = enable_dprc_irqs(mc_dev); ++ if (error < 0) ++ goto error_unregister_irq_handlers; ++ ++ return 0; ++ ++error_unregister_irq_handlers: ++ unregister_dprc_irq_handlers(mc_dev); ++ ++error_free_irqs: ++ fsl_mc_free_irqs(mc_dev); ++ return error; ++} ++ ++/* ++ * Creates a DPMCP for a DPRC's built-in MC portal ++ */ ++static int dprc_create_dpmcp(struct fsl_mc_device *dprc_dev) ++{ ++ int error; ++ struct dpmcp_cfg dpmcp_cfg; ++ uint16_t dpmcp_handle; ++ struct dprc_res_req res_req; ++ struct dpmcp_attr dpmcp_attr; ++ struct fsl_mc_bus *mc_bus = to_fsl_mc_bus(dprc_dev); ++ ++ dpmcp_cfg.portal_id = mc_bus->dprc_attr.portal_id; ++ error = dpmcp_create(dprc_dev->mc_io, ++ MC_CMD_FLAG_INTR_DIS, ++ &dpmcp_cfg, ++ &dpmcp_handle); ++ if (error < 0) { ++ dev_err(&dprc_dev->dev, "dpmcp_create() failed: %d\n", ++ error); ++ return error; ++ } ++ ++ /* ++ * Set the state of the newly created DPMCP object to be "plugged": ++ */ ++ ++ error = dpmcp_get_attributes(dprc_dev->mc_io, ++ MC_CMD_FLAG_INTR_DIS, ++ dpmcp_handle, ++ &dpmcp_attr); ++ if (error < 0) { ++ dev_err(&dprc_dev->dev, "dpmcp_get_attributes() failed: %d\n", ++ error); ++ goto error_destroy_dpmcp; ++ } ++ ++ if (WARN_ON(dpmcp_attr.id != mc_bus->dprc_attr.portal_id)) { ++ error = -EINVAL; ++ goto error_destroy_dpmcp; ++ } ++ ++ strcpy(res_req.type, "dpmcp"); ++ res_req.num = 1; ++ res_req.options = ++ (DPRC_RES_REQ_OPT_EXPLICIT | DPRC_RES_REQ_OPT_PLUGGED); ++ res_req.id_base_align = dpmcp_attr.id; ++ ++ error = dprc_assign(dprc_dev->mc_io, ++ MC_CMD_FLAG_INTR_DIS, ++ dprc_dev->mc_handle, ++ dprc_dev->obj_desc.id, ++ &res_req); ++ ++ if (error < 0) { ++ dev_err(&dprc_dev->dev, "dprc_assign() failed: %d\n", error); ++ goto error_destroy_dpmcp; ++ } ++ ++ (void)dpmcp_close(dprc_dev->mc_io, ++ MC_CMD_FLAG_INTR_DIS, ++ dpmcp_handle); ++ return 0; ++ ++error_destroy_dpmcp: ++ (void)dpmcp_destroy(dprc_dev->mc_io, ++ MC_CMD_FLAG_INTR_DIS, ++ dpmcp_handle); ++ return error; ++} ++ ++/* ++ * Destroys the DPMCP for a DPRC's built-in MC portal ++ */ ++static void dprc_destroy_dpmcp(struct fsl_mc_device *dprc_dev) ++{ ++ int error; ++ uint16_t dpmcp_handle; ++ struct fsl_mc_bus *mc_bus = to_fsl_mc_bus(dprc_dev); ++ ++ if (WARN_ON(!dprc_dev->mc_io || dprc_dev->mc_io->dpmcp_dev)) ++ return; ++ ++ error = dpmcp_open(dprc_dev->mc_io, ++ MC_CMD_FLAG_INTR_DIS, ++ mc_bus->dprc_attr.portal_id, ++ &dpmcp_handle); ++ if (error < 0) { ++ dev_err(&dprc_dev->dev, "dpmcp_open() failed: %d\n", ++ error); ++ return; ++ } ++ ++ error = dpmcp_destroy(dprc_dev->mc_io, ++ MC_CMD_FLAG_INTR_DIS, ++ dpmcp_handle); ++ if (error < 0) { ++ dev_err(&dprc_dev->dev, "dpmcp_destroy() failed: %d\n", ++ error); ++ return; ++ } ++} ++ ++/** ++ * dprc_probe - callback invoked when a DPRC is being bound to this driver ++ * ++ * @mc_dev: Pointer to fsl-mc device representing a DPRC ++ * ++ * It opens the physical DPRC in the MC. ++ * It scans the DPRC to discover the MC objects contained in it. ++ * It creates the interrupt pool for the MC bus associated with the DPRC. ++ * It configures the interrupts for the DPRC device itself. ++ */ ++static int dprc_probe(struct fsl_mc_device *mc_dev) ++{ ++ int error; ++ size_t region_size; ++ struct fsl_mc_bus *mc_bus = to_fsl_mc_bus(mc_dev); ++ bool mc_io_created = false; ++ bool dev_root_set = false; ++ ++ if (WARN_ON(strcmp(mc_dev->obj_desc.type, "dprc") != 0)) ++ return -EINVAL; ++ ++ if (mc_dev->mc_io) { ++ /* ++ * This is the root DPRC ++ */ ++ if (WARN_ON(fsl_mc_bus_type.dev_root)) ++ return -EINVAL; ++ ++ fsl_mc_bus_type.dev_root = &mc_dev->dev; ++ dev_root_set = true; ++ } else { ++ /* ++ * This is a child DPRC ++ */ ++ if (WARN_ON(!fsl_mc_bus_type.dev_root)) ++ return -EINVAL; ++ ++ if (WARN_ON(mc_dev->obj_desc.region_count == 0)) ++ return -EINVAL; ++ ++ region_size = mc_dev->regions[0].end - ++ mc_dev->regions[0].start + 1; ++ ++ error = fsl_create_mc_io(&mc_dev->dev, ++ mc_dev->regions[0].start, ++ region_size, ++ NULL, 0, &mc_dev->mc_io); ++ if (error < 0) ++ return error; ++ ++ mc_io_created = true; ++ } ++ ++ error = dprc_open(mc_dev->mc_io, 0, mc_dev->obj_desc.id, ++ &mc_dev->mc_handle); ++ if (error < 0) { ++ dev_err(&mc_dev->dev, "dprc_open() failed: %d\n", error); ++ goto error_cleanup_mc_io; ++ } ++ ++ error = dprc_get_attributes(mc_dev->mc_io, 0, mc_dev->mc_handle, ++ &mc_bus->dprc_attr); ++ if (error < 0) { ++ dev_err(&mc_dev->dev, "dprc_get_attributes() failed: %d\n", ++ error); ++ goto error_cleanup_open; ++ } ++ ++ if (mc_bus->dprc_attr.version.major < DPRC_MIN_VER_MAJOR || ++ (mc_bus->dprc_attr.version.major == DPRC_MIN_VER_MAJOR && ++ mc_bus->dprc_attr.version.minor < DPRC_MIN_VER_MINOR)) { ++ dev_err(&mc_dev->dev, ++ "ERROR: DPRC version %d.%d not supported\n", ++ mc_bus->dprc_attr.version.major, ++ mc_bus->dprc_attr.version.minor); ++ error = -ENOTSUPP; ++ goto error_cleanup_open; ++ } ++ ++ if (fsl_mc_interrupts_supported()) { ++ /* ++ * Create DPMCP for the DPRC's built-in portal: ++ */ ++ error = dprc_create_dpmcp(mc_dev); ++ if (error < 0) ++ goto error_cleanup_open; ++ } ++ ++ mutex_init(&mc_bus->scan_mutex); ++ ++ /* ++ * Discover MC objects in the DPRC object: ++ */ ++ error = dprc_scan_container(mc_dev); ++ if (error < 0) ++ goto error_destroy_dpmcp; ++ ++ if (fsl_mc_interrupts_supported()) { ++ /* ++ * The fsl_mc_device object associated with the DPMCP object ++ * created above was created as part of the ++ * dprc_scan_container() call above: ++ */ ++ if (WARN_ON(!mc_dev->mc_io->dpmcp_dev)) { ++ error = -EINVAL; ++ goto error_cleanup_dprc_scan; ++ } ++ ++ /* ++ * Allocate MC portal to be used in atomic context ++ * (e.g., to program MSIs from program_msi_at_mc()) ++ */ ++ error = fsl_mc_portal_allocate(NULL, ++ FSL_MC_IO_ATOMIC_CONTEXT_PORTAL, ++ &mc_bus->atomic_mc_io); ++ if (error < 0) ++ goto error_cleanup_dprc_scan; ++ ++ pr_info("fsl-mc: Allocated dpmcp.%d to dprc.%d for atomic MC I/O\n", ++ mc_bus->atomic_mc_io->dpmcp_dev->obj_desc.id, ++ mc_dev->obj_desc.id); ++ ++ /* ++ * Open DPRC handle to be used with mc_bus->atomic_mc_io: ++ */ ++ error = dprc_open(mc_bus->atomic_mc_io, 0, mc_dev->obj_desc.id, ++ &mc_bus->atomic_dprc_handle); ++ if (error < 0) { ++ dev_err(&mc_dev->dev, "dprc_open() failed: %d\n", ++ error); ++ goto error_cleanup_atomic_mc_io; ++ } ++ ++ /* ++ * Configure interrupt for the DPMCP object associated with the ++ * DPRC object's built-in portal: ++ * ++ * NOTE: We have to do this after calling dprc_scan_container(), ++ * since dprc_scan_container() populates the IRQ pool for ++ * this DPRC. ++ */ ++ error = fsl_mc_io_setup_dpmcp_irq(mc_dev->mc_io); ++ if (error < 0) ++ goto error_cleanup_atomic_dprc_handle; ++ ++ /* ++ * Configure interrupts for the DPRC object associated with ++ * this MC bus: ++ */ ++ error = dprc_setup_irqs(mc_dev); ++ if (error < 0) ++ goto error_cleanup_atomic_dprc_handle; ++ } ++ ++ dev_info(&mc_dev->dev, "DPRC device bound to driver"); ++ return 0; ++ ++error_cleanup_atomic_dprc_handle: ++ (void)dprc_close(mc_bus->atomic_mc_io, 0, mc_bus->atomic_dprc_handle); ++ ++error_cleanup_atomic_mc_io: ++ fsl_mc_portal_free(mc_bus->atomic_mc_io); ++ ++error_cleanup_dprc_scan: ++ fsl_mc_io_unset_dpmcp(mc_dev->mc_io); ++ device_for_each_child(&mc_dev->dev, NULL, __fsl_mc_device_remove); ++ dprc_cleanup_all_resource_pools(mc_dev); ++ if (fsl_mc_interrupts_supported()) ++ fsl_mc_cleanup_irq_pool(mc_bus); ++ ++error_destroy_dpmcp: ++ dprc_destroy_dpmcp(mc_dev); ++ ++error_cleanup_open: ++ (void)dprc_close(mc_dev->mc_io, 0, mc_dev->mc_handle); ++ ++error_cleanup_mc_io: ++ if (mc_io_created) { ++ fsl_destroy_mc_io(mc_dev->mc_io); ++ mc_dev->mc_io = NULL; ++ } ++ ++ if (dev_root_set) ++ fsl_mc_bus_type.dev_root = NULL; ++ ++ return error; ++} ++ ++/* ++ * Tear down interrupts for a given DPRC object ++ */ ++static void dprc_teardown_irqs(struct fsl_mc_device *mc_dev) ++{ ++ (void)disable_dprc_irqs(mc_dev); ++ unregister_dprc_irq_handlers(mc_dev); ++ fsl_mc_free_irqs(mc_dev); ++} ++ ++/** ++ * dprc_remove - callback invoked when a DPRC is being unbound from this driver ++ * ++ * @mc_dev: Pointer to fsl-mc device representing the DPRC ++ * ++ * It removes the DPRC's child objects from Linux (not from the MC) and ++ * closes the DPRC device in the MC. ++ * It tears down the interrupts that were configured for the DPRC device. ++ * It destroys the interrupt pool associated with this MC bus. ++ */ ++static int dprc_remove(struct fsl_mc_device *mc_dev) ++{ ++ int error; ++ struct fsl_mc_bus *mc_bus = to_fsl_mc_bus(mc_dev); ++ ++ if (WARN_ON(strcmp(mc_dev->obj_desc.type, "dprc") != 0)) ++ return -EINVAL; ++ if (WARN_ON(!mc_dev->mc_io)) ++ return -EINVAL; ++ ++ if (WARN_ON(!mc_bus->irq_resources)) ++ return -EINVAL; ++ ++ if (fsl_mc_interrupts_supported()) { ++ dprc_teardown_irqs(mc_dev); ++ error = dprc_close(mc_bus->atomic_mc_io, 0, ++ mc_bus->atomic_dprc_handle); ++ if (error < 0) { ++ dev_err(&mc_dev->dev, "dprc_close() failed: %d\n", ++ error); ++ } ++ ++ fsl_mc_portal_free(mc_bus->atomic_mc_io); ++ } ++ ++ fsl_mc_io_unset_dpmcp(mc_dev->mc_io); ++ device_for_each_child(&mc_dev->dev, NULL, __fsl_mc_device_remove); ++ dprc_cleanup_all_resource_pools(mc_dev); ++ dprc_destroy_dpmcp(mc_dev); ++ error = dprc_close(mc_dev->mc_io, 0, mc_dev->mc_handle); ++ if (error < 0) ++ dev_err(&mc_dev->dev, "dprc_close() failed: %d\n", error); ++ ++ if (fsl_mc_interrupts_supported()) ++ fsl_mc_cleanup_irq_pool(mc_bus); ++ ++ fsl_destroy_mc_io(mc_dev->mc_io); ++ mc_dev->mc_io = NULL; ++ ++ if (&mc_dev->dev == fsl_mc_bus_type.dev_root) ++ fsl_mc_bus_type.dev_root = NULL; ++ ++ dev_info(&mc_dev->dev, "DPRC device unbound from driver"); ++ return 0; ++} ++ ++static const struct fsl_mc_device_match_id match_id_table[] = { ++ { ++ .vendor = FSL_MC_VENDOR_FREESCALE, ++ .obj_type = "dprc"}, ++ {.vendor = 0x0}, ++}; ++ ++static struct fsl_mc_driver dprc_driver = { ++ .driver = { ++ .name = FSL_MC_DPRC_DRIVER_NAME, ++ .owner = THIS_MODULE, ++ .pm = NULL, ++ }, ++ .match_id_table = match_id_table, ++ .probe = dprc_probe, ++ .remove = dprc_remove, ++}; ++ ++int __init dprc_driver_init(void) ++{ ++ return fsl_mc_driver_register(&dprc_driver); ++} ++ ++void dprc_driver_exit(void) ++{ ++ fsl_mc_driver_unregister(&dprc_driver); ++} +diff --git a/drivers/staging/fsl-mc/bus/dprc.c b/drivers/staging/fsl-mc/bus/dprc.c +new file mode 100644 +index 0000000..4d86438 +--- /dev/null ++++ b/drivers/staging/fsl-mc/bus/dprc.c +@@ -0,0 +1,1218 @@ ++/* Copyright 2013-2014 Freescale Semiconductor Inc. ++* ++* Redistribution and use in source and binary forms, with or without ++* modification, are permitted provided that the following conditions are met: ++* * Redistributions of source code must retain the above copyright ++* notice, this list of conditions and the following disclaimer. ++* * Redistributions in binary form must reproduce the above copyright ++* notice, this list of conditions and the following disclaimer in the ++* documentation and/or other materials provided with the distribution. ++* * Neither the name of the above-listed copyright holders nor the ++* names of any contributors may be used to endorse or promote products ++* derived from this software without specific prior written permission. ++* ++* ++* ALTERNATIVELY, this software may be distributed under the terms of the ++* GNU General Public License ("GPL") as published by the Free Software ++* Foundation, either version 2 of that License or (at your option) any ++* later version. ++* ++* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE ++* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++* POSSIBILITY OF SUCH DAMAGE. ++*/ ++#include "../include/mc-sys.h" ++#include "../include/mc-cmd.h" ++#include "../include/dprc.h" ++#include "dprc-cmd.h" ++ ++int dprc_open(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ int container_id, ++ uint16_t *token) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPRC_CMDID_OPEN, cmd_flags, ++ 0); ++ cmd.params[0] |= mc_enc(0, 32, container_id); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ *token = MC_CMD_HDR_READ_TOKEN(cmd.header); ++ ++ return 0; ++} ++EXPORT_SYMBOL(dprc_open); ++ ++int dprc_close(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPRC_CMDID_CLOSE, cmd_flags, ++ token); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++EXPORT_SYMBOL(dprc_close); ++ ++int dprc_create_container(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ struct dprc_cfg *cfg, ++ int *child_container_id, ++ uint64_t *child_portal_offset) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.params[0] |= mc_enc(32, 16, cfg->icid); ++ cmd.params[0] |= mc_enc(0, 32, cfg->options); ++ cmd.params[1] |= mc_enc(32, 32, cfg->portal_id); ++ cmd.params[2] |= mc_enc(0, 8, cfg->label[0]); ++ cmd.params[2] |= mc_enc(8, 8, cfg->label[1]); ++ cmd.params[2] |= mc_enc(16, 8, cfg->label[2]); ++ cmd.params[2] |= mc_enc(24, 8, cfg->label[3]); ++ cmd.params[2] |= mc_enc(32, 8, cfg->label[4]); ++ cmd.params[2] |= mc_enc(40, 8, cfg->label[5]); ++ cmd.params[2] |= mc_enc(48, 8, cfg->label[6]); ++ cmd.params[2] |= mc_enc(56, 8, cfg->label[7]); ++ cmd.params[3] |= mc_enc(0, 8, cfg->label[8]); ++ cmd.params[3] |= mc_enc(8, 8, cfg->label[9]); ++ cmd.params[3] |= mc_enc(16, 8, cfg->label[10]); ++ cmd.params[3] |= mc_enc(24, 8, cfg->label[11]); ++ cmd.params[3] |= mc_enc(32, 8, cfg->label[12]); ++ cmd.params[3] |= mc_enc(40, 8, cfg->label[13]); ++ cmd.params[3] |= mc_enc(48, 8, cfg->label[14]); ++ cmd.params[3] |= mc_enc(56, 8, cfg->label[15]); ++ ++ cmd.header = mc_encode_cmd_header(DPRC_CMDID_CREATE_CONT, ++ cmd_flags, ++ token); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ *child_container_id = mc_dec(cmd.params[1], 0, 32); ++ *child_portal_offset = mc_dec(cmd.params[2], 0, 64); ++ ++ return 0; ++} ++EXPORT_SYMBOL(dprc_create_container); ++ ++int dprc_destroy_container(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int child_container_id) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPRC_CMDID_DESTROY_CONT, ++ cmd_flags, ++ token); ++ cmd.params[0] |= mc_enc(0, 32, child_container_id); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++EXPORT_SYMBOL(dprc_destroy_container); ++ ++int dprc_reset_container(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int child_container_id) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPRC_CMDID_RESET_CONT, ++ cmd_flags, ++ token); ++ cmd.params[0] |= mc_enc(0, 32, child_container_id); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++EXPORT_SYMBOL(dprc_reset_container); ++ ++int dprc_get_irq(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ int *type, ++ struct dprc_irq_cfg *irq_cfg) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPRC_CMDID_GET_IRQ, ++ cmd_flags, ++ token); ++ cmd.params[0] |= mc_enc(32, 8, irq_index); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ irq_cfg->val = mc_dec(cmd.params[0], 0, 32); ++ irq_cfg->paddr = mc_dec(cmd.params[1], 0, 64); ++ irq_cfg->irq_num = mc_dec(cmd.params[2], 0, 32); ++ *type = mc_dec(cmd.params[2], 32, 32); ++ ++ return 0; ++} ++EXPORT_SYMBOL(dprc_get_irq); ++ ++int dprc_set_irq(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ struct dprc_irq_cfg *irq_cfg) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPRC_CMDID_SET_IRQ, ++ cmd_flags, ++ token); ++ cmd.params[0] |= mc_enc(32, 8, irq_index); ++ cmd.params[0] |= mc_enc(0, 32, irq_cfg->val); ++ cmd.params[1] |= mc_enc(0, 64, irq_cfg->paddr); ++ cmd.params[2] |= mc_enc(0, 32, irq_cfg->irq_num); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++EXPORT_SYMBOL(dprc_set_irq); ++ ++int dprc_get_irq_enable(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint8_t *en) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPRC_CMDID_GET_IRQ_ENABLE, ++ cmd_flags, ++ token); ++ cmd.params[0] |= mc_enc(32, 8, irq_index); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ *en = mc_dec(cmd.params[0], 0, 8); ++ ++ return 0; ++} ++EXPORT_SYMBOL(dprc_get_irq_enable); ++ ++int dprc_set_irq_enable(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint8_t en) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPRC_CMDID_SET_IRQ_ENABLE, ++ cmd_flags, ++ token); ++ cmd.params[0] |= mc_enc(0, 8, en); ++ cmd.params[0] |= mc_enc(32, 8, irq_index); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++EXPORT_SYMBOL(dprc_set_irq_enable); ++ ++int dprc_get_irq_mask(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint32_t *mask) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPRC_CMDID_GET_IRQ_MASK, ++ cmd_flags, ++ token); ++ cmd.params[0] |= mc_enc(32, 8, irq_index); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ *mask = mc_dec(cmd.params[0], 0, 32); ++ ++ return 0; ++} ++EXPORT_SYMBOL(dprc_get_irq_mask); ++ ++int dprc_set_irq_mask(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint32_t mask) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPRC_CMDID_SET_IRQ_MASK, ++ cmd_flags, ++ token); ++ cmd.params[0] |= mc_enc(0, 32, mask); ++ cmd.params[0] |= mc_enc(32, 8, irq_index); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++EXPORT_SYMBOL(dprc_set_irq_mask); ++ ++int dprc_get_irq_status(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint32_t *status) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPRC_CMDID_GET_IRQ_STATUS, ++ cmd_flags, ++ token); ++ cmd.params[0] |= mc_enc(0, 32, *status); ++ cmd.params[0] |= mc_enc(32, 8, irq_index); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ *status = mc_dec(cmd.params[0], 0, 32); ++ ++ return 0; ++} ++EXPORT_SYMBOL(dprc_get_irq_status); ++ ++int dprc_clear_irq_status(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint32_t status) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPRC_CMDID_CLEAR_IRQ_STATUS, ++ cmd_flags, ++ token); ++ cmd.params[0] |= mc_enc(0, 32, status); ++ cmd.params[0] |= mc_enc(32, 8, irq_index); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++EXPORT_SYMBOL(dprc_clear_irq_status); ++ ++int dprc_get_attributes(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ struct dprc_attributes *attr) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPRC_CMDID_GET_ATTR, ++ cmd_flags, ++ token); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ attr->container_id = mc_dec(cmd.params[0], 0, 32); ++ attr->icid = mc_dec(cmd.params[0], 32, 16); ++ attr->options = mc_dec(cmd.params[1], 0, 32); ++ attr->portal_id = mc_dec(cmd.params[1], 32, 32); ++ attr->version.major = mc_dec(cmd.params[2], 0, 16); ++ attr->version.minor = mc_dec(cmd.params[2], 16, 16); ++ ++ return 0; ++} ++EXPORT_SYMBOL(dprc_get_attributes); ++ ++int dprc_set_res_quota(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int child_container_id, ++ char *type, ++ uint16_t quota) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPRC_CMDID_SET_RES_QUOTA, ++ cmd_flags, ++ token); ++ cmd.params[0] |= mc_enc(0, 32, child_container_id); ++ cmd.params[0] |= mc_enc(32, 16, quota); ++ cmd.params[1] |= mc_enc(0, 8, type[0]); ++ cmd.params[1] |= mc_enc(8, 8, type[1]); ++ cmd.params[1] |= mc_enc(16, 8, type[2]); ++ cmd.params[1] |= mc_enc(24, 8, type[3]); ++ cmd.params[1] |= mc_enc(32, 8, type[4]); ++ cmd.params[1] |= mc_enc(40, 8, type[5]); ++ cmd.params[1] |= mc_enc(48, 8, type[6]); ++ cmd.params[1] |= mc_enc(56, 8, type[7]); ++ cmd.params[2] |= mc_enc(0, 8, type[8]); ++ cmd.params[2] |= mc_enc(8, 8, type[9]); ++ cmd.params[2] |= mc_enc(16, 8, type[10]); ++ cmd.params[2] |= mc_enc(24, 8, type[11]); ++ cmd.params[2] |= mc_enc(32, 8, type[12]); ++ cmd.params[2] |= mc_enc(40, 8, type[13]); ++ cmd.params[2] |= mc_enc(48, 8, type[14]); ++ cmd.params[2] |= mc_enc(56, 8, '\0'); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++EXPORT_SYMBOL(dprc_set_res_quota); ++ ++int dprc_get_res_quota(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int child_container_id, ++ char *type, ++ uint16_t *quota) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPRC_CMDID_GET_RES_QUOTA, ++ cmd_flags, ++ token); ++ cmd.params[0] |= mc_enc(0, 32, child_container_id); ++ cmd.params[1] |= mc_enc(0, 8, type[0]); ++ cmd.params[1] |= mc_enc(8, 8, type[1]); ++ cmd.params[1] |= mc_enc(16, 8, type[2]); ++ cmd.params[1] |= mc_enc(24, 8, type[3]); ++ cmd.params[1] |= mc_enc(32, 8, type[4]); ++ cmd.params[1] |= mc_enc(40, 8, type[5]); ++ cmd.params[1] |= mc_enc(48, 8, type[6]); ++ cmd.params[1] |= mc_enc(56, 8, type[7]); ++ cmd.params[2] |= mc_enc(0, 8, type[8]); ++ cmd.params[2] |= mc_enc(8, 8, type[9]); ++ cmd.params[2] |= mc_enc(16, 8, type[10]); ++ cmd.params[2] |= mc_enc(24, 8, type[11]); ++ cmd.params[2] |= mc_enc(32, 8, type[12]); ++ cmd.params[2] |= mc_enc(40, 8, type[13]); ++ cmd.params[2] |= mc_enc(48, 8, type[14]); ++ cmd.params[2] |= mc_enc(56, 8, '\0'); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ *quota = mc_dec(cmd.params[0], 32, 16); ++ ++ return 0; ++} ++EXPORT_SYMBOL(dprc_get_res_quota); ++ ++int dprc_assign(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int container_id, ++ struct dprc_res_req *res_req) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPRC_CMDID_ASSIGN, ++ cmd_flags, ++ token); ++ cmd.params[0] |= mc_enc(0, 32, container_id); ++ cmd.params[0] |= mc_enc(32, 32, res_req->options); ++ cmd.params[1] |= mc_enc(0, 32, res_req->num); ++ cmd.params[1] |= mc_enc(32, 32, res_req->id_base_align); ++ cmd.params[2] |= mc_enc(0, 8, res_req->type[0]); ++ cmd.params[2] |= mc_enc(8, 8, res_req->type[1]); ++ cmd.params[2] |= mc_enc(16, 8, res_req->type[2]); ++ cmd.params[2] |= mc_enc(24, 8, res_req->type[3]); ++ cmd.params[2] |= mc_enc(32, 8, res_req->type[4]); ++ cmd.params[2] |= mc_enc(40, 8, res_req->type[5]); ++ cmd.params[2] |= mc_enc(48, 8, res_req->type[6]); ++ cmd.params[2] |= mc_enc(56, 8, res_req->type[7]); ++ cmd.params[3] |= mc_enc(0, 8, res_req->type[8]); ++ cmd.params[3] |= mc_enc(8, 8, res_req->type[9]); ++ cmd.params[3] |= mc_enc(16, 8, res_req->type[10]); ++ cmd.params[3] |= mc_enc(24, 8, res_req->type[11]); ++ cmd.params[3] |= mc_enc(32, 8, res_req->type[12]); ++ cmd.params[3] |= mc_enc(40, 8, res_req->type[13]); ++ cmd.params[3] |= mc_enc(48, 8, res_req->type[14]); ++ cmd.params[3] |= mc_enc(56, 8, res_req->type[15]); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++EXPORT_SYMBOL(dprc_assign); ++ ++int dprc_unassign(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int child_container_id, ++ struct dprc_res_req *res_req) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPRC_CMDID_UNASSIGN, ++ cmd_flags, ++ token); ++ cmd.params[0] |= mc_enc(0, 32, child_container_id); ++ cmd.params[0] |= mc_enc(32, 32, res_req->options); ++ cmd.params[1] |= mc_enc(0, 32, res_req->num); ++ cmd.params[1] |= mc_enc(32, 32, res_req->id_base_align); ++ cmd.params[2] |= mc_enc(0, 8, res_req->type[0]); ++ cmd.params[2] |= mc_enc(8, 8, res_req->type[1]); ++ cmd.params[2] |= mc_enc(16, 8, res_req->type[2]); ++ cmd.params[2] |= mc_enc(24, 8, res_req->type[3]); ++ cmd.params[2] |= mc_enc(32, 8, res_req->type[4]); ++ cmd.params[2] |= mc_enc(40, 8, res_req->type[5]); ++ cmd.params[2] |= mc_enc(48, 8, res_req->type[6]); ++ cmd.params[2] |= mc_enc(56, 8, res_req->type[7]); ++ cmd.params[3] |= mc_enc(0, 8, res_req->type[8]); ++ cmd.params[3] |= mc_enc(8, 8, res_req->type[9]); ++ cmd.params[3] |= mc_enc(16, 8, res_req->type[10]); ++ cmd.params[3] |= mc_enc(24, 8, res_req->type[11]); ++ cmd.params[3] |= mc_enc(32, 8, res_req->type[12]); ++ cmd.params[3] |= mc_enc(40, 8, res_req->type[13]); ++ cmd.params[3] |= mc_enc(48, 8, res_req->type[14]); ++ cmd.params[3] |= mc_enc(56, 8, res_req->type[15]); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++EXPORT_SYMBOL(dprc_unassign); ++ ++int dprc_get_pool_count(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int *pool_count) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPRC_CMDID_GET_POOL_COUNT, ++ cmd_flags, ++ token); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ *pool_count = mc_dec(cmd.params[0], 0, 32); ++ ++ return 0; ++} ++EXPORT_SYMBOL(dprc_get_pool_count); ++ ++int dprc_get_pool(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int pool_index, ++ char *type) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPRC_CMDID_GET_POOL, ++ cmd_flags, ++ token); ++ cmd.params[0] |= mc_enc(0, 32, pool_index); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ type[0] = mc_dec(cmd.params[1], 0, 8); ++ type[1] = mc_dec(cmd.params[1], 8, 8); ++ type[2] = mc_dec(cmd.params[1], 16, 8); ++ type[3] = mc_dec(cmd.params[1], 24, 8); ++ type[4] = mc_dec(cmd.params[1], 32, 8); ++ type[5] = mc_dec(cmd.params[1], 40, 8); ++ type[6] = mc_dec(cmd.params[1], 48, 8); ++ type[7] = mc_dec(cmd.params[1], 56, 8); ++ type[8] = mc_dec(cmd.params[2], 0, 8); ++ type[9] = mc_dec(cmd.params[2], 8, 8); ++ type[10] = mc_dec(cmd.params[2], 16, 8); ++ type[11] = mc_dec(cmd.params[2], 24, 8); ++ type[12] = mc_dec(cmd.params[2], 32, 8); ++ type[13] = mc_dec(cmd.params[2], 40, 8); ++ type[14] = mc_dec(cmd.params[2], 48, 8); ++ type[15] = '\0'; ++ ++ return 0; ++} ++EXPORT_SYMBOL(dprc_get_pool); ++ ++int dprc_get_obj_count(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int *obj_count) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPRC_CMDID_GET_OBJ_COUNT, ++ cmd_flags, ++ token); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ *obj_count = mc_dec(cmd.params[0], 32, 32); ++ ++ return 0; ++} ++EXPORT_SYMBOL(dprc_get_obj_count); ++ ++int dprc_get_obj(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int obj_index, ++ struct dprc_obj_desc *obj_desc) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPRC_CMDID_GET_OBJ, ++ cmd_flags, ++ token); ++ cmd.params[0] |= mc_enc(0, 32, obj_index); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ obj_desc->id = mc_dec(cmd.params[0], 32, 32); ++ obj_desc->vendor = mc_dec(cmd.params[1], 0, 16); ++ obj_desc->irq_count = mc_dec(cmd.params[1], 16, 8); ++ obj_desc->region_count = mc_dec(cmd.params[1], 24, 8); ++ obj_desc->state = mc_dec(cmd.params[1], 32, 32); ++ obj_desc->ver_major = mc_dec(cmd.params[2], 0, 16); ++ obj_desc->ver_minor = mc_dec(cmd.params[2], 16, 16); ++ obj_desc->flags = mc_dec(cmd.params[2], 32, 16); ++ obj_desc->type[0] = mc_dec(cmd.params[3], 0, 8); ++ obj_desc->type[1] = mc_dec(cmd.params[3], 8, 8); ++ obj_desc->type[2] = mc_dec(cmd.params[3], 16, 8); ++ obj_desc->type[3] = mc_dec(cmd.params[3], 24, 8); ++ obj_desc->type[4] = mc_dec(cmd.params[3], 32, 8); ++ obj_desc->type[5] = mc_dec(cmd.params[3], 40, 8); ++ obj_desc->type[6] = mc_dec(cmd.params[3], 48, 8); ++ obj_desc->type[7] = mc_dec(cmd.params[3], 56, 8); ++ obj_desc->type[8] = mc_dec(cmd.params[4], 0, 8); ++ obj_desc->type[9] = mc_dec(cmd.params[4], 8, 8); ++ obj_desc->type[10] = mc_dec(cmd.params[4], 16, 8); ++ obj_desc->type[11] = mc_dec(cmd.params[4], 24, 8); ++ obj_desc->type[12] = mc_dec(cmd.params[4], 32, 8); ++ obj_desc->type[13] = mc_dec(cmd.params[4], 40, 8); ++ obj_desc->type[14] = mc_dec(cmd.params[4], 48, 8); ++ obj_desc->type[15] = '\0'; ++ obj_desc->label[0] = mc_dec(cmd.params[5], 0, 8); ++ obj_desc->label[1] = mc_dec(cmd.params[5], 8, 8); ++ obj_desc->label[2] = mc_dec(cmd.params[5], 16, 8); ++ obj_desc->label[3] = mc_dec(cmd.params[5], 24, 8); ++ obj_desc->label[4] = mc_dec(cmd.params[5], 32, 8); ++ obj_desc->label[5] = mc_dec(cmd.params[5], 40, 8); ++ obj_desc->label[6] = mc_dec(cmd.params[5], 48, 8); ++ obj_desc->label[7] = mc_dec(cmd.params[5], 56, 8); ++ obj_desc->label[8] = mc_dec(cmd.params[6], 0, 8); ++ obj_desc->label[9] = mc_dec(cmd.params[6], 8, 8); ++ obj_desc->label[10] = mc_dec(cmd.params[6], 16, 8); ++ obj_desc->label[11] = mc_dec(cmd.params[6], 24, 8); ++ obj_desc->label[12] = mc_dec(cmd.params[6], 32, 8); ++ obj_desc->label[13] = mc_dec(cmd.params[6], 40, 8); ++ obj_desc->label[14] = mc_dec(cmd.params[6], 48, 8); ++ obj_desc->label[15] = '\0'; ++ return 0; ++} ++EXPORT_SYMBOL(dprc_get_obj); ++ ++int dprc_get_obj_desc(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ char *obj_type, ++ int obj_id, ++ struct dprc_obj_desc *obj_desc) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPRC_CMDID_GET_OBJ_DESC, ++ cmd_flags, ++ token); ++ cmd.params[0] |= mc_enc(0, 32, obj_id); ++ cmd.params[1] |= mc_enc(0, 8, obj_type[0]); ++ cmd.params[1] |= mc_enc(8, 8, obj_type[1]); ++ cmd.params[1] |= mc_enc(16, 8, obj_type[2]); ++ cmd.params[1] |= mc_enc(24, 8, obj_type[3]); ++ cmd.params[1] |= mc_enc(32, 8, obj_type[4]); ++ cmd.params[1] |= mc_enc(40, 8, obj_type[5]); ++ cmd.params[1] |= mc_enc(48, 8, obj_type[6]); ++ cmd.params[1] |= mc_enc(56, 8, obj_type[7]); ++ cmd.params[2] |= mc_enc(0, 8, obj_type[8]); ++ cmd.params[2] |= mc_enc(8, 8, obj_type[9]); ++ cmd.params[2] |= mc_enc(16, 8, obj_type[10]); ++ cmd.params[2] |= mc_enc(24, 8, obj_type[11]); ++ cmd.params[2] |= mc_enc(32, 8, obj_type[12]); ++ cmd.params[2] |= mc_enc(40, 8, obj_type[13]); ++ cmd.params[2] |= mc_enc(48, 8, obj_type[14]); ++ cmd.params[2] |= mc_enc(56, 8, obj_type[15]); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ obj_desc->id = (int)mc_dec(cmd.params[0], 32, 32); ++ obj_desc->vendor = (uint16_t)mc_dec(cmd.params[1], 0, 16); ++ obj_desc->vendor = (uint8_t)mc_dec(cmd.params[1], 16, 8); ++ obj_desc->region_count = (uint8_t)mc_dec(cmd.params[1], 24, 8); ++ obj_desc->state = (uint32_t)mc_dec(cmd.params[1], 32, 32); ++ obj_desc->ver_major = (uint16_t)mc_dec(cmd.params[2], 0, 16); ++ obj_desc->ver_minor = (uint16_t)mc_dec(cmd.params[2], 16, 16); ++ obj_desc->flags = mc_dec(cmd.params[2], 32, 16); ++ obj_desc->type[0] = (char)mc_dec(cmd.params[3], 0, 8); ++ obj_desc->type[1] = (char)mc_dec(cmd.params[3], 8, 8); ++ obj_desc->type[2] = (char)mc_dec(cmd.params[3], 16, 8); ++ obj_desc->type[3] = (char)mc_dec(cmd.params[3], 24, 8); ++ obj_desc->type[4] = (char)mc_dec(cmd.params[3], 32, 8); ++ obj_desc->type[5] = (char)mc_dec(cmd.params[3], 40, 8); ++ obj_desc->type[6] = (char)mc_dec(cmd.params[3], 48, 8); ++ obj_desc->type[7] = (char)mc_dec(cmd.params[3], 56, 8); ++ obj_desc->type[8] = (char)mc_dec(cmd.params[4], 0, 8); ++ obj_desc->type[9] = (char)mc_dec(cmd.params[4], 8, 8); ++ obj_desc->type[10] = (char)mc_dec(cmd.params[4], 16, 8); ++ obj_desc->type[11] = (char)mc_dec(cmd.params[4], 24, 8); ++ obj_desc->type[12] = (char)mc_dec(cmd.params[4], 32, 8); ++ obj_desc->type[13] = (char)mc_dec(cmd.params[4], 40, 8); ++ obj_desc->type[14] = (char)mc_dec(cmd.params[4], 48, 8); ++ obj_desc->type[15] = (char)mc_dec(cmd.params[4], 56, 8); ++ obj_desc->label[0] = (char)mc_dec(cmd.params[5], 0, 8); ++ obj_desc->label[1] = (char)mc_dec(cmd.params[5], 8, 8); ++ obj_desc->label[2] = (char)mc_dec(cmd.params[5], 16, 8); ++ obj_desc->label[3] = (char)mc_dec(cmd.params[5], 24, 8); ++ obj_desc->label[4] = (char)mc_dec(cmd.params[5], 32, 8); ++ obj_desc->label[5] = (char)mc_dec(cmd.params[5], 40, 8); ++ obj_desc->label[6] = (char)mc_dec(cmd.params[5], 48, 8); ++ obj_desc->label[7] = (char)mc_dec(cmd.params[5], 56, 8); ++ obj_desc->label[8] = (char)mc_dec(cmd.params[6], 0, 8); ++ obj_desc->label[9] = (char)mc_dec(cmd.params[6], 8, 8); ++ obj_desc->label[10] = (char)mc_dec(cmd.params[6], 16, 8); ++ obj_desc->label[11] = (char)mc_dec(cmd.params[6], 24, 8); ++ obj_desc->label[12] = (char)mc_dec(cmd.params[6], 32, 8); ++ obj_desc->label[13] = (char)mc_dec(cmd.params[6], 40, 8); ++ obj_desc->label[14] = (char)mc_dec(cmd.params[6], 48, 8); ++ obj_desc->label[15] = (char)mc_dec(cmd.params[6], 56, 8); ++ ++ return 0; ++} ++EXPORT_SYMBOL(dprc_get_obj_desc); ++ ++int dprc_set_obj_irq(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ char *obj_type, ++ int obj_id, ++ uint8_t irq_index, ++ struct dprc_irq_cfg *irq_cfg) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPRC_CMDID_SET_OBJ_IRQ, ++ cmd_flags, ++ token); ++ cmd.params[0] |= mc_enc(32, 8, irq_index); ++ cmd.params[0] |= mc_enc(0, 32, irq_cfg->val); ++ cmd.params[1] |= mc_enc(0, 64, irq_cfg->paddr); ++ cmd.params[2] |= mc_enc(0, 32, irq_cfg->irq_num); ++ cmd.params[2] |= mc_enc(32, 32, obj_id); ++ cmd.params[3] |= mc_enc(0, 8, obj_type[0]); ++ cmd.params[3] |= mc_enc(8, 8, obj_type[1]); ++ cmd.params[3] |= mc_enc(16, 8, obj_type[2]); ++ cmd.params[3] |= mc_enc(24, 8, obj_type[3]); ++ cmd.params[3] |= mc_enc(32, 8, obj_type[4]); ++ cmd.params[3] |= mc_enc(40, 8, obj_type[5]); ++ cmd.params[3] |= mc_enc(48, 8, obj_type[6]); ++ cmd.params[3] |= mc_enc(56, 8, obj_type[7]); ++ cmd.params[4] |= mc_enc(0, 8, obj_type[8]); ++ cmd.params[4] |= mc_enc(8, 8, obj_type[9]); ++ cmd.params[4] |= mc_enc(16, 8, obj_type[10]); ++ cmd.params[4] |= mc_enc(24, 8, obj_type[11]); ++ cmd.params[4] |= mc_enc(32, 8, obj_type[12]); ++ cmd.params[4] |= mc_enc(40, 8, obj_type[13]); ++ cmd.params[4] |= mc_enc(48, 8, obj_type[14]); ++ cmd.params[4] |= mc_enc(56, 8, obj_type[15]); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++EXPORT_SYMBOL(dprc_set_obj_irq); ++ ++int dprc_get_obj_irq(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ char *obj_type, ++ int obj_id, ++ uint8_t irq_index, ++ int *type, ++ struct dprc_irq_cfg *irq_cfg) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPRC_CMDID_GET_OBJ_IRQ, ++ cmd_flags, ++ token); ++ cmd.params[0] |= mc_enc(0, 32, obj_id); ++ cmd.params[0] |= mc_enc(32, 8, irq_index); ++ cmd.params[1] |= mc_enc(0, 8, obj_type[0]); ++ cmd.params[1] |= mc_enc(8, 8, obj_type[1]); ++ cmd.params[1] |= mc_enc(16, 8, obj_type[2]); ++ cmd.params[1] |= mc_enc(24, 8, obj_type[3]); ++ cmd.params[1] |= mc_enc(32, 8, obj_type[4]); ++ cmd.params[1] |= mc_enc(40, 8, obj_type[5]); ++ cmd.params[1] |= mc_enc(48, 8, obj_type[6]); ++ cmd.params[1] |= mc_enc(56, 8, obj_type[7]); ++ cmd.params[2] |= mc_enc(0, 8, obj_type[8]); ++ cmd.params[2] |= mc_enc(8, 8, obj_type[9]); ++ cmd.params[2] |= mc_enc(16, 8, obj_type[10]); ++ cmd.params[2] |= mc_enc(24, 8, obj_type[11]); ++ cmd.params[2] |= mc_enc(32, 8, obj_type[12]); ++ cmd.params[2] |= mc_enc(40, 8, obj_type[13]); ++ cmd.params[2] |= mc_enc(48, 8, obj_type[14]); ++ cmd.params[2] |= mc_enc(56, 8, obj_type[15]); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ irq_cfg->val = (uint32_t)mc_dec(cmd.params[0], 0, 32); ++ irq_cfg->paddr = (uint64_t)mc_dec(cmd.params[1], 0, 64); ++ irq_cfg->irq_num = (int)mc_dec(cmd.params[2], 0, 32); ++ *type = (int)mc_dec(cmd.params[2], 32, 32); ++ ++ return 0; ++} ++EXPORT_SYMBOL(dprc_get_obj_irq); ++ ++int dprc_get_res_count(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ char *type, ++ int *res_count) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ *res_count = 0; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPRC_CMDID_GET_RES_COUNT, ++ cmd_flags, ++ token); ++ cmd.params[1] |= mc_enc(0, 8, type[0]); ++ cmd.params[1] |= mc_enc(8, 8, type[1]); ++ cmd.params[1] |= mc_enc(16, 8, type[2]); ++ cmd.params[1] |= mc_enc(24, 8, type[3]); ++ cmd.params[1] |= mc_enc(32, 8, type[4]); ++ cmd.params[1] |= mc_enc(40, 8, type[5]); ++ cmd.params[1] |= mc_enc(48, 8, type[6]); ++ cmd.params[1] |= mc_enc(56, 8, type[7]); ++ cmd.params[2] |= mc_enc(0, 8, type[8]); ++ cmd.params[2] |= mc_enc(8, 8, type[9]); ++ cmd.params[2] |= mc_enc(16, 8, type[10]); ++ cmd.params[2] |= mc_enc(24, 8, type[11]); ++ cmd.params[2] |= mc_enc(32, 8, type[12]); ++ cmd.params[2] |= mc_enc(40, 8, type[13]); ++ cmd.params[2] |= mc_enc(48, 8, type[14]); ++ cmd.params[2] |= mc_enc(56, 8, '\0'); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ *res_count = mc_dec(cmd.params[0], 0, 32); ++ ++ return 0; ++} ++EXPORT_SYMBOL(dprc_get_res_count); ++ ++int dprc_get_res_ids(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ char *type, ++ struct dprc_res_ids_range_desc *range_desc) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPRC_CMDID_GET_RES_IDS, ++ cmd_flags, ++ token); ++ cmd.params[0] |= mc_enc(42, 7, range_desc->iter_status); ++ cmd.params[1] |= mc_enc(0, 32, range_desc->base_id); ++ cmd.params[1] |= mc_enc(32, 32, range_desc->last_id); ++ cmd.params[2] |= mc_enc(0, 8, type[0]); ++ cmd.params[2] |= mc_enc(8, 8, type[1]); ++ cmd.params[2] |= mc_enc(16, 8, type[2]); ++ cmd.params[2] |= mc_enc(24, 8, type[3]); ++ cmd.params[2] |= mc_enc(32, 8, type[4]); ++ cmd.params[2] |= mc_enc(40, 8, type[5]); ++ cmd.params[2] |= mc_enc(48, 8, type[6]); ++ cmd.params[2] |= mc_enc(56, 8, type[7]); ++ cmd.params[3] |= mc_enc(0, 8, type[8]); ++ cmd.params[3] |= mc_enc(8, 8, type[9]); ++ cmd.params[3] |= mc_enc(16, 8, type[10]); ++ cmd.params[3] |= mc_enc(24, 8, type[11]); ++ cmd.params[3] |= mc_enc(32, 8, type[12]); ++ cmd.params[3] |= mc_enc(40, 8, type[13]); ++ cmd.params[3] |= mc_enc(48, 8, type[14]); ++ cmd.params[3] |= mc_enc(56, 8, '\0'); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ range_desc->iter_status = mc_dec(cmd.params[0], 42, 7); ++ range_desc->base_id = mc_dec(cmd.params[1], 0, 32); ++ range_desc->last_id = mc_dec(cmd.params[1], 32, 32); ++ ++ return 0; ++} ++EXPORT_SYMBOL(dprc_get_res_ids); ++ ++int dprc_get_obj_region(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ char *obj_type, ++ int obj_id, ++ uint8_t region_index, ++ struct dprc_region_desc *region_desc) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPRC_CMDID_GET_OBJ_REG, ++ cmd_flags, ++ token); ++ cmd.params[0] |= mc_enc(0, 32, obj_id); ++ cmd.params[0] |= mc_enc(48, 8, region_index); ++ cmd.params[3] |= mc_enc(0, 8, obj_type[0]); ++ cmd.params[3] |= mc_enc(8, 8, obj_type[1]); ++ cmd.params[3] |= mc_enc(16, 8, obj_type[2]); ++ cmd.params[3] |= mc_enc(24, 8, obj_type[3]); ++ cmd.params[3] |= mc_enc(32, 8, obj_type[4]); ++ cmd.params[3] |= mc_enc(40, 8, obj_type[5]); ++ cmd.params[3] |= mc_enc(48, 8, obj_type[6]); ++ cmd.params[3] |= mc_enc(56, 8, obj_type[7]); ++ cmd.params[4] |= mc_enc(0, 8, obj_type[8]); ++ cmd.params[4] |= mc_enc(8, 8, obj_type[9]); ++ cmd.params[4] |= mc_enc(16, 8, obj_type[10]); ++ cmd.params[4] |= mc_enc(24, 8, obj_type[11]); ++ cmd.params[4] |= mc_enc(32, 8, obj_type[12]); ++ cmd.params[4] |= mc_enc(40, 8, obj_type[13]); ++ cmd.params[4] |= mc_enc(48, 8, obj_type[14]); ++ cmd.params[4] |= mc_enc(56, 8, '\0'); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ region_desc->base_offset = mc_dec(cmd.params[1], 0, 64); ++ region_desc->size = mc_dec(cmd.params[2], 0, 32); ++ ++ return 0; ++} ++EXPORT_SYMBOL(dprc_get_obj_region); ++ ++int dprc_set_obj_label(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ char *obj_type, ++ int obj_id, ++ char *label) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPRC_CMDID_SET_OBJ_LABEL, ++ cmd_flags, ++ token); ++ ++ cmd.params[0] |= mc_enc(0, 32, obj_id); ++ cmd.params[1] |= mc_enc(0, 8, label[0]); ++ cmd.params[1] |= mc_enc(8, 8, label[1]); ++ cmd.params[1] |= mc_enc(16, 8, label[2]); ++ cmd.params[1] |= mc_enc(24, 8, label[3]); ++ cmd.params[1] |= mc_enc(32, 8, label[4]); ++ cmd.params[1] |= mc_enc(40, 8, label[5]); ++ cmd.params[1] |= mc_enc(48, 8, label[6]); ++ cmd.params[1] |= mc_enc(56, 8, label[7]); ++ cmd.params[2] |= mc_enc(0, 8, label[8]); ++ cmd.params[2] |= mc_enc(8, 8, label[9]); ++ cmd.params[2] |= mc_enc(16, 8, label[10]); ++ cmd.params[2] |= mc_enc(24, 8, label[11]); ++ cmd.params[2] |= mc_enc(32, 8, label[12]); ++ cmd.params[2] |= mc_enc(40, 8, label[13]); ++ cmd.params[2] |= mc_enc(48, 8, label[14]); ++ cmd.params[2] |= mc_enc(56, 8, label[15]); ++ cmd.params[3] |= mc_enc(0, 8, obj_type[0]); ++ cmd.params[3] |= mc_enc(8, 8, obj_type[1]); ++ cmd.params[3] |= mc_enc(16, 8, obj_type[2]); ++ cmd.params[3] |= mc_enc(24, 8, obj_type[3]); ++ cmd.params[3] |= mc_enc(32, 8, obj_type[4]); ++ cmd.params[3] |= mc_enc(40, 8, obj_type[5]); ++ cmd.params[3] |= mc_enc(48, 8, obj_type[6]); ++ cmd.params[3] |= mc_enc(56, 8, obj_type[7]); ++ cmd.params[4] |= mc_enc(0, 8, obj_type[8]); ++ cmd.params[4] |= mc_enc(8, 8, obj_type[9]); ++ cmd.params[4] |= mc_enc(16, 8, obj_type[10]); ++ cmd.params[4] |= mc_enc(24, 8, obj_type[11]); ++ cmd.params[4] |= mc_enc(32, 8, obj_type[12]); ++ cmd.params[4] |= mc_enc(40, 8, obj_type[13]); ++ cmd.params[4] |= mc_enc(48, 8, obj_type[14]); ++ cmd.params[4] |= mc_enc(56, 8, obj_type[15]); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++EXPORT_SYMBOL(dprc_set_obj_label); ++ ++int dprc_connect(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ const struct dprc_endpoint *endpoint1, ++ const struct dprc_endpoint *endpoint2, ++ const struct dprc_connection_cfg *cfg) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPRC_CMDID_CONNECT, ++ cmd_flags, ++ token); ++ cmd.params[0] |= mc_enc(0, 32, endpoint1->id); ++ cmd.params[0] |= mc_enc(32, 32, endpoint1->if_id); ++ cmd.params[1] |= mc_enc(0, 32, endpoint2->id); ++ cmd.params[1] |= mc_enc(32, 32, endpoint2->if_id); ++ cmd.params[2] |= mc_enc(0, 8, endpoint1->type[0]); ++ cmd.params[2] |= mc_enc(8, 8, endpoint1->type[1]); ++ cmd.params[2] |= mc_enc(16, 8, endpoint1->type[2]); ++ cmd.params[2] |= mc_enc(24, 8, endpoint1->type[3]); ++ cmd.params[2] |= mc_enc(32, 8, endpoint1->type[4]); ++ cmd.params[2] |= mc_enc(40, 8, endpoint1->type[5]); ++ cmd.params[2] |= mc_enc(48, 8, endpoint1->type[6]); ++ cmd.params[2] |= mc_enc(56, 8, endpoint1->type[7]); ++ cmd.params[3] |= mc_enc(0, 8, endpoint1->type[8]); ++ cmd.params[3] |= mc_enc(8, 8, endpoint1->type[9]); ++ cmd.params[3] |= mc_enc(16, 8, endpoint1->type[10]); ++ cmd.params[3] |= mc_enc(24, 8, endpoint1->type[11]); ++ cmd.params[3] |= mc_enc(32, 8, endpoint1->type[12]); ++ cmd.params[3] |= mc_enc(40, 8, endpoint1->type[13]); ++ cmd.params[3] |= mc_enc(48, 8, endpoint1->type[14]); ++ cmd.params[3] |= mc_enc(56, 8, endpoint1->type[15]); ++ cmd.params[4] |= mc_enc(0, 32, cfg->max_rate); ++ cmd.params[4] |= mc_enc(32, 32, cfg->committed_rate); ++ cmd.params[5] |= mc_enc(0, 8, endpoint2->type[0]); ++ cmd.params[5] |= mc_enc(8, 8, endpoint2->type[1]); ++ cmd.params[5] |= mc_enc(16, 8, endpoint2->type[2]); ++ cmd.params[5] |= mc_enc(24, 8, endpoint2->type[3]); ++ cmd.params[5] |= mc_enc(32, 8, endpoint2->type[4]); ++ cmd.params[5] |= mc_enc(40, 8, endpoint2->type[5]); ++ cmd.params[5] |= mc_enc(48, 8, endpoint2->type[6]); ++ cmd.params[5] |= mc_enc(56, 8, endpoint2->type[7]); ++ cmd.params[6] |= mc_enc(0, 8, endpoint2->type[8]); ++ cmd.params[6] |= mc_enc(8, 8, endpoint2->type[9]); ++ cmd.params[6] |= mc_enc(16, 8, endpoint2->type[10]); ++ cmd.params[6] |= mc_enc(24, 8, endpoint2->type[11]); ++ cmd.params[6] |= mc_enc(32, 8, endpoint2->type[12]); ++ cmd.params[6] |= mc_enc(40, 8, endpoint2->type[13]); ++ cmd.params[6] |= mc_enc(48, 8, endpoint2->type[14]); ++ cmd.params[6] |= mc_enc(56, 8, endpoint2->type[15]); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++EXPORT_SYMBOL(dprc_connect); ++ ++int dprc_disconnect(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ const struct dprc_endpoint *endpoint) ++{ ++ struct mc_command cmd = { 0 }; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPRC_CMDID_DISCONNECT, ++ cmd_flags, ++ token); ++ cmd.params[0] |= mc_enc(0, 32, endpoint->id); ++ cmd.params[0] |= mc_enc(32, 32, endpoint->if_id); ++ cmd.params[1] |= mc_enc(0, 8, endpoint->type[0]); ++ cmd.params[1] |= mc_enc(8, 8, endpoint->type[1]); ++ cmd.params[1] |= mc_enc(16, 8, endpoint->type[2]); ++ cmd.params[1] |= mc_enc(24, 8, endpoint->type[3]); ++ cmd.params[1] |= mc_enc(32, 8, endpoint->type[4]); ++ cmd.params[1] |= mc_enc(40, 8, endpoint->type[5]); ++ cmd.params[1] |= mc_enc(48, 8, endpoint->type[6]); ++ cmd.params[1] |= mc_enc(56, 8, endpoint->type[7]); ++ cmd.params[2] |= mc_enc(0, 8, endpoint->type[8]); ++ cmd.params[2] |= mc_enc(8, 8, endpoint->type[9]); ++ cmd.params[2] |= mc_enc(16, 8, endpoint->type[10]); ++ cmd.params[2] |= mc_enc(24, 8, endpoint->type[11]); ++ cmd.params[2] |= mc_enc(32, 8, endpoint->type[12]); ++ cmd.params[2] |= mc_enc(40, 8, endpoint->type[13]); ++ cmd.params[2] |= mc_enc(48, 8, endpoint->type[14]); ++ cmd.params[2] |= mc_enc(56, 8, endpoint->type[15]); ++ ++ /* send command to mc*/ ++ return mc_send_command(mc_io, &cmd); ++} ++EXPORT_SYMBOL(dprc_disconnect); ++ ++int dprc_get_connection(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ const struct dprc_endpoint *endpoint1, ++ struct dprc_endpoint *endpoint2, ++ int *state) ++{ ++ struct mc_command cmd = { 0 }; ++ int err; ++ ++ /* prepare command */ ++ cmd.header = mc_encode_cmd_header(DPRC_CMDID_GET_CONNECTION, ++ cmd_flags, ++ token); ++ cmd.params[0] |= mc_enc(0, 32, endpoint1->id); ++ cmd.params[0] |= mc_enc(32, 32, endpoint1->if_id); ++ cmd.params[1] |= mc_enc(0, 8, endpoint1->type[0]); ++ cmd.params[1] |= mc_enc(8, 8, endpoint1->type[1]); ++ cmd.params[1] |= mc_enc(16, 8, endpoint1->type[2]); ++ cmd.params[1] |= mc_enc(24, 8, endpoint1->type[3]); ++ cmd.params[1] |= mc_enc(32, 8, endpoint1->type[4]); ++ cmd.params[1] |= mc_enc(40, 8, endpoint1->type[5]); ++ cmd.params[1] |= mc_enc(48, 8, endpoint1->type[6]); ++ cmd.params[1] |= mc_enc(56, 8, endpoint1->type[7]); ++ cmd.params[2] |= mc_enc(0, 8, endpoint1->type[8]); ++ cmd.params[2] |= mc_enc(8, 8, endpoint1->type[9]); ++ cmd.params[2] |= mc_enc(16, 8, endpoint1->type[10]); ++ cmd.params[2] |= mc_enc(24, 8, endpoint1->type[11]); ++ cmd.params[2] |= mc_enc(32, 8, endpoint1->type[12]); ++ cmd.params[2] |= mc_enc(40, 8, endpoint1->type[13]); ++ cmd.params[2] |= mc_enc(48, 8, endpoint1->type[14]); ++ cmd.params[2] |= mc_enc(56, 8, endpoint1->type[15]); ++ ++ /* send command to mc*/ ++ err = mc_send_command(mc_io, &cmd); ++ if (err) ++ return err; ++ ++ /* retrieve response parameters */ ++ endpoint2->id = mc_dec(cmd.params[3], 0, 32); ++ endpoint2->if_id = mc_dec(cmd.params[3], 32, 32); ++ endpoint2->type[0] = mc_dec(cmd.params[4], 0, 8); ++ endpoint2->type[1] = mc_dec(cmd.params[4], 8, 8); ++ endpoint2->type[2] = mc_dec(cmd.params[4], 16, 8); ++ endpoint2->type[3] = mc_dec(cmd.params[4], 24, 8); ++ endpoint2->type[4] = mc_dec(cmd.params[4], 32, 8); ++ endpoint2->type[5] = mc_dec(cmd.params[4], 40, 8); ++ endpoint2->type[6] = mc_dec(cmd.params[4], 48, 8); ++ endpoint2->type[7] = mc_dec(cmd.params[4], 56, 8); ++ endpoint2->type[8] = mc_dec(cmd.params[5], 0, 8); ++ endpoint2->type[9] = mc_dec(cmd.params[5], 8, 8); ++ endpoint2->type[10] = mc_dec(cmd.params[5], 16, 8); ++ endpoint2->type[11] = mc_dec(cmd.params[5], 24, 8); ++ endpoint2->type[12] = mc_dec(cmd.params[5], 32, 8); ++ endpoint2->type[13] = mc_dec(cmd.params[5], 40, 8); ++ endpoint2->type[14] = mc_dec(cmd.params[5], 48, 8); ++ endpoint2->type[15] = mc_dec(cmd.params[5], 56, 8); ++ *state = mc_dec(cmd.params[6], 0, 32); ++ ++ return 0; ++} ++EXPORT_SYMBOL(dprc_get_connection); +diff --git a/drivers/staging/fsl-mc/bus/mc-allocator.c b/drivers/staging/fsl-mc/bus/mc-allocator.c +new file mode 100644 +index 0000000..a3940a0 +--- /dev/null ++++ b/drivers/staging/fsl-mc/bus/mc-allocator.c +@@ -0,0 +1,716 @@ ++/* ++ * Freescale MC object device allocator driver ++ * ++ * Copyright (C) 2013 Freescale Semiconductor, Inc. ++ * ++ * This file is licensed under the terms of the GNU General Public ++ * License version 2. This program is licensed "as is" without any ++ * warranty of any kind, whether express or implied. ++ */ ++ ++#include "../include/mc-private.h" ++#include "../include/mc-sys.h" ++#include ++#include "../include/dpbp-cmd.h" ++#include "../include/dpcon-cmd.h" ++#include "dpmcp-cmd.h" ++#include "dpmcp.h" ++ ++/** ++ * fsl_mc_resource_pool_add_device - add allocatable device to a resource ++ * pool of a given MC bus ++ * ++ * @mc_bus: pointer to the MC bus ++ * @pool_type: MC bus pool type ++ * @mc_dev: Pointer to allocatable MC object device ++ * ++ * It adds an allocatable MC object device to a container's resource pool of ++ * the given resource type ++ */ ++static int __must_check fsl_mc_resource_pool_add_device(struct fsl_mc_bus ++ *mc_bus, ++ enum fsl_mc_pool_type ++ pool_type, ++ struct fsl_mc_device ++ *mc_dev) ++{ ++ struct fsl_mc_resource_pool *res_pool; ++ struct fsl_mc_resource *resource; ++ struct fsl_mc_device *mc_bus_dev = &mc_bus->mc_dev; ++ int error = -EINVAL; ++ bool mutex_locked = false; ++ ++ if (WARN_ON(pool_type < 0 || pool_type >= FSL_MC_NUM_POOL_TYPES)) ++ goto out; ++ if (WARN_ON(!FSL_MC_IS_ALLOCATABLE(mc_dev->obj_desc.type))) ++ goto out; ++ if (WARN_ON(mc_dev->resource)) ++ goto out; ++ ++ res_pool = &mc_bus->resource_pools[pool_type]; ++ if (WARN_ON(res_pool->type != pool_type)) ++ goto out; ++ if (WARN_ON(res_pool->mc_bus != mc_bus)) ++ goto out; ++ ++ mutex_lock(&res_pool->mutex); ++ mutex_locked = true; ++ ++ if (WARN_ON(res_pool->max_count < 0)) ++ goto out; ++ if (WARN_ON(res_pool->free_count < 0 || ++ res_pool->free_count > res_pool->max_count)) ++ goto out; ++ ++ resource = devm_kzalloc(&mc_bus_dev->dev, sizeof(*resource), ++ GFP_KERNEL); ++ if (!resource) { ++ error = -ENOMEM; ++ dev_err(&mc_bus_dev->dev, ++ "Failed to allocate memory for fsl_mc_resource\n"); ++ goto out; ++ } ++ ++ resource->type = pool_type; ++ resource->id = mc_dev->obj_desc.id; ++ resource->data = mc_dev; ++ resource->parent_pool = res_pool; ++ INIT_LIST_HEAD(&resource->node); ++ list_add_tail(&resource->node, &res_pool->free_list); ++ mc_dev->resource = resource; ++ res_pool->free_count++; ++ res_pool->max_count++; ++ error = 0; ++out: ++ if (mutex_locked) ++ mutex_unlock(&res_pool->mutex); ++ ++ return error; ++} ++ ++/** ++ * fsl_mc_resource_pool_remove_device - remove an allocatable device from a ++ * resource pool ++ * ++ * @mc_dev: Pointer to allocatable MC object device ++ * ++ * It permanently removes an allocatable MC object device from the resource ++ * pool, the device is currently in, as long as it is in the pool's free list. ++ */ ++static int __must_check fsl_mc_resource_pool_remove_device(struct fsl_mc_device ++ *mc_dev) ++{ ++ struct fsl_mc_device *mc_bus_dev; ++ struct fsl_mc_bus *mc_bus; ++ struct fsl_mc_resource_pool *res_pool; ++ struct fsl_mc_resource *resource; ++ int error = -EINVAL; ++ bool mutex_locked = false; ++ ++ if (WARN_ON(!FSL_MC_IS_ALLOCATABLE(mc_dev->obj_desc.type))) ++ goto out; ++ ++ resource = mc_dev->resource; ++ if (WARN_ON(!resource || resource->data != mc_dev)) ++ goto out; ++ ++ mc_bus_dev = to_fsl_mc_device(mc_dev->dev.parent); ++ mc_bus = to_fsl_mc_bus(mc_bus_dev); ++ res_pool = resource->parent_pool; ++ if (WARN_ON(res_pool != &mc_bus->resource_pools[resource->type])) ++ goto out; ++ ++ mutex_lock(&res_pool->mutex); ++ mutex_locked = true; ++ ++ if (WARN_ON(res_pool->max_count <= 0)) ++ goto out; ++ if (WARN_ON(res_pool->free_count <= 0 || ++ res_pool->free_count > res_pool->max_count)) ++ goto out; ++ ++ /* ++ * If the device is currently allocated, its resource is not ++ * in the free list and thus, the device cannot be removed. ++ */ ++ if (list_empty(&resource->node)) { ++ error = -EBUSY; ++ dev_err(&mc_bus_dev->dev, ++ "Device %s cannot be removed from resource pool\n", ++ dev_name(&mc_dev->dev)); ++ goto out; ++ } ++ ++ list_del(&resource->node); ++ INIT_LIST_HEAD(&resource->node); ++ res_pool->free_count--; ++ res_pool->max_count--; ++ ++ devm_kfree(&mc_bus_dev->dev, resource); ++ mc_dev->resource = NULL; ++ error = 0; ++out: ++ if (mutex_locked) ++ mutex_unlock(&res_pool->mutex); ++ ++ return error; ++} ++ ++static const char *const fsl_mc_pool_type_strings[] = { ++ [FSL_MC_POOL_DPMCP] = "dpmcp", ++ [FSL_MC_POOL_DPBP] = "dpbp", ++ [FSL_MC_POOL_DPCON] = "dpcon", ++ [FSL_MC_POOL_IRQ] = "irq", ++}; ++ ++static int __must_check object_type_to_pool_type(const char *object_type, ++ enum fsl_mc_pool_type ++ *pool_type) ++{ ++ unsigned int i; ++ ++ for (i = 0; i < ARRAY_SIZE(fsl_mc_pool_type_strings); i++) { ++ if (strcmp(object_type, fsl_mc_pool_type_strings[i]) == 0) { ++ *pool_type = i; ++ return 0; ++ } ++ } ++ ++ return -EINVAL; ++} ++ ++int __must_check fsl_mc_resource_allocate(struct fsl_mc_bus *mc_bus, ++ enum fsl_mc_pool_type pool_type, ++ struct fsl_mc_resource **new_resource) ++{ ++ struct fsl_mc_resource_pool *res_pool; ++ struct fsl_mc_resource *resource; ++ struct fsl_mc_device *mc_bus_dev = &mc_bus->mc_dev; ++ int error = -EINVAL; ++ bool mutex_locked = false; ++ ++ BUILD_BUG_ON(ARRAY_SIZE(fsl_mc_pool_type_strings) != ++ FSL_MC_NUM_POOL_TYPES); ++ ++ *new_resource = NULL; ++ if (WARN_ON(pool_type < 0 || pool_type >= FSL_MC_NUM_POOL_TYPES)) ++ goto error; ++ ++ res_pool = &mc_bus->resource_pools[pool_type]; ++ if (WARN_ON(res_pool->mc_bus != mc_bus)) ++ goto error; ++ ++ mutex_lock(&res_pool->mutex); ++ mutex_locked = true; ++ resource = list_first_entry_or_null(&res_pool->free_list, ++ struct fsl_mc_resource, node); ++ ++ if (!resource) { ++ WARN_ON(res_pool->free_count != 0); ++ error = -ENXIO; ++ dev_err(&mc_bus_dev->dev, ++ "No more resources of type %s left\n", ++ fsl_mc_pool_type_strings[pool_type]); ++ goto error; ++ } ++ ++ if (WARN_ON(resource->type != pool_type)) ++ goto error; ++ if (WARN_ON(resource->parent_pool != res_pool)) ++ goto error; ++ if (WARN_ON(res_pool->free_count <= 0 || ++ res_pool->free_count > res_pool->max_count)) ++ goto error; ++ ++ list_del(&resource->node); ++ INIT_LIST_HEAD(&resource->node); ++ ++ res_pool->free_count--; ++ mutex_unlock(&res_pool->mutex); ++ *new_resource = resource; ++ return 0; ++error: ++ if (mutex_locked) ++ mutex_unlock(&res_pool->mutex); ++ ++ return error; ++} ++EXPORT_SYMBOL_GPL(fsl_mc_resource_allocate); ++ ++void fsl_mc_resource_free(struct fsl_mc_resource *resource) ++{ ++ struct fsl_mc_resource_pool *res_pool; ++ bool mutex_locked = false; ++ ++ res_pool = resource->parent_pool; ++ if (WARN_ON(resource->type != res_pool->type)) ++ goto out; ++ ++ mutex_lock(&res_pool->mutex); ++ mutex_locked = true; ++ if (WARN_ON(res_pool->free_count < 0 || ++ res_pool->free_count >= res_pool->max_count)) ++ goto out; ++ ++ if (WARN_ON(!list_empty(&resource->node))) ++ goto out; ++ ++ list_add_tail(&resource->node, &res_pool->free_list); ++ res_pool->free_count++; ++out: ++ if (mutex_locked) ++ mutex_unlock(&res_pool->mutex); ++} ++EXPORT_SYMBOL_GPL(fsl_mc_resource_free); ++ ++/** ++ * fsl_mc_portal_allocate - Allocates an MC portal ++ * ++ * @mc_dev: MC device for which the MC portal is to be allocated ++ * @mc_io_flags: Flags for the fsl_mc_io object that wraps the allocated ++ * MC portal. ++ * @new_mc_io: Pointer to area where the pointer to the fsl_mc_io object ++ * that wraps the allocated MC portal is to be returned ++ * ++ * This function allocates an MC portal from the device's parent DPRC, ++ * from the corresponding MC bus' pool of MC portals and wraps ++ * it in a new fsl_mc_io object. If 'mc_dev' is a DPRC itself, the ++ * portal is allocated from its own MC bus. ++ */ ++int __must_check fsl_mc_portal_allocate(struct fsl_mc_device *mc_dev, ++ uint16_t mc_io_flags, ++ struct fsl_mc_io **new_mc_io) ++{ ++ struct fsl_mc_device *mc_bus_dev; ++ struct fsl_mc_bus *mc_bus; ++ phys_addr_t mc_portal_phys_addr; ++ size_t mc_portal_size; ++ struct fsl_mc_device *dpmcp_dev; ++ int error = -EINVAL; ++ struct fsl_mc_resource *resource = NULL; ++ struct fsl_mc_io *mc_io = NULL; ++ ++ if (!mc_dev) { ++ if (WARN_ON(!fsl_mc_bus_type.dev_root)) ++ return error; ++ ++ mc_bus_dev = to_fsl_mc_device(fsl_mc_bus_type.dev_root); ++ } else if (mc_dev->flags & FSL_MC_IS_DPRC) { ++ mc_bus_dev = mc_dev; ++ } else { ++ if (WARN_ON(mc_dev->dev.parent->bus != &fsl_mc_bus_type)) ++ return error; ++ ++ mc_bus_dev = to_fsl_mc_device(mc_dev->dev.parent); ++ } ++ ++ mc_bus = to_fsl_mc_bus(mc_bus_dev); ++ *new_mc_io = NULL; ++ error = fsl_mc_resource_allocate(mc_bus, FSL_MC_POOL_DPMCP, &resource); ++ if (error < 0) ++ return error; ++ ++ error = -EINVAL; ++ dpmcp_dev = resource->data; ++ if (WARN_ON(!dpmcp_dev || ++ strcmp(dpmcp_dev->obj_desc.type, "dpmcp") != 0)) ++ goto error_cleanup_resource; ++ ++ if (dpmcp_dev->obj_desc.ver_major < DPMCP_MIN_VER_MAJOR || ++ (dpmcp_dev->obj_desc.ver_major == DPMCP_MIN_VER_MAJOR && ++ dpmcp_dev->obj_desc.ver_minor < DPMCP_MIN_VER_MINOR)) { ++ dev_err(&dpmcp_dev->dev, ++ "ERROR: Version %d.%d of DPMCP not supported.\n", ++ dpmcp_dev->obj_desc.ver_major, ++ dpmcp_dev->obj_desc.ver_minor); ++ error = -ENOTSUPP; ++ goto error_cleanup_resource; ++ } ++ ++ if (WARN_ON(dpmcp_dev->obj_desc.region_count == 0)) ++ goto error_cleanup_resource; ++ ++ mc_portal_phys_addr = dpmcp_dev->regions[0].start; ++ mc_portal_size = dpmcp_dev->regions[0].end - ++ dpmcp_dev->regions[0].start + 1; ++ ++ if (WARN_ON(mc_portal_size != mc_bus_dev->mc_io->portal_size)) ++ goto error_cleanup_resource; ++ ++ error = fsl_create_mc_io(&mc_bus_dev->dev, ++ mc_portal_phys_addr, ++ mc_portal_size, dpmcp_dev, ++ mc_io_flags, &mc_io); ++ if (error < 0) ++ goto error_cleanup_resource; ++ ++ *new_mc_io = mc_io; ++ return 0; ++ ++error_cleanup_resource: ++ fsl_mc_resource_free(resource); ++ return error; ++} ++EXPORT_SYMBOL_GPL(fsl_mc_portal_allocate); ++ ++/** ++ * fsl_mc_portal_free - Returns an MC portal to the pool of free MC portals ++ * of a given MC bus ++ * ++ * @mc_io: Pointer to the fsl_mc_io object that wraps the MC portal to free ++ */ ++void fsl_mc_portal_free(struct fsl_mc_io *mc_io) ++{ ++ struct fsl_mc_device *dpmcp_dev; ++ struct fsl_mc_resource *resource; ++ ++ /* ++ * Every mc_io obtained by calling fsl_mc_portal_allocate() is supposed ++ * to have a DPMCP object associated with. ++ */ ++ dpmcp_dev = mc_io->dpmcp_dev; ++ if (WARN_ON(!dpmcp_dev)) ++ return; ++ if (WARN_ON(strcmp(dpmcp_dev->obj_desc.type, "dpmcp") != 0)) ++ return; ++ if (WARN_ON(dpmcp_dev->mc_io != mc_io)) ++ return; ++ ++ resource = dpmcp_dev->resource; ++ if (WARN_ON(!resource || resource->type != FSL_MC_POOL_DPMCP)) ++ return; ++ ++ if (WARN_ON(resource->data != dpmcp_dev)) ++ return; ++ ++ fsl_destroy_mc_io(mc_io); ++ fsl_mc_resource_free(resource); ++} ++EXPORT_SYMBOL_GPL(fsl_mc_portal_free); ++ ++/** ++ * fsl_mc_portal_reset - Resets the dpmcp object for a given fsl_mc_io object ++ * ++ * @mc_io: Pointer to the fsl_mc_io object that wraps the MC portal to free ++ */ ++int fsl_mc_portal_reset(struct fsl_mc_io *mc_io) ++{ ++ int error; ++ struct fsl_mc_device *dpmcp_dev = mc_io->dpmcp_dev; ++ ++ if (WARN_ON(!dpmcp_dev)) ++ return -EINVAL; ++ ++ error = dpmcp_reset(mc_io, 0, dpmcp_dev->mc_handle); ++ if (error < 0) { ++ dev_err(&dpmcp_dev->dev, "dpmcp_reset() failed: %d\n", error); ++ return error; ++ } ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(fsl_mc_portal_reset); ++ ++/** ++ * fsl_mc_object_allocate - Allocates a MC object device of the given ++ * pool type from a given MC bus ++ * ++ * @mc_dev: MC device for which the MC object device is to be allocated ++ * @pool_type: MC bus resource pool type ++ * @new_mc_dev: Pointer to area where the pointer to the allocated ++ * MC object device is to be returned ++ * ++ * This function allocates a MC object device from the device's parent DPRC, ++ * from the corresponding MC bus' pool of allocatable MC object devices of ++ * the given resource type. mc_dev cannot be a DPRC itself. ++ * ++ * NOTE: pool_type must be different from FSL_MC_POOL_MCP, since MC ++ * portals are allocated using fsl_mc_portal_allocate(), instead of ++ * this function. ++ */ ++int __must_check fsl_mc_object_allocate(struct fsl_mc_device *mc_dev, ++ enum fsl_mc_pool_type pool_type, ++ struct fsl_mc_device **new_mc_adev) ++{ ++ struct fsl_mc_device *mc_bus_dev; ++ struct fsl_mc_bus *mc_bus; ++ struct fsl_mc_device *mc_adev; ++ int error = -EINVAL; ++ struct fsl_mc_resource *resource = NULL; ++ ++ *new_mc_adev = NULL; ++ if (WARN_ON(mc_dev->flags & FSL_MC_IS_DPRC)) ++ goto error; ++ ++ if (WARN_ON(mc_dev->dev.parent->bus != &fsl_mc_bus_type)) ++ goto error; ++ ++ if (WARN_ON(pool_type == FSL_MC_POOL_DPMCP)) ++ goto error; ++ ++ mc_bus_dev = to_fsl_mc_device(mc_dev->dev.parent); ++ mc_bus = to_fsl_mc_bus(mc_bus_dev); ++ error = fsl_mc_resource_allocate(mc_bus, pool_type, &resource); ++ if (error < 0) ++ goto error; ++ ++ mc_adev = resource->data; ++ if (WARN_ON(!mc_adev)) ++ goto error; ++ ++ *new_mc_adev = mc_adev; ++ return 0; ++error: ++ if (resource) ++ fsl_mc_resource_free(resource); ++ ++ return error; ++} ++EXPORT_SYMBOL_GPL(fsl_mc_object_allocate); ++ ++/** ++ * fsl_mc_object_free - Returns an allocatable MC object device to the ++ * corresponding resource pool of a given MC bus. ++ * ++ * @mc_adev: Pointer to the MC object device ++ */ ++void fsl_mc_object_free(struct fsl_mc_device *mc_adev) ++{ ++ struct fsl_mc_resource *resource; ++ ++ resource = mc_adev->resource; ++ if (WARN_ON(resource->type == FSL_MC_POOL_DPMCP)) ++ return; ++ if (WARN_ON(resource->data != mc_adev)) ++ return; ++ ++ fsl_mc_resource_free(resource); ++} ++EXPORT_SYMBOL_GPL(fsl_mc_object_free); ++ ++/** ++ * It allocates the IRQs required by a given MC object device. The ++ * IRQs are allocated from the interrupt pool associated with the ++ * MC bus that contains the device, if the device is not a DPRC device. ++ * Otherwise, the IRQs are allocated from the interrupt pool associated ++ * with the MC bus that represents the DPRC device itself. ++ */ ++int __must_check fsl_mc_allocate_irqs(struct fsl_mc_device *mc_dev) ++{ ++ int i; ++ int irq_count; ++ int res_allocated_count = 0; ++ int error = -EINVAL; ++ struct fsl_mc_device_irq **irqs = NULL; ++ struct fsl_mc_bus *mc_bus; ++ struct fsl_mc_resource_pool *res_pool; ++ struct fsl_mc *mc = dev_get_drvdata(fsl_mc_bus_type.dev_root->parent); ++ ++ if (!mc->gic_supported) ++ return -ENOTSUPP; ++ ++ if (WARN_ON(mc_dev->irqs)) ++ goto error; ++ ++ irq_count = mc_dev->obj_desc.irq_count; ++ if (WARN_ON(irq_count == 0)) ++ goto error; ++ ++ if (strcmp(mc_dev->obj_desc.type, "dprc") == 0) ++ mc_bus = to_fsl_mc_bus(mc_dev); ++ else ++ mc_bus = to_fsl_mc_bus(to_fsl_mc_device(mc_dev->dev.parent)); ++ ++ if (WARN_ON(!mc_bus->irq_resources)) ++ goto error; ++ ++ res_pool = &mc_bus->resource_pools[FSL_MC_POOL_IRQ]; ++ if (res_pool->free_count < irq_count) { ++ dev_err(&mc_dev->dev, ++ "Not able to allocate %u irqs for device\n", irq_count); ++ error = -ENOSPC; ++ goto error; ++ } ++ ++ irqs = devm_kzalloc(&mc_dev->dev, irq_count * sizeof(irqs[0]), ++ GFP_KERNEL); ++ if (!irqs) { ++ error = -ENOMEM; ++ dev_err(&mc_dev->dev, "No memory to allocate irqs[]\n"); ++ goto error; ++ } ++ ++ for (i = 0; i < irq_count; i++) { ++ struct fsl_mc_resource *resource; ++ ++ error = fsl_mc_resource_allocate(mc_bus, FSL_MC_POOL_IRQ, ++ &resource); ++ if (error < 0) ++ goto error; ++ ++ irqs[i] = to_fsl_mc_irq(resource); ++ res_allocated_count++; ++ ++ WARN_ON(irqs[i]->mc_dev); ++ irqs[i]->mc_dev = mc_dev; ++ irqs[i]->dev_irq_index = i; ++ } ++ ++ mc_dev->irqs = irqs; ++ return 0; ++error: ++ for (i = 0; i < res_allocated_count; i++) { ++ irqs[i]->mc_dev = NULL; ++ fsl_mc_resource_free(&irqs[i]->resource); ++ } ++ ++ if (irqs) ++ devm_kfree(&mc_dev->dev, irqs); ++ ++ return error; ++} ++EXPORT_SYMBOL_GPL(fsl_mc_allocate_irqs); ++ ++/* ++ * It frees the IRQs that were allocated for a MC object device, by ++ * returning them to the corresponding interrupt pool. ++ */ ++void fsl_mc_free_irqs(struct fsl_mc_device *mc_dev) ++{ ++ int i; ++ int irq_count; ++ struct fsl_mc_bus *mc_bus; ++ struct fsl_mc_device_irq **irqs = mc_dev->irqs; ++ ++ if (WARN_ON(!irqs)) ++ return; ++ ++ irq_count = mc_dev->obj_desc.irq_count; ++ ++ if (strcmp(mc_dev->obj_desc.type, "dprc") == 0) ++ mc_bus = to_fsl_mc_bus(mc_dev); ++ else ++ mc_bus = to_fsl_mc_bus(to_fsl_mc_device(mc_dev->dev.parent)); ++ ++ if (WARN_ON(!mc_bus->irq_resources)) ++ return; ++ ++ for (i = 0; i < irq_count; i++) { ++ WARN_ON(!irqs[i]->mc_dev); ++ irqs[i]->mc_dev = NULL; ++ fsl_mc_resource_free(&irqs[i]->resource); ++ } ++ ++ devm_kfree(&mc_dev->dev, mc_dev->irqs); ++ mc_dev->irqs = NULL; ++} ++EXPORT_SYMBOL_GPL(fsl_mc_free_irqs); ++ ++/** ++ * fsl_mc_allocator_probe - callback invoked when an allocatable device is ++ * being added to the system ++ */ ++static int fsl_mc_allocator_probe(struct fsl_mc_device *mc_dev) ++{ ++ enum fsl_mc_pool_type pool_type; ++ struct fsl_mc_device *mc_bus_dev; ++ struct fsl_mc_bus *mc_bus; ++ int error = -EINVAL; ++ ++ if (WARN_ON(!FSL_MC_IS_ALLOCATABLE(mc_dev->obj_desc.type))) ++ goto error; ++ ++ mc_bus_dev = to_fsl_mc_device(mc_dev->dev.parent); ++ if (WARN_ON(mc_bus_dev->dev.bus != &fsl_mc_bus_type)) ++ goto error; ++ ++ mc_bus = to_fsl_mc_bus(mc_bus_dev); ++ ++ /* ++ * If mc_dev is the DPMCP object for the parent DPRC's built-in ++ * portal, we don't add this DPMCP to the DPMCP object pool, ++ * but instead allocate it directly to the parent DPRC (mc_bus_dev): ++ */ ++ if (strcmp(mc_dev->obj_desc.type, "dpmcp") == 0 && ++ mc_dev->obj_desc.id == mc_bus->dprc_attr.portal_id) { ++ error = fsl_mc_io_set_dpmcp(mc_bus_dev->mc_io, mc_dev); ++ if (error < 0) ++ goto error; ++ } else { ++ error = object_type_to_pool_type(mc_dev->obj_desc.type, ++ &pool_type); ++ if (error < 0) ++ goto error; ++ ++ error = fsl_mc_resource_pool_add_device(mc_bus, pool_type, ++ mc_dev); ++ if (error < 0) ++ goto error; ++ } ++ ++ dev_dbg(&mc_dev->dev, ++ "Allocatable MC object device bound to fsl_mc_allocator driver"); ++ return 0; ++error: ++ ++ return error; ++} ++ ++/** ++ * fsl_mc_allocator_remove - callback invoked when an allocatable device is ++ * being removed from the system ++ */ ++static int fsl_mc_allocator_remove(struct fsl_mc_device *mc_dev) ++{ ++ int error; ++ ++ if (WARN_ON(!FSL_MC_IS_ALLOCATABLE(mc_dev->obj_desc.type))) ++ return -EINVAL; ++ ++ if (mc_dev->resource) { ++ error = fsl_mc_resource_pool_remove_device(mc_dev); ++ if (error < 0) ++ return error; ++ } ++ ++ dev_dbg(&mc_dev->dev, ++ "Allocatable MC object device unbound from fsl_mc_allocator driver"); ++ return 0; ++} ++ ++static const struct fsl_mc_device_match_id match_id_table[] = { ++ { ++ .vendor = FSL_MC_VENDOR_FREESCALE, ++ .obj_type = "dpbp", ++ }, ++ { ++ .vendor = FSL_MC_VENDOR_FREESCALE, ++ .obj_type = "dpmcp", ++ }, ++ { ++ .vendor = FSL_MC_VENDOR_FREESCALE, ++ .obj_type = "dpcon", ++ }, ++ {.vendor = 0x0}, ++}; ++ ++static struct fsl_mc_driver fsl_mc_allocator_driver = { ++ .driver = { ++ .name = "fsl_mc_allocator", ++ .owner = THIS_MODULE, ++ .pm = NULL, ++ }, ++ .match_id_table = match_id_table, ++ .probe = fsl_mc_allocator_probe, ++ .remove = fsl_mc_allocator_remove, ++}; ++ ++int __init fsl_mc_allocator_driver_init(void) ++{ ++ return fsl_mc_driver_register(&fsl_mc_allocator_driver); ++} ++ ++void __exit fsl_mc_allocator_driver_exit(void) ++{ ++ fsl_mc_driver_unregister(&fsl_mc_allocator_driver); ++} +diff --git a/drivers/staging/fsl-mc/bus/mc-bus.c b/drivers/staging/fsl-mc/bus/mc-bus.c +new file mode 100644 +index 0000000..f173b35 +--- /dev/null ++++ b/drivers/staging/fsl-mc/bus/mc-bus.c +@@ -0,0 +1,1347 @@ ++/* ++ * Freescale Management Complex (MC) bus driver ++ * ++ * Copyright (C) 2014 Freescale Semiconductor, Inc. ++ * Author: German Rivera ++ * ++ * This file is licensed under the terms of the GNU General Public ++ * License version 2. This program is licensed "as is" without any ++ * warranty of any kind, whether express or implied. ++ */ ++ ++#include "../include/mc-private.h" ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "../include/dpmng.h" ++#include "../include/mc-sys.h" ++#include "dprc-cmd.h" ++ ++/* ++ * IOMMU stream ID flags ++ */ ++#define STREAM_ID_PL_MASK BIT(9) /* privilege level */ ++#define STREAM_ID_BMT_MASK BIT(8) /* bypass memory translation */ ++#define STREAM_ID_VA_MASK BIT(7) /* virtual address translation ++ * (two-stage translation) */ ++#define STREAM_ID_ICID_MASK (BIT(7) - 1) /* isolation context ID ++ * (translation context) */ ++ ++#define MAX_STREAM_ID_ICID STREAM_ID_ICID_MASK ++ ++static struct kmem_cache *mc_dev_cache; ++ ++/** ++ * fsl_mc_bus_match - device to driver matching callback ++ * @dev: the MC object device structure to match against ++ * @drv: the device driver to search for matching MC object device id ++ * structures ++ * ++ * Returns 1 on success, 0 otherwise. ++ */ ++static int fsl_mc_bus_match(struct device *dev, struct device_driver *drv) ++{ ++ const struct fsl_mc_device_match_id *id; ++ struct fsl_mc_device *mc_dev = to_fsl_mc_device(dev); ++ struct fsl_mc_driver *mc_drv = to_fsl_mc_driver(drv); ++ bool found = false; ++ ++ /* When driver_override is set, only bind to the matching driver */ ++ if (mc_dev->driver_override) { ++ found = !strcmp(mc_dev->driver_override, mc_drv->driver.name); ++ goto out; ++ } ++ ++ if (!mc_drv->match_id_table) ++ goto out; ++ ++ /* ++ * If the object is not 'plugged' don't match. ++ * Only exception is the root DPRC, which is a special case. ++ * ++ * NOTE: Only when this function is invoked for the root DPRC, ++ * mc_dev->mc_io is not NULL ++ */ ++ if ((mc_dev->obj_desc.state & DPRC_OBJ_STATE_PLUGGED) == 0 && ++ !mc_dev->mc_io) ++ goto out; ++ ++ /* ++ * Traverse the match_id table of the given driver, trying to find ++ * a matching for the given MC object device. ++ */ ++ for (id = mc_drv->match_id_table; id->vendor != 0x0; id++) { ++ if (id->vendor == mc_dev->obj_desc.vendor && ++ strcmp(id->obj_type, mc_dev->obj_desc.type) == 0) { ++ found = true; ++ ++ break; ++ } ++ } ++ ++out: ++ dev_dbg(dev, "%smatched\n", found ? "" : "not "); ++ return found; ++} ++ ++/** ++ * fsl_mc_bus_uevent - callback invoked when a device is added ++ */ ++static int fsl_mc_bus_uevent(struct device *dev, struct kobj_uevent_env *env) ++{ ++ pr_debug("%s invoked\n", __func__); ++ return 0; ++} ++ ++static ssize_t driver_override_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct fsl_mc_device *mc_dev = to_fsl_mc_device(dev); ++ const char *driver_override, *old = mc_dev->driver_override; ++ char *cp; ++ ++ if (WARN_ON(dev->bus != &fsl_mc_bus_type)) ++ return -EINVAL; ++ ++ if (count > PATH_MAX) ++ return -EINVAL; ++ ++ driver_override = kstrndup(buf, count, GFP_KERNEL); ++ if (!driver_override) ++ return -ENOMEM; ++ ++ cp = strchr(driver_override, '\n'); ++ if (cp) ++ *cp = '\0'; ++ ++ if (strlen(driver_override)) { ++ mc_dev->driver_override = driver_override; ++ } else { ++ kfree(driver_override); ++ mc_dev->driver_override = NULL; ++ } ++ ++ kfree(old); ++ ++ return count; ++} ++ ++static ssize_t driver_override_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct fsl_mc_device *mc_dev = to_fsl_mc_device(dev); ++ ++ return sprintf(buf, "%s\n", mc_dev->driver_override); ++} ++ ++static DEVICE_ATTR_RW(driver_override); ++ ++static ssize_t rescan_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ unsigned long val; ++ unsigned int irq_count; ++ struct fsl_mc_device *root_mc_dev; ++ struct fsl_mc_bus *root_mc_bus; ++ ++ if (!is_root_dprc(dev)) ++ return -EINVAL; ++ ++ root_mc_dev = to_fsl_mc_device(dev); ++ root_mc_bus = to_fsl_mc_bus(root_mc_dev); ++ ++ if (kstrtoul(buf, 0, &val) < 0) ++ return -EINVAL; ++ ++ if (val) { ++ mutex_lock(&root_mc_bus->scan_mutex); ++ dprc_scan_objects(root_mc_dev, NULL, &irq_count); ++ mutex_unlock(&root_mc_bus->scan_mutex); ++ } ++ ++ return count; ++} ++ ++static DEVICE_ATTR_WO(rescan); ++ ++static struct attribute *fsl_mc_dev_attrs[] = { ++ &dev_attr_driver_override.attr, ++ &dev_attr_rescan.attr, ++ NULL, ++}; ++ ++static const struct attribute_group fsl_mc_dev_group = { ++ .attrs = fsl_mc_dev_attrs, ++}; ++ ++static const struct attribute_group *fsl_mc_dev_groups[] = { ++ &fsl_mc_dev_group, ++ NULL, ++}; ++ ++static int scan_fsl_mc_bus(struct device *dev, void *data) ++{ ++ unsigned int irq_count; ++ struct fsl_mc_device *root_mc_dev; ++ struct fsl_mc_bus *root_mc_bus; ++ ++ if (is_root_dprc(dev)) { ++ root_mc_dev = to_fsl_mc_device(dev); ++ root_mc_bus = to_fsl_mc_bus(root_mc_dev); ++ mutex_lock(&root_mc_bus->scan_mutex); ++ dprc_scan_objects(root_mc_dev, NULL, &irq_count); ++ mutex_unlock(&root_mc_bus->scan_mutex); ++ } ++ ++ return 0; ++} ++ ++static ssize_t bus_rescan_store(struct bus_type *bus, ++ const char *buf, size_t count) ++{ ++ unsigned long val; ++ ++ if (kstrtoul(buf, 0, &val) < 0) ++ return -EINVAL; ++ ++ if (val) ++ bus_for_each_dev(bus, NULL, NULL, scan_fsl_mc_bus); ++ ++ return count; ++} ++static BUS_ATTR(rescan, (S_IWUSR|S_IWGRP), NULL, bus_rescan_store); ++ ++static struct attribute *fsl_mc_bus_attrs[] = { ++ &bus_attr_rescan.attr, ++ NULL, ++}; ++ ++static const struct attribute_group fsl_mc_bus_group = { ++ .attrs = fsl_mc_bus_attrs, ++}; ++ ++static const struct attribute_group *fsl_mc_bus_groups[] = { ++ &fsl_mc_bus_group, ++ NULL, ++}; ++ ++struct bus_type fsl_mc_bus_type = { ++ .name = "fsl-mc", ++ .match = fsl_mc_bus_match, ++ .uevent = fsl_mc_bus_uevent, ++ .dev_groups = fsl_mc_dev_groups, ++ .bus_groups = fsl_mc_bus_groups, ++}; ++EXPORT_SYMBOL_GPL(fsl_mc_bus_type); ++ ++static int fsl_mc_driver_probe(struct device *dev) ++{ ++ struct fsl_mc_driver *mc_drv; ++ struct fsl_mc_device *mc_dev = to_fsl_mc_device(dev); ++ int error; ++ ++ if (WARN_ON(!dev->driver)) ++ return -EINVAL; ++ ++ mc_drv = to_fsl_mc_driver(dev->driver); ++ if (WARN_ON(!mc_drv->probe)) ++ return -EINVAL; ++ ++ error = mc_drv->probe(mc_dev); ++ if (error < 0) { ++ dev_err(dev, "MC object device probe callback failed: %d\n", ++ error); ++ return error; ++ } ++ ++ return 0; ++} ++ ++static int fsl_mc_driver_remove(struct device *dev) ++{ ++ struct fsl_mc_driver *mc_drv = to_fsl_mc_driver(dev->driver); ++ struct fsl_mc_device *mc_dev = to_fsl_mc_device(dev); ++ int error; ++ ++ if (WARN_ON(!dev->driver)) ++ return -EINVAL; ++ ++ error = mc_drv->remove(mc_dev); ++ if (error < 0) { ++ dev_err(dev, ++ "MC object device remove callback failed: %d\n", ++ error); ++ return error; ++ } ++ ++ return 0; ++} ++ ++static void fsl_mc_driver_shutdown(struct device *dev) ++{ ++ struct fsl_mc_driver *mc_drv = to_fsl_mc_driver(dev->driver); ++ struct fsl_mc_device *mc_dev = to_fsl_mc_device(dev); ++ ++ mc_drv->shutdown(mc_dev); ++} ++ ++/** ++ * __fsl_mc_driver_register - registers a child device driver with the ++ * MC bus ++ * ++ * This function is implicitly invoked from the registration function of ++ * fsl_mc device drivers, which is generated by the ++ * module_fsl_mc_driver() macro. ++ */ ++int __fsl_mc_driver_register(struct fsl_mc_driver *mc_driver, ++ struct module *owner) ++{ ++ int error; ++ ++ mc_driver->driver.owner = owner; ++ mc_driver->driver.bus = &fsl_mc_bus_type; ++ ++ if (mc_driver->probe) ++ mc_driver->driver.probe = fsl_mc_driver_probe; ++ ++ if (mc_driver->remove) ++ mc_driver->driver.remove = fsl_mc_driver_remove; ++ ++ if (mc_driver->shutdown) ++ mc_driver->driver.shutdown = fsl_mc_driver_shutdown; ++ ++ error = driver_register(&mc_driver->driver); ++ if (error < 0) { ++ pr_err("driver_register() failed for %s: %d\n", ++ mc_driver->driver.name, error); ++ return error; ++ } ++ ++ pr_info("MC object device driver %s registered\n", ++ mc_driver->driver.name); ++ return 0; ++} ++EXPORT_SYMBOL_GPL(__fsl_mc_driver_register); ++ ++/** ++ * fsl_mc_driver_unregister - unregisters a device driver from the ++ * MC bus ++ */ ++void fsl_mc_driver_unregister(struct fsl_mc_driver *mc_driver) ++{ ++ driver_unregister(&mc_driver->driver); ++} ++EXPORT_SYMBOL_GPL(fsl_mc_driver_unregister); ++ ++bool fsl_mc_interrupts_supported(void) ++{ ++ struct fsl_mc *mc = dev_get_drvdata(fsl_mc_bus_type.dev_root->parent); ++ ++ return mc->gic_supported; ++} ++EXPORT_SYMBOL_GPL(fsl_mc_interrupts_supported); ++ ++static int get_dprc_attr(struct fsl_mc_io *mc_io, ++ int container_id, struct dprc_attributes *attr) ++{ ++ uint16_t dprc_handle; ++ int error; ++ ++ error = dprc_open(mc_io, 0, container_id, &dprc_handle); ++ if (error < 0) { ++ pr_err("dprc_open() failed: %d\n", error); ++ return error; ++ } ++ ++ memset(attr, 0, sizeof(struct dprc_attributes)); ++ error = dprc_get_attributes(mc_io, 0, dprc_handle, attr); ++ if (error < 0) { ++ pr_err("dprc_get_attributes() failed: %d\n", error); ++ goto common_cleanup; ++ } ++ ++ error = 0; ++ ++common_cleanup: ++ (void)dprc_close(mc_io, 0, dprc_handle); ++ return error; ++} ++ ++static int get_dprc_icid(struct fsl_mc_io *mc_io, ++ int container_id, uint16_t *icid) ++{ ++ struct dprc_attributes attr; ++ int error; ++ ++ error = get_dprc_attr(mc_io, container_id, &attr); ++ if (error == 0) ++ *icid = attr.icid; ++ ++ return error; ++} ++ ++static int get_dprc_version(struct fsl_mc_io *mc_io, ++ int container_id, uint16_t *major, uint16_t *minor) ++{ ++ struct dprc_attributes attr; ++ int error; ++ ++ error = get_dprc_attr(mc_io, container_id, &attr); ++ if (error == 0) { ++ *major = attr.version.major; ++ *minor = attr.version.minor; ++ } ++ ++ return error; ++} ++ ++static int translate_mc_addr(enum fsl_mc_region_types mc_region_type, ++ uint64_t mc_offset, phys_addr_t *phys_addr) ++{ ++ int i; ++ struct fsl_mc *mc = dev_get_drvdata(fsl_mc_bus_type.dev_root->parent); ++ ++ if (mc->num_translation_ranges == 0) { ++ /* ++ * Do identity mapping: ++ */ ++ *phys_addr = mc_offset; ++ return 0; ++ } ++ ++ for (i = 0; i < mc->num_translation_ranges; i++) { ++ struct fsl_mc_addr_translation_range *range = ++ &mc->translation_ranges[i]; ++ ++ if (mc_region_type == range->mc_region_type && ++ mc_offset >= range->start_mc_offset && ++ mc_offset < range->end_mc_offset) { ++ *phys_addr = range->start_phys_addr + ++ (mc_offset - range->start_mc_offset); ++ return 0; ++ } ++ } ++ ++ return -EFAULT; ++} ++ ++static int fsl_mc_device_get_mmio_regions(struct fsl_mc_device *mc_dev, ++ struct fsl_mc_device *mc_bus_dev) ++{ ++ int i; ++ int error; ++ struct resource *regions; ++ struct dprc_obj_desc *obj_desc = &mc_dev->obj_desc; ++ struct device *parent_dev = mc_dev->dev.parent; ++ enum fsl_mc_region_types mc_region_type; ++ ++ if (strcmp(obj_desc->type, "dprc") == 0 || ++ strcmp(obj_desc->type, "dpmcp") == 0) { ++ mc_region_type = FSL_MC_PORTAL; ++ } else if (strcmp(obj_desc->type, "dpio") == 0) { ++ mc_region_type = FSL_QBMAN_PORTAL; ++ } else { ++ /* ++ * This function should not have been called for this MC object ++ * type, as this object type is not supposed to have MMIO ++ * regions ++ */ ++ WARN_ON(true); ++ return -EINVAL; ++ } ++ ++ regions = kmalloc_array(obj_desc->region_count, ++ sizeof(regions[0]), GFP_KERNEL); ++ if (!regions) ++ return -ENOMEM; ++ ++ for (i = 0; i < obj_desc->region_count; i++) { ++ struct dprc_region_desc region_desc; ++ ++ error = dprc_get_obj_region(mc_bus_dev->mc_io, ++ 0, ++ mc_bus_dev->mc_handle, ++ obj_desc->type, ++ obj_desc->id, i, ®ion_desc); ++ if (error < 0) { ++ dev_err(parent_dev, ++ "dprc_get_obj_region() failed: %d\n", error); ++ goto error_cleanup_regions; ++ } ++ ++ WARN_ON(region_desc.size == 0); ++ error = translate_mc_addr(mc_region_type, ++ region_desc.base_offset, ++ ®ions[i].start); ++ if (error < 0) { ++ dev_err(parent_dev, ++ "Invalid MC offset: %#x (for %s.%d\'s region %d)\n", ++ region_desc.base_offset, ++ obj_desc->type, obj_desc->id, i); ++ goto error_cleanup_regions; ++ } ++ ++ regions[i].end = regions[i].start + region_desc.size - 1; ++ regions[i].name = "fsl-mc object MMIO region"; ++ regions[i].flags = IORESOURCE_IO; ++ if (region_desc.flags & DPRC_REGION_CACHEABLE) ++ regions[i].flags |= IORESOURCE_CACHEABLE; ++ } ++ ++ mc_dev->regions = regions; ++ return 0; ++ ++error_cleanup_regions: ++ kfree(regions); ++ return error; ++} ++ ++/** ++ * Add a newly discovered MC object device to be visible in Linux ++ */ ++int fsl_mc_device_add(struct dprc_obj_desc *obj_desc, ++ struct fsl_mc_io *mc_io, ++ struct device *parent_dev, ++ const char *driver_override, ++ struct fsl_mc_device **new_mc_dev) ++{ ++ int error; ++ struct fsl_mc_device *mc_dev = NULL; ++ struct fsl_mc_bus *mc_bus = NULL; ++ struct fsl_mc_device *parent_mc_dev; ++ ++ if (parent_dev->bus == &fsl_mc_bus_type) ++ parent_mc_dev = to_fsl_mc_device(parent_dev); ++ else ++ parent_mc_dev = NULL; ++ ++ if (strcmp(obj_desc->type, "dprc") == 0) { ++ /* ++ * Allocate an MC bus device object: ++ */ ++ mc_bus = devm_kzalloc(parent_dev, sizeof(*mc_bus), GFP_KERNEL); ++ if (!mc_bus) ++ return -ENOMEM; ++ ++ mc_dev = &mc_bus->mc_dev; ++ } else { ++ /* ++ * Allocate a regular fsl_mc_device object: ++ */ ++ mc_dev = kmem_cache_zalloc(mc_dev_cache, GFP_KERNEL); ++ if (!mc_dev) ++ return -ENOMEM; ++ } ++ ++ mc_dev->obj_desc = *obj_desc; ++ mc_dev->mc_io = mc_io; ++ if (driver_override) { ++ /* ++ * We trust driver_override, so we don't need to use ++ * kstrndup() here ++ */ ++ mc_dev->driver_override = kstrdup(driver_override, GFP_KERNEL); ++ if (!mc_dev->driver_override) { ++ error = -ENOMEM; ++ goto error_cleanup_dev; ++ } ++ } ++ ++ device_initialize(&mc_dev->dev); ++ INIT_LIST_HEAD(&mc_dev->dev.msi_list); ++ mc_dev->dev.parent = parent_dev; ++ mc_dev->dev.bus = &fsl_mc_bus_type; ++ dev_set_name(&mc_dev->dev, "%s.%d", obj_desc->type, obj_desc->id); ++ ++ if (strcmp(obj_desc->type, "dprc") == 0) { ++ struct fsl_mc_io *mc_io2; ++ ++ mc_dev->flags |= FSL_MC_IS_DPRC; ++ ++ /* ++ * To get the DPRC's ICID, we need to open the DPRC ++ * in get_dprc_icid(). For child DPRCs, we do so using the ++ * parent DPRC's MC portal instead of the child DPRC's MC ++ * portal, in case the child DPRC is already opened with ++ * its own portal (e.g., the DPRC used by AIOP). ++ * ++ * NOTE: There cannot be more than one active open for a ++ * given MC object, using the same MC portal. ++ */ ++ if (parent_mc_dev) { ++ /* ++ * device being added is a child DPRC device ++ */ ++ mc_io2 = parent_mc_dev->mc_io; ++ } else { ++ /* ++ * device being added is the root DPRC device ++ */ ++ if (WARN_ON(!mc_io)) { ++ error = -EINVAL; ++ goto error_cleanup_dev; ++ } ++ ++ mc_io2 = mc_io; ++ } ++ ++ error = get_dprc_icid(mc_io2, obj_desc->id, &mc_dev->icid); ++ if (error < 0) ++ goto error_cleanup_dev; ++ } else { ++ /* ++ * A non-DPRC MC object device has to be a child of another ++ * MC object (specifically a DPRC object) ++ */ ++ mc_dev->icid = parent_mc_dev->icid; ++ mc_dev->dma_mask = FSL_MC_DEFAULT_DMA_MASK; ++ mc_dev->dev.dma_mask = &mc_dev->dma_mask; ++ } ++ ++ /* ++ * Get MMIO regions for the device from the MC: ++ * ++ * NOTE: the root DPRC is a special case as its MMIO region is ++ * obtained from the device tree ++ */ ++ if (parent_mc_dev && obj_desc->region_count != 0) { ++ error = fsl_mc_device_get_mmio_regions(mc_dev, ++ parent_mc_dev); ++ if (error < 0) ++ goto error_cleanup_dev; ++ } ++ ++ /* ++ * Objects are coherent, unless 'no shareability' flag set. ++ * FIXME: fill up @dma_base, @size, @iommu ++ */ ++ if (!(obj_desc->flags & DPRC_OBJ_FLAG_NO_MEM_SHAREABILITY)) ++ arch_setup_dma_ops(&mc_dev->dev, 0, 0, NULL, true); ++ ++ /* ++ * The device-specific probe callback will get invoked by device_add() ++ */ ++ error = device_add(&mc_dev->dev); ++ if (error < 0) { ++ dev_err(parent_dev, ++ "device_add() failed for device %s: %d\n", ++ dev_name(&mc_dev->dev), error); ++ goto error_cleanup_dev; ++ } ++ ++ (void)get_device(&mc_dev->dev); ++ dev_dbg(parent_dev, "Added MC object device %s\n", ++ dev_name(&mc_dev->dev)); ++ ++ *new_mc_dev = mc_dev; ++ return 0; ++ ++error_cleanup_dev: ++ kfree(mc_dev->regions); ++ if (mc_bus) ++ devm_kfree(parent_dev, mc_bus); ++ else ++ kmem_cache_free(mc_dev_cache, mc_dev); ++ ++ return error; ++} ++EXPORT_SYMBOL_GPL(fsl_mc_device_add); ++ ++/** ++ * fsl_mc_device_remove - Remove a MC object device from being visible to ++ * Linux ++ * ++ * @mc_dev: Pointer to a MC object device object ++ */ ++void fsl_mc_device_remove(struct fsl_mc_device *mc_dev) ++{ ++ struct fsl_mc_bus *mc_bus = NULL; ++ ++ kfree(mc_dev->regions); ++ ++ /* ++ * The device-specific remove callback will get invoked by device_del() ++ */ ++ device_del(&mc_dev->dev); ++ put_device(&mc_dev->dev); ++ ++ if (strcmp(mc_dev->obj_desc.type, "dprc") == 0) { ++ mc_bus = to_fsl_mc_bus(mc_dev); ++ ++ if (&mc_dev->dev == fsl_mc_bus_type.dev_root) ++ fsl_mc_bus_type.dev_root = NULL; ++ } else ++ WARN_ON(mc_dev->mc_io != NULL); ++ ++ kfree(mc_dev->driver_override); ++ mc_dev->driver_override = NULL; ++ if (mc_bus) ++ devm_kfree(mc_dev->dev.parent, mc_bus); ++ else ++ kmem_cache_free(mc_dev_cache, mc_dev); ++} ++EXPORT_SYMBOL_GPL(fsl_mc_device_remove); ++ ++static int mc_bus_msi_prepare(struct irq_domain *domain, struct device *dev, ++ int nvec, msi_alloc_info_t *info) ++{ ++ int error; ++ u32 its_dev_id; ++ struct dprc_attributes dprc_attr; ++ struct fsl_mc_device *mc_bus_dev = to_fsl_mc_device(dev); ++ ++ if (WARN_ON(!(mc_bus_dev->flags & FSL_MC_IS_DPRC))) ++ return -EINVAL; ++ ++ error = dprc_get_attributes(mc_bus_dev->mc_io, ++ 0, ++ mc_bus_dev->mc_handle, &dprc_attr); ++ if (error < 0) { ++ dev_err(&mc_bus_dev->dev, ++ "dprc_get_attributes() failed: %d\n", ++ error); ++ return error; ++ } ++ ++ /* ++ * Build the device Id to be passed to the GIC-ITS: ++ * ++ * NOTE: This device id corresponds to the IOMMU stream ID ++ * associated with the DPRC object. ++ */ ++ its_dev_id = mc_bus_dev->icid; ++ if (its_dev_id > STREAM_ID_ICID_MASK) { ++ dev_err(&mc_bus_dev->dev, ++ "Invalid ICID: %#x\n", its_dev_id); ++ return -ERANGE; ++ } ++ ++ if (dprc_attr.options & DPRC_CFG_OPT_AIOP) ++ its_dev_id |= STREAM_ID_PL_MASK | STREAM_ID_BMT_MASK; ++ ++ return __its_msi_prepare(domain, its_dev_id, dev, nvec, info); ++} ++ ++static void mc_bus_mask_msi_irq(struct irq_data *d) ++{ ++ /* Bus specefic Mask */ ++ irq_chip_mask_parent(d); ++} ++ ++static void mc_bus_unmask_msi_irq(struct irq_data *d) ++{ ++ /* Bus specefic unmask */ ++ irq_chip_unmask_parent(d); ++} ++ ++static void program_msi_at_mc(struct fsl_mc_device *mc_bus_dev, ++ struct fsl_mc_device_irq *irq) ++{ ++ int error; ++ struct fsl_mc_device *owner_mc_dev = irq->mc_dev; ++ struct fsl_mc_bus *mc_bus = to_fsl_mc_bus(mc_bus_dev); ++ struct dprc_irq_cfg irq_cfg; ++ ++ /* ++ * irq->msi_paddr is 0x0 when this function is invoked in the ++ * free_irq() code path. In this case, for the MC, we don't ++ * really need to "unprogram" the MSI, so we just return. ++ * This helps avoid subtle ordering problems in the MC ++ * bus IRQ teardown logic. ++ * FIXME: evaluate whether there is a better way to address ++ * the underlying issue (upstreamability concern) ++ */ ++ if (irq->msi_paddr == 0x0) ++ return; ++ ++ if (WARN_ON(!owner_mc_dev)) ++ return; ++ ++ irq_cfg.paddr = irq->msi_paddr; ++ irq_cfg.val = irq->msi_value; ++ irq_cfg.irq_num = irq->irq_number; ++ ++ if (owner_mc_dev == mc_bus_dev) { ++ /* ++ * IRQ is for the mc_bus_dev's DPRC itself ++ */ ++ error = dprc_set_irq(mc_bus->atomic_mc_io, ++ MC_CMD_FLAG_INTR_DIS | MC_CMD_FLAG_PRI, ++ mc_bus->atomic_dprc_handle, ++ irq->dev_irq_index, ++ &irq_cfg); ++ if (error < 0) { ++ dev_err(&owner_mc_dev->dev, ++ "dprc_set_irq() failed: %d\n", error); ++ } ++ } else { ++ error = dprc_set_obj_irq(mc_bus->atomic_mc_io, ++ MC_CMD_FLAG_INTR_DIS | MC_CMD_FLAG_PRI, ++ mc_bus->atomic_dprc_handle, ++ owner_mc_dev->obj_desc.type, ++ owner_mc_dev->obj_desc.id, ++ irq->dev_irq_index, ++ &irq_cfg); ++ if (error < 0) { ++ dev_err(&owner_mc_dev->dev, ++ "dprc_obj_set_irq() failed: %d\n", error); ++ } ++ } ++} ++ ++/* ++ * This function is invoked from devm_request_irq(), ++ * devm_request_threaded_irq(), dev_free_irq() ++ */ ++static void mc_bus_msi_domain_write_msg(struct irq_data *irq_data, ++ struct msi_msg *msg) ++{ ++ struct msi_desc *msi_entry = irq_data->msi_desc; ++ struct fsl_mc_device *mc_bus_dev = to_fsl_mc_device(msi_entry->dev); ++ struct fsl_mc_bus *mc_bus = to_fsl_mc_bus(mc_bus_dev); ++ struct fsl_mc_device_irq *irq_res = ++ &mc_bus->irq_resources[msi_entry->msi_attrib.entry_nr]; ++ ++ /* ++ * NOTE: This function is invoked with interrupts disabled ++ */ ++ ++ if (irq_res->irq_number == irq_data->irq) { ++ irq_res->msi_paddr = ++ ((u64)msg->address_hi << 32) | msg->address_lo; ++ ++ irq_res->msi_value = msg->data; ++ ++ /* ++ * Program the MSI (paddr, value) pair in the device: ++ */ ++ program_msi_at_mc(mc_bus_dev, irq_res); ++ } ++} ++ ++static struct irq_chip mc_bus_msi_irq_chip = { ++ .name = "fsl-mc-bus-msi", ++ .irq_unmask = mc_bus_unmask_msi_irq, ++ .irq_mask = mc_bus_mask_msi_irq, ++ .irq_eoi = irq_chip_eoi_parent, ++ .irq_write_msi_msg = mc_bus_msi_domain_write_msg, ++}; ++ ++static struct msi_domain_ops mc_bus_msi_ops = { ++ .msi_prepare = mc_bus_msi_prepare, ++}; ++ ++static struct msi_domain_info mc_bus_msi_domain_info = { ++ .flags = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS | ++ MSI_FLAG_MULTI_PCI_MSI | MSI_FLAG_PCI_MSIX), ++ .ops = &mc_bus_msi_ops, ++ .chip = &mc_bus_msi_irq_chip, ++}; ++ ++static int create_mc_irq_domain(struct platform_device *mc_pdev, ++ struct irq_domain **new_irq_domain) ++{ ++ int error; ++ struct device_node *its_of_node; ++ struct irq_domain *its_domain; ++ struct irq_domain *irq_domain; ++ struct device_node *mc_of_node = mc_pdev->dev.of_node; ++ ++ its_of_node = of_parse_phandle(mc_of_node, "msi-parent", 0); ++ if (!its_of_node) { ++ dev_err(&mc_pdev->dev, ++ "msi-parent phandle missing for %s\n", ++ mc_of_node->full_name); ++ return -ENOENT; ++ } ++ ++ /* ++ * Extract MSI parent node: ++ */ ++ its_domain = irq_find_host(its_of_node); ++ if (!its_domain) { ++ dev_err(&mc_pdev->dev, "Unable to find parent domain\n"); ++ error = -ENOENT; ++ goto cleanup_its_of_node; ++ } ++ ++ irq_domain = msi_create_irq_domain(mc_of_node, &mc_bus_msi_domain_info, ++ its_domain->parent); ++ if (!irq_domain) { ++ dev_err(&mc_pdev->dev, "Failed to allocate msi_domain\n"); ++ error = -ENOMEM; ++ goto cleanup_its_of_node; ++ } ++ ++ dev_dbg(&mc_pdev->dev, "Allocated MSI domain\n"); ++ *new_irq_domain = irq_domain; ++ return 0; ++ ++cleanup_its_of_node: ++ of_node_put(its_of_node); ++ return error; ++} ++ ++/* ++ * Initialize the interrupt pool associated with a MC bus. ++ * It allocates a block of IRQs from the GIC-ITS ++ */ ++int __must_check fsl_mc_populate_irq_pool(struct fsl_mc_bus *mc_bus, ++ unsigned int irq_count) ++{ ++ unsigned int i; ++ struct msi_desc *msi_entry; ++ struct msi_desc *next_msi_entry; ++ struct fsl_mc_device_irq *irq_resources; ++ struct fsl_mc_device_irq *irq_res; ++ int error; ++ struct fsl_mc_device *mc_bus_dev = &mc_bus->mc_dev; ++ struct fsl_mc *mc = dev_get_drvdata(fsl_mc_bus_type.dev_root->parent); ++ struct fsl_mc_resource_pool *res_pool = ++ &mc_bus->resource_pools[FSL_MC_POOL_IRQ]; ++ ++ /* ++ * Detect duplicate invocations of this function: ++ */ ++ if (WARN_ON(!list_empty(&mc_bus_dev->dev.msi_list))) ++ return -EINVAL; ++ ++ if (WARN_ON(irq_count == 0 || ++ irq_count > FSL_MC_IRQ_POOL_MAX_TOTAL_IRQS)) ++ return -EINVAL; ++ ++ irq_resources = ++ devm_kzalloc(&mc_bus_dev->dev, ++ sizeof(*irq_resources) * irq_count, ++ GFP_KERNEL); ++ if (!irq_resources) ++ return -ENOMEM; ++ ++ for (i = 0; i < irq_count; i++) { ++ irq_res = &irq_resources[i]; ++ msi_entry = alloc_msi_entry(&mc_bus_dev->dev); ++ if (!msi_entry) { ++ dev_err(&mc_bus_dev->dev, "Failed to allocate msi entry\n"); ++ error = -ENOMEM; ++ goto cleanup_msi_entries; ++ } ++ ++ msi_entry->msi_attrib.is_msix = 1; ++ msi_entry->msi_attrib.is_64 = 1; ++ msi_entry->msi_attrib.entry_nr = i; ++ msi_entry->nvec_used = 1; ++ list_add_tail(&msi_entry->list, &mc_bus_dev->dev.msi_list); ++ ++ /* ++ * NOTE: irq_res->msi_paddr will be set by the ++ * mc_bus_msi_domain_write_msg() callback ++ */ ++ irq_res->resource.type = res_pool->type; ++ irq_res->resource.data = irq_res; ++ irq_res->resource.parent_pool = res_pool; ++ INIT_LIST_HEAD(&irq_res->resource.node); ++ list_add_tail(&irq_res->resource.node, &res_pool->free_list); ++ } ++ ++ /* ++ * NOTE: Calling this function will trigger the invocation of the ++ * mc_bus_msi_prepare() callback ++ */ ++ error = msi_domain_alloc_irqs(mc->irq_domain, ++ &mc_bus_dev->dev, irq_count); ++ ++ if (error) { ++ dev_err(&mc_bus_dev->dev, "Failed to allocate IRQs\n"); ++ goto cleanup_msi_entries; ++ } ++ ++ for_each_msi_entry(msi_entry, &mc_bus_dev->dev) { ++ u32 irq_num = msi_entry->irq; ++ ++ irq_res = &irq_resources[msi_entry->msi_attrib.entry_nr]; ++ irq_res->irq_number = irq_num; ++ irq_res->resource.id = irq_num; ++ } ++ ++ res_pool->max_count = irq_count; ++ res_pool->free_count = irq_count; ++ mc_bus->irq_resources = irq_resources; ++ return 0; ++ ++cleanup_msi_entries: ++ list_for_each_entry_safe(msi_entry, next_msi_entry, ++ &mc_bus_dev->dev.msi_list, list) { ++ list_del(&msi_entry->list); ++ kfree(msi_entry); ++ } ++ ++ devm_kfree(&mc_bus_dev->dev, irq_resources); ++ return error; ++} ++EXPORT_SYMBOL_GPL(fsl_mc_populate_irq_pool); ++ ++/** ++ * Teardown the interrupt pool associated with an MC bus. ++ * It frees the IRQs that were allocated to the pool, back to the GIC-ITS. ++ */ ++void fsl_mc_cleanup_irq_pool(struct fsl_mc_bus *mc_bus) ++{ ++ struct msi_desc *msi_entry; ++ struct msi_desc *next_msi_entry; ++ struct fsl_mc *mc = dev_get_drvdata(fsl_mc_bus_type.dev_root->parent); ++ struct fsl_mc_resource_pool *res_pool = ++ &mc_bus->resource_pools[FSL_MC_POOL_IRQ]; ++ ++ if (WARN_ON(!mc_bus->irq_resources)) ++ return; ++ ++ if (WARN_ON(res_pool->max_count == 0)) ++ return; ++ ++ if (WARN_ON(res_pool->free_count != res_pool->max_count)) ++ return; ++ ++ msi_domain_free_irqs(mc->irq_domain, &mc_bus->mc_dev.dev); ++ list_for_each_entry_safe(msi_entry, next_msi_entry, ++ &mc_bus->mc_dev.dev.msi_list, list) { ++ list_del(&msi_entry->list); ++ kfree(msi_entry); ++ } ++ ++ devm_kfree(&mc_bus->mc_dev.dev, mc_bus->irq_resources); ++ res_pool->max_count = 0; ++ res_pool->free_count = 0; ++ mc_bus->irq_resources = NULL; ++} ++EXPORT_SYMBOL_GPL(fsl_mc_cleanup_irq_pool); ++ ++static int parse_mc_ranges(struct device *dev, ++ int *paddr_cells, ++ int *mc_addr_cells, ++ int *mc_size_cells, ++ const __be32 **ranges_start, ++ uint8_t *num_ranges) ++{ ++ const __be32 *prop; ++ int range_tuple_cell_count; ++ int ranges_len; ++ int tuple_len; ++ struct device_node *mc_node = dev->of_node; ++ ++ *ranges_start = of_get_property(mc_node, "ranges", &ranges_len); ++ if (!(*ranges_start) || !ranges_len) { ++ dev_warn(dev, ++ "missing or empty ranges property for device tree node '%s'\n", ++ mc_node->name); ++ ++ *num_ranges = 0; ++ return 0; ++ } ++ ++ *paddr_cells = of_n_addr_cells(mc_node); ++ ++ prop = of_get_property(mc_node, "#address-cells", NULL); ++ if (prop) ++ *mc_addr_cells = be32_to_cpup(prop); ++ else ++ *mc_addr_cells = *paddr_cells; ++ ++ prop = of_get_property(mc_node, "#size-cells", NULL); ++ if (prop) ++ *mc_size_cells = be32_to_cpup(prop); ++ else ++ *mc_size_cells = of_n_size_cells(mc_node); ++ ++ range_tuple_cell_count = *paddr_cells + *mc_addr_cells + ++ *mc_size_cells; ++ ++ tuple_len = range_tuple_cell_count * sizeof(__be32); ++ if (ranges_len % tuple_len != 0) { ++ dev_err(dev, "malformed ranges property '%s'\n", mc_node->name); ++ return -EINVAL; ++ } ++ ++ *num_ranges = ranges_len / tuple_len; ++ return 0; ++} ++ ++static int get_mc_addr_translation_ranges(struct device *dev, ++ struct fsl_mc_addr_translation_range ++ **ranges, ++ uint8_t *num_ranges) ++{ ++ int error; ++ int paddr_cells; ++ int mc_addr_cells; ++ int mc_size_cells; ++ int i; ++ const __be32 *ranges_start; ++ const __be32 *cell; ++ ++ error = parse_mc_ranges(dev, ++ &paddr_cells, ++ &mc_addr_cells, ++ &mc_size_cells, ++ &ranges_start, ++ num_ranges); ++ if (error < 0) ++ return error; ++ ++ if (!(*num_ranges)) { ++ /* ++ * Missing or empty ranges property ("ranges;") for the ++ * 'fsl,qoriq-mc' node. In this case, identity mapping ++ * will be used. ++ */ ++ *ranges = NULL; ++ return 0; ++ } ++ ++ *ranges = devm_kcalloc(dev, *num_ranges, ++ sizeof(struct fsl_mc_addr_translation_range), ++ GFP_KERNEL); ++ if (!(*ranges)) ++ return -ENOMEM; ++ ++ cell = ranges_start; ++ for (i = 0; i < *num_ranges; ++i) { ++ struct fsl_mc_addr_translation_range *range = &(*ranges)[i]; ++ ++ range->mc_region_type = of_read_number(cell, 1); ++ range->start_mc_offset = of_read_number(cell + 1, ++ mc_addr_cells - 1); ++ cell += mc_addr_cells; ++ range->start_phys_addr = of_read_number(cell, paddr_cells); ++ cell += paddr_cells; ++ range->end_mc_offset = range->start_mc_offset + ++ of_read_number(cell, mc_size_cells); ++ ++ cell += mc_size_cells; ++ } ++ ++ return 0; ++} ++ ++/** ++ * fsl_mc_bus_probe - callback invoked when the root MC bus is being ++ * added ++ */ ++static int fsl_mc_bus_probe(struct platform_device *pdev) ++{ ++ struct dprc_obj_desc obj_desc; ++ int error; ++ struct fsl_mc *mc; ++ struct fsl_mc_device *mc_bus_dev = NULL; ++ struct fsl_mc_io *mc_io = NULL; ++ int container_id; ++ phys_addr_t mc_portal_phys_addr; ++ uint32_t mc_portal_size; ++ struct mc_version mc_version; ++ struct resource res; ++ ++ dev_info(&pdev->dev, "Root MC bus device probed"); ++ ++ mc = devm_kzalloc(&pdev->dev, sizeof(*mc), GFP_KERNEL); ++ if (!mc) ++ return -ENOMEM; ++ ++ platform_set_drvdata(pdev, mc); ++ error = create_mc_irq_domain(pdev, &mc->irq_domain); ++ if (error < 0) { ++ dev_warn(&pdev->dev, ++ "WARNING: MC bus driver will run without interrupt support\n"); ++ } else { ++ mc->gic_supported = true; ++ } ++ ++ /* ++ * Get physical address of MC portal for the root DPRC: ++ */ ++ error = of_address_to_resource(pdev->dev.of_node, 0, &res); ++ if (error < 0) { ++ dev_err(&pdev->dev, ++ "of_address_to_resource() failed for %s\n", ++ pdev->dev.of_node->full_name); ++ goto error_cleanup_irq_domain; ++ } ++ ++ mc_portal_phys_addr = res.start; ++ mc_portal_size = resource_size(&res); ++ error = fsl_create_mc_io(&pdev->dev, mc_portal_phys_addr, ++ mc_portal_size, NULL, 0, &mc_io); ++ if (error < 0) ++ goto error_cleanup_irq_domain; ++ ++ error = mc_get_version(mc_io, 0, &mc_version); ++ if (error != 0) { ++ dev_err(&pdev->dev, ++ "mc_get_version() failed with error %d\n", error); ++ goto error_cleanup_mc_io; ++ } ++ ++ dev_info(&pdev->dev, ++ "Freescale Management Complex Firmware version: %u.%u.%u\n", ++ mc_version.major, mc_version.minor, mc_version.revision); ++ ++ error = get_mc_addr_translation_ranges(&pdev->dev, ++ &mc->translation_ranges, ++ &mc->num_translation_ranges); ++ if (error < 0) ++ goto error_cleanup_mc_io; ++ ++ error = dpmng_get_container_id(mc_io, 0, &container_id); ++ if (error < 0) { ++ dev_err(&pdev->dev, ++ "dpmng_get_container_id() failed: %d\n", error); ++ goto error_cleanup_mc_io; ++ } ++ ++ memset(&obj_desc, 0, sizeof(struct dprc_obj_desc)); ++ error = get_dprc_version(mc_io, container_id, ++ &obj_desc.ver_major, &obj_desc.ver_minor); ++ if (error < 0) ++ goto error_cleanup_mc_io; ++ ++ obj_desc.vendor = FSL_MC_VENDOR_FREESCALE; ++ strcpy(obj_desc.type, "dprc"); ++ obj_desc.id = container_id; ++ obj_desc.irq_count = 1; ++ obj_desc.region_count = 0; ++ ++ error = fsl_mc_device_add(&obj_desc, mc_io, &pdev->dev, NULL, ++ &mc_bus_dev); ++ if (error < 0) ++ goto error_cleanup_mc_io; ++ ++ mc->root_mc_bus_dev = mc_bus_dev; ++ return 0; ++ ++error_cleanup_mc_io: ++ fsl_destroy_mc_io(mc_io); ++ ++error_cleanup_irq_domain: ++ if (mc->gic_supported) ++ irq_domain_remove(mc->irq_domain); ++ ++ return error; ++} ++ ++/** ++ * fsl_mc_bus_remove - callback invoked when the root MC bus is being ++ * removed ++ */ ++static int fsl_mc_bus_remove(struct platform_device *pdev) ++{ ++ struct fsl_mc *mc = platform_get_drvdata(pdev); ++ ++ if (WARN_ON(&mc->root_mc_bus_dev->dev != fsl_mc_bus_type.dev_root)) ++ return -EINVAL; ++ ++ if (mc->gic_supported) ++ irq_domain_remove(mc->irq_domain); ++ ++ fsl_mc_device_remove(mc->root_mc_bus_dev); ++ fsl_destroy_mc_io(mc->root_mc_bus_dev->mc_io); ++ mc->root_mc_bus_dev->mc_io = NULL; ++ ++ dev_info(&pdev->dev, "Root MC bus device removed"); ++ return 0; ++} ++ ++static const struct of_device_id fsl_mc_bus_match_table[] = { ++ {.compatible = "fsl,qoriq-mc",}, ++ {}, ++}; ++ ++MODULE_DEVICE_TABLE(of, fsl_mc_bus_match_table); ++ ++static struct platform_driver fsl_mc_bus_driver = { ++ .driver = { ++ .name = "fsl_mc_bus", ++ .owner = THIS_MODULE, ++ .pm = NULL, ++ .of_match_table = fsl_mc_bus_match_table, ++ }, ++ .probe = fsl_mc_bus_probe, ++ .remove = fsl_mc_bus_remove, ++}; ++ ++static int __init fsl_mc_bus_driver_init(void) ++{ ++ int error; ++ ++ mc_dev_cache = kmem_cache_create("fsl_mc_device", ++ sizeof(struct fsl_mc_device), 0, 0, ++ NULL); ++ if (!mc_dev_cache) { ++ pr_err("Could not create fsl_mc_device cache\n"); ++ return -ENOMEM; ++ } ++ ++ error = bus_register(&fsl_mc_bus_type); ++ if (error < 0) { ++ pr_err("fsl-mc bus type registration failed: %d\n", error); ++ goto error_cleanup_cache; ++ } ++ ++ pr_info("fsl-mc bus type registered\n"); ++ ++ error = platform_driver_register(&fsl_mc_bus_driver); ++ if (error < 0) { ++ pr_err("platform_driver_register() failed: %d\n", error); ++ goto error_cleanup_bus; ++ } ++ ++ error = dprc_driver_init(); ++ if (error < 0) ++ goto error_cleanup_driver; ++ ++ error = fsl_mc_allocator_driver_init(); ++ if (error < 0) ++ goto error_cleanup_dprc_driver; ++ ++ return 0; ++ ++error_cleanup_dprc_driver: ++ dprc_driver_exit(); ++ ++error_cleanup_driver: ++ platform_driver_unregister(&fsl_mc_bus_driver); ++ ++error_cleanup_bus: ++ bus_unregister(&fsl_mc_bus_type); ++ ++error_cleanup_cache: ++ kmem_cache_destroy(mc_dev_cache); ++ return error; ++} ++ ++postcore_initcall(fsl_mc_bus_driver_init); ++ ++static void __exit fsl_mc_bus_driver_exit(void) ++{ ++ if (WARN_ON(!mc_dev_cache)) ++ return; ++ ++ fsl_mc_allocator_driver_exit(); ++ dprc_driver_exit(); ++ platform_driver_unregister(&fsl_mc_bus_driver); ++ bus_unregister(&fsl_mc_bus_type); ++ kmem_cache_destroy(mc_dev_cache); ++ pr_info("MC bus unregistered\n"); ++} ++ ++module_exit(fsl_mc_bus_driver_exit); ++ ++MODULE_AUTHOR("Freescale Semiconductor Inc."); ++MODULE_DESCRIPTION("Freescale Management Complex (MC) bus driver"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/staging/fsl-mc/bus/mc-ioctl.h b/drivers/staging/fsl-mc/bus/mc-ioctl.h +new file mode 100644 +index 0000000..d5c1bc3 +--- /dev/null ++++ b/drivers/staging/fsl-mc/bus/mc-ioctl.h +@@ -0,0 +1,25 @@ ++/* ++ * Freescale Management Complex (MC) ioclt interface ++ * ++ * Copyright (C) 2014 Freescale Semiconductor, Inc. ++ * Author: German Rivera ++ * Lijun Pan ++ * ++ * This file is licensed under the terms of the GNU General Public ++ * License version 2. This program is licensed "as is" without any ++ * warranty of any kind, whether express or implied. ++ */ ++#ifndef _FSL_MC_IOCTL_H_ ++#define _FSL_MC_IOCTL_H_ ++ ++#include ++ ++#define RESTOOL_IOCTL_TYPE 'R' ++ ++#define RESTOOL_GET_ROOT_DPRC_INFO \ ++ _IOR(RESTOOL_IOCTL_TYPE, 0x1, uint32_t) ++ ++#define RESTOOL_SEND_MC_COMMAND \ ++ _IOWR(RESTOOL_IOCTL_TYPE, 0x4, struct mc_command) ++ ++#endif /* _FSL_MC_IOCTL_H_ */ +diff --git a/drivers/staging/fsl-mc/bus/mc-restool.c b/drivers/staging/fsl-mc/bus/mc-restool.c +new file mode 100644 +index 0000000..d261c1a +--- /dev/null ++++ b/drivers/staging/fsl-mc/bus/mc-restool.c +@@ -0,0 +1,312 @@ ++/* ++ * Freescale Management Complex (MC) restool driver ++ * ++ * Copyright (C) 2014 Freescale Semiconductor, Inc. ++ * Author: German Rivera ++ * Lijun Pan ++ * This file is licensed under the terms of the GNU General Public ++ * License version 2. This program is licensed "as is" without any ++ * warranty of any kind, whether express or implied. ++ */ ++ ++#include "../include/mc-private.h" ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "mc-ioctl.h" ++#include "../include/mc-sys.h" ++#include "../include/mc-cmd.h" ++#include "../include/dpmng.h" ++ ++/** ++ * Maximum number of DPRCs that can be opened at the same time ++ */ ++#define MAX_DPRC_HANDLES 64 ++ ++/** ++ * struct fsl_mc_restool - Management Complex (MC) resource manager object ++ * @tool_mc_io: pointer to the MC I/O object used by the restool ++ */ ++struct fsl_mc_restool { ++ struct fsl_mc_io *tool_mc_io; ++}; ++ ++/** ++ * struct global_state - indicating the number of static and dynamic instance ++ * @dynamic_instance_count - number of dynamically created instances ++ * @static_instance_in_use - static instance is in use or not ++ * @mutex - mutex lock to serialze the operations ++ */ ++struct global_state { ++ uint32_t dynamic_instance_count; ++ bool static_instance_in_use; ++ struct mutex mutex; ++}; ++ ++static struct fsl_mc_restool fsl_mc_restool = { 0 }; ++static struct global_state global_state = { 0 }; ++ ++static int fsl_mc_restool_dev_open(struct inode *inode, struct file *filep) ++{ ++ struct fsl_mc_device *root_mc_dev; ++ int error = 0; ++ struct fsl_mc_restool *fsl_mc_restool_new = NULL; ++ ++ mutex_lock(&global_state.mutex); ++ ++ if (WARN_ON(fsl_mc_bus_type.dev_root == NULL)) { ++ error = -EINVAL; ++ goto error; ++ } ++ ++ if (!global_state.static_instance_in_use) { ++ global_state.static_instance_in_use = true; ++ filep->private_data = &fsl_mc_restool; ++ } else { ++ fsl_mc_restool_new = kmalloc(sizeof(struct fsl_mc_restool), ++ GFP_KERNEL); ++ if (fsl_mc_restool_new == NULL) { ++ error = -ENOMEM; ++ goto error; ++ } ++ memset(fsl_mc_restool_new, 0, sizeof(*fsl_mc_restool_new)); ++ ++ root_mc_dev = to_fsl_mc_device(fsl_mc_bus_type.dev_root); ++ error = fsl_mc_portal_allocate(root_mc_dev, 0, ++ &fsl_mc_restool_new->tool_mc_io); ++ if (error < 0) { ++ pr_err("Not able to allocate MC portal\n"); ++ goto error; ++ } ++ ++global_state.dynamic_instance_count; ++ filep->private_data = fsl_mc_restool_new; ++ } ++ ++ mutex_unlock(&global_state.mutex); ++ return 0; ++error: ++ if (fsl_mc_restool_new != NULL && ++ fsl_mc_restool_new->tool_mc_io != NULL) { ++ fsl_mc_portal_free(fsl_mc_restool_new->tool_mc_io); ++ fsl_mc_restool_new->tool_mc_io = NULL; ++ } ++ ++ kfree(fsl_mc_restool_new); ++ mutex_unlock(&global_state.mutex); ++ return error; ++} ++ ++static int fsl_mc_restool_dev_release(struct inode *inode, struct file *filep) ++{ ++ struct fsl_mc_restool *fsl_mc_restool_local = filep->private_data; ++ ++ if (WARN_ON(filep->private_data == NULL)) ++ return -EINVAL; ++ ++ mutex_lock(&global_state.mutex); ++ ++ if (WARN_ON(global_state.dynamic_instance_count == 0 && ++ !global_state.static_instance_in_use)) { ++ mutex_unlock(&global_state.mutex); ++ return -EINVAL; ++ } ++ ++ /* Globally clean up opened/untracked handles */ ++ fsl_mc_portal_reset(fsl_mc_restool_local->tool_mc_io); ++ ++ pr_debug("dynamic instance count: %d\n", ++ global_state.dynamic_instance_count); ++ pr_debug("static instance count: %d\n", ++ global_state.static_instance_in_use); ++ ++ /* ++ * must check ++ * whether fsl_mc_restool_local is dynamic or global instance ++ * Otherwise it will free up the reserved portal by accident ++ * or even not free up the dynamic allocated portal ++ * if 2 or more instances running concurrently ++ */ ++ if (fsl_mc_restool_local == &fsl_mc_restool) { ++ pr_debug("this is reserved portal"); ++ pr_debug("reserved portal not in use\n"); ++ global_state.static_instance_in_use = false; ++ } else { ++ pr_debug("this is dynamically allocated portal"); ++ pr_debug("free one dynamically allocated portal\n"); ++ fsl_mc_portal_free(fsl_mc_restool_local->tool_mc_io); ++ kfree(filep->private_data); ++ --global_state.dynamic_instance_count; ++ } ++ ++ filep->private_data = NULL; ++ mutex_unlock(&global_state.mutex); ++ return 0; ++} ++ ++static int restool_get_root_dprc_info(unsigned long arg) ++{ ++ int error = -EINVAL; ++ uint32_t root_dprc_id; ++ struct fsl_mc_device *root_mc_dev; ++ ++ root_mc_dev = to_fsl_mc_device(fsl_mc_bus_type.dev_root); ++ root_dprc_id = root_mc_dev->obj_desc.id; ++ error = copy_to_user((void __user *)arg, &root_dprc_id, ++ sizeof(root_dprc_id)); ++ if (error < 0) { ++ pr_err("copy_to_user() failed with error %d\n", error); ++ goto error; ++ } ++ ++ return 0; ++error: ++ return error; ++} ++ ++static int restool_send_mc_command(unsigned long arg, ++ struct fsl_mc_restool *fsl_mc_restool) ++{ ++ int error = -EINVAL; ++ struct mc_command mc_cmd; ++ ++ error = copy_from_user(&mc_cmd, (void __user *)arg, sizeof(mc_cmd)); ++ if (error < 0) { ++ pr_err("copy_to_user() failed with error %d\n", error); ++ goto error; ++ } ++ ++ /* ++ * Send MC command to the MC: ++ */ ++ error = mc_send_command(fsl_mc_restool->tool_mc_io, &mc_cmd); ++ if (error < 0) ++ goto error; ++ ++ error = copy_to_user((void __user *)arg, &mc_cmd, sizeof(mc_cmd)); ++ if (error < 0) { ++ pr_err("copy_to_user() failed with error %d\n", error); ++ goto error; ++ } ++ ++ return 0; ++error: ++ return error; ++} ++ ++static long ++fsl_mc_restool_dev_ioctl(struct file *file, unsigned int cmd, unsigned long arg) ++{ ++ int error = -EINVAL; ++ ++ if (WARN_ON(fsl_mc_bus_type.dev_root == NULL)) ++ goto out; ++ ++ switch (cmd) { ++ case RESTOOL_GET_ROOT_DPRC_INFO: ++ error = restool_get_root_dprc_info(arg); ++ break; ++ ++ case RESTOOL_SEND_MC_COMMAND: ++ error = restool_send_mc_command(arg, file->private_data); ++ break; ++ default: ++ error = -EINVAL; ++ } ++out: ++ return error; ++} ++ ++static const struct file_operations fsl_mc_restool_dev_fops = { ++ .owner = THIS_MODULE, ++ .open = fsl_mc_restool_dev_open, ++ .release = fsl_mc_restool_dev_release, ++ .unlocked_ioctl = fsl_mc_restool_dev_ioctl, ++ .compat_ioctl = fsl_mc_restool_dev_ioctl, ++}; ++ ++static struct miscdevice fsl_mc_restool_dev = { ++ .minor = MISC_DYNAMIC_MINOR, ++ .name = "mc_restool", ++ .fops = &fsl_mc_restool_dev_fops ++}; ++ ++static int __init fsl_mc_restool_driver_init(void) ++{ ++ struct fsl_mc_device *root_mc_dev; ++ int error = -EINVAL; ++ bool restool_dev_registered = false; ++ ++ mutex_init(&global_state.mutex); ++ ++ if (WARN_ON(fsl_mc_restool.tool_mc_io != NULL)) ++ goto error; ++ ++ if (WARN_ON(global_state.dynamic_instance_count != 0)) ++ goto error; ++ ++ if (WARN_ON(global_state.static_instance_in_use)) ++ goto error; ++ ++ if (fsl_mc_bus_type.dev_root == NULL) { ++ pr_err("fsl-mc bus not found, restool driver registration failed\n"); ++ goto error; ++ } ++ ++ root_mc_dev = to_fsl_mc_device(fsl_mc_bus_type.dev_root); ++ error = fsl_mc_portal_allocate(root_mc_dev, 0, ++ &fsl_mc_restool.tool_mc_io); ++ if (error < 0) { ++ pr_err("Not able to allocate MC portal\n"); ++ goto error; ++ } ++ ++ error = misc_register(&fsl_mc_restool_dev); ++ if (error < 0) { ++ pr_err("misc_register() failed: %d\n", error); ++ goto error; ++ } ++ ++ restool_dev_registered = true; ++ pr_info("%s driver registered\n", fsl_mc_restool_dev.name); ++ return 0; ++error: ++ if (restool_dev_registered) ++ misc_deregister(&fsl_mc_restool_dev); ++ ++ if (fsl_mc_restool.tool_mc_io != NULL) { ++ fsl_mc_portal_free(fsl_mc_restool.tool_mc_io); ++ fsl_mc_restool.tool_mc_io = NULL; ++ } ++ ++ return error; ++} ++ ++module_init(fsl_mc_restool_driver_init); ++ ++static void __exit fsl_mc_restool_driver_exit(void) ++{ ++ if (WARN_ON(fsl_mc_restool.tool_mc_io == NULL)) ++ return; ++ ++ if (WARN_ON(global_state.dynamic_instance_count != 0)) ++ return; ++ ++ if (WARN_ON(global_state.static_instance_in_use)) ++ return; ++ ++ misc_deregister(&fsl_mc_restool_dev); ++ fsl_mc_portal_free(fsl_mc_restool.tool_mc_io); ++ fsl_mc_restool.tool_mc_io = NULL; ++ pr_info("%s driver unregistered\n", fsl_mc_restool_dev.name); ++} ++ ++module_exit(fsl_mc_restool_driver_exit); ++ ++MODULE_AUTHOR("Freescale Semiconductor Inc."); ++MODULE_DESCRIPTION("Freescale's MC restool driver"); ++MODULE_LICENSE("GPL"); ++ +diff --git a/drivers/staging/fsl-mc/bus/mc-sys.c b/drivers/staging/fsl-mc/bus/mc-sys.c +new file mode 100644 +index 0000000..d3b6940 +--- /dev/null ++++ b/drivers/staging/fsl-mc/bus/mc-sys.c +@@ -0,0 +1,677 @@ ++/* Copyright 2013-2014 Freescale Semiconductor Inc. ++ * ++ * I/O services to send MC commands to the MC hardware ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * * Neither the name of the above-listed copyright holders nor the ++ * names of any contributors may be used to endorse or promote products ++ * derived from this software without specific prior written permission. ++ * ++ * ++ * ALTERNATIVELY, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") as published by the Free Software ++ * Foundation, either version 2 of that License or (at your option) any ++ * later version. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE ++ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++#include "../include/mc-sys.h" ++#include "../include/mc-cmd.h" ++#include "../include/mc.h" ++#include ++#include ++#include ++#include ++#include ++#include "dpmcp.h" ++ ++/** ++ * Timeout in milliseconds to wait for the completion of an MC command ++ * 5000 ms is barely enough for dpsw/dpdmux creation ++ * TODO: if MC firmware could response faster, we should decrease this value ++ */ ++#define MC_CMD_COMPLETION_TIMEOUT_MS 5000 ++ ++/* ++ * usleep_range() min and max values used to throttle down polling ++ * iterations while waiting for MC command completion ++ */ ++#define MC_CMD_COMPLETION_POLLING_MIN_SLEEP_USECS 10 ++#define MC_CMD_COMPLETION_POLLING_MAX_SLEEP_USECS 500 ++ ++#define MC_CMD_HDR_READ_CMDID(_hdr) \ ++ ((uint16_t)mc_dec((_hdr), MC_CMD_HDR_CMDID_O, MC_CMD_HDR_CMDID_S)) ++ ++/** ++ * dpmcp_irq0_handler - Regular ISR for DPMCP interrupt 0 ++ * ++ * @irq: IRQ number of the interrupt being handled ++ * @arg: Pointer to device structure ++ */ ++static irqreturn_t dpmcp_irq0_handler(int irq_num, void *arg) ++{ ++ struct device *dev = (struct device *)arg; ++ struct fsl_mc_device *dpmcp_dev = to_fsl_mc_device(dev); ++ struct fsl_mc_io *mc_io = dpmcp_dev->mc_io; ++ ++ dev_dbg(dev, "DPMCP IRQ %d triggered on CPU %u\n", irq_num, ++ smp_processor_id()); ++ ++ if (WARN_ON(dpmcp_dev->irqs[0]->irq_number != (uint32_t)irq_num)) ++ goto out; ++ ++ if (WARN_ON(!mc_io)) ++ goto out; ++ ++ complete(&mc_io->mc_command_done_completion); ++out: ++ return IRQ_HANDLED; ++} ++ ++/* ++ * Disable and clear interrupts for a given DPMCP object ++ */ ++static int disable_dpmcp_irq(struct fsl_mc_device *dpmcp_dev) ++{ ++ int error; ++ ++ /* ++ * Disable generation of the DPMCP interrupt: ++ */ ++ error = dpmcp_set_irq_enable(dpmcp_dev->mc_io, ++ MC_CMD_FLAG_INTR_DIS, ++ dpmcp_dev->mc_handle, ++ DPMCP_IRQ_INDEX, 0); ++ if (error < 0) { ++ dev_err(&dpmcp_dev->dev, ++ "dpmcp_set_irq_enable() failed: %d\n", error); ++ ++ return error; ++ } ++ ++ /* ++ * Disable all DPMCP interrupt causes: ++ */ ++ error = dpmcp_set_irq_mask(dpmcp_dev->mc_io, ++ MC_CMD_FLAG_INTR_DIS, ++ dpmcp_dev->mc_handle, ++ DPMCP_IRQ_INDEX, 0x0); ++ if (error < 0) { ++ dev_err(&dpmcp_dev->dev, ++ "dpmcp_set_irq_mask() failed: %d\n", error); ++ ++ return error; ++ } ++ ++ return 0; ++} ++ ++static void unregister_dpmcp_irq_handler(struct fsl_mc_device *dpmcp_dev) ++{ ++ struct fsl_mc_device_irq *irq = dpmcp_dev->irqs[DPMCP_IRQ_INDEX]; ++ ++ devm_free_irq(&dpmcp_dev->dev, irq->irq_number, &dpmcp_dev->dev); ++} ++ ++static int register_dpmcp_irq_handler(struct fsl_mc_device *dpmcp_dev) ++{ ++ int error; ++ struct fsl_mc_device_irq *irq = dpmcp_dev->irqs[DPMCP_IRQ_INDEX]; ++ ++ error = devm_request_irq(&dpmcp_dev->dev, ++ irq->irq_number, ++ dpmcp_irq0_handler, ++ IRQF_NO_SUSPEND | IRQF_ONESHOT, ++ "FSL MC DPMCP irq0", ++ &dpmcp_dev->dev); ++ if (error < 0) { ++ dev_err(&dpmcp_dev->dev, ++ "devm_request_irq() failed: %d\n", ++ error); ++ return error; ++ } ++ ++ return 0; ++} ++ ++static int enable_dpmcp_irq(struct fsl_mc_device *dpmcp_dev) ++{ ++ int error; ++ ++ /* ++ * Enable MC command completion event to trigger DPMCP interrupt: ++ */ ++ error = dpmcp_set_irq_mask(dpmcp_dev->mc_io, ++ MC_CMD_FLAG_INTR_DIS, ++ dpmcp_dev->mc_handle, ++ DPMCP_IRQ_INDEX, ++ DPMCP_IRQ_EVENT_CMD_DONE); ++ if (error < 0) { ++ dev_err(&dpmcp_dev->dev, ++ "dpmcp_set_irq_mask() failed: %d\n", error); ++ ++ return error; ++ } ++ ++ /* ++ * Enable generation of the interrupt: ++ */ ++ error = dpmcp_set_irq_enable(dpmcp_dev->mc_io, ++ MC_CMD_FLAG_INTR_DIS, ++ dpmcp_dev->mc_handle, ++ DPMCP_IRQ_INDEX, 1); ++ if (error < 0) { ++ dev_err(&dpmcp_dev->dev, ++ "dpmcp_set_irq_enable() failed: %d\n", error); ++ ++ return error; ++ } ++ ++ return 0; ++} ++ ++/* ++ * Setup MC command completion interrupt for the DPMCP device associated with a ++ * given fsl_mc_io object ++ */ ++int fsl_mc_io_setup_dpmcp_irq(struct fsl_mc_io *mc_io) ++{ ++ int error; ++ struct fsl_mc_device *dpmcp_dev = mc_io->dpmcp_dev; ++ ++ if (WARN_ON(mc_io->flags & FSL_MC_IO_ATOMIC_CONTEXT_PORTAL)) ++ return -EINVAL; ++ ++ if (WARN_ON(!dpmcp_dev)) ++ return -EINVAL; ++ ++ if (WARN_ON(!fsl_mc_interrupts_supported())) ++ return -EINVAL; ++ ++ if (WARN_ON(dpmcp_dev->obj_desc.irq_count != 1)) ++ return -EINVAL; ++ ++ if (WARN_ON(dpmcp_dev->mc_io != mc_io)) ++ return -EINVAL; ++ ++ error = fsl_mc_allocate_irqs(dpmcp_dev); ++ if (error < 0) ++ return error; ++ ++ error = disable_dpmcp_irq(dpmcp_dev); ++ if (error < 0) ++ goto error_free_irqs; ++ ++ error = register_dpmcp_irq_handler(dpmcp_dev); ++ if (error < 0) ++ goto error_free_irqs; ++ ++ error = enable_dpmcp_irq(dpmcp_dev); ++ if (error < 0) ++ goto error_unregister_irq_handler; ++ ++ mc_io->mc_command_done_irq_armed = true; ++ return 0; ++ ++error_unregister_irq_handler: ++ unregister_dpmcp_irq_handler(dpmcp_dev); ++ ++error_free_irqs: ++ fsl_mc_free_irqs(dpmcp_dev); ++ ++ return error; ++} ++EXPORT_SYMBOL_GPL(fsl_mc_io_setup_dpmcp_irq); ++ ++/* ++ * Tear down interrupts for the DPMCP device associated with a given fsl_mc_io ++ * object ++ */ ++static void teardown_dpmcp_irq(struct fsl_mc_io *mc_io) ++{ ++ struct fsl_mc_device *dpmcp_dev = mc_io->dpmcp_dev; ++ ++ if (WARN_ON(!dpmcp_dev)) ++ return; ++ if (WARN_ON(!fsl_mc_interrupts_supported())) ++ return; ++ if (WARN_ON(!dpmcp_dev->irqs)) ++ return; ++ ++ mc_io->mc_command_done_irq_armed = false; ++ (void)disable_dpmcp_irq(dpmcp_dev); ++ unregister_dpmcp_irq_handler(dpmcp_dev); ++ fsl_mc_free_irqs(dpmcp_dev); ++} ++ ++/** ++ * Creates an MC I/O object ++ * ++ * @dev: device to be associated with the MC I/O object ++ * @mc_portal_phys_addr: physical address of the MC portal to use ++ * @mc_portal_size: size in bytes of the MC portal ++ * @resource: Pointer to MC bus object allocator resource associated ++ * with this MC I/O object or NULL if none. ++ * @flags: flags for the new MC I/O object ++ * @new_mc_io: Area to return pointer to newly created MC I/O object ++ * ++ * Returns '0' on Success; Error code otherwise. ++ */ ++int __must_check fsl_create_mc_io(struct device *dev, ++ phys_addr_t mc_portal_phys_addr, ++ uint32_t mc_portal_size, ++ struct fsl_mc_device *dpmcp_dev, ++ uint32_t flags, struct fsl_mc_io **new_mc_io) ++{ ++ int error; ++ struct fsl_mc_io *mc_io; ++ void __iomem *mc_portal_virt_addr; ++ struct resource *res; ++ ++ mc_io = devm_kzalloc(dev, sizeof(*mc_io), GFP_KERNEL); ++ if (!mc_io) ++ return -ENOMEM; ++ ++ mc_io->dev = dev; ++ mc_io->flags = flags; ++ mc_io->portal_phys_addr = mc_portal_phys_addr; ++ mc_io->portal_size = mc_portal_size; ++ mc_io->mc_command_done_irq_armed = false; ++ if (flags & FSL_MC_IO_ATOMIC_CONTEXT_PORTAL) { ++ spin_lock_init(&mc_io->spinlock); ++ } else { ++ mutex_init(&mc_io->mutex); ++ init_completion(&mc_io->mc_command_done_completion); ++ } ++ ++ res = devm_request_mem_region(dev, ++ mc_portal_phys_addr, ++ mc_portal_size, ++ "mc_portal"); ++ if (!res) { ++ dev_err(dev, ++ "devm_request_mem_region failed for MC portal %#llx\n", ++ mc_portal_phys_addr); ++ return -EBUSY; ++ } ++ ++ mc_portal_virt_addr = devm_ioremap_nocache(dev, ++ mc_portal_phys_addr, ++ mc_portal_size); ++ if (!mc_portal_virt_addr) { ++ dev_err(dev, ++ "devm_ioremap_nocache failed for MC portal %#llx\n", ++ mc_portal_phys_addr); ++ return -ENXIO; ++ } ++ ++ mc_io->portal_virt_addr = mc_portal_virt_addr; ++ if (dpmcp_dev) { ++ error = fsl_mc_io_set_dpmcp(mc_io, dpmcp_dev); ++ if (error < 0) ++ goto error_destroy_mc_io; ++ ++ if (!(flags & FSL_MC_IO_ATOMIC_CONTEXT_PORTAL) && ++ fsl_mc_interrupts_supported()) { ++ error = fsl_mc_io_setup_dpmcp_irq(mc_io); ++ if (error < 0) ++ goto error_destroy_mc_io; ++ } ++ } ++ ++ *new_mc_io = mc_io; ++ return 0; ++ ++error_destroy_mc_io: ++ fsl_destroy_mc_io(mc_io); ++ return error; ++ ++} ++EXPORT_SYMBOL_GPL(fsl_create_mc_io); ++ ++/** ++ * Destroys an MC I/O object ++ * ++ * @mc_io: MC I/O object to destroy ++ */ ++void fsl_destroy_mc_io(struct fsl_mc_io *mc_io) ++{ ++ struct fsl_mc_device *dpmcp_dev = mc_io->dpmcp_dev; ++ ++ if (dpmcp_dev) ++ fsl_mc_io_unset_dpmcp(mc_io); ++ ++ devm_iounmap(mc_io->dev, mc_io->portal_virt_addr); ++ devm_release_mem_region(mc_io->dev, ++ mc_io->portal_phys_addr, ++ mc_io->portal_size); ++ ++ mc_io->portal_virt_addr = NULL; ++ devm_kfree(mc_io->dev, mc_io); ++} ++EXPORT_SYMBOL_GPL(fsl_destroy_mc_io); ++ ++int fsl_mc_io_set_dpmcp(struct fsl_mc_io *mc_io, ++ struct fsl_mc_device *dpmcp_dev) ++{ ++ int error; ++ ++ if (WARN_ON(!dpmcp_dev)) ++ return -EINVAL; ++ ++ if (WARN_ON(mc_io->dpmcp_dev)) ++ return -EINVAL; ++ ++ if (WARN_ON(dpmcp_dev->mc_io)) ++ return -EINVAL; ++ ++ if (!(mc_io->flags & FSL_MC_IO_ATOMIC_CONTEXT_PORTAL)) { ++ error = dpmcp_open(mc_io, ++ 0, ++ dpmcp_dev->obj_desc.id, ++ &dpmcp_dev->mc_handle); ++ if (error < 0) ++ return error; ++ } ++ ++ mc_io->dpmcp_dev = dpmcp_dev; ++ dpmcp_dev->mc_io = mc_io; ++ return 0; ++} ++EXPORT_SYMBOL_GPL(fsl_mc_io_set_dpmcp); ++ ++void fsl_mc_io_unset_dpmcp(struct fsl_mc_io *mc_io) ++{ ++ int error; ++ struct fsl_mc_device *dpmcp_dev = mc_io->dpmcp_dev; ++ ++ if (WARN_ON(!dpmcp_dev)) ++ return; ++ ++ if (WARN_ON(dpmcp_dev->mc_io != mc_io)) ++ return; ++ ++ if (!(mc_io->flags & FSL_MC_IO_ATOMIC_CONTEXT_PORTAL)) { ++ if (dpmcp_dev->irqs) ++ teardown_dpmcp_irq(mc_io); ++ ++ error = dpmcp_close(mc_io, ++ 0, ++ dpmcp_dev->mc_handle); ++ if (error < 0) { ++ dev_err(&dpmcp_dev->dev, "dpmcp_close() failed: %d\n", ++ error); ++ } ++ } ++ ++ mc_io->dpmcp_dev = NULL; ++ dpmcp_dev->mc_io = NULL; ++} ++EXPORT_SYMBOL_GPL(fsl_mc_io_unset_dpmcp); ++ ++static int mc_status_to_error(enum mc_cmd_status status) ++{ ++ static const int mc_status_to_error_map[] = { ++ [MC_CMD_STATUS_OK] = 0, ++ [MC_CMD_STATUS_AUTH_ERR] = -EACCES, ++ [MC_CMD_STATUS_NO_PRIVILEGE] = -EPERM, ++ [MC_CMD_STATUS_DMA_ERR] = -EIO, ++ [MC_CMD_STATUS_CONFIG_ERR] = -ENXIO, ++ [MC_CMD_STATUS_TIMEOUT] = -ETIMEDOUT, ++ [MC_CMD_STATUS_NO_RESOURCE] = -ENAVAIL, ++ [MC_CMD_STATUS_NO_MEMORY] = -ENOMEM, ++ [MC_CMD_STATUS_BUSY] = -EBUSY, ++ [MC_CMD_STATUS_UNSUPPORTED_OP] = -ENOTSUPP, ++ [MC_CMD_STATUS_INVALID_STATE] = -ENODEV, ++ }; ++ ++ if (WARN_ON((u32)status >= ARRAY_SIZE(mc_status_to_error_map))) ++ return -EINVAL; ++ ++ return mc_status_to_error_map[status]; ++} ++ ++static const char *mc_status_to_string(enum mc_cmd_status status) ++{ ++ static const char *const status_strings[] = { ++ [MC_CMD_STATUS_OK] = "Command completed successfully", ++ [MC_CMD_STATUS_READY] = "Command ready to be processed", ++ [MC_CMD_STATUS_AUTH_ERR] = "Authentication error", ++ [MC_CMD_STATUS_NO_PRIVILEGE] = "No privilege", ++ [MC_CMD_STATUS_DMA_ERR] = "DMA or I/O error", ++ [MC_CMD_STATUS_CONFIG_ERR] = "Configuration error", ++ [MC_CMD_STATUS_TIMEOUT] = "Operation timed out", ++ [MC_CMD_STATUS_NO_RESOURCE] = "No resources", ++ [MC_CMD_STATUS_NO_MEMORY] = "No memory available", ++ [MC_CMD_STATUS_BUSY] = "Device is busy", ++ [MC_CMD_STATUS_UNSUPPORTED_OP] = "Unsupported operation", ++ [MC_CMD_STATUS_INVALID_STATE] = "Invalid state" ++ }; ++ ++ if ((unsigned int)status >= ARRAY_SIZE(status_strings)) ++ return "Unknown MC error"; ++ ++ return status_strings[status]; ++} ++ ++/** ++ * mc_write_command - writes a command to a Management Complex (MC) portal ++ * ++ * @portal: pointer to an MC portal ++ * @cmd: pointer to a filled command ++ */ ++static inline void mc_write_command(struct mc_command __iomem *portal, ++ struct mc_command *cmd) ++{ ++ int i; ++ ++ /* copy command parameters into the portal */ ++ for (i = 0; i < MC_CMD_NUM_OF_PARAMS; i++) ++ writeq(cmd->params[i], &portal->params[i]); ++ ++ /* submit the command by writing the header */ ++ writeq(cmd->header, &portal->header); ++} ++ ++/** ++ * mc_read_response - reads the response for the last MC command from a ++ * Management Complex (MC) portal ++ * ++ * @portal: pointer to an MC portal ++ * @resp: pointer to command response buffer ++ * ++ * Returns MC_CMD_STATUS_OK on Success; Error code otherwise. ++ */ ++static inline enum mc_cmd_status mc_read_response(struct mc_command __iomem * ++ portal, ++ struct mc_command *resp) ++{ ++ int i; ++ enum mc_cmd_status status; ++ ++ /* Copy command response header from MC portal: */ ++ resp->header = readq(&portal->header); ++ status = MC_CMD_HDR_READ_STATUS(resp->header); ++ if (status != MC_CMD_STATUS_OK) ++ return status; ++ ++ /* Copy command response data from MC portal: */ ++ for (i = 0; i < MC_CMD_NUM_OF_PARAMS; i++) ++ resp->params[i] = readq(&portal->params[i]); ++ ++ return status; ++} ++ ++static int mc_completion_wait(struct fsl_mc_io *mc_io, struct mc_command *cmd, ++ enum mc_cmd_status *mc_status) ++{ ++ enum mc_cmd_status status; ++ unsigned long jiffies_left; ++ unsigned long timeout_jiffies = ++ msecs_to_jiffies(MC_CMD_COMPLETION_TIMEOUT_MS); ++ ++ if (WARN_ON(!mc_io->dpmcp_dev)) ++ return -EINVAL; ++ ++ if (WARN_ON(mc_io->flags & FSL_MC_IO_ATOMIC_CONTEXT_PORTAL)) ++ return -EINVAL; ++ ++ for (;;) { ++ status = mc_read_response(mc_io->portal_virt_addr, cmd); ++ if (status != MC_CMD_STATUS_READY) ++ break; ++ ++ jiffies_left = wait_for_completion_timeout( ++ &mc_io->mc_command_done_completion, ++ timeout_jiffies); ++ if (jiffies_left == 0) ++ return -ETIMEDOUT; ++ } ++ ++ *mc_status = status; ++ return 0; ++} ++ ++static int mc_polling_wait_preemptible(struct fsl_mc_io *mc_io, ++ struct mc_command *cmd, ++ enum mc_cmd_status *mc_status) ++{ ++ enum mc_cmd_status status; ++ unsigned long jiffies_until_timeout = ++ jiffies + msecs_to_jiffies(MC_CMD_COMPLETION_TIMEOUT_MS); ++ ++ for (;;) { ++ status = mc_read_response(mc_io->portal_virt_addr, cmd); ++ if (status != MC_CMD_STATUS_READY) ++ break; ++ ++ usleep_range(MC_CMD_COMPLETION_POLLING_MIN_SLEEP_USECS, ++ MC_CMD_COMPLETION_POLLING_MAX_SLEEP_USECS); ++ ++ if (time_after_eq(jiffies, jiffies_until_timeout)) ++ return -ETIMEDOUT; ++ } ++ ++ *mc_status = status; ++ return 0; ++} ++ ++static int mc_polling_wait_atomic(struct fsl_mc_io *mc_io, ++ struct mc_command *cmd, ++ enum mc_cmd_status *mc_status) ++{ ++ enum mc_cmd_status status; ++ unsigned long timeout_usecs = MC_CMD_COMPLETION_TIMEOUT_MS * 1000; ++ ++ BUILD_BUG_ON((MC_CMD_COMPLETION_TIMEOUT_MS * 1000) % ++ MC_CMD_COMPLETION_POLLING_MAX_SLEEP_USECS != 0); ++ ++ for (;;) { ++ status = mc_read_response(mc_io->portal_virt_addr, cmd); ++ if (status != MC_CMD_STATUS_READY) ++ break; ++ ++ udelay(MC_CMD_COMPLETION_POLLING_MAX_SLEEP_USECS); ++ timeout_usecs -= MC_CMD_COMPLETION_POLLING_MAX_SLEEP_USECS; ++ if (timeout_usecs == 0) ++ return -ETIMEDOUT; ++ } ++ ++ *mc_status = status; ++ return 0; ++} ++ ++/** ++ * Sends a command to the MC device using the given MC I/O object ++ * ++ * @mc_io: MC I/O object to be used ++ * @cmd: command to be sent ++ * ++ * Returns '0' on Success; Error code otherwise. ++ */ ++int mc_send_command(struct fsl_mc_io *mc_io, struct mc_command *cmd) ++{ ++ int error; ++ enum mc_cmd_status status; ++ unsigned long irq_flags = 0; ++ bool dpmcp_completion_intr_disabled = ++ (MC_CMD_HDR_READ_FLAGS(cmd->header) & MC_CMD_FLAG_INTR_DIS); ++ ++ if (WARN_ON(in_irq() && ++ (!dpmcp_completion_intr_disabled || ++ !(mc_io->flags & FSL_MC_IO_ATOMIC_CONTEXT_PORTAL)))) ++ return -EINVAL; ++ ++ if (mc_io->flags & FSL_MC_IO_ATOMIC_CONTEXT_PORTAL) ++ spin_lock_irqsave(&mc_io->spinlock, irq_flags); ++ else ++ mutex_lock(&mc_io->mutex); ++ ++ /* ++ * Send command to the MC hardware: ++ */ ++ mc_write_command(mc_io->portal_virt_addr, cmd); ++ ++ /* ++ * Wait for response from the MC hardware: ++ */ ++ if (mc_io->mc_command_done_irq_armed && !dpmcp_completion_intr_disabled) ++ error = mc_completion_wait(mc_io, cmd, &status); ++ else if (!(mc_io->flags & FSL_MC_IO_ATOMIC_CONTEXT_PORTAL)) ++ error = mc_polling_wait_preemptible(mc_io, cmd, &status); ++ else ++ error = mc_polling_wait_atomic(mc_io, cmd, &status); ++ ++ if (error < 0) { ++ if (error == -ETIMEDOUT) { ++ pr_debug("MC command timed out (portal: %#llx, obj handle: %#x, command: %#x)\n", ++ mc_io->portal_phys_addr, ++ (unsigned int) ++ MC_CMD_HDR_READ_TOKEN(cmd->header), ++ (unsigned int) ++ MC_CMD_HDR_READ_CMDID(cmd->header)); ++ } ++ goto common_exit; ++ ++ } ++ ++ if (status != MC_CMD_STATUS_OK) { ++ pr_debug("MC command failed: portal: %#llx, obj handle: %#x, command: %#x, status: %s (%#x)\n", ++ mc_io->portal_phys_addr, ++ (unsigned int)MC_CMD_HDR_READ_TOKEN(cmd->header), ++ (unsigned int)MC_CMD_HDR_READ_CMDID(cmd->header), ++ mc_status_to_string(status), ++ (unsigned int)status); ++ ++ error = mc_status_to_error(status); ++ goto common_exit; ++ } ++ ++ error = 0; ++ ++common_exit: ++ if (mc_io->flags & FSL_MC_IO_ATOMIC_CONTEXT_PORTAL) ++ spin_unlock_irqrestore(&mc_io->spinlock, irq_flags); ++ else ++ mutex_unlock(&mc_io->mutex); ++ ++ return error; ++} ++EXPORT_SYMBOL(mc_send_command); +diff --git a/drivers/staging/fsl-mc/include/dpbp-cmd.h b/drivers/staging/fsl-mc/include/dpbp-cmd.h +new file mode 100644 +index 0000000..1ec04e4 +--- /dev/null ++++ b/drivers/staging/fsl-mc/include/dpbp-cmd.h +@@ -0,0 +1,62 @@ ++/* Copyright 2013-2015 Freescale Semiconductor Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * * Neither the name of the above-listed copyright holders nor the ++ * names of any contributors may be used to endorse or promote products ++ * derived from this software without specific prior written permission. ++ * ++ * ++ * ALTERNATIVELY, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") as published by the Free Software ++ * Foundation, either version 2 of that License or (at your option) any ++ * later version. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE ++ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++#ifndef _FSL_DPBP_CMD_H ++#define _FSL_DPBP_CMD_H ++ ++/* DPBP Version */ ++#define DPBP_VER_MAJOR 2 ++#define DPBP_VER_MINOR 2 ++ ++/* Command IDs */ ++#define DPBP_CMDID_CLOSE 0x800 ++#define DPBP_CMDID_OPEN 0x804 ++#define DPBP_CMDID_CREATE 0x904 ++#define DPBP_CMDID_DESTROY 0x900 ++ ++#define DPBP_CMDID_ENABLE 0x002 ++#define DPBP_CMDID_DISABLE 0x003 ++#define DPBP_CMDID_GET_ATTR 0x004 ++#define DPBP_CMDID_RESET 0x005 ++#define DPBP_CMDID_IS_ENABLED 0x006 ++ ++#define DPBP_CMDID_SET_IRQ 0x010 ++#define DPBP_CMDID_GET_IRQ 0x011 ++#define DPBP_CMDID_SET_IRQ_ENABLE 0x012 ++#define DPBP_CMDID_GET_IRQ_ENABLE 0x013 ++#define DPBP_CMDID_SET_IRQ_MASK 0x014 ++#define DPBP_CMDID_GET_IRQ_MASK 0x015 ++#define DPBP_CMDID_GET_IRQ_STATUS 0x016 ++#define DPBP_CMDID_CLEAR_IRQ_STATUS 0x017 ++ ++#define DPBP_CMDID_SET_NOTIFICATIONS 0x01b0 ++#define DPBP_CMDID_GET_NOTIFICATIONS 0x01b1 ++#endif /* _FSL_DPBP_CMD_H */ +diff --git a/drivers/staging/fsl-mc/include/dpbp.h b/drivers/staging/fsl-mc/include/dpbp.h +new file mode 100644 +index 0000000..9856bb8 +--- /dev/null ++++ b/drivers/staging/fsl-mc/include/dpbp.h +@@ -0,0 +1,438 @@ ++/* Copyright 2013-2015 Freescale Semiconductor Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * * Neither the name of the above-listed copyright holders nor the ++ * names of any contributors may be used to endorse or promote products ++ * derived from this software without specific prior written permission. ++ * ++ * ++ * ALTERNATIVELY, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") as published by the Free Software ++ * Foundation, either version 2 of that License or (at your option) any ++ * later version. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE ++ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++#ifndef __FSL_DPBP_H ++#define __FSL_DPBP_H ++ ++/* Data Path Buffer Pool API ++ * Contains initialization APIs and runtime control APIs for DPBP ++ */ ++ ++struct fsl_mc_io; ++ ++/** ++ * dpbp_open() - Open a control session for the specified object. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @dpbp_id: DPBP unique ID ++ * @token: Returned token; use in subsequent API calls ++ * ++ * This function can be used to open a control session for an ++ * already created object; an object may have been declared in ++ * the DPL or by calling the dpbp_create function. ++ * This function returns a unique authentication token, ++ * associated with the specific object ID and the specific MC ++ * portal; this token must be used in all subsequent commands for ++ * this specific object ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpbp_open(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ int dpbp_id, ++ uint16_t *token); ++ ++/** ++ * dpbp_close() - Close the control session of the object ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPBP object ++ * ++ * After this function is called, no further operations are ++ * allowed on the object without opening a new control session. ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpbp_close(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token); ++ ++/** ++ * struct dpbp_cfg - Structure representing DPBP configuration ++ * @options: place holder ++ */ ++struct dpbp_cfg { ++ uint32_t options; ++}; ++ ++/** ++ * dpbp_create() - Create the DPBP object. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @cfg: Configuration structure ++ * @token: Returned token; use in subsequent API calls ++ * ++ * Create the DPBP object, allocate required resources and ++ * perform required initialization. ++ * ++ * The object can be created either by declaring it in the ++ * DPL file, or by calling this function. ++ * This function returns a unique authentication token, ++ * associated with the specific object ID and the specific MC ++ * portal; this token must be used in all subsequent calls to ++ * this specific object. For objects that are created using the ++ * DPL file, call dpbp_open function to get an authentication ++ * token first. ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpbp_create(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ const struct dpbp_cfg *cfg, ++ uint16_t *token); ++ ++/** ++ * dpbp_destroy() - Destroy the DPBP object and release all its resources. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPBP object ++ * ++ * Return: '0' on Success; error code otherwise. ++ */ ++int dpbp_destroy(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token); ++ ++/** ++ * dpbp_enable() - Enable the DPBP. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPBP object ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpbp_enable(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token); ++ ++/** ++ * dpbp_disable() - Disable the DPBP. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPBP object ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpbp_disable(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token); ++ ++/** ++ * dpbp_is_enabled() - Check if the DPBP is enabled. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPBP object ++ * @en: Returns '1' if object is enabled; '0' otherwise ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpbp_is_enabled(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int *en); ++ ++/** ++ * dpbp_reset() - Reset the DPBP, returns the object to initial state. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPBP object ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpbp_reset(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token); ++ ++/** ++ * struct dpbp_irq_cfg - IRQ configuration ++ * @addr: Address that must be written to signal a message-based interrupt ++ * @val: Value to write into irq_addr address ++ * @irq_num: A user defined number associated with this IRQ ++ */ ++struct dpbp_irq_cfg { ++ uint64_t addr; ++ uint32_t val; ++ int irq_num; ++}; ++ ++/** ++ * dpbp_set_irq() - Set IRQ information for the DPBP to trigger an interrupt. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPBP object ++ * @irq_index: Identifies the interrupt index to configure ++ * @irq_cfg: IRQ configuration ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpbp_set_irq(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ struct dpbp_irq_cfg *irq_cfg); ++ ++/** ++ * dpbp_get_irq() - Get IRQ information from the DPBP. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPBP object ++ * @irq_index: The interrupt index to configure ++ * @type: Interrupt type: 0 represents message interrupt ++ * type (both irq_addr and irq_val are valid) ++ * @irq_cfg: IRQ attributes ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpbp_get_irq(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ int *type, ++ struct dpbp_irq_cfg *irq_cfg); ++ ++/** ++ * dpbp_set_irq_enable() - Set overall interrupt state. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPBP object ++ * @irq_index: The interrupt index to configure ++ * @en: Interrupt state - enable = 1, disable = 0 ++ * ++ * Allows GPP software to control when interrupts are generated. ++ * Each interrupt can have up to 32 causes. The enable/disable control's the ++ * overall interrupt state. if the interrupt is disabled no causes will cause ++ * an interrupt. ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpbp_set_irq_enable(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint8_t en); ++ ++/** ++ * dpbp_get_irq_enable() - Get overall interrupt state ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPBP object ++ * @irq_index: The interrupt index to configure ++ * @en: Returned interrupt state - enable = 1, disable = 0 ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpbp_get_irq_enable(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint8_t *en); ++ ++/** ++ * dpbp_set_irq_mask() - Set interrupt mask. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPBP object ++ * @irq_index: The interrupt index to configure ++ * @mask: Event mask to trigger interrupt; ++ * each bit: ++ * 0 = ignore event ++ * 1 = consider event for asserting IRQ ++ * ++ * Every interrupt can have up to 32 causes and the interrupt model supports ++ * masking/unmasking each cause independently ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpbp_set_irq_mask(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint32_t mask); ++ ++/** ++ * dpbp_get_irq_mask() - Get interrupt mask. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPBP object ++ * @irq_index: The interrupt index to configure ++ * @mask: Returned event mask to trigger interrupt ++ * ++ * Every interrupt can have up to 32 causes and the interrupt model supports ++ * masking/unmasking each cause independently ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpbp_get_irq_mask(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint32_t *mask); ++ ++/** ++ * dpbp_get_irq_status() - Get the current status of any pending interrupts. ++ * ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPBP object ++ * @irq_index: The interrupt index to configure ++ * @status: Returned interrupts status - one bit per cause: ++ * 0 = no interrupt pending ++ * 1 = interrupt pending ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpbp_get_irq_status(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint32_t *status); ++ ++/** ++ * dpbp_clear_irq_status() - Clear a pending interrupt's status ++ * ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPBP object ++ * @irq_index: The interrupt index to configure ++ * @status: Bits to clear (W1C) - one bit per cause: ++ * 0 = don't change ++ * 1 = clear status bit ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpbp_clear_irq_status(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint32_t status); ++ ++/** ++ * struct dpbp_attr - Structure representing DPBP attributes ++ * @id: DPBP object ID ++ * @version: DPBP version ++ * @bpid: Hardware buffer pool ID; should be used as an argument in ++ * acquire/release operations on buffers ++ */ ++struct dpbp_attr { ++ int id; ++ /** ++ * struct version - Structure representing DPBP version ++ * @major: DPBP major version ++ * @minor: DPBP minor version ++ */ ++ struct { ++ uint16_t major; ++ uint16_t minor; ++ } version; ++ uint16_t bpid; ++}; ++ ++/** ++ * dpbp_get_attributes - Retrieve DPBP attributes. ++ * ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPBP object ++ * @attr: Returned object's attributes ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpbp_get_attributes(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ struct dpbp_attr *attr); ++ ++/** ++ * DPBP notifications options ++ */ ++ ++/** ++ * BPSCN write will attempt to allocate into a cache (coherent write) ++ */ ++#define DPBP_NOTIF_OPT_COHERENT_WRITE 0x00000001 ++ ++/** ++ * struct dpbp_notification_cfg - Structure representing DPBP notifications ++ * towards software ++ * @depletion_entry: below this threshold the pool is "depleted"; ++ * set it to '0' to disable it ++ * @depletion_exit: greater than or equal to this threshold the pool exit its ++ * "depleted" state ++ * @surplus_entry: above this threshold the pool is in "surplus" state; ++ * set it to '0' to disable it ++ * @surplus_exit: less than or equal to this threshold the pool exit its ++ * "surplus" state ++ * @message_iova: MUST be given if either 'depletion_entry' or 'surplus_entry' ++ * is not '0' (enable); I/O virtual address (must be in DMA-able memory), ++ * must be 16B aligned. ++ * @message_ctx: The context that will be part of the BPSCN message and will ++ * be written to 'message_iova' ++ * @options: Mask of available options; use 'DPBP_NOTIF_OPT_' values ++ */ ++struct dpbp_notification_cfg { ++ uint32_t depletion_entry; ++ uint32_t depletion_exit; ++ uint32_t surplus_entry; ++ uint32_t surplus_exit; ++ uint64_t message_iova; ++ uint64_t message_ctx; ++ uint16_t options; ++}; ++ ++/** ++ * dpbp_set_notifications() - Set notifications towards software ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPBP object ++ * @cfg: notifications configuration ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpbp_set_notifications(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ struct dpbp_notification_cfg *cfg); ++ ++/** ++ * dpbp_get_notifications() - Get the notifications configuration ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPBP object ++ * @cfg: notifications configuration ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpbp_get_notifications(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ struct dpbp_notification_cfg *cfg); ++ ++#endif /* __FSL_DPBP_H */ +diff --git a/drivers/staging/fsl-mc/include/dpcon-cmd.h b/drivers/staging/fsl-mc/include/dpcon-cmd.h +new file mode 100644 +index 0000000..ecb40d0 +--- /dev/null ++++ b/drivers/staging/fsl-mc/include/dpcon-cmd.h +@@ -0,0 +1,162 @@ ++/* Copyright 2013-2015 Freescale Semiconductor Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * * Neither the name of the above-listed copyright holders nor the ++ * names of any contributors may be used to endorse or promote products ++ * derived from this software without specific prior written permission. ++ * ++ * ++ * ALTERNATIVELY, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") as published by the Free Software ++ * Foundation, either version 2 of that License or (at your option) any ++ * later version. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE ++ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++#ifndef _FSL_DPCON_CMD_H ++#define _FSL_DPCON_CMD_H ++ ++/* DPCON Version */ ++#define DPCON_VER_MAJOR 2 ++#define DPCON_VER_MINOR 2 ++ ++/* Command IDs */ ++#define DPCON_CMDID_CLOSE 0x800 ++#define DPCON_CMDID_OPEN 0x808 ++#define DPCON_CMDID_CREATE 0x908 ++#define DPCON_CMDID_DESTROY 0x900 ++ ++#define DPCON_CMDID_ENABLE 0x002 ++#define DPCON_CMDID_DISABLE 0x003 ++#define DPCON_CMDID_GET_ATTR 0x004 ++#define DPCON_CMDID_RESET 0x005 ++#define DPCON_CMDID_IS_ENABLED 0x006 ++ ++#define DPCON_CMDID_SET_IRQ 0x010 ++#define DPCON_CMDID_GET_IRQ 0x011 ++#define DPCON_CMDID_SET_IRQ_ENABLE 0x012 ++#define DPCON_CMDID_GET_IRQ_ENABLE 0x013 ++#define DPCON_CMDID_SET_IRQ_MASK 0x014 ++#define DPCON_CMDID_GET_IRQ_MASK 0x015 ++#define DPCON_CMDID_GET_IRQ_STATUS 0x016 ++#define DPCON_CMDID_CLEAR_IRQ_STATUS 0x017 ++ ++#define DPCON_CMDID_SET_NOTIFICATION 0x100 ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPCON_CMD_OPEN(cmd, dpcon_id) \ ++ MC_CMD_OP(cmd, 0, 0, 32, int, dpcon_id) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPCON_CMD_CREATE(cmd, cfg) \ ++ MC_CMD_OP(cmd, 0, 0, 8, uint8_t, cfg->num_priorities) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPCON_RSP_IS_ENABLED(cmd, en) \ ++ MC_RSP_OP(cmd, 0, 0, 1, int, en) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPCON_CMD_SET_IRQ(cmd, irq_index, irq_cfg) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 0, 8, uint8_t, irq_index);\ ++ MC_CMD_OP(cmd, 0, 32, 32, uint32_t, irq_cfg->val);\ ++ MC_CMD_OP(cmd, 1, 0, 64, uint64_t, irq_cfg->addr);\ ++ MC_CMD_OP(cmd, 2, 0, 32, int, irq_cfg->irq_num); \ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPCON_CMD_GET_IRQ(cmd, irq_index) \ ++ MC_CMD_OP(cmd, 0, 32, 8, uint8_t, irq_index) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPCON_RSP_GET_IRQ(cmd, type, irq_cfg) \ ++do { \ ++ MC_RSP_OP(cmd, 0, 0, 32, uint32_t, irq_cfg->val);\ ++ MC_RSP_OP(cmd, 1, 0, 64, uint64_t, irq_cfg->addr);\ ++ MC_RSP_OP(cmd, 2, 0, 32, int, irq_cfg->irq_num); \ ++ MC_RSP_OP(cmd, 2, 32, 32, int, type);\ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPCON_CMD_SET_IRQ_ENABLE(cmd, irq_index, en) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 0, 8, uint8_t, en); \ ++ MC_CMD_OP(cmd, 0, 32, 8, uint8_t, irq_index);\ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPCON_CMD_GET_IRQ_ENABLE(cmd, irq_index) \ ++ MC_CMD_OP(cmd, 0, 32, 8, uint8_t, irq_index) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPCON_RSP_GET_IRQ_ENABLE(cmd, en) \ ++ MC_RSP_OP(cmd, 0, 0, 8, uint8_t, en) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPCON_CMD_SET_IRQ_MASK(cmd, irq_index, mask) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 0, 32, uint32_t, mask); \ ++ MC_CMD_OP(cmd, 0, 32, 8, uint8_t, irq_index);\ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPCON_CMD_GET_IRQ_MASK(cmd, irq_index) \ ++ MC_CMD_OP(cmd, 0, 32, 8, uint8_t, irq_index) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPCON_RSP_GET_IRQ_MASK(cmd, mask) \ ++ MC_RSP_OP(cmd, 0, 0, 32, uint32_t, mask) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPCON_CMD_GET_IRQ_STATUS(cmd, irq_index, status) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 0, 32, uint32_t, status);\ ++ MC_CMD_OP(cmd, 0, 32, 8, uint8_t, irq_index);\ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPCON_RSP_GET_IRQ_STATUS(cmd, status) \ ++ MC_RSP_OP(cmd, 0, 0, 32, uint32_t, status) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPCON_CMD_CLEAR_IRQ_STATUS(cmd, irq_index, status) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 0, 32, uint32_t, status); \ ++ MC_CMD_OP(cmd, 0, 32, 8, uint8_t, irq_index);\ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPCON_RSP_GET_ATTR(cmd, attr) \ ++do { \ ++ MC_RSP_OP(cmd, 0, 0, 32, int, attr->id);\ ++ MC_RSP_OP(cmd, 0, 32, 16, uint16_t, attr->qbman_ch_id);\ ++ MC_RSP_OP(cmd, 0, 48, 8, uint8_t, attr->num_priorities);\ ++ MC_RSP_OP(cmd, 1, 0, 16, uint16_t, attr->version.major);\ ++ MC_RSP_OP(cmd, 1, 16, 16, uint16_t, attr->version.minor);\ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPCON_CMD_SET_NOTIFICATION(cmd, cfg) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 0, 32, int, cfg->dpio_id);\ ++ MC_CMD_OP(cmd, 0, 32, 8, uint8_t, cfg->priority);\ ++ MC_CMD_OP(cmd, 1, 0, 64, uint64_t, cfg->user_ctx);\ ++} while (0) ++ ++#endif /* _FSL_DPCON_CMD_H */ +diff --git a/drivers/staging/fsl-mc/include/dpcon.h b/drivers/staging/fsl-mc/include/dpcon.h +new file mode 100644 +index 0000000..2555be5 +--- /dev/null ++++ b/drivers/staging/fsl-mc/include/dpcon.h +@@ -0,0 +1,407 @@ ++/* Copyright 2013-2015 Freescale Semiconductor Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * * Neither the name of the above-listed copyright holders nor the ++ * names of any contributors may be used to endorse or promote products ++ * derived from this software without specific prior written permission. ++ * ++ * ++ * ALTERNATIVELY, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") as published by the Free Software ++ * Foundation, either version 2 of that License or (at your option) any ++ * later version. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE ++ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++#ifndef __FSL_DPCON_H ++#define __FSL_DPCON_H ++ ++/* Data Path Concentrator API ++ * Contains initialization APIs and runtime control APIs for DPCON ++ */ ++ ++struct fsl_mc_io; ++ ++/** General DPCON macros */ ++ ++/** ++ * Use it to disable notifications; see dpcon_set_notification() ++ */ ++#define DPCON_INVALID_DPIO_ID (int)(-1) ++ ++/** ++ * dpcon_open() - Open a control session for the specified object ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @dpcon_id: DPCON unique ID ++ * @token: Returned token; use in subsequent API calls ++ * ++ * This function can be used to open a control session for an ++ * already created object; an object may have been declared in ++ * the DPL or by calling the dpcon_create() function. ++ * This function returns a unique authentication token, ++ * associated with the specific object ID and the specific MC ++ * portal; this token must be used in all subsequent commands for ++ * this specific object. ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpcon_open(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ int dpcon_id, ++ uint16_t *token); ++ ++/** ++ * dpcon_close() - Close the control session of the object ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPCON object ++ * ++ * After this function is called, no further operations are ++ * allowed on the object without opening a new control session. ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpcon_close(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token); ++ ++/** ++ * struct dpcon_cfg - Structure representing DPCON configuration ++ * @num_priorities: Number of priorities for the DPCON channel (1-8) ++ */ ++struct dpcon_cfg { ++ uint8_t num_priorities; ++}; ++ ++/** ++ * dpcon_create() - Create the DPCON object. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @cfg: Configuration structure ++ * @token: Returned token; use in subsequent API calls ++ * ++ * Create the DPCON object, allocate required resources and ++ * perform required initialization. ++ * ++ * The object can be created either by declaring it in the ++ * DPL file, or by calling this function. ++ * ++ * This function returns a unique authentication token, ++ * associated with the specific object ID and the specific MC ++ * portal; this token must be used in all subsequent calls to ++ * this specific object. For objects that are created using the ++ * DPL file, call dpcon_open() function to get an authentication ++ * token first. ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpcon_create(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ const struct dpcon_cfg *cfg, ++ uint16_t *token); ++ ++/** ++ * dpcon_destroy() - Destroy the DPCON object and release all its resources. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPCON object ++ * ++ * Return: '0' on Success; error code otherwise. ++ */ ++int dpcon_destroy(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token); ++ ++/** ++ * dpcon_enable() - Enable the DPCON ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPCON object ++ * ++ * Return: '0' on Success; Error code otherwise ++ */ ++int dpcon_enable(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token); ++ ++/** ++ * dpcon_disable() - Disable the DPCON ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPCON object ++ * ++ * Return: '0' on Success; Error code otherwise ++ */ ++int dpcon_disable(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token); ++ ++/** ++ * dpcon_is_enabled() - Check if the DPCON is enabled. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPCON object ++ * @en: Returns '1' if object is enabled; '0' otherwise ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpcon_is_enabled(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int *en); ++ ++/** ++ * dpcon_reset() - Reset the DPCON, returns the object to initial state. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPCON object ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpcon_reset(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token); ++ ++/** ++ * struct dpcon_irq_cfg - IRQ configuration ++ * @addr: Address that must be written to signal a message-based interrupt ++ * @val: Value to write into irq_addr address ++ * @irq_num: A user defined number associated with this IRQ ++ */ ++struct dpcon_irq_cfg { ++ uint64_t addr; ++ uint32_t val; ++ int irq_num; ++}; ++ ++/** ++ * dpcon_set_irq() - Set IRQ information for the DPCON to trigger an interrupt. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPCON object ++ * @irq_index: Identifies the interrupt index to configure ++ * @irq_cfg: IRQ configuration ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpcon_set_irq(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ struct dpcon_irq_cfg *irq_cfg); ++ ++/** ++ * dpcon_get_irq() - Get IRQ information from the DPCON. ++ * ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPCON object ++ * @irq_index: The interrupt index to configure ++ * @type: Interrupt type: 0 represents message interrupt ++ * type (both irq_addr and irq_val are valid) ++ * @irq_cfg: IRQ attributes ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpcon_get_irq(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ int *type, ++ struct dpcon_irq_cfg *irq_cfg); ++ ++/** ++ * dpcon_set_irq_enable() - Set overall interrupt state. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPCON object ++ * @irq_index: The interrupt index to configure ++ * @en: Interrupt state - enable = 1, disable = 0 ++ * ++ * Allows GPP software to control when interrupts are generated. ++ * Each interrupt can have up to 32 causes. The enable/disable control's the ++ * overall interrupt state. if the interrupt is disabled no causes will cause ++ * an interrupt. ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpcon_set_irq_enable(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint8_t en); ++ ++/** ++ * dpcon_get_irq_enable() - Get overall interrupt state. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPCON object ++ * @irq_index: The interrupt index to configure ++ * @en: Returned interrupt state - enable = 1, disable = 0 ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpcon_get_irq_enable(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint8_t *en); ++ ++/** ++ * dpcon_set_irq_mask() - Set interrupt mask. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPCON object ++ * @irq_index: The interrupt index to configure ++ * @mask: Event mask to trigger interrupt; ++ * each bit: ++ * 0 = ignore event ++ * 1 = consider event for asserting IRQ ++ * ++ * Every interrupt can have up to 32 causes and the interrupt model supports ++ * masking/unmasking each cause independently ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpcon_set_irq_mask(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint32_t mask); ++ ++/** ++ * dpcon_get_irq_mask() - Get interrupt mask. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPCON object ++ * @irq_index: The interrupt index to configure ++ * @mask: Returned event mask to trigger interrupt ++ * ++ * Every interrupt can have up to 32 causes and the interrupt model supports ++ * masking/unmasking each cause independently ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpcon_get_irq_mask(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint32_t *mask); ++ ++/** ++ * dpcon_get_irq_status() - Get the current status of any pending interrupts. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPCON object ++ * @irq_index: The interrupt index to configure ++ * @status: interrupts status - one bit per cause: ++ * 0 = no interrupt pending ++ * 1 = interrupt pending ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpcon_get_irq_status(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint32_t *status); ++ ++/** ++ * dpcon_clear_irq_status() - Clear a pending interrupt's status ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPCON object ++ * @irq_index: The interrupt index to configure ++ * @status: bits to clear (W1C) - one bit per cause: ++ * 0 = don't change ++ * 1 = clear status bit ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpcon_clear_irq_status(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint32_t status); ++ ++/** ++ * struct dpcon_attr - Structure representing DPCON attributes ++ * @id: DPCON object ID ++ * @version: DPCON version ++ * @qbman_ch_id: Channel ID to be used by dequeue operation ++ * @num_priorities: Number of priorities for the DPCON channel (1-8) ++ */ ++struct dpcon_attr { ++ int id; ++ /** ++ * struct version - DPCON version ++ * @major: DPCON major version ++ * @minor: DPCON minor version ++ */ ++ struct { ++ uint16_t major; ++ uint16_t minor; ++ } version; ++ uint16_t qbman_ch_id; ++ uint8_t num_priorities; ++}; ++ ++/** ++ * dpcon_get_attributes() - Retrieve DPCON attributes. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPCON object ++ * @attr: Object's attributes ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpcon_get_attributes(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ struct dpcon_attr *attr); ++ ++/** ++ * struct dpcon_notification_cfg - Structure representing notification parameters ++ * @dpio_id: DPIO object ID; must be configured with a notification channel; ++ * to disable notifications set it to 'DPCON_INVALID_DPIO_ID'; ++ * @priority: Priority selection within the DPIO channel; valid values ++ * are 0-7, depending on the number of priorities in that channel ++ * @user_ctx: User context value provided with each CDAN message ++ */ ++struct dpcon_notification_cfg { ++ int dpio_id; ++ uint8_t priority; ++ uint64_t user_ctx; ++}; ++ ++/** ++ * dpcon_set_notification() - Set DPCON notification destination ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPCON object ++ * @cfg: Notification parameters ++ * ++ * Return: '0' on Success; Error code otherwise ++ */ ++int dpcon_set_notification(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ struct dpcon_notification_cfg *cfg); ++ ++#endif /* __FSL_DPCON_H */ +diff --git a/drivers/staging/fsl-mc/include/dpmac-cmd.h b/drivers/staging/fsl-mc/include/dpmac-cmd.h +new file mode 100644 +index 0000000..c123aab +--- /dev/null ++++ b/drivers/staging/fsl-mc/include/dpmac-cmd.h +@@ -0,0 +1,192 @@ ++/* Copyright 2013-2015 Freescale Semiconductor Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * * Neither the name of the above-listed copyright holders nor the ++ * names of any contributors may be used to endorse or promote products ++ * derived from this software without specific prior written permission. ++ * ++ * ++ * ALTERNATIVELY, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") as published by the Free Software ++ * Foundation, either version 2 of that License or (at your option) any ++ * later version. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE ++ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++#ifndef _FSL_DPMAC_CMD_H ++#define _FSL_DPMAC_CMD_H ++ ++/* DPMAC Version */ ++#define DPMAC_VER_MAJOR 3 ++#define DPMAC_VER_MINOR 0 ++ ++/* Command IDs */ ++#define DPMAC_CMDID_CLOSE 0x800 ++#define DPMAC_CMDID_OPEN 0x80c ++#define DPMAC_CMDID_CREATE 0x90c ++#define DPMAC_CMDID_DESTROY 0x900 ++ ++#define DPMAC_CMDID_GET_ATTR 0x004 ++#define DPMAC_CMDID_RESET 0x005 ++ ++#define DPMAC_CMDID_SET_IRQ 0x010 ++#define DPMAC_CMDID_GET_IRQ 0x011 ++#define DPMAC_CMDID_SET_IRQ_ENABLE 0x012 ++#define DPMAC_CMDID_GET_IRQ_ENABLE 0x013 ++#define DPMAC_CMDID_SET_IRQ_MASK 0x014 ++#define DPMAC_CMDID_GET_IRQ_MASK 0x015 ++#define DPMAC_CMDID_GET_IRQ_STATUS 0x016 ++#define DPMAC_CMDID_CLEAR_IRQ_STATUS 0x017 ++ ++#define DPMAC_CMDID_MDIO_READ 0x0c0 ++#define DPMAC_CMDID_MDIO_WRITE 0x0c1 ++#define DPMAC_CMDID_GET_LINK_CFG 0x0c2 ++#define DPMAC_CMDID_SET_LINK_STATE 0x0c3 ++#define DPMAC_CMDID_GET_COUNTER 0x0c4 ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPMAC_CMD_CREATE(cmd, cfg) \ ++ MC_CMD_OP(cmd, 0, 0, 32, int, cfg->mac_id) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPMAC_CMD_OPEN(cmd, dpmac_id) \ ++ MC_CMD_OP(cmd, 0, 0, 32, int, dpmac_id) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPMAC_CMD_SET_IRQ(cmd, irq_index, irq_addr, irq_val, user_irq_id) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 0, 8, uint8_t, irq_index);\ ++ MC_CMD_OP(cmd, 0, 32, 32, uint32_t, irq_val);\ ++ MC_CMD_OP(cmd, 1, 0, 64, uint64_t, irq_addr); \ ++ MC_CMD_OP(cmd, 2, 0, 32, int, user_irq_id); \ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPMAC_CMD_GET_IRQ(cmd, irq_index) \ ++ MC_CMD_OP(cmd, 0, 32, 8, uint8_t, irq_index) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPMAC_RSP_GET_IRQ(cmd, type, irq_addr, irq_val, user_irq_id) \ ++do { \ ++ MC_RSP_OP(cmd, 0, 0, 32, uint32_t, irq_val); \ ++ MC_RSP_OP(cmd, 1, 0, 64, uint64_t, irq_addr); \ ++ MC_RSP_OP(cmd, 2, 0, 32, int, user_irq_id); \ ++ MC_RSP_OP(cmd, 2, 32, 32, int, type); \ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPMAC_CMD_SET_IRQ_ENABLE(cmd, irq_index, en) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 0, 8, uint8_t, en); \ ++ MC_CMD_OP(cmd, 0, 32, 8, uint8_t, irq_index);\ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPMAC_CMD_GET_IRQ_ENABLE(cmd, irq_index) \ ++ MC_CMD_OP(cmd, 0, 32, 8, uint8_t, irq_index) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPMAC_RSP_GET_IRQ_ENABLE(cmd, en) \ ++ MC_RSP_OP(cmd, 0, 0, 8, uint8_t, en) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPMAC_CMD_SET_IRQ_MASK(cmd, irq_index, mask) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 0, 32, uint32_t, mask);\ ++ MC_CMD_OP(cmd, 0, 32, 8, uint8_t, irq_index);\ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPMAC_CMD_GET_IRQ_MASK(cmd, irq_index) \ ++ MC_CMD_OP(cmd, 0, 32, 8, uint8_t, irq_index) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPMAC_RSP_GET_IRQ_MASK(cmd, mask) \ ++ MC_RSP_OP(cmd, 0, 0, 32, uint32_t, mask) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPMAC_CMD_GET_IRQ_STATUS(cmd, irq_index) \ ++ MC_CMD_OP(cmd, 0, 32, 8, uint8_t, irq_index) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPMAC_RSP_GET_IRQ_STATUS(cmd, status) \ ++ MC_RSP_OP(cmd, 0, 0, 32, uint32_t, status) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPMAC_CMD_CLEAR_IRQ_STATUS(cmd, irq_index, status) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 0, 32, uint32_t, status); \ ++ MC_CMD_OP(cmd, 0, 32, 8, uint8_t, irq_index);\ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPMAC_RSP_GET_ATTRIBUTES(cmd, attr) \ ++do { \ ++ MC_RSP_OP(cmd, 0, 0, 32, int, attr->phy_id);\ ++ MC_RSP_OP(cmd, 0, 32, 32, int, attr->id);\ ++ MC_RSP_OP(cmd, 1, 0, 16, uint16_t, attr->version.major);\ ++ MC_RSP_OP(cmd, 1, 16, 16, uint16_t, attr->version.minor);\ ++ MC_RSP_OP(cmd, 1, 32, 8, enum dpmac_link_type, attr->link_type);\ ++ MC_RSP_OP(cmd, 1, 40, 8, enum dpmac_eth_if, attr->eth_if);\ ++ MC_RSP_OP(cmd, 2, 0, 32, uint32_t, attr->max_rate);\ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPMAC_CMD_MDIO_READ(cmd, cfg) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 0, 8, uint8_t, cfg->phy_addr); \ ++ MC_CMD_OP(cmd, 0, 8, 8, uint8_t, cfg->reg); \ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPMAC_RSP_MDIO_READ(cmd, data) \ ++ MC_RSP_OP(cmd, 0, 16, 16, uint16_t, data) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPMAC_CMD_MDIO_WRITE(cmd, cfg) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 0, 8, uint8_t, cfg->phy_addr); \ ++ MC_CMD_OP(cmd, 0, 8, 8, uint8_t, cfg->reg); \ ++ MC_CMD_OP(cmd, 0, 16, 16, uint16_t, cfg->data); \ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPMAC_RSP_GET_LINK_CFG(cmd, cfg) \ ++do { \ ++ MC_RSP_OP(cmd, 0, 0, 64, uint64_t, cfg->options); \ ++ MC_RSP_OP(cmd, 1, 0, 32, uint32_t, cfg->rate); \ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPMAC_CMD_SET_LINK_STATE(cmd, cfg) \ ++do { \ ++ MC_CMD_OP(cmd, 0, 0, 64, uint64_t, cfg->options); \ ++ MC_CMD_OP(cmd, 1, 0, 32, uint32_t, cfg->rate); \ ++ MC_CMD_OP(cmd, 2, 0, 1, int, cfg->up); \ ++} while (0) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPMAC_CMD_GET_COUNTER(cmd, type) \ ++ MC_CMD_OP(cmd, 0, 0, 8, enum dpmac_counter, type) ++ ++/* cmd, param, offset, width, type, arg_name */ ++#define DPMAC_RSP_GET_COUNTER(cmd, counter) \ ++ MC_RSP_OP(cmd, 1, 0, 64, uint64_t, counter) ++ ++#endif /* _FSL_DPMAC_CMD_H */ +diff --git a/drivers/staging/fsl-mc/include/dpmac.h b/drivers/staging/fsl-mc/include/dpmac.h +new file mode 100644 +index 0000000..88091b5 +--- /dev/null ++++ b/drivers/staging/fsl-mc/include/dpmac.h +@@ -0,0 +1,528 @@ ++/* Copyright 2013-2015 Freescale Semiconductor Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * * Neither the name of the above-listed copyright holders nor the ++ * names of any contributors may be used to endorse or promote products ++ * derived from this software without specific prior written permission. ++ * ++ * ++ * ALTERNATIVELY, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") as published by the Free Software ++ * Foundation, either version 2 of that License or (at your option) any ++ * later version. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE ++ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++#ifndef __FSL_DPMAC_H ++#define __FSL_DPMAC_H ++ ++/* Data Path MAC API ++ * Contains initialization APIs and runtime control APIs for DPMAC ++ */ ++ ++struct fsl_mc_io; ++ ++/** ++ * dpmac_open() - Open a control session for the specified object. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @dpmac_id: DPMAC unique ID ++ * @token: Returned token; use in subsequent API calls ++ * ++ * This function can be used to open a control session for an ++ * already created object; an object may have been declared in ++ * the DPL or by calling the dpmac_create function. ++ * This function returns a unique authentication token, ++ * associated with the specific object ID and the specific MC ++ * portal; this token must be used in all subsequent commands for ++ * this specific object ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpmac_open(struct fsl_mc_io *mc_io, int dpmac_id, uint16_t *token); ++ ++/** ++ * dpmac_close() - Close the control session of the object ++ * @mc_io: Pointer to MC portal's I/O object ++ * @token: Token of DPMAC object ++ * ++ * After this function is called, no further operations are ++ * allowed on the object without opening a new control session. ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpmac_close(struct fsl_mc_io *mc_io, uint16_t token); ++ ++/** ++ * enum dpmac_link_type - DPMAC link type ++ * @DPMAC_LINK_TYPE_NONE: No link ++ * @DPMAC_LINK_TYPE_FIXED: Link is fixed type ++ * @DPMAC_LINK_TYPE_PHY: Link by PHY ID ++ * @DPMAC_LINK_TYPE_BACKPLANE: Backplane link type ++ */ ++enum dpmac_link_type { ++ DPMAC_LINK_TYPE_NONE, ++ DPMAC_LINK_TYPE_FIXED, ++ DPMAC_LINK_TYPE_PHY, ++ DPMAC_LINK_TYPE_BACKPLANE ++}; ++ ++/** ++ * enum dpmac_eth_if - DPMAC Ethrnet interface ++ * @DPMAC_ETH_IF_MII: MII interface ++ * @DPMAC_ETH_IF_RMII: RMII interface ++ * @DPMAC_ETH_IF_SMII: SMII interface ++ * @DPMAC_ETH_IF_GMII: GMII interface ++ * @DPMAC_ETH_IF_RGMII: RGMII interface ++ * @DPMAC_ETH_IF_SGMII: SGMII interface ++ * @DPMAC_ETH_IF_XGMII: XGMII interface ++ * @DPMAC_ETH_IF_QSGMII: QSGMII interface ++ * @DPMAC_ETH_IF_XAUI: XAUI interface ++ * @DPMAC_ETH_IF_XFI: XFI interface ++ */ ++enum dpmac_eth_if { ++ DPMAC_ETH_IF_MII, ++ DPMAC_ETH_IF_RMII, ++ DPMAC_ETH_IF_SMII, ++ DPMAC_ETH_IF_GMII, ++ DPMAC_ETH_IF_RGMII, ++ DPMAC_ETH_IF_SGMII, ++ DPMAC_ETH_IF_XGMII, ++ DPMAC_ETH_IF_QSGMII, ++ DPMAC_ETH_IF_XAUI, ++ DPMAC_ETH_IF_XFI ++}; ++ ++/** ++ * struct dpmac_cfg() - Structure representing DPMAC configuration ++ * @mac_id: Represents the Hardware MAC ID; in case of multiple WRIOP, ++ * the MAC IDs are continuous. ++ * For example: 2 WRIOPs, 16 MACs in each: ++ * MAC IDs for the 1st WRIOP: 1-16, ++ * MAC IDs for the 2nd WRIOP: 17-32. ++ */ ++struct dpmac_cfg { ++ int mac_id; ++}; ++ ++/** ++ * dpmac_create() - Create the DPMAC object. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cfg: Configuration structure ++ * @token: Returned token; use in subsequent API calls ++ * ++ * Create the DPMAC object, allocate required resources and ++ * perform required initialization. ++ * ++ * The object can be created either by declaring it in the ++ * DPL file, or by calling this function. ++ * This function returns a unique authentication token, ++ * associated with the specific object ID and the specific MC ++ * portal; this token must be used in all subsequent calls to ++ * this specific object. For objects that are created using the ++ * DPL file, call dpmac_open function to get an authentication ++ * token first. ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpmac_create(struct fsl_mc_io *mc_io, ++ const struct dpmac_cfg *cfg, ++ uint16_t *token); ++ ++/** ++ * dpmac_destroy() - Destroy the DPMAC object and release all its resources. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @token: Token of DPMAC object ++ * ++ * Return: '0' on Success; error code otherwise. ++ */ ++int dpmac_destroy(struct fsl_mc_io *mc_io, uint16_t token); ++ ++/* DPMAC IRQ Index and Events */ ++ ++/* IRQ index */ ++#define DPMAC_IRQ_INDEX 0 ++/* IRQ event - indicates a change in link state */ ++#define DPMAC_IRQ_EVENT_LINK_CFG_REQ 0x00000001 ++/* irq event - Indicates that the link state changed */ ++#define DPMAC_IRQ_EVENT_LINK_CHANGED 0x00000002 ++ ++/** ++ * dpmac_set_irq() - Set IRQ information for the DPMAC to trigger an interrupt. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @token: Token of DPMAC object ++ * @irq_index: Identifies the interrupt index to configure ++ * @irq_addr: Address that must be written to ++ * signal a message-based interrupt ++ * @irq_val: Value to write into irq_addr address ++ * @user_irq_id: A user defined number associated with this IRQ ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpmac_set_irq(struct fsl_mc_io *mc_io, ++ uint16_t token, ++ uint8_t irq_index, ++ uint64_t irq_addr, ++ uint32_t irq_val, ++ int user_irq_id); ++ ++/** ++ * dpmac_get_irq() - Get IRQ information from the DPMAC. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @token: Token of DPMAC object ++ * @irq_index: The interrupt index to configure ++ * @type: Interrupt type: 0 represents message interrupt ++ * type (both irq_addr and irq_val are valid) ++ * @irq_addr: Returned address that must be written to ++ * signal the message-based interrupt ++ * @irq_val: Value to write into irq_addr address ++ * @user_irq_id: A user defined number associated with this IRQ ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpmac_get_irq(struct fsl_mc_io *mc_io, ++ uint16_t token, ++ uint8_t irq_index, ++ int *type, ++ uint64_t *irq_addr, ++ uint32_t *irq_val, ++ int *user_irq_id); ++ ++/** ++ * dpmac_set_irq_enable() - Set overall interrupt state. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @token: Token of DPMAC object ++ * @irq_index: The interrupt index to configure ++ * @en: Interrupt state - enable = 1, disable = 0 ++ * ++ * Allows GPP software to control when interrupts are generated. ++ * Each interrupt can have up to 32 causes. The enable/disable control's the ++ * overall interrupt state. if the interrupt is disabled no causes will cause ++ * an interrupt. ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpmac_set_irq_enable(struct fsl_mc_io *mc_io, ++ uint16_t token, ++ uint8_t irq_index, ++ uint8_t en); ++ ++/** ++ * dpmac_get_irq_enable() - Get overall interrupt state ++ * @mc_io: Pointer to MC portal's I/O object ++ * @token: Token of DPMAC object ++ * @irq_index: The interrupt index to configure ++ * @en: Returned interrupt state - enable = 1, disable = 0 ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpmac_get_irq_enable(struct fsl_mc_io *mc_io, ++ uint16_t token, ++ uint8_t irq_index, ++ uint8_t *en); ++ ++/** ++ * dpmac_set_irq_mask() - Set interrupt mask. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @token: Token of DPMAC object ++ * @irq_index: The interrupt index to configure ++ * @mask: Event mask to trigger interrupt; ++ * each bit: ++ * 0 = ignore event ++ * 1 = consider event for asserting IRQ ++ * ++ * Every interrupt can have up to 32 causes and the interrupt model supports ++ * masking/unmasking each cause independently ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpmac_set_irq_mask(struct fsl_mc_io *mc_io, ++ uint16_t token, ++ uint8_t irq_index, ++ uint32_t mask); ++ ++/** ++ * dpmac_get_irq_mask() - Get interrupt mask. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @token: Token of DPMAC object ++ * @irq_index: The interrupt index to configure ++ * @mask: Returned event mask to trigger interrupt ++ * ++ * Every interrupt can have up to 32 causes and the interrupt model supports ++ * masking/unmasking each cause independently ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpmac_get_irq_mask(struct fsl_mc_io *mc_io, ++ uint16_t token, ++ uint8_t irq_index, ++ uint32_t *mask); ++ ++/** ++ * dpmac_get_irq_status() - Get the current status of any pending interrupts. ++ * ++ * @mc_io: Pointer to MC portal's I/O object ++ * @token: Token of DPMAC object ++ * @irq_index: The interrupt index to configure ++ * @status: Returned interrupts status - one bit per cause: ++ * 0 = no interrupt pending ++ * 1 = interrupt pending ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpmac_get_irq_status(struct fsl_mc_io *mc_io, ++ uint16_t token, ++ uint8_t irq_index, ++ uint32_t *status); ++ ++/** ++ * dpmac_clear_irq_status() - Clear a pending interrupt's status ++ * ++ * @mc_io: Pointer to MC portal's I/O object ++ * @token: Token of DPMAC object ++ * @irq_index: The interrupt index to configure ++ * @status: Bits to clear (W1C) - one bit per cause: ++ * 0 = don't change ++ * 1 = clear status bit ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpmac_clear_irq_status(struct fsl_mc_io *mc_io, ++ uint16_t token, ++ uint8_t irq_index, ++ uint32_t status); ++ ++/** ++ * struct dpmac_attr - Structure representing DPMAC attributes ++ * @id: DPMAC object ID ++ * @phy_id: PHY ID ++ * @link_type: link type ++ * @eth_if: Ethernet interface ++ * @max_rate: Maximum supported rate - in Mbps ++ * @version: DPMAC version ++ */ ++struct dpmac_attr { ++ int id; ++ int phy_id; ++ enum dpmac_link_type link_type; ++ enum dpmac_eth_if eth_if; ++ uint32_t max_rate; ++ /** ++ * struct version - Structure representing DPMAC version ++ * @major: DPMAC major version ++ * @minor: DPMAC minor version ++ */ ++ struct { ++ uint16_t major; ++ uint16_t minor; ++ } version; ++}; ++ ++/** ++ * dpmac_get_attributes - Retrieve DPMAC attributes. ++ * ++ * @mc_io: Pointer to MC portal's I/O object ++ * @token: Token of DPMAC object ++ * @attr: Returned object's attributes ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpmac_get_attributes(struct fsl_mc_io *mc_io, ++ uint16_t token, ++ struct dpmac_attr *attr); ++ ++/** ++ * struct dpmac_mdio_cfg - DPMAC MDIO read/write parameters ++ * @phy_addr: MDIO device address ++ * @reg: Address of the register within the Clause 45 PHY device from which data ++ * is to be read ++ * @data: Data read/write from/to MDIO ++ */ ++struct dpmac_mdio_cfg { ++ uint8_t phy_addr; ++ uint8_t reg; ++ uint16_t data; ++}; ++ ++/** ++ * dpmac_mdio_read() - Perform MDIO read transaction ++ * @mc_io: Pointer to opaque I/O object ++ * @token: Token of DPMAC object ++ * @cfg: Structure with MDIO transaction parameters ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpmac_mdio_read(struct fsl_mc_io *mc_io, uint16_t token, ++ struct dpmac_mdio_cfg *cfg); ++ ++ ++/** ++ * dpmac_mdio_write() - Perform MDIO write transaction ++ * @mc_io: Pointer to opaque I/O object ++ * @token: Token of DPMAC object ++ * @cfg: Structure with MDIO transaction parameters ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpmac_mdio_write(struct fsl_mc_io *mc_io, uint16_t token, ++ struct dpmac_mdio_cfg *cfg); ++ ++/* DPMAC link configuration/state options */ ++ ++/* Enable auto-negotiation */ ++#define DPMAC_LINK_OPT_AUTONEG 0x0000000000000001ULL ++/* Enable half-duplex mode */ ++#define DPMAC_LINK_OPT_HALF_DUPLEX 0x0000000000000002ULL ++/* Enable pause frames */ ++#define DPMAC_LINK_OPT_PAUSE 0x0000000000000004ULL ++/* Enable a-symmetric pause frames */ ++#define DPMAC_LINK_OPT_ASYM_PAUSE 0x0000000000000008ULL ++ ++/** ++ * struct dpmac_link_cfg - Structure representing DPMAC link configuration ++ * @rate: Link's rate - in Mbps ++ * @options: Enable/Disable DPMAC link cfg features (bitmap) ++ */ ++struct dpmac_link_cfg { ++ uint32_t rate; ++ uint64_t options; ++}; ++ ++/** ++ * dpmac_get_link_cfg() - Get Ethernet link configuration ++ * @mc_io: Pointer to opaque I/O object ++ * @token: Token of DPMAC object ++ * @cfg: Returned structure with the link configuration ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpmac_get_link_cfg(struct fsl_mc_io *mc_io, uint16_t token, ++ struct dpmac_link_cfg *cfg); ++ ++/** ++ * struct dpmac_link_state - DPMAC link configuration request ++ * @rate: Rate in Mbps ++ * @options: Enable/Disable DPMAC link cfg features (bitmap) ++ * @up: Link state ++ */ ++struct dpmac_link_state { ++ uint32_t rate; ++ uint64_t options; ++ int up; ++}; ++ ++/** ++ * dpmac_set_link_state() - Set the Ethernet link status ++ * @mc_io: Pointer to opaque I/O object ++ * @token: Token of DPMAC object ++ * @link_state: Link state configuration ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpmac_set_link_state(struct fsl_mc_io *mc_io, uint16_t token, ++ struct dpmac_link_state *link_state); ++ ++/** ++ * enum dpni_counter - DPNI counter types ++ * @DPMAC_CNT_ING_FRAME_64: counts 64-octet frame, good or bad. ++ * @DPMAC_CNT_ING_FRAME_127: counts 65- to 127-octet frame, good or bad. ++ * @DPMAC_CNT_ING_FRAME_255: counts 128- to 255-octet frame, good or bad. ++ * @DPMAC_CNT_ING_FRAME_511: counts 256- to 511-octet frame, good or bad. ++ * @DPMAC_CNT_ING_FRAME_1023: counts 512- to 1023-octet frame, good or bad. ++ * @DPMAC_CNT_ING_FRAME_1518: counts 1024- to 1518-octet frame, good or bad. ++ * @DPMAC_CNT_ING_FRAME_1519_MAX: counts 1519-octet frame and larger ++ * (up to max frame length specified), ++ * good or bad. ++ * @DPMAC_CNT_ING_FRAG: counts packet which is shorter than 64 octets received ++ * with a wrong CRC ++ * @DPMAC_CNT_ING_JABBER: counts packet longer than the maximum frame length ++ * specified, with a bad frame check sequence. ++ * @DPMAC_CNT_ING_FRAME_DISCARD: counts dropped packet due to internal errors. ++ * Occurs when a receive FIFO overflows. ++ * Includes also packets truncated as a result of ++ * the receive FIFO overflow. ++ * @DPMAC_CNT_ING_ALIGN_ERR: counts frame with an alignment error ++ * (optional used for wrong SFD) ++ * @DPMAC_CNT_EGR_UNDERSIZED: counts packet transmitted that was less than 64 ++ * octets long with a good CRC. ++ * @DPMAC_CNT_ING_OVERSIZED: counts packet longer than the maximum frame length ++ * specified, with a good frame check sequence. ++ * @DPMAC_CNT_ING_VALID_PAUSE_FRAME: counts valid pause frame (regular and PFC). ++ * @DPMAC_CNT_EGR_VALID_PAUSE_FRAME: counts valid pause frame transmitted ++ * (regular and PFC). ++ * @DPMAC_CNT_ING_BYTE: counts octet received except preamble for all valid ++ frames and valid pause frames. ++ * @DPMAC_CNT_ING_MCAST_FRAME: counts received multicast frame ++ * @DPMAC_CNT_ING_BCAST_FRAME: counts received broadcast frame ++ * @DPMAC_CNT_ING_ALL_FRAME: counts each good or bad packet received. ++ * @DPMAC_CNT_ING_UCAST_FRAME: counts received unicast frame ++ * @DPMAC_CNT_ING_ERR_FRAME: counts frame received with an error ++ * (except for undersized/fragment frame) ++ * @DPMAC_CNT_EGR_BYTE: counts octet transmitted except preamble for all valid ++ * frames and valid pause frames transmitted. ++ * @DPMAC_CNT_EGR_MCAST_FRAME: counts transmitted multicast frame ++ * @DPMAC_CNT_EGR_BCAST_FRAME: counts transmitted broadcast frame ++ * @DPMAC_CNT_EGR_UCAST_FRAME: counts transmitted unicast frame ++ * @DPMAC_CNT_EGR_ERR_FRAME: counts frame transmitted with an error ++ * @DPMAC_CNT_ING_GOOD_FRAME: counts frame received without error, including ++ * pause frames. ++ */ ++enum dpmac_counter { ++ DPMAC_CNT_ING_FRAME_64, ++ DPMAC_CNT_ING_FRAME_127, ++ DPMAC_CNT_ING_FRAME_255, ++ DPMAC_CNT_ING_FRAME_511, ++ DPMAC_CNT_ING_FRAME_1023, ++ DPMAC_CNT_ING_FRAME_1518, ++ DPMAC_CNT_ING_FRAME_1519_MAX, ++ DPMAC_CNT_ING_FRAG, ++ DPMAC_CNT_ING_JABBER, ++ DPMAC_CNT_ING_FRAME_DISCARD, ++ DPMAC_CNT_ING_ALIGN_ERR, ++ DPMAC_CNT_EGR_UNDERSIZED, ++ DPMAC_CNT_ING_OVERSIZED, ++ DPMAC_CNT_ING_VALID_PAUSE_FRAME, ++ DPMAC_CNT_EGR_VALID_PAUSE_FRAME, ++ DPMAC_CNT_ING_BYTE, ++ DPMAC_CNT_ING_MCAST_FRAME, ++ DPMAC_CNT_ING_BCAST_FRAME, ++ DPMAC_CNT_ING_ALL_FRAME, ++ DPMAC_CNT_ING_UCAST_FRAME, ++ DPMAC_CNT_ING_ERR_FRAME, ++ DPMAC_CNT_EGR_BYTE, ++ DPMAC_CNT_EGR_MCAST_FRAME, ++ DPMAC_CNT_EGR_BCAST_FRAME, ++ DPMAC_CNT_EGR_UCAST_FRAME, ++ DPMAC_CNT_EGR_ERR_FRAME, ++ DPMAC_CNT_ING_GOOD_FRAME ++}; ++ ++/** ++ * dpmac_get_counter() - Read a specific DPMAC counter ++ * @mc_io: Pointer to opaque I/O object ++ * @token: Token of DPMAC object ++ * @type: The requested counter ++ * @counter: Returned counter value ++ * ++ * Return: The requested counter; '0' otherwise. ++ */ ++int dpmac_get_counter(struct fsl_mc_io *mc_io, uint16_t token, ++ enum dpmac_counter type, ++ uint64_t *counter); ++ ++#endif /* __FSL_DPMAC_H */ +diff --git a/drivers/staging/fsl-mc/include/dpmng.h b/drivers/staging/fsl-mc/include/dpmng.h +new file mode 100644 +index 0000000..d1c4588 +--- /dev/null ++++ b/drivers/staging/fsl-mc/include/dpmng.h +@@ -0,0 +1,80 @@ ++/* Copyright 2013-2015 Freescale Semiconductor Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * * Neither the name of the above-listed copyright holders nor the ++ * names of any contributors may be used to endorse or promote products ++ * derived from this software without specific prior written permission. ++ * ++ * ++ * ALTERNATIVELY, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") as published by the Free Software ++ * Foundation, either version 2 of that License or (at your option) any ++ * later version. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE ++ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++#ifndef __FSL_DPMNG_H ++#define __FSL_DPMNG_H ++ ++/* Management Complex General API ++ * Contains general API for the Management Complex firmware ++ */ ++ ++struct fsl_mc_io; ++ ++/** ++ * struct mc_version ++ * @major: Major version number: incremented on API compatibility changes ++ * @minor: Minor version number: incremented on API additions (that are ++ * backward compatible); reset when major version is incremented ++ * @revision: Internal revision number: incremented on implementation changes ++ * and/or bug fixes that have no impact on API ++ */ ++struct mc_version { ++ uint32_t major; ++ uint32_t minor; ++ uint32_t revision; ++}; ++ ++/** ++ * mc_get_version() - Retrieves the Management Complex firmware ++ * version information ++ * @mc_io: Pointer to opaque I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @mc_ver_info: Returned version information structure ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int mc_get_version(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ struct mc_version *mc_ver_info); ++ ++/** ++ * dpmng_get_container_id() - Get container ID associated with a given portal. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @container_id: Requested container ID ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dpmng_get_container_id(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ int *container_id); ++ ++#endif /* __FSL_DPMNG_H */ +diff --git a/drivers/staging/fsl-mc/include/dprc.h b/drivers/staging/fsl-mc/include/dprc.h +new file mode 100644 +index 0000000..810ded0 +--- /dev/null ++++ b/drivers/staging/fsl-mc/include/dprc.h +@@ -0,0 +1,990 @@ ++/* Copyright 2013-2015 Freescale Semiconductor Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * * Neither the name of the above-listed copyright holders nor the ++ * names of any contributors may be used to endorse or promote products ++ * derived from this software without specific prior written permission. ++ * ++ * ++ * ALTERNATIVELY, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") as published by the Free Software ++ * Foundation, either version 2 of that License or (at your option) any ++ * later version. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE ++ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++#ifndef _FSL_DPRC_H ++#define _FSL_DPRC_H ++ ++#include "mc-cmd.h" ++ ++/* Data Path Resource Container API ++ * Contains DPRC API for managing and querying DPAA resources ++ */ ++ ++struct fsl_mc_io; ++ ++/** ++ * Set this value as the icid value in dprc_cfg structure when creating a ++ * container, in case the ICID is not selected by the user and should be ++ * allocated by the DPRC from the pool of ICIDs. ++ */ ++#define DPRC_GET_ICID_FROM_POOL (uint16_t)(~(0)) ++ ++/** ++ * Set this value as the portal_id value in dprc_cfg structure when creating a ++ * container, in case the portal ID is not specifically selected by the ++ * user and should be allocated by the DPRC from the pool of portal ids. ++ */ ++#define DPRC_GET_PORTAL_ID_FROM_POOL (int)(~(0)) ++ ++/** ++ * dprc_open() - Open DPRC object for use ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @container_id: Container ID to open ++ * @token: Returned token of DPRC object ++ * ++ * Return: '0' on Success; Error code otherwise. ++ * ++ * @warning Required before any operation on the object. ++ */ ++int dprc_open(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ int container_id, ++ uint16_t *token); ++ ++/** ++ * dprc_close() - Close the control session of the object ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPRC object ++ * ++ * After this function is called, no further operations are ++ * allowed on the object without opening a new control session. ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dprc_close(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token); ++ ++/** ++ * Container general options ++ * ++ * These options may be selected at container creation by the container creator ++ * and can be retrieved using dprc_get_attributes() ++ */ ++ ++/* Spawn Policy Option allowed - Indicates that the new container is allowed ++ * to spawn and have its own child containers. ++ */ ++#define DPRC_CFG_OPT_SPAWN_ALLOWED 0x00000001 ++ ++/* General Container allocation policy - Indicates that the new container is ++ * allowed to allocate requested resources from its parent container; if not ++ * set, the container is only allowed to use resources in its own pools; Note ++ * that this is a container's global policy, but the parent container may ++ * override it and set specific quota per resource type. ++ */ ++#define DPRC_CFG_OPT_ALLOC_ALLOWED 0x00000002 ++ ++/* Object initialization allowed - software context associated with this ++ * container is allowed to invoke object initialization operations. ++ */ ++#define DPRC_CFG_OPT_OBJ_CREATE_ALLOWED 0x00000004 ++ ++/* Topology change allowed - software context associated with this ++ * container is allowed to invoke topology operations, such as attach/detach ++ * of network objects. ++ */ ++#define DPRC_CFG_OPT_TOPOLOGY_CHANGES_ALLOWED 0x00000008 ++ ++/* AIOP - Indicates that container belongs to AIOP. */ ++#define DPRC_CFG_OPT_AIOP 0x00000020 ++ ++/* IRQ Config - Indicates that the container allowed to configure its IRQs. */ ++#define DPRC_CFG_OPT_IRQ_CFG_ALLOWED 0x00000040 ++ ++/** ++ * struct dprc_cfg - Container configuration options ++ * @icid: Container's ICID; if set to 'DPRC_GET_ICID_FROM_POOL', a free ++ * ICID value is allocated by the DPRC ++ * @portal_id: Portal ID; if set to 'DPRC_GET_PORTAL_ID_FROM_POOL', a free ++ * portal ID is allocated by the DPRC ++ * @options: Combination of 'DPRC_CFG_OPT_' options ++ * @label: Object's label ++ */ ++struct dprc_cfg { ++ uint16_t icid; ++ int portal_id; ++ uint64_t options; ++ char label[16]; ++}; ++ ++/** ++ * dprc_create_container() - Create child container ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPRC object ++ * @cfg: Child container configuration ++ * @child_container_id: Returned child container ID ++ * @child_portal_offset: Returned child portal offset from MC portal base ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dprc_create_container(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ struct dprc_cfg *cfg, ++ int *child_container_id, ++ uint64_t *child_portal_offset); ++ ++/** ++ * dprc_destroy_container() - Destroy child container. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPRC object ++ * @child_container_id: ID of the container to destroy ++ * ++ * This function terminates the child container, so following this call the ++ * child container ID becomes invalid. ++ * ++ * Notes: ++ * - All resources and objects of the destroyed container are returned to the ++ * parent container or destroyed if were created be the destroyed container. ++ * - This function destroy all the child containers of the specified ++ * container prior to destroying the container itself. ++ * ++ * warning: Only the parent container is allowed to destroy a child policy ++ * Container 0 can't be destroyed ++ * ++ * Return: '0' on Success; Error code otherwise. ++ * ++ */ ++int dprc_destroy_container(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int child_container_id); ++ ++/** ++ * dprc_reset_container - Reset child container. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPRC object ++ * @child_container_id: ID of the container to reset ++ * ++ * In case a software context crashes or becomes non-responsive, the parent ++ * may wish to reset its resources container before the software context is ++ * restarted. ++ * ++ * This routine informs all objects assigned to the child container that the ++ * container is being reset, so they may perform any cleanup operations that are ++ * needed. All objects handles that were owned by the child container shall be ++ * closed. ++ * ++ * Note that such request may be submitted even if the child software context ++ * has not crashed, but the resulting object cleanup operations will not be ++ * aware of that. ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dprc_reset_container(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int child_container_id); ++ ++/* IRQ */ ++ ++/* IRQ index */ ++#define DPRC_IRQ_INDEX 0 ++ ++/* Number of dprc's IRQs */ ++#define DPRC_NUM_OF_IRQS 1 ++ ++/* DPRC IRQ events */ ++ ++/* IRQ event - Indicates that a new object added to the container */ ++#define DPRC_IRQ_EVENT_OBJ_ADDED 0x00000001 ++ ++/* IRQ event - Indicates that an object was removed from the container */ ++#define DPRC_IRQ_EVENT_OBJ_REMOVED 0x00000002 ++ ++/* IRQ event - Indicates that resources added to the container */ ++#define DPRC_IRQ_EVENT_RES_ADDED 0x00000004 ++ ++/* IRQ event - Indicates that resources removed from the container */ ++#define DPRC_IRQ_EVENT_RES_REMOVED 0x00000008 ++ ++/* IRQ event - Indicates that one of the descendant containers that opened by ++ * this container is destroyed ++ */ ++#define DPRC_IRQ_EVENT_CONTAINER_DESTROYED 0x00000010 ++ ++/* IRQ event - Indicates that on one of the container's opened object is ++ * destroyed ++ */ ++#define DPRC_IRQ_EVENT_OBJ_DESTROYED 0x00000020 ++ ++/* Irq event - Indicates that object is created at the container */ ++#define DPRC_IRQ_EVENT_OBJ_CREATED 0x00000040 ++ ++/** ++ * struct dprc_irq_cfg - IRQ configuration ++ * @paddr: Address that must be written to signal a message-based interrupt ++ * @val: Value to write into irq_addr address ++ * @irq_num: A user defined number associated with this IRQ ++ */ ++struct dprc_irq_cfg { ++ uint64_t paddr; ++ uint32_t val; ++ int irq_num; ++}; ++ ++/** ++ * dprc_set_irq() - Set IRQ information for the DPRC to trigger an interrupt. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPRC object ++ * @irq_index: Identifies the interrupt index to configure ++ * @irq_cfg: IRQ configuration ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dprc_set_irq(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ struct dprc_irq_cfg *irq_cfg); ++ ++/** ++ * dprc_get_irq() - Get IRQ information from the DPRC. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPRC object ++ * @irq_index: The interrupt index to configure ++ * @type: Interrupt type: 0 represents message interrupt ++ * type (both irq_addr and irq_val are valid) ++ * @irq_cfg: IRQ attributes ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dprc_get_irq(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ int *type, ++ struct dprc_irq_cfg *irq_cfg); ++ ++/** ++ * dprc_set_irq_enable() - Set overall interrupt state. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPRC object ++ * @irq_index: The interrupt index to configure ++ * @en: Interrupt state - enable = 1, disable = 0 ++ * ++ * Allows GPP software to control when interrupts are generated. ++ * Each interrupt can have up to 32 causes. The enable/disable control's the ++ * overall interrupt state. if the interrupt is disabled no causes will cause ++ * an interrupt. ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dprc_set_irq_enable(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint8_t en); ++ ++/** ++ * dprc_get_irq_enable() - Get overall interrupt state. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPRC object ++ * @irq_index: The interrupt index to configure ++ * @en: Returned interrupt state - enable = 1, disable = 0 ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dprc_get_irq_enable(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint8_t *en); ++ ++/** ++ * dprc_set_irq_mask() - Set interrupt mask. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPRC object ++ * @irq_index: The interrupt index to configure ++ * @mask: event mask to trigger interrupt; ++ * each bit: ++ * 0 = ignore event ++ * 1 = consider event for asserting irq ++ * ++ * Every interrupt can have up to 32 causes and the interrupt model supports ++ * masking/unmasking each cause independently ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dprc_set_irq_mask(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint32_t mask); ++ ++/** ++ * dprc_get_irq_mask() - Get interrupt mask. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPRC object ++ * @irq_index: The interrupt index to configure ++ * @mask: Returned event mask to trigger interrupt ++ * ++ * Every interrupt can have up to 32 causes and the interrupt model supports ++ * masking/unmasking each cause independently ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dprc_get_irq_mask(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint32_t *mask); ++ ++/** ++ * dprc_get_irq_status() - Get the current status of any pending interrupts. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPRC object ++ * @irq_index: The interrupt index to configure ++ * @status: Returned interrupts status - one bit per cause: ++ * 0 = no interrupt pending ++ * 1 = interrupt pending ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dprc_get_irq_status(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint32_t *status); ++ ++/** ++ * dprc_clear_irq_status() - Clear a pending interrupt's status ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPRC object ++ * @irq_index: The interrupt index to configure ++ * @status: bits to clear (W1C) - one bit per cause: ++ * 0 = don't change ++ * 1 = clear status bit ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dprc_clear_irq_status(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ uint8_t irq_index, ++ uint32_t status); ++ ++/** ++ * struct dprc_attributes - Container attributes ++ * @container_id: Container's ID ++ * @icid: Container's ICID ++ * @portal_id: Container's portal ID ++ * @options: Container's options as set at container's creation ++ * @version: DPRC version ++ */ ++struct dprc_attributes { ++ int container_id; ++ uint16_t icid; ++ int portal_id; ++ uint64_t options; ++ /** ++ * struct version - DPRC version ++ * @major: DPRC major version ++ * @minor: DPRC minor version ++ */ ++ struct { ++ uint16_t major; ++ uint16_t minor; ++ } version; ++}; ++ ++/** ++ * dprc_get_attributes() - Obtains container attributes ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPRC object ++ * @attributes: Returned container attributes ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dprc_get_attributes(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ struct dprc_attributes *attributes); ++ ++/** ++ * dprc_set_res_quota() - Set allocation policy for a specific resource/object ++ * type in a child container ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPRC object ++ * @child_container_id: ID of the child container ++ * @type: Resource/object type ++ * @quota: Sets the maximum number of resources of the selected type ++ * that the child container is allowed to allocate from its parent; ++ * when quota is set to -1, the policy is the same as container's ++ * general policy. ++ * ++ * Allocation policy determines whether or not a container may allocate ++ * resources from its parent. Each container has a 'global' allocation policy ++ * that is set when the container is created. ++ * ++ * This function sets allocation policy for a specific resource type. ++ * The default policy for all resource types matches the container's 'global' ++ * allocation policy. ++ * ++ * Return: '0' on Success; Error code otherwise. ++ * ++ * @warning Only the parent container is allowed to change a child policy. ++ */ ++int dprc_set_res_quota(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int child_container_id, ++ char *type, ++ uint16_t quota); ++ ++/** ++ * dprc_get_res_quota() - Gets the allocation policy of a specific ++ * resource/object type in a child container ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPRC object ++ * @child_container_id: ID of the child container ++ * @type: resource/object type ++ * @quota: Returnes the maximum number of resources of the selected type ++ * that the child container is allowed to allocate from the parent; ++ * when quota is set to -1, the policy is the same as container's ++ * general policy. ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dprc_get_res_quota(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int child_container_id, ++ char *type, ++ uint16_t *quota); ++ ++/* Resource request options */ ++ ++/* Explicit resource ID request - The requested objects/resources ++ * are explicit and sequential (in case of resources). ++ * The base ID is given at res_req at base_align field ++ */ ++#define DPRC_RES_REQ_OPT_EXPLICIT 0x00000001 ++ ++/* Aligned resources request - Relevant only for resources ++ * request (and not objects). Indicates that resources base ID should be ++ * sequential and aligned to the value given at dprc_res_req base_align field ++ */ ++#define DPRC_RES_REQ_OPT_ALIGNED 0x00000002 ++ ++/* Plugged Flag - Relevant only for object assignment request. ++ * Indicates that after all objects assigned. An interrupt will be invoked at ++ * the relevant GPP. The assigned object will be marked as plugged. ++ * plugged objects can't be assigned from their container ++ */ ++#define DPRC_RES_REQ_OPT_PLUGGED 0x00000004 ++ ++/** ++ * struct dprc_res_req - Resource request descriptor, to be used in assignment ++ * or un-assignment of resources and objects. ++ * @type: Resource/object type: Represent as a NULL terminated string. ++ * This string may received by using dprc_get_pool() to get resource ++ * type and dprc_get_obj() to get object type; ++ * Note: it is not possible to assign/un-assign DPRC objects ++ * @num: Number of resources ++ * @options: Request options: combination of DPRC_RES_REQ_OPT_ options ++ * @id_base_align: In case of explicit assignment (DPRC_RES_REQ_OPT_EXPLICIT ++ * is set at option), this field represents the required base ID ++ * for resource allocation; In case of aligned assignment ++ * (DPRC_RES_REQ_OPT_ALIGNED is set at option), this field ++ * indicates the required alignment for the resource ID(s) - ++ * use 0 if there is no alignment or explicit ID requirements ++ */ ++struct dprc_res_req { ++ char type[16]; ++ uint32_t num; ++ uint32_t options; ++ int id_base_align; ++}; ++ ++/** ++ * dprc_assign() - Assigns objects or resource to a child container. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPRC object ++ * @container_id: ID of the child container ++ * @res_req: Describes the type and amount of resources to ++ * assign to the given container ++ * ++ * Assignment is usually done by a parent (this DPRC) to one of its child ++ * containers. ++ * ++ * According to the DPRC allocation policy, the assigned resources may be taken ++ * (allocated) from the container's ancestors, if not enough resources are ++ * available in the container itself. ++ * ++ * The type of assignment depends on the dprc_res_req options, as follows: ++ * - DPRC_RES_REQ_OPT_EXPLICIT: indicates that assigned resources should have ++ * the explicit base ID specified at the id_base_align field of res_req. ++ * - DPRC_RES_REQ_OPT_ALIGNED: indicates that the assigned resources should be ++ * aligned to the value given at id_base_align field of res_req. ++ * - DPRC_RES_REQ_OPT_PLUGGED: Relevant only for object assignment, ++ * and indicates that the object must be set to the plugged state. ++ * ++ * A container may use this function with its own ID in order to change a ++ * object state to plugged or unplugged. ++ * ++ * If IRQ information has been set in the child DPRC, it will signal an ++ * interrupt following every change in its object assignment. ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dprc_assign(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int container_id, ++ struct dprc_res_req *res_req); ++ ++/** ++ * dprc_unassign() - Un-assigns objects or resources from a child container ++ * and moves them into this (parent) DPRC. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPRC object ++ * @child_container_id: ID of the child container ++ * @res_req: Describes the type and amount of resources to un-assign from ++ * the child container ++ * ++ * Un-assignment of objects can succeed only if the object is not in the ++ * plugged or opened state. ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dprc_unassign(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int child_container_id, ++ struct dprc_res_req *res_req); ++ ++/** ++ * dprc_get_pool_count() - Get the number of dprc's pools ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @mc_io: Pointer to MC portal's I/O object ++ * @token: Token of DPRC object ++ * @pool_count: Returned number of resource pools in the dprc ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dprc_get_pool_count(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int *pool_count); ++ ++/** ++ * dprc_get_pool() - Get the type (string) of a certain dprc's pool ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPRC object ++ * @pool_index: Index of the pool to be queried (< pool_count) ++ * @type: The type of the pool ++ * ++ * The pool types retrieved one by one by incrementing ++ * pool_index up to (not including) the value of pool_count returned ++ * from dprc_get_pool_count(). dprc_get_pool_count() must ++ * be called prior to dprc_get_pool(). ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dprc_get_pool(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int pool_index, ++ char *type); ++ ++/** ++ * dprc_get_obj_count() - Obtains the number of objects in the DPRC ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPRC object ++ * @obj_count: Number of objects assigned to the DPRC ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dprc_get_obj_count(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int *obj_count); ++ ++/* Objects Attributes Flags */ ++ ++/* Opened state - Indicates that an object is open by at least one owner */ ++#define DPRC_OBJ_STATE_OPEN 0x00000001 ++/* Plugged state - Indicates that the object is plugged */ ++#define DPRC_OBJ_STATE_PLUGGED 0x00000002 ++ ++/** ++ * Shareability flag - Object flag indicating no memory shareability. ++ * the object generates memory accesses that are non coherent with other ++ * masters; ++ * user is responsible for proper memory handling through IOMMU configuration. ++ */ ++#define DPRC_OBJ_FLAG_NO_MEM_SHAREABILITY 0x0001 ++ ++/** ++ * struct dprc_obj_desc - Object descriptor, returned from dprc_get_obj() ++ * @type: Type of object: NULL terminated string ++ * @id: ID of logical object resource ++ * @vendor: Object vendor identifier ++ * @ver_major: Major version number ++ * @ver_minor: Minor version number ++ * @irq_count: Number of interrupts supported by the object ++ * @region_count: Number of mappable regions supported by the object ++ * @state: Object state: combination of DPRC_OBJ_STATE_ states ++ * @label: Object label ++ * @flags: Object's flags ++ */ ++struct dprc_obj_desc { ++ char type[16]; ++ int id; ++ uint16_t vendor; ++ uint16_t ver_major; ++ uint16_t ver_minor; ++ uint8_t irq_count; ++ uint8_t region_count; ++ uint32_t state; ++ char label[16]; ++ uint16_t flags; ++}; ++ ++/** ++ * dprc_get_obj() - Get general information on an object ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPRC object ++ * @obj_index: Index of the object to be queried (< obj_count) ++ * @obj_desc: Returns the requested object descriptor ++ * ++ * The object descriptors are retrieved one by one by incrementing ++ * obj_index up to (not including) the value of obj_count returned ++ * from dprc_get_obj_count(). dprc_get_obj_count() must ++ * be called prior to dprc_get_obj(). ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dprc_get_obj(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ int obj_index, ++ struct dprc_obj_desc *obj_desc); ++ ++/** ++ * dprc_get_obj_desc() - Get object descriptor. ++ * ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPRC object ++ * @obj_type: The type of the object to get its descriptor. ++ * @obj_id: The id of the object to get its descriptor ++ * @obj_desc: The returned descriptor to fill and return to the user ++ * ++ * Return: '0' on Success; Error code otherwise. ++ * ++ */ ++int dprc_get_obj_desc(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ char *obj_type, ++ int obj_id, ++ struct dprc_obj_desc *obj_desc); ++ ++/** ++ * dprc_set_obj_irq() - Set IRQ information for object to trigger an interrupt. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPRC object ++ * @obj_type: Type of the object to set its IRQ ++ * @obj_id: ID of the object to set its IRQ ++ * @irq_index: The interrupt index to configure ++ * @irq_cfg: IRQ configuration ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dprc_set_obj_irq(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ char *obj_type, ++ int obj_id, ++ uint8_t irq_index, ++ struct dprc_irq_cfg *irq_cfg); ++ ++/** ++ * dprc_get_obj_irq() - Get IRQ information from object. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPRC object ++ * @obj_type: Type od the object to get its IRQ ++ * @obj_id: ID of the object to get its IRQ ++ * @irq_index: The interrupt index to configure ++ * @type: Interrupt type: 0 represents message interrupt ++ * type (both irq_addr and irq_val are valid) ++ * @irq_cfg: The returned IRQ attributes ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dprc_get_obj_irq(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ char *obj_type, ++ int obj_id, ++ uint8_t irq_index, ++ int *type, ++ struct dprc_irq_cfg *irq_cfg); ++ ++/** ++ * dprc_get_res_count() - Obtains the number of free resources that are assigned ++ * to this container, by pool type ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPRC object ++ * @type: pool type ++ * @res_count: Returned number of free resources of the given ++ * resource type that are assigned to this DPRC ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dprc_get_res_count(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ char *type, ++ int *res_count); ++ ++/** ++ * enum dprc_iter_status - Iteration status ++ * @DPRC_ITER_STATUS_FIRST: Perform first iteration ++ * @DPRC_ITER_STATUS_MORE: Indicates more/next iteration is needed ++ * @DPRC_ITER_STATUS_LAST: Indicates last iteration ++ */ ++enum dprc_iter_status { ++ DPRC_ITER_STATUS_FIRST = 0, ++ DPRC_ITER_STATUS_MORE = 1, ++ DPRC_ITER_STATUS_LAST = 2 ++}; ++ ++/** ++ * struct dprc_res_ids_range_desc - Resource ID range descriptor ++ * @base_id: Base resource ID of this range ++ * @last_id: Last resource ID of this range ++ * @iter_status: Iteration status - should be set to DPRC_ITER_STATUS_FIRST at ++ * first iteration; while the returned marker is DPRC_ITER_STATUS_MORE, ++ * additional iterations are needed, until the returned marker is ++ * DPRC_ITER_STATUS_LAST ++ */ ++struct dprc_res_ids_range_desc { ++ int base_id; ++ int last_id; ++ enum dprc_iter_status iter_status; ++}; ++ ++/** ++ * dprc_get_res_ids() - Obtains IDs of free resources in the container ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPRC object ++ * @type: pool type ++ * @range_desc: range descriptor ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dprc_get_res_ids(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ char *type, ++ struct dprc_res_ids_range_desc *range_desc); ++ ++/* Region flags */ ++/* Cacheable - Indicates that region should be mapped as cacheable */ ++#define DPRC_REGION_CACHEABLE 0x00000001 ++ ++/** ++ * enum dprc_region_type - Region type ++ * @DPRC_REGION_TYPE_MC_PORTAL: MC portal region ++ * @DPRC_REGION_TYPE_QBMAN_PORTAL: Qbman portal region ++ */ ++enum dprc_region_type { ++ DPRC_REGION_TYPE_MC_PORTAL, ++ DPRC_REGION_TYPE_QBMAN_PORTAL ++}; ++ ++/** ++ * struct dprc_region_desc - Mappable region descriptor ++ * @base_offset: Region offset from region's base address. ++ * For DPMCP and DPRC objects, region base is offset from SoC MC portals ++ * base address; For DPIO, region base is offset from SoC QMan portals ++ * base address ++ * @size: Region size (in bytes) ++ * @flags: Region attributes ++ * @type: Portal region type ++ */ ++struct dprc_region_desc { ++ uint32_t base_offset; ++ uint32_t size; ++ uint32_t flags; ++ enum dprc_region_type type; ++}; ++ ++/** ++ * dprc_get_obj_region() - Get region information for a specified object. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPRC object ++ * @obj_type: Object type as returned in dprc_get_obj() ++ * @obj_id: Unique object instance as returned in dprc_get_obj() ++ * @region_index: The specific region to query ++ * @region_desc: Returns the requested region descriptor ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dprc_get_obj_region(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ char *obj_type, ++ int obj_id, ++ uint8_t region_index, ++ struct dprc_region_desc *region_desc); ++ ++/** ++ * dprc_set_obj_label() - Set object label. ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPRC object ++ * @obj_type: Object's type ++ * @obj_id: Object's ID ++ * @label: The required label. The maximum length is 16 chars. ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dprc_set_obj_label(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ char *obj_type, ++ int obj_id, ++ char *label); ++ ++/** ++ * struct dprc_endpoint - Endpoint description for link connect/disconnect ++ * operations ++ * @type: Endpoint object type: NULL terminated string ++ * @id: Endpoint object ID ++ * @if_id: Interface ID; should be set for endpoints with multiple ++ * interfaces ("dpsw", "dpdmux"); for others, always set to 0 ++ */ ++struct dprc_endpoint { ++ char type[16]; ++ int id; ++ int if_id; ++}; ++ ++/** ++ * struct dprc_connection_cfg - Connection configuration. ++ * Used for virtual connections only ++ * @committed_rate: Committed rate (Mbits/s) ++ * @max_rate: Maximum rate (Mbits/s) ++ */ ++struct dprc_connection_cfg { ++ uint32_t committed_rate; ++ uint32_t max_rate; ++}; ++ ++/** ++ * dprc_connect() - Connect two endpoints to create a network link between them ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPRC object ++ * @endpoint1: Endpoint 1 configuration parameters ++ * @endpoint2: Endpoint 2 configuration parameters ++ * @cfg: Connection configuration. The connection configuration is ignored for ++ * connections made to DPMAC objects, where rate is retrieved from the ++ * MAC configuration. ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dprc_connect(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ const struct dprc_endpoint *endpoint1, ++ const struct dprc_endpoint *endpoint2, ++ const struct dprc_connection_cfg *cfg); ++ ++/** ++ * dprc_disconnect() - Disconnect one endpoint to remove its network connection ++ * @mc_io: Pointer to MC portal's I/O object ++ * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++ * @token: Token of DPRC object ++ * @endpoint: Endpoint configuration parameters ++ * ++ * Return: '0' on Success; Error code otherwise. ++ */ ++int dprc_disconnect(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ const struct dprc_endpoint *endpoint); ++ ++/** ++* dprc_get_connection() - Get connected endpoint and link status if connection ++* exists. ++* @mc_io: Pointer to MC portal's I/O object ++* @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' ++* @token: Token of DPRC object ++* @endpoint1: Endpoint 1 configuration parameters ++* @endpoint2: Returned endpoint 2 configuration parameters ++* @state: Returned link state: ++* 1 - link is up; ++* 0 - link is down; ++* -1 - no connection (endpoint2 information is irrelevant) ++* ++* Return: '0' on Success; -ENAVAIL if connection does not exist. ++*/ ++int dprc_get_connection(struct fsl_mc_io *mc_io, ++ uint32_t cmd_flags, ++ uint16_t token, ++ const struct dprc_endpoint *endpoint1, ++ struct dprc_endpoint *endpoint2, ++ int *state); ++ ++#endif /* _FSL_DPRC_H */ ++ +diff --git a/drivers/staging/fsl-mc/include/fsl_dpaa2_fd.h b/drivers/staging/fsl-mc/include/fsl_dpaa2_fd.h +new file mode 100644 +index 0000000..3e9af59 +--- /dev/null ++++ b/drivers/staging/fsl-mc/include/fsl_dpaa2_fd.h +@@ -0,0 +1,774 @@ ++/* Copyright 2014 Freescale Semiconductor Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * * Neither the name of Freescale Semiconductor nor the ++ * names of its contributors may be used to endorse or promote products ++ * derived from this software without specific prior written permission. ++ * ++ * ++ * ALTERNATIVELY, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") as published by the Free Software ++ * Foundation, either version 2 of that License or (at your option) any ++ * later version. ++ * ++ * THIS SOFTWARE IS PROVIDED BY Freescale Semiconductor ``AS IS'' AND ANY ++ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ++ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE ++ * DISCLAIMED. IN NO EVENT SHALL Freescale Semiconductor BE LIABLE FOR ANY ++ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ++ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; ++ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ++ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ++ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS ++ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ++ */ ++#ifndef __FSL_DPAA2_FD_H ++#define __FSL_DPAA2_FD_H ++ ++/** ++ * DOC: DPAA2 FD - Frame Descriptor APIs for DPAA2 ++ * ++ * Frame Descriptors (FDs) are used to describe frame data in the DPAA2. ++ * Frames can be enqueued and dequeued to Frame Queues which are consumed ++ * by the various DPAA accelerators (WRIOP, SEC, PME, DCE) ++ * ++ * There are three types of frames: Single, Scatter Gather and Frame Lists. ++ * ++ * The set of APIs in this file must be used to create, manipulate and ++ * query Frame Descriptor. ++ * ++ */ ++ ++/** ++ * struct dpaa2_fd - Place-holder for FDs. ++ * @words: for easier/faster copying the whole FD structure. ++ * @addr_lo: the lower 32 bits of the address in FD. ++ * @addr_hi: the upper 32 bits of the address in FD. ++ * @len: the length field in FD. ++ * @bpid_offset: represent the bpid and offset fields in FD ++ * @frc: frame context ++ * @ctrl: the 32bit control bits including dd, sc,... va, err. ++ * @flc_lo: the lower 32bit of flow context. ++ * @flc_hi: the upper 32bits of flow context. ++ * ++ * This structure represents the basic Frame Descriptor used in the system. ++ * We represent it via the simplest form that we need for now. Different ++ * overlays may be needed to support different options, etc. (It is impractical ++ * to define One True Struct, because the resulting encoding routines (lots of ++ * read-modify-writes) would be worst-case performance whether or not ++ * circumstances required them.) ++ */ ++struct dpaa2_fd { ++ union { ++ u32 words[8]; ++ struct dpaa2_fd_simple { ++ u32 addr_lo; ++ u32 addr_hi; ++ u32 len; ++ /* offset in the MS 16 bits, BPID in the LS 16 bits */ ++ u32 bpid_offset; ++ u32 frc; /* frame context */ ++ /* "err", "va", "cbmt", "asal", [...] */ ++ u32 ctrl; ++ /* flow context */ ++ u32 flc_lo; ++ u32 flc_hi; ++ } simple; ++ }; ++}; ++ ++enum dpaa2_fd_format { ++ dpaa2_fd_single = 0, ++ dpaa2_fd_list, ++ dpaa2_fd_sg ++}; ++ ++/* Accessors for SG entry fields ++ * ++ * These setters and getters assume little endian format. For converting ++ * between LE and cpu endianness, the specific conversion functions must be ++ * called before the SGE contents are accessed by the core (on Rx), ++ * respectively before the SG table is sent to hardware (on Tx) ++ */ ++ ++/** ++ * dpaa2_fd_get_addr() - get the addr field of frame descriptor ++ * @fd: the given frame descriptor. ++ * ++ * Return the address in the frame descriptor. ++ */ ++static inline dma_addr_t dpaa2_fd_get_addr(const struct dpaa2_fd *fd) ++{ ++ return (dma_addr_t)((((uint64_t)fd->simple.addr_hi) << 32) ++ + fd->simple.addr_lo); ++} ++ ++/** ++ * dpaa2_fd_set_addr() - Set the addr field of frame descriptor ++ * @fd: the given frame descriptor. ++ * @addr: the address needs to be set in frame descriptor. ++ */ ++static inline void dpaa2_fd_set_addr(struct dpaa2_fd *fd, dma_addr_t addr) ++{ ++ fd->simple.addr_hi = upper_32_bits(addr); ++ fd->simple.addr_lo = lower_32_bits(addr); ++} ++ ++/** ++ * dpaa2_fd_get_frc() - Get the frame context in the frame descriptor ++ * @fd: the given frame descriptor. ++ * ++ * Return the frame context field in the frame descriptor. ++ */ ++static inline u32 dpaa2_fd_get_frc(const struct dpaa2_fd *fd) ++{ ++ return fd->simple.frc; ++} ++ ++/** ++ * dpaa2_fd_set_frc() - Set the frame context in the frame descriptor ++ * @fd: the given frame descriptor. ++ * @frc: the frame context needs to be set in frame descriptor. ++ */ ++static inline void dpaa2_fd_set_frc(struct dpaa2_fd *fd, u32 frc) ++{ ++ fd->simple.frc = frc; ++} ++ ++/** ++ * dpaa2_fd_get_flc() - Get the flow context in the frame descriptor ++ * @fd: the given frame descriptor. ++ * ++ * Return the flow context in the frame descriptor. ++ */ ++static inline dma_addr_t dpaa2_fd_get_flc(const struct dpaa2_fd *fd) ++{ ++ return (dma_addr_t)((((uint64_t)fd->simple.flc_hi) << 32) + ++ fd->simple.flc_lo); ++} ++ ++/** ++ * dpaa2_fd_set_flc() - Set the flow context field of frame descriptor ++ * @fd: the given frame descriptor. ++ * @flc_addr: the flow context needs to be set in frame descriptor. ++ */ ++static inline void dpaa2_fd_set_flc(struct dpaa2_fd *fd, dma_addr_t flc_addr) ++{ ++ fd->simple.flc_hi = upper_32_bits(flc_addr); ++ fd->simple.flc_lo = lower_32_bits(flc_addr); ++} ++ ++/** ++ * dpaa2_fd_get_len() - Get the length in the frame descriptor ++ * @fd: the given frame descriptor. ++ * ++ * Return the length field in the frame descriptor. ++ */ ++static inline u32 dpaa2_fd_get_len(const struct dpaa2_fd *fd) ++{ ++ return fd->simple.len; ++} ++ ++/** ++ * dpaa2_fd_set_len() - Set the length field of frame descriptor ++ * @fd: the given frame descriptor. ++ * @len: the length needs to be set in frame descriptor. ++ */ ++static inline void dpaa2_fd_set_len(struct dpaa2_fd *fd, u32 len) ++{ ++ fd->simple.len = len; ++} ++ ++/** ++ * dpaa2_fd_get_offset() - Get the offset field in the frame descriptor ++ * @fd: the given frame descriptor. ++ * ++ * Return the offset. ++ */ ++static inline uint16_t dpaa2_fd_get_offset(const struct dpaa2_fd *fd) ++{ ++ return (uint16_t)(fd->simple.bpid_offset >> 16) & 0x0FFF; ++} ++ ++/** ++ * dpaa2_fd_set_offset() - Set the offset field of frame descriptor ++ * ++ * @fd: the given frame descriptor. ++ * @offset: the offset needs to be set in frame descriptor. ++ */ ++static inline void dpaa2_fd_set_offset(struct dpaa2_fd *fd, uint16_t offset) ++{ ++ fd->simple.bpid_offset &= 0xF000FFFF; ++ fd->simple.bpid_offset |= (u32)offset << 16; ++} ++ ++/** ++ * dpaa2_fd_get_format() - Get the format field in the frame descriptor ++ * @fd: the given frame descriptor. ++ * ++ * Return the format. ++ */ ++static inline enum dpaa2_fd_format dpaa2_fd_get_format( ++ const struct dpaa2_fd *fd) ++{ ++ return (enum dpaa2_fd_format)((fd->simple.bpid_offset >> 28) & 0x3); ++} ++ ++/** ++ * dpaa2_fd_set_format() - Set the format field of frame descriptor ++ * ++ * @fd: the given frame descriptor. ++ * @format: the format needs to be set in frame descriptor. ++ */ ++static inline void dpaa2_fd_set_format(struct dpaa2_fd *fd, ++ enum dpaa2_fd_format format) ++{ ++ fd->simple.bpid_offset &= 0xCFFFFFFF; ++ fd->simple.bpid_offset |= (u32)format << 28; ++} ++ ++/** ++ * dpaa2_fd_get_bpid() - Get the bpid field in the frame descriptor ++ * @fd: the given frame descriptor. ++ * ++ * Return the bpid. ++ */ ++static inline uint16_t dpaa2_fd_get_bpid(const struct dpaa2_fd *fd) ++{ ++ return (uint16_t)(fd->simple.bpid_offset & 0xFFFF); ++} ++ ++/** ++ * dpaa2_fd_set_bpid() - Set the bpid field of frame descriptor ++ * ++ * @fd: the given frame descriptor. ++ * @bpid: the bpid needs to be set in frame descriptor. ++ */ ++static inline void dpaa2_fd_set_bpid(struct dpaa2_fd *fd, uint16_t bpid) ++{ ++ fd->simple.bpid_offset &= 0xFFFF0000; ++ fd->simple.bpid_offset |= (u32)bpid; ++} ++ ++/** ++ * struct dpaa2_sg_entry - the scatter-gathering structure ++ * @addr_lo: the lower 32bit of address ++ * @addr_hi: the upper 32bit of address ++ * @len: the length in this sg entry. ++ * @bpid_offset: offset in the MS 16 bits, BPID in the LS 16 bits. ++ */ ++struct dpaa2_sg_entry { ++ u32 addr_lo; ++ u32 addr_hi; ++ u32 len; ++ u32 bpid_offset; ++}; ++ ++enum dpaa2_sg_format { ++ dpaa2_sg_single = 0, ++ dpaa2_sg_frame_data, ++ dpaa2_sg_sgt_ext ++}; ++ ++/** ++ * dpaa2_sg_get_addr() - Get the address from SG entry ++ * @sg: the given scatter-gathering object. ++ * ++ * Return the address. ++ */ ++static inline dma_addr_t dpaa2_sg_get_addr(const struct dpaa2_sg_entry *sg) ++{ ++ return (dma_addr_t)((((u64)sg->addr_hi) << 32) + sg->addr_lo); ++} ++ ++/** ++ * dpaa2_sg_set_addr() - Set the address in SG entry ++ * @sg: the given scatter-gathering object. ++ * @addr: the address to be set. ++ */ ++static inline void dpaa2_sg_set_addr(struct dpaa2_sg_entry *sg, dma_addr_t addr) ++{ ++ sg->addr_hi = upper_32_bits(addr); ++ sg->addr_lo = lower_32_bits(addr); ++} ++ ++ ++static inline bool dpaa2_sg_short_len(const struct dpaa2_sg_entry *sg) ++{ ++ return (sg->bpid_offset >> 30) & 0x1; ++} ++ ++/** ++ * dpaa2_sg_get_len() - Get the length in SG entry ++ * @sg: the given scatter-gathering object. ++ * ++ * Return the length. ++ */ ++static inline u32 dpaa2_sg_get_len(const struct dpaa2_sg_entry *sg) ++{ ++ if (dpaa2_sg_short_len(sg)) ++ return sg->len & 0x1FFFF; ++ return sg->len; ++} ++ ++/** ++ * dpaa2_sg_set_len() - Set the length in SG entry ++ * @sg: the given scatter-gathering object. ++ * @len: the length to be set. ++ */ ++static inline void dpaa2_sg_set_len(struct dpaa2_sg_entry *sg, u32 len) ++{ ++ sg->len = len; ++} ++ ++/** ++ * dpaa2_sg_get_offset() - Get the offset in SG entry ++ * @sg: the given scatter-gathering object. ++ * ++ * Return the offset. ++ */ ++static inline u16 dpaa2_sg_get_offset(const struct dpaa2_sg_entry *sg) ++{ ++ return (u16)(sg->bpid_offset >> 16) & 0x0FFF; ++} ++ ++/** ++ * dpaa2_sg_set_offset() - Set the offset in SG entry ++ * @sg: the given scatter-gathering object. ++ * @offset: the offset to be set. ++ */ ++static inline void dpaa2_sg_set_offset(struct dpaa2_sg_entry *sg, ++ u16 offset) ++{ ++ sg->bpid_offset &= 0xF000FFFF; ++ sg->bpid_offset |= (u32)offset << 16; ++} ++ ++/** ++ * dpaa2_sg_get_format() - Get the SG format in SG entry ++ * @sg: the given scatter-gathering object. ++ * ++ * Return the format. ++ */ ++static inline enum dpaa2_sg_format ++ dpaa2_sg_get_format(const struct dpaa2_sg_entry *sg) ++{ ++ return (enum dpaa2_sg_format)((sg->bpid_offset >> 28) & 0x3); ++} ++ ++/** ++ * dpaa2_sg_set_format() - Set the SG format in SG entry ++ * @sg: the given scatter-gathering object. ++ * @format: the format to be set. ++ */ ++static inline void dpaa2_sg_set_format(struct dpaa2_sg_entry *sg, ++ enum dpaa2_sg_format format) ++{ ++ sg->bpid_offset &= 0xCFFFFFFF; ++ sg->bpid_offset |= (u32)format << 28; ++} ++ ++/** ++ * dpaa2_sg_get_bpid() - Get the buffer pool id in SG entry ++ * @sg: the given scatter-gathering object. ++ * ++ * Return the bpid. ++ */ ++static inline u16 dpaa2_sg_get_bpid(const struct dpaa2_sg_entry *sg) ++{ ++ return (u16)(sg->bpid_offset & 0x3FFF); ++} ++ ++/** ++ * dpaa2_sg_set_bpid() - Set the buffer pool id in SG entry ++ * @sg: the given scatter-gathering object. ++ * @bpid: the bpid to be set. ++ */ ++static inline void dpaa2_sg_set_bpid(struct dpaa2_sg_entry *sg, u16 bpid) ++{ ++ sg->bpid_offset &= 0xFFFFC000; ++ sg->bpid_offset |= (u32)bpid; ++} ++ ++/** ++ * dpaa2_sg_is_final() - Check final bit in SG entry ++ * @sg: the given scatter-gathering object. ++ * ++ * Return bool. ++ */ ++static inline bool dpaa2_sg_is_final(const struct dpaa2_sg_entry *sg) ++{ ++ return !!(sg->bpid_offset >> 31); ++} ++ ++/** ++ * dpaa2_sg_set_final() - Set the final bit in SG entry ++ * @sg: the given scatter-gathering object. ++ * @final: the final boolean to be set. ++ */ ++static inline void dpaa2_sg_set_final(struct dpaa2_sg_entry *sg, bool final) ++{ ++ sg->bpid_offset &= 0x7FFFFFFF; ++ sg->bpid_offset |= (u32)final << 31; ++} ++ ++/* Endianness conversion helper functions ++ * The accelerator drivers which construct / read scatter gather entries ++ * need to call these in order to account for endianness mismatches between ++ * hardware and cpu ++ */ ++#ifdef __BIG_ENDIAN ++/** ++ * dpaa2_sg_cpu_to_le() - convert scatter gather entry from native cpu ++ * format little endian format. ++ * @sg: the given scatter gather entry. ++ */ ++static inline void dpaa2_sg_cpu_to_le(struct dpaa2_sg_entry *sg) ++{ ++ uint32_t *p = (uint32_t *)sg; ++ int i; ++ ++ for (i = 0; i < sizeof(*sg) / sizeof(u32); i++) ++ cpu_to_le32s(p++); ++} ++ ++/** ++ * dpaa2_sg_le_to_cpu() - convert scatter gather entry from little endian ++ * format to native cpu format. ++ * @sg: the given scatter gather entry. ++ */ ++static inline void dpaa2_sg_le_to_cpu(struct dpaa2_sg_entry *sg) ++{ ++ uint32_t *p = (uint32_t *)sg; ++ int i; ++ ++ for (i = 0; i < sizeof(*sg) / sizeof(u32); i++) ++ le32_to_cpus(p++); ++} ++#else ++#define dpaa2_sg_cpu_to_le(sg) ++#define dpaa2_sg_le_to_cpu(sg) ++#endif /* __BIG_ENDIAN */ ++ ++ ++/** ++ * struct dpaa2_fl_entry - structure for frame list entry. ++ * @addr_lo: the lower 32bit of address ++ * @addr_hi: the upper 32bit of address ++ * @len: the length in this sg entry. ++ * @bpid_offset: offset in the MS 16 bits, BPID in the LS 16 bits. ++ * @frc: frame context ++ * @ctrl: the 32bit control bits including dd, sc,... va, err. ++ * @flc_lo: the lower 32bit of flow context. ++ * @flc_hi: the upper 32bits of flow context. ++ * ++ * Frame List Entry (FLE) ++ * Identical to dpaa2_fd.simple layout, but some bits are different ++ */ ++struct dpaa2_fl_entry { ++ u32 addr_lo; ++ u32 addr_hi; ++ u32 len; ++ u32 bpid_offset; ++ u32 frc; ++ u32 ctrl; ++ u32 flc_lo; ++ u32 flc_hi; ++}; ++ ++enum dpaa2_fl_format { ++ dpaa2_fl_single = 0, ++ dpaa2_fl_res, ++ dpaa2_fl_sg ++}; ++ ++/** ++ * dpaa2_fl_get_addr() - Get address in the frame list entry ++ * @fle: the given frame list entry. ++ * ++ * Return address for the get function. ++ */ ++static inline dma_addr_t dpaa2_fl_get_addr(const struct dpaa2_fl_entry *fle) ++{ ++ return (dma_addr_t)((((uint64_t)fle->addr_hi) << 32) + fle->addr_lo); ++} ++ ++/** ++ * dpaa2_fl_set_addr() - Set the address in the frame list entry ++ * @fle: the given frame list entry. ++ * @addr: the address needs to be set. ++ * ++ */ ++static inline void dpaa2_fl_set_addr(struct dpaa2_fl_entry *fle, ++ dma_addr_t addr) ++{ ++ fle->addr_hi = upper_32_bits(addr); ++ fle->addr_lo = lower_32_bits(addr); ++} ++ ++/** ++ * dpaa2_fl_get_flc() - Get the flow context in the frame list entry ++ * @fle: the given frame list entry. ++ * ++ * Return flow context for the get function. ++ */ ++static inline dma_addr_t dpaa2_fl_get_flc(const struct dpaa2_fl_entry *fle) ++{ ++ return (dma_addr_t)((((uint64_t)fle->flc_hi) << 32) + fle->flc_lo); ++} ++ ++/** ++ * dpaa2_fl_set_flc() - Set the flow context in the frame list entry ++ * @fle: the given frame list entry. ++ * @flc_addr: the flow context address needs to be set. ++ * ++ */ ++static inline void dpaa2_fl_set_flc(struct dpaa2_fl_entry *fle, ++ dma_addr_t flc_addr) ++{ ++ fle->flc_hi = upper_32_bits(flc_addr); ++ fle->flc_lo = lower_32_bits(flc_addr); ++} ++ ++/** ++ * dpaa2_fl_get_len() - Get the length in the frame list entry ++ * @fle: the given frame list entry. ++ * ++ * Return length for the get function. ++ */ ++static inline u32 dpaa2_fl_get_len(const struct dpaa2_fl_entry *fle) ++{ ++ return fle->len; ++} ++ ++/** ++ * dpaa2_fl_set_len() - Set the length in the frame list entry ++ * @fle: the given frame list entry. ++ * @len: the length needs to be set. ++ * ++ */ ++static inline void dpaa2_fl_set_len(struct dpaa2_fl_entry *fle, u32 len) ++{ ++ fle->len = len; ++} ++ ++/** ++ * dpaa2_fl_get_offset() - Get/Set the offset in the frame list entry ++ * @fle: the given frame list entry. ++ * ++ * Return offset for the get function. ++ */ ++static inline uint16_t dpaa2_fl_get_offset(const struct dpaa2_fl_entry *fle) ++{ ++ return (uint16_t)(fle->bpid_offset >> 16) & 0x0FFF; ++} ++ ++/** ++ * dpaa2_fl_set_offset() - Set the offset in the frame list entry ++ * @fle: the given frame list entry. ++ * @offset: the offset needs to be set. ++ * ++ */ ++static inline void dpaa2_fl_set_offset(struct dpaa2_fl_entry *fle, ++ uint16_t offset) ++{ ++ fle->bpid_offset &= 0xF000FFFF; ++ fle->bpid_offset |= (u32)(offset & 0x0FFF) << 16; ++} ++ ++/** ++ * dpaa2_fl_get_format() - Get the format in the frame list entry ++ * @fle: the given frame list entry. ++ * ++ * Return frame list format for the get function. ++ */ ++static inline enum dpaa2_fl_format dpaa2_fl_get_format( ++ const struct dpaa2_fl_entry *fle) ++{ ++ return (enum dpaa2_fl_format)((fle->bpid_offset >> 28) & 0x3); ++} ++ ++/** ++ * dpaa2_fl_set_format() - Set the format in the frame list entry ++ * @fle: the given frame list entry. ++ * @format: the frame list format needs to be set. ++ * ++ */ ++static inline void dpaa2_fl_set_format(struct dpaa2_fl_entry *fle, ++ enum dpaa2_fl_format format) ++{ ++ fle->bpid_offset &= 0xCFFFFFFF; ++ fle->bpid_offset |= (u32)(format & 0x3) << 28; ++} ++ ++/** ++ * dpaa2_fl_get_bpid() - Get the buffer pool id in the frame list entry ++ * @fle: the given frame list entry. ++ * ++ * Return bpid for the get function. ++ */ ++static inline uint16_t dpaa2_fl_get_bpid(const struct dpaa2_fl_entry *fle) ++{ ++ return (uint16_t)(fle->bpid_offset & 0x3FFF); ++} ++ ++/** ++ * dpaa2_fl_set_bpid() - Set the buffer pool id in the frame list entry ++ * @fle: the given frame list entry. ++ * @bpid: the buffer pool id needs to be set. ++ * ++ */ ++static inline void dpaa2_fl_set_bpid(struct dpaa2_fl_entry *fle, uint16_t bpid) ++{ ++ fle->bpid_offset &= 0xFFFFC000; ++ fle->bpid_offset |= (u32)bpid; ++} ++ ++/** dpaa2_fl_is_final() - check the final bit is set or not in the frame list. ++ * @fle: the given frame list entry. ++ * ++ * Return final bit settting. ++ */ ++static inline bool dpaa2_fl_is_final(const struct dpaa2_fl_entry *fle) ++{ ++ return !!(fle->bpid_offset >> 31); ++} ++ ++/** ++ * dpaa2_fl_set_final() - Set the final bit in the frame list entry ++ * @fle: the given frame list entry. ++ * @final: the final bit needs to be set. ++ * ++ */ ++static inline void dpaa2_fl_set_final(struct dpaa2_fl_entry *fle, bool final) ++{ ++ fle->bpid_offset &= 0x7FFFFFFF; ++ fle->bpid_offset |= (u32)final << 31; ++} ++ ++/** ++ * struct dpaa2_dq - the qman result structure ++ * @dont_manipulate_directly: the 16 32bit data to represent the whole ++ * possible qman dequeue result. ++ * ++ * When frames are dequeued, the FDs show up inside "dequeue" result structures ++ * (if at all, not all dequeue results contain valid FDs). This structure type ++ * is intentionally defined without internal detail, and the only reason it ++ * isn't declared opaquely (without size) is to allow the user to provide ++ * suitably-sized (and aligned) memory for these entries. ++ */ ++struct dpaa2_dq { ++ uint32_t dont_manipulate_directly[16]; ++}; ++ ++/* Parsing frame dequeue results */ ++/* FQ empty */ ++#define DPAA2_DQ_STAT_FQEMPTY 0x80 ++/* FQ held active */ ++#define DPAA2_DQ_STAT_HELDACTIVE 0x40 ++/* FQ force eligible */ ++#define DPAA2_DQ_STAT_FORCEELIGIBLE 0x20 ++/* Valid frame */ ++#define DPAA2_DQ_STAT_VALIDFRAME 0x10 ++/* FQ ODP enable */ ++#define DPAA2_DQ_STAT_ODPVALID 0x04 ++/* Volatile dequeue */ ++#define DPAA2_DQ_STAT_VOLATILE 0x02 ++/* volatile dequeue command is expired */ ++#define DPAA2_DQ_STAT_EXPIRED 0x01 ++ ++/** ++ * dpaa2_dq_flags() - Get the stat field of dequeue response ++ * @dq: the dequeue result. ++ */ ++uint32_t dpaa2_dq_flags(const struct dpaa2_dq *dq); ++ ++/** ++ * dpaa2_dq_is_pull() - Check whether the dq response is from a pull ++ * command. ++ * @dq: the dequeue result. ++ * ++ * Return 1 for volatile(pull) dequeue, 0 for static dequeue. ++ */ ++static inline int dpaa2_dq_is_pull(const struct dpaa2_dq *dq) ++{ ++ return (int)(dpaa2_dq_flags(dq) & DPAA2_DQ_STAT_VOLATILE); ++} ++ ++/** ++ * dpaa2_dq_is_pull_complete() - Check whether the pull command is completed. ++ * @dq: the dequeue result. ++ * ++ * Return boolean. ++ */ ++static inline int dpaa2_dq_is_pull_complete( ++ const struct dpaa2_dq *dq) ++{ ++ return (int)(dpaa2_dq_flags(dq) & DPAA2_DQ_STAT_EXPIRED); ++} ++ ++/** ++ * dpaa2_dq_seqnum() - Get the seqnum field in dequeue response ++ * seqnum is valid only if VALIDFRAME flag is TRUE ++ * @dq: the dequeue result. ++ * ++ * Return seqnum. ++ */ ++uint16_t dpaa2_dq_seqnum(const struct dpaa2_dq *dq); ++ ++/** ++ * dpaa2_dq_odpid() - Get the seqnum field in dequeue response ++ * odpid is valid only if ODPVAILD flag is TRUE. ++ * @dq: the dequeue result. ++ * ++ * Return odpid. ++ */ ++uint16_t dpaa2_dq_odpid(const struct dpaa2_dq *dq); ++ ++/** ++ * dpaa2_dq_fqid() - Get the fqid in dequeue response ++ * @dq: the dequeue result. ++ * ++ * Return fqid. ++ */ ++uint32_t dpaa2_dq_fqid(const struct dpaa2_dq *dq); ++ ++/** ++ * dpaa2_dq_byte_count() - Get the byte count in dequeue response ++ * @dq: the dequeue result. ++ * ++ * Return the byte count remaining in the FQ. ++ */ ++uint32_t dpaa2_dq_byte_count(const struct dpaa2_dq *dq); ++ ++/** ++ * dpaa2_dq_frame_count() - Get the frame count in dequeue response ++ * @dq: the dequeue result. ++ * ++ * Return the frame count remaining in the FQ. ++ */ ++uint32_t dpaa2_dq_frame_count(const struct dpaa2_dq *dq); ++ ++/** ++ * dpaa2_dq_fd_ctx() - Get the frame queue context in dequeue response ++ * @dq: the dequeue result. ++ * ++ * Return the frame queue context. ++ */ ++uint64_t dpaa2_dq_fqd_ctx(const struct dpaa2_dq *dq); ++ ++/** ++ * dpaa2_dq_fd() - Get the frame descriptor in dequeue response ++ * @dq: the dequeue result. ++ * ++ * Return the frame descriptor. ++ */ ++const struct dpaa2_fd *dpaa2_dq_fd(const struct dpaa2_dq *dq); ++ ++#endif /* __FSL_DPAA2_FD_H */ +diff --git a/drivers/staging/fsl-mc/include/fsl_dpaa2_io.h b/drivers/staging/fsl-mc/include/fsl_dpaa2_io.h +new file mode 100644 +index 0000000..6ea2ff9 +--- /dev/null ++++ b/drivers/staging/fsl-mc/include/fsl_dpaa2_io.h +@@ -0,0 +1,619 @@ ++/* Copyright 2014 Freescale Semiconductor Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * * Neither the name of Freescale Semiconductor nor the ++ * names of its contributors may be used to endorse or promote products ++ * derived from this software without specific prior written permission. ++ * ++ * ++ * ALTERNATIVELY, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") as published by the Free Software ++ * Foundation, either version 2 of that License or (at your option) any ++ * later version. ++ * ++ * THIS SOFTWARE IS PROVIDED BY Freescale Semiconductor ``AS IS'' AND ANY ++ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ++ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE ++ * DISCLAIMED. IN NO EVENT SHALL Freescale Semiconductor BE LIABLE FOR ANY ++ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ++ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; ++ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ++ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ++ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS ++ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ++ */ ++#ifndef __FSL_DPAA2_IO_H ++#define __FSL_DPAA2_IO_H ++ ++#include "fsl_dpaa2_fd.h" ++ ++struct dpaa2_io; ++struct dpaa2_io_store; ++ ++/** ++ * DOC: DPIO Service Management ++ * ++ * The DPIO service provides APIs for users to interact with the datapath ++ * by enqueueing and dequeing frame descriptors. ++ * ++ * The following set of APIs can be used to enqueue and dequeue frames ++ * as well as producing notification callbacks when data is available ++ * for dequeue. ++ */ ++ ++/** ++ * struct dpaa2_io_desc - The DPIO descriptor. ++ * @receives_notifications: Use notificaton mode. ++ * @has_irq: use irq-based proessing. ++ * @will_poll: use poll processing. ++ * @has_8prio: set for channel with 8 priority WQs. ++ * @cpu: the cpu index that at least interrupt handlers will execute on. ++ * @stash_affinity: the stash affinity for this portal favour 'cpu' ++ * @regs_cena: the cache enabled regs. ++ * @regs_cinh: the cache inhibited regs. ++ * @dpio_id: The dpio index. ++ * @qman_version: the qman version ++ * ++ * Describe the attributes and features of the DPIO object. ++ */ ++struct dpaa2_io_desc { ++ /* non-zero iff the DPIO has a channel */ ++ int receives_notifications; ++ /* non-zero if the DPIO portal interrupt is handled. If so, the ++ * caller/OS handles the interrupt and calls dpaa2_io_service_irq(). */ ++ int has_irq; ++ /* non-zero if the caller/OS is prepared to called the ++ * dpaa2_io_service_poll() routine as part of its run-to-completion (or ++ * scheduling) loop. If so, the DPIO service may dynamically switch some ++ * of its processing between polling-based and irq-based. It is illegal ++ * combination to have (!has_irq && !will_poll). */ ++ int will_poll; ++ /* ignored unless 'receives_notifications'. Non-zero iff the channel has ++ * 8 priority WQs, otherwise the channel has 2. */ ++ int has_8prio; ++ /* the cpu index that at least interrupt handlers will execute on. And ++ * if 'stash_affinity' is non-zero, the cache targeted by stash ++ * transactions is affine to this cpu. */ ++ int cpu; ++ /* non-zero if stash transactions for this portal favour 'cpu' over ++ * other CPUs. (Eg. zero if there's no stashing, or stashing is to ++ * shared cache.) */ ++ int stash_affinity; ++ /* Caller-provided flags, determined by bus-scanning and/or creation of ++ * DPIO objects via MC commands. */ ++ void *regs_cena; ++ void *regs_cinh; ++ int dpio_id; ++ uint32_t qman_version; ++}; ++ ++/** ++ * dpaa2_io_create() - create a dpaa2_io object. ++ * @desc: the dpaa2_io descriptor ++ * ++ * Activates a "struct dpaa2_io" corresponding to the given config of an actual ++ * DPIO object. This handle can be used on it's own (like a one-portal "DPIO ++ * service") or later be added to a service-type "struct dpaa2_io" object. Note, ++ * the information required on 'cfg' is copied so the caller is free to do as ++ * they wish with the input parameter upon return. ++ * ++ * Return a valid dpaa2_io object for success, or NULL for failure. ++ */ ++struct dpaa2_io *dpaa2_io_create(const struct dpaa2_io_desc *desc); ++ ++/** ++ * dpaa2_io_create_service() - Create an (initially empty) DPIO service. ++ * ++ * Return a valid dpaa2_io object for success, or NULL for failure. ++ */ ++struct dpaa2_io *dpaa2_io_create_service(void); ++ ++/** ++ * dpaa2_io_default_service() - Use the driver's own global (and initially ++ * empty) DPIO service. ++ * ++ * This increments the reference count, so don't forget to use dpaa2_io_down() ++ * for each time this function is called. ++ * ++ * Return a valid dpaa2_io object for success, or NULL for failure. ++ */ ++struct dpaa2_io *dpaa2_io_default_service(void); ++ ++/** ++ * dpaa2_io_down() - release the dpaa2_io object. ++ * @d: the dpaa2_io object to be released. ++ * ++ * The "struct dpaa2_io" type can represent an individual DPIO object (as ++ * described by "struct dpaa2_io_desc") or an instance of a "DPIO service", ++ * which can be used to group/encapsulate multiple DPIO objects. In all cases, ++ * each handle obtained should be released using this function. ++ */ ++void dpaa2_io_down(struct dpaa2_io *d); ++ ++/** ++ * dpaa2_io_service_add() - Add the given DPIO object to the given DPIO service. ++ * @service: the given DPIO service. ++ * @obj: the given DPIO object. ++ * ++ * 'service' must have been created by dpaa2_io_create_service() and 'obj' ++ * must have been created by dpaa2_io_create(). This increments the reference ++ * count on the object that 'obj' refers to, so the user could call ++ * dpaa2_io_down(obj) after this and the object will persist within the service ++ * (and will be destroyed when the service is destroyed). ++ * ++ * Return 0 for success, or -EINVAL for failure. ++ */ ++int dpaa2_io_service_add(struct dpaa2_io *service, struct dpaa2_io *obj); ++ ++/** ++ * dpaa2_io_get_descriptor() - Get the DPIO descriptor of the given DPIO object. ++ * @obj: the given DPIO object. ++ * @desc: the returned DPIO descriptor. ++ * ++ * This function will return failure if the given dpaa2_io struct represents a ++ * service rather than an individual DPIO object, otherwise it returns zero and ++ * the given 'cfg' structure is filled in. ++ * ++ * Return 0 for success, or -EINVAL for failure. ++ */ ++int dpaa2_io_get_descriptor(struct dpaa2_io *obj, struct dpaa2_io_desc *desc); ++ ++/** ++ * dpaa2_io_poll() - Process any notifications and h/w-initiated events that ++ * are polling-driven. ++ * @obj: the given DPIO object. ++ * ++ * Obligatory for DPIO objects that have dpaa2_io_desc::will_poll non-zero. ++ * ++ * Return 0 for success, or -EINVAL for failure. ++ */ ++int dpaa2_io_poll(struct dpaa2_io *obj); ++ ++/** ++ * dpaa2_io_irq() - Process any notifications and h/w-initiated events that are ++ * irq-driven. ++ * @obj: the given DPIO object. ++ * ++ * Obligatory for DPIO objects that have dpaa2_io_desc::has_irq non-zero. ++ * ++ * Return IRQ_HANDLED for success, or -EINVAL for failure. ++ */ ++int dpaa2_io_irq(struct dpaa2_io *obj); ++ ++/** ++ * dpaa2_io_pause_poll() - Used to stop polling. ++ * @obj: the given DPIO object. ++ * ++ * If a polling application is going to stop polling for a period of time and ++ * supports interrupt processing, it can call this function to convert all ++ * processing to IRQ. (Eg. when sleeping.) ++ * ++ * Return -EINVAL. ++ */ ++int dpaa2_io_pause_poll(struct dpaa2_io *obj); ++ ++/** ++ * dpaa2_io_resume_poll() - Resume polling ++ * @obj: the given DPIO object. ++ * ++ * Return -EINVAL. ++ */ ++int dpaa2_io_resume_poll(struct dpaa2_io *obj); ++ ++/** ++ * dpaa2_io_service_notifications() - Get a mask of cpus that the DPIO service ++ * can receive notifications on. ++ * @s: the given DPIO object. ++ * @mask: the mask of cpus. ++ * ++ * Note that this is a run-time snapshot. If things like cpu-hotplug are ++ * supported in the target system, then an attempt to register notifications ++ * for a cpu that appears present in the given mask might fail if that cpu has ++ * gone offline in the mean time. ++ */ ++void dpaa2_io_service_notifications(struct dpaa2_io *s, cpumask_t *mask); ++ ++/** ++ * dpaa2_io_service_stashing - Get a mask of cpus that the DPIO service has stash ++ * affinity to. ++ * @s: the given DPIO object. ++ * @mask: the mask of cpus. ++ */ ++void dpaa2_io_service_stashing(struct dpaa2_io *s, cpumask_t *mask); ++ ++/** ++ * dpaa2_io_service_nonaffine() - Check the DPIO service's cpu affinity ++ * for stashing. ++ * @s: the given DPIO object. ++ * ++ * Return a boolean, whether or not the DPIO service has resources that have no ++ * particular cpu affinity for stashing. (Useful to know if you wish to operate ++ * on CPUs that the service has no affinity to, you would choose to use ++ * resources that are neutral, rather than affine to a different CPU.) Unlike ++ * other service-specific APIs, this one doesn't return an error if it is passed ++ * a non-service object. So don't do it. ++ */ ++int dpaa2_io_service_has_nonaffine(struct dpaa2_io *s); ++ ++/*************************/ ++/* Notification handling */ ++/*************************/ ++ ++/** ++ * struct dpaa2_io_notification_ctx - The DPIO notification context structure. ++ * @cb: the callback to be invoked when the notification arrives. ++ * @is_cdan: Zero/FALSE for FQDAN, non-zero/TRUE for CDAN. ++ * @id: FQID or channel ID, needed for rearm. ++ * @desired_cpu: the cpu on which the notifications will show up. ++ * @actual_cpu: the cpu the notification actually shows up. ++ * @migration_cb: callback function used for migration. ++ * @dpio_id: the dpio index. ++ * @qman64: the 64-bit context value shows up in the FQDAN/CDAN. ++ * @node: the list node. ++ * @dpio_private: the dpio object internal to dpio_service. ++ * ++ * When a FQDAN/CDAN registration is made (eg. by DPNI/DPCON/DPAI code), a ++ * context of the following type is used. The caller can embed it within a ++ * larger structure in order to add state that is tracked along with the ++ * notification (this may be useful when callbacks are invoked that pass this ++ * notification context as a parameter). ++ */ ++struct dpaa2_io_notification_ctx { ++ void (*cb)(struct dpaa2_io_notification_ctx *); ++ int is_cdan; ++ uint32_t id; ++ /* This specifies which cpu the user wants notifications to show up on ++ * (ie. to execute 'cb'). If notification-handling on that cpu is not ++ * available at the time of notification registration, the registration ++ * will fail. */ ++ int desired_cpu; ++ /* If the target platform supports cpu-hotplug or other features ++ * (related to power-management, one would expect) that can migrate IRQ ++ * handling of a given DPIO object, then this value will potentially be ++ * different to 'desired_cpu' at run-time. */ ++ int actual_cpu; ++ /* And if migration does occur and this callback is non-NULL, it will ++ * be invoked prior to any futher notification callbacks executing on ++ * 'newcpu'. Note that 'oldcpu' is what 'actual_cpu' was prior to the ++ * migration, and 'newcpu' is what it is now. Both could conceivably be ++ * different to 'desired_cpu'. */ ++ void (*migration_cb)(struct dpaa2_io_notification_ctx *, ++ int oldcpu, int newcpu); ++ /* These are returned from dpaa2_io_service_register(). ++ * 'dpio_id' is the dpaa2_io_desc::dpio_id value of the DPIO object that ++ * has been selected by the service for receiving the notifications. The ++ * caller can use this value in the MC command that attaches the FQ (or ++ * channel) of their DPNI (or DPCON, respectively) to this DPIO for ++ * notification-generation. ++ * 'qman64' is the 64-bit context value that needs to be sent in the ++ * same MC command in order to be programmed into the FQ or channel - ++ * this is the 64-bit value that shows up in the FQDAN/CDAN messages to ++ * the DPIO object, and the DPIO service specifies this value back to ++ * the caller so that the notifications that show up will be ++ * comprensible/demux-able to the DPIO service. */ ++ int dpio_id; ++ uint64_t qman64; ++ /* These fields are internal to the DPIO service once the context is ++ * registered. TBD: may require more internal state fields. */ ++ struct list_head node; ++ void *dpio_private; ++}; ++ ++/** ++ * dpaa2_io_service_register() - Prepare for servicing of FQDAN or CDAN ++ * notifications on the given DPIO service. ++ * @service: the given DPIO service. ++ * @ctx: the notification context. ++ * ++ * The MC command to attach the caller's DPNI/DPCON/DPAI device to a ++ * DPIO object is performed after this function is called. In that way, (a) the ++ * DPIO service is "ready" to handle a notification arrival (which might happen ++ * before the "attach" command to MC has returned control of execution back to ++ * the caller), and (b) the DPIO service can provide back to the caller the ++ * 'dpio_id' and 'qman64' parameters that it should pass along in the MC command ++ * in order for the DPNI/DPCON/DPAI resources to be configured to produce the ++ * right notification fields to the DPIO service. ++ * ++ * Return 0 for success, or -ENODEV for failure. ++ */ ++int dpaa2_io_service_register(struct dpaa2_io *service, ++ struct dpaa2_io_notification_ctx *ctx); ++ ++/** ++ * dpaa2_io_service_deregister - The opposite of 'register'. ++ * @service: the given DPIO service. ++ * @ctx: the notification context. ++ * ++ * Note that 'register' should be called *before* ++ * making the MC call to attach the notification-producing device to the ++ * notification-handling DPIO service, the 'unregister' function should be ++ * called *after* making the MC call to detach the notification-producing ++ * device. ++ * ++ * Return 0 for success. ++ */ ++int dpaa2_io_service_deregister(struct dpaa2_io *service, ++ struct dpaa2_io_notification_ctx *ctx); ++ ++/** ++ * dpaa2_io_service_rearm() - Rearm the notification for the given DPIO service. ++ * @service: the given DPIO service. ++ * @ctx: the notification context. ++ * ++ * Once a FQDAN/CDAN has been produced, the corresponding FQ/channel is ++ * considered "disarmed". Ie. the user can issue pull dequeue operations on that ++ * traffic source for as long as it likes. Eventually it may wish to "rearm" ++ * that source to allow it to produce another FQDAN/CDAN, that's what this ++ * function achieves. ++ * ++ * Return 0 for success, or -ENODEV if no service available, -EBUSY/-EIO for not ++ * being able to implement the rearm the notifiaton due to setting CDAN or ++ * scheduling fq. ++ */ ++int dpaa2_io_service_rearm(struct dpaa2_io *service, ++ struct dpaa2_io_notification_ctx *ctx); ++ ++/** ++ * dpaa2_io_from_registration() - Get the DPIO object from the given notification ++ * context. ++ * @ctx: the given notifiation context. ++ * @ret: the returned DPIO object. ++ * ++ * Like 'dpaa2_io_service_get_persistent()' (see below), except that the ++ * returned handle is not selected based on a 'cpu' argument, but is the same ++ * DPIO object that the given notification context is registered against. The ++ * returned handle carries a reference count, so a corresponding dpaa2_io_down() ++ * would be required when the reference is no longer needed. ++ * ++ * Return 0 for success, or -EINVAL for failure. ++ */ ++int dpaa2_io_from_registration(struct dpaa2_io_notification_ctx *ctx, ++ struct dpaa2_io **ret); ++ ++/**********************************/ ++/* General usage of DPIO services */ ++/**********************************/ ++ ++/** ++ * dpaa2_io_service_get_persistent() - Get the DPIO resource from the given ++ * notification context and cpu. ++ * @service: the DPIO service. ++ * @cpu: the cpu that the DPIO resource has stashing affinity to. ++ * @ret: the returned DPIO resource. ++ * ++ * The various DPIO interfaces can accept a "struct dpaa2_io" handle that refers ++ * to an individual DPIO object or to a whole service. In the latter case, an ++ * internal choice is made for each operation. This function supports the former ++ * case, by selecting an individual DPIO object *from* the service in order for ++ * it to be used multiple times to provide "persistence". The returned handle ++ * also carries a reference count, so a corresponding dpaa2_io_down() would be ++ * required when the reference is no longer needed. Note, a parameter of -1 for ++ * 'cpu' will select a DPIO resource that has no particular stashing affinity to ++ * any cpu (eg. one that stashes to platform cache). ++ * ++ * Return 0 for success, or -ENODEV for failure. ++ */ ++int dpaa2_io_service_get_persistent(struct dpaa2_io *service, int cpu, ++ struct dpaa2_io **ret); ++ ++/*****************/ ++/* Pull dequeues */ ++/*****************/ ++ ++/** ++ * dpaa2_io_service_pull_fq() - pull dequeue functions from a fq. ++ * @d: the given DPIO service. ++ * @fqid: the given frame queue id. ++ * @s: the dpaa2_io_store object for the result. ++ * ++ * To support DCA/order-preservation, it will be necessary to support an ++ * alternative form, because they must ultimately dequeue to DQRR rather than a ++ * user-supplied dpaa2_io_store. Furthermore, those dequeue results will ++ * "complete" using a caller-provided callback (from DQRR processing) rather ++ * than the caller explicitly looking at their dpaa2_io_store for results. Eg. ++ * the alternative form will likely take a callback parameter rather than a ++ * store parameter. Ignoring it for now to keep the picture clearer. ++ * ++ * Return 0 for success, or error code for failure. ++ */ ++int dpaa2_io_service_pull_fq(struct dpaa2_io *d, uint32_t fqid, ++ struct dpaa2_io_store *s); ++ ++/** ++ * dpaa2_io_service_pull_channel() - pull dequeue functions from a channel. ++ * @d: the given DPIO service. ++ * @channelid: the given channel id. ++ * @s: the dpaa2_io_store object for the result. ++ * ++ * To support DCA/order-preservation, it will be necessary to support an ++ * alternative form, because they must ultimately dequeue to DQRR rather than a ++ * user-supplied dpaa2_io_store. Furthermore, those dequeue results will ++ * "complete" using a caller-provided callback (from DQRR processing) rather ++ * than the caller explicitly looking at their dpaa2_io_store for results. Eg. ++ * the alternative form will likely take a callback parameter rather than a ++ * store parameter. Ignoring it for now to keep the picture clearer. ++ * ++ * Return 0 for success, or error code for failure. ++ */ ++int dpaa2_io_service_pull_channel(struct dpaa2_io *d, uint32_t channelid, ++ struct dpaa2_io_store *s); ++ ++/************/ ++/* Enqueues */ ++/************/ ++ ++/** ++ * dpaa2_io_service_enqueue_fq() - Enqueue a frame to a frame queue. ++ * @d: the given DPIO service. ++ * @fqid: the given frame queue id. ++ * @fd: the frame descriptor which is enqueued. ++ * ++ * This definition bypasses some features that are not expected to be priority-1 ++ * features, and may not be needed at all via current assumptions (QBMan's ++ * feature set is wider than the MC object model is intendeding to support, ++ * initially at least). Plus, keeping them out (for now) keeps the API view ++ * simpler. Missing features are; ++ * - enqueue confirmation (results DMA'd back to the user) ++ * - ORP ++ * - DCA/order-preservation (see note in "pull dequeues") ++ * - enqueue consumption interrupts ++ * ++ * Return 0 for successful enqueue, or -EBUSY if the enqueue ring is not ready, ++ * or -ENODEV if there is no dpio service. ++ */ ++int dpaa2_io_service_enqueue_fq(struct dpaa2_io *d, ++ uint32_t fqid, ++ const struct dpaa2_fd *fd); ++ ++/** ++ * dpaa2_io_service_enqueue_qd() - Enqueue a frame to a QD. ++ * @d: the given DPIO service. ++ * @qdid: the given queuing destination id. ++ * @prio: the given queuing priority. ++ * @qdbin: the given queuing destination bin. ++ * @fd: the frame descriptor which is enqueued. ++ * ++ * This definition bypasses some features that are not expected to be priority-1 ++ * features, and may not be needed at all via current assumptions (QBMan's ++ * feature set is wider than the MC object model is intendeding to support, ++ * initially at least). Plus, keeping them out (for now) keeps the API view ++ * simpler. Missing features are; ++ * - enqueue confirmation (results DMA'd back to the user) ++ * - ORP ++ * - DCA/order-preservation (see note in "pull dequeues") ++ * - enqueue consumption interrupts ++ * ++ * Return 0 for successful enqueue, or -EBUSY if the enqueue ring is not ready, ++ * or -ENODEV if there is no dpio service. ++ */ ++int dpaa2_io_service_enqueue_qd(struct dpaa2_io *d, ++ uint32_t qdid, uint8_t prio, uint16_t qdbin, ++ const struct dpaa2_fd *fd); ++ ++/*******************/ ++/* Buffer handling */ ++/*******************/ ++ ++/** ++ * dpaa2_io_service_release() - Release buffers to a buffer pool. ++ * @d: the given DPIO object. ++ * @bpid: the buffer pool id. ++ * @buffers: the buffers to be released. ++ * @num_buffers: the number of the buffers to be released. ++ * ++ * Return 0 for success, and negative error code for failure. ++ */ ++int dpaa2_io_service_release(struct dpaa2_io *d, ++ uint32_t bpid, ++ const uint64_t *buffers, ++ unsigned int num_buffers); ++ ++/** ++ * dpaa2_io_service_acquire() - Acquire buffers from a buffer pool. ++ * @d: the given DPIO object. ++ * @bpid: the buffer pool id. ++ * @buffers: the buffer addresses for acquired buffers. ++ * @num_buffers: the expected number of the buffers to acquire. ++ * ++ * Return a negative error code if the command failed, otherwise it returns ++ * the number of buffers acquired, which may be less than the number requested. ++ * Eg. if the buffer pool is empty, this will return zero. ++ */ ++int dpaa2_io_service_acquire(struct dpaa2_io *d, ++ uint32_t bpid, ++ uint64_t *buffers, ++ unsigned int num_buffers); ++ ++/***************/ ++/* DPIO stores */ ++/***************/ ++ ++/* These are reusable memory blocks for retrieving dequeue results into, and to ++ * assist with parsing those results once they show up. They also hide the ++ * details of how to use "tokens" to make detection of DMA results possible (ie. ++ * comparing memory before the DMA and after it) while minimising the needless ++ * clearing/rewriting of those memory locations between uses. ++ */ ++ ++/** ++ * dpaa2_io_store_create() - Create the dma memory storage for dequeue ++ * result. ++ * @max_frames: the maximum number of dequeued result for frames, must be <= 16. ++ * @dev: the device to allow mapping/unmapping the DMAable region. ++ * ++ * Constructor - max_frames must be <= 16. The user provides the ++ * device struct to allow mapping/unmapping of the DMAable region. Area for ++ * storage will be allocated during create. The size of this storage is ++ * "max_frames*sizeof(struct dpaa2_dq)". The 'dpaa2_io_store' returned is a ++ * wrapper structure allocated within the DPIO code, which owns and manages ++ * allocated store. ++ * ++ * Return dpaa2_io_store struct for successfuly created storage memory, or NULL ++ * if not getting the stroage for dequeue result in create API. ++ */ ++struct dpaa2_io_store *dpaa2_io_store_create(unsigned int max_frames, ++ struct device *dev); ++ ++/** ++ * dpaa2_io_store_destroy() - Destroy the dma memory storage for dequeue ++ * result. ++ * @s: the storage memory to be destroyed. ++ * ++ * Frees to specified storage memory. ++ */ ++void dpaa2_io_store_destroy(struct dpaa2_io_store *s); ++ ++/** ++ * dpaa2_io_store_next() - Determine when the next dequeue result is available. ++ * @s: the dpaa2_io_store object. ++ * @is_last: indicate whether this is the last frame in the pull command. ++ * ++ * Once dpaa2_io_store has been passed to a function that performs dequeues to ++ * it, like dpaa2_ni_rx(), this function can be used to determine when the next ++ * frame result is available. Once this function returns non-NULL, a subsequent ++ * call to it will try to find the *next* dequeue result. ++ * ++ * Note that if a pull-dequeue has a null result because the target FQ/channel ++ * was empty, then this function will return NULL rather than expect the caller ++ * to always check for this on his own side. As such, "is_last" can be used to ++ * differentiate between "end-of-empty-dequeue" and "still-waiting". ++ * ++ * Return dequeue result for a valid dequeue result, or NULL for empty dequeue. ++ */ ++struct dpaa2_dq *dpaa2_io_store_next(struct dpaa2_io_store *s, int *is_last); ++ ++#ifdef CONFIG_FSL_QBMAN_DEBUG ++/** ++ * dpaa2_io_query_fq_count() - Get the frame and byte count for a given fq. ++ * @d: the given DPIO object. ++ * @fqid: the id of frame queue to be queried. ++ * @fcnt: the queried frame count. ++ * @bcnt: the queried byte count. ++ * ++ * Knowing the FQ count at run-time can be useful in debugging situations. ++ * The instantaneous frame- and byte-count are hereby returned. ++ * ++ * Return 0 for a successful query, and negative error code if query fails. ++ */ ++int dpaa2_io_query_fq_count(struct dpaa2_io *d, uint32_t fqid, ++ uint32_t *fcnt, uint32_t *bcnt); ++ ++/** ++ * dpaa2_io_query_bp_count() - Query the number of buffers currenty in a ++ * buffer pool. ++ * @d: the given DPIO object. ++ * @bpid: the index of buffer pool to be queried. ++ * @num: the queried number of buffers in the buffer pool. ++ * ++ * Return 0 for a sucessful query, and negative error code if query fails. ++ */ ++int dpaa2_io_query_bp_count(struct dpaa2_io *d, uint32_t bpid, ++ uint32_t *num); ++#endif ++#endif /* __FSL_DPAA2_IO_H */ +diff --git a/drivers/staging/fsl-mc/include/mc-cmd.h b/drivers/staging/fsl-mc/include/mc-cmd.h +new file mode 100644 +index 0000000..00f0b74 +--- /dev/null ++++ b/drivers/staging/fsl-mc/include/mc-cmd.h +@@ -0,0 +1,133 @@ ++/* Copyright 2013-2015 Freescale Semiconductor Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * * Neither the name of the above-listed copyright holders nor the ++ * names of any contributors may be used to endorse or promote products ++ * derived from this software without specific prior written permission. ++ * ++ * ++ * ALTERNATIVELY, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") as published by the Free Software ++ * Foundation, either version 2 of that License or (at your option) any ++ * later version. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE ++ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++#ifndef __FSL_MC_CMD_H ++#define __FSL_MC_CMD_H ++ ++#define MC_CMD_NUM_OF_PARAMS 7 ++ ++#define MAKE_UMASK64(_width) \ ++ ((uint64_t)((_width) < 64 ? ((uint64_t)1 << (_width)) - 1 : \ ++ (uint64_t)-1)) ++ ++static inline uint64_t mc_enc(int lsoffset, int width, uint64_t val) ++{ ++ return (uint64_t)(((uint64_t)val & MAKE_UMASK64(width)) << lsoffset); ++} ++ ++static inline uint64_t mc_dec(uint64_t val, int lsoffset, int width) ++{ ++ return (uint64_t)((val >> lsoffset) & MAKE_UMASK64(width)); ++} ++ ++struct mc_command { ++ uint64_t header; ++ uint64_t params[MC_CMD_NUM_OF_PARAMS]; ++}; ++ ++enum mc_cmd_status { ++ MC_CMD_STATUS_OK = 0x0, /* Completed successfully */ ++ MC_CMD_STATUS_READY = 0x1, /* Ready to be processed */ ++ MC_CMD_STATUS_AUTH_ERR = 0x3, /* Authentication error */ ++ MC_CMD_STATUS_NO_PRIVILEGE = 0x4, /* No privilege */ ++ MC_CMD_STATUS_DMA_ERR = 0x5, /* DMA or I/O error */ ++ MC_CMD_STATUS_CONFIG_ERR = 0x6, /* Configuration error */ ++ MC_CMD_STATUS_TIMEOUT = 0x7, /* Operation timed out */ ++ MC_CMD_STATUS_NO_RESOURCE = 0x8, /* No resources */ ++ MC_CMD_STATUS_NO_MEMORY = 0x9, /* No memory available */ ++ MC_CMD_STATUS_BUSY = 0xA, /* Device is busy */ ++ MC_CMD_STATUS_UNSUPPORTED_OP = 0xB, /* Unsupported operation */ ++ MC_CMD_STATUS_INVALID_STATE = 0xC /* Invalid state */ ++}; ++ ++/* ++ * MC command flags ++ */ ++ ++/* High priority flag */ ++#define MC_CMD_FLAG_PRI 0x00008000 ++/* Command completion flag */ ++#define MC_CMD_FLAG_INTR_DIS 0x01000000 ++ ++/* TODO Remove following two defines after completion of flib 8.0.0 ++integration */ ++#define MC_CMD_PRI_LOW 0 /*!< Low Priority command indication */ ++#define MC_CMD_PRI_HIGH 1 /*!< High Priority command indication */ ++ ++#define MC_CMD_HDR_CMDID_O 52 /* Command ID field offset */ ++#define MC_CMD_HDR_CMDID_S 12 /* Command ID field size */ ++#define MC_CMD_HDR_TOKEN_O 38 /* Token field offset */ ++#define MC_CMD_HDR_TOKEN_S 10 /* Token field size */ ++#define MC_CMD_HDR_STATUS_O 16 /* Status field offset */ ++#define MC_CMD_HDR_STATUS_S 8 /* Status field size*/ ++#define MC_CMD_HDR_FLAGS_O 0 /* Flags field offset */ ++#define MC_CMD_HDR_FLAGS_S 32 /* Flags field size*/ ++#define MC_CMD_HDR_FLAGS_MASK 0xFF00FF00 /* Command flags mask */ ++ ++#define MC_CMD_HDR_READ_STATUS(_hdr) \ ++ ((enum mc_cmd_status)mc_dec((_hdr), \ ++ MC_CMD_HDR_STATUS_O, MC_CMD_HDR_STATUS_S)) ++ ++#define MC_CMD_HDR_READ_TOKEN(_hdr) \ ++ ((uint16_t)mc_dec((_hdr), MC_CMD_HDR_TOKEN_O, MC_CMD_HDR_TOKEN_S)) ++ ++#define MC_CMD_HDR_READ_FLAGS(_hdr) \ ++ ((uint32_t)mc_dec((_hdr), MC_CMD_HDR_FLAGS_O, MC_CMD_HDR_FLAGS_S)) ++ ++#define MC_PREP_OP(_ext, _param, _offset, _width, _type, _arg) \ ++ ((_ext)[_param] |= cpu_to_le64(mc_enc((_offset), (_width), _arg))) ++ ++#define MC_EXT_OP(_ext, _param, _offset, _width, _type, _arg) \ ++ (_arg = (_type)mc_dec(cpu_to_le64(_ext[_param]), (_offset), (_width))) ++ ++#define MC_CMD_OP(_cmd, _param, _offset, _width, _type, _arg) \ ++ ((_cmd).params[_param] |= mc_enc((_offset), (_width), _arg)) ++ ++#define MC_RSP_OP(_cmd, _param, _offset, _width, _type, _arg) \ ++ (_arg = (_type)mc_dec(_cmd.params[_param], (_offset), (_width))) ++ ++static inline uint64_t mc_encode_cmd_header(uint16_t cmd_id, ++ uint32_t cmd_flags, ++ uint16_t token) ++{ ++ uint64_t hdr; ++ ++ hdr = mc_enc(MC_CMD_HDR_CMDID_O, MC_CMD_HDR_CMDID_S, cmd_id); ++ hdr |= mc_enc(MC_CMD_HDR_FLAGS_O, MC_CMD_HDR_FLAGS_S, ++ (cmd_flags & MC_CMD_HDR_FLAGS_MASK)); ++ hdr |= mc_enc(MC_CMD_HDR_TOKEN_O, MC_CMD_HDR_TOKEN_S, token); ++ hdr |= mc_enc(MC_CMD_HDR_STATUS_O, MC_CMD_HDR_STATUS_S, ++ MC_CMD_STATUS_READY); ++ ++ return hdr; ++} ++ ++#endif /* __FSL_MC_CMD_H */ +diff --git a/drivers/staging/fsl-mc/include/mc-private.h b/drivers/staging/fsl-mc/include/mc-private.h +new file mode 100644 +index 0000000..58ed441 +--- /dev/null ++++ b/drivers/staging/fsl-mc/include/mc-private.h +@@ -0,0 +1,168 @@ ++/* ++ * Freescale Management Complex (MC) bus private declarations ++ * ++ * Copyright (C) 2014 Freescale Semiconductor, Inc. ++ * Author: German Rivera ++ * ++ * This file is licensed under the terms of the GNU General Public ++ * License version 2. This program is licensed "as is" without any ++ * warranty of any kind, whether express or implied. ++ */ ++#ifndef _FSL_MC_PRIVATE_H_ ++#define _FSL_MC_PRIVATE_H_ ++ ++#include "../include/mc.h" ++#include ++#include ++ ++#define FSL_MC_DPRC_DRIVER_NAME "fsl_mc_dprc" ++ ++#define FSL_MC_DEVICE_MATCH(_mc_dev, _obj_desc) \ ++ (strcmp((_mc_dev)->obj_desc.type, (_obj_desc)->type) == 0 && \ ++ (_mc_dev)->obj_desc.id == (_obj_desc)->id) ++ ++#define FSL_MC_IS_ALLOCATABLE(_obj_type) \ ++ (strcmp(_obj_type, "dpbp") == 0 || \ ++ strcmp(_obj_type, "dpmcp") == 0 || \ ++ strcmp(_obj_type, "dpcon") == 0) ++ ++/** ++ * Maximum number of total IRQs that can be pre-allocated for an MC bus' ++ * IRQ pool ++ */ ++#define FSL_MC_IRQ_POOL_MAX_TOTAL_IRQS 256 ++ ++/** ++ * Maximum number of extra IRQs pre-reallocated for an MC bus' IRQ pool, ++ * to be used by dynamically created MC objects ++ */ ++#define FSL_MC_IRQ_POOL_MAX_EXTRA_IRQS 64 ++ ++/** ++ * struct fsl_mc - Private data of a "fsl,qoriq-mc" platform device ++ * @root_mc_bus_dev: MC object device representing the root DPRC ++ * @irq_domain: IRQ domain for the fsl-mc bus type ++ * @gic_supported: boolean flag that indicates if the GIC interrupt controller ++ * is supported. ++ * @num_translation_ranges: number of entries in addr_translation_ranges ++ * @addr_translation_ranges: array of bus to system address translation ranges ++ */ ++struct fsl_mc { ++ struct fsl_mc_device *root_mc_bus_dev; ++ struct irq_domain *irq_domain; ++ bool gic_supported; ++ uint8_t num_translation_ranges; ++ struct fsl_mc_addr_translation_range *translation_ranges; ++}; ++ ++/** ++ * enum mc_region_types - Types of MC MMIO regions ++ */ ++enum fsl_mc_region_types { ++ FSL_MC_PORTAL = 0x0, ++ FSL_QBMAN_PORTAL, ++ ++ /* ++ * New offset types must be added above this entry ++ */ ++ FSL_NUM_MC_OFFSET_TYPES ++}; ++ ++/** ++ * struct fsl_mc_addr_translation_range - bus to system address translation ++ * range ++ * @mc_region_type: Type of MC region for the range being translated ++ * @start_mc_offset: Start MC offset of the range being translated ++ * @end_mc_offset: MC offset of the first byte after the range (last MC ++ * offset of the range is end_mc_offset - 1) ++ * @start_phys_addr: system physical address corresponding to start_mc_addr ++ */ ++struct fsl_mc_addr_translation_range { ++ enum fsl_mc_region_types mc_region_type; ++ uint64_t start_mc_offset; ++ uint64_t end_mc_offset; ++ phys_addr_t start_phys_addr; ++}; ++ ++/** ++ * struct fsl_mc_resource_pool - Pool of MC resources of a given ++ * type ++ * @type: type of resources in the pool ++ * @max_count: maximum number of resources in the pool ++ * @free_count: number of free resources in the pool ++ * @mutex: mutex to serialize access to the pool's free list ++ * @free_list: anchor node of list of free resources in the pool ++ * @mc_bus: pointer to the MC bus that owns this resource pool ++ */ ++struct fsl_mc_resource_pool { ++ enum fsl_mc_pool_type type; ++ int16_t max_count; ++ int16_t free_count; ++ struct mutex mutex; /* serializes access to free_list */ ++ struct list_head free_list; ++ struct fsl_mc_bus *mc_bus; ++}; ++ ++/** ++ * struct fsl_mc_bus - logical bus that corresponds to a physical DPRC ++ * @mc_dev: fsl-mc device for the bus device itself. ++ * @resource_pools: array of resource pools (one pool per resource type) ++ * for this MC bus. These resources represent allocatable entities ++ * from the physical DPRC. ++ * @atomic_mc_io: mc_io object to be used to send DPRC commands to the MC ++ * in atomic context (e.g., when programming MSIs in program_msi_at_mc()). ++ * @atomic_dprc_handle: DPRC handle opened using the atomic_mc_io's portal. ++ * @irq_resources: Pointer to array of IRQ objects for the IRQ pool. ++ * @scan_mutex: Serializes bus scanning ++ * @dprc_attr: DPRC attributes ++ */ ++struct fsl_mc_bus { ++ struct fsl_mc_device mc_dev; ++ struct fsl_mc_resource_pool resource_pools[FSL_MC_NUM_POOL_TYPES]; ++ struct fsl_mc_device_irq *irq_resources; ++ struct fsl_mc_io *atomic_mc_io; ++ uint16_t atomic_dprc_handle; ++ struct mutex scan_mutex; /* serializes bus scanning */ ++ struct dprc_attributes dprc_attr; ++}; ++ ++#define to_fsl_mc_bus(_mc_dev) \ ++ container_of(_mc_dev, struct fsl_mc_bus, mc_dev) ++ ++int __must_check fsl_mc_device_add(struct dprc_obj_desc *obj_desc, ++ struct fsl_mc_io *mc_io, ++ struct device *parent_dev, ++ const char *driver_override, ++ struct fsl_mc_device **new_mc_dev); ++ ++void fsl_mc_device_remove(struct fsl_mc_device *mc_dev); ++ ++int dprc_scan_objects(struct fsl_mc_device *mc_bus_dev, ++ const char *driver_override, ++ unsigned int *total_irq_count); ++ ++int __init dprc_driver_init(void); ++ ++void dprc_driver_exit(void); ++ ++int __init fsl_mc_allocator_driver_init(void); ++ ++void __exit fsl_mc_allocator_driver_exit(void); ++ ++int __must_check fsl_mc_resource_allocate(struct fsl_mc_bus *mc_bus, ++ enum fsl_mc_pool_type pool_type, ++ struct fsl_mc_resource ++ **new_resource); ++ ++void fsl_mc_resource_free(struct fsl_mc_resource *resource); ++ ++int __must_check fsl_mc_populate_irq_pool(struct fsl_mc_bus *mc_bus, ++ unsigned int irq_count); ++ ++void fsl_mc_cleanup_irq_pool(struct fsl_mc_bus *mc_bus); ++ ++void dprc_init_all_resource_pools(struct fsl_mc_device *mc_bus_dev); ++ ++void dprc_cleanup_all_resource_pools(struct fsl_mc_device *mc_bus_dev); ++ ++#endif /* _FSL_MC_PRIVATE_H_ */ +diff --git a/drivers/staging/fsl-mc/include/mc-sys.h b/drivers/staging/fsl-mc/include/mc-sys.h +new file mode 100644 +index 0000000..b08df85 +--- /dev/null ++++ b/drivers/staging/fsl-mc/include/mc-sys.h +@@ -0,0 +1,128 @@ ++/* Copyright 2013-2014 Freescale Semiconductor Inc. ++ * ++ * Interface of the I/O services to send MC commands to the MC hardware ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * * Neither the name of the above-listed copyright holders nor the ++ * names of any contributors may be used to endorse or promote products ++ * derived from this software without specific prior written permission. ++ * ++ * ++ * ALTERNATIVELY, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") as published by the Free Software ++ * Foundation, either version 2 of that License or (at your option) any ++ * later version. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE ++ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++#ifndef _FSL_MC_SYS_H ++#define _FSL_MC_SYS_H ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++/** ++ * Bit masks for a MC I/O object (struct fsl_mc_io) flags ++ */ ++#define FSL_MC_IO_ATOMIC_CONTEXT_PORTAL 0x0001 ++ ++struct fsl_mc_resource; ++struct mc_command; ++ ++/** ++ * struct fsl_mc_io - MC I/O object to be passed-in to mc_send_command() ++ * @dev: device associated with this Mc I/O object ++ * @flags: flags for mc_send_command() ++ * @portal_size: MC command portal size in bytes ++ * @portal_phys_addr: MC command portal physical address ++ * @portal_virt_addr: MC command portal virtual address ++ * @dpmcp_dev: pointer to the DPMCP device associated with the MC portal. ++ * @mc_command_done_irq_armed: Flag indicating that the MC command done IRQ ++ * is currently armed. ++ * @mc_command_done_completion: Completion variable to be signaled when an MC ++ * command sent to the MC fw is completed. ++ * ++ * Fields are only meaningful if the FSL_MC_IO_ATOMIC_CONTEXT_PORTAL flag is not ++ * set: ++ * @mutex: Mutex to serialize mc_send_command() calls that use the same MC ++ * portal, if the fsl_mc_io object was created with the ++ * FSL_MC_IO_ATOMIC_CONTEXT_PORTAL flag off. mc_send_command() calls for this ++ * fsl_mc_io object must be made only from non-atomic context. ++ * @mc_command_done_completion: Linux completion variable to be signaled ++ * when a DPMCP command completion interrupts is received. ++ * @mc_command_done_irq_armed: Boolean flag that indicates if interrupts have ++ * been successfully configured for the corresponding DPMCP object. ++ * ++ * Fields are only meaningful if the FSL_MC_IO_ATOMIC_CONTEXT_PORTAL flag is ++ * set: ++ * @spinlock: Spinlock to serialize mc_send_command() calls that use the same MC ++ * portal, if the fsl_mc_io object was created with the ++ * FSL_MC_IO_ATOMIC_CONTEXT_PORTAL flag on. mc_send_command() calls for this ++ * fsl_mc_io object can be made from atomic or non-atomic context. ++ */ ++struct fsl_mc_io { ++ struct device *dev; ++ uint16_t flags; ++ uint16_t portal_size; ++ phys_addr_t portal_phys_addr; ++ void __iomem *portal_virt_addr; ++ struct fsl_mc_device *dpmcp_dev; ++ union { ++ /* ++ * These fields are only meaningful if the ++ * FSL_MC_IO_ATOMIC_CONTEXT_PORTAL flag is not set ++ */ ++ struct { ++ struct mutex mutex; /* serializes mc_send_command() */ ++ struct completion mc_command_done_completion; ++ bool mc_command_done_irq_armed; ++ }; ++ ++ /* ++ * This field is only meaningful if the ++ * FSL_MC_IO_ATOMIC_CONTEXT_PORTAL flag is set ++ */ ++ spinlock_t spinlock; /* serializes mc_send_command() */ ++ }; ++}; ++ ++int __must_check fsl_create_mc_io(struct device *dev, ++ phys_addr_t mc_portal_phys_addr, ++ uint32_t mc_portal_size, ++ struct fsl_mc_device *dpmcp_dev, ++ uint32_t flags, struct fsl_mc_io **new_mc_io); ++ ++void fsl_destroy_mc_io(struct fsl_mc_io *mc_io); ++ ++int fsl_mc_io_set_dpmcp(struct fsl_mc_io *mc_io, ++ struct fsl_mc_device *dpmcp_dev); ++ ++void fsl_mc_io_unset_dpmcp(struct fsl_mc_io *mc_io); ++ ++int fsl_mc_io_setup_dpmcp_irq(struct fsl_mc_io *mc_io); ++ ++int mc_send_command(struct fsl_mc_io *mc_io, struct mc_command *cmd); ++ ++#endif /* _FSL_MC_SYS_H */ +diff --git a/drivers/staging/fsl-mc/include/mc.h b/drivers/staging/fsl-mc/include/mc.h +new file mode 100644 +index 0000000..bbeb121 +--- /dev/null ++++ b/drivers/staging/fsl-mc/include/mc.h +@@ -0,0 +1,244 @@ ++/* ++ * Freescale Management Complex (MC) bus public interface ++ * ++ * Copyright (C) 2014 Freescale Semiconductor, Inc. ++ * Author: German Rivera ++ * ++ * This file is licensed under the terms of the GNU General Public ++ * License version 2. This program is licensed "as is" without any ++ * warranty of any kind, whether express or implied. ++ */ ++#ifndef _FSL_MC_H_ ++#define _FSL_MC_H_ ++ ++#include ++#include ++#include ++#include ++#include ++#include "../include/dprc.h" ++ ++#define FSL_MC_VENDOR_FREESCALE 0x1957 ++ ++struct fsl_mc_device; ++struct fsl_mc_io; ++ ++/** ++ * struct fsl_mc_driver - MC object device driver object ++ * @driver: Generic device driver ++ * @match_id_table: table of supported device matching Ids ++ * @probe: Function called when a device is added ++ * @remove: Function called when a device is removed ++ * @shutdown: Function called at shutdown time to quiesce the device ++ * @suspend: Function called when a device is stopped ++ * @resume: Function called when a device is resumed ++ * ++ * Generic DPAA device driver object for device drivers that are registered ++ * with a DPRC bus. This structure is to be embedded in each device-specific ++ * driver structure. ++ */ ++struct fsl_mc_driver { ++ struct device_driver driver; ++ const struct fsl_mc_device_match_id *match_id_table; ++ int (*probe)(struct fsl_mc_device *dev); ++ int (*remove)(struct fsl_mc_device *dev); ++ void (*shutdown)(struct fsl_mc_device *dev); ++ int (*suspend)(struct fsl_mc_device *dev, pm_message_t state); ++ int (*resume)(struct fsl_mc_device *dev); ++}; ++ ++#define to_fsl_mc_driver(_drv) \ ++ container_of(_drv, struct fsl_mc_driver, driver) ++ ++/** ++ * struct fsl_mc_device_match_id - MC object device Id entry for driver matching ++ * @vendor: vendor ID ++ * @obj_type: MC object type ++ * @ver_major: MC object version major number ++ * @ver_minor: MC object version minor number ++ * ++ * Type of entries in the "device Id" table for MC object devices supported by ++ * a MC object device driver. The last entry of the table has vendor set to 0x0 ++ */ ++struct fsl_mc_device_match_id { ++ uint16_t vendor; ++ const char obj_type[16]; ++ uint32_t ver_major; ++ uint32_t ver_minor; ++}; ++ ++/** ++ * enum fsl_mc_pool_type - Types of allocatable MC bus resources ++ * ++ * Entries in these enum are used as indices in the array of resource ++ * pools of an fsl_mc_bus object. ++ */ ++enum fsl_mc_pool_type { ++ FSL_MC_POOL_DPMCP = 0x0, /* corresponds to "dpmcp" in the MC */ ++ FSL_MC_POOL_DPBP, /* corresponds to "dpbp" in the MC */ ++ FSL_MC_POOL_DPCON, /* corresponds to "dpcon" in the MC */ ++ FSL_MC_POOL_IRQ, ++ ++ /* ++ * NOTE: New resource pool types must be added before this entry ++ */ ++ FSL_MC_NUM_POOL_TYPES ++}; ++ ++/** ++ * struct fsl_mc_resource - MC generic resource ++ * @type: type of resource ++ * @id: unique MC resource Id within the resources of the same type ++ * @data: pointer to resource-specific data if the resource is currently ++ * allocated, or NULL if the resource is not currently allocated. ++ * @parent_pool: pointer to the parent resource pool from which this ++ * resource is allocated from. ++ * @node: Node in the free list of the corresponding resource pool ++ * ++ * NOTE: This structure is to be embedded as a field of specific ++ * MC resource structures. ++ */ ++struct fsl_mc_resource { ++ enum fsl_mc_pool_type type; ++ int32_t id; ++ void *data; ++ struct fsl_mc_resource_pool *parent_pool; ++ struct list_head node; ++}; ++ ++/** ++ * struct fsl_mc_device_irq - MC object device message-based interrupt ++ * @msi_paddr: message-based interrupt physical address ++ * @msi_value: message-based interrupt data value ++ * @irq_number: Linux IRQ number assigned to the interrupt ++ * @mc_dev: MC object device that owns this interrupt ++ * @dev_irq_index: device-relative IRQ index ++ * @resource: MC generic resource associated with the interrupt ++ */ ++struct fsl_mc_device_irq { ++ phys_addr_t msi_paddr; ++ uint32_t msi_value; ++ uint32_t irq_number; ++ struct fsl_mc_device *mc_dev; ++ uint8_t dev_irq_index; ++ struct fsl_mc_resource resource; ++}; ++ ++#define to_fsl_mc_irq(_mc_resource) \ ++ container_of(_mc_resource, struct fsl_mc_device_irq, resource) ++ ++/** ++ * Bit masks for a MC object device (struct fsl_mc_device) flags ++ */ ++#define FSL_MC_IS_DPRC 0x0001 ++ ++/** ++ * root dprc's parent is a platform device ++ * that platform device's bus type is platform_bus_type. ++ */ ++#define is_root_dprc(dev) \ ++ ((to_fsl_mc_device(dev)->flags & FSL_MC_IS_DPRC) && \ ++ ((dev)->bus == &fsl_mc_bus_type) && \ ++ ((dev)->parent->bus == &platform_bus_type)) ++ ++/** ++ * Default DMA mask for devices on a fsl-mc bus ++ */ ++#define FSL_MC_DEFAULT_DMA_MASK (~0ULL) ++ ++/** ++ * struct fsl_mc_device - MC object device object ++ * @dev: Linux driver model device object ++ * @dma_mask: Default DMA mask ++ * @flags: MC object device flags ++ * @icid: Isolation context ID for the device ++ * @mc_handle: MC handle for the corresponding MC object opened ++ * @mc_io: Pointer to MC IO object assigned to this device or ++ * NULL if none. ++ * @obj_desc: MC description of the DPAA device ++ * @regions: pointer to array of MMIO region entries ++ * @irqs: pointer to array of pointers to interrupts allocated to this device ++ * @resource: generic resource associated with this MC object device, if any. ++ * @driver_override: Driver name to force a match ++ * ++ * Generic device object for MC object devices that are "attached" to a ++ * MC bus. ++ * ++ * NOTES: ++ * - For a non-DPRC object its icid is the same as its parent DPRC's icid. ++ * - The SMMU notifier callback gets invoked after device_add() has been ++ * called for an MC object device, but before the device-specific probe ++ * callback gets called. ++ * - DP_OBJ_DPRC objects are the only MC objects that have built-in MC ++ * portals. For all other MC objects, their device drivers are responsible for ++ * allocating MC portals for them by calling fsl_mc_portal_allocate(). ++ * - Some types of MC objects (e.g., DP_OBJ_DPBP, DP_OBJ_DPCON) are ++ * treated as resources that can be allocated/deallocated from the ++ * corresponding resource pool in the object's parent DPRC, using the ++ * fsl_mc_object_allocate()/fsl_mc_object_free() functions. These MC objects ++ * are known as "allocatable" objects. For them, the corresponding ++ * fsl_mc_device's 'resource' points to the associated resource object. ++ * For MC objects that are not allocatable (e.g., DP_OBJ_DPRC, DP_OBJ_DPNI), ++ * 'resource' is NULL. ++ */ ++struct fsl_mc_device { ++ struct device dev; ++ uint64_t dma_mask; ++ uint16_t flags; ++ uint16_t icid; ++ uint16_t mc_handle; ++ struct fsl_mc_io *mc_io; ++ struct dprc_obj_desc obj_desc; ++ struct resource *regions; ++ struct fsl_mc_device_irq **irqs; ++ struct fsl_mc_resource *resource; ++ const char *driver_override; ++}; ++ ++#define to_fsl_mc_device(_dev) \ ++ container_of(_dev, struct fsl_mc_device, dev) ++ ++/* ++ * module_fsl_mc_driver() - Helper macro for drivers that don't do ++ * anything special in module init/exit. This eliminates a lot of ++ * boilerplate. Each module may only use this macro once, and ++ * calling it replaces module_init() and module_exit() ++ */ ++#define module_fsl_mc_driver(__fsl_mc_driver) \ ++ module_driver(__fsl_mc_driver, fsl_mc_driver_register, \ ++ fsl_mc_driver_unregister) ++ ++/* ++ * Macro to avoid include chaining to get THIS_MODULE ++ */ ++#define fsl_mc_driver_register(drv) \ ++ __fsl_mc_driver_register(drv, THIS_MODULE) ++ ++int __must_check __fsl_mc_driver_register(struct fsl_mc_driver *fsl_mc_driver, ++ struct module *owner); ++ ++void fsl_mc_driver_unregister(struct fsl_mc_driver *driver); ++ ++bool fsl_mc_interrupts_supported(void); ++ ++int __must_check fsl_mc_portal_allocate(struct fsl_mc_device *mc_dev, ++ uint16_t mc_io_flags, ++ struct fsl_mc_io **new_mc_io); ++ ++void fsl_mc_portal_free(struct fsl_mc_io *mc_io); ++ ++int fsl_mc_portal_reset(struct fsl_mc_io *mc_io); ++ ++int __must_check fsl_mc_object_allocate(struct fsl_mc_device *mc_dev, ++ enum fsl_mc_pool_type pool_type, ++ struct fsl_mc_device **new_mc_adev); ++ ++void fsl_mc_object_free(struct fsl_mc_device *mc_adev); ++ ++int __must_check fsl_mc_allocate_irqs(struct fsl_mc_device *mc_dev); ++ ++void fsl_mc_free_irqs(struct fsl_mc_device *mc_dev); ++ ++extern struct bus_type fsl_mc_bus_type; ++ ++#endif /* _FSL_MC_H_ */ +diff --git a/drivers/staging/fsl-mc/include/net.h b/drivers/staging/fsl-mc/include/net.h +new file mode 100644 +index 0000000..7480f6a +--- /dev/null ++++ b/drivers/staging/fsl-mc/include/net.h +@@ -0,0 +1,481 @@ ++/* Copyright 2013-2015 Freescale Semiconductor Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * * Neither the name of the above-listed copyright holders nor the ++ * names of any contributors may be used to endorse or promote products ++ * derived from this software without specific prior written permission. ++ * ++ * ++ * ALTERNATIVELY, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") as published by the Free Software ++ * Foundation, either version 2 of that License or (at your option) any ++ * later version. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE ++ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++#ifndef __FSL_NET_H ++#define __FSL_NET_H ++ ++#define LAST_HDR_INDEX 0xFFFFFFFF ++ ++/*****************************************************************************/ ++/* Protocol fields */ ++/*****************************************************************************/ ++ ++/************************* Ethernet fields *********************************/ ++#define NH_FLD_ETH_DA (1) ++#define NH_FLD_ETH_SA (NH_FLD_ETH_DA << 1) ++#define NH_FLD_ETH_LENGTH (NH_FLD_ETH_DA << 2) ++#define NH_FLD_ETH_TYPE (NH_FLD_ETH_DA << 3) ++#define NH_FLD_ETH_FINAL_CKSUM (NH_FLD_ETH_DA << 4) ++#define NH_FLD_ETH_PADDING (NH_FLD_ETH_DA << 5) ++#define NH_FLD_ETH_ALL_FIELDS ((NH_FLD_ETH_DA << 6) - 1) ++ ++#define NH_FLD_ETH_ADDR_SIZE 6 ++ ++/*************************** VLAN fields ***********************************/ ++#define NH_FLD_VLAN_VPRI (1) ++#define NH_FLD_VLAN_CFI (NH_FLD_VLAN_VPRI << 1) ++#define NH_FLD_VLAN_VID (NH_FLD_VLAN_VPRI << 2) ++#define NH_FLD_VLAN_LENGTH (NH_FLD_VLAN_VPRI << 3) ++#define NH_FLD_VLAN_TYPE (NH_FLD_VLAN_VPRI << 4) ++#define NH_FLD_VLAN_ALL_FIELDS ((NH_FLD_VLAN_VPRI << 5) - 1) ++ ++#define NH_FLD_VLAN_TCI (NH_FLD_VLAN_VPRI | \ ++ NH_FLD_VLAN_CFI | \ ++ NH_FLD_VLAN_VID) ++ ++/************************ IP (generic) fields ******************************/ ++#define NH_FLD_IP_VER (1) ++#define NH_FLD_IP_DSCP (NH_FLD_IP_VER << 2) ++#define NH_FLD_IP_ECN (NH_FLD_IP_VER << 3) ++#define NH_FLD_IP_PROTO (NH_FLD_IP_VER << 4) ++#define NH_FLD_IP_SRC (NH_FLD_IP_VER << 5) ++#define NH_FLD_IP_DST (NH_FLD_IP_VER << 6) ++#define NH_FLD_IP_TOS_TC (NH_FLD_IP_VER << 7) ++#define NH_FLD_IP_ID (NH_FLD_IP_VER << 8) ++#define NH_FLD_IP_ALL_FIELDS ((NH_FLD_IP_VER << 9) - 1) ++ ++#define NH_FLD_IP_PROTO_SIZE 1 ++ ++/***************************** IPV4 fields *********************************/ ++#define NH_FLD_IPV4_VER (1) ++#define NH_FLD_IPV4_HDR_LEN (NH_FLD_IPV4_VER << 1) ++#define NH_FLD_IPV4_TOS (NH_FLD_IPV4_VER << 2) ++#define NH_FLD_IPV4_TOTAL_LEN (NH_FLD_IPV4_VER << 3) ++#define NH_FLD_IPV4_ID (NH_FLD_IPV4_VER << 4) ++#define NH_FLD_IPV4_FLAG_D (NH_FLD_IPV4_VER << 5) ++#define NH_FLD_IPV4_FLAG_M (NH_FLD_IPV4_VER << 6) ++#define NH_FLD_IPV4_OFFSET (NH_FLD_IPV4_VER << 7) ++#define NH_FLD_IPV4_TTL (NH_FLD_IPV4_VER << 8) ++#define NH_FLD_IPV4_PROTO (NH_FLD_IPV4_VER << 9) ++#define NH_FLD_IPV4_CKSUM (NH_FLD_IPV4_VER << 10) ++#define NH_FLD_IPV4_SRC_IP (NH_FLD_IPV4_VER << 11) ++#define NH_FLD_IPV4_DST_IP (NH_FLD_IPV4_VER << 12) ++#define NH_FLD_IPV4_OPTS (NH_FLD_IPV4_VER << 13) ++#define NH_FLD_IPV4_OPTS_COUNT (NH_FLD_IPV4_VER << 14) ++#define NH_FLD_IPV4_ALL_FIELDS ((NH_FLD_IPV4_VER << 15) - 1) ++ ++#define NH_FLD_IPV4_ADDR_SIZE 4 ++#define NH_FLD_IPV4_PROTO_SIZE 1 ++ ++/***************************** IPV6 fields *********************************/ ++#define NH_FLD_IPV6_VER (1) ++#define NH_FLD_IPV6_TC (NH_FLD_IPV6_VER << 1) ++#define NH_FLD_IPV6_SRC_IP (NH_FLD_IPV6_VER << 2) ++#define NH_FLD_IPV6_DST_IP (NH_FLD_IPV6_VER << 3) ++#define NH_FLD_IPV6_NEXT_HDR (NH_FLD_IPV6_VER << 4) ++#define NH_FLD_IPV6_FL (NH_FLD_IPV6_VER << 5) ++#define NH_FLD_IPV6_HOP_LIMIT (NH_FLD_IPV6_VER << 6) ++#define NH_FLD_IPV6_ID (NH_FLD_IPV6_VER << 7) ++#define NH_FLD_IPV6_ALL_FIELDS ((NH_FLD_IPV6_VER << 8) - 1) ++ ++#define NH_FLD_IPV6_ADDR_SIZE 16 ++#define NH_FLD_IPV6_NEXT_HDR_SIZE 1 ++ ++/***************************** ICMP fields *********************************/ ++#define NH_FLD_ICMP_TYPE (1) ++#define NH_FLD_ICMP_CODE (NH_FLD_ICMP_TYPE << 1) ++#define NH_FLD_ICMP_CKSUM (NH_FLD_ICMP_TYPE << 2) ++#define NH_FLD_ICMP_ID (NH_FLD_ICMP_TYPE << 3) ++#define NH_FLD_ICMP_SQ_NUM (NH_FLD_ICMP_TYPE << 4) ++#define NH_FLD_ICMP_ALL_FIELDS ((NH_FLD_ICMP_TYPE << 5) - 1) ++ ++#define NH_FLD_ICMP_CODE_SIZE 1 ++#define NH_FLD_ICMP_TYPE_SIZE 1 ++ ++/***************************** IGMP fields *********************************/ ++#define NH_FLD_IGMP_VERSION (1) ++#define NH_FLD_IGMP_TYPE (NH_FLD_IGMP_VERSION << 1) ++#define NH_FLD_IGMP_CKSUM (NH_FLD_IGMP_VERSION << 2) ++#define NH_FLD_IGMP_DATA (NH_FLD_IGMP_VERSION << 3) ++#define NH_FLD_IGMP_ALL_FIELDS ((NH_FLD_IGMP_VERSION << 4) - 1) ++ ++/***************************** TCP fields **********************************/ ++#define NH_FLD_TCP_PORT_SRC (1) ++#define NH_FLD_TCP_PORT_DST (NH_FLD_TCP_PORT_SRC << 1) ++#define NH_FLD_TCP_SEQ (NH_FLD_TCP_PORT_SRC << 2) ++#define NH_FLD_TCP_ACK (NH_FLD_TCP_PORT_SRC << 3) ++#define NH_FLD_TCP_OFFSET (NH_FLD_TCP_PORT_SRC << 4) ++#define NH_FLD_TCP_FLAGS (NH_FLD_TCP_PORT_SRC << 5) ++#define NH_FLD_TCP_WINDOW (NH_FLD_TCP_PORT_SRC << 6) ++#define NH_FLD_TCP_CKSUM (NH_FLD_TCP_PORT_SRC << 7) ++#define NH_FLD_TCP_URGPTR (NH_FLD_TCP_PORT_SRC << 8) ++#define NH_FLD_TCP_OPTS (NH_FLD_TCP_PORT_SRC << 9) ++#define NH_FLD_TCP_OPTS_COUNT (NH_FLD_TCP_PORT_SRC << 10) ++#define NH_FLD_TCP_ALL_FIELDS ((NH_FLD_TCP_PORT_SRC << 11) - 1) ++ ++#define NH_FLD_TCP_PORT_SIZE 2 ++ ++/***************************** UDP fields **********************************/ ++#define NH_FLD_UDP_PORT_SRC (1) ++#define NH_FLD_UDP_PORT_DST (NH_FLD_UDP_PORT_SRC << 1) ++#define NH_FLD_UDP_LEN (NH_FLD_UDP_PORT_SRC << 2) ++#define NH_FLD_UDP_CKSUM (NH_FLD_UDP_PORT_SRC << 3) ++#define NH_FLD_UDP_ALL_FIELDS ((NH_FLD_UDP_PORT_SRC << 4) - 1) ++ ++#define NH_FLD_UDP_PORT_SIZE 2 ++ ++/*************************** UDP-lite fields *******************************/ ++#define NH_FLD_UDP_LITE_PORT_SRC (1) ++#define NH_FLD_UDP_LITE_PORT_DST (NH_FLD_UDP_LITE_PORT_SRC << 1) ++#define NH_FLD_UDP_LITE_ALL_FIELDS \ ++ ((NH_FLD_UDP_LITE_PORT_SRC << 2) - 1) ++ ++#define NH_FLD_UDP_LITE_PORT_SIZE 2 ++ ++/*************************** UDP-encap-ESP fields **************************/ ++#define NH_FLD_UDP_ENC_ESP_PORT_SRC (1) ++#define NH_FLD_UDP_ENC_ESP_PORT_DST (NH_FLD_UDP_ENC_ESP_PORT_SRC << 1) ++#define NH_FLD_UDP_ENC_ESP_LEN (NH_FLD_UDP_ENC_ESP_PORT_SRC << 2) ++#define NH_FLD_UDP_ENC_ESP_CKSUM (NH_FLD_UDP_ENC_ESP_PORT_SRC << 3) ++#define NH_FLD_UDP_ENC_ESP_SPI (NH_FLD_UDP_ENC_ESP_PORT_SRC << 4) ++#define NH_FLD_UDP_ENC_ESP_SEQUENCE_NUM (NH_FLD_UDP_ENC_ESP_PORT_SRC << 5) ++#define NH_FLD_UDP_ENC_ESP_ALL_FIELDS \ ++ ((NH_FLD_UDP_ENC_ESP_PORT_SRC << 6) - 1) ++ ++#define NH_FLD_UDP_ENC_ESP_PORT_SIZE 2 ++#define NH_FLD_UDP_ENC_ESP_SPI_SIZE 4 ++ ++/***************************** SCTP fields *********************************/ ++#define NH_FLD_SCTP_PORT_SRC (1) ++#define NH_FLD_SCTP_PORT_DST (NH_FLD_SCTP_PORT_SRC << 1) ++#define NH_FLD_SCTP_VER_TAG (NH_FLD_SCTP_PORT_SRC << 2) ++#define NH_FLD_SCTP_CKSUM (NH_FLD_SCTP_PORT_SRC << 3) ++#define NH_FLD_SCTP_ALL_FIELDS ((NH_FLD_SCTP_PORT_SRC << 4) - 1) ++ ++#define NH_FLD_SCTP_PORT_SIZE 2 ++ ++/***************************** DCCP fields *********************************/ ++#define NH_FLD_DCCP_PORT_SRC (1) ++#define NH_FLD_DCCP_PORT_DST (NH_FLD_DCCP_PORT_SRC << 1) ++#define NH_FLD_DCCP_ALL_FIELDS ((NH_FLD_DCCP_PORT_SRC << 2) - 1) ++ ++#define NH_FLD_DCCP_PORT_SIZE 2 ++ ++/***************************** IPHC fields *********************************/ ++#define NH_FLD_IPHC_CID (1) ++#define NH_FLD_IPHC_CID_TYPE (NH_FLD_IPHC_CID << 1) ++#define NH_FLD_IPHC_HCINDEX (NH_FLD_IPHC_CID << 2) ++#define NH_FLD_IPHC_GEN (NH_FLD_IPHC_CID << 3) ++#define NH_FLD_IPHC_D_BIT (NH_FLD_IPHC_CID << 4) ++#define NH_FLD_IPHC_ALL_FIELDS ((NH_FLD_IPHC_CID << 5) - 1) ++ ++/***************************** SCTP fields *********************************/ ++#define NH_FLD_SCTP_CHUNK_DATA_TYPE (1) ++#define NH_FLD_SCTP_CHUNK_DATA_FLAGS (NH_FLD_SCTP_CHUNK_DATA_TYPE << 1) ++#define NH_FLD_SCTP_CHUNK_DATA_LENGTH (NH_FLD_SCTP_CHUNK_DATA_TYPE << 2) ++#define NH_FLD_SCTP_CHUNK_DATA_TSN (NH_FLD_SCTP_CHUNK_DATA_TYPE << 3) ++#define NH_FLD_SCTP_CHUNK_DATA_STREAM_ID (NH_FLD_SCTP_CHUNK_DATA_TYPE << 4) ++#define NH_FLD_SCTP_CHUNK_DATA_STREAM_SQN (NH_FLD_SCTP_CHUNK_DATA_TYPE << 5) ++#define NH_FLD_SCTP_CHUNK_DATA_PAYLOAD_PID (NH_FLD_SCTP_CHUNK_DATA_TYPE << 6) ++#define NH_FLD_SCTP_CHUNK_DATA_UNORDERED (NH_FLD_SCTP_CHUNK_DATA_TYPE << 7) ++#define NH_FLD_SCTP_CHUNK_DATA_BEGGINING (NH_FLD_SCTP_CHUNK_DATA_TYPE << 8) ++#define NH_FLD_SCTP_CHUNK_DATA_END (NH_FLD_SCTP_CHUNK_DATA_TYPE << 9) ++#define NH_FLD_SCTP_CHUNK_DATA_ALL_FIELDS \ ++ ((NH_FLD_SCTP_CHUNK_DATA_TYPE << 10) - 1) ++ ++/*************************** L2TPV2 fields *********************************/ ++#define NH_FLD_L2TPV2_TYPE_BIT (1) ++#define NH_FLD_L2TPV2_LENGTH_BIT (NH_FLD_L2TPV2_TYPE_BIT << 1) ++#define NH_FLD_L2TPV2_SEQUENCE_BIT (NH_FLD_L2TPV2_TYPE_BIT << 2) ++#define NH_FLD_L2TPV2_OFFSET_BIT (NH_FLD_L2TPV2_TYPE_BIT << 3) ++#define NH_FLD_L2TPV2_PRIORITY_BIT (NH_FLD_L2TPV2_TYPE_BIT << 4) ++#define NH_FLD_L2TPV2_VERSION (NH_FLD_L2TPV2_TYPE_BIT << 5) ++#define NH_FLD_L2TPV2_LEN (NH_FLD_L2TPV2_TYPE_BIT << 6) ++#define NH_FLD_L2TPV2_TUNNEL_ID (NH_FLD_L2TPV2_TYPE_BIT << 7) ++#define NH_FLD_L2TPV2_SESSION_ID (NH_FLD_L2TPV2_TYPE_BIT << 8) ++#define NH_FLD_L2TPV2_NS (NH_FLD_L2TPV2_TYPE_BIT << 9) ++#define NH_FLD_L2TPV2_NR (NH_FLD_L2TPV2_TYPE_BIT << 10) ++#define NH_FLD_L2TPV2_OFFSET_SIZE (NH_FLD_L2TPV2_TYPE_BIT << 11) ++#define NH_FLD_L2TPV2_FIRST_BYTE (NH_FLD_L2TPV2_TYPE_BIT << 12) ++#define NH_FLD_L2TPV2_ALL_FIELDS \ ++ ((NH_FLD_L2TPV2_TYPE_BIT << 13) - 1) ++ ++/*************************** L2TPV3 fields *********************************/ ++#define NH_FLD_L2TPV3_CTRL_TYPE_BIT (1) ++#define NH_FLD_L2TPV3_CTRL_LENGTH_BIT (NH_FLD_L2TPV3_CTRL_TYPE_BIT << 1) ++#define NH_FLD_L2TPV3_CTRL_SEQUENCE_BIT (NH_FLD_L2TPV3_CTRL_TYPE_BIT << 2) ++#define NH_FLD_L2TPV3_CTRL_VERSION (NH_FLD_L2TPV3_CTRL_TYPE_BIT << 3) ++#define NH_FLD_L2TPV3_CTRL_LENGTH (NH_FLD_L2TPV3_CTRL_TYPE_BIT << 4) ++#define NH_FLD_L2TPV3_CTRL_CONTROL (NH_FLD_L2TPV3_CTRL_TYPE_BIT << 5) ++#define NH_FLD_L2TPV3_CTRL_SENT (NH_FLD_L2TPV3_CTRL_TYPE_BIT << 6) ++#define NH_FLD_L2TPV3_CTRL_RECV (NH_FLD_L2TPV3_CTRL_TYPE_BIT << 7) ++#define NH_FLD_L2TPV3_CTRL_FIRST_BYTE (NH_FLD_L2TPV3_CTRL_TYPE_BIT << 8) ++#define NH_FLD_L2TPV3_CTRL_ALL_FIELDS \ ++ ((NH_FLD_L2TPV3_CTRL_TYPE_BIT << 9) - 1) ++ ++#define NH_FLD_L2TPV3_SESS_TYPE_BIT (1) ++#define NH_FLD_L2TPV3_SESS_VERSION (NH_FLD_L2TPV3_SESS_TYPE_BIT << 1) ++#define NH_FLD_L2TPV3_SESS_ID (NH_FLD_L2TPV3_SESS_TYPE_BIT << 2) ++#define NH_FLD_L2TPV3_SESS_COOKIE (NH_FLD_L2TPV3_SESS_TYPE_BIT << 3) ++#define NH_FLD_L2TPV3_SESS_ALL_FIELDS \ ++ ((NH_FLD_L2TPV3_SESS_TYPE_BIT << 4) - 1) ++ ++/**************************** PPP fields ***********************************/ ++#define NH_FLD_PPP_PID (1) ++#define NH_FLD_PPP_COMPRESSED (NH_FLD_PPP_PID << 1) ++#define NH_FLD_PPP_ALL_FIELDS ((NH_FLD_PPP_PID << 2) - 1) ++ ++/************************** PPPoE fields ***********************************/ ++#define NH_FLD_PPPOE_VER (1) ++#define NH_FLD_PPPOE_TYPE (NH_FLD_PPPOE_VER << 1) ++#define NH_FLD_PPPOE_CODE (NH_FLD_PPPOE_VER << 2) ++#define NH_FLD_PPPOE_SID (NH_FLD_PPPOE_VER << 3) ++#define NH_FLD_PPPOE_LEN (NH_FLD_PPPOE_VER << 4) ++#define NH_FLD_PPPOE_SESSION (NH_FLD_PPPOE_VER << 5) ++#define NH_FLD_PPPOE_PID (NH_FLD_PPPOE_VER << 6) ++#define NH_FLD_PPPOE_ALL_FIELDS ((NH_FLD_PPPOE_VER << 7) - 1) ++ ++/************************* PPP-Mux fields **********************************/ ++#define NH_FLD_PPPMUX_PID (1) ++#define NH_FLD_PPPMUX_CKSUM (NH_FLD_PPPMUX_PID << 1) ++#define NH_FLD_PPPMUX_COMPRESSED (NH_FLD_PPPMUX_PID << 2) ++#define NH_FLD_PPPMUX_ALL_FIELDS ((NH_FLD_PPPMUX_PID << 3) - 1) ++ ++/*********************** PPP-Mux sub-frame fields **************************/ ++#define NH_FLD_PPPMUX_SUBFRM_PFF (1) ++#define NH_FLD_PPPMUX_SUBFRM_LXT (NH_FLD_PPPMUX_SUBFRM_PFF << 1) ++#define NH_FLD_PPPMUX_SUBFRM_LEN (NH_FLD_PPPMUX_SUBFRM_PFF << 2) ++#define NH_FLD_PPPMUX_SUBFRM_PID (NH_FLD_PPPMUX_SUBFRM_PFF << 3) ++#define NH_FLD_PPPMUX_SUBFRM_USE_PID (NH_FLD_PPPMUX_SUBFRM_PFF << 4) ++#define NH_FLD_PPPMUX_SUBFRM_ALL_FIELDS \ ++ ((NH_FLD_PPPMUX_SUBFRM_PFF << 5) - 1) ++ ++/*************************** LLC fields ************************************/ ++#define NH_FLD_LLC_DSAP (1) ++#define NH_FLD_LLC_SSAP (NH_FLD_LLC_DSAP << 1) ++#define NH_FLD_LLC_CTRL (NH_FLD_LLC_DSAP << 2) ++#define NH_FLD_LLC_ALL_FIELDS ((NH_FLD_LLC_DSAP << 3) - 1) ++ ++/*************************** NLPID fields **********************************/ ++#define NH_FLD_NLPID_NLPID (1) ++#define NH_FLD_NLPID_ALL_FIELDS ((NH_FLD_NLPID_NLPID << 1) - 1) ++ ++/*************************** SNAP fields ***********************************/ ++#define NH_FLD_SNAP_OUI (1) ++#define NH_FLD_SNAP_PID (NH_FLD_SNAP_OUI << 1) ++#define NH_FLD_SNAP_ALL_FIELDS ((NH_FLD_SNAP_OUI << 2) - 1) ++ ++/*************************** LLC SNAP fields *******************************/ ++#define NH_FLD_LLC_SNAP_TYPE (1) ++#define NH_FLD_LLC_SNAP_ALL_FIELDS ((NH_FLD_LLC_SNAP_TYPE << 1) - 1) ++ ++#define NH_FLD_ARP_HTYPE (1) ++#define NH_FLD_ARP_PTYPE (NH_FLD_ARP_HTYPE << 1) ++#define NH_FLD_ARP_HLEN (NH_FLD_ARP_HTYPE << 2) ++#define NH_FLD_ARP_PLEN (NH_FLD_ARP_HTYPE << 3) ++#define NH_FLD_ARP_OPER (NH_FLD_ARP_HTYPE << 4) ++#define NH_FLD_ARP_SHA (NH_FLD_ARP_HTYPE << 5) ++#define NH_FLD_ARP_SPA (NH_FLD_ARP_HTYPE << 6) ++#define NH_FLD_ARP_THA (NH_FLD_ARP_HTYPE << 7) ++#define NH_FLD_ARP_TPA (NH_FLD_ARP_HTYPE << 8) ++#define NH_FLD_ARP_ALL_FIELDS ((NH_FLD_ARP_HTYPE << 9) - 1) ++ ++/*************************** RFC2684 fields ********************************/ ++#define NH_FLD_RFC2684_LLC (1) ++#define NH_FLD_RFC2684_NLPID (NH_FLD_RFC2684_LLC << 1) ++#define NH_FLD_RFC2684_OUI (NH_FLD_RFC2684_LLC << 2) ++#define NH_FLD_RFC2684_PID (NH_FLD_RFC2684_LLC << 3) ++#define NH_FLD_RFC2684_VPN_OUI (NH_FLD_RFC2684_LLC << 4) ++#define NH_FLD_RFC2684_VPN_IDX (NH_FLD_RFC2684_LLC << 5) ++#define NH_FLD_RFC2684_ALL_FIELDS ((NH_FLD_RFC2684_LLC << 6) - 1) ++ ++/*************************** User defined fields ***************************/ ++#define NH_FLD_USER_DEFINED_SRCPORT (1) ++#define NH_FLD_USER_DEFINED_PCDID (NH_FLD_USER_DEFINED_SRCPORT << 1) ++#define NH_FLD_USER_DEFINED_ALL_FIELDS \ ++ ((NH_FLD_USER_DEFINED_SRCPORT << 2) - 1) ++ ++/*************************** Payload fields ********************************/ ++#define NH_FLD_PAYLOAD_BUFFER (1) ++#define NH_FLD_PAYLOAD_SIZE (NH_FLD_PAYLOAD_BUFFER << 1) ++#define NH_FLD_MAX_FRM_SIZE (NH_FLD_PAYLOAD_BUFFER << 2) ++#define NH_FLD_MIN_FRM_SIZE (NH_FLD_PAYLOAD_BUFFER << 3) ++#define NH_FLD_PAYLOAD_TYPE (NH_FLD_PAYLOAD_BUFFER << 4) ++#define NH_FLD_FRAME_SIZE (NH_FLD_PAYLOAD_BUFFER << 5) ++#define NH_FLD_PAYLOAD_ALL_FIELDS ((NH_FLD_PAYLOAD_BUFFER << 6) - 1) ++ ++/*************************** GRE fields ************************************/ ++#define NH_FLD_GRE_TYPE (1) ++#define NH_FLD_GRE_ALL_FIELDS ((NH_FLD_GRE_TYPE << 1) - 1) ++ ++/*************************** MINENCAP fields *******************************/ ++#define NH_FLD_MINENCAP_SRC_IP (1) ++#define NH_FLD_MINENCAP_DST_IP (NH_FLD_MINENCAP_SRC_IP << 1) ++#define NH_FLD_MINENCAP_TYPE (NH_FLD_MINENCAP_SRC_IP << 2) ++#define NH_FLD_MINENCAP_ALL_FIELDS \ ++ ((NH_FLD_MINENCAP_SRC_IP << 3) - 1) ++ ++/*************************** IPSEC AH fields *******************************/ ++#define NH_FLD_IPSEC_AH_SPI (1) ++#define NH_FLD_IPSEC_AH_NH (NH_FLD_IPSEC_AH_SPI << 1) ++#define NH_FLD_IPSEC_AH_ALL_FIELDS ((NH_FLD_IPSEC_AH_SPI << 2) - 1) ++ ++/*************************** IPSEC ESP fields ******************************/ ++#define NH_FLD_IPSEC_ESP_SPI (1) ++#define NH_FLD_IPSEC_ESP_SEQUENCE_NUM (NH_FLD_IPSEC_ESP_SPI << 1) ++#define NH_FLD_IPSEC_ESP_ALL_FIELDS ((NH_FLD_IPSEC_ESP_SPI << 2) - 1) ++ ++#define NH_FLD_IPSEC_ESP_SPI_SIZE 4 ++ ++/*************************** MPLS fields ***********************************/ ++#define NH_FLD_MPLS_LABEL_STACK (1) ++#define NH_FLD_MPLS_LABEL_STACK_ALL_FIELDS \ ++ ((NH_FLD_MPLS_LABEL_STACK << 1) - 1) ++ ++/*************************** MACSEC fields *********************************/ ++#define NH_FLD_MACSEC_SECTAG (1) ++#define NH_FLD_MACSEC_ALL_FIELDS ((NH_FLD_MACSEC_SECTAG << 1) - 1) ++ ++/*************************** GTP fields ************************************/ ++#define NH_FLD_GTP_TEID (1) ++ ++ ++/* Protocol options */ ++ ++/* Ethernet options */ ++#define NH_OPT_ETH_BROADCAST 1 ++#define NH_OPT_ETH_MULTICAST 2 ++#define NH_OPT_ETH_UNICAST 3 ++#define NH_OPT_ETH_BPDU 4 ++ ++#define NH_ETH_IS_MULTICAST_ADDR(addr) (addr[0] & 0x01) ++/* also applicable for broadcast */ ++ ++/* VLAN options */ ++#define NH_OPT_VLAN_CFI 1 ++ ++/* IPV4 options */ ++#define NH_OPT_IPV4_UNICAST 1 ++#define NH_OPT_IPV4_MULTICAST 2 ++#define NH_OPT_IPV4_BROADCAST 3 ++#define NH_OPT_IPV4_OPTION 4 ++#define NH_OPT_IPV4_FRAG 5 ++#define NH_OPT_IPV4_INITIAL_FRAG 6 ++ ++/* IPV6 options */ ++#define NH_OPT_IPV6_UNICAST 1 ++#define NH_OPT_IPV6_MULTICAST 2 ++#define NH_OPT_IPV6_OPTION 3 ++#define NH_OPT_IPV6_FRAG 4 ++#define NH_OPT_IPV6_INITIAL_FRAG 5 ++ ++/* General IP options (may be used for any version) */ ++#define NH_OPT_IP_FRAG 1 ++#define NH_OPT_IP_INITIAL_FRAG 2 ++#define NH_OPT_IP_OPTION 3 ++ ++/* Minenc. options */ ++#define NH_OPT_MINENCAP_SRC_ADDR_PRESENT 1 ++ ++/* GRE. options */ ++#define NH_OPT_GRE_ROUTING_PRESENT 1 ++ ++/* TCP options */ ++#define NH_OPT_TCP_OPTIONS 1 ++#define NH_OPT_TCP_CONTROL_HIGH_BITS 2 ++#define NH_OPT_TCP_CONTROL_LOW_BITS 3 ++ ++/* CAPWAP options */ ++#define NH_OPT_CAPWAP_DTLS 1 ++ ++enum net_prot { ++ NET_PROT_NONE = 0, ++ NET_PROT_PAYLOAD, ++ NET_PROT_ETH, ++ NET_PROT_VLAN, ++ NET_PROT_IPV4, ++ NET_PROT_IPV6, ++ NET_PROT_IP, ++ NET_PROT_TCP, ++ NET_PROT_UDP, ++ NET_PROT_UDP_LITE, ++ NET_PROT_IPHC, ++ NET_PROT_SCTP, ++ NET_PROT_SCTP_CHUNK_DATA, ++ NET_PROT_PPPOE, ++ NET_PROT_PPP, ++ NET_PROT_PPPMUX, ++ NET_PROT_PPPMUX_SUBFRM, ++ NET_PROT_L2TPV2, ++ NET_PROT_L2TPV3_CTRL, ++ NET_PROT_L2TPV3_SESS, ++ NET_PROT_LLC, ++ NET_PROT_LLC_SNAP, ++ NET_PROT_NLPID, ++ NET_PROT_SNAP, ++ NET_PROT_MPLS, ++ NET_PROT_IPSEC_AH, ++ NET_PROT_IPSEC_ESP, ++ NET_PROT_UDP_ENC_ESP, /* RFC 3948 */ ++ NET_PROT_MACSEC, ++ NET_PROT_GRE, ++ NET_PROT_MINENCAP, ++ NET_PROT_DCCP, ++ NET_PROT_ICMP, ++ NET_PROT_IGMP, ++ NET_PROT_ARP, ++ NET_PROT_CAPWAP_DATA, ++ NET_PROT_CAPWAP_CTRL, ++ NET_PROT_RFC2684, ++ NET_PROT_ICMPV6, ++ NET_PROT_FCOE, ++ NET_PROT_FIP, ++ NET_PROT_ISCSI, ++ NET_PROT_GTP, ++ NET_PROT_USER_DEFINED_L2, ++ NET_PROT_USER_DEFINED_L3, ++ NET_PROT_USER_DEFINED_L4, ++ NET_PROT_USER_DEFINED_L5, ++ NET_PROT_USER_DEFINED_SHIM1, ++ NET_PROT_USER_DEFINED_SHIM2, ++ ++ NET_PROT_DUMMY_LAST ++}; ++ ++/*! IEEE8021.Q */ ++#define NH_IEEE8021Q_ETYPE 0x8100 ++#define NH_IEEE8021Q_HDR(etype, pcp, dei, vlan_id) \ ++ ((((uint32_t)(etype & 0xFFFF)) << 16) | \ ++ (((uint32_t)(pcp & 0x07)) << 13) | \ ++ (((uint32_t)(dei & 0x01)) << 12) | \ ++ (((uint32_t)(vlan_id & 0xFFF)))) ++ ++#endif /* __FSL_NET_H */ +diff --git a/drivers/usb/core/config.c b/drivers/usb/core/config.c +index b9ddf0c..894894f 100644 +--- a/drivers/usb/core/config.c ++++ b/drivers/usb/core/config.c +@@ -115,7 +115,8 @@ static void usb_parse_ss_endpoint_companion(struct device *ddev, int cfgno, + USB_SS_MULT(desc->bmAttributes) > 3) { + dev_warn(ddev, "Isoc endpoint has Mult of %d in " + "config %d interface %d altsetting %d ep %d: " +- "setting to 3\n", desc->bmAttributes + 1, ++ "setting to 3\n", ++ USB_SS_MULT(desc->bmAttributes), + cfgno, inum, asnum, ep->desc.bEndpointAddress); + ep->ss_ep_comp.bmAttributes = 2; + } +diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c +index d7a6d8b..66be3b4 100644 +--- a/drivers/usb/core/driver.c ++++ b/drivers/usb/core/driver.c +@@ -499,11 +499,15 @@ static int usb_unbind_interface(struct device *dev) + int usb_driver_claim_interface(struct usb_driver *driver, + struct usb_interface *iface, void *priv) + { +- struct device *dev = &iface->dev; ++ struct device *dev; + struct usb_device *udev; + int retval = 0; + int lpm_disable_error; + ++ if (!iface) ++ return -ENODEV; ++ ++ dev = &iface->dev; + if (dev->driver) + return -EBUSY; + +diff --git a/drivers/usb/core/hcd-pci.c b/drivers/usb/core/hcd-pci.c +index efc9531..a4c0b85 100644 +--- a/drivers/usb/core/hcd-pci.c ++++ b/drivers/usb/core/hcd-pci.c +@@ -74,6 +74,15 @@ static void for_each_companion(struct pci_dev *pdev, struct usb_hcd *hcd, + if (companion->bus != pdev->bus || + PCI_SLOT(companion->devfn) != slot) + continue; ++ ++ /* ++ * Companion device should be either UHCI,OHCI or EHCI host ++ * controller, otherwise skip. ++ */ ++ if (companion->class != CL_UHCI && companion->class != CL_OHCI && ++ companion->class != CL_EHCI) ++ continue; ++ + companion_hcd = pci_get_drvdata(companion); + if (!companion_hcd || !companion_hcd->self.root_hub) + continue; +diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c +index 2222899..d8e1d5c 100644 +--- a/drivers/usb/core/hub.c ++++ b/drivers/usb/core/hub.c +@@ -124,6 +124,10 @@ struct usb_hub *usb_hub_to_struct_hub(struct usb_device *hdev) + + static int usb_device_supports_lpm(struct usb_device *udev) + { ++ /* Some devices have trouble with LPM */ ++ if (udev->quirks & USB_QUIRK_NO_LPM) ++ return 0; ++ + /* USB 2.1 (and greater) devices indicate LPM support through + * their USB 2.0 Extended Capabilities BOS descriptor. + */ +@@ -1030,10 +1034,20 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type) + unsigned delay; + + /* Continue a partial initialization */ +- if (type == HUB_INIT2) +- goto init2; +- if (type == HUB_INIT3) ++ if (type == HUB_INIT2 || type == HUB_INIT3) { ++ device_lock(hub->intfdev); ++ ++ /* Was the hub disconnected while we were waiting? */ ++ if (hub->disconnected) { ++ device_unlock(hub->intfdev); ++ kref_put(&hub->kref, hub_release); ++ return; ++ } ++ if (type == HUB_INIT2) ++ goto init2; + goto init3; ++ } ++ kref_get(&hub->kref); + + /* The superspeed hub except for root hub has to use Hub Depth + * value as an offset into the route string to locate the bits +@@ -1231,6 +1245,7 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type) + queue_delayed_work(system_power_efficient_wq, + &hub->init_work, + msecs_to_jiffies(delay)); ++ device_unlock(hub->intfdev); + return; /* Continues at init3: below */ + } else { + msleep(delay); +@@ -1252,6 +1267,11 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type) + /* Allow autosuspend if it was suppressed */ + if (type <= HUB_INIT3) + usb_autopm_put_interface_async(to_usb_interface(hub->intfdev)); ++ ++ if (type == HUB_INIT2 || type == HUB_INIT3) ++ device_unlock(hub->intfdev); ++ ++ kref_put(&hub->kref, hub_release); + } + + /* Implement the continuations for the delays above */ +@@ -4222,7 +4242,7 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1, + { + struct usb_device *hdev = hub->hdev; + struct usb_hcd *hcd = bus_to_hcd(hdev->bus); +- int i, j, retval; ++ int retries, operations, retval, i; + unsigned delay = HUB_SHORT_RESET_TIME; + enum usb_device_speed oldspeed = udev->speed; + const char *speed; +@@ -4324,7 +4344,7 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1, + * first 8 bytes of the device descriptor to get the ep0 maxpacket + * value. + */ +- for (i = 0; i < GET_DESCRIPTOR_TRIES; (++i, msleep(100))) { ++ for (retries = 0; retries < GET_DESCRIPTOR_TRIES; (++retries, msleep(100))) { + bool did_new_scheme = false; + + if (use_new_scheme(udev, retry_counter)) { +@@ -4351,7 +4371,7 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1, + * 255 is for WUSB devices, we actually need to use + * 512 (WUSB1.0[4.8.1]). + */ +- for (j = 0; j < 3; ++j) { ++ for (operations = 0; operations < 3; ++operations) { + buf->bMaxPacketSize0 = 0; + r = usb_control_msg(udev, usb_rcvaddr0pipe(), + USB_REQ_GET_DESCRIPTOR, USB_DIR_IN, +@@ -4371,7 +4391,13 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1, + r = -EPROTO; + break; + } +- if (r == 0) ++ /* ++ * Some devices time out if they are powered on ++ * when already connected. They need a second ++ * reset. But only on the first attempt, ++ * lest we get into a time out/reset loop ++ */ ++ if (r == 0 || (r == -ETIMEDOUT && retries == 0)) + break; + } + udev->descriptor.bMaxPacketSize0 = +@@ -4403,7 +4429,7 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1, + * authorization will assign the final address. + */ + if (udev->wusb == 0) { +- for (j = 0; j < SET_ADDRESS_TRIES; ++j) { ++ for (operations = 0; operations < SET_ADDRESS_TRIES; ++operations) { + retval = hub_set_address(udev, devnum); + if (retval >= 0) + break; +@@ -4498,6 +4524,8 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1, + goto fail; + } + ++ usb_detect_quirks(udev); ++ + if (udev->wusb == 0 && le16_to_cpu(udev->descriptor.bcdUSB) >= 0x0201) { + retval = usb_get_bos_descriptor(udev); + if (!retval) { +@@ -4692,7 +4720,6 @@ static void hub_port_connect(struct usb_hub *hub, int port1, u16 portstatus, + if (status < 0) + goto loop; + +- usb_detect_quirks(udev); + if (udev->quirks & USB_QUIRK_DELAY_INIT) + msleep(1000); + +@@ -5324,9 +5351,6 @@ static int usb_reset_and_verify_device(struct usb_device *udev) + if (udev->usb2_hw_lpm_enabled == 1) + usb_set_usb2_hardware_lpm(udev, 0); + +- bos = udev->bos; +- udev->bos = NULL; +- + /* Disable LPM and LTM while we reset the device and reinstall the alt + * settings. Device-initiated LPM settings, and system exit latency + * settings are cleared when the device is reset, so we have to set +@@ -5335,15 +5359,17 @@ static int usb_reset_and_verify_device(struct usb_device *udev) + ret = usb_unlocked_disable_lpm(udev); + if (ret) { + dev_err(&udev->dev, "%s Failed to disable LPM\n.", __func__); +- goto re_enumerate; ++ goto re_enumerate_no_bos; + } + ret = usb_disable_ltm(udev); + if (ret) { + dev_err(&udev->dev, "%s Failed to disable LTM\n.", + __func__); +- goto re_enumerate; ++ goto re_enumerate_no_bos; + } + ++ bos = udev->bos; ++ + for (i = 0; i < SET_CONFIG_TRIES; ++i) { + + /* ep0 maxpacket size may change; let the HCD know about it. +@@ -5435,15 +5461,19 @@ done: + usb_set_usb2_hardware_lpm(udev, 1); + usb_unlocked_enable_lpm(udev); + usb_enable_ltm(udev); +- usb_release_bos_descriptor(udev); +- udev->bos = bos; ++ /* release the new BOS descriptor allocated by hub_port_init() */ ++ if (udev->bos != bos) { ++ usb_release_bos_descriptor(udev); ++ udev->bos = bos; ++ } + return 0; + + re_enumerate: +- /* LPM state doesn't matter when we're about to destroy the device. */ +- hub_port_logical_disconnect(parent_hub, port1); + usb_release_bos_descriptor(udev); + udev->bos = bos; ++re_enumerate_no_bos: ++ /* LPM state doesn't matter when we're about to destroy the device. */ ++ hub_port_logical_disconnect(parent_hub, port1); + return -ENODEV; + } + +diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c +index 8a77a41..6b53fc3 100644 +--- a/drivers/usb/core/quirks.c ++++ b/drivers/usb/core/quirks.c +@@ -196,6 +196,12 @@ static const struct usb_device_id usb_quirk_list[] = { + { USB_DEVICE(0x1a0a, 0x0200), .driver_info = + USB_QUIRK_LINEAR_UFRAME_INTR_BINTERVAL }, + ++ /* Blackmagic Design Intensity Shuttle */ ++ { USB_DEVICE(0x1edb, 0xbd3b), .driver_info = USB_QUIRK_NO_LPM }, ++ ++ /* Blackmagic Design UltraStudio SDI */ ++ { USB_DEVICE(0x1edb, 0xbd4f), .driver_info = USB_QUIRK_NO_LPM }, ++ + { } /* terminating entry must be last */ + }; + +diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c +index b0f4d52..17eeab8 100644 +--- a/drivers/usb/dwc3/core.c ++++ b/drivers/usb/dwc3/core.c +@@ -673,22 +673,20 @@ static int dwc3_probe(struct platform_device *pdev) + * since it will be requested by the xhci-plat driver. + */ + regs = devm_ioremap_resource(dev, res); +- if (IS_ERR(regs)) +- return PTR_ERR(regs); ++ if (IS_ERR(regs)) { ++ ret = PTR_ERR(regs); ++ goto err0; ++ } + + dwc->regs = regs; + dwc->regs_size = resource_size(res); +- /* +- * restore res->start back to its original value so that, +- * in case the probe is deferred, we don't end up getting error in +- * request the memory region the next time probe is called. +- */ +- res->start -= DWC3_GLOBALS_REGS_START; + + if (node) { + dwc->maximum_speed = of_usb_get_maximum_speed(node); + + dwc->needs_fifo_resize = of_property_read_bool(node, "tx-fifo-resize"); ++ dwc->configure_gfladj = ++ of_property_read_bool(node, "configure-gfladj"); + dwc->dr_mode = of_usb_get_dr_mode(node); + } else if (pdata) { + dwc->maximum_speed = pdata->maximum_speed; +@@ -703,7 +701,7 @@ static int dwc3_probe(struct platform_device *pdev) + + ret = dwc3_core_get_phy(dwc); + if (ret) +- return ret; ++ goto err0; + + spin_lock_init(&dwc->lock); + platform_set_drvdata(pdev, dwc); +@@ -722,7 +720,25 @@ static int dwc3_probe(struct platform_device *pdev) + if (ret) { + dev_err(dwc->dev, "failed to allocate event buffers\n"); + ret = -ENOMEM; +- goto err0; ++ goto err1; ++ } ++ ++ /* Adjust Frame Length */ ++ if (dwc->configure_gfladj) ++ dwc3_writel(dwc->regs, DWC3_GFLADJ, GFLADJ_30MHZ_REG_SEL | ++ GFLADJ_30MHZ(GFLADJ_30MHZ_DEFAULT)); ++ ++ /* Change burst beat and outstanding pipelined transfers requests */ ++ dwc3_writel(dwc->regs, DWC3_GSBUSCFG0, ++ (dwc3_readl(dwc->regs, DWC3_GSBUSCFG0) & ~0xff) | 0xf); ++ dwc3_writel(dwc->regs, DWC3_GSBUSCFG1, ++ dwc3_readl(dwc->regs, DWC3_GSBUSCFG1) | 0xf00); ++ ++ /* Enable Snooping */ ++ if (node && of_dma_is_coherent(node)) { ++ dwc3_writel(dwc->regs, DWC3_GSBUSCFG0, ++ dwc3_readl(dwc->regs, DWC3_GSBUSCFG0) | 0x22220000); ++ dev_dbg(dev, "enabled snooping for usb\n"); + } + + if (IS_ENABLED(CONFIG_USB_DWC3_HOST)) +@@ -736,65 +752,81 @@ static int dwc3_probe(struct platform_device *pdev) + ret = dwc3_core_init(dwc); + if (ret) { + dev_err(dev, "failed to initialize core\n"); +- goto err0; ++ goto err1; + } + + usb_phy_set_suspend(dwc->usb2_phy, 0); + usb_phy_set_suspend(dwc->usb3_phy, 0); + ret = phy_power_on(dwc->usb2_generic_phy); + if (ret < 0) +- goto err1; ++ goto err2; + + ret = phy_power_on(dwc->usb3_generic_phy); + if (ret < 0) +- goto err_usb2phy_power; ++ goto err3; + + ret = dwc3_event_buffers_setup(dwc); + if (ret) { + dev_err(dwc->dev, "failed to setup event buffers\n"); +- goto err_usb3phy_power; ++ goto err4; + } + + ret = dwc3_core_init_mode(dwc); + if (ret) +- goto err2; ++ goto err5; + + ret = dwc3_debugfs_init(dwc); + if (ret) { + dev_err(dev, "failed to initialize debugfs\n"); +- goto err3; ++ goto err6; + } + + pm_runtime_allow(dev); + + return 0; + +-err3: ++err6: + dwc3_core_exit_mode(dwc); + +-err2: ++err5: + dwc3_event_buffers_cleanup(dwc); + +-err_usb3phy_power: ++err4: + phy_power_off(dwc->usb3_generic_phy); + +-err_usb2phy_power: ++err3: + phy_power_off(dwc->usb2_generic_phy); + +-err1: ++err2: + usb_phy_set_suspend(dwc->usb2_phy, 1); + usb_phy_set_suspend(dwc->usb3_phy, 1); + dwc3_core_exit(dwc); + +-err0: ++err1: + dwc3_free_event_buffers(dwc); + ++err0: ++ /* ++ * restore res->start back to its original value so that, in case the ++ * probe is deferred, we don't end up getting error in request the ++ * memory region the next time probe is called. ++ */ ++ res->start -= DWC3_GLOBALS_REGS_START; ++ + return ret; + } + + static int dwc3_remove(struct platform_device *pdev) + { + struct dwc3 *dwc = platform_get_drvdata(pdev); ++ struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ ++ /* ++ * restore res->start back to its original value so that, in case the ++ * probe is deferred, we don't end up getting error in request the ++ * memory region the next time probe is called. ++ */ ++ res->start -= DWC3_GLOBALS_REGS_START; + + dwc3_debugfs_exit(dwc); + dwc3_core_exit_mode(dwc); +diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h +index 66f6256..aec8953 100644 +--- a/drivers/usb/dwc3/core.h ++++ b/drivers/usb/dwc3/core.h +@@ -26,6 +26,7 @@ + #include + #include + #include ++#include + + #include + #include +@@ -123,6 +124,7 @@ + #define DWC3_GEVNTCOUNT(n) (0xc40c + (n * 0x10)) + + #define DWC3_GHWPARAMS8 0xc600 ++#define DWC3_GFLADJ 0xc630 + + /* Device Registers */ + #define DWC3_DCFG 0xc700 +@@ -210,6 +212,11 @@ + #define DWC3_GHWPARAMS4_HIBER_SCRATCHBUFS(n) (((n) & (0x0f << 13)) >> 13) + #define DWC3_MAX_HIBER_SCRATCHBUFS 15 + ++/* Global Frame Length Adjustment Register */ ++#define GFLADJ_30MHZ_REG_SEL (1 << 7) ++#define GFLADJ_30MHZ(n) ((n) & 0x3f) ++#define GFLADJ_30MHZ_DEFAULT 0x20 ++ + /* Device Configuration Register */ + #define DWC3_DCFG_DEVADDR(addr) ((addr) << 3) + #define DWC3_DCFG_DEVADDR_MASK DWC3_DCFG_DEVADDR(0x7f) +@@ -766,6 +773,7 @@ struct dwc3 { + unsigned has_hibernation:1; + unsigned is_selfpowered:1; + unsigned needs_fifo_resize:1; ++ unsigned configure_gfladj:1; + unsigned pullups_connected:1; + unsigned resize_fifos:1; + unsigned setup_packet_pending:1; +diff --git a/drivers/usb/dwc3/host.c b/drivers/usb/dwc3/host.c +index dcb8ca0..c41d46c 100644 +--- a/drivers/usb/dwc3/host.c ++++ b/drivers/usb/dwc3/host.c +@@ -39,6 +39,12 @@ int dwc3_host_init(struct dwc3 *dwc) + xhci->dev.dma_mask = dwc->dev->dma_mask; + xhci->dev.dma_parms = dwc->dev->dma_parms; + ++ /* set DMA operations */ ++ if (dwc->dev->of_node && of_dma_is_coherent(dwc->dev->of_node)) { ++ xhci->dev.archdata.dma_ops = dwc->dev->archdata.dma_ops; ++ dev_dbg(dwc->dev, "set dma_ops for usb\n"); ++ } ++ + dwc->xhci = xhci; + + ret = platform_device_add_resources(xhci, dwc->xhci_resources, +diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c +index 7e5c90e..c6027ac 100644 +--- a/drivers/usb/host/xhci-pci.c ++++ b/drivers/usb/host/xhci-pci.c +@@ -23,10 +23,17 @@ + #include + #include + #include ++#include + + #include "xhci.h" + #include "xhci-trace.h" + ++#define SSIC_PORT_NUM 2 ++#define SSIC_PORT_CFG2 0x880c ++#define SSIC_PORT_CFG2_OFFSET 0x30 ++#define PROG_DONE (1 << 30) ++#define SSIC_PORT_UNUSED (1 << 31) ++ + /* Device for a quirk */ + #define PCI_VENDOR_ID_FRESCO_LOGIC 0x1b73 + #define PCI_DEVICE_ID_FRESCO_LOGIC_PDK 0x1000 +@@ -40,6 +47,8 @@ + #define PCI_DEVICE_ID_INTEL_CHERRYVIEW_XHCI 0x22b5 + #define PCI_DEVICE_ID_INTEL_SUNRISEPOINT_H_XHCI 0xa12f + #define PCI_DEVICE_ID_INTEL_SUNRISEPOINT_LP_XHCI 0x9d2f ++#define PCI_DEVICE_ID_INTEL_BROXTON_M_XHCI 0x0aa8 ++#define PCI_DEVICE_ID_INTEL_BROXTON_B_XHCI 0x1aa8 + + static const char hcd_name[] = "xhci_hcd"; + +@@ -140,9 +149,15 @@ static void xhci_pci_quirks(struct device *dev, struct xhci_hcd *xhci) + if (pdev->vendor == PCI_VENDOR_ID_INTEL && + (pdev->device == PCI_DEVICE_ID_INTEL_SUNRISEPOINT_LP_XHCI || + pdev->device == PCI_DEVICE_ID_INTEL_SUNRISEPOINT_H_XHCI || +- pdev->device == PCI_DEVICE_ID_INTEL_CHERRYVIEW_XHCI)) { ++ pdev->device == PCI_DEVICE_ID_INTEL_CHERRYVIEW_XHCI || ++ pdev->device == PCI_DEVICE_ID_INTEL_BROXTON_M_XHCI || ++ pdev->device == PCI_DEVICE_ID_INTEL_BROXTON_B_XHCI)) { + xhci->quirks |= XHCI_PME_STUCK_QUIRK; + } ++ if (pdev->vendor == PCI_VENDOR_ID_INTEL && ++ pdev->device == PCI_DEVICE_ID_INTEL_CHERRYVIEW_XHCI) { ++ xhci->quirks |= XHCI_SSIC_PORT_UNUSED; ++ } + if (pdev->vendor == PCI_VENDOR_ID_ETRON && + pdev->device == PCI_DEVICE_ID_EJ168) { + xhci->quirks |= XHCI_RESET_ON_RESUME; +@@ -169,20 +184,18 @@ static void xhci_pci_quirks(struct device *dev, struct xhci_hcd *xhci) + "QUIRK: Resetting on resume"); + } + +-/* +- * Make sure PME works on some Intel xHCI controllers by writing 1 to clear +- * the Internal PME flag bit in vendor specific PMCTRL register at offset 0x80a4 +- */ +-static void xhci_pme_quirk(struct xhci_hcd *xhci) ++#ifdef CONFIG_ACPI ++static void xhci_pme_acpi_rtd3_enable(struct pci_dev *dev) + { +- u32 val; +- void __iomem *reg; +- +- reg = (void __iomem *) xhci->cap_regs + 0x80a4; +- val = readl(reg); +- writel(val | BIT(28), reg); +- readl(reg); ++ static const u8 intel_dsm_uuid[] = { ++ 0xb7, 0x0c, 0x34, 0xac, 0x01, 0xe9, 0xbf, 0x45, ++ 0xb7, 0xe6, 0x2b, 0x34, 0xec, 0x93, 0x1e, 0x23, ++ }; ++ acpi_evaluate_dsm(ACPI_HANDLE(&dev->dev), intel_dsm_uuid, 3, 1, NULL); + } ++#else ++ static void xhci_pme_acpi_rtd3_enable(struct pci_dev *dev) { } ++#endif /* CONFIG_ACPI */ + + /* called during probe() after chip reset completes */ + static int xhci_pci_setup(struct usb_hcd *hcd) +@@ -263,6 +276,9 @@ static int xhci_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) + HCC_MAX_PSA(xhci->hcc_params) >= 4) + xhci->shared_hcd->can_do_streams = 1; + ++ if (xhci->quirks & XHCI_PME_STUCK_QUIRK) ++ xhci_pme_acpi_rtd3_enable(dev); ++ + /* USB-2 and USB-3 roothubs initialized, allow runtime pm suspend */ + pm_runtime_put_noidle(&dev->dev); + +@@ -282,6 +298,7 @@ static void xhci_pci_remove(struct pci_dev *dev) + struct xhci_hcd *xhci; + + xhci = hcd_to_xhci(pci_get_drvdata(dev)); ++ xhci->xhc_state |= XHCI_STATE_REMOVING; + if (xhci->shared_hcd) { + usb_remove_hcd(xhci->shared_hcd); + usb_put_hcd(xhci->shared_hcd); +@@ -296,10 +313,65 @@ static void xhci_pci_remove(struct pci_dev *dev) + } + + #ifdef CONFIG_PM ++/* ++ * In some Intel xHCI controllers, in order to get D3 working, ++ * through a vendor specific SSIC CONFIG register at offset 0x883c, ++ * SSIC PORT need to be marked as "unused" before putting xHCI ++ * into D3. After D3 exit, the SSIC port need to be marked as "used". ++ * Without this change, xHCI might not enter D3 state. ++ */ ++static void xhci_ssic_port_unused_quirk(struct usb_hcd *hcd, bool suspend) ++{ ++ struct xhci_hcd *xhci = hcd_to_xhci(hcd); ++ u32 val; ++ void __iomem *reg; ++ int i; ++ ++ for (i = 0; i < SSIC_PORT_NUM; i++) { ++ reg = (void __iomem *) xhci->cap_regs + ++ SSIC_PORT_CFG2 + ++ i * SSIC_PORT_CFG2_OFFSET; ++ ++ /* Notify SSIC that SSIC profile programming is not done. */ ++ val = readl(reg) & ~PROG_DONE; ++ writel(val, reg); ++ ++ /* Mark SSIC port as unused(suspend) or used(resume) */ ++ val = readl(reg); ++ if (suspend) ++ val |= SSIC_PORT_UNUSED; ++ else ++ val &= ~SSIC_PORT_UNUSED; ++ writel(val, reg); ++ ++ /* Notify SSIC that SSIC profile programming is done */ ++ val = readl(reg) | PROG_DONE; ++ writel(val, reg); ++ readl(reg); ++ } ++} ++ ++/* ++ * Make sure PME works on some Intel xHCI controllers by writing 1 to clear ++ * the Internal PME flag bit in vendor specific PMCTRL register at offset 0x80a4 ++ */ ++static void xhci_pme_quirk(struct usb_hcd *hcd) ++{ ++ struct xhci_hcd *xhci = hcd_to_xhci(hcd); ++ void __iomem *reg; ++ u32 val; ++ ++ reg = (void __iomem *) xhci->cap_regs + 0x80a4; ++ val = readl(reg); ++ writel(val | BIT(28), reg); ++ readl(reg); ++} ++ + static int xhci_pci_suspend(struct usb_hcd *hcd, bool do_wakeup) + { + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + struct pci_dev *pdev = to_pci_dev(hcd->self.controller); ++ int ret; + + /* + * Systems with the TI redriver that loses port status change events +@@ -309,9 +381,16 @@ static int xhci_pci_suspend(struct usb_hcd *hcd, bool do_wakeup) + pdev->no_d3cold = true; + + if (xhci->quirks & XHCI_PME_STUCK_QUIRK) +- xhci_pme_quirk(xhci); ++ xhci_pme_quirk(hcd); ++ ++ if (xhci->quirks & XHCI_SSIC_PORT_UNUSED) ++ xhci_ssic_port_unused_quirk(hcd, true); + +- return xhci_suspend(xhci, do_wakeup); ++ ret = xhci_suspend(xhci, do_wakeup); ++ if (ret && (xhci->quirks & XHCI_SSIC_PORT_UNUSED)) ++ xhci_ssic_port_unused_quirk(hcd, false); ++ ++ return ret; + } + + static int xhci_pci_resume(struct usb_hcd *hcd, bool hibernated) +@@ -341,8 +420,11 @@ static int xhci_pci_resume(struct usb_hcd *hcd, bool hibernated) + if (pdev->vendor == PCI_VENDOR_ID_INTEL) + usb_enable_intel_xhci_ports(pdev); + ++ if (xhci->quirks & XHCI_SSIC_PORT_UNUSED) ++ xhci_ssic_port_unused_quirk(hcd, false); ++ + if (xhci->quirks & XHCI_PME_STUCK_QUIRK) +- xhci_pme_quirk(xhci); ++ xhci_pme_quirk(hcd); + + retval = xhci_resume(xhci, hibernated); + return retval; +diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c +index 1e5fb8c..04e7525 100644 +--- a/drivers/usb/host/xhci-ring.c ++++ b/drivers/usb/host/xhci-ring.c +@@ -3840,8 +3840,12 @@ static int queue_command(struct xhci_hcd *xhci, struct xhci_command *cmd, + { + int reserved_trbs = xhci->cmd_ring_reserved_trbs; + int ret; +- if (xhci->xhc_state & XHCI_STATE_DYING) ++ ++ if ((xhci->xhc_state & XHCI_STATE_DYING) || ++ (xhci->xhc_state & XHCI_STATE_HALTED)) { ++ xhci_dbg(xhci, "xHCI dying or halted, can't queue_command\n"); + return -ESHUTDOWN; ++ } + + if (!command_must_succeed) + reserved_trbs++; +diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c +index 98380fa..600a137 100644 +--- a/drivers/usb/host/xhci.c ++++ b/drivers/usb/host/xhci.c +@@ -147,7 +147,8 @@ static int xhci_start(struct xhci_hcd *xhci) + "waited %u microseconds.\n", + XHCI_MAX_HALT_USEC); + if (!ret) +- xhci->xhc_state &= ~(XHCI_STATE_HALTED | XHCI_STATE_DYING); ++ /* clear state flags. Including dying, halted or removing */ ++ xhci->xhc_state = 0; + + return ret; + } +@@ -1102,8 +1103,8 @@ int xhci_resume(struct xhci_hcd *xhci, bool hibernated) + /* Resume root hubs only when have pending events. */ + status = readl(&xhci->op_regs->status); + if (status & STS_EINT) { +- usb_hcd_resume_root_hub(hcd); + usb_hcd_resume_root_hub(xhci->shared_hcd); ++ usb_hcd_resume_root_hub(hcd); + } + } + +@@ -1118,10 +1119,10 @@ int xhci_resume(struct xhci_hcd *xhci, bool hibernated) + + /* Re-enable port polling. */ + xhci_dbg(xhci, "%s: starting port polling.\n", __func__); +- set_bit(HCD_FLAG_POLL_RH, &hcd->flags); +- usb_hcd_poll_rh_status(hcd); + set_bit(HCD_FLAG_POLL_RH, &xhci->shared_hcd->flags); + usb_hcd_poll_rh_status(xhci->shared_hcd); ++ set_bit(HCD_FLAG_POLL_RH, &hcd->flags); ++ usb_hcd_poll_rh_status(hcd); + + return retval; + } +@@ -1548,7 +1549,9 @@ int xhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) + xhci_dbg_trace(xhci, trace_xhci_dbg_cancel_urb, + "HW died, freeing TD."); + urb_priv = urb->hcpriv; +- for (i = urb_priv->td_cnt; i < urb_priv->length; i++) { ++ for (i = urb_priv->td_cnt; ++ i < urb_priv->length && xhci->devs[urb->dev->slot_id]; ++ i++) { + td = urb_priv->td[i]; + if (!list_empty(&td->td_list)) + list_del_init(&td->td_list); +@@ -1682,8 +1685,10 @@ int xhci_drop_endpoint(struct usb_hcd *hcd, struct usb_device *udev, + cpu_to_le32(EP_STATE_DISABLED)) || + le32_to_cpu(ctrl_ctx->drop_flags) & + xhci_get_endpoint_flag(&ep->desc)) { +- xhci_warn(xhci, "xHCI %s called with disabled ep %p\n", +- __func__, ep); ++ /* Do not warn when called after a usb_device_reset */ ++ if (xhci->devs[udev->slot_id]->eps[ep_index].ring != NULL) ++ xhci_warn(xhci, "xHCI %s called with disabled ep %p\n", ++ __func__, ep); + return 0; + } + +@@ -2751,7 +2756,8 @@ int xhci_check_bandwidth(struct usb_hcd *hcd, struct usb_device *udev) + if (ret <= 0) + return ret; + xhci = hcd_to_xhci(hcd); +- if (xhci->xhc_state & XHCI_STATE_DYING) ++ if ((xhci->xhc_state & XHCI_STATE_DYING) || ++ (xhci->xhc_state & XHCI_STATE_REMOVING)) + return -ENODEV; + + xhci_dbg(xhci, "%s called for udev %p\n", __func__, udev); +@@ -3793,7 +3799,7 @@ static int xhci_setup_device(struct usb_hcd *hcd, struct usb_device *udev, + u64 temp_64; + struct xhci_command *command; + +- if (xhci->xhc_state) /* dying or halted */ ++ if (xhci->xhc_state) /* dying, removing or halted */ + return -EINVAL; + + if (!udev->slot_id) { +@@ -4912,6 +4918,16 @@ int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks) + goto error; + xhci_dbg(xhci, "Reset complete\n"); + ++ /* ++ * On some xHCI controllers (e.g. R-Car SoCs), the AC64 bit (bit 0) ++ * of HCCPARAMS1 is set to 1. However, the xHCs don't support 64-bit ++ * address memory pointers actually. So, this driver clears the AC64 ++ * bit of xhci->hcc_params to call dma_set_coherent_mask(dev, ++ * DMA_BIT_MASK(32)) in this xhci_gen_setup(). ++ */ ++ if (xhci->quirks & XHCI_NO_64BIT_SUPPORT) ++ xhci->hcc_params &= ~BIT(0); ++ + /* Set dma_mask and coherent_dma_mask to 64-bits, + * if xHC supports 64-bit addressing */ + if (HCC_64BIT_ADDR(xhci->hcc_params) && +diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h +index 54f386f..3850cb2 100644 +--- a/drivers/usb/host/xhci.h ++++ b/drivers/usb/host/xhci.h +@@ -1531,6 +1531,7 @@ struct xhci_hcd { + */ + #define XHCI_STATE_DYING (1 << 0) + #define XHCI_STATE_HALTED (1 << 1) ++#define XHCI_STATE_REMOVING (1 << 2) + /* Statistics */ + int error_bitmask; + unsigned int quirks; +@@ -1565,6 +1566,8 @@ struct xhci_hcd { + /* For controllers with a broken beyond repair streams implementation */ + #define XHCI_BROKEN_STREAMS (1 << 19) + #define XHCI_PME_STUCK_QUIRK (1 << 20) ++#define XHCI_SSIC_PORT_UNUSED (1 << 22) ++#define XHCI_NO_64BIT_SUPPORT (1 << 23) + unsigned int num_active_eps; + unsigned int limit_active_eps; + /* There are two roothubs to keep track of bus suspend info for */ +diff --git a/drivers/vfio/Kconfig b/drivers/vfio/Kconfig +index d8c5763..db494c0 100644 +--- a/drivers/vfio/Kconfig ++++ b/drivers/vfio/Kconfig +@@ -16,7 +16,7 @@ config VFIO_SPAPR_EEH + menuconfig VFIO + tristate "VFIO Non-Privileged userspace driver framework" + depends on IOMMU_API +- select VFIO_IOMMU_TYPE1 if X86 ++ select VFIO_IOMMU_TYPE1 if (X86 || ARM_SMMU) + select VFIO_IOMMU_SPAPR_TCE if (PPC_POWERNV || PPC_PSERIES) + select VFIO_SPAPR_EEH if (PPC_POWERNV || PPC_PSERIES) + select ANON_INODES +@@ -27,3 +27,6 @@ menuconfig VFIO + If you don't know what to do here, say N. + + source "drivers/vfio/pci/Kconfig" ++#source "drivers/vfio/platform/Kconfig" ++source "drivers/vfio/fsl-mc/Kconfig" ++ +diff --git a/drivers/vfio/Makefile b/drivers/vfio/Makefile +index 0b035b1..69bcd84 100644 +--- a/drivers/vfio/Makefile ++++ b/drivers/vfio/Makefile +@@ -3,3 +3,4 @@ obj-$(CONFIG_VFIO_IOMMU_TYPE1) += vfio_iommu_type1.o + obj-$(CONFIG_VFIO_IOMMU_SPAPR_TCE) += vfio_iommu_spapr_tce.o + obj-$(CONFIG_VFIO_SPAPR_EEH) += vfio_spapr_eeh.o + obj-$(CONFIG_VFIO_PCI) += pci/ ++obj-$(CONFIG_VFIO_FSL_MC) += fsl-mc/ +diff --git a/drivers/vfio/fsl-mc/Kconfig b/drivers/vfio/fsl-mc/Kconfig +new file mode 100644 +index 0000000..eb6ba2b +--- /dev/null ++++ b/drivers/vfio/fsl-mc/Kconfig +@@ -0,0 +1,9 @@ ++config VFIO_FSL_MC ++ tristate "VFIO support for Freescale Management Complex devices" ++ depends on VFIO && FSL_MC_BUS && EVENTFD ++ help ++ Support for the Freescale Management Complex(MC) VFIO driver. ++ This is required to passthrough Freescale MC devices using the ++ VFIO framework. ++ ++ If you don't know what to do here, say N. +diff --git a/drivers/vfio/fsl-mc/Makefile b/drivers/vfio/fsl-mc/Makefile +new file mode 100644 +index 0000000..2aca75a +--- /dev/null ++++ b/drivers/vfio/fsl-mc/Makefile +@@ -0,0 +1,2 @@ ++vfio-fsl_mc-y := vfio_fsl_mc.o ++obj-$(CONFIG_VFIO_FSL_MC) += vfio_fsl_mc.o vfio_fsl_mc_intr.o +diff --git a/drivers/vfio/fsl-mc/vfio_fsl_mc.c b/drivers/vfio/fsl-mc/vfio_fsl_mc.c +new file mode 100644 +index 0000000..ffbe845 +--- /dev/null ++++ b/drivers/vfio/fsl-mc/vfio_fsl_mc.c +@@ -0,0 +1,603 @@ ++/* ++ * Freescale Management Complex (MC) device passthrough using VFIO ++ * ++ * Copyright (C) 2014 Freescale Semiconductor, Inc. ++ * Author: Bharat Bhushan ++ * ++ * This file is licensed under the terms of the GNU General Public ++ * License version 2. This program is licensed "as is" without any ++ * warranty of any kind, whether express or implied. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "../../staging/fsl-mc/include/mc.h" ++#include "../../staging/fsl-mc/include/mc-sys.h" ++#include "../../staging/fsl-mc/include/mc-private.h" ++ ++#include "vfio_fsl_mc_private.h" ++struct fsl_mc_io *vfio_mc_io = NULL; ++struct fsl_mc_io *vfio_atomic_mc_io = NULL; ++ ++static DEFINE_MUTEX(driver_lock); ++ ++/* Validate that requested address range falls in one of container's ++ * device region. ++ */ ++static bool vfio_validate_mmap_addr(struct vfio_fsl_mc_device *vdev, ++ unsigned long addr, unsigned long size) ++{ ++ struct fsl_mc_device *mc_dev = vdev->mc_dev; ++ phys_addr_t region_addr; ++ size_t region_size; ++ int idx; ++ ++ /* Do not try to validate if address range wraps */ ++ if ((addr + size) < addr) ++ return false; ++ ++ /* Hack to allow mmap GITS_TRANSLATOR Register Page */ ++ if (addr == 0x6030000) ++ return true; ++ ++ for (idx = 0; idx < mc_dev->obj_desc.region_count; idx++) { ++ region_addr = mc_dev->regions[idx].start; ++ region_size = mc_dev->regions[idx].end - ++ mc_dev->regions[idx].start + 1; ++ ++ /* ++ * Align search to minimum mappable size of PAGE_SIZE. ++ * Thanks to our hardware that even though the ++ * region_size is less then PAGE_SIZE but there ++ * is no other device maps in this address range. ++ * So no security threat/issue in mapping PAGE_SIZE. ++ */ ++ if (region_size < PAGE_SIZE) ++ region_size = PAGE_SIZE; ++ ++ if (addr >= region_addr && ++ ((addr + size) <= (region_addr + region_size))) ++ return true; ++ } ++ ++ return false; ++} ++ ++static long vfio_fsl_mc_ioctl(void *device_data, ++ unsigned int cmd, unsigned long arg) ++{ ++ struct vfio_fsl_mc_device *vdev = device_data; ++ struct fsl_mc_device *mc_dev = vdev->mc_dev; ++ struct device *dev = &mc_dev->dev; ++ unsigned long minsz; ++ int ret; ++ ++ if (WARN_ON(!mc_dev)) ++ return -ENODEV; ++ ++ switch (cmd) { ++ case VFIO_DEVICE_GET_INFO: ++ { ++ struct vfio_device_info info; ++ ++ minsz = offsetofend(struct vfio_device_info, num_irqs); ++ ++ if (copy_from_user(&info, (void __user *)arg, minsz)) ++ return -EFAULT; ++ ++ if (info.argsz < minsz) ++ return -EINVAL; ++ ++ info.flags = VFIO_DEVICE_FLAGS_FSL_MC; ++ if (strcmp(mc_dev->obj_desc.type, "dprc") == 0) ++ info.flags |= VFIO_DEVICE_FLAGS_RESET; ++ ++ info.num_regions = mc_dev->obj_desc.region_count; ++ info.num_irqs = mc_dev->obj_desc.irq_count; ++ ++ return copy_to_user((void __user *)arg, &info, minsz); ++ } ++ case VFIO_DEVICE_GET_REGION_INFO: ++ { ++ struct vfio_region_info info; ++ ++ minsz = offsetofend(struct vfio_region_info, offset); ++ ++ if (copy_from_user(&info, (void __user *)arg, minsz)) ++ return -EFAULT; ++ ++ if (info.argsz < minsz) ++ return -EINVAL; ++ ++ info.offset = mc_dev->regions[info.index].start; ++ info.size = mc_dev->regions[info.index].end - ++ mc_dev->regions[info.index].start + 1; ++ info.flags = VFIO_REGION_INFO_FLAG_READ | ++ VFIO_REGION_INFO_FLAG_WRITE | ++ VFIO_REGION_INFO_FLAG_MMAP; ++ ++ return copy_to_user((void __user *)arg, &info, minsz); ++ } ++ case VFIO_DEVICE_GET_IRQ_INFO: ++ { ++ struct vfio_irq_info info; ++ ++ minsz = offsetofend(struct vfio_irq_info, count); ++ if (copy_from_user(&info, (void __user *)arg, minsz)) ++ return -EFAULT; ++ ++ if (info.argsz < minsz) ++ return -EINVAL; ++ ++ if (info.index >= mc_dev->obj_desc.irq_count) ++ return -EINVAL; ++ ++ if (vdev->mc_irqs[info.index].irq_initialized) { ++ info.flags = vdev->mc_irqs[info.index].flags; ++ info.count = vdev->mc_irqs[info.index].count; ++ } else { ++ /* ++ * If IRQs are not initialized then these can not ++ * be configuted and used by user-space/ ++ */ ++ info.flags = 0; ++ info.count = 0; ++ } ++ ++ return copy_to_user((void __user *)arg, &info, minsz); ++ } ++ case VFIO_DEVICE_SET_IRQS: ++ { ++ struct vfio_irq_set hdr; ++ u8 *data = NULL; ++ int ret = 0; ++ ++ minsz = offsetofend(struct vfio_irq_set, count); ++ ++ if (copy_from_user(&hdr, (void __user *)arg, minsz)) ++ return -EFAULT; ++ ++ if (hdr.argsz < minsz) ++ return -EINVAL; ++ ++ if (hdr.index >= mc_dev->obj_desc.irq_count) ++ return -EINVAL; ++ ++ if (hdr.start != 0 || hdr.count > 1) ++ return -EINVAL; ++ ++ if (hdr.count == 0 && ++ (!(hdr.flags & VFIO_IRQ_SET_DATA_NONE) || ++ !(hdr.flags & VFIO_IRQ_SET_ACTION_TRIGGER))) ++ return -EINVAL; ++ ++ if (hdr.flags & ~(VFIO_IRQ_SET_DATA_TYPE_MASK | ++ VFIO_IRQ_SET_ACTION_TYPE_MASK)) ++ return -EINVAL; ++ ++ if (!(hdr.flags & VFIO_IRQ_SET_DATA_NONE)) { ++ size_t size; ++ ++ if (hdr.flags & VFIO_IRQ_SET_DATA_BOOL) ++ size = sizeof(uint8_t); ++ else if (hdr.flags & VFIO_IRQ_SET_DATA_EVENTFD) ++ size = sizeof(int32_t); ++ else ++ return -EINVAL; ++ ++ if (hdr.argsz - minsz < hdr.count * size) ++ return -EINVAL; ++ ++ data = memdup_user((void __user *)(arg + minsz), ++ hdr.count * size); ++ if (IS_ERR(data)) ++ return PTR_ERR(data); ++ } ++ ++ ret = vfio_fsl_mc_set_irqs_ioctl(vdev, hdr.flags, ++ hdr.index, hdr.start, ++ hdr.count, data); ++ return ret; ++ } ++ case VFIO_DEVICE_RESET: ++ { ++ if (strcmp(mc_dev->obj_desc.type, "dprc") != 0) ++ return -EINVAL; ++ ++ ret = dprc_reset_container(mc_dev->mc_io, 0, ++ mc_dev->mc_handle, ++ mc_dev->obj_desc.id); ++ if (ret) { ++ dev_err(dev, "Error in resetting container %d\n", ret); ++ return ret; ++ } ++ ++ ret = 0; ++ break; ++ } ++ default: ++ ret = -EINVAL; ++ } ++ ++ return ret; ++} ++ ++static ssize_t vfio_fsl_mc_read(void *device_data, char __user *buf, ++ size_t count, loff_t *ppos) ++{ ++ struct vfio_fsl_mc_device *vdev = device_data; ++ struct fsl_mc_device *mc_dev = vdev->mc_dev; ++ struct device *dev = &mc_dev->dev; ++ ++ dev_err(dev, "%s: Unimplemented\n", __func__); ++ return -EFAULT; ++} ++ ++static ssize_t vfio_fsl_mc_write(void *device_data, const char __user *buf, ++ size_t count, loff_t *ppos) ++{ ++ struct vfio_fsl_mc_device *vdev = device_data; ++ struct fsl_mc_device *mc_dev = vdev->mc_dev; ++ struct device *dev = &mc_dev->dev; ++ ++ dev_err(dev, "%s: Unimplemented\n", __func__); ++ return -EFAULT; ++} ++ ++/* Allows mmaping fsl_mc device regions in assigned DPRC */ ++static int vfio_fsl_mc_mmap(void *device_data, struct vm_area_struct *vma) ++{ ++ struct vfio_fsl_mc_device *vdev = device_data; ++ struct fsl_mc_device *mc_dev = vdev->mc_dev; ++ unsigned long size = vma->vm_end - vma->vm_start; ++ unsigned long addr = vma->vm_pgoff << PAGE_SHIFT; ++ int ret; ++ ++ if (vma->vm_end < vma->vm_start) ++ return -EINVAL; ++ if (vma->vm_start & ~PAGE_MASK) ++ return -EINVAL; ++ if (vma->vm_end & ~PAGE_MASK) ++ return -EINVAL; ++ if ((vma->vm_flags & VM_SHARED) == 0) ++ return -EINVAL; ++ ++ if (!vfio_validate_mmap_addr(vdev, addr, size)) ++ return -EINVAL; ++ ++ vma->vm_private_data = mc_dev; ++ ++#define QBMAN_SWP_CENA_BASE 0x818000000ULL ++ if ((addr & 0xFFF000000) == QBMAN_SWP_CENA_BASE) ++ vma->vm_page_prot = pgprot_cached_ns(vma->vm_page_prot); ++ else ++ vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); ++ ++ ret = remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, ++ size, vma->vm_page_prot); ++ return ret; ++} ++ ++static void vfio_fsl_mc_release(void *device_data) ++{ ++ struct vfio_fsl_mc_device *vdev = device_data; ++ struct fsl_mc_device *mc_dev = vdev->mc_dev; ++ ++ if (WARN_ON(mc_dev == NULL)) ++ return; ++ ++ mutex_lock(&driver_lock); ++ vdev->refcnt--; ++ ++ if (strcmp(mc_dev->obj_desc.type, "dprc") == 0) ++ dprc_reset_container(mc_dev->mc_io, 0, mc_dev->mc_handle, ++ mc_dev->obj_desc.id); ++ else ++ vfio_fsl_mc_unconfigure_irqs(vdev); ++ ++ mutex_unlock(&driver_lock); ++ ++ module_put(THIS_MODULE); ++} ++ ++static int vfio_fsl_mc_open(void *device_data) ++{ ++ struct vfio_fsl_mc_device *vdev = device_data; ++ ++ if (!try_module_get(THIS_MODULE)) ++ return -ENODEV; ++ ++ mutex_lock(&driver_lock); ++ vdev->refcnt++; ++ mutex_unlock(&driver_lock); ++ ++ return 0; ++} ++ ++static const struct vfio_device_ops vfio_fsl_mc_ops = { ++ .name = "vfio-fsl-mc", ++ .open = vfio_fsl_mc_open, ++ .release = vfio_fsl_mc_release, ++ .ioctl = vfio_fsl_mc_ioctl, ++ .read = vfio_fsl_mc_read, ++ .write = vfio_fsl_mc_write, ++ .mmap = vfio_fsl_mc_mmap, ++}; ++ ++static int vfio_fsl_mc_device_remove(struct device *dev, void *data) ++{ ++ struct fsl_mc_device *mc_dev; ++ WARN_ON(dev == NULL); ++ ++ mc_dev = to_fsl_mc_device(dev); ++ if (WARN_ON(mc_dev == NULL)) ++ return -ENODEV; ++ ++ fsl_mc_device_remove(mc_dev); ++ return 0; ++} ++ ++static int vfio_fsl_mc_probe(struct fsl_mc_device *mc_dev) ++{ ++ struct vfio_fsl_mc_device *vdev; ++ struct iommu_group *group; ++ struct device *dev = &mc_dev->dev; ++ struct fsl_mc_bus *mc_bus; ++ unsigned int irq_count; ++ int ret; ++ ++ dev_info(dev, "Binding with vfio-fsl_mc driver\n"); ++ ++ group = iommu_group_get(dev); ++ if (!group) { ++ dev_err(dev, "%s: VFIO: No IOMMU group\n", __func__); ++ return -EINVAL; ++ } ++ ++ vdev = kzalloc(sizeof(*vdev), GFP_KERNEL); ++ if (!vdev) { ++ iommu_group_put(group); ++ return -ENOMEM; ++ } ++ ++ if (strcmp(mc_dev->obj_desc.type, "dprc") == 0) { ++ vdev->mc_dev = mc_dev; ++ ++ /* Free inbuilt dprc MC portal if exists */ ++ if (mc_dev->mc_io && (mc_dev->mc_io != vfio_mc_io)) ++ fsl_destroy_mc_io(mc_dev->mc_io); ++ ++ /* Use New Allocated MC Portal (DPMCP object) */ ++ mc_dev->mc_io = vfio_mc_io; ++ ++ ret = dprc_open(mc_dev->mc_io, ++ 0, ++ mc_dev->obj_desc.id, ++ &mc_dev->mc_handle); ++ if (ret) { ++ dev_err(dev, "dprc_open() failed: error = %d\n", ret); ++ goto free_vfio_device; ++ } ++ ++ /* Initialize resource pool */ ++ dprc_init_all_resource_pools(mc_dev); ++ ++ mc_bus = to_fsl_mc_bus(mc_dev); ++ mutex_init(&mc_bus->scan_mutex); ++ ++ mc_bus->atomic_mc_io = vfio_atomic_mc_io; ++ ret = dprc_open(mc_bus->atomic_mc_io, 0, mc_dev->obj_desc.id, ++ &mc_bus->atomic_dprc_handle); ++ if (ret < 0) { ++ dev_err(dev, "fail to open dprc with atomic io (%d)\n", ret); ++ goto clean_resource_pool; ++ } ++ ++ if (fsl_mc_interrupts_supported() && !mc_bus->irq_resources) { ++ irq_count = FSL_MC_IRQ_POOL_MAX_EXTRA_IRQS; ++ ret = fsl_mc_populate_irq_pool(mc_bus, irq_count); ++ if (ret < 0) { ++ dev_err(dev, "%s: Failed to init irq-pool\n", ++ __func__); ++ goto free_open_dprc; ++ } ++ } ++ ++ mutex_lock(&mc_bus->scan_mutex); ++ ret = dprc_scan_objects(mc_dev, mc_dev->driver_override, ++ &irq_count); ++ mutex_unlock(&mc_bus->scan_mutex); ++ if (ret) { ++ dev_err(dev, "dprc_scan_objects() fails (%d)\n", ret); ++ goto clean_irq_pool; ++ } ++ ++ ret = vfio_add_group_dev(dev, &vfio_fsl_mc_ops, vdev); ++ if (ret) { ++ dev_err(dev, "%s: Failed to add to vfio group\n", ++ __func__); ++ goto dprc_clean_scan_objects; ++ } ++ ++ ret = vfio_fsl_mc_init_irqs(vdev); ++ if (ret) { ++ dev_err(dev, "%s: Failed to setup irqs\n", ++ __func__); ++ vfio_del_group_dev(dev); ++ goto dprc_clean_scan_objects; ++ } ++ } else { ++ vdev->mc_dev = mc_dev; ++ ++ /* Use New Allocated MC Portal (DPMCP object) */ ++ mc_dev->mc_io = vfio_mc_io; ++ ++ ret = vfio_add_group_dev(dev, &vfio_fsl_mc_ops, vdev); ++ if (ret) { ++ dev_err(dev, "%s: Failed to add to vfio group\n", ++ __func__); ++ goto free_vfio_device; ++ } ++ ++ if (mc_dev->obj_desc.irq_count) { ++ ret = vfio_fsl_mc_init_irqs(vdev); ++ if (ret) { ++ dev_err(dev, "%s: Failed to setup irqs\n", ++ __func__); ++ vfio_del_group_dev(dev); ++ goto free_vfio_device; ++ } ++ } ++ } ++ ++ return 0; ++ ++dprc_clean_scan_objects: ++ fsl_mc_cleanup_irq_pool(mc_bus); ++ device_for_each_child(&mc_dev->dev, NULL, vfio_fsl_mc_device_remove); ++ ++clean_irq_pool: ++ fsl_mc_cleanup_irq_pool(mc_bus); ++ ++free_open_dprc: ++ dprc_close(vfio_atomic_mc_io, 0, mc_dev->mc_handle); ++ ++clean_resource_pool: ++ dprc_cleanup_all_resource_pools(mc_dev); ++ dprc_close(mc_dev->mc_io, 0, mc_dev->mc_handle); ++ ++free_vfio_device: ++ kfree(vdev); ++ iommu_group_put(group); ++ return ret; ++} ++ ++static int vfio_fsl_mc_remove(struct fsl_mc_device *mc_dev) ++{ ++ struct vfio_fsl_mc_device *vdev; ++ struct fsl_mc_bus *mc_bus; ++ struct device *dev = &mc_dev->dev; ++ int ret; ++ ++ dev_info(dev, "Un-binding with vfio-fsl-mc driver\n"); ++ ++ vdev = vfio_del_group_dev(dev); ++ if (!vdev) ++ return -EINVAL; ++ ++ /* Only FSL-MC DPRC device can be unbound */ ++ if (strcmp(mc_dev->obj_desc.type, "dprc") == 0) { ++ device_for_each_child(dev, NULL, vfio_fsl_mc_device_remove); ++ ++ vfio_fsl_mc_free_irqs(vdev); ++ dprc_cleanup_all_resource_pools(mc_dev); ++ mc_bus = to_fsl_mc_bus(mc_dev); ++ ++ if (fsl_mc_interrupts_supported()) ++ fsl_mc_cleanup_irq_pool(mc_bus); ++ ++ ret = dprc_close(mc_dev->mc_io, 0, mc_dev->mc_handle); ++ if (ret < 0) ++ dev_err(dev, "dprc_close() fails %d\n", ret); ++ ++ ret = dprc_close(mc_bus->atomic_mc_io, 0, ++ mc_bus->atomic_dprc_handle); ++ if (ret < 0) ++ dev_err(dev, "dprc_close(atomic-io) fails %d\n", ret); ++ } else { ++ if (mc_dev->obj_desc.irq_count) ++ vfio_fsl_mc_free_irqs(vdev); ++ ++ mc_dev->mc_io = NULL; ++ } ++ ++ iommu_group_put(mc_dev->dev.iommu_group); ++ kfree(vdev); ++ ++ return 0; ++} ++ ++/* ++ * vfio-fsl_mc is a meta-driver, so use driver_override interface to ++ * bind a fsl_mc container with this driver and match_id_table is NULL. ++ */ ++static struct fsl_mc_driver vfio_fsl_mc_driver = { ++ .probe = vfio_fsl_mc_probe, ++ .remove = vfio_fsl_mc_remove, ++ .match_id_table = NULL, ++ .driver = { ++ .name = "vfio-fsl-mc", ++ .owner = THIS_MODULE, ++ }, ++}; ++ ++static int __init vfio_fsl_mc_driver_init(void) ++{ ++ int err; ++ struct fsl_mc_device *root_mc_dev; ++ ++ if (fsl_mc_bus_type.dev_root == NULL) { ++ pr_err("%s: Driver registration fails as no fsl_mc_bus found\n", ++ __func__); ++ return -ENODEV; ++ } ++ ++ root_mc_dev = to_fsl_mc_device(fsl_mc_bus_type.dev_root); ++ ++ /* Allocate a new MC portal (DPMCP object) */ ++ err = fsl_mc_portal_allocate(root_mc_dev, 0, &vfio_mc_io); ++ if (err < 0) ++ goto err; ++ ++ /* Reset MCP before move on */ ++ err = fsl_mc_portal_reset(vfio_mc_io); ++ if (err < 0) ++ return err; ++ ++ /* Allocate a new MC portal (DPMCP object) */ ++ err = fsl_mc_portal_allocate(root_mc_dev, ++ FSL_MC_IO_ATOMIC_CONTEXT_PORTAL, ++ &vfio_atomic_mc_io); ++ if (err < 0) ++ goto err; ++ ++ err = fsl_mc_driver_register(&vfio_fsl_mc_driver); ++ if (err < 0) ++ goto err; ++ ++ return 0; ++err: ++ if (vfio_mc_io) ++ fsl_mc_portal_free(vfio_mc_io); ++ ++ if (vfio_atomic_mc_io) ++ fsl_mc_portal_free(vfio_atomic_mc_io); ++ ++ vfio_atomic_mc_io = NULL; ++ vfio_mc_io = NULL; ++ return err; ++} ++ ++static void __exit vfio_fsl_mc_driver_exit(void) ++{ ++ fsl_mc_portal_free(vfio_mc_io); ++ fsl_mc_portal_free(vfio_atomic_mc_io); ++ fsl_mc_driver_unregister(&vfio_fsl_mc_driver); ++} ++ ++module_init(vfio_fsl_mc_driver_init); ++module_exit(vfio_fsl_mc_driver_exit); ++ ++MODULE_AUTHOR("Bharat Bhushan "); ++MODULE_LICENSE("GPL v2"); ++MODULE_DESCRIPTION("VFIO for FSL MC devices - User Level meta-driver"); +diff --git a/drivers/vfio/fsl-mc/vfio_fsl_mc_intr.c b/drivers/vfio/fsl-mc/vfio_fsl_mc_intr.c +new file mode 100644 +index 0000000..a4db758 +--- /dev/null ++++ b/drivers/vfio/fsl-mc/vfio_fsl_mc_intr.c +@@ -0,0 +1,273 @@ ++/* ++ * Freescale Management Complex (MC) device passthrough using VFIO ++ * ++ * Copyright (C) 2014 Freescale Semiconductor, Inc. ++ * Author: Bharat Bhushan ++ * ++ * This file is licensed under the terms of the GNU General Public ++ * License version 2. This program is licensed "as is" without any ++ * warranty of any kind, whether express or implied. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "../../staging/fsl-mc/include/mc.h" ++#include "../../staging/fsl-mc/include/mc-sys.h" ++#include "../../staging/fsl-mc/include/mc-private.h" ++#include ++ ++#include "vfio_fsl_mc_private.h" ++ ++static irqreturn_t vfio_fsl_mc_irq_handler(int irq_num, void *arg) ++{ ++ struct vfio_fsl_mc_irq *mc_irq = (struct vfio_fsl_mc_irq *)arg; ++ ++ eventfd_signal(mc_irq->trigger, 1); ++ return IRQ_HANDLED; ++} ++ ++int vfio_fsl_mc_configure_irq(struct vfio_fsl_mc_device *vdev, ++ int irq_index) ++{ ++ int error; ++ struct fsl_mc_device *mc_dev = vdev->mc_dev; ++ struct fsl_mc_device_irq *irq = mc_dev->irqs[irq_index]; ++ struct vfio_fsl_mc_irq *mc_irq = &vdev->mc_irqs[irq_index]; ++ struct device *dev = &mc_dev->dev; ++ ++ if (WARN_ON(!mc_irq->irq_initialized)) ++ return -EOPNOTSUPP; ++ ++ if (WARN_ON(mc_irq->irq_configured)) ++ return -EINVAL; ++ ++ mc_irq->name = kasprintf(GFP_KERNEL, "%s-%s-%d", "vfio-fsl-mc", ++ dev_name(dev), irq->irq_number); ++ ++ error = request_irq(irq->irq_number, vfio_fsl_mc_irq_handler, ++ 0, mc_irq->name, mc_irq); ++ if (error < 0) { ++ dev_err(&mc_dev->dev, ++ "IRQ registration fails with error: %d\n", error); ++ kfree(mc_irq->name); ++ return error; ++ } ++ ++ mc_irq->irq_configured = true; ++ return 0; ++} ++ ++static void vfio_fsl_mc_unconfigure_irq(struct vfio_fsl_mc_device *vdev, ++ int irq_index) ++{ ++ struct fsl_mc_device_irq *irq = vdev->mc_dev->irqs[irq_index]; ++ struct vfio_fsl_mc_irq *mc_irq = &vdev->mc_irqs[irq_index]; ++ ++ if (!vdev->mc_irqs[irq_index].irq_configured) ++ return; ++ ++ free_irq(irq->irq_number, mc_irq); ++ kfree(vdev->mc_irqs[irq_index].name); ++ vdev->mc_irqs[irq_index].irq_configured = false; ++} ++ ++static int vfio_fsl_mc_setup_irqs(struct fsl_mc_device *mc_dev) ++{ ++ int ret; ++ int irq_count = mc_dev->obj_desc.irq_count; ++ int hwirq; ++ int i; ++ ++ /* Allocate IRQs */ ++ ret = fsl_mc_allocate_irqs(mc_dev); ++ if (ret) ++ return ret; ++ ++ /* Disable IRQs */ ++ for (i = 0; i < irq_count; i++) { ++ hwirq = mc_dev->irqs[i]->irq_number; ++ disable_irq_nosync(hwirq); ++ } ++ ++ return 0; ++} ++ ++int vfio_fsl_mc_init_irqs(struct vfio_fsl_mc_device *vdev) ++{ ++ struct fsl_mc_device *mc_dev = vdev->mc_dev; ++ struct device *dev = &mc_dev->dev; ++ int irq_count = mc_dev->obj_desc.irq_count; ++ struct vfio_fsl_mc_irq *mc_irq; ++ int ret, i; ++ ++ mc_irq = kcalloc(irq_count, sizeof(*mc_irq), GFP_KERNEL); ++ if (mc_irq == NULL) ++ return -ENOMEM; ++ ++ ret = vfio_fsl_mc_setup_irqs(mc_dev); ++ if (ret) { ++ kfree(mc_irq); ++ dev_err(dev, "vfio_fsl_mc_setup_irqs Fails %d\n", ret); ++ return ret; ++ } ++ ++ for (i = 0; i < irq_count; i++) { ++ mc_irq[i].count = 1; ++ mc_irq[i].flags = VFIO_IRQ_INFO_EVENTFD | ++ VFIO_IRQ_INFO_MASKABLE; ++ mc_irq[i].irq_initialized = true; ++ } ++ ++ vdev->mc_irqs = mc_irq; ++ ++ return 0; ++} ++ ++int vfio_fsl_mc_unconfigure_irqs(struct vfio_fsl_mc_device *vdev) ++{ ++ struct fsl_mc_device *mc_dev = vdev->mc_dev; ++ int i; ++ ++ for (i = 0; i < mc_dev->obj_desc.irq_count; i++) { ++ if (!vdev->mc_irqs[i].irq_initialized) ++ continue; ++ ++ vfio_fsl_mc_unconfigure_irq(vdev, i); ++ } ++ return 0; ++} ++ ++/* Free All IRQs for the given MC object */ ++void vfio_fsl_mc_free_irqs(struct vfio_fsl_mc_device *vdev) ++{ ++ struct fsl_mc_device *mc_dev = vdev->mc_dev; ++ ++ vfio_fsl_mc_unconfigure_irqs(vdev); ++ fsl_mc_free_irqs(mc_dev); ++ ++ kfree(vdev->mc_irqs); ++} ++ ++static int vfio_fsl_mc_irq_mask(struct vfio_fsl_mc_device *vdev, ++ unsigned index, unsigned start, ++ unsigned count, uint32_t flags, void *data, ++ uint32_t mask) ++{ ++ uint8_t arr; ++ ++ if (start != 0 || count != 1) ++ return -EINVAL; ++ ++ switch (flags & VFIO_IRQ_SET_DATA_TYPE_MASK) { ++ case VFIO_IRQ_SET_DATA_BOOL: ++ arr = *(uint8_t *) data; ++ if (arr != 0x1) ++ return -EINVAL; ++ ++ case VFIO_IRQ_SET_DATA_NONE: ++ return -ENOTTY; /* To be Implemented */ ++ case VFIO_IRQ_SET_DATA_EVENTFD: ++ return -ENOTTY; /* To be Implemented */ ++ ++ default: ++ return -ENOTTY; ++ } ++ ++ return 0; ++} ++ ++static int vfio_fsl_mc_config_irq_signal(struct vfio_fsl_mc_device *vdev, ++ int irq_index, int32_t fd) ++{ ++ struct eventfd_ctx *trigger; ++ struct vfio_fsl_mc_irq *mc_irq = &vdev->mc_irqs[irq_index]; ++ int ret; ++ ++ if (vdev->mc_irqs[irq_index].trigger) { ++ eventfd_ctx_put(vdev->mc_irqs[irq_index].trigger); ++ vdev->mc_irqs[irq_index].trigger = NULL; ++ } ++ ++ if (fd < 0) ++ return 0; ++ ++ trigger = eventfd_ctx_fdget(fd); ++ if (IS_ERR(trigger)) ++ return PTR_ERR(trigger); ++ ++ /* If IRQ not configured the configure */ ++ if (!mc_irq->irq_configured) { ++ ret = vfio_fsl_mc_configure_irq(vdev, irq_index); ++ if (ret) { ++ eventfd_ctx_put(trigger); ++ return ret; ++ } ++ } ++ ++ vdev->mc_irqs[irq_index].trigger = trigger; ++ return 0; ++} ++ ++static int vfio_fsl_mc_set_irq_trigger(struct vfio_fsl_mc_device *vdev, ++ unsigned index, unsigned start, ++ unsigned count, uint32_t flags, void *data) ++{ ++ struct fsl_mc_device *mc_dev = vdev->mc_dev; ++ int32_t fd; ++ int hwirq; ++ ++ /* If count = 0 and DATA_NONE, disable interrupt */ ++ if (!count && (flags & VFIO_IRQ_SET_DATA_NONE)) { ++ hwirq = mc_dev->irqs[index]->irq_number; ++ disable_irq_nosync(hwirq); ++ return 0; ++ } ++ ++ if (flags & VFIO_IRQ_SET_DATA_BOOL) ++ fd = *(int8_t *)data; ++ else if (flags & VFIO_IRQ_SET_DATA_EVENTFD) ++ fd = *(int32_t *)data; ++ else ++ return -EINVAL; ++ ++ if (start != 0 || count != 1) ++ return -EINVAL; ++ ++ return vfio_fsl_mc_config_irq_signal(vdev, index, fd); ++} ++ ++int vfio_fsl_mc_set_irqs_ioctl(struct vfio_fsl_mc_device *vdev, ++ uint32_t flags, unsigned index, unsigned start, ++ unsigned count, void *data) ++{ ++ int ret = -ENOTTY; ++ ++ switch (flags & VFIO_IRQ_SET_ACTION_TYPE_MASK) { ++ case VFIO_IRQ_SET_ACTION_MASK: ++ /* mask all sources */ ++ ret = vfio_fsl_mc_irq_mask(vdev, index, start, ++ count, flags, data, 0); ++ break; ++ case VFIO_IRQ_SET_ACTION_UNMASK: ++ /* unmask all sources */ ++ ret = vfio_fsl_mc_irq_mask(vdev, index, start, ++ count, flags, data, ~0); ++ break; ++ case VFIO_IRQ_SET_ACTION_TRIGGER: ++ ret = vfio_fsl_mc_set_irq_trigger(vdev, index, start, ++ count, flags, data); ++ break; ++ } ++ ++ return ret; ++} +diff --git a/drivers/vfio/fsl-mc/vfio_fsl_mc_private.h b/drivers/vfio/fsl-mc/vfio_fsl_mc_private.h +new file mode 100644 +index 0000000..8980536 +--- /dev/null ++++ b/drivers/vfio/fsl-mc/vfio_fsl_mc_private.h +@@ -0,0 +1,43 @@ ++/* ++ * Freescale Management Complex VFIO private declarations ++ * ++ * Copyright (C) 2014 Freescale Semiconductor, Inc. ++ * Author: Bharat Bhushan ++ * ++ * This file is licensed under the terms of the GNU General Public ++ * License version 2. This program is licensed "as is" without any ++ * warranty of any kind, whether express or implied. ++ */ ++ ++#include "../../staging/fsl-mc/include/mc.h" ++ ++#ifndef VFIO_FSL_MC_PRIVATE_H ++#define VFIO_FSL_MC_PRIVATE_H ++ ++struct vfio_fsl_mc_irq { ++ struct eventfd_ctx *trigger; ++ u32 flags; ++ u32 count; ++ char *name; ++ bool irq_initialized; ++ bool irq_configured; ++}; ++ ++struct vfio_fsl_mc_device { ++ struct fsl_mc_device *mc_dev; ++ int refcnt; ++ struct vfio_fsl_mc_irq *mc_irqs; ++}; ++ ++int vfio_fsl_mc_init_irqs(struct vfio_fsl_mc_device *vdev); ++ ++void vfio_fsl_mc_free_irqs(struct vfio_fsl_mc_device *vdev); ++ ++int vfio_fsl_mc_configure_irq(struct vfio_fsl_mc_device *vdev, int irq_idx); ++ ++int vfio_fsl_mc_unconfigure_irqs(struct vfio_fsl_mc_device *vdev); ++ ++int vfio_fsl_mc_set_irqs_ioctl(struct vfio_fsl_mc_device *vdev, ++ uint32_t flags, unsigned index, unsigned start, ++ unsigned count, void *data); ++#endif /* VFIO_PCI_PRIVATE_H */ +diff --git a/drivers/vfio/pci/vfio_pci_intrs.c b/drivers/vfio/pci/vfio_pci_intrs.c +index 553212f..e8d695b 100644 +--- a/drivers/vfio/pci/vfio_pci_intrs.c ++++ b/drivers/vfio/pci/vfio_pci_intrs.c +@@ -560,7 +560,7 @@ static int vfio_msi_set_vector_signal(struct vfio_pci_device *vdev, + struct msi_msg msg; + + get_cached_msi_msg(irq, &msg); +- write_msi_msg(irq, &msg); ++ pci_write_msi_msg(irq, &msg); + } + + ret = request_irq(irq, vfio_msihandler, 0, +diff --git a/drivers/vfio/vfio_iommu_type1.c b/drivers/vfio/vfio_iommu_type1.c +index 4a9d666..d795e07 100644 +--- a/drivers/vfio/vfio_iommu_type1.c ++++ b/drivers/vfio/vfio_iommu_type1.c +@@ -547,6 +547,8 @@ static int vfio_dma_do_map(struct vfio_iommu *iommu, + prot |= IOMMU_WRITE; + if (map->flags & VFIO_DMA_MAP_FLAG_READ) + prot |= IOMMU_READ; ++ if (map->flags & VFIO_DMA_MAP_FLAG_MMIO) ++ prot |= IOMMU_MMIO; + + if (!prot || !size || (size | iova | vaddr) & mask) + return -EINVAL; +@@ -933,7 +935,8 @@ static long vfio_iommu_type1_ioctl(void *iommu_data, + } else if (cmd == VFIO_IOMMU_MAP_DMA) { + struct vfio_iommu_type1_dma_map map; + uint32_t mask = VFIO_DMA_MAP_FLAG_READ | +- VFIO_DMA_MAP_FLAG_WRITE; ++ VFIO_DMA_MAP_FLAG_WRITE | ++ VFIO_DMA_MAP_FLAG_MMIO; + + minsz = offsetofend(struct vfio_iommu_type1_dma_map, size); + +diff --git a/fs/Kconfig b/fs/Kconfig +index 664991a..1481093 100644 +--- a/fs/Kconfig ++++ b/fs/Kconfig +@@ -210,6 +210,7 @@ source "fs/ufs/Kconfig" + source "fs/exofs/Kconfig" + source "fs/f2fs/Kconfig" + source "fs/efivarfs/Kconfig" ++source "fs/aufs/Kconfig" + + endif # MISC_FILESYSTEMS + +diff --git a/fs/Makefile b/fs/Makefile +index da0bbb4..c8bc724 100644 +--- a/fs/Makefile ++++ b/fs/Makefile +@@ -126,3 +126,4 @@ obj-y += exofs/ # Multiple modules + obj-$(CONFIG_CEPH_FS) += ceph/ + obj-$(CONFIG_PSTORE) += pstore/ + obj-$(CONFIG_EFIVAR_FS) += efivarfs/ ++obj-$(CONFIG_AUFS_FS) += aufs/ +diff --git a/fs/aufs/Kconfig b/fs/aufs/Kconfig +new file mode 100644 +index 0000000..63560ce +--- /dev/null ++++ b/fs/aufs/Kconfig +@@ -0,0 +1,185 @@ ++config AUFS_FS ++ tristate "Aufs (Advanced multi layered unification filesystem) support" ++ help ++ Aufs is a stackable unification filesystem such as Unionfs, ++ which unifies several directories and provides a merged single ++ directory. ++ In the early days, aufs was entirely re-designed and ++ re-implemented Unionfs Version 1.x series. Introducing many ++ original ideas, approaches and improvements, it becomes totally ++ different from Unionfs while keeping the basic features. ++ ++if AUFS_FS ++choice ++ prompt "Maximum number of branches" ++ default AUFS_BRANCH_MAX_127 ++ help ++ Specifies the maximum number of branches (or member directories) ++ in a single aufs. The larger value consumes more system ++ resources and has a minor impact to performance. ++config AUFS_BRANCH_MAX_127 ++ bool "127" ++ help ++ Specifies the maximum number of branches (or member directories) ++ in a single aufs. The larger value consumes more system ++ resources and has a minor impact to performance. ++config AUFS_BRANCH_MAX_511 ++ bool "511" ++ help ++ Specifies the maximum number of branches (or member directories) ++ in a single aufs. The larger value consumes more system ++ resources and has a minor impact to performance. ++config AUFS_BRANCH_MAX_1023 ++ bool "1023" ++ help ++ Specifies the maximum number of branches (or member directories) ++ in a single aufs. The larger value consumes more system ++ resources and has a minor impact to performance. ++config AUFS_BRANCH_MAX_32767 ++ bool "32767" ++ help ++ Specifies the maximum number of branches (or member directories) ++ in a single aufs. The larger value consumes more system ++ resources and has a minor impact to performance. ++endchoice ++ ++config AUFS_SBILIST ++ bool ++ depends on AUFS_MAGIC_SYSRQ || PROC_FS ++ default y ++ help ++ Automatic configuration for internal use. ++ When aufs supports Magic SysRq or /proc, enabled automatically. ++ ++config AUFS_HNOTIFY ++ bool "Detect direct branch access (bypassing aufs)" ++ help ++ If you want to modify files on branches directly, eg. bypassing aufs, ++ and want aufs to detect the changes of them fully, then enable this ++ option and use 'udba=notify' mount option. ++ Currently there is only one available configuration, "fsnotify". ++ It will have a negative impact to the performance. ++ See detail in aufs.5. ++ ++choice ++ prompt "method" if AUFS_HNOTIFY ++ default AUFS_HFSNOTIFY ++config AUFS_HFSNOTIFY ++ bool "fsnotify" ++ select FSNOTIFY ++endchoice ++ ++config AUFS_EXPORT ++ bool "NFS-exportable aufs" ++ depends on EXPORTFS ++ help ++ If you want to export your mounted aufs via NFS, then enable this ++ option. There are several requirements for this configuration. ++ See detail in aufs.5. ++ ++config AUFS_INO_T_64 ++ bool ++ depends on AUFS_EXPORT ++ depends on 64BIT && !(ALPHA || S390) ++ default y ++ help ++ Automatic configuration for internal use. ++ /* typedef unsigned long/int __kernel_ino_t */ ++ /* alpha and s390x are int */ ++ ++config AUFS_XATTR ++ bool "support for XATTR/EA (including Security Labels)" ++ help ++ If your branch fs supports XATTR/EA and you want to make them ++ available in aufs too, then enable this opsion and specify the ++ branch attributes for EA. ++ See detail in aufs.5. ++ ++config AUFS_FHSM ++ bool "File-based Hierarchical Storage Management" ++ help ++ Hierarchical Storage Management (or HSM) is a well-known feature ++ in the storage world. Aufs provides this feature as file-based. ++ with multiple branches. ++ These multiple branches are prioritized, ie. the topmost one ++ should be the fastest drive and be used heavily. ++ ++config AUFS_RDU ++ bool "Readdir in userspace" ++ help ++ Aufs has two methods to provide a merged view for a directory, ++ by a user-space library and by kernel-space natively. The latter ++ is always enabled but sometimes large and slow. ++ If you enable this option, install the library in aufs2-util ++ package, and set some environment variables for your readdir(3), ++ then the work will be handled in user-space which generally ++ shows better performance in most cases. ++ See detail in aufs.5. ++ ++config AUFS_SHWH ++ bool "Show whiteouts" ++ help ++ If you want to make the whiteouts in aufs visible, then enable ++ this option and specify 'shwh' mount option. Although it may ++ sounds like philosophy or something, but in technically it ++ simply shows the name of whiteout with keeping its behaviour. ++ ++config AUFS_BR_RAMFS ++ bool "Ramfs (initramfs/rootfs) as an aufs branch" ++ help ++ If you want to use ramfs as an aufs branch fs, then enable this ++ option. Generally tmpfs is recommended. ++ Aufs prohibited them to be a branch fs by default, because ++ initramfs becomes unusable after switch_root or something ++ generally. If you sets initramfs as an aufs branch and boot your ++ system by switch_root, you will meet a problem easily since the ++ files in initramfs may be inaccessible. ++ Unless you are going to use ramfs as an aufs branch fs without ++ switch_root or something, leave it N. ++ ++config AUFS_BR_FUSE ++ bool "Fuse fs as an aufs branch" ++ depends on FUSE_FS ++ select AUFS_POLL ++ help ++ If you want to use fuse-based userspace filesystem as an aufs ++ branch fs, then enable this option. ++ It implements the internal poll(2) operation which is ++ implemented by fuse only (curretnly). ++ ++config AUFS_POLL ++ bool ++ help ++ Automatic configuration for internal use. ++ ++config AUFS_BR_HFSPLUS ++ bool "Hfsplus as an aufs branch" ++ depends on HFSPLUS_FS ++ default y ++ help ++ If you want to use hfsplus fs as an aufs branch fs, then enable ++ this option. This option introduces a small overhead at ++ copying-up a file on hfsplus. ++ ++config AUFS_BDEV_LOOP ++ bool ++ depends on BLK_DEV_LOOP ++ default y ++ help ++ Automatic configuration for internal use. ++ Convert =[ym] into =y. ++ ++config AUFS_DEBUG ++ bool "Debug aufs" ++ help ++ Enable this to compile aufs internal debug code. ++ It will have a negative impact to the performance. ++ ++config AUFS_MAGIC_SYSRQ ++ bool ++ depends on AUFS_DEBUG && MAGIC_SYSRQ ++ default y ++ help ++ Automatic configuration for internal use. ++ When aufs supports Magic SysRq, enabled automatically. ++endif +diff --git a/fs/aufs/Makefile b/fs/aufs/Makefile +new file mode 100644 +index 0000000..c7a501e +--- /dev/null ++++ b/fs/aufs/Makefile +@@ -0,0 +1,44 @@ ++ ++include ${src}/magic.mk ++ifeq (${CONFIG_AUFS_FS},m) ++include ${src}/conf.mk ++endif ++-include ${src}/priv_def.mk ++ ++# cf. include/linux/kernel.h ++# enable pr_debug ++ccflags-y += -DDEBUG ++# sparse requires the full pathname ++ifdef M ++ccflags-y += -include ${M}/../../include/uapi/linux/aufs_type.h ++else ++ccflags-y += -include ${srctree}/include/uapi/linux/aufs_type.h ++endif ++ ++obj-$(CONFIG_AUFS_FS) += aufs.o ++aufs-y := module.o sbinfo.o super.o branch.o xino.o sysaufs.o opts.o \ ++ wkq.o vfsub.o dcsub.o \ ++ cpup.o whout.o wbr_policy.o \ ++ dinfo.o dentry.o \ ++ dynop.o \ ++ finfo.o file.o f_op.o \ ++ dir.o vdir.o \ ++ iinfo.o inode.o i_op.o i_op_add.o i_op_del.o i_op_ren.o \ ++ mvdown.o ioctl.o ++ ++# all are boolean ++aufs-$(CONFIG_PROC_FS) += procfs.o plink.o ++aufs-$(CONFIG_SYSFS) += sysfs.o ++aufs-$(CONFIG_DEBUG_FS) += dbgaufs.o ++aufs-$(CONFIG_AUFS_BDEV_LOOP) += loop.o ++aufs-$(CONFIG_AUFS_HNOTIFY) += hnotify.o ++aufs-$(CONFIG_AUFS_HFSNOTIFY) += hfsnotify.o ++aufs-$(CONFIG_AUFS_EXPORT) += export.o ++aufs-$(CONFIG_AUFS_XATTR) += xattr.o ++aufs-$(CONFIG_FS_POSIX_ACL) += posix_acl.o ++aufs-$(CONFIG_AUFS_FHSM) += fhsm.o ++aufs-$(CONFIG_AUFS_POLL) += poll.o ++aufs-$(CONFIG_AUFS_RDU) += rdu.o ++aufs-$(CONFIG_AUFS_BR_HFSPLUS) += hfsplus.o ++aufs-$(CONFIG_AUFS_DEBUG) += debug.o ++aufs-$(CONFIG_AUFS_MAGIC_SYSRQ) += sysrq.o +diff --git a/fs/aufs/aufs.h b/fs/aufs/aufs.h +new file mode 100644 +index 0000000..e48d268 +--- /dev/null ++++ b/fs/aufs/aufs.h +@@ -0,0 +1,59 @@ ++/* ++ * Copyright (C) 2005-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * all header files ++ */ ++ ++#ifndef __AUFS_H__ ++#define __AUFS_H__ ++ ++#ifdef __KERNEL__ ++ ++#define AuStub(type, name, body, ...) \ ++ static inline type name(__VA_ARGS__) { body; } ++ ++#define AuStubVoid(name, ...) \ ++ AuStub(void, name, , __VA_ARGS__) ++#define AuStubInt0(name, ...) \ ++ AuStub(int, name, return 0, __VA_ARGS__) ++ ++#include "debug.h" ++ ++#include "branch.h" ++#include "cpup.h" ++#include "dcsub.h" ++#include "dbgaufs.h" ++#include "dentry.h" ++#include "dir.h" ++#include "dynop.h" ++#include "file.h" ++#include "fstype.h" ++#include "inode.h" ++#include "loop.h" ++#include "module.h" ++#include "opts.h" ++#include "rwsem.h" ++#include "spl.h" ++#include "super.h" ++#include "sysaufs.h" ++#include "vfsub.h" ++#include "whout.h" ++#include "wkq.h" ++ ++#endif /* __KERNEL__ */ ++#endif /* __AUFS_H__ */ +diff --git a/fs/aufs/branch.c b/fs/aufs/branch.c +new file mode 100644 +index 0000000..17210b2 +--- /dev/null ++++ b/fs/aufs/branch.c +@@ -0,0 +1,1402 @@ ++/* ++ * Copyright (C) 2005-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * branch management ++ */ ++ ++#include ++#include ++#include "aufs.h" ++ ++/* ++ * free a single branch ++ */ ++static void au_br_do_free(struct au_branch *br) ++{ ++ int i; ++ struct au_wbr *wbr; ++ struct au_dykey **key; ++ ++ au_hnotify_fin_br(br); ++ ++ if (br->br_xino.xi_file) ++ fput(br->br_xino.xi_file); ++ mutex_destroy(&br->br_xino.xi_nondir_mtx); ++ ++ AuDebugOn(atomic_read(&br->br_count)); ++ ++ wbr = br->br_wbr; ++ if (wbr) { ++ for (i = 0; i < AuBrWh_Last; i++) ++ dput(wbr->wbr_wh[i]); ++ AuDebugOn(atomic_read(&wbr->wbr_wh_running)); ++ AuRwDestroy(&wbr->wbr_wh_rwsem); ++ } ++ ++ if (br->br_fhsm) { ++ au_br_fhsm_fin(br->br_fhsm); ++ kfree(br->br_fhsm); ++ } ++ ++ key = br->br_dykey; ++ for (i = 0; i < AuBrDynOp; i++, key++) ++ if (*key) ++ au_dy_put(*key); ++ else ++ break; ++ ++ /* recursive lock, s_umount of branch's */ ++ lockdep_off(); ++ path_put(&br->br_path); ++ lockdep_on(); ++ kfree(wbr); ++ kfree(br); ++} ++ ++/* ++ * frees all branches ++ */ ++void au_br_free(struct au_sbinfo *sbinfo) ++{ ++ aufs_bindex_t bmax; ++ struct au_branch **br; ++ ++ AuRwMustWriteLock(&sbinfo->si_rwsem); ++ ++ bmax = sbinfo->si_bend + 1; ++ br = sbinfo->si_branch; ++ while (bmax--) ++ au_br_do_free(*br++); ++} ++ ++/* ++ * find the index of a branch which is specified by @br_id. ++ */ ++int au_br_index(struct super_block *sb, aufs_bindex_t br_id) ++{ ++ aufs_bindex_t bindex, bend; ++ ++ bend = au_sbend(sb); ++ for (bindex = 0; bindex <= bend; bindex++) ++ if (au_sbr_id(sb, bindex) == br_id) ++ return bindex; ++ return -1; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* ++ * add a branch ++ */ ++ ++static int test_overlap(struct super_block *sb, struct dentry *h_adding, ++ struct dentry *h_root) ++{ ++ if (unlikely(h_adding == h_root ++ || au_test_loopback_overlap(sb, h_adding))) ++ return 1; ++ if (h_adding->d_sb != h_root->d_sb) ++ return 0; ++ return au_test_subdir(h_adding, h_root) ++ || au_test_subdir(h_root, h_adding); ++} ++ ++/* ++ * returns a newly allocated branch. @new_nbranch is a number of branches ++ * after adding a branch. ++ */ ++static struct au_branch *au_br_alloc(struct super_block *sb, int new_nbranch, ++ int perm) ++{ ++ struct au_branch *add_branch; ++ struct dentry *root; ++ int err; ++ ++ err = -ENOMEM; ++ root = sb->s_root; ++ add_branch = kzalloc(sizeof(*add_branch), GFP_NOFS); ++ if (unlikely(!add_branch)) ++ goto out; ++ ++ err = au_hnotify_init_br(add_branch, perm); ++ if (unlikely(err)) ++ goto out_br; ++ ++ if (au_br_writable(perm)) { ++ /* may be freed separately at changing the branch permission */ ++ add_branch->br_wbr = kzalloc(sizeof(*add_branch->br_wbr), ++ GFP_NOFS); ++ if (unlikely(!add_branch->br_wbr)) ++ goto out_hnotify; ++ } ++ ++ if (au_br_fhsm(perm)) { ++ err = au_fhsm_br_alloc(add_branch); ++ if (unlikely(err)) ++ goto out_wbr; ++ } ++ ++ err = au_sbr_realloc(au_sbi(sb), new_nbranch); ++ if (!err) ++ err = au_di_realloc(au_di(root), new_nbranch); ++ if (!err) ++ err = au_ii_realloc(au_ii(root->d_inode), new_nbranch); ++ if (!err) ++ return add_branch; /* success */ ++ ++out_wbr: ++ kfree(add_branch->br_wbr); ++out_hnotify: ++ au_hnotify_fin_br(add_branch); ++out_br: ++ kfree(add_branch); ++out: ++ return ERR_PTR(err); ++} ++ ++/* ++ * test if the branch permission is legal or not. ++ */ ++static int test_br(struct inode *inode, int brperm, char *path) ++{ ++ int err; ++ ++ err = (au_br_writable(brperm) && IS_RDONLY(inode)); ++ if (!err) ++ goto out; ++ ++ err = -EINVAL; ++ pr_err("write permission for readonly mount or inode, %s\n", path); ++ ++out: ++ return err; ++} ++ ++/* ++ * returns: ++ * 0: success, the caller will add it ++ * plus: success, it is already unified, the caller should ignore it ++ * minus: error ++ */ ++static int test_add(struct super_block *sb, struct au_opt_add *add, int remount) ++{ ++ int err; ++ aufs_bindex_t bend, bindex; ++ struct dentry *root; ++ struct inode *inode, *h_inode; ++ ++ root = sb->s_root; ++ bend = au_sbend(sb); ++ if (unlikely(bend >= 0 ++ && au_find_dbindex(root, add->path.dentry) >= 0)) { ++ err = 1; ++ if (!remount) { ++ err = -EINVAL; ++ pr_err("%s duplicated\n", add->pathname); ++ } ++ goto out; ++ } ++ ++ err = -ENOSPC; /* -E2BIG; */ ++ if (unlikely(AUFS_BRANCH_MAX <= add->bindex ++ || AUFS_BRANCH_MAX - 1 <= bend)) { ++ pr_err("number of branches exceeded %s\n", add->pathname); ++ goto out; ++ } ++ ++ err = -EDOM; ++ if (unlikely(add->bindex < 0 || bend + 1 < add->bindex)) { ++ pr_err("bad index %d\n", add->bindex); ++ goto out; ++ } ++ ++ inode = add->path.dentry->d_inode; ++ err = -ENOENT; ++ if (unlikely(!inode->i_nlink)) { ++ pr_err("no existence %s\n", add->pathname); ++ goto out; ++ } ++ ++ err = -EINVAL; ++ if (unlikely(inode->i_sb == sb)) { ++ pr_err("%s must be outside\n", add->pathname); ++ goto out; ++ } ++ ++ if (unlikely(au_test_fs_unsuppoted(inode->i_sb))) { ++ pr_err("unsupported filesystem, %s (%s)\n", ++ add->pathname, au_sbtype(inode->i_sb)); ++ goto out; ++ } ++ ++ if (unlikely(inode->i_sb->s_stack_depth)) { ++ pr_err("already stacked, %s (%s)\n", ++ add->pathname, au_sbtype(inode->i_sb)); ++ goto out; ++ } ++ ++ err = test_br(add->path.dentry->d_inode, add->perm, add->pathname); ++ if (unlikely(err)) ++ goto out; ++ ++ if (bend < 0) ++ return 0; /* success */ ++ ++ err = -EINVAL; ++ for (bindex = 0; bindex <= bend; bindex++) ++ if (unlikely(test_overlap(sb, add->path.dentry, ++ au_h_dptr(root, bindex)))) { ++ pr_err("%s is overlapped\n", add->pathname); ++ goto out; ++ } ++ ++ err = 0; ++ if (au_opt_test(au_mntflags(sb), WARN_PERM)) { ++ h_inode = au_h_dptr(root, 0)->d_inode; ++ if ((h_inode->i_mode & S_IALLUGO) != (inode->i_mode & S_IALLUGO) ++ || !uid_eq(h_inode->i_uid, inode->i_uid) ++ || !gid_eq(h_inode->i_gid, inode->i_gid)) ++ pr_warn("uid/gid/perm %s %u/%u/0%o, %u/%u/0%o\n", ++ add->pathname, ++ i_uid_read(inode), i_gid_read(inode), ++ (inode->i_mode & S_IALLUGO), ++ i_uid_read(h_inode), i_gid_read(h_inode), ++ (h_inode->i_mode & S_IALLUGO)); ++ } ++ ++out: ++ return err; ++} ++ ++/* ++ * initialize or clean the whiteouts for an adding branch ++ */ ++static int au_br_init_wh(struct super_block *sb, struct au_branch *br, ++ int new_perm) ++{ ++ int err, old_perm; ++ aufs_bindex_t bindex; ++ struct mutex *h_mtx; ++ struct au_wbr *wbr; ++ struct au_hinode *hdir; ++ ++ err = vfsub_mnt_want_write(au_br_mnt(br)); ++ if (unlikely(err)) ++ goto out; ++ ++ wbr = br->br_wbr; ++ old_perm = br->br_perm; ++ br->br_perm = new_perm; ++ hdir = NULL; ++ h_mtx = NULL; ++ bindex = au_br_index(sb, br->br_id); ++ if (0 <= bindex) { ++ hdir = au_hi(sb->s_root->d_inode, bindex); ++ au_hn_imtx_lock_nested(hdir, AuLsc_I_PARENT); ++ } else { ++ h_mtx = &au_br_dentry(br)->d_inode->i_mutex; ++ mutex_lock_nested(h_mtx, AuLsc_I_PARENT); ++ } ++ if (!wbr) ++ err = au_wh_init(br, sb); ++ else { ++ wbr_wh_write_lock(wbr); ++ err = au_wh_init(br, sb); ++ wbr_wh_write_unlock(wbr); ++ } ++ if (hdir) ++ au_hn_imtx_unlock(hdir); ++ else ++ mutex_unlock(h_mtx); ++ vfsub_mnt_drop_write(au_br_mnt(br)); ++ br->br_perm = old_perm; ++ ++ if (!err && wbr && !au_br_writable(new_perm)) { ++ kfree(wbr); ++ br->br_wbr = NULL; ++ } ++ ++out: ++ return err; ++} ++ ++static int au_wbr_init(struct au_branch *br, struct super_block *sb, ++ int perm) ++{ ++ int err; ++ struct kstatfs kst; ++ struct au_wbr *wbr; ++ ++ wbr = br->br_wbr; ++ au_rw_init(&wbr->wbr_wh_rwsem); ++ atomic_set(&wbr->wbr_wh_running, 0); ++ ++ /* ++ * a limit for rmdir/rename a dir ++ * cf. AUFS_MAX_NAMELEN in include/uapi/linux/aufs_type.h ++ */ ++ err = vfs_statfs(&br->br_path, &kst); ++ if (unlikely(err)) ++ goto out; ++ err = -EINVAL; ++ if (kst.f_namelen >= NAME_MAX) ++ err = au_br_init_wh(sb, br, perm); ++ else ++ pr_err("%pd(%s), unsupported namelen %ld\n", ++ au_br_dentry(br), ++ au_sbtype(au_br_dentry(br)->d_sb), kst.f_namelen); ++ ++out: ++ return err; ++} ++ ++/* initialize a new branch */ ++static int au_br_init(struct au_branch *br, struct super_block *sb, ++ struct au_opt_add *add) ++{ ++ int err; ++ ++ err = 0; ++ mutex_init(&br->br_xino.xi_nondir_mtx); ++ br->br_perm = add->perm; ++ br->br_path = add->path; /* set first, path_get() later */ ++ spin_lock_init(&br->br_dykey_lock); ++ atomic_set(&br->br_count, 0); ++ atomic_set(&br->br_xino_running, 0); ++ br->br_id = au_new_br_id(sb); ++ AuDebugOn(br->br_id < 0); ++ ++ if (au_br_writable(add->perm)) { ++ err = au_wbr_init(br, sb, add->perm); ++ if (unlikely(err)) ++ goto out_err; ++ } ++ ++ if (au_opt_test(au_mntflags(sb), XINO)) { ++ err = au_xino_br(sb, br, add->path.dentry->d_inode->i_ino, ++ au_sbr(sb, 0)->br_xino.xi_file, /*do_test*/1); ++ if (unlikely(err)) { ++ AuDebugOn(br->br_xino.xi_file); ++ goto out_err; ++ } ++ } ++ ++ sysaufs_br_init(br); ++ path_get(&br->br_path); ++ goto out; /* success */ ++ ++out_err: ++ memset(&br->br_path, 0, sizeof(br->br_path)); ++out: ++ return err; ++} ++ ++static void au_br_do_add_brp(struct au_sbinfo *sbinfo, aufs_bindex_t bindex, ++ struct au_branch *br, aufs_bindex_t bend, ++ aufs_bindex_t amount) ++{ ++ struct au_branch **brp; ++ ++ AuRwMustWriteLock(&sbinfo->si_rwsem); ++ ++ brp = sbinfo->si_branch + bindex; ++ memmove(brp + 1, brp, sizeof(*brp) * amount); ++ *brp = br; ++ sbinfo->si_bend++; ++ if (unlikely(bend < 0)) ++ sbinfo->si_bend = 0; ++} ++ ++static void au_br_do_add_hdp(struct au_dinfo *dinfo, aufs_bindex_t bindex, ++ aufs_bindex_t bend, aufs_bindex_t amount) ++{ ++ struct au_hdentry *hdp; ++ ++ AuRwMustWriteLock(&dinfo->di_rwsem); ++ ++ hdp = dinfo->di_hdentry + bindex; ++ memmove(hdp + 1, hdp, sizeof(*hdp) * amount); ++ au_h_dentry_init(hdp); ++ dinfo->di_bend++; ++ if (unlikely(bend < 0)) ++ dinfo->di_bstart = 0; ++} ++ ++static void au_br_do_add_hip(struct au_iinfo *iinfo, aufs_bindex_t bindex, ++ aufs_bindex_t bend, aufs_bindex_t amount) ++{ ++ struct au_hinode *hip; ++ ++ AuRwMustWriteLock(&iinfo->ii_rwsem); ++ ++ hip = iinfo->ii_hinode + bindex; ++ memmove(hip + 1, hip, sizeof(*hip) * amount); ++ hip->hi_inode = NULL; ++ au_hn_init(hip); ++ iinfo->ii_bend++; ++ if (unlikely(bend < 0)) ++ iinfo->ii_bstart = 0; ++} ++ ++static void au_br_do_add(struct super_block *sb, struct au_branch *br, ++ aufs_bindex_t bindex) ++{ ++ struct dentry *root, *h_dentry; ++ struct inode *root_inode; ++ aufs_bindex_t bend, amount; ++ ++ root = sb->s_root; ++ root_inode = root->d_inode; ++ bend = au_sbend(sb); ++ amount = bend + 1 - bindex; ++ h_dentry = au_br_dentry(br); ++ au_sbilist_lock(); ++ au_br_do_add_brp(au_sbi(sb), bindex, br, bend, amount); ++ au_br_do_add_hdp(au_di(root), bindex, bend, amount); ++ au_br_do_add_hip(au_ii(root_inode), bindex, bend, amount); ++ au_set_h_dptr(root, bindex, dget(h_dentry)); ++ au_set_h_iptr(root_inode, bindex, au_igrab(h_dentry->d_inode), ++ /*flags*/0); ++ au_sbilist_unlock(); ++} ++ ++int au_br_add(struct super_block *sb, struct au_opt_add *add, int remount) ++{ ++ int err; ++ aufs_bindex_t bend, add_bindex; ++ struct dentry *root, *h_dentry; ++ struct inode *root_inode; ++ struct au_branch *add_branch; ++ ++ root = sb->s_root; ++ root_inode = root->d_inode; ++ IMustLock(root_inode); ++ err = test_add(sb, add, remount); ++ if (unlikely(err < 0)) ++ goto out; ++ if (err) { ++ err = 0; ++ goto out; /* success */ ++ } ++ ++ bend = au_sbend(sb); ++ add_branch = au_br_alloc(sb, bend + 2, add->perm); ++ err = PTR_ERR(add_branch); ++ if (IS_ERR(add_branch)) ++ goto out; ++ ++ err = au_br_init(add_branch, sb, add); ++ if (unlikely(err)) { ++ au_br_do_free(add_branch); ++ goto out; ++ } ++ ++ add_bindex = add->bindex; ++ if (!remount) ++ au_br_do_add(sb, add_branch, add_bindex); ++ else { ++ sysaufs_brs_del(sb, add_bindex); ++ au_br_do_add(sb, add_branch, add_bindex); ++ sysaufs_brs_add(sb, add_bindex); ++ } ++ ++ h_dentry = add->path.dentry; ++ if (!add_bindex) { ++ au_cpup_attr_all(root_inode, /*force*/1); ++ sb->s_maxbytes = h_dentry->d_sb->s_maxbytes; ++ } else ++ au_add_nlink(root_inode, h_dentry->d_inode); ++ ++ /* ++ * this test/set prevents aufs from handling unnecesary notify events ++ * of xino files, in case of re-adding a writable branch which was ++ * once detached from aufs. ++ */ ++ if (au_xino_brid(sb) < 0 ++ && au_br_writable(add_branch->br_perm) ++ && !au_test_fs_bad_xino(h_dentry->d_sb) ++ && add_branch->br_xino.xi_file ++ && add_branch->br_xino.xi_file->f_dentry->d_parent == h_dentry) ++ au_xino_brid_set(sb, add_branch->br_id); ++ ++out: ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++static unsigned long long au_farray_cb(void *a, ++ unsigned long long max __maybe_unused, ++ void *arg) ++{ ++ unsigned long long n; ++ struct file **p, *f; ++ struct au_sphlhead *files; ++ struct au_finfo *finfo; ++ struct super_block *sb = arg; ++ ++ n = 0; ++ p = a; ++ files = &au_sbi(sb)->si_files; ++ spin_lock(&files->spin); ++ hlist_for_each_entry(finfo, &files->head, fi_hlist) { ++ f = finfo->fi_file; ++ if (file_count(f) ++ && !special_file(file_inode(f)->i_mode)) { ++ get_file(f); ++ *p++ = f; ++ n++; ++ AuDebugOn(n > max); ++ } ++ } ++ spin_unlock(&files->spin); ++ ++ return n; ++} ++ ++static struct file **au_farray_alloc(struct super_block *sb, ++ unsigned long long *max) ++{ ++ *max = atomic_long_read(&au_sbi(sb)->si_nfiles); ++ return au_array_alloc(max, au_farray_cb, sb); ++} ++ ++static void au_farray_free(struct file **a, unsigned long long max) ++{ ++ unsigned long long ull; ++ ++ for (ull = 0; ull < max; ull++) ++ if (a[ull]) ++ fput(a[ull]); ++ kvfree(a); ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* ++ * delete a branch ++ */ ++ ++/* to show the line number, do not make it inlined function */ ++#define AuVerbose(do_info, fmt, ...) do { \ ++ if (do_info) \ ++ pr_info(fmt, ##__VA_ARGS__); \ ++} while (0) ++ ++static int au_test_ibusy(struct inode *inode, aufs_bindex_t bstart, ++ aufs_bindex_t bend) ++{ ++ return (inode && !S_ISDIR(inode->i_mode)) || bstart == bend; ++} ++ ++static int au_test_dbusy(struct dentry *dentry, aufs_bindex_t bstart, ++ aufs_bindex_t bend) ++{ ++ return au_test_ibusy(dentry->d_inode, bstart, bend); ++} ++ ++/* ++ * test if the branch is deletable or not. ++ */ ++static int test_dentry_busy(struct dentry *root, aufs_bindex_t bindex, ++ unsigned int sigen, const unsigned int verbose) ++{ ++ int err, i, j, ndentry; ++ aufs_bindex_t bstart, bend; ++ struct au_dcsub_pages dpages; ++ struct au_dpage *dpage; ++ struct dentry *d; ++ ++ err = au_dpages_init(&dpages, GFP_NOFS); ++ if (unlikely(err)) ++ goto out; ++ err = au_dcsub_pages(&dpages, root, NULL, NULL); ++ if (unlikely(err)) ++ goto out_dpages; ++ ++ for (i = 0; !err && i < dpages.ndpage; i++) { ++ dpage = dpages.dpages + i; ++ ndentry = dpage->ndentry; ++ for (j = 0; !err && j < ndentry; j++) { ++ d = dpage->dentries[j]; ++ AuDebugOn(au_dcount(d) <= 0); ++ if (!au_digen_test(d, sigen)) { ++ di_read_lock_child(d, AuLock_IR); ++ if (unlikely(au_dbrange_test(d))) { ++ di_read_unlock(d, AuLock_IR); ++ continue; ++ } ++ } else { ++ di_write_lock_child(d); ++ if (unlikely(au_dbrange_test(d))) { ++ di_write_unlock(d); ++ continue; ++ } ++ err = au_reval_dpath(d, sigen); ++ if (!err) ++ di_downgrade_lock(d, AuLock_IR); ++ else { ++ di_write_unlock(d); ++ break; ++ } ++ } ++ ++ /* AuDbgDentry(d); */ ++ bstart = au_dbstart(d); ++ bend = au_dbend(d); ++ if (bstart <= bindex ++ && bindex <= bend ++ && au_h_dptr(d, bindex) ++ && au_test_dbusy(d, bstart, bend)) { ++ err = -EBUSY; ++ AuVerbose(verbose, "busy %pd\n", d); ++ AuDbgDentry(d); ++ } ++ di_read_unlock(d, AuLock_IR); ++ } ++ } ++ ++out_dpages: ++ au_dpages_free(&dpages); ++out: ++ return err; ++} ++ ++static int test_inode_busy(struct super_block *sb, aufs_bindex_t bindex, ++ unsigned int sigen, const unsigned int verbose) ++{ ++ int err; ++ unsigned long long max, ull; ++ struct inode *i, **array; ++ aufs_bindex_t bstart, bend; ++ ++ array = au_iarray_alloc(sb, &max); ++ err = PTR_ERR(array); ++ if (IS_ERR(array)) ++ goto out; ++ ++ err = 0; ++ AuDbg("b%d\n", bindex); ++ for (ull = 0; !err && ull < max; ull++) { ++ i = array[ull]; ++ if (unlikely(!i)) ++ break; ++ if (i->i_ino == AUFS_ROOT_INO) ++ continue; ++ ++ /* AuDbgInode(i); */ ++ if (au_iigen(i, NULL) == sigen) ++ ii_read_lock_child(i); ++ else { ++ ii_write_lock_child(i); ++ err = au_refresh_hinode_self(i); ++ au_iigen_dec(i); ++ if (!err) ++ ii_downgrade_lock(i); ++ else { ++ ii_write_unlock(i); ++ break; ++ } ++ } ++ ++ bstart = au_ibstart(i); ++ bend = au_ibend(i); ++ if (bstart <= bindex ++ && bindex <= bend ++ && au_h_iptr(i, bindex) ++ && au_test_ibusy(i, bstart, bend)) { ++ err = -EBUSY; ++ AuVerbose(verbose, "busy i%lu\n", i->i_ino); ++ AuDbgInode(i); ++ } ++ ii_read_unlock(i); ++ } ++ au_iarray_free(array, max); ++ ++out: ++ return err; ++} ++ ++static int test_children_busy(struct dentry *root, aufs_bindex_t bindex, ++ const unsigned int verbose) ++{ ++ int err; ++ unsigned int sigen; ++ ++ sigen = au_sigen(root->d_sb); ++ DiMustNoWaiters(root); ++ IiMustNoWaiters(root->d_inode); ++ di_write_unlock(root); ++ err = test_dentry_busy(root, bindex, sigen, verbose); ++ if (!err) ++ err = test_inode_busy(root->d_sb, bindex, sigen, verbose); ++ di_write_lock_child(root); /* aufs_write_lock() calls ..._child() */ ++ ++ return err; ++} ++ ++static int test_dir_busy(struct file *file, aufs_bindex_t br_id, ++ struct file **to_free, int *idx) ++{ ++ int err; ++ unsigned char matched, root; ++ aufs_bindex_t bindex, bend; ++ struct au_fidir *fidir; ++ struct au_hfile *hfile; ++ ++ err = 0; ++ root = IS_ROOT(file->f_dentry); ++ if (root) { ++ get_file(file); ++ to_free[*idx] = file; ++ (*idx)++; ++ goto out; ++ } ++ ++ matched = 0; ++ fidir = au_fi(file)->fi_hdir; ++ AuDebugOn(!fidir); ++ bend = au_fbend_dir(file); ++ for (bindex = au_fbstart(file); bindex <= bend; bindex++) { ++ hfile = fidir->fd_hfile + bindex; ++ if (!hfile->hf_file) ++ continue; ++ ++ if (hfile->hf_br->br_id == br_id) { ++ matched = 1; ++ break; ++ } ++ } ++ if (matched) ++ err = -EBUSY; ++ ++out: ++ return err; ++} ++ ++static int test_file_busy(struct super_block *sb, aufs_bindex_t br_id, ++ struct file **to_free, int opened) ++{ ++ int err, idx; ++ unsigned long long ull, max; ++ aufs_bindex_t bstart; ++ struct file *file, **array; ++ struct dentry *root; ++ struct au_hfile *hfile; ++ ++ array = au_farray_alloc(sb, &max); ++ err = PTR_ERR(array); ++ if (IS_ERR(array)) ++ goto out; ++ ++ err = 0; ++ idx = 0; ++ root = sb->s_root; ++ di_write_unlock(root); ++ for (ull = 0; ull < max; ull++) { ++ file = array[ull]; ++ if (unlikely(!file)) ++ break; ++ ++ /* AuDbg("%pD\n", file); */ ++ fi_read_lock(file); ++ bstart = au_fbstart(file); ++ if (!d_is_dir(file->f_path.dentry)) { ++ hfile = &au_fi(file)->fi_htop; ++ if (hfile->hf_br->br_id == br_id) ++ err = -EBUSY; ++ } else ++ err = test_dir_busy(file, br_id, to_free, &idx); ++ fi_read_unlock(file); ++ if (unlikely(err)) ++ break; ++ } ++ di_write_lock_child(root); ++ au_farray_free(array, max); ++ AuDebugOn(idx > opened); ++ ++out: ++ return err; ++} ++ ++static void br_del_file(struct file **to_free, unsigned long long opened, ++ aufs_bindex_t br_id) ++{ ++ unsigned long long ull; ++ aufs_bindex_t bindex, bstart, bend, bfound; ++ struct file *file; ++ struct au_fidir *fidir; ++ struct au_hfile *hfile; ++ ++ for (ull = 0; ull < opened; ull++) { ++ file = to_free[ull]; ++ if (unlikely(!file)) ++ break; ++ ++ /* AuDbg("%pD\n", file); */ ++ AuDebugOn(!d_is_dir(file->f_path.dentry)); ++ bfound = -1; ++ fidir = au_fi(file)->fi_hdir; ++ AuDebugOn(!fidir); ++ fi_write_lock(file); ++ bstart = au_fbstart(file); ++ bend = au_fbend_dir(file); ++ for (bindex = bstart; bindex <= bend; bindex++) { ++ hfile = fidir->fd_hfile + bindex; ++ if (!hfile->hf_file) ++ continue; ++ ++ if (hfile->hf_br->br_id == br_id) { ++ bfound = bindex; ++ break; ++ } ++ } ++ AuDebugOn(bfound < 0); ++ au_set_h_fptr(file, bfound, NULL); ++ if (bfound == bstart) { ++ for (bstart++; bstart <= bend; bstart++) ++ if (au_hf_dir(file, bstart)) { ++ au_set_fbstart(file, bstart); ++ break; ++ } ++ } ++ fi_write_unlock(file); ++ } ++} ++ ++static void au_br_do_del_brp(struct au_sbinfo *sbinfo, ++ const aufs_bindex_t bindex, ++ const aufs_bindex_t bend) ++{ ++ struct au_branch **brp, **p; ++ ++ AuRwMustWriteLock(&sbinfo->si_rwsem); ++ ++ brp = sbinfo->si_branch + bindex; ++ if (bindex < bend) ++ memmove(brp, brp + 1, sizeof(*brp) * (bend - bindex)); ++ sbinfo->si_branch[0 + bend] = NULL; ++ sbinfo->si_bend--; ++ ++ p = krealloc(sbinfo->si_branch, sizeof(*p) * bend, AuGFP_SBILIST); ++ if (p) ++ sbinfo->si_branch = p; ++ /* harmless error */ ++} ++ ++static void au_br_do_del_hdp(struct au_dinfo *dinfo, const aufs_bindex_t bindex, ++ const aufs_bindex_t bend) ++{ ++ struct au_hdentry *hdp, *p; ++ ++ AuRwMustWriteLock(&dinfo->di_rwsem); ++ ++ hdp = dinfo->di_hdentry; ++ if (bindex < bend) ++ memmove(hdp + bindex, hdp + bindex + 1, ++ sizeof(*hdp) * (bend - bindex)); ++ hdp[0 + bend].hd_dentry = NULL; ++ dinfo->di_bend--; ++ ++ p = krealloc(hdp, sizeof(*p) * bend, AuGFP_SBILIST); ++ if (p) ++ dinfo->di_hdentry = p; ++ /* harmless error */ ++} ++ ++static void au_br_do_del_hip(struct au_iinfo *iinfo, const aufs_bindex_t bindex, ++ const aufs_bindex_t bend) ++{ ++ struct au_hinode *hip, *p; ++ ++ AuRwMustWriteLock(&iinfo->ii_rwsem); ++ ++ hip = iinfo->ii_hinode + bindex; ++ if (bindex < bend) ++ memmove(hip, hip + 1, sizeof(*hip) * (bend - bindex)); ++ iinfo->ii_hinode[0 + bend].hi_inode = NULL; ++ au_hn_init(iinfo->ii_hinode + bend); ++ iinfo->ii_bend--; ++ ++ p = krealloc(iinfo->ii_hinode, sizeof(*p) * bend, AuGFP_SBILIST); ++ if (p) ++ iinfo->ii_hinode = p; ++ /* harmless error */ ++} ++ ++static void au_br_do_del(struct super_block *sb, aufs_bindex_t bindex, ++ struct au_branch *br) ++{ ++ aufs_bindex_t bend; ++ struct au_sbinfo *sbinfo; ++ struct dentry *root, *h_root; ++ struct inode *inode, *h_inode; ++ struct au_hinode *hinode; ++ ++ SiMustWriteLock(sb); ++ ++ root = sb->s_root; ++ inode = root->d_inode; ++ sbinfo = au_sbi(sb); ++ bend = sbinfo->si_bend; ++ ++ h_root = au_h_dptr(root, bindex); ++ hinode = au_hi(inode, bindex); ++ h_inode = au_igrab(hinode->hi_inode); ++ au_hiput(hinode); ++ ++ au_sbilist_lock(); ++ au_br_do_del_brp(sbinfo, bindex, bend); ++ au_br_do_del_hdp(au_di(root), bindex, bend); ++ au_br_do_del_hip(au_ii(inode), bindex, bend); ++ au_sbilist_unlock(); ++ ++ dput(h_root); ++ iput(h_inode); ++ au_br_do_free(br); ++} ++ ++static unsigned long long empty_cb(void *array, unsigned long long max, ++ void *arg) ++{ ++ return max; ++} ++ ++int au_br_del(struct super_block *sb, struct au_opt_del *del, int remount) ++{ ++ int err, rerr, i; ++ unsigned long long opened; ++ unsigned int mnt_flags; ++ aufs_bindex_t bindex, bend, br_id; ++ unsigned char do_wh, verbose; ++ struct au_branch *br; ++ struct au_wbr *wbr; ++ struct dentry *root; ++ struct file **to_free; ++ ++ err = 0; ++ opened = 0; ++ to_free = NULL; ++ root = sb->s_root; ++ bindex = au_find_dbindex(root, del->h_path.dentry); ++ if (bindex < 0) { ++ if (remount) ++ goto out; /* success */ ++ err = -ENOENT; ++ pr_err("%s no such branch\n", del->pathname); ++ goto out; ++ } ++ AuDbg("bindex b%d\n", bindex); ++ ++ err = -EBUSY; ++ mnt_flags = au_mntflags(sb); ++ verbose = !!au_opt_test(mnt_flags, VERBOSE); ++ bend = au_sbend(sb); ++ if (unlikely(!bend)) { ++ AuVerbose(verbose, "no more branches left\n"); ++ goto out; ++ } ++ br = au_sbr(sb, bindex); ++ AuDebugOn(!path_equal(&br->br_path, &del->h_path)); ++ ++ br_id = br->br_id; ++ opened = atomic_read(&br->br_count); ++ if (unlikely(opened)) { ++ to_free = au_array_alloc(&opened, empty_cb, NULL); ++ err = PTR_ERR(to_free); ++ if (IS_ERR(to_free)) ++ goto out; ++ ++ err = test_file_busy(sb, br_id, to_free, opened); ++ if (unlikely(err)) { ++ AuVerbose(verbose, "%llu file(s) opened\n", opened); ++ goto out; ++ } ++ } ++ ++ wbr = br->br_wbr; ++ do_wh = wbr && (wbr->wbr_whbase || wbr->wbr_plink || wbr->wbr_orph); ++ if (do_wh) { ++ /* instead of WbrWhMustWriteLock(wbr) */ ++ SiMustWriteLock(sb); ++ for (i = 0; i < AuBrWh_Last; i++) { ++ dput(wbr->wbr_wh[i]); ++ wbr->wbr_wh[i] = NULL; ++ } ++ } ++ ++ err = test_children_busy(root, bindex, verbose); ++ if (unlikely(err)) { ++ if (do_wh) ++ goto out_wh; ++ goto out; ++ } ++ ++ err = 0; ++ if (to_free) { ++ /* ++ * now we confirmed the branch is deletable. ++ * let's free the remaining opened dirs on the branch. ++ */ ++ di_write_unlock(root); ++ br_del_file(to_free, opened, br_id); ++ di_write_lock_child(root); ++ } ++ ++ if (!remount) ++ au_br_do_del(sb, bindex, br); ++ else { ++ sysaufs_brs_del(sb, bindex); ++ au_br_do_del(sb, bindex, br); ++ sysaufs_brs_add(sb, bindex); ++ } ++ ++ if (!bindex) { ++ au_cpup_attr_all(root->d_inode, /*force*/1); ++ sb->s_maxbytes = au_sbr_sb(sb, 0)->s_maxbytes; ++ } else ++ au_sub_nlink(root->d_inode, del->h_path.dentry->d_inode); ++ if (au_opt_test(mnt_flags, PLINK)) ++ au_plink_half_refresh(sb, br_id); ++ ++ if (au_xino_brid(sb) == br_id) ++ au_xino_brid_set(sb, -1); ++ goto out; /* success */ ++ ++out_wh: ++ /* revert */ ++ rerr = au_br_init_wh(sb, br, br->br_perm); ++ if (rerr) ++ pr_warn("failed re-creating base whiteout, %s. (%d)\n", ++ del->pathname, rerr); ++out: ++ if (to_free) ++ au_farray_free(to_free, opened); ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++static int au_ibusy(struct super_block *sb, struct aufs_ibusy __user *arg) ++{ ++ int err; ++ aufs_bindex_t bstart, bend; ++ struct aufs_ibusy ibusy; ++ struct inode *inode, *h_inode; ++ ++ err = -EPERM; ++ if (unlikely(!capable(CAP_SYS_ADMIN))) ++ goto out; ++ ++ err = copy_from_user(&ibusy, arg, sizeof(ibusy)); ++ if (!err) ++ err = !access_ok(VERIFY_WRITE, &arg->h_ino, sizeof(arg->h_ino)); ++ if (unlikely(err)) { ++ err = -EFAULT; ++ AuTraceErr(err); ++ goto out; ++ } ++ ++ err = -EINVAL; ++ si_read_lock(sb, AuLock_FLUSH); ++ if (unlikely(ibusy.bindex < 0 || ibusy.bindex > au_sbend(sb))) ++ goto out_unlock; ++ ++ err = 0; ++ ibusy.h_ino = 0; /* invalid */ ++ inode = ilookup(sb, ibusy.ino); ++ if (!inode ++ || inode->i_ino == AUFS_ROOT_INO ++ || is_bad_inode(inode)) ++ goto out_unlock; ++ ++ ii_read_lock_child(inode); ++ bstart = au_ibstart(inode); ++ bend = au_ibend(inode); ++ if (bstart <= ibusy.bindex && ibusy.bindex <= bend) { ++ h_inode = au_h_iptr(inode, ibusy.bindex); ++ if (h_inode && au_test_ibusy(inode, bstart, bend)) ++ ibusy.h_ino = h_inode->i_ino; ++ } ++ ii_read_unlock(inode); ++ iput(inode); ++ ++out_unlock: ++ si_read_unlock(sb); ++ if (!err) { ++ err = __put_user(ibusy.h_ino, &arg->h_ino); ++ if (unlikely(err)) { ++ err = -EFAULT; ++ AuTraceErr(err); ++ } ++ } ++out: ++ return err; ++} ++ ++long au_ibusy_ioctl(struct file *file, unsigned long arg) ++{ ++ return au_ibusy(file->f_dentry->d_sb, (void __user *)arg); ++} ++ ++#ifdef CONFIG_COMPAT ++long au_ibusy_compat_ioctl(struct file *file, unsigned long arg) ++{ ++ return au_ibusy(file->f_dentry->d_sb, compat_ptr(arg)); ++} ++#endif ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* ++ * change a branch permission ++ */ ++ ++static void au_warn_ima(void) ++{ ++#ifdef CONFIG_IMA ++ /* since it doesn't support mark_files_ro() */ ++ AuWarn1("RW -> RO makes IMA to produce wrong message\n"); ++#endif ++} ++ ++static int do_need_sigen_inc(int a, int b) ++{ ++ return au_br_whable(a) && !au_br_whable(b); ++} ++ ++static int need_sigen_inc(int old, int new) ++{ ++ return do_need_sigen_inc(old, new) ++ || do_need_sigen_inc(new, old); ++} ++ ++static int au_br_mod_files_ro(struct super_block *sb, aufs_bindex_t bindex) ++{ ++ int err, do_warn; ++ unsigned int mnt_flags; ++ unsigned long long ull, max; ++ aufs_bindex_t br_id; ++ unsigned char verbose, writer; ++ struct file *file, *hf, **array; ++ struct inode *inode; ++ struct au_hfile *hfile; ++ ++ mnt_flags = au_mntflags(sb); ++ verbose = !!au_opt_test(mnt_flags, VERBOSE); ++ ++ array = au_farray_alloc(sb, &max); ++ err = PTR_ERR(array); ++ if (IS_ERR(array)) ++ goto out; ++ ++ do_warn = 0; ++ br_id = au_sbr_id(sb, bindex); ++ for (ull = 0; ull < max; ull++) { ++ file = array[ull]; ++ if (unlikely(!file)) ++ break; ++ ++ /* AuDbg("%pD\n", file); */ ++ fi_read_lock(file); ++ if (unlikely(au_test_mmapped(file))) { ++ err = -EBUSY; ++ AuVerbose(verbose, "mmapped %pD\n", file); ++ AuDbgFile(file); ++ FiMustNoWaiters(file); ++ fi_read_unlock(file); ++ goto out_array; ++ } ++ ++ inode = file_inode(file); ++ hfile = &au_fi(file)->fi_htop; ++ hf = hfile->hf_file; ++ if (!S_ISREG(inode->i_mode) ++ || !(file->f_mode & FMODE_WRITE) ++ || hfile->hf_br->br_id != br_id ++ || !(hf->f_mode & FMODE_WRITE)) ++ array[ull] = NULL; ++ else { ++ do_warn = 1; ++ get_file(file); ++ } ++ ++ FiMustNoWaiters(file); ++ fi_read_unlock(file); ++ fput(file); ++ } ++ ++ err = 0; ++ if (do_warn) ++ au_warn_ima(); ++ ++ for (ull = 0; ull < max; ull++) { ++ file = array[ull]; ++ if (!file) ++ continue; ++ ++ /* todo: already flushed? */ ++ /* ++ * fs/super.c:mark_files_ro() is gone, but aufs keeps its ++ * approach which resets f_mode and calls mnt_drop_write() and ++ * file_release_write() for each file, because the branch ++ * attribute in aufs world is totally different from the native ++ * fs rw/ro mode. ++ */ ++ /* fi_read_lock(file); */ ++ hfile = &au_fi(file)->fi_htop; ++ hf = hfile->hf_file; ++ /* fi_read_unlock(file); */ ++ spin_lock(&hf->f_lock); ++ writer = !!(hf->f_mode & FMODE_WRITER); ++ hf->f_mode &= ~(FMODE_WRITE | FMODE_WRITER); ++ spin_unlock(&hf->f_lock); ++ if (writer) { ++ put_write_access(file_inode(hf)); ++ __mnt_drop_write(hf->f_path.mnt); ++ } ++ } ++ ++out_array: ++ au_farray_free(array, max); ++out: ++ AuTraceErr(err); ++ return err; ++} ++ ++int au_br_mod(struct super_block *sb, struct au_opt_mod *mod, int remount, ++ int *do_refresh) ++{ ++ int err, rerr; ++ aufs_bindex_t bindex; ++ struct dentry *root; ++ struct au_branch *br; ++ struct au_br_fhsm *bf; ++ ++ root = sb->s_root; ++ bindex = au_find_dbindex(root, mod->h_root); ++ if (bindex < 0) { ++ if (remount) ++ return 0; /* success */ ++ err = -ENOENT; ++ pr_err("%s no such branch\n", mod->path); ++ goto out; ++ } ++ AuDbg("bindex b%d\n", bindex); ++ ++ err = test_br(mod->h_root->d_inode, mod->perm, mod->path); ++ if (unlikely(err)) ++ goto out; ++ ++ br = au_sbr(sb, bindex); ++ AuDebugOn(mod->h_root != au_br_dentry(br)); ++ if (br->br_perm == mod->perm) ++ return 0; /* success */ ++ ++ /* pre-allocate for non-fhsm --> fhsm */ ++ bf = NULL; ++ if (!au_br_fhsm(br->br_perm) && au_br_fhsm(mod->perm)) { ++ err = au_fhsm_br_alloc(br); ++ if (unlikely(err)) ++ goto out; ++ bf = br->br_fhsm; ++ br->br_fhsm = NULL; ++ } ++ ++ if (au_br_writable(br->br_perm)) { ++ /* remove whiteout base */ ++ err = au_br_init_wh(sb, br, mod->perm); ++ if (unlikely(err)) ++ goto out_bf; ++ ++ if (!au_br_writable(mod->perm)) { ++ /* rw --> ro, file might be mmapped */ ++ DiMustNoWaiters(root); ++ IiMustNoWaiters(root->d_inode); ++ di_write_unlock(root); ++ err = au_br_mod_files_ro(sb, bindex); ++ /* aufs_write_lock() calls ..._child() */ ++ di_write_lock_child(root); ++ ++ if (unlikely(err)) { ++ rerr = -ENOMEM; ++ br->br_wbr = kzalloc(sizeof(*br->br_wbr), ++ GFP_NOFS); ++ if (br->br_wbr) ++ rerr = au_wbr_init(br, sb, br->br_perm); ++ if (unlikely(rerr)) { ++ AuIOErr("nested error %d (%d)\n", ++ rerr, err); ++ br->br_perm = mod->perm; ++ } ++ } ++ } ++ } else if (au_br_writable(mod->perm)) { ++ /* ro --> rw */ ++ err = -ENOMEM; ++ br->br_wbr = kzalloc(sizeof(*br->br_wbr), GFP_NOFS); ++ if (br->br_wbr) { ++ err = au_wbr_init(br, sb, mod->perm); ++ if (unlikely(err)) { ++ kfree(br->br_wbr); ++ br->br_wbr = NULL; ++ } ++ } ++ } ++ if (unlikely(err)) ++ goto out_bf; ++ ++ if (au_br_fhsm(br->br_perm)) { ++ if (!au_br_fhsm(mod->perm)) { ++ /* fhsm --> non-fhsm */ ++ au_br_fhsm_fin(br->br_fhsm); ++ kfree(br->br_fhsm); ++ br->br_fhsm = NULL; ++ } ++ } else if (au_br_fhsm(mod->perm)) ++ /* non-fhsm --> fhsm */ ++ br->br_fhsm = bf; ++ ++ *do_refresh |= need_sigen_inc(br->br_perm, mod->perm); ++ br->br_perm = mod->perm; ++ goto out; /* success */ ++ ++out_bf: ++ kfree(bf); ++out: ++ AuTraceErr(err); ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++int au_br_stfs(struct au_branch *br, struct aufs_stfs *stfs) ++{ ++ int err; ++ struct kstatfs kstfs; ++ ++ err = vfs_statfs(&br->br_path, &kstfs); ++ if (!err) { ++ stfs->f_blocks = kstfs.f_blocks; ++ stfs->f_bavail = kstfs.f_bavail; ++ stfs->f_files = kstfs.f_files; ++ stfs->f_ffree = kstfs.f_ffree; ++ } ++ ++ return err; ++} +diff --git a/fs/aufs/branch.h b/fs/aufs/branch.h +new file mode 100644 +index 0000000..6ae006e +--- /dev/null ++++ b/fs/aufs/branch.h +@@ -0,0 +1,279 @@ ++/* ++ * Copyright (C) 2005-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * branch filesystems and xino for them ++ */ ++ ++#ifndef __AUFS_BRANCH_H__ ++#define __AUFS_BRANCH_H__ ++ ++#ifdef __KERNEL__ ++ ++#include ++#include "dynop.h" ++#include "rwsem.h" ++#include "super.h" ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* a xino file */ ++struct au_xino_file { ++ struct file *xi_file; ++ struct mutex xi_nondir_mtx; ++ ++ /* todo: make xino files an array to support huge inode number */ ++ ++#ifdef CONFIG_DEBUG_FS ++ struct dentry *xi_dbgaufs; ++#endif ++}; ++ ++/* File-based Hierarchical Storage Management */ ++struct au_br_fhsm { ++#ifdef CONFIG_AUFS_FHSM ++ struct mutex bf_lock; ++ unsigned long bf_jiffy; ++ struct aufs_stfs bf_stfs; ++ int bf_readable; ++#endif ++}; ++ ++/* members for writable branch only */ ++enum {AuBrWh_BASE, AuBrWh_PLINK, AuBrWh_ORPH, AuBrWh_Last}; ++struct au_wbr { ++ struct au_rwsem wbr_wh_rwsem; ++ struct dentry *wbr_wh[AuBrWh_Last]; ++ atomic_t wbr_wh_running; ++#define wbr_whbase wbr_wh[AuBrWh_BASE] /* whiteout base */ ++#define wbr_plink wbr_wh[AuBrWh_PLINK] /* pseudo-link dir */ ++#define wbr_orph wbr_wh[AuBrWh_ORPH] /* dir for orphans */ ++ ++ /* mfs mode */ ++ unsigned long long wbr_bytes; ++}; ++ ++/* ext2 has 3 types of operations at least, ext3 has 4 */ ++#define AuBrDynOp (AuDyLast * 4) ++ ++#ifdef CONFIG_AUFS_HFSNOTIFY ++/* support for asynchronous destruction */ ++struct au_br_hfsnotify { ++ struct fsnotify_group *hfsn_group; ++}; ++#endif ++ ++/* sysfs entries */ ++struct au_brsysfs { ++ char name[16]; ++ struct attribute attr; ++}; ++ ++enum { ++ AuBrSysfs_BR, ++ AuBrSysfs_BRID, ++ AuBrSysfs_Last ++}; ++ ++/* protected by superblock rwsem */ ++struct au_branch { ++ struct au_xino_file br_xino; ++ ++ aufs_bindex_t br_id; ++ ++ int br_perm; ++ struct path br_path; ++ spinlock_t br_dykey_lock; ++ struct au_dykey *br_dykey[AuBrDynOp]; ++ atomic_t br_count; ++ ++ struct au_wbr *br_wbr; ++ struct au_br_fhsm *br_fhsm; ++ ++ /* xino truncation */ ++ atomic_t br_xino_running; ++ ++#ifdef CONFIG_AUFS_HFSNOTIFY ++ struct au_br_hfsnotify *br_hfsn; ++#endif ++ ++#ifdef CONFIG_SYSFS ++ /* entries under sysfs per mount-point */ ++ struct au_brsysfs br_sysfs[AuBrSysfs_Last]; ++#endif ++}; ++ ++/* ---------------------------------------------------------------------- */ ++ ++static inline struct vfsmount *au_br_mnt(struct au_branch *br) ++{ ++ return br->br_path.mnt; ++} ++ ++static inline struct dentry *au_br_dentry(struct au_branch *br) ++{ ++ return br->br_path.dentry; ++} ++ ++static inline struct super_block *au_br_sb(struct au_branch *br) ++{ ++ return au_br_mnt(br)->mnt_sb; ++} ++ ++static inline int au_br_rdonly(struct au_branch *br) ++{ ++ return ((au_br_sb(br)->s_flags & MS_RDONLY) ++ || !au_br_writable(br->br_perm)) ++ ? -EROFS : 0; ++} ++ ++static inline int au_br_hnotifyable(int brperm __maybe_unused) ++{ ++#ifdef CONFIG_AUFS_HNOTIFY ++ return !(brperm & AuBrPerm_RR); ++#else ++ return 0; ++#endif ++} ++ ++static inline int au_br_test_oflag(int oflag, struct au_branch *br) ++{ ++ int err, exec_flag; ++ ++ err = 0; ++ exec_flag = oflag & __FMODE_EXEC; ++ if (unlikely(exec_flag && (au_br_mnt(br)->mnt_flags & MNT_NOEXEC))) ++ err = -EACCES; ++ ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* branch.c */ ++struct au_sbinfo; ++void au_br_free(struct au_sbinfo *sinfo); ++int au_br_index(struct super_block *sb, aufs_bindex_t br_id); ++struct au_opt_add; ++int au_br_add(struct super_block *sb, struct au_opt_add *add, int remount); ++struct au_opt_del; ++int au_br_del(struct super_block *sb, struct au_opt_del *del, int remount); ++long au_ibusy_ioctl(struct file *file, unsigned long arg); ++#ifdef CONFIG_COMPAT ++long au_ibusy_compat_ioctl(struct file *file, unsigned long arg); ++#endif ++struct au_opt_mod; ++int au_br_mod(struct super_block *sb, struct au_opt_mod *mod, int remount, ++ int *do_refresh); ++struct aufs_stfs; ++int au_br_stfs(struct au_branch *br, struct aufs_stfs *stfs); ++ ++/* xino.c */ ++static const loff_t au_loff_max = LLONG_MAX; ++ ++int au_xib_trunc(struct super_block *sb); ++ssize_t xino_fread(au_readf_t func, struct file *file, void *buf, size_t size, ++ loff_t *pos); ++ssize_t xino_fwrite(au_writef_t func, struct file *file, void *buf, size_t size, ++ loff_t *pos); ++struct file *au_xino_create2(struct file *base_file, struct file *copy_src); ++struct file *au_xino_create(struct super_block *sb, char *fname, int silent); ++ino_t au_xino_new_ino(struct super_block *sb); ++void au_xino_delete_inode(struct inode *inode, const int unlinked); ++int au_xino_write(struct super_block *sb, aufs_bindex_t bindex, ino_t h_ino, ++ ino_t ino); ++int au_xino_read(struct super_block *sb, aufs_bindex_t bindex, ino_t h_ino, ++ ino_t *ino); ++int au_xino_br(struct super_block *sb, struct au_branch *br, ino_t hino, ++ struct file *base_file, int do_test); ++int au_xino_trunc(struct super_block *sb, aufs_bindex_t bindex); ++ ++struct au_opt_xino; ++int au_xino_set(struct super_block *sb, struct au_opt_xino *xino, int remount); ++void au_xino_clr(struct super_block *sb); ++struct file *au_xino_def(struct super_block *sb); ++int au_xino_path(struct seq_file *seq, struct file *file); ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* Superblock to branch */ ++static inline ++aufs_bindex_t au_sbr_id(struct super_block *sb, aufs_bindex_t bindex) ++{ ++ return au_sbr(sb, bindex)->br_id; ++} ++ ++static inline ++struct vfsmount *au_sbr_mnt(struct super_block *sb, aufs_bindex_t bindex) ++{ ++ return au_br_mnt(au_sbr(sb, bindex)); ++} ++ ++static inline ++struct super_block *au_sbr_sb(struct super_block *sb, aufs_bindex_t bindex) ++{ ++ return au_br_sb(au_sbr(sb, bindex)); ++} ++ ++static inline void au_sbr_put(struct super_block *sb, aufs_bindex_t bindex) ++{ ++ atomic_dec(&au_sbr(sb, bindex)->br_count); ++} ++ ++static inline int au_sbr_perm(struct super_block *sb, aufs_bindex_t bindex) ++{ ++ return au_sbr(sb, bindex)->br_perm; ++} ++ ++static inline int au_sbr_whable(struct super_block *sb, aufs_bindex_t bindex) ++{ ++ return au_br_whable(au_sbr_perm(sb, bindex)); ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* ++ * wbr_wh_read_lock, wbr_wh_write_lock ++ * wbr_wh_read_unlock, wbr_wh_write_unlock, wbr_wh_downgrade_lock ++ */ ++AuSimpleRwsemFuncs(wbr_wh, struct au_wbr *wbr, &wbr->wbr_wh_rwsem); ++ ++#define WbrWhMustNoWaiters(wbr) AuRwMustNoWaiters(&wbr->wbr_wh_rwsem) ++#define WbrWhMustAnyLock(wbr) AuRwMustAnyLock(&wbr->wbr_wh_rwsem) ++#define WbrWhMustWriteLock(wbr) AuRwMustWriteLock(&wbr->wbr_wh_rwsem) ++ ++/* ---------------------------------------------------------------------- */ ++ ++#ifdef CONFIG_AUFS_FHSM ++static inline void au_br_fhsm_init(struct au_br_fhsm *brfhsm) ++{ ++ mutex_init(&brfhsm->bf_lock); ++ brfhsm->bf_jiffy = 0; ++ brfhsm->bf_readable = 0; ++} ++ ++static inline void au_br_fhsm_fin(struct au_br_fhsm *brfhsm) ++{ ++ mutex_destroy(&brfhsm->bf_lock); ++} ++#else ++AuStubVoid(au_br_fhsm_init, struct au_br_fhsm *brfhsm) ++AuStubVoid(au_br_fhsm_fin, struct au_br_fhsm *brfhsm) ++#endif ++ ++#endif /* __KERNEL__ */ ++#endif /* __AUFS_BRANCH_H__ */ +diff --git a/fs/aufs/conf.mk b/fs/aufs/conf.mk +new file mode 100644 +index 0000000..0bbb2d3 +--- /dev/null ++++ b/fs/aufs/conf.mk +@@ -0,0 +1,38 @@ ++ ++AuConfStr = CONFIG_AUFS_FS=${CONFIG_AUFS_FS} ++ ++define AuConf ++ifdef ${1} ++AuConfStr += ${1}=${${1}} ++endif ++endef ++ ++AuConfAll = BRANCH_MAX_127 BRANCH_MAX_511 BRANCH_MAX_1023 BRANCH_MAX_32767 \ ++ SBILIST \ ++ HNOTIFY HFSNOTIFY \ ++ EXPORT INO_T_64 \ ++ XATTR \ ++ FHSM \ ++ RDU \ ++ SHWH \ ++ BR_RAMFS \ ++ BR_FUSE POLL \ ++ BR_HFSPLUS \ ++ BDEV_LOOP \ ++ DEBUG MAGIC_SYSRQ ++$(foreach i, ${AuConfAll}, \ ++ $(eval $(call AuConf,CONFIG_AUFS_${i}))) ++ ++AuConfName = ${obj}/conf.str ++${AuConfName}.tmp: FORCE ++ @echo ${AuConfStr} | tr ' ' '\n' | sed -e 's/^/"/' -e 's/$$/\\n"/' > $@ ++${AuConfName}: ${AuConfName}.tmp ++ @diff -q $< $@ > /dev/null 2>&1 || { \ ++ echo ' GEN ' $@; \ ++ cp -p $< $@; \ ++ } ++FORCE: ++clean-files += ${AuConfName} ${AuConfName}.tmp ++${obj}/sysfs.o: ${AuConfName} ++ ++-include ${srctree}/${src}/conf_priv.mk +diff --git a/fs/aufs/cpup.c b/fs/aufs/cpup.c +new file mode 100644 +index 0000000..9d8b767 +--- /dev/null ++++ b/fs/aufs/cpup.c +@@ -0,0 +1,1368 @@ ++/* ++ * Copyright (C) 2005-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * copy-up functions, see wbr_policy.c for copy-down ++ */ ++ ++#include ++#include ++#include ++#include "aufs.h" ++ ++void au_cpup_attr_flags(struct inode *dst, unsigned int iflags) ++{ ++ const unsigned int mask = S_DEAD | S_SWAPFILE | S_PRIVATE ++ | S_NOATIME | S_NOCMTIME | S_AUTOMOUNT; ++ ++ BUILD_BUG_ON(sizeof(iflags) != sizeof(dst->i_flags)); ++ ++ dst->i_flags |= iflags & ~mask; ++ if (au_test_fs_notime(dst->i_sb)) ++ dst->i_flags |= S_NOATIME | S_NOCMTIME; ++} ++ ++void au_cpup_attr_timesizes(struct inode *inode) ++{ ++ struct inode *h_inode; ++ ++ h_inode = au_h_iptr(inode, au_ibstart(inode)); ++ fsstack_copy_attr_times(inode, h_inode); ++ fsstack_copy_inode_size(inode, h_inode); ++} ++ ++void au_cpup_attr_nlink(struct inode *inode, int force) ++{ ++ struct inode *h_inode; ++ struct super_block *sb; ++ aufs_bindex_t bindex, bend; ++ ++ sb = inode->i_sb; ++ bindex = au_ibstart(inode); ++ h_inode = au_h_iptr(inode, bindex); ++ if (!force ++ && !S_ISDIR(h_inode->i_mode) ++ && au_opt_test(au_mntflags(sb), PLINK) ++ && au_plink_test(inode)) ++ return; ++ ++ /* ++ * 0 can happen in revalidating. ++ * h_inode->i_mutex may not be held here, but it is harmless since once ++ * i_nlink reaches 0, it will never become positive except O_TMPFILE ++ * case. ++ * todo: O_TMPFILE+linkat(AT_SYMLINK_FOLLOW) bypassing aufs may cause ++ * the incorrect link count. ++ */ ++ set_nlink(inode, h_inode->i_nlink); ++ ++ /* ++ * fewer nlink makes find(1) noisy, but larger nlink doesn't. ++ * it may includes whplink directory. ++ */ ++ if (S_ISDIR(h_inode->i_mode)) { ++ bend = au_ibend(inode); ++ for (bindex++; bindex <= bend; bindex++) { ++ h_inode = au_h_iptr(inode, bindex); ++ if (h_inode) ++ au_add_nlink(inode, h_inode); ++ } ++ } ++} ++ ++void au_cpup_attr_changeable(struct inode *inode) ++{ ++ struct inode *h_inode; ++ ++ h_inode = au_h_iptr(inode, au_ibstart(inode)); ++ inode->i_mode = h_inode->i_mode; ++ inode->i_uid = h_inode->i_uid; ++ inode->i_gid = h_inode->i_gid; ++ au_cpup_attr_timesizes(inode); ++ au_cpup_attr_flags(inode, h_inode->i_flags); ++} ++ ++void au_cpup_igen(struct inode *inode, struct inode *h_inode) ++{ ++ struct au_iinfo *iinfo = au_ii(inode); ++ ++ IiMustWriteLock(inode); ++ ++ iinfo->ii_higen = h_inode->i_generation; ++ iinfo->ii_hsb1 = h_inode->i_sb; ++} ++ ++void au_cpup_attr_all(struct inode *inode, int force) ++{ ++ struct inode *h_inode; ++ ++ h_inode = au_h_iptr(inode, au_ibstart(inode)); ++ au_cpup_attr_changeable(inode); ++ if (inode->i_nlink > 0) ++ au_cpup_attr_nlink(inode, force); ++ inode->i_rdev = h_inode->i_rdev; ++ inode->i_blkbits = h_inode->i_blkbits; ++ au_cpup_igen(inode, h_inode); ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* Note: dt_dentry and dt_h_dentry are not dget/dput-ed */ ++ ++/* keep the timestamps of the parent dir when cpup */ ++void au_dtime_store(struct au_dtime *dt, struct dentry *dentry, ++ struct path *h_path) ++{ ++ struct inode *h_inode; ++ ++ dt->dt_dentry = dentry; ++ dt->dt_h_path = *h_path; ++ h_inode = h_path->dentry->d_inode; ++ dt->dt_atime = h_inode->i_atime; ++ dt->dt_mtime = h_inode->i_mtime; ++ /* smp_mb(); */ ++} ++ ++void au_dtime_revert(struct au_dtime *dt) ++{ ++ struct iattr attr; ++ int err; ++ ++ attr.ia_atime = dt->dt_atime; ++ attr.ia_mtime = dt->dt_mtime; ++ attr.ia_valid = ATTR_FORCE | ATTR_MTIME | ATTR_MTIME_SET ++ | ATTR_ATIME | ATTR_ATIME_SET; ++ ++ /* no delegation since this is a directory */ ++ err = vfsub_notify_change(&dt->dt_h_path, &attr, /*delegated*/NULL); ++ if (unlikely(err)) ++ pr_warn("restoring timestamps failed(%d). ignored\n", err); ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* internal use only */ ++struct au_cpup_reg_attr { ++ int valid; ++ struct kstat st; ++ unsigned int iflags; /* inode->i_flags */ ++}; ++ ++static noinline_for_stack ++int cpup_iattr(struct dentry *dst, aufs_bindex_t bindex, struct dentry *h_src, ++ struct au_cpup_reg_attr *h_src_attr) ++{ ++ int err, sbits, icex; ++ unsigned int mnt_flags; ++ unsigned char verbose; ++ struct iattr ia; ++ struct path h_path; ++ struct inode *h_isrc, *h_idst; ++ struct kstat *h_st; ++ struct au_branch *br; ++ ++ h_path.dentry = au_h_dptr(dst, bindex); ++ h_idst = h_path.dentry->d_inode; ++ br = au_sbr(dst->d_sb, bindex); ++ h_path.mnt = au_br_mnt(br); ++ h_isrc = h_src->d_inode; ++ ia.ia_valid = ATTR_FORCE | ATTR_UID | ATTR_GID ++ | ATTR_ATIME | ATTR_MTIME ++ | ATTR_ATIME_SET | ATTR_MTIME_SET; ++ if (h_src_attr && h_src_attr->valid) { ++ h_st = &h_src_attr->st; ++ ia.ia_uid = h_st->uid; ++ ia.ia_gid = h_st->gid; ++ ia.ia_atime = h_st->atime; ++ ia.ia_mtime = h_st->mtime; ++ if (h_idst->i_mode != h_st->mode ++ && !S_ISLNK(h_idst->i_mode)) { ++ ia.ia_valid |= ATTR_MODE; ++ ia.ia_mode = h_st->mode; ++ } ++ sbits = !!(h_st->mode & (S_ISUID | S_ISGID)); ++ au_cpup_attr_flags(h_idst, h_src_attr->iflags); ++ } else { ++ ia.ia_uid = h_isrc->i_uid; ++ ia.ia_gid = h_isrc->i_gid; ++ ia.ia_atime = h_isrc->i_atime; ++ ia.ia_mtime = h_isrc->i_mtime; ++ if (h_idst->i_mode != h_isrc->i_mode ++ && !S_ISLNK(h_idst->i_mode)) { ++ ia.ia_valid |= ATTR_MODE; ++ ia.ia_mode = h_isrc->i_mode; ++ } ++ sbits = !!(h_isrc->i_mode & (S_ISUID | S_ISGID)); ++ au_cpup_attr_flags(h_idst, h_isrc->i_flags); ++ } ++ /* no delegation since it is just created */ ++ err = vfsub_notify_change(&h_path, &ia, /*delegated*/NULL); ++ ++ /* is this nfs only? */ ++ if (!err && sbits && au_test_nfs(h_path.dentry->d_sb)) { ++ ia.ia_valid = ATTR_FORCE | ATTR_MODE; ++ ia.ia_mode = h_isrc->i_mode; ++ err = vfsub_notify_change(&h_path, &ia, /*delegated*/NULL); ++ } ++ ++ icex = br->br_perm & AuBrAttr_ICEX; ++ if (!err) { ++ mnt_flags = au_mntflags(dst->d_sb); ++ verbose = !!au_opt_test(mnt_flags, VERBOSE); ++ err = au_cpup_xattr(h_path.dentry, h_src, icex, verbose); ++ } ++ ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++static int au_do_copy_file(struct file *dst, struct file *src, loff_t len, ++ char *buf, unsigned long blksize) ++{ ++ int err; ++ size_t sz, rbytes, wbytes; ++ unsigned char all_zero; ++ char *p, *zp; ++ struct mutex *h_mtx; ++ /* reduce stack usage */ ++ struct iattr *ia; ++ ++ zp = page_address(ZERO_PAGE(0)); ++ if (unlikely(!zp)) ++ return -ENOMEM; /* possible? */ ++ ++ err = 0; ++ all_zero = 0; ++ while (len) { ++ AuDbg("len %lld\n", len); ++ sz = blksize; ++ if (len < blksize) ++ sz = len; ++ ++ rbytes = 0; ++ /* todo: signal_pending? */ ++ while (!rbytes || err == -EAGAIN || err == -EINTR) { ++ rbytes = vfsub_read_k(src, buf, sz, &src->f_pos); ++ err = rbytes; ++ } ++ if (unlikely(err < 0)) ++ break; ++ ++ all_zero = 0; ++ if (len >= rbytes && rbytes == blksize) ++ all_zero = !memcmp(buf, zp, rbytes); ++ if (!all_zero) { ++ wbytes = rbytes; ++ p = buf; ++ while (wbytes) { ++ size_t b; ++ ++ b = vfsub_write_k(dst, p, wbytes, &dst->f_pos); ++ err = b; ++ /* todo: signal_pending? */ ++ if (unlikely(err == -EAGAIN || err == -EINTR)) ++ continue; ++ if (unlikely(err < 0)) ++ break; ++ wbytes -= b; ++ p += b; ++ } ++ if (unlikely(err < 0)) ++ break; ++ } else { ++ loff_t res; ++ ++ AuLabel(hole); ++ res = vfsub_llseek(dst, rbytes, SEEK_CUR); ++ err = res; ++ if (unlikely(res < 0)) ++ break; ++ } ++ len -= rbytes; ++ err = 0; ++ } ++ ++ /* the last block may be a hole */ ++ if (!err && all_zero) { ++ AuLabel(last hole); ++ ++ err = 1; ++ if (au_test_nfs(dst->f_dentry->d_sb)) { ++ /* nfs requires this step to make last hole */ ++ /* is this only nfs? */ ++ do { ++ /* todo: signal_pending? */ ++ err = vfsub_write_k(dst, "\0", 1, &dst->f_pos); ++ } while (err == -EAGAIN || err == -EINTR); ++ if (err == 1) ++ dst->f_pos--; ++ } ++ ++ if (err == 1) { ++ ia = (void *)buf; ++ ia->ia_size = dst->f_pos; ++ ia->ia_valid = ATTR_SIZE | ATTR_FILE; ++ ia->ia_file = dst; ++ h_mtx = &file_inode(dst)->i_mutex; ++ mutex_lock_nested(h_mtx, AuLsc_I_CHILD2); ++ /* no delegation since it is just created */ ++ err = vfsub_notify_change(&dst->f_path, ia, ++ /*delegated*/NULL); ++ mutex_unlock(h_mtx); ++ } ++ } ++ ++ return err; ++} ++ ++int au_copy_file(struct file *dst, struct file *src, loff_t len) ++{ ++ int err; ++ unsigned long blksize; ++ unsigned char do_kfree; ++ char *buf; ++ ++ err = -ENOMEM; ++ blksize = dst->f_dentry->d_sb->s_blocksize; ++ if (!blksize || PAGE_SIZE < blksize) ++ blksize = PAGE_SIZE; ++ AuDbg("blksize %lu\n", blksize); ++ do_kfree = (blksize != PAGE_SIZE && blksize >= sizeof(struct iattr *)); ++ if (do_kfree) ++ buf = kmalloc(blksize, GFP_NOFS); ++ else ++ buf = (void *)__get_free_page(GFP_NOFS); ++ if (unlikely(!buf)) ++ goto out; ++ ++ if (len > (1 << 22)) ++ AuDbg("copying a large file %lld\n", (long long)len); ++ ++ src->f_pos = 0; ++ dst->f_pos = 0; ++ err = au_do_copy_file(dst, src, len, buf, blksize); ++ if (do_kfree) ++ kfree(buf); ++ else ++ free_page((unsigned long)buf); ++ ++out: ++ return err; ++} ++ ++/* ++ * to support a sparse file which is opened with O_APPEND, ++ * we need to close the file. ++ */ ++static int au_cp_regular(struct au_cp_generic *cpg) ++{ ++ int err, i; ++ enum { SRC, DST }; ++ struct { ++ aufs_bindex_t bindex; ++ unsigned int flags; ++ struct dentry *dentry; ++ int force_wr; ++ struct file *file; ++ void *label; ++ } *f, file[] = { ++ { ++ .bindex = cpg->bsrc, ++ .flags = O_RDONLY | O_NOATIME | O_LARGEFILE, ++ .label = &&out ++ }, ++ { ++ .bindex = cpg->bdst, ++ .flags = O_WRONLY | O_NOATIME | O_LARGEFILE, ++ .force_wr = !!au_ftest_cpup(cpg->flags, RWDST), ++ .label = &&out_src ++ } ++ }; ++ struct super_block *sb; ++ struct task_struct *tsk = current; ++ ++ /* bsrc branch can be ro/rw. */ ++ sb = cpg->dentry->d_sb; ++ f = file; ++ for (i = 0; i < 2; i++, f++) { ++ f->dentry = au_h_dptr(cpg->dentry, f->bindex); ++ f->file = au_h_open(cpg->dentry, f->bindex, f->flags, ++ /*file*/NULL, f->force_wr); ++ err = PTR_ERR(f->file); ++ if (IS_ERR(f->file)) ++ goto *f->label; ++ } ++ ++ /* try stopping to update while we copyup */ ++ IMustLock(file[SRC].dentry->d_inode); ++ err = au_copy_file(file[DST].file, file[SRC].file, cpg->len); ++ ++ /* i wonder if we had O_NO_DELAY_FPUT flag */ ++ if (tsk->flags & PF_KTHREAD) ++ __fput_sync(file[DST].file); ++ else { ++ WARN(1, "%pD\nPlease report this warning to aufs-users ML", ++ file[DST].file); ++ fput(file[DST].file); ++ /* ++ * too bad. ++ * we have to call both since we don't know which place the file ++ * was added to. ++ */ ++ task_work_run(); ++ flush_delayed_fput(); ++ } ++ au_sbr_put(sb, file[DST].bindex); ++ ++out_src: ++ fput(file[SRC].file); ++ au_sbr_put(sb, file[SRC].bindex); ++out: ++ return err; ++} ++ ++static int au_do_cpup_regular(struct au_cp_generic *cpg, ++ struct au_cpup_reg_attr *h_src_attr) ++{ ++ int err, rerr; ++ loff_t l; ++ struct path h_path; ++ struct inode *h_src_inode, *h_dst_inode; ++ ++ err = 0; ++ h_src_inode = au_h_iptr(cpg->dentry->d_inode, cpg->bsrc); ++ l = i_size_read(h_src_inode); ++ if (cpg->len == -1 || l < cpg->len) ++ cpg->len = l; ++ if (cpg->len) { ++ /* try stopping to update while we are referencing */ ++ mutex_lock_nested(&h_src_inode->i_mutex, AuLsc_I_CHILD); ++ au_pin_hdir_unlock(cpg->pin); ++ ++ h_path.dentry = au_h_dptr(cpg->dentry, cpg->bsrc); ++ h_path.mnt = au_sbr_mnt(cpg->dentry->d_sb, cpg->bsrc); ++ h_src_attr->iflags = h_src_inode->i_flags; ++ err = vfs_getattr(&h_path, &h_src_attr->st); ++ if (unlikely(err)) { ++ mutex_unlock(&h_src_inode->i_mutex); ++ goto out; ++ } ++ h_src_attr->valid = 1; ++ err = au_cp_regular(cpg); ++ mutex_unlock(&h_src_inode->i_mutex); ++ rerr = au_pin_hdir_relock(cpg->pin); ++ if (!err && rerr) ++ err = rerr; ++ } ++ if (!err && (h_src_inode->i_state & I_LINKABLE)) { ++ h_path.dentry = au_h_dptr(cpg->dentry, cpg->bdst); ++ h_dst_inode = h_path.dentry->d_inode; ++ spin_lock(&h_dst_inode->i_lock); ++ h_dst_inode->i_state |= I_LINKABLE; ++ spin_unlock(&h_dst_inode->i_lock); ++ } ++ ++out: ++ return err; ++} ++ ++static int au_do_cpup_symlink(struct path *h_path, struct dentry *h_src, ++ struct inode *h_dir) ++{ ++ int err, symlen; ++ mm_segment_t old_fs; ++ union { ++ char *k; ++ char __user *u; ++ } sym; ++ ++ err = -ENOSYS; ++ if (unlikely(!h_src->d_inode->i_op->readlink)) ++ goto out; ++ ++ err = -ENOMEM; ++ sym.k = (void *)__get_free_page(GFP_NOFS); ++ if (unlikely(!sym.k)) ++ goto out; ++ ++ /* unnecessary to support mmap_sem since symlink is not mmap-able */ ++ old_fs = get_fs(); ++ set_fs(KERNEL_DS); ++ symlen = h_src->d_inode->i_op->readlink(h_src, sym.u, PATH_MAX); ++ err = symlen; ++ set_fs(old_fs); ++ ++ if (symlen > 0) { ++ sym.k[symlen] = 0; ++ err = vfsub_symlink(h_dir, h_path, sym.k); ++ } ++ free_page((unsigned long)sym.k); ++ ++out: ++ return err; ++} ++ ++/* ++ * regardless 'acl' option, reset all ACL. ++ * All ACL will be copied up later from the original entry on the lower branch. ++ */ ++static int au_reset_acl(struct inode *h_dir, struct path *h_path, umode_t mode) ++{ ++ int err; ++ struct dentry *h_dentry; ++ struct inode *h_inode; ++ ++ h_dentry = h_path->dentry; ++ h_inode = h_dentry->d_inode; ++ /* forget_all_cached_acls(h_inode)); */ ++ err = vfsub_removexattr(h_dentry, XATTR_NAME_POSIX_ACL_ACCESS); ++ AuTraceErr(err); ++ if (err == -EOPNOTSUPP) ++ err = 0; ++ if (!err) ++ err = vfsub_acl_chmod(h_inode, mode); ++ ++ AuTraceErr(err); ++ return err; ++} ++ ++static int au_do_cpup_dir(struct au_cp_generic *cpg, struct dentry *dst_parent, ++ struct inode *h_dir, struct path *h_path) ++{ ++ int err; ++ struct inode *dir; ++ ++ err = vfsub_removexattr(h_path->dentry, XATTR_NAME_POSIX_ACL_DEFAULT); ++ AuTraceErr(err); ++ if (err == -EOPNOTSUPP) ++ err = 0; ++ if (unlikely(err)) ++ goto out; ++ ++ /* ++ * strange behaviour from the users view, ++ * particularry setattr case ++ */ ++ dir = dst_parent->d_inode; ++ if (au_ibstart(dir) == cpg->bdst) ++ au_cpup_attr_nlink(dir, /*force*/1); ++ au_cpup_attr_nlink(cpg->dentry->d_inode, /*force*/1); ++ ++out: ++ return err; ++} ++ ++static noinline_for_stack ++int cpup_entry(struct au_cp_generic *cpg, struct dentry *dst_parent, ++ struct au_cpup_reg_attr *h_src_attr) ++{ ++ int err; ++ umode_t mode; ++ unsigned int mnt_flags; ++ unsigned char isdir, isreg, force; ++ const unsigned char do_dt = !!au_ftest_cpup(cpg->flags, DTIME); ++ struct au_dtime dt; ++ struct path h_path; ++ struct dentry *h_src, *h_dst, *h_parent; ++ struct inode *h_inode, *h_dir; ++ struct super_block *sb; ++ ++ /* bsrc branch can be ro/rw. */ ++ h_src = au_h_dptr(cpg->dentry, cpg->bsrc); ++ h_inode = h_src->d_inode; ++ AuDebugOn(h_inode != au_h_iptr(cpg->dentry->d_inode, cpg->bsrc)); ++ ++ /* try stopping to be referenced while we are creating */ ++ h_dst = au_h_dptr(cpg->dentry, cpg->bdst); ++ if (au_ftest_cpup(cpg->flags, RENAME)) ++ AuDebugOn(strncmp(h_dst->d_name.name, AUFS_WH_PFX, ++ AUFS_WH_PFX_LEN)); ++ h_parent = h_dst->d_parent; /* dir inode is locked */ ++ h_dir = h_parent->d_inode; ++ IMustLock(h_dir); ++ AuDebugOn(h_parent != h_dst->d_parent); ++ ++ sb = cpg->dentry->d_sb; ++ h_path.mnt = au_sbr_mnt(sb, cpg->bdst); ++ if (do_dt) { ++ h_path.dentry = h_parent; ++ au_dtime_store(&dt, dst_parent, &h_path); ++ } ++ h_path.dentry = h_dst; ++ ++ isreg = 0; ++ isdir = 0; ++ mode = h_inode->i_mode; ++ switch (mode & S_IFMT) { ++ case S_IFREG: ++ isreg = 1; ++ err = vfsub_create(h_dir, &h_path, S_IRUSR | S_IWUSR, ++ /*want_excl*/true); ++ if (!err) ++ err = au_do_cpup_regular(cpg, h_src_attr); ++ break; ++ case S_IFDIR: ++ isdir = 1; ++ err = vfsub_mkdir(h_dir, &h_path, mode); ++ if (!err) ++ err = au_do_cpup_dir(cpg, dst_parent, h_dir, &h_path); ++ break; ++ case S_IFLNK: ++ err = au_do_cpup_symlink(&h_path, h_src, h_dir); ++ break; ++ case S_IFCHR: ++ case S_IFBLK: ++ AuDebugOn(!capable(CAP_MKNOD)); ++ /*FALLTHROUGH*/ ++ case S_IFIFO: ++ case S_IFSOCK: ++ err = vfsub_mknod(h_dir, &h_path, mode, h_inode->i_rdev); ++ break; ++ default: ++ AuIOErr("Unknown inode type 0%o\n", mode); ++ err = -EIO; ++ } ++ if (!err) ++ err = au_reset_acl(h_dir, &h_path, mode); ++ ++ mnt_flags = au_mntflags(sb); ++ if (!au_opt_test(mnt_flags, UDBA_NONE) ++ && !isdir ++ && au_opt_test(mnt_flags, XINO) ++ && (h_inode->i_nlink == 1 ++ || (h_inode->i_state & I_LINKABLE)) ++ /* todo: unnecessary? */ ++ /* && cpg->dentry->d_inode->i_nlink == 1 */ ++ && cpg->bdst < cpg->bsrc ++ && !au_ftest_cpup(cpg->flags, KEEPLINO)) ++ au_xino_write(sb, cpg->bsrc, h_inode->i_ino, /*ino*/0); ++ /* ignore this error */ ++ ++ if (!err) { ++ force = 0; ++ if (isreg) { ++ force = !!cpg->len; ++ if (cpg->len == -1) ++ force = !!i_size_read(h_inode); ++ } ++ au_fhsm_wrote(sb, cpg->bdst, force); ++ } ++ ++ if (do_dt) ++ au_dtime_revert(&dt); ++ return err; ++} ++ ++static int au_do_ren_after_cpup(struct au_cp_generic *cpg, struct path *h_path) ++{ ++ int err; ++ struct dentry *dentry, *h_dentry, *h_parent, *parent; ++ struct inode *h_dir; ++ aufs_bindex_t bdst; ++ ++ dentry = cpg->dentry; ++ bdst = cpg->bdst; ++ h_dentry = au_h_dptr(dentry, bdst); ++ if (!au_ftest_cpup(cpg->flags, OVERWRITE)) { ++ dget(h_dentry); ++ au_set_h_dptr(dentry, bdst, NULL); ++ err = au_lkup_neg(dentry, bdst, /*wh*/0); ++ if (!err) ++ h_path->dentry = dget(au_h_dptr(dentry, bdst)); ++ au_set_h_dptr(dentry, bdst, h_dentry); ++ } else { ++ err = 0; ++ parent = dget_parent(dentry); ++ h_parent = au_h_dptr(parent, bdst); ++ dput(parent); ++ h_path->dentry = vfsub_lkup_one(&dentry->d_name, h_parent); ++ if (IS_ERR(h_path->dentry)) ++ err = PTR_ERR(h_path->dentry); ++ } ++ if (unlikely(err)) ++ goto out; ++ ++ h_parent = h_dentry->d_parent; /* dir inode is locked */ ++ h_dir = h_parent->d_inode; ++ IMustLock(h_dir); ++ AuDbg("%pd %pd\n", h_dentry, h_path->dentry); ++ /* no delegation since it is just created */ ++ err = vfsub_rename(h_dir, h_dentry, h_dir, h_path, /*delegated*/NULL); ++ dput(h_path->dentry); ++ ++out: ++ return err; ++} ++ ++/* ++ * copyup the @dentry from @bsrc to @bdst. ++ * the caller must set the both of lower dentries. ++ * @len is for truncating when it is -1 copyup the entire file. ++ * in link/rename cases, @dst_parent may be different from the real one. ++ * basic->bsrc can be larger than basic->bdst. ++ */ ++static int au_cpup_single(struct au_cp_generic *cpg, struct dentry *dst_parent) ++{ ++ int err, rerr; ++ aufs_bindex_t old_ibstart; ++ unsigned char isdir, plink; ++ struct dentry *h_src, *h_dst, *h_parent; ++ struct inode *dst_inode, *h_dir, *inode, *delegated; ++ struct super_block *sb; ++ struct au_branch *br; ++ /* to reuduce stack size */ ++ struct { ++ struct au_dtime dt; ++ struct path h_path; ++ struct au_cpup_reg_attr h_src_attr; ++ } *a; ++ ++ err = -ENOMEM; ++ a = kmalloc(sizeof(*a), GFP_NOFS); ++ if (unlikely(!a)) ++ goto out; ++ a->h_src_attr.valid = 0; ++ ++ sb = cpg->dentry->d_sb; ++ br = au_sbr(sb, cpg->bdst); ++ a->h_path.mnt = au_br_mnt(br); ++ h_dst = au_h_dptr(cpg->dentry, cpg->bdst); ++ h_parent = h_dst->d_parent; /* dir inode is locked */ ++ h_dir = h_parent->d_inode; ++ IMustLock(h_dir); ++ ++ h_src = au_h_dptr(cpg->dentry, cpg->bsrc); ++ inode = cpg->dentry->d_inode; ++ ++ if (!dst_parent) ++ dst_parent = dget_parent(cpg->dentry); ++ else ++ dget(dst_parent); ++ ++ plink = !!au_opt_test(au_mntflags(sb), PLINK); ++ dst_inode = au_h_iptr(inode, cpg->bdst); ++ if (dst_inode) { ++ if (unlikely(!plink)) { ++ err = -EIO; ++ AuIOErr("hi%lu(i%lu) exists on b%d " ++ "but plink is disabled\n", ++ dst_inode->i_ino, inode->i_ino, cpg->bdst); ++ goto out_parent; ++ } ++ ++ if (dst_inode->i_nlink) { ++ const int do_dt = au_ftest_cpup(cpg->flags, DTIME); ++ ++ h_src = au_plink_lkup(inode, cpg->bdst); ++ err = PTR_ERR(h_src); ++ if (IS_ERR(h_src)) ++ goto out_parent; ++ if (unlikely(!h_src->d_inode)) { ++ err = -EIO; ++ AuIOErr("i%lu exists on b%d " ++ "but not pseudo-linked\n", ++ inode->i_ino, cpg->bdst); ++ dput(h_src); ++ goto out_parent; ++ } ++ ++ if (do_dt) { ++ a->h_path.dentry = h_parent; ++ au_dtime_store(&a->dt, dst_parent, &a->h_path); ++ } ++ ++ a->h_path.dentry = h_dst; ++ delegated = NULL; ++ err = vfsub_link(h_src, h_dir, &a->h_path, &delegated); ++ if (!err && au_ftest_cpup(cpg->flags, RENAME)) ++ err = au_do_ren_after_cpup(cpg, &a->h_path); ++ if (do_dt) ++ au_dtime_revert(&a->dt); ++ if (unlikely(err == -EWOULDBLOCK)) { ++ pr_warn("cannot retry for NFSv4 delegation" ++ " for an internal link\n"); ++ iput(delegated); ++ } ++ dput(h_src); ++ goto out_parent; ++ } else ++ /* todo: cpup_wh_file? */ ++ /* udba work */ ++ au_update_ibrange(inode, /*do_put_zero*/1); ++ } ++ ++ isdir = S_ISDIR(inode->i_mode); ++ old_ibstart = au_ibstart(inode); ++ err = cpup_entry(cpg, dst_parent, &a->h_src_attr); ++ if (unlikely(err)) ++ goto out_rev; ++ dst_inode = h_dst->d_inode; ++ mutex_lock_nested(&dst_inode->i_mutex, AuLsc_I_CHILD2); ++ /* todo: necessary? */ ++ /* au_pin_hdir_unlock(cpg->pin); */ ++ ++ err = cpup_iattr(cpg->dentry, cpg->bdst, h_src, &a->h_src_attr); ++ if (unlikely(err)) { ++ /* todo: necessary? */ ++ /* au_pin_hdir_relock(cpg->pin); */ /* ignore an error */ ++ mutex_unlock(&dst_inode->i_mutex); ++ goto out_rev; ++ } ++ ++ if (cpg->bdst < old_ibstart) { ++ if (S_ISREG(inode->i_mode)) { ++ err = au_dy_iaop(inode, cpg->bdst, dst_inode); ++ if (unlikely(err)) { ++ /* ignore an error */ ++ /* au_pin_hdir_relock(cpg->pin); */ ++ mutex_unlock(&dst_inode->i_mutex); ++ goto out_rev; ++ } ++ } ++ au_set_ibstart(inode, cpg->bdst); ++ } else ++ au_set_ibend(inode, cpg->bdst); ++ au_set_h_iptr(inode, cpg->bdst, au_igrab(dst_inode), ++ au_hi_flags(inode, isdir)); ++ ++ /* todo: necessary? */ ++ /* err = au_pin_hdir_relock(cpg->pin); */ ++ mutex_unlock(&dst_inode->i_mutex); ++ if (unlikely(err)) ++ goto out_rev; ++ ++ if (!isdir ++ && (h_src->d_inode->i_nlink > 1 ++ || h_src->d_inode->i_state & I_LINKABLE) ++ && plink) ++ au_plink_append(inode, cpg->bdst, h_dst); ++ ++ if (au_ftest_cpup(cpg->flags, RENAME)) { ++ a->h_path.dentry = h_dst; ++ err = au_do_ren_after_cpup(cpg, &a->h_path); ++ } ++ if (!err) ++ goto out_parent; /* success */ ++ ++ /* revert */ ++out_rev: ++ a->h_path.dentry = h_parent; ++ au_dtime_store(&a->dt, dst_parent, &a->h_path); ++ a->h_path.dentry = h_dst; ++ rerr = 0; ++ if (h_dst->d_inode) { ++ if (!isdir) { ++ /* no delegation since it is just created */ ++ rerr = vfsub_unlink(h_dir, &a->h_path, ++ /*delegated*/NULL, /*force*/0); ++ } else ++ rerr = vfsub_rmdir(h_dir, &a->h_path); ++ } ++ au_dtime_revert(&a->dt); ++ if (rerr) { ++ AuIOErr("failed removing broken entry(%d, %d)\n", err, rerr); ++ err = -EIO; ++ } ++out_parent: ++ dput(dst_parent); ++ kfree(a); ++out: ++ return err; ++} ++ ++#if 0 /* reserved */ ++struct au_cpup_single_args { ++ int *errp; ++ struct au_cp_generic *cpg; ++ struct dentry *dst_parent; ++}; ++ ++static void au_call_cpup_single(void *args) ++{ ++ struct au_cpup_single_args *a = args; ++ ++ au_pin_hdir_acquire_nest(a->cpg->pin); ++ *a->errp = au_cpup_single(a->cpg, a->dst_parent); ++ au_pin_hdir_release(a->cpg->pin); ++} ++#endif ++ ++/* ++ * prevent SIGXFSZ in copy-up. ++ * testing CAP_MKNOD is for generic fs, ++ * but CAP_FSETID is for xfs only, currently. ++ */ ++static int au_cpup_sio_test(struct au_pin *pin, umode_t mode) ++{ ++ int do_sio; ++ struct super_block *sb; ++ struct inode *h_dir; ++ ++ do_sio = 0; ++ sb = au_pinned_parent(pin)->d_sb; ++ if (!au_wkq_test() ++ && (!au_sbi(sb)->si_plink_maint_pid ++ || au_plink_maint(sb, AuLock_NOPLM))) { ++ switch (mode & S_IFMT) { ++ case S_IFREG: ++ /* no condition about RLIMIT_FSIZE and the file size */ ++ do_sio = 1; ++ break; ++ case S_IFCHR: ++ case S_IFBLK: ++ do_sio = !capable(CAP_MKNOD); ++ break; ++ } ++ if (!do_sio) ++ do_sio = ((mode & (S_ISUID | S_ISGID)) ++ && !capable(CAP_FSETID)); ++ /* this workaround may be removed in the future */ ++ if (!do_sio) { ++ h_dir = au_pinned_h_dir(pin); ++ do_sio = h_dir->i_mode & S_ISVTX; ++ } ++ } ++ ++ return do_sio; ++} ++ ++#if 0 /* reserved */ ++int au_sio_cpup_single(struct au_cp_generic *cpg, struct dentry *dst_parent) ++{ ++ int err, wkq_err; ++ struct dentry *h_dentry; ++ ++ h_dentry = au_h_dptr(cpg->dentry, cpg->bsrc); ++ if (!au_cpup_sio_test(pin, h_dentry->d_inode->i_mode)) ++ err = au_cpup_single(cpg, dst_parent); ++ else { ++ struct au_cpup_single_args args = { ++ .errp = &err, ++ .cpg = cpg, ++ .dst_parent = dst_parent ++ }; ++ wkq_err = au_wkq_wait(au_call_cpup_single, &args); ++ if (unlikely(wkq_err)) ++ err = wkq_err; ++ } ++ ++ return err; ++} ++#endif ++ ++/* ++ * copyup the @dentry from the first active lower branch to @bdst, ++ * using au_cpup_single(). ++ */ ++static int au_cpup_simple(struct au_cp_generic *cpg) ++{ ++ int err; ++ unsigned int flags_orig; ++ struct dentry *dentry; ++ ++ AuDebugOn(cpg->bsrc < 0); ++ ++ dentry = cpg->dentry; ++ DiMustWriteLock(dentry); ++ ++ err = au_lkup_neg(dentry, cpg->bdst, /*wh*/1); ++ if (!err) { ++ flags_orig = cpg->flags; ++ au_fset_cpup(cpg->flags, RENAME); ++ err = au_cpup_single(cpg, NULL); ++ cpg->flags = flags_orig; ++ if (!err) ++ return 0; /* success */ ++ ++ /* revert */ ++ au_set_h_dptr(dentry, cpg->bdst, NULL); ++ au_set_dbstart(dentry, cpg->bsrc); ++ } ++ ++ return err; ++} ++ ++struct au_cpup_simple_args { ++ int *errp; ++ struct au_cp_generic *cpg; ++}; ++ ++static void au_call_cpup_simple(void *args) ++{ ++ struct au_cpup_simple_args *a = args; ++ ++ au_pin_hdir_acquire_nest(a->cpg->pin); ++ *a->errp = au_cpup_simple(a->cpg); ++ au_pin_hdir_release(a->cpg->pin); ++} ++ ++static int au_do_sio_cpup_simple(struct au_cp_generic *cpg) ++{ ++ int err, wkq_err; ++ struct dentry *dentry, *parent; ++ struct file *h_file; ++ struct inode *h_dir; ++ ++ dentry = cpg->dentry; ++ h_file = NULL; ++ if (au_ftest_cpup(cpg->flags, HOPEN)) { ++ AuDebugOn(cpg->bsrc < 0); ++ h_file = au_h_open_pre(dentry, cpg->bsrc, /*force_wr*/0); ++ err = PTR_ERR(h_file); ++ if (IS_ERR(h_file)) ++ goto out; ++ } ++ ++ parent = dget_parent(dentry); ++ h_dir = au_h_iptr(parent->d_inode, cpg->bdst); ++ if (!au_test_h_perm_sio(h_dir, MAY_EXEC | MAY_WRITE) ++ && !au_cpup_sio_test(cpg->pin, dentry->d_inode->i_mode)) ++ err = au_cpup_simple(cpg); ++ else { ++ struct au_cpup_simple_args args = { ++ .errp = &err, ++ .cpg = cpg ++ }; ++ wkq_err = au_wkq_wait(au_call_cpup_simple, &args); ++ if (unlikely(wkq_err)) ++ err = wkq_err; ++ } ++ ++ dput(parent); ++ if (h_file) ++ au_h_open_post(dentry, cpg->bsrc, h_file); ++ ++out: ++ return err; ++} ++ ++int au_sio_cpup_simple(struct au_cp_generic *cpg) ++{ ++ aufs_bindex_t bsrc, bend; ++ struct dentry *dentry, *h_dentry; ++ ++ if (cpg->bsrc < 0) { ++ dentry = cpg->dentry; ++ bend = au_dbend(dentry); ++ for (bsrc = cpg->bdst + 1; bsrc <= bend; bsrc++) { ++ h_dentry = au_h_dptr(dentry, bsrc); ++ if (h_dentry) { ++ AuDebugOn(!h_dentry->d_inode); ++ break; ++ } ++ } ++ AuDebugOn(bsrc > bend); ++ cpg->bsrc = bsrc; ++ } ++ AuDebugOn(cpg->bsrc <= cpg->bdst); ++ return au_do_sio_cpup_simple(cpg); ++} ++ ++int au_sio_cpdown_simple(struct au_cp_generic *cpg) ++{ ++ AuDebugOn(cpg->bdst <= cpg->bsrc); ++ return au_do_sio_cpup_simple(cpg); ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* ++ * copyup the deleted file for writing. ++ */ ++static int au_do_cpup_wh(struct au_cp_generic *cpg, struct dentry *wh_dentry, ++ struct file *file) ++{ ++ int err; ++ unsigned int flags_orig; ++ aufs_bindex_t bsrc_orig; ++ struct dentry *h_d_dst, *h_d_start; ++ struct au_dinfo *dinfo; ++ struct au_hdentry *hdp; ++ ++ dinfo = au_di(cpg->dentry); ++ AuRwMustWriteLock(&dinfo->di_rwsem); ++ ++ bsrc_orig = cpg->bsrc; ++ cpg->bsrc = dinfo->di_bstart; ++ hdp = dinfo->di_hdentry; ++ h_d_dst = hdp[0 + cpg->bdst].hd_dentry; ++ dinfo->di_bstart = cpg->bdst; ++ hdp[0 + cpg->bdst].hd_dentry = wh_dentry; ++ h_d_start = NULL; ++ if (file) { ++ h_d_start = hdp[0 + cpg->bsrc].hd_dentry; ++ hdp[0 + cpg->bsrc].hd_dentry = au_hf_top(file)->f_dentry; ++ } ++ flags_orig = cpg->flags; ++ cpg->flags = !AuCpup_DTIME; ++ err = au_cpup_single(cpg, /*h_parent*/NULL); ++ cpg->flags = flags_orig; ++ if (file) { ++ if (!err) ++ err = au_reopen_nondir(file); ++ hdp[0 + cpg->bsrc].hd_dentry = h_d_start; ++ } ++ hdp[0 + cpg->bdst].hd_dentry = h_d_dst; ++ dinfo->di_bstart = cpg->bsrc; ++ cpg->bsrc = bsrc_orig; ++ ++ return err; ++} ++ ++static int au_cpup_wh(struct au_cp_generic *cpg, struct file *file) ++{ ++ int err; ++ aufs_bindex_t bdst; ++ struct au_dtime dt; ++ struct dentry *dentry, *parent, *h_parent, *wh_dentry; ++ struct au_branch *br; ++ struct path h_path; ++ ++ dentry = cpg->dentry; ++ bdst = cpg->bdst; ++ br = au_sbr(dentry->d_sb, bdst); ++ parent = dget_parent(dentry); ++ h_parent = au_h_dptr(parent, bdst); ++ wh_dentry = au_whtmp_lkup(h_parent, br, &dentry->d_name); ++ err = PTR_ERR(wh_dentry); ++ if (IS_ERR(wh_dentry)) ++ goto out; ++ ++ h_path.dentry = h_parent; ++ h_path.mnt = au_br_mnt(br); ++ au_dtime_store(&dt, parent, &h_path); ++ err = au_do_cpup_wh(cpg, wh_dentry, file); ++ if (unlikely(err)) ++ goto out_wh; ++ ++ dget(wh_dentry); ++ h_path.dentry = wh_dentry; ++ if (!d_is_dir(wh_dentry)) { ++ /* no delegation since it is just created */ ++ err = vfsub_unlink(h_parent->d_inode, &h_path, ++ /*delegated*/NULL, /*force*/0); ++ } else ++ err = vfsub_rmdir(h_parent->d_inode, &h_path); ++ if (unlikely(err)) { ++ AuIOErr("failed remove copied-up tmp file %pd(%d)\n", ++ wh_dentry, err); ++ err = -EIO; ++ } ++ au_dtime_revert(&dt); ++ au_set_hi_wh(dentry->d_inode, bdst, wh_dentry); ++ ++out_wh: ++ dput(wh_dentry); ++out: ++ dput(parent); ++ return err; ++} ++ ++struct au_cpup_wh_args { ++ int *errp; ++ struct au_cp_generic *cpg; ++ struct file *file; ++}; ++ ++static void au_call_cpup_wh(void *args) ++{ ++ struct au_cpup_wh_args *a = args; ++ ++ au_pin_hdir_acquire_nest(a->cpg->pin); ++ *a->errp = au_cpup_wh(a->cpg, a->file); ++ au_pin_hdir_release(a->cpg->pin); ++} ++ ++int au_sio_cpup_wh(struct au_cp_generic *cpg, struct file *file) ++{ ++ int err, wkq_err; ++ aufs_bindex_t bdst; ++ struct dentry *dentry, *parent, *h_orph, *h_parent; ++ struct inode *dir, *h_dir, *h_tmpdir; ++ struct au_wbr *wbr; ++ struct au_pin wh_pin, *pin_orig; ++ ++ dentry = cpg->dentry; ++ bdst = cpg->bdst; ++ parent = dget_parent(dentry); ++ dir = parent->d_inode; ++ h_orph = NULL; ++ h_parent = NULL; ++ h_dir = au_igrab(au_h_iptr(dir, bdst)); ++ h_tmpdir = h_dir; ++ pin_orig = NULL; ++ if (!h_dir->i_nlink) { ++ wbr = au_sbr(dentry->d_sb, bdst)->br_wbr; ++ h_orph = wbr->wbr_orph; ++ ++ h_parent = dget(au_h_dptr(parent, bdst)); ++ au_set_h_dptr(parent, bdst, dget(h_orph)); ++ h_tmpdir = h_orph->d_inode; ++ au_set_h_iptr(dir, bdst, au_igrab(h_tmpdir), /*flags*/0); ++ ++ mutex_lock_nested(&h_tmpdir->i_mutex, AuLsc_I_PARENT3); ++ /* todo: au_h_open_pre()? */ ++ ++ pin_orig = cpg->pin; ++ au_pin_init(&wh_pin, dentry, bdst, AuLsc_DI_PARENT, ++ AuLsc_I_PARENT3, cpg->pin->udba, AuPin_DI_LOCKED); ++ cpg->pin = &wh_pin; ++ } ++ ++ if (!au_test_h_perm_sio(h_tmpdir, MAY_EXEC | MAY_WRITE) ++ && !au_cpup_sio_test(cpg->pin, dentry->d_inode->i_mode)) ++ err = au_cpup_wh(cpg, file); ++ else { ++ struct au_cpup_wh_args args = { ++ .errp = &err, ++ .cpg = cpg, ++ .file = file ++ }; ++ wkq_err = au_wkq_wait(au_call_cpup_wh, &args); ++ if (unlikely(wkq_err)) ++ err = wkq_err; ++ } ++ ++ if (h_orph) { ++ mutex_unlock(&h_tmpdir->i_mutex); ++ /* todo: au_h_open_post()? */ ++ au_set_h_iptr(dir, bdst, au_igrab(h_dir), /*flags*/0); ++ au_set_h_dptr(parent, bdst, h_parent); ++ AuDebugOn(!pin_orig); ++ cpg->pin = pin_orig; ++ } ++ iput(h_dir); ++ dput(parent); ++ ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* ++ * generic routine for both of copy-up and copy-down. ++ */ ++/* cf. revalidate function in file.c */ ++int au_cp_dirs(struct dentry *dentry, aufs_bindex_t bdst, ++ int (*cp)(struct dentry *dentry, aufs_bindex_t bdst, ++ struct au_pin *pin, ++ struct dentry *h_parent, void *arg), ++ void *arg) ++{ ++ int err; ++ struct au_pin pin; ++ struct dentry *d, *parent, *h_parent, *real_parent; ++ ++ err = 0; ++ parent = dget_parent(dentry); ++ if (IS_ROOT(parent)) ++ goto out; ++ ++ au_pin_init(&pin, dentry, bdst, AuLsc_DI_PARENT2, AuLsc_I_PARENT2, ++ au_opt_udba(dentry->d_sb), AuPin_MNT_WRITE); ++ ++ /* do not use au_dpage */ ++ real_parent = parent; ++ while (1) { ++ dput(parent); ++ parent = dget_parent(dentry); ++ h_parent = au_h_dptr(parent, bdst); ++ if (h_parent) ++ goto out; /* success */ ++ ++ /* find top dir which is necessary to cpup */ ++ do { ++ d = parent; ++ dput(parent); ++ parent = dget_parent(d); ++ di_read_lock_parent3(parent, !AuLock_IR); ++ h_parent = au_h_dptr(parent, bdst); ++ di_read_unlock(parent, !AuLock_IR); ++ } while (!h_parent); ++ ++ if (d != real_parent) ++ di_write_lock_child3(d); ++ ++ /* somebody else might create while we were sleeping */ ++ if (!au_h_dptr(d, bdst) || !au_h_dptr(d, bdst)->d_inode) { ++ if (au_h_dptr(d, bdst)) ++ au_update_dbstart(d); ++ ++ au_pin_set_dentry(&pin, d); ++ err = au_do_pin(&pin); ++ if (!err) { ++ err = cp(d, bdst, &pin, h_parent, arg); ++ au_unpin(&pin); ++ } ++ } ++ ++ if (d != real_parent) ++ di_write_unlock(d); ++ if (unlikely(err)) ++ break; ++ } ++ ++out: ++ dput(parent); ++ return err; ++} ++ ++static int au_cpup_dir(struct dentry *dentry, aufs_bindex_t bdst, ++ struct au_pin *pin, ++ struct dentry *h_parent __maybe_unused, ++ void *arg __maybe_unused) ++{ ++ struct au_cp_generic cpg = { ++ .dentry = dentry, ++ .bdst = bdst, ++ .bsrc = -1, ++ .len = 0, ++ .pin = pin, ++ .flags = AuCpup_DTIME ++ }; ++ return au_sio_cpup_simple(&cpg); ++} ++ ++int au_cpup_dirs(struct dentry *dentry, aufs_bindex_t bdst) ++{ ++ return au_cp_dirs(dentry, bdst, au_cpup_dir, NULL); ++} ++ ++int au_test_and_cpup_dirs(struct dentry *dentry, aufs_bindex_t bdst) ++{ ++ int err; ++ struct dentry *parent; ++ struct inode *dir; ++ ++ parent = dget_parent(dentry); ++ dir = parent->d_inode; ++ err = 0; ++ if (au_h_iptr(dir, bdst)) ++ goto out; ++ ++ di_read_unlock(parent, AuLock_IR); ++ di_write_lock_parent(parent); ++ /* someone else might change our inode while we were sleeping */ ++ if (!au_h_iptr(dir, bdst)) ++ err = au_cpup_dirs(dentry, bdst); ++ di_downgrade_lock(parent, AuLock_IR); ++ ++out: ++ dput(parent); ++ return err; ++} +diff --git a/fs/aufs/cpup.h b/fs/aufs/cpup.h +new file mode 100644 +index 0000000..7721429 +--- /dev/null ++++ b/fs/aufs/cpup.h +@@ -0,0 +1,94 @@ ++/* ++ * Copyright (C) 2005-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * copy-up/down functions ++ */ ++ ++#ifndef __AUFS_CPUP_H__ ++#define __AUFS_CPUP_H__ ++ ++#ifdef __KERNEL__ ++ ++#include ++ ++struct inode; ++struct file; ++struct au_pin; ++ ++void au_cpup_attr_flags(struct inode *dst, unsigned int iflags); ++void au_cpup_attr_timesizes(struct inode *inode); ++void au_cpup_attr_nlink(struct inode *inode, int force); ++void au_cpup_attr_changeable(struct inode *inode); ++void au_cpup_igen(struct inode *inode, struct inode *h_inode); ++void au_cpup_attr_all(struct inode *inode, int force); ++ ++/* ---------------------------------------------------------------------- */ ++ ++struct au_cp_generic { ++ struct dentry *dentry; ++ aufs_bindex_t bdst, bsrc; ++ loff_t len; ++ struct au_pin *pin; ++ unsigned int flags; ++}; ++ ++/* cpup flags */ ++#define AuCpup_DTIME 1 /* do dtime_store/revert */ ++#define AuCpup_KEEPLINO (1 << 1) /* do not clear the lower xino, ++ for link(2) */ ++#define AuCpup_RENAME (1 << 2) /* rename after cpup */ ++#define AuCpup_HOPEN (1 << 3) /* call h_open_pre/post() in ++ cpup */ ++#define AuCpup_OVERWRITE (1 << 4) /* allow overwriting the ++ existing entry */ ++#define AuCpup_RWDST (1 << 5) /* force write target even if ++ the branch is marked as RO */ ++ ++#define au_ftest_cpup(flags, name) ((flags) & AuCpup_##name) ++#define au_fset_cpup(flags, name) \ ++ do { (flags) |= AuCpup_##name; } while (0) ++#define au_fclr_cpup(flags, name) \ ++ do { (flags) &= ~AuCpup_##name; } while (0) ++ ++int au_copy_file(struct file *dst, struct file *src, loff_t len); ++int au_sio_cpup_simple(struct au_cp_generic *cpg); ++int au_sio_cpdown_simple(struct au_cp_generic *cpg); ++int au_sio_cpup_wh(struct au_cp_generic *cpg, struct file *file); ++ ++int au_cp_dirs(struct dentry *dentry, aufs_bindex_t bdst, ++ int (*cp)(struct dentry *dentry, aufs_bindex_t bdst, ++ struct au_pin *pin, ++ struct dentry *h_parent, void *arg), ++ void *arg); ++int au_cpup_dirs(struct dentry *dentry, aufs_bindex_t bdst); ++int au_test_and_cpup_dirs(struct dentry *dentry, aufs_bindex_t bdst); ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* keep timestamps when copyup */ ++struct au_dtime { ++ struct dentry *dt_dentry; ++ struct path dt_h_path; ++ struct timespec dt_atime, dt_mtime; ++}; ++void au_dtime_store(struct au_dtime *dt, struct dentry *dentry, ++ struct path *h_path); ++void au_dtime_revert(struct au_dtime *dt); ++ ++#endif /* __KERNEL__ */ ++#endif /* __AUFS_CPUP_H__ */ +diff --git a/fs/aufs/dbgaufs.c b/fs/aufs/dbgaufs.c +new file mode 100644 +index 0000000..b4fdc25 +--- /dev/null ++++ b/fs/aufs/dbgaufs.c +@@ -0,0 +1,432 @@ ++/* ++ * Copyright (C) 2005-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * debugfs interface ++ */ ++ ++#include ++#include "aufs.h" ++ ++#ifndef CONFIG_SYSFS ++#error DEBUG_FS depends upon SYSFS ++#endif ++ ++static struct dentry *dbgaufs; ++static const mode_t dbgaufs_mode = S_IRUSR | S_IRGRP | S_IROTH; ++ ++/* 20 is max digits length of ulong 64 */ ++struct dbgaufs_arg { ++ int n; ++ char a[20 * 4]; ++}; ++ ++/* ++ * common function for all XINO files ++ */ ++static int dbgaufs_xi_release(struct inode *inode __maybe_unused, ++ struct file *file) ++{ ++ kfree(file->private_data); ++ return 0; ++} ++ ++static int dbgaufs_xi_open(struct file *xf, struct file *file, int do_fcnt) ++{ ++ int err; ++ struct kstat st; ++ struct dbgaufs_arg *p; ++ ++ err = -ENOMEM; ++ p = kmalloc(sizeof(*p), GFP_NOFS); ++ if (unlikely(!p)) ++ goto out; ++ ++ err = 0; ++ p->n = 0; ++ file->private_data = p; ++ if (!xf) ++ goto out; ++ ++ err = vfs_getattr(&xf->f_path, &st); ++ if (!err) { ++ if (do_fcnt) ++ p->n = snprintf ++ (p->a, sizeof(p->a), "%ld, %llux%lu %lld\n", ++ (long)file_count(xf), st.blocks, st.blksize, ++ (long long)st.size); ++ else ++ p->n = snprintf(p->a, sizeof(p->a), "%llux%lu %lld\n", ++ st.blocks, st.blksize, ++ (long long)st.size); ++ AuDebugOn(p->n >= sizeof(p->a)); ++ } else { ++ p->n = snprintf(p->a, sizeof(p->a), "err %d\n", err); ++ err = 0; ++ } ++ ++out: ++ return err; ++ ++} ++ ++static ssize_t dbgaufs_xi_read(struct file *file, char __user *buf, ++ size_t count, loff_t *ppos) ++{ ++ struct dbgaufs_arg *p; ++ ++ p = file->private_data; ++ return simple_read_from_buffer(buf, count, ppos, p->a, p->n); ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++struct dbgaufs_plink_arg { ++ int n; ++ char a[]; ++}; ++ ++static int dbgaufs_plink_release(struct inode *inode __maybe_unused, ++ struct file *file) ++{ ++ free_page((unsigned long)file->private_data); ++ return 0; ++} ++ ++static int dbgaufs_plink_open(struct inode *inode, struct file *file) ++{ ++ int err, i, limit; ++ unsigned long n, sum; ++ struct dbgaufs_plink_arg *p; ++ struct au_sbinfo *sbinfo; ++ struct super_block *sb; ++ struct au_sphlhead *sphl; ++ ++ err = -ENOMEM; ++ p = (void *)get_zeroed_page(GFP_NOFS); ++ if (unlikely(!p)) ++ goto out; ++ ++ err = -EFBIG; ++ sbinfo = inode->i_private; ++ sb = sbinfo->si_sb; ++ si_noflush_read_lock(sb); ++ if (au_opt_test(au_mntflags(sb), PLINK)) { ++ limit = PAGE_SIZE - sizeof(p->n); ++ ++ /* the number of buckets */ ++ n = snprintf(p->a + p->n, limit, "%d\n", AuPlink_NHASH); ++ p->n += n; ++ limit -= n; ++ ++ sum = 0; ++ for (i = 0, sphl = sbinfo->si_plink; ++ i < AuPlink_NHASH; ++ i++, sphl++) { ++ n = au_sphl_count(sphl); ++ sum += n; ++ ++ n = snprintf(p->a + p->n, limit, "%lu ", n); ++ p->n += n; ++ limit -= n; ++ if (unlikely(limit <= 0)) ++ goto out_free; ++ } ++ p->a[p->n - 1] = '\n'; ++ ++ /* the sum of plinks */ ++ n = snprintf(p->a + p->n, limit, "%lu\n", sum); ++ p->n += n; ++ limit -= n; ++ if (unlikely(limit <= 0)) ++ goto out_free; ++ } else { ++#define str "1\n0\n0\n" ++ p->n = sizeof(str) - 1; ++ strcpy(p->a, str); ++#undef str ++ } ++ si_read_unlock(sb); ++ ++ err = 0; ++ file->private_data = p; ++ goto out; /* success */ ++ ++out_free: ++ free_page((unsigned long)p); ++out: ++ return err; ++} ++ ++static ssize_t dbgaufs_plink_read(struct file *file, char __user *buf, ++ size_t count, loff_t *ppos) ++{ ++ struct dbgaufs_plink_arg *p; ++ ++ p = file->private_data; ++ return simple_read_from_buffer(buf, count, ppos, p->a, p->n); ++} ++ ++static const struct file_operations dbgaufs_plink_fop = { ++ .owner = THIS_MODULE, ++ .open = dbgaufs_plink_open, ++ .release = dbgaufs_plink_release, ++ .read = dbgaufs_plink_read ++}; ++ ++/* ---------------------------------------------------------------------- */ ++ ++static int dbgaufs_xib_open(struct inode *inode, struct file *file) ++{ ++ int err; ++ struct au_sbinfo *sbinfo; ++ struct super_block *sb; ++ ++ sbinfo = inode->i_private; ++ sb = sbinfo->si_sb; ++ si_noflush_read_lock(sb); ++ err = dbgaufs_xi_open(sbinfo->si_xib, file, /*do_fcnt*/0); ++ si_read_unlock(sb); ++ return err; ++} ++ ++static const struct file_operations dbgaufs_xib_fop = { ++ .owner = THIS_MODULE, ++ .open = dbgaufs_xib_open, ++ .release = dbgaufs_xi_release, ++ .read = dbgaufs_xi_read ++}; ++ ++/* ---------------------------------------------------------------------- */ ++ ++#define DbgaufsXi_PREFIX "xi" ++ ++static int dbgaufs_xino_open(struct inode *inode, struct file *file) ++{ ++ int err; ++ long l; ++ struct au_sbinfo *sbinfo; ++ struct super_block *sb; ++ struct file *xf; ++ struct qstr *name; ++ ++ err = -ENOENT; ++ xf = NULL; ++ name = &file->f_dentry->d_name; ++ if (unlikely(name->len < sizeof(DbgaufsXi_PREFIX) ++ || memcmp(name->name, DbgaufsXi_PREFIX, ++ sizeof(DbgaufsXi_PREFIX) - 1))) ++ goto out; ++ err = kstrtol(name->name + sizeof(DbgaufsXi_PREFIX) - 1, 10, &l); ++ if (unlikely(err)) ++ goto out; ++ ++ sbinfo = inode->i_private; ++ sb = sbinfo->si_sb; ++ si_noflush_read_lock(sb); ++ if (l <= au_sbend(sb)) { ++ xf = au_sbr(sb, (aufs_bindex_t)l)->br_xino.xi_file; ++ err = dbgaufs_xi_open(xf, file, /*do_fcnt*/1); ++ } else ++ err = -ENOENT; ++ si_read_unlock(sb); ++ ++out: ++ return err; ++} ++ ++static const struct file_operations dbgaufs_xino_fop = { ++ .owner = THIS_MODULE, ++ .open = dbgaufs_xino_open, ++ .release = dbgaufs_xi_release, ++ .read = dbgaufs_xi_read ++}; ++ ++void dbgaufs_brs_del(struct super_block *sb, aufs_bindex_t bindex) ++{ ++ aufs_bindex_t bend; ++ struct au_branch *br; ++ struct au_xino_file *xi; ++ ++ if (!au_sbi(sb)->si_dbgaufs) ++ return; ++ ++ bend = au_sbend(sb); ++ for (; bindex <= bend; bindex++) { ++ br = au_sbr(sb, bindex); ++ xi = &br->br_xino; ++ debugfs_remove(xi->xi_dbgaufs); ++ xi->xi_dbgaufs = NULL; ++ } ++} ++ ++void dbgaufs_brs_add(struct super_block *sb, aufs_bindex_t bindex) ++{ ++ struct au_sbinfo *sbinfo; ++ struct dentry *parent; ++ struct au_branch *br; ++ struct au_xino_file *xi; ++ aufs_bindex_t bend; ++ char name[sizeof(DbgaufsXi_PREFIX) + 5]; /* "xi" bindex NULL */ ++ ++ sbinfo = au_sbi(sb); ++ parent = sbinfo->si_dbgaufs; ++ if (!parent) ++ return; ++ ++ bend = au_sbend(sb); ++ for (; bindex <= bend; bindex++) { ++ snprintf(name, sizeof(name), DbgaufsXi_PREFIX "%d", bindex); ++ br = au_sbr(sb, bindex); ++ xi = &br->br_xino; ++ AuDebugOn(xi->xi_dbgaufs); ++ xi->xi_dbgaufs = debugfs_create_file(name, dbgaufs_mode, parent, ++ sbinfo, &dbgaufs_xino_fop); ++ /* ignore an error */ ++ if (unlikely(!xi->xi_dbgaufs)) ++ AuWarn1("failed %s under debugfs\n", name); ++ } ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++#ifdef CONFIG_AUFS_EXPORT ++static int dbgaufs_xigen_open(struct inode *inode, struct file *file) ++{ ++ int err; ++ struct au_sbinfo *sbinfo; ++ struct super_block *sb; ++ ++ sbinfo = inode->i_private; ++ sb = sbinfo->si_sb; ++ si_noflush_read_lock(sb); ++ err = dbgaufs_xi_open(sbinfo->si_xigen, file, /*do_fcnt*/0); ++ si_read_unlock(sb); ++ return err; ++} ++ ++static const struct file_operations dbgaufs_xigen_fop = { ++ .owner = THIS_MODULE, ++ .open = dbgaufs_xigen_open, ++ .release = dbgaufs_xi_release, ++ .read = dbgaufs_xi_read ++}; ++ ++static int dbgaufs_xigen_init(struct au_sbinfo *sbinfo) ++{ ++ int err; ++ ++ /* ++ * This function is a dynamic '__init' function actually, ++ * so the tiny check for si_rwsem is unnecessary. ++ */ ++ /* AuRwMustWriteLock(&sbinfo->si_rwsem); */ ++ ++ err = -EIO; ++ sbinfo->si_dbgaufs_xigen = debugfs_create_file ++ ("xigen", dbgaufs_mode, sbinfo->si_dbgaufs, sbinfo, ++ &dbgaufs_xigen_fop); ++ if (sbinfo->si_dbgaufs_xigen) ++ err = 0; ++ ++ return err; ++} ++#else ++static int dbgaufs_xigen_init(struct au_sbinfo *sbinfo) ++{ ++ return 0; ++} ++#endif /* CONFIG_AUFS_EXPORT */ ++ ++/* ---------------------------------------------------------------------- */ ++ ++void dbgaufs_si_fin(struct au_sbinfo *sbinfo) ++{ ++ /* ++ * This function is a dynamic '__fin' function actually, ++ * so the tiny check for si_rwsem is unnecessary. ++ */ ++ /* AuRwMustWriteLock(&sbinfo->si_rwsem); */ ++ ++ debugfs_remove_recursive(sbinfo->si_dbgaufs); ++ sbinfo->si_dbgaufs = NULL; ++ kobject_put(&sbinfo->si_kobj); ++} ++ ++int dbgaufs_si_init(struct au_sbinfo *sbinfo) ++{ ++ int err; ++ char name[SysaufsSiNameLen]; ++ ++ /* ++ * This function is a dynamic '__init' function actually, ++ * so the tiny check for si_rwsem is unnecessary. ++ */ ++ /* AuRwMustWriteLock(&sbinfo->si_rwsem); */ ++ ++ err = -ENOENT; ++ if (!dbgaufs) { ++ AuErr1("/debug/aufs is uninitialized\n"); ++ goto out; ++ } ++ ++ err = -EIO; ++ sysaufs_name(sbinfo, name); ++ sbinfo->si_dbgaufs = debugfs_create_dir(name, dbgaufs); ++ if (unlikely(!sbinfo->si_dbgaufs)) ++ goto out; ++ kobject_get(&sbinfo->si_kobj); ++ ++ sbinfo->si_dbgaufs_xib = debugfs_create_file ++ ("xib", dbgaufs_mode, sbinfo->si_dbgaufs, sbinfo, ++ &dbgaufs_xib_fop); ++ if (unlikely(!sbinfo->si_dbgaufs_xib)) ++ goto out_dir; ++ ++ sbinfo->si_dbgaufs_plink = debugfs_create_file ++ ("plink", dbgaufs_mode, sbinfo->si_dbgaufs, sbinfo, ++ &dbgaufs_plink_fop); ++ if (unlikely(!sbinfo->si_dbgaufs_plink)) ++ goto out_dir; ++ ++ err = dbgaufs_xigen_init(sbinfo); ++ if (!err) ++ goto out; /* success */ ++ ++out_dir: ++ dbgaufs_si_fin(sbinfo); ++out: ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++void dbgaufs_fin(void) ++{ ++ debugfs_remove(dbgaufs); ++} ++ ++int __init dbgaufs_init(void) ++{ ++ int err; ++ ++ err = -EIO; ++ dbgaufs = debugfs_create_dir(AUFS_NAME, NULL); ++ if (dbgaufs) ++ err = 0; ++ return err; ++} +diff --git a/fs/aufs/dbgaufs.h b/fs/aufs/dbgaufs.h +new file mode 100644 +index 0000000..d1e09bd +--- /dev/null ++++ b/fs/aufs/dbgaufs.h +@@ -0,0 +1,48 @@ ++/* ++ * Copyright (C) 2005-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * debugfs interface ++ */ ++ ++#ifndef __DBGAUFS_H__ ++#define __DBGAUFS_H__ ++ ++#ifdef __KERNEL__ ++ ++struct super_block; ++struct au_sbinfo; ++ ++#ifdef CONFIG_DEBUG_FS ++/* dbgaufs.c */ ++void dbgaufs_brs_del(struct super_block *sb, aufs_bindex_t bindex); ++void dbgaufs_brs_add(struct super_block *sb, aufs_bindex_t bindex); ++void dbgaufs_si_fin(struct au_sbinfo *sbinfo); ++int dbgaufs_si_init(struct au_sbinfo *sbinfo); ++void dbgaufs_fin(void); ++int __init dbgaufs_init(void); ++#else ++AuStubVoid(dbgaufs_brs_del, struct super_block *sb, aufs_bindex_t bindex) ++AuStubVoid(dbgaufs_brs_add, struct super_block *sb, aufs_bindex_t bindex) ++AuStubVoid(dbgaufs_si_fin, struct au_sbinfo *sbinfo) ++AuStubInt0(dbgaufs_si_init, struct au_sbinfo *sbinfo) ++AuStubVoid(dbgaufs_fin, void) ++AuStubInt0(__init dbgaufs_init, void) ++#endif /* CONFIG_DEBUG_FS */ ++ ++#endif /* __KERNEL__ */ ++#endif /* __DBGAUFS_H__ */ +diff --git a/fs/aufs/dcsub.c b/fs/aufs/dcsub.c +new file mode 100644 +index 0000000..832baa4 +--- /dev/null ++++ b/fs/aufs/dcsub.c +@@ -0,0 +1,224 @@ ++/* ++ * Copyright (C) 2005-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * sub-routines for dentry cache ++ */ ++ ++#include "aufs.h" ++ ++static void au_dpage_free(struct au_dpage *dpage) ++{ ++ int i; ++ struct dentry **p; ++ ++ p = dpage->dentries; ++ for (i = 0; i < dpage->ndentry; i++) ++ dput(*p++); ++ free_page((unsigned long)dpage->dentries); ++} ++ ++int au_dpages_init(struct au_dcsub_pages *dpages, gfp_t gfp) ++{ ++ int err; ++ void *p; ++ ++ err = -ENOMEM; ++ dpages->dpages = kmalloc(sizeof(*dpages->dpages), gfp); ++ if (unlikely(!dpages->dpages)) ++ goto out; ++ ++ p = (void *)__get_free_page(gfp); ++ if (unlikely(!p)) ++ goto out_dpages; ++ ++ dpages->dpages[0].ndentry = 0; ++ dpages->dpages[0].dentries = p; ++ dpages->ndpage = 1; ++ return 0; /* success */ ++ ++out_dpages: ++ kfree(dpages->dpages); ++out: ++ return err; ++} ++ ++void au_dpages_free(struct au_dcsub_pages *dpages) ++{ ++ int i; ++ struct au_dpage *p; ++ ++ p = dpages->dpages; ++ for (i = 0; i < dpages->ndpage; i++) ++ au_dpage_free(p++); ++ kfree(dpages->dpages); ++} ++ ++static int au_dpages_append(struct au_dcsub_pages *dpages, ++ struct dentry *dentry, gfp_t gfp) ++{ ++ int err, sz; ++ struct au_dpage *dpage; ++ void *p; ++ ++ dpage = dpages->dpages + dpages->ndpage - 1; ++ sz = PAGE_SIZE / sizeof(dentry); ++ if (unlikely(dpage->ndentry >= sz)) { ++ AuLabel(new dpage); ++ err = -ENOMEM; ++ sz = dpages->ndpage * sizeof(*dpages->dpages); ++ p = au_kzrealloc(dpages->dpages, sz, ++ sz + sizeof(*dpages->dpages), gfp); ++ if (unlikely(!p)) ++ goto out; ++ ++ dpages->dpages = p; ++ dpage = dpages->dpages + dpages->ndpage; ++ p = (void *)__get_free_page(gfp); ++ if (unlikely(!p)) ++ goto out; ++ ++ dpage->ndentry = 0; ++ dpage->dentries = p; ++ dpages->ndpage++; ++ } ++ ++ AuDebugOn(au_dcount(dentry) <= 0); ++ dpage->dentries[dpage->ndentry++] = dget_dlock(dentry); ++ return 0; /* success */ ++ ++out: ++ return err; ++} ++ ++/* todo: BAD approach */ ++/* copied from linux/fs/dcache.c */ ++enum d_walk_ret { ++ D_WALK_CONTINUE, ++ D_WALK_QUIT, ++ D_WALK_NORETRY, ++ D_WALK_SKIP, ++}; ++ ++extern void d_walk(struct dentry *parent, void *data, ++ enum d_walk_ret (*enter)(void *, struct dentry *), ++ void (*finish)(void *)); ++ ++struct ac_dpages_arg { ++ int err; ++ struct au_dcsub_pages *dpages; ++ struct super_block *sb; ++ au_dpages_test test; ++ void *arg; ++}; ++ ++static enum d_walk_ret au_call_dpages_append(void *_arg, struct dentry *dentry) ++{ ++ enum d_walk_ret ret; ++ struct ac_dpages_arg *arg = _arg; ++ ++ ret = D_WALK_CONTINUE; ++ if (dentry->d_sb == arg->sb ++ && !IS_ROOT(dentry) ++ && au_dcount(dentry) > 0 ++ && au_di(dentry) ++ && (!arg->test || arg->test(dentry, arg->arg))) { ++ arg->err = au_dpages_append(arg->dpages, dentry, GFP_ATOMIC); ++ if (unlikely(arg->err)) ++ ret = D_WALK_QUIT; ++ } ++ ++ return ret; ++} ++ ++int au_dcsub_pages(struct au_dcsub_pages *dpages, struct dentry *root, ++ au_dpages_test test, void *arg) ++{ ++ struct ac_dpages_arg args = { ++ .err = 0, ++ .dpages = dpages, ++ .sb = root->d_sb, ++ .test = test, ++ .arg = arg ++ }; ++ ++ d_walk(root, &args, au_call_dpages_append, NULL); ++ ++ return args.err; ++} ++ ++int au_dcsub_pages_rev(struct au_dcsub_pages *dpages, struct dentry *dentry, ++ int do_include, au_dpages_test test, void *arg) ++{ ++ int err; ++ ++ err = 0; ++ write_seqlock(&rename_lock); ++ spin_lock(&dentry->d_lock); ++ if (do_include ++ && au_dcount(dentry) > 0 ++ && (!test || test(dentry, arg))) ++ err = au_dpages_append(dpages, dentry, GFP_ATOMIC); ++ spin_unlock(&dentry->d_lock); ++ if (unlikely(err)) ++ goto out; ++ ++ /* ++ * RCU for vfsmount is unnecessary since this is a traverse in a single ++ * mount ++ */ ++ while (!IS_ROOT(dentry)) { ++ dentry = dentry->d_parent; /* rename_lock is locked */ ++ spin_lock(&dentry->d_lock); ++ if (au_dcount(dentry) > 0 ++ && (!test || test(dentry, arg))) ++ err = au_dpages_append(dpages, dentry, GFP_ATOMIC); ++ spin_unlock(&dentry->d_lock); ++ if (unlikely(err)) ++ break; ++ } ++ ++out: ++ write_sequnlock(&rename_lock); ++ return err; ++} ++ ++static inline int au_dcsub_dpages_aufs(struct dentry *dentry, void *arg) ++{ ++ return au_di(dentry) && dentry->d_sb == arg; ++} ++ ++int au_dcsub_pages_rev_aufs(struct au_dcsub_pages *dpages, ++ struct dentry *dentry, int do_include) ++{ ++ return au_dcsub_pages_rev(dpages, dentry, do_include, ++ au_dcsub_dpages_aufs, dentry->d_sb); ++} ++ ++int au_test_subdir(struct dentry *d1, struct dentry *d2) ++{ ++ struct path path[2] = { ++ { ++ .dentry = d1 ++ }, ++ { ++ .dentry = d2 ++ } ++ }; ++ ++ return path_is_under(path + 0, path + 1); ++} +diff --git a/fs/aufs/dcsub.h b/fs/aufs/dcsub.h +new file mode 100644 +index 0000000..7997944 +--- /dev/null ++++ b/fs/aufs/dcsub.h +@@ -0,0 +1,123 @@ ++/* ++ * Copyright (C) 2005-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * sub-routines for dentry cache ++ */ ++ ++#ifndef __AUFS_DCSUB_H__ ++#define __AUFS_DCSUB_H__ ++ ++#ifdef __KERNEL__ ++ ++#include ++#include ++ ++struct au_dpage { ++ int ndentry; ++ struct dentry **dentries; ++}; ++ ++struct au_dcsub_pages { ++ int ndpage; ++ struct au_dpage *dpages; ++}; ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* dcsub.c */ ++int au_dpages_init(struct au_dcsub_pages *dpages, gfp_t gfp); ++void au_dpages_free(struct au_dcsub_pages *dpages); ++typedef int (*au_dpages_test)(struct dentry *dentry, void *arg); ++int au_dcsub_pages(struct au_dcsub_pages *dpages, struct dentry *root, ++ au_dpages_test test, void *arg); ++int au_dcsub_pages_rev(struct au_dcsub_pages *dpages, struct dentry *dentry, ++ int do_include, au_dpages_test test, void *arg); ++int au_dcsub_pages_rev_aufs(struct au_dcsub_pages *dpages, ++ struct dentry *dentry, int do_include); ++int au_test_subdir(struct dentry *d1, struct dentry *d2); ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* ++ * todo: in linux-3.13, several similar (but faster) helpers are added to ++ * include/linux/dcache.h. Try them (in the future). ++ */ ++ ++static inline int au_d_hashed_positive(struct dentry *d) ++{ ++ int err; ++ struct inode *inode = d->d_inode; ++ ++ err = 0; ++ if (unlikely(d_unhashed(d) || !inode || !inode->i_nlink)) ++ err = -ENOENT; ++ return err; ++} ++ ++static inline int au_d_linkable(struct dentry *d) ++{ ++ int err; ++ struct inode *inode = d->d_inode; ++ ++ err = au_d_hashed_positive(d); ++ if (err ++ && inode ++ && (inode->i_state & I_LINKABLE)) ++ err = 0; ++ return err; ++} ++ ++static inline int au_d_alive(struct dentry *d) ++{ ++ int err; ++ struct inode *inode; ++ ++ err = 0; ++ if (!IS_ROOT(d)) ++ err = au_d_hashed_positive(d); ++ else { ++ inode = d->d_inode; ++ if (unlikely(d_unlinked(d) || !inode || !inode->i_nlink)) ++ err = -ENOENT; ++ } ++ return err; ++} ++ ++static inline int au_alive_dir(struct dentry *d) ++{ ++ int err; ++ ++ err = au_d_alive(d); ++ if (unlikely(err || IS_DEADDIR(d->d_inode))) ++ err = -ENOENT; ++ return err; ++} ++ ++static inline int au_qstreq(struct qstr *a, struct qstr *b) ++{ ++ return a->len == b->len ++ && !memcmp(a->name, b->name, a->len); ++} ++ ++static inline int au_dcount(struct dentry *d) ++{ ++ return (int)d_count(d); ++} ++ ++#endif /* __KERNEL__ */ ++#endif /* __AUFS_DCSUB_H__ */ +diff --git a/fs/aufs/debug.c b/fs/aufs/debug.c +new file mode 100644 +index 0000000..2747d13 +--- /dev/null ++++ b/fs/aufs/debug.c +@@ -0,0 +1,436 @@ ++/* ++ * Copyright (C) 2005-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * debug print functions ++ */ ++ ++#include "aufs.h" ++ ++/* Returns 0, or -errno. arg is in kp->arg. */ ++static int param_atomic_t_set(const char *val, const struct kernel_param *kp) ++{ ++ int err, n; ++ ++ err = kstrtoint(val, 0, &n); ++ if (!err) { ++ if (n > 0) ++ au_debug_on(); ++ else ++ au_debug_off(); ++ } ++ return err; ++} ++ ++/* Returns length written or -errno. Buffer is 4k (ie. be short!) */ ++static int param_atomic_t_get(char *buffer, const struct kernel_param *kp) ++{ ++ atomic_t *a; ++ ++ a = kp->arg; ++ return sprintf(buffer, "%d", atomic_read(a)); ++} ++ ++static struct kernel_param_ops param_ops_atomic_t = { ++ .set = param_atomic_t_set, ++ .get = param_atomic_t_get ++ /* void (*free)(void *arg) */ ++}; ++ ++atomic_t aufs_debug = ATOMIC_INIT(0); ++MODULE_PARM_DESC(debug, "debug print"); ++module_param_named(debug, aufs_debug, atomic_t, S_IRUGO | S_IWUSR | S_IWGRP); ++ ++DEFINE_MUTEX(au_dbg_mtx); /* just to serialize the dbg msgs */ ++char *au_plevel = KERN_DEBUG; ++#define dpri(fmt, ...) do { \ ++ if ((au_plevel \ ++ && strcmp(au_plevel, KERN_DEBUG)) \ ++ || au_debug_test()) \ ++ printk("%s" fmt, au_plevel, ##__VA_ARGS__); \ ++} while (0) ++ ++/* ---------------------------------------------------------------------- */ ++ ++void au_dpri_whlist(struct au_nhash *whlist) ++{ ++ unsigned long ul, n; ++ struct hlist_head *head; ++ struct au_vdir_wh *pos; ++ ++ n = whlist->nh_num; ++ head = whlist->nh_head; ++ for (ul = 0; ul < n; ul++) { ++ hlist_for_each_entry(pos, head, wh_hash) ++ dpri("b%d, %.*s, %d\n", ++ pos->wh_bindex, ++ pos->wh_str.len, pos->wh_str.name, ++ pos->wh_str.len); ++ head++; ++ } ++} ++ ++void au_dpri_vdir(struct au_vdir *vdir) ++{ ++ unsigned long ul; ++ union au_vdir_deblk_p p; ++ unsigned char *o; ++ ++ if (!vdir || IS_ERR(vdir)) { ++ dpri("err %ld\n", PTR_ERR(vdir)); ++ return; ++ } ++ ++ dpri("deblk %u, nblk %lu, deblk %p, last{%lu, %p}, ver %lu\n", ++ vdir->vd_deblk_sz, vdir->vd_nblk, vdir->vd_deblk, ++ vdir->vd_last.ul, vdir->vd_last.p.deblk, vdir->vd_version); ++ for (ul = 0; ul < vdir->vd_nblk; ul++) { ++ p.deblk = vdir->vd_deblk[ul]; ++ o = p.deblk; ++ dpri("[%lu]: %p\n", ul, o); ++ } ++} ++ ++static int do_pri_inode(aufs_bindex_t bindex, struct inode *inode, int hn, ++ struct dentry *wh) ++{ ++ char *n = NULL; ++ int l = 0; ++ ++ if (!inode || IS_ERR(inode)) { ++ dpri("i%d: err %ld\n", bindex, PTR_ERR(inode)); ++ return -1; ++ } ++ ++ /* the type of i_blocks depends upon CONFIG_LBDAF */ ++ BUILD_BUG_ON(sizeof(inode->i_blocks) != sizeof(unsigned long) ++ && sizeof(inode->i_blocks) != sizeof(u64)); ++ if (wh) { ++ n = (void *)wh->d_name.name; ++ l = wh->d_name.len; ++ } ++ ++ dpri("i%d: %p, i%lu, %s, cnt %d, nl %u, 0%o, sz %llu, blk %llu," ++ " hn %d, ct %lld, np %lu, st 0x%lx, f 0x%x, v %llu, g %x%s%.*s\n", ++ bindex, inode, ++ inode->i_ino, inode->i_sb ? au_sbtype(inode->i_sb) : "??", ++ atomic_read(&inode->i_count), inode->i_nlink, inode->i_mode, ++ i_size_read(inode), (unsigned long long)inode->i_blocks, ++ hn, (long long)timespec_to_ns(&inode->i_ctime) & 0x0ffff, ++ inode->i_mapping ? inode->i_mapping->nrpages : 0, ++ inode->i_state, inode->i_flags, inode->i_version, ++ inode->i_generation, ++ l ? ", wh " : "", l, n); ++ return 0; ++} ++ ++void au_dpri_inode(struct inode *inode) ++{ ++ struct au_iinfo *iinfo; ++ aufs_bindex_t bindex; ++ int err, hn; ++ ++ err = do_pri_inode(-1, inode, -1, NULL); ++ if (err || !au_test_aufs(inode->i_sb)) ++ return; ++ ++ iinfo = au_ii(inode); ++ if (!iinfo) ++ return; ++ dpri("i-1: bstart %d, bend %d, gen %d\n", ++ iinfo->ii_bstart, iinfo->ii_bend, au_iigen(inode, NULL)); ++ if (iinfo->ii_bstart < 0) ++ return; ++ hn = 0; ++ for (bindex = iinfo->ii_bstart; bindex <= iinfo->ii_bend; bindex++) { ++ hn = !!au_hn(iinfo->ii_hinode + bindex); ++ do_pri_inode(bindex, iinfo->ii_hinode[0 + bindex].hi_inode, hn, ++ iinfo->ii_hinode[0 + bindex].hi_whdentry); ++ } ++} ++ ++void au_dpri_dalias(struct inode *inode) ++{ ++ struct dentry *d; ++ ++ spin_lock(&inode->i_lock); ++ hlist_for_each_entry(d, &inode->i_dentry, d_u.d_alias) ++ au_dpri_dentry(d); ++ spin_unlock(&inode->i_lock); ++} ++ ++static int do_pri_dentry(aufs_bindex_t bindex, struct dentry *dentry) ++{ ++ struct dentry *wh = NULL; ++ int hn; ++ struct au_iinfo *iinfo; ++ ++ if (!dentry || IS_ERR(dentry)) { ++ dpri("d%d: err %ld\n", bindex, PTR_ERR(dentry)); ++ return -1; ++ } ++ /* do not call dget_parent() here */ ++ /* note: access d_xxx without d_lock */ ++ dpri("d%d: %p, %pd2?, %s, cnt %d, flags 0x%x, %shashed\n", ++ bindex, dentry, dentry, ++ dentry->d_sb ? au_sbtype(dentry->d_sb) : "??", ++ au_dcount(dentry), dentry->d_flags, ++ d_unhashed(dentry) ? "un" : ""); ++ hn = -1; ++ if (bindex >= 0 && dentry->d_inode && au_test_aufs(dentry->d_sb)) { ++ iinfo = au_ii(dentry->d_inode); ++ if (iinfo) { ++ hn = !!au_hn(iinfo->ii_hinode + bindex); ++ wh = iinfo->ii_hinode[0 + bindex].hi_whdentry; ++ } ++ } ++ do_pri_inode(bindex, dentry->d_inode, hn, wh); ++ return 0; ++} ++ ++void au_dpri_dentry(struct dentry *dentry) ++{ ++ struct au_dinfo *dinfo; ++ aufs_bindex_t bindex; ++ int err; ++ struct au_hdentry *hdp; ++ ++ err = do_pri_dentry(-1, dentry); ++ if (err || !au_test_aufs(dentry->d_sb)) ++ return; ++ ++ dinfo = au_di(dentry); ++ if (!dinfo) ++ return; ++ dpri("d-1: bstart %d, bend %d, bwh %d, bdiropq %d, gen %d, tmp %d\n", ++ dinfo->di_bstart, dinfo->di_bend, ++ dinfo->di_bwh, dinfo->di_bdiropq, au_digen(dentry), ++ dinfo->di_tmpfile); ++ if (dinfo->di_bstart < 0) ++ return; ++ hdp = dinfo->di_hdentry; ++ for (bindex = dinfo->di_bstart; bindex <= dinfo->di_bend; bindex++) ++ do_pri_dentry(bindex, hdp[0 + bindex].hd_dentry); ++} ++ ++static int do_pri_file(aufs_bindex_t bindex, struct file *file) ++{ ++ char a[32]; ++ ++ if (!file || IS_ERR(file)) { ++ dpri("f%d: err %ld\n", bindex, PTR_ERR(file)); ++ return -1; ++ } ++ a[0] = 0; ++ if (bindex < 0 ++ && !IS_ERR_OR_NULL(file->f_dentry) ++ && au_test_aufs(file->f_dentry->d_sb) ++ && au_fi(file)) ++ snprintf(a, sizeof(a), ", gen %d, mmapped %d", ++ au_figen(file), atomic_read(&au_fi(file)->fi_mmapped)); ++ dpri("f%d: mode 0x%x, flags 0%o, cnt %ld, v %llu, pos %llu%s\n", ++ bindex, file->f_mode, file->f_flags, (long)file_count(file), ++ file->f_version, file->f_pos, a); ++ if (!IS_ERR_OR_NULL(file->f_dentry)) ++ do_pri_dentry(bindex, file->f_dentry); ++ return 0; ++} ++ ++void au_dpri_file(struct file *file) ++{ ++ struct au_finfo *finfo; ++ struct au_fidir *fidir; ++ struct au_hfile *hfile; ++ aufs_bindex_t bindex; ++ int err; ++ ++ err = do_pri_file(-1, file); ++ if (err ++ || IS_ERR_OR_NULL(file->f_dentry) ++ || !au_test_aufs(file->f_dentry->d_sb)) ++ return; ++ ++ finfo = au_fi(file); ++ if (!finfo) ++ return; ++ if (finfo->fi_btop < 0) ++ return; ++ fidir = finfo->fi_hdir; ++ if (!fidir) ++ do_pri_file(finfo->fi_btop, finfo->fi_htop.hf_file); ++ else ++ for (bindex = finfo->fi_btop; ++ bindex >= 0 && bindex <= fidir->fd_bbot; ++ bindex++) { ++ hfile = fidir->fd_hfile + bindex; ++ do_pri_file(bindex, hfile ? hfile->hf_file : NULL); ++ } ++} ++ ++static int do_pri_br(aufs_bindex_t bindex, struct au_branch *br) ++{ ++ struct vfsmount *mnt; ++ struct super_block *sb; ++ ++ if (!br || IS_ERR(br)) ++ goto out; ++ mnt = au_br_mnt(br); ++ if (!mnt || IS_ERR(mnt)) ++ goto out; ++ sb = mnt->mnt_sb; ++ if (!sb || IS_ERR(sb)) ++ goto out; ++ ++ dpri("s%d: {perm 0x%x, id %d, cnt %d, wbr %p}, " ++ "%s, dev 0x%02x%02x, flags 0x%lx, cnt %d, active %d, " ++ "xino %d\n", ++ bindex, br->br_perm, br->br_id, atomic_read(&br->br_count), ++ br->br_wbr, au_sbtype(sb), MAJOR(sb->s_dev), MINOR(sb->s_dev), ++ sb->s_flags, sb->s_count, ++ atomic_read(&sb->s_active), !!br->br_xino.xi_file); ++ return 0; ++ ++out: ++ dpri("s%d: err %ld\n", bindex, PTR_ERR(br)); ++ return -1; ++} ++ ++void au_dpri_sb(struct super_block *sb) ++{ ++ struct au_sbinfo *sbinfo; ++ aufs_bindex_t bindex; ++ int err; ++ /* to reuduce stack size */ ++ struct { ++ struct vfsmount mnt; ++ struct au_branch fake; ++ } *a; ++ ++ /* this function can be called from magic sysrq */ ++ a = kzalloc(sizeof(*a), GFP_ATOMIC); ++ if (unlikely(!a)) { ++ dpri("no memory\n"); ++ return; ++ } ++ ++ a->mnt.mnt_sb = sb; ++ a->fake.br_path.mnt = &a->mnt; ++ atomic_set(&a->fake.br_count, 0); ++ smp_mb(); /* atomic_set */ ++ err = do_pri_br(-1, &a->fake); ++ kfree(a); ++ dpri("dev 0x%x\n", sb->s_dev); ++ if (err || !au_test_aufs(sb)) ++ return; ++ ++ sbinfo = au_sbi(sb); ++ if (!sbinfo) ++ return; ++ dpri("nw %d, gen %u, kobj %d\n", ++ atomic_read(&sbinfo->si_nowait.nw_len), sbinfo->si_generation, ++ atomic_read(&sbinfo->si_kobj.kref.refcount)); ++ for (bindex = 0; bindex <= sbinfo->si_bend; bindex++) ++ do_pri_br(bindex, sbinfo->si_branch[0 + bindex]); ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++void __au_dbg_verify_dinode(struct dentry *dentry, const char *func, int line) ++{ ++ struct inode *h_inode, *inode = dentry->d_inode; ++ struct dentry *h_dentry; ++ aufs_bindex_t bindex, bend, bi; ++ ++ if (!inode /* || au_di(dentry)->di_lsc == AuLsc_DI_TMP */) ++ return; ++ ++ bend = au_dbend(dentry); ++ bi = au_ibend(inode); ++ if (bi < bend) ++ bend = bi; ++ bindex = au_dbstart(dentry); ++ bi = au_ibstart(inode); ++ if (bi > bindex) ++ bindex = bi; ++ ++ for (; bindex <= bend; bindex++) { ++ h_dentry = au_h_dptr(dentry, bindex); ++ if (!h_dentry) ++ continue; ++ h_inode = au_h_iptr(inode, bindex); ++ if (unlikely(h_inode != h_dentry->d_inode)) { ++ au_debug_on(); ++ AuDbg("b%d, %s:%d\n", bindex, func, line); ++ AuDbgDentry(dentry); ++ AuDbgInode(inode); ++ au_debug_off(); ++ BUG(); ++ } ++ } ++} ++ ++void au_dbg_verify_gen(struct dentry *parent, unsigned int sigen) ++{ ++ int err, i, j; ++ struct au_dcsub_pages dpages; ++ struct au_dpage *dpage; ++ struct dentry **dentries; ++ ++ err = au_dpages_init(&dpages, GFP_NOFS); ++ AuDebugOn(err); ++ err = au_dcsub_pages_rev_aufs(&dpages, parent, /*do_include*/1); ++ AuDebugOn(err); ++ for (i = dpages.ndpage - 1; !err && i >= 0; i--) { ++ dpage = dpages.dpages + i; ++ dentries = dpage->dentries; ++ for (j = dpage->ndentry - 1; !err && j >= 0; j--) ++ AuDebugOn(au_digen_test(dentries[j], sigen)); ++ } ++ au_dpages_free(&dpages); ++} ++ ++void au_dbg_verify_kthread(void) ++{ ++ if (au_wkq_test()) { ++ au_dbg_blocked(); ++ /* ++ * It may be recursive, but udba=notify between two aufs mounts, ++ * where a single ro branch is shared, is not a problem. ++ */ ++ /* WARN_ON(1); */ ++ } ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++int __init au_debug_init(void) ++{ ++ aufs_bindex_t bindex; ++ struct au_vdir_destr destr; ++ ++ bindex = -1; ++ AuDebugOn(bindex >= 0); ++ ++ destr.len = -1; ++ AuDebugOn(destr.len < NAME_MAX); ++ ++#ifdef CONFIG_4KSTACKS ++ pr_warn("CONFIG_4KSTACKS is defined.\n"); ++#endif ++ ++ return 0; ++} +diff --git a/fs/aufs/debug.h b/fs/aufs/debug.h +new file mode 100644 +index 0000000..039e6f8 +--- /dev/null ++++ b/fs/aufs/debug.h +@@ -0,0 +1,228 @@ ++/* ++ * Copyright (C) 2005-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * debug print functions ++ */ ++ ++#ifndef __AUFS_DEBUG_H__ ++#define __AUFS_DEBUG_H__ ++ ++#ifdef __KERNEL__ ++ ++#include ++#include ++#include ++#include ++ ++#ifdef CONFIG_AUFS_DEBUG ++#define AuDebugOn(a) BUG_ON(a) ++ ++/* module parameter */ ++extern atomic_t aufs_debug; ++static inline void au_debug_on(void) ++{ ++ atomic_inc(&aufs_debug); ++} ++static inline void au_debug_off(void) ++{ ++ atomic_dec_if_positive(&aufs_debug); ++} ++ ++static inline int au_debug_test(void) ++{ ++ return atomic_read(&aufs_debug) > 0; ++} ++#else ++#define AuDebugOn(a) do {} while (0) ++AuStubVoid(au_debug_on, void) ++AuStubVoid(au_debug_off, void) ++AuStubInt0(au_debug_test, void) ++#endif /* CONFIG_AUFS_DEBUG */ ++ ++#define param_check_atomic_t(name, p) __param_check(name, p, atomic_t) ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* debug print */ ++ ++#define AuDbg(fmt, ...) do { \ ++ if (au_debug_test()) \ ++ pr_debug("DEBUG: " fmt, ##__VA_ARGS__); \ ++} while (0) ++#define AuLabel(l) AuDbg(#l "\n") ++#define AuIOErr(fmt, ...) pr_err("I/O Error, " fmt, ##__VA_ARGS__) ++#define AuWarn1(fmt, ...) do { \ ++ static unsigned char _c; \ ++ if (!_c++) \ ++ pr_warn(fmt, ##__VA_ARGS__); \ ++} while (0) ++ ++#define AuErr1(fmt, ...) do { \ ++ static unsigned char _c; \ ++ if (!_c++) \ ++ pr_err(fmt, ##__VA_ARGS__); \ ++} while (0) ++ ++#define AuIOErr1(fmt, ...) do { \ ++ static unsigned char _c; \ ++ if (!_c++) \ ++ AuIOErr(fmt, ##__VA_ARGS__); \ ++} while (0) ++ ++#define AuUnsupportMsg "This operation is not supported." \ ++ " Please report this application to aufs-users ML." ++#define AuUnsupport(fmt, ...) do { \ ++ pr_err(AuUnsupportMsg "\n" fmt, ##__VA_ARGS__); \ ++ dump_stack(); \ ++} while (0) ++ ++#define AuTraceErr(e) do { \ ++ if (unlikely((e) < 0)) \ ++ AuDbg("err %d\n", (int)(e)); \ ++} while (0) ++ ++#define AuTraceErrPtr(p) do { \ ++ if (IS_ERR(p)) \ ++ AuDbg("err %ld\n", PTR_ERR(p)); \ ++} while (0) ++ ++/* dirty macros for debug print, use with "%.*s" and caution */ ++#define AuLNPair(qstr) (qstr)->len, (qstr)->name ++ ++/* ---------------------------------------------------------------------- */ ++ ++struct dentry; ++#ifdef CONFIG_AUFS_DEBUG ++extern struct mutex au_dbg_mtx; ++extern char *au_plevel; ++struct au_nhash; ++void au_dpri_whlist(struct au_nhash *whlist); ++struct au_vdir; ++void au_dpri_vdir(struct au_vdir *vdir); ++struct inode; ++void au_dpri_inode(struct inode *inode); ++void au_dpri_dalias(struct inode *inode); ++void au_dpri_dentry(struct dentry *dentry); ++struct file; ++void au_dpri_file(struct file *filp); ++struct super_block; ++void au_dpri_sb(struct super_block *sb); ++ ++#define au_dbg_verify_dinode(d) __au_dbg_verify_dinode(d, __func__, __LINE__) ++void __au_dbg_verify_dinode(struct dentry *dentry, const char *func, int line); ++void au_dbg_verify_gen(struct dentry *parent, unsigned int sigen); ++void au_dbg_verify_kthread(void); ++ ++int __init au_debug_init(void); ++ ++#define AuDbgWhlist(w) do { \ ++ mutex_lock(&au_dbg_mtx); \ ++ AuDbg(#w "\n"); \ ++ au_dpri_whlist(w); \ ++ mutex_unlock(&au_dbg_mtx); \ ++} while (0) ++ ++#define AuDbgVdir(v) do { \ ++ mutex_lock(&au_dbg_mtx); \ ++ AuDbg(#v "\n"); \ ++ au_dpri_vdir(v); \ ++ mutex_unlock(&au_dbg_mtx); \ ++} while (0) ++ ++#define AuDbgInode(i) do { \ ++ mutex_lock(&au_dbg_mtx); \ ++ AuDbg(#i "\n"); \ ++ au_dpri_inode(i); \ ++ mutex_unlock(&au_dbg_mtx); \ ++} while (0) ++ ++#define AuDbgDAlias(i) do { \ ++ mutex_lock(&au_dbg_mtx); \ ++ AuDbg(#i "\n"); \ ++ au_dpri_dalias(i); \ ++ mutex_unlock(&au_dbg_mtx); \ ++} while (0) ++ ++#define AuDbgDentry(d) do { \ ++ mutex_lock(&au_dbg_mtx); \ ++ AuDbg(#d "\n"); \ ++ au_dpri_dentry(d); \ ++ mutex_unlock(&au_dbg_mtx); \ ++} while (0) ++ ++#define AuDbgFile(f) do { \ ++ mutex_lock(&au_dbg_mtx); \ ++ AuDbg(#f "\n"); \ ++ au_dpri_file(f); \ ++ mutex_unlock(&au_dbg_mtx); \ ++} while (0) ++ ++#define AuDbgSb(sb) do { \ ++ mutex_lock(&au_dbg_mtx); \ ++ AuDbg(#sb "\n"); \ ++ au_dpri_sb(sb); \ ++ mutex_unlock(&au_dbg_mtx); \ ++} while (0) ++ ++#define AuDbgSym(addr) do { \ ++ char sym[KSYM_SYMBOL_LEN]; \ ++ sprint_symbol(sym, (unsigned long)addr); \ ++ AuDbg("%s\n", sym); \ ++} while (0) ++#else ++AuStubVoid(au_dbg_verify_dinode, struct dentry *dentry) ++AuStubVoid(au_dbg_verify_dir_parent, struct dentry *dentry, unsigned int sigen) ++AuStubVoid(au_dbg_verify_nondir_parent, struct dentry *dentry, ++ unsigned int sigen) ++AuStubVoid(au_dbg_verify_gen, struct dentry *parent, unsigned int sigen) ++AuStubVoid(au_dbg_verify_kthread, void) ++AuStubInt0(__init au_debug_init, void) ++ ++#define AuDbgWhlist(w) do {} while (0) ++#define AuDbgVdir(v) do {} while (0) ++#define AuDbgInode(i) do {} while (0) ++#define AuDbgDAlias(i) do {} while (0) ++#define AuDbgDentry(d) do {} while (0) ++#define AuDbgFile(f) do {} while (0) ++#define AuDbgSb(sb) do {} while (0) ++#define AuDbgSym(addr) do {} while (0) ++#endif /* CONFIG_AUFS_DEBUG */ ++ ++/* ---------------------------------------------------------------------- */ ++ ++#ifdef CONFIG_AUFS_MAGIC_SYSRQ ++int __init au_sysrq_init(void); ++void au_sysrq_fin(void); ++ ++#ifdef CONFIG_HW_CONSOLE ++#define au_dbg_blocked() do { \ ++ WARN_ON(1); \ ++ handle_sysrq('w'); \ ++} while (0) ++#else ++AuStubVoid(au_dbg_blocked, void) ++#endif ++ ++#else ++AuStubInt0(__init au_sysrq_init, void) ++AuStubVoid(au_sysrq_fin, void) ++AuStubVoid(au_dbg_blocked, void) ++#endif /* CONFIG_AUFS_MAGIC_SYSRQ */ ++ ++#endif /* __KERNEL__ */ ++#endif /* __AUFS_DEBUG_H__ */ +diff --git a/fs/aufs/dentry.c b/fs/aufs/dentry.c +new file mode 100644 +index 0000000..ed56947 +--- /dev/null ++++ b/fs/aufs/dentry.c +@@ -0,0 +1,1129 @@ ++/* ++ * Copyright (C) 2005-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * lookup and dentry operations ++ */ ++ ++#include ++#include "aufs.h" ++ ++#define AuLkup_ALLOW_NEG 1 ++#define AuLkup_IGNORE_PERM (1 << 1) ++#define au_ftest_lkup(flags, name) ((flags) & AuLkup_##name) ++#define au_fset_lkup(flags, name) \ ++ do { (flags) |= AuLkup_##name; } while (0) ++#define au_fclr_lkup(flags, name) \ ++ do { (flags) &= ~AuLkup_##name; } while (0) ++ ++struct au_do_lookup_args { ++ unsigned int flags; ++ mode_t type; ++}; ++ ++/* ++ * returns positive/negative dentry, NULL or an error. ++ * NULL means whiteout-ed or not-found. ++ */ ++static struct dentry* ++au_do_lookup(struct dentry *h_parent, struct dentry *dentry, ++ aufs_bindex_t bindex, struct qstr *wh_name, ++ struct au_do_lookup_args *args) ++{ ++ struct dentry *h_dentry; ++ struct inode *h_inode; ++ struct au_branch *br; ++ int wh_found, opq; ++ unsigned char wh_able; ++ const unsigned char allow_neg = !!au_ftest_lkup(args->flags, ALLOW_NEG); ++ const unsigned char ignore_perm = !!au_ftest_lkup(args->flags, ++ IGNORE_PERM); ++ ++ wh_found = 0; ++ br = au_sbr(dentry->d_sb, bindex); ++ wh_able = !!au_br_whable(br->br_perm); ++ if (wh_able) ++ wh_found = au_wh_test(h_parent, wh_name, /*try_sio*/0); ++ h_dentry = ERR_PTR(wh_found); ++ if (!wh_found) ++ goto real_lookup; ++ if (unlikely(wh_found < 0)) ++ goto out; ++ ++ /* We found a whiteout */ ++ /* au_set_dbend(dentry, bindex); */ ++ au_set_dbwh(dentry, bindex); ++ if (!allow_neg) ++ return NULL; /* success */ ++ ++real_lookup: ++ if (!ignore_perm) ++ h_dentry = vfsub_lkup_one(&dentry->d_name, h_parent); ++ else ++ h_dentry = au_sio_lkup_one(&dentry->d_name, h_parent); ++ if (IS_ERR(h_dentry)) { ++ if (PTR_ERR(h_dentry) == -ENAMETOOLONG ++ && !allow_neg) ++ h_dentry = NULL; ++ goto out; ++ } ++ ++ h_inode = h_dentry->d_inode; ++ if (!h_inode) { ++ if (!allow_neg) ++ goto out_neg; ++ } else if (wh_found ++ || (args->type && args->type != (h_inode->i_mode & S_IFMT))) ++ goto out_neg; ++ ++ if (au_dbend(dentry) <= bindex) ++ au_set_dbend(dentry, bindex); ++ if (au_dbstart(dentry) < 0 || bindex < au_dbstart(dentry)) ++ au_set_dbstart(dentry, bindex); ++ au_set_h_dptr(dentry, bindex, h_dentry); ++ ++ if (!d_is_dir(h_dentry) ++ || !wh_able ++ || (d_is_positive(dentry) && !d_is_dir(dentry))) ++ goto out; /* success */ ++ ++ mutex_lock_nested(&h_inode->i_mutex, AuLsc_I_CHILD); ++ opq = au_diropq_test(h_dentry); ++ mutex_unlock(&h_inode->i_mutex); ++ if (opq > 0) ++ au_set_dbdiropq(dentry, bindex); ++ else if (unlikely(opq < 0)) { ++ au_set_h_dptr(dentry, bindex, NULL); ++ h_dentry = ERR_PTR(opq); ++ } ++ goto out; ++ ++out_neg: ++ dput(h_dentry); ++ h_dentry = NULL; ++out: ++ return h_dentry; ++} ++ ++static int au_test_shwh(struct super_block *sb, const struct qstr *name) ++{ ++ if (unlikely(!au_opt_test(au_mntflags(sb), SHWH) ++ && !strncmp(name->name, AUFS_WH_PFX, AUFS_WH_PFX_LEN))) ++ return -EPERM; ++ return 0; ++} ++ ++/* ++ * returns the number of lower positive dentries, ++ * otherwise an error. ++ * can be called at unlinking with @type is zero. ++ */ ++int au_lkup_dentry(struct dentry *dentry, aufs_bindex_t bstart, mode_t type) ++{ ++ int npositive, err; ++ aufs_bindex_t bindex, btail, bdiropq; ++ unsigned char isdir, dirperm1; ++ struct qstr whname; ++ struct au_do_lookup_args args = { ++ .flags = 0, ++ .type = type ++ }; ++ const struct qstr *name = &dentry->d_name; ++ struct dentry *parent; ++ struct inode *inode; ++ struct super_block *sb; ++ ++ sb = dentry->d_sb; ++ err = au_test_shwh(sb, name); ++ if (unlikely(err)) ++ goto out; ++ ++ err = au_wh_name_alloc(&whname, name); ++ if (unlikely(err)) ++ goto out; ++ ++ inode = dentry->d_inode; ++ isdir = !!d_is_dir(dentry); ++ if (!type) ++ au_fset_lkup(args.flags, ALLOW_NEG); ++ dirperm1 = !!au_opt_test(au_mntflags(sb), DIRPERM1); ++ ++ npositive = 0; ++ parent = dget_parent(dentry); ++ btail = au_dbtaildir(parent); ++ for (bindex = bstart; bindex <= btail; bindex++) { ++ struct dentry *h_parent, *h_dentry; ++ struct inode *h_inode, *h_dir; ++ ++ h_dentry = au_h_dptr(dentry, bindex); ++ if (h_dentry) { ++ if (h_dentry->d_inode) ++ npositive++; ++ if (type != S_IFDIR) ++ break; ++ continue; ++ } ++ h_parent = au_h_dptr(parent, bindex); ++ if (!h_parent || !d_is_dir(h_parent)) ++ continue; ++ ++ h_dir = h_parent->d_inode; ++ mutex_lock_nested(&h_dir->i_mutex, AuLsc_I_PARENT); ++ h_dentry = au_do_lookup(h_parent, dentry, bindex, &whname, ++ &args); ++ mutex_unlock(&h_dir->i_mutex); ++ err = PTR_ERR(h_dentry); ++ if (IS_ERR(h_dentry)) ++ goto out_parent; ++ if (h_dentry) ++ au_fclr_lkup(args.flags, ALLOW_NEG); ++ if (dirperm1) ++ au_fset_lkup(args.flags, IGNORE_PERM); ++ ++ if (au_dbwh(dentry) == bindex) ++ break; ++ if (!h_dentry) ++ continue; ++ h_inode = h_dentry->d_inode; ++ if (!h_inode) ++ continue; ++ npositive++; ++ if (!args.type) ++ args.type = h_inode->i_mode & S_IFMT; ++ if (args.type != S_IFDIR) ++ break; ++ else if (isdir) { ++ /* the type of lower may be different */ ++ bdiropq = au_dbdiropq(dentry); ++ if (bdiropq >= 0 && bdiropq <= bindex) ++ break; ++ } ++ } ++ ++ if (npositive) { ++ AuLabel(positive); ++ au_update_dbstart(dentry); ++ } ++ err = npositive; ++ if (unlikely(!au_opt_test(au_mntflags(sb), UDBA_NONE) ++ && au_dbstart(dentry) < 0)) { ++ err = -EIO; ++ AuIOErr("both of real entry and whiteout found, %pd, err %d\n", ++ dentry, err); ++ } ++ ++out_parent: ++ dput(parent); ++ kfree(whname.name); ++out: ++ return err; ++} ++ ++struct dentry *au_sio_lkup_one(struct qstr *name, struct dentry *parent) ++{ ++ struct dentry *dentry; ++ int wkq_err; ++ ++ if (!au_test_h_perm_sio(parent->d_inode, MAY_EXEC)) ++ dentry = vfsub_lkup_one(name, parent); ++ else { ++ struct vfsub_lkup_one_args args = { ++ .errp = &dentry, ++ .name = name, ++ .parent = parent ++ }; ++ ++ wkq_err = au_wkq_wait(vfsub_call_lkup_one, &args); ++ if (unlikely(wkq_err)) ++ dentry = ERR_PTR(wkq_err); ++ } ++ ++ return dentry; ++} ++ ++/* ++ * lookup @dentry on @bindex which should be negative. ++ */ ++int au_lkup_neg(struct dentry *dentry, aufs_bindex_t bindex, int wh) ++{ ++ int err; ++ struct dentry *parent, *h_parent, *h_dentry; ++ struct au_branch *br; ++ ++ parent = dget_parent(dentry); ++ h_parent = au_h_dptr(parent, bindex); ++ br = au_sbr(dentry->d_sb, bindex); ++ if (wh) ++ h_dentry = au_whtmp_lkup(h_parent, br, &dentry->d_name); ++ else ++ h_dentry = au_sio_lkup_one(&dentry->d_name, h_parent); ++ err = PTR_ERR(h_dentry); ++ if (IS_ERR(h_dentry)) ++ goto out; ++ if (unlikely(h_dentry->d_inode)) { ++ err = -EIO; ++ AuIOErr("%pd should be negative on b%d.\n", h_dentry, bindex); ++ dput(h_dentry); ++ goto out; ++ } ++ ++ err = 0; ++ if (bindex < au_dbstart(dentry)) ++ au_set_dbstart(dentry, bindex); ++ if (au_dbend(dentry) < bindex) ++ au_set_dbend(dentry, bindex); ++ au_set_h_dptr(dentry, bindex, h_dentry); ++ ++out: ++ dput(parent); ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* subset of struct inode */ ++struct au_iattr { ++ unsigned long i_ino; ++ /* unsigned int i_nlink; */ ++ kuid_t i_uid; ++ kgid_t i_gid; ++ u64 i_version; ++/* ++ loff_t i_size; ++ blkcnt_t i_blocks; ++*/ ++ umode_t i_mode; ++}; ++ ++static void au_iattr_save(struct au_iattr *ia, struct inode *h_inode) ++{ ++ ia->i_ino = h_inode->i_ino; ++ /* ia->i_nlink = h_inode->i_nlink; */ ++ ia->i_uid = h_inode->i_uid; ++ ia->i_gid = h_inode->i_gid; ++ ia->i_version = h_inode->i_version; ++/* ++ ia->i_size = h_inode->i_size; ++ ia->i_blocks = h_inode->i_blocks; ++*/ ++ ia->i_mode = (h_inode->i_mode & S_IFMT); ++} ++ ++static int au_iattr_test(struct au_iattr *ia, struct inode *h_inode) ++{ ++ return ia->i_ino != h_inode->i_ino ++ /* || ia->i_nlink != h_inode->i_nlink */ ++ || !uid_eq(ia->i_uid, h_inode->i_uid) ++ || !gid_eq(ia->i_gid, h_inode->i_gid) ++ || ia->i_version != h_inode->i_version ++/* ++ || ia->i_size != h_inode->i_size ++ || ia->i_blocks != h_inode->i_blocks ++*/ ++ || ia->i_mode != (h_inode->i_mode & S_IFMT); ++} ++ ++static int au_h_verify_dentry(struct dentry *h_dentry, struct dentry *h_parent, ++ struct au_branch *br) ++{ ++ int err; ++ struct au_iattr ia; ++ struct inode *h_inode; ++ struct dentry *h_d; ++ struct super_block *h_sb; ++ ++ err = 0; ++ memset(&ia, -1, sizeof(ia)); ++ h_sb = h_dentry->d_sb; ++ h_inode = h_dentry->d_inode; ++ if (h_inode) ++ au_iattr_save(&ia, h_inode); ++ else if (au_test_nfs(h_sb) || au_test_fuse(h_sb)) ++ /* nfs d_revalidate may return 0 for negative dentry */ ++ /* fuse d_revalidate always return 0 for negative dentry */ ++ goto out; ++ ++ /* main purpose is namei.c:cached_lookup() and d_revalidate */ ++ h_d = vfsub_lkup_one(&h_dentry->d_name, h_parent); ++ err = PTR_ERR(h_d); ++ if (IS_ERR(h_d)) ++ goto out; ++ ++ err = 0; ++ if (unlikely(h_d != h_dentry ++ || h_d->d_inode != h_inode ++ || (h_inode && au_iattr_test(&ia, h_inode)))) ++ err = au_busy_or_stale(); ++ dput(h_d); ++ ++out: ++ AuTraceErr(err); ++ return err; ++} ++ ++int au_h_verify(struct dentry *h_dentry, unsigned int udba, struct inode *h_dir, ++ struct dentry *h_parent, struct au_branch *br) ++{ ++ int err; ++ ++ err = 0; ++ if (udba == AuOpt_UDBA_REVAL ++ && !au_test_fs_remote(h_dentry->d_sb)) { ++ IMustLock(h_dir); ++ err = (h_dentry->d_parent->d_inode != h_dir); ++ } else if (udba != AuOpt_UDBA_NONE) ++ err = au_h_verify_dentry(h_dentry, h_parent, br); ++ ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++static int au_do_refresh_hdentry(struct dentry *dentry, struct dentry *parent) ++{ ++ int err; ++ aufs_bindex_t new_bindex, bindex, bend, bwh, bdiropq; ++ struct au_hdentry tmp, *p, *q; ++ struct au_dinfo *dinfo; ++ struct super_block *sb; ++ ++ DiMustWriteLock(dentry); ++ ++ sb = dentry->d_sb; ++ dinfo = au_di(dentry); ++ bend = dinfo->di_bend; ++ bwh = dinfo->di_bwh; ++ bdiropq = dinfo->di_bdiropq; ++ p = dinfo->di_hdentry + dinfo->di_bstart; ++ for (bindex = dinfo->di_bstart; bindex <= bend; bindex++, p++) { ++ if (!p->hd_dentry) ++ continue; ++ ++ new_bindex = au_br_index(sb, p->hd_id); ++ if (new_bindex == bindex) ++ continue; ++ ++ if (dinfo->di_bwh == bindex) ++ bwh = new_bindex; ++ if (dinfo->di_bdiropq == bindex) ++ bdiropq = new_bindex; ++ if (new_bindex < 0) { ++ au_hdput(p); ++ p->hd_dentry = NULL; ++ continue; ++ } ++ ++ /* swap two lower dentries, and loop again */ ++ q = dinfo->di_hdentry + new_bindex; ++ tmp = *q; ++ *q = *p; ++ *p = tmp; ++ if (tmp.hd_dentry) { ++ bindex--; ++ p--; ++ } ++ } ++ ++ dinfo->di_bwh = -1; ++ if (bwh >= 0 && bwh <= au_sbend(sb) && au_sbr_whable(sb, bwh)) ++ dinfo->di_bwh = bwh; ++ ++ dinfo->di_bdiropq = -1; ++ if (bdiropq >= 0 ++ && bdiropq <= au_sbend(sb) ++ && au_sbr_whable(sb, bdiropq)) ++ dinfo->di_bdiropq = bdiropq; ++ ++ err = -EIO; ++ dinfo->di_bstart = -1; ++ dinfo->di_bend = -1; ++ bend = au_dbend(parent); ++ p = dinfo->di_hdentry; ++ for (bindex = 0; bindex <= bend; bindex++, p++) ++ if (p->hd_dentry) { ++ dinfo->di_bstart = bindex; ++ break; ++ } ++ ++ if (dinfo->di_bstart >= 0) { ++ p = dinfo->di_hdentry + bend; ++ for (bindex = bend; bindex >= 0; bindex--, p--) ++ if (p->hd_dentry) { ++ dinfo->di_bend = bindex; ++ err = 0; ++ break; ++ } ++ } ++ ++ return err; ++} ++ ++static void au_do_hide(struct dentry *dentry) ++{ ++ struct inode *inode; ++ ++ inode = dentry->d_inode; ++ if (inode) { ++ if (!S_ISDIR(inode->i_mode)) { ++ if (inode->i_nlink && !d_unhashed(dentry)) ++ drop_nlink(inode); ++ } else { ++ clear_nlink(inode); ++ /* stop next lookup */ ++ inode->i_flags |= S_DEAD; ++ } ++ smp_mb(); /* necessary? */ ++ } ++ d_drop(dentry); ++} ++ ++static int au_hide_children(struct dentry *parent) ++{ ++ int err, i, j, ndentry; ++ struct au_dcsub_pages dpages; ++ struct au_dpage *dpage; ++ struct dentry *dentry; ++ ++ err = au_dpages_init(&dpages, GFP_NOFS); ++ if (unlikely(err)) ++ goto out; ++ err = au_dcsub_pages(&dpages, parent, NULL, NULL); ++ if (unlikely(err)) ++ goto out_dpages; ++ ++ /* in reverse order */ ++ for (i = dpages.ndpage - 1; i >= 0; i--) { ++ dpage = dpages.dpages + i; ++ ndentry = dpage->ndentry; ++ for (j = ndentry - 1; j >= 0; j--) { ++ dentry = dpage->dentries[j]; ++ if (dentry != parent) ++ au_do_hide(dentry); ++ } ++ } ++ ++out_dpages: ++ au_dpages_free(&dpages); ++out: ++ return err; ++} ++ ++static void au_hide(struct dentry *dentry) ++{ ++ int err; ++ ++ AuDbgDentry(dentry); ++ if (d_is_dir(dentry)) { ++ /* shrink_dcache_parent(dentry); */ ++ err = au_hide_children(dentry); ++ if (unlikely(err)) ++ AuIOErr("%pd, failed hiding children, ignored %d\n", ++ dentry, err); ++ } ++ au_do_hide(dentry); ++} ++ ++/* ++ * By adding a dirty branch, a cached dentry may be affected in various ways. ++ * ++ * a dirty branch is added ++ * - on the top of layers ++ * - in the middle of layers ++ * - to the bottom of layers ++ * ++ * on the added branch there exists ++ * - a whiteout ++ * - a diropq ++ * - a same named entry ++ * + exist ++ * * negative --> positive ++ * * positive --> positive ++ * - type is unchanged ++ * - type is changed ++ * + doesn't exist ++ * * negative --> negative ++ * * positive --> negative (rejected by au_br_del() for non-dir case) ++ * - none ++ */ ++static int au_refresh_by_dinfo(struct dentry *dentry, struct au_dinfo *dinfo, ++ struct au_dinfo *tmp) ++{ ++ int err; ++ aufs_bindex_t bindex, bend; ++ struct { ++ struct dentry *dentry; ++ struct inode *inode; ++ mode_t mode; ++ } orig_h, tmp_h = { ++ .dentry = NULL ++ }; ++ struct au_hdentry *hd; ++ struct inode *inode, *h_inode; ++ struct dentry *h_dentry; ++ ++ err = 0; ++ AuDebugOn(dinfo->di_bstart < 0); ++ orig_h.dentry = dinfo->di_hdentry[dinfo->di_bstart].hd_dentry; ++ orig_h.inode = orig_h.dentry->d_inode; ++ orig_h.mode = 0; ++ if (orig_h.inode) ++ orig_h.mode = orig_h.inode->i_mode & S_IFMT; ++ if (tmp->di_bstart >= 0) { ++ tmp_h.dentry = tmp->di_hdentry[tmp->di_bstart].hd_dentry; ++ tmp_h.inode = tmp_h.dentry->d_inode; ++ if (tmp_h.inode) ++ tmp_h.mode = tmp_h.inode->i_mode & S_IFMT; ++ } ++ ++ inode = dentry->d_inode; ++ if (!orig_h.inode) { ++ AuDbg("nagative originally\n"); ++ if (inode) { ++ au_hide(dentry); ++ goto out; ++ } ++ AuDebugOn(inode); ++ AuDebugOn(dinfo->di_bstart != dinfo->di_bend); ++ AuDebugOn(dinfo->di_bdiropq != -1); ++ ++ if (!tmp_h.inode) { ++ AuDbg("negative --> negative\n"); ++ /* should have only one negative lower */ ++ if (tmp->di_bstart >= 0 ++ && tmp->di_bstart < dinfo->di_bstart) { ++ AuDebugOn(tmp->di_bstart != tmp->di_bend); ++ AuDebugOn(dinfo->di_bstart != dinfo->di_bend); ++ au_set_h_dptr(dentry, dinfo->di_bstart, NULL); ++ au_di_cp(dinfo, tmp); ++ hd = tmp->di_hdentry + tmp->di_bstart; ++ au_set_h_dptr(dentry, tmp->di_bstart, ++ dget(hd->hd_dentry)); ++ } ++ au_dbg_verify_dinode(dentry); ++ } else { ++ AuDbg("negative --> positive\n"); ++ /* ++ * similar to the behaviour of creating with bypassing ++ * aufs. ++ * unhash it in order to force an error in the ++ * succeeding create operation. ++ * we should not set S_DEAD here. ++ */ ++ d_drop(dentry); ++ /* au_di_swap(tmp, dinfo); */ ++ au_dbg_verify_dinode(dentry); ++ } ++ } else { ++ AuDbg("positive originally\n"); ++ /* inode may be NULL */ ++ AuDebugOn(inode && (inode->i_mode & S_IFMT) != orig_h.mode); ++ if (!tmp_h.inode) { ++ AuDbg("positive --> negative\n"); ++ /* or bypassing aufs */ ++ au_hide(dentry); ++ if (tmp->di_bwh >= 0 && tmp->di_bwh <= dinfo->di_bstart) ++ dinfo->di_bwh = tmp->di_bwh; ++ if (inode) ++ err = au_refresh_hinode_self(inode); ++ au_dbg_verify_dinode(dentry); ++ } else if (orig_h.mode == tmp_h.mode) { ++ AuDbg("positive --> positive, same type\n"); ++ if (!S_ISDIR(orig_h.mode) ++ && dinfo->di_bstart > tmp->di_bstart) { ++ /* ++ * similar to the behaviour of removing and ++ * creating. ++ */ ++ au_hide(dentry); ++ if (inode) ++ err = au_refresh_hinode_self(inode); ++ au_dbg_verify_dinode(dentry); ++ } else { ++ /* fill empty slots */ ++ if (dinfo->di_bstart > tmp->di_bstart) ++ dinfo->di_bstart = tmp->di_bstart; ++ if (dinfo->di_bend < tmp->di_bend) ++ dinfo->di_bend = tmp->di_bend; ++ dinfo->di_bwh = tmp->di_bwh; ++ dinfo->di_bdiropq = tmp->di_bdiropq; ++ hd = tmp->di_hdentry; ++ bend = dinfo->di_bend; ++ for (bindex = tmp->di_bstart; bindex <= bend; ++ bindex++) { ++ if (au_h_dptr(dentry, bindex)) ++ continue; ++ h_dentry = hd[bindex].hd_dentry; ++ if (!h_dentry) ++ continue; ++ h_inode = h_dentry->d_inode; ++ AuDebugOn(!h_inode); ++ AuDebugOn(orig_h.mode ++ != (h_inode->i_mode ++ & S_IFMT)); ++ au_set_h_dptr(dentry, bindex, ++ dget(h_dentry)); ++ } ++ err = au_refresh_hinode(inode, dentry); ++ au_dbg_verify_dinode(dentry); ++ } ++ } else { ++ AuDbg("positive --> positive, different type\n"); ++ /* similar to the behaviour of removing and creating */ ++ au_hide(dentry); ++ if (inode) ++ err = au_refresh_hinode_self(inode); ++ au_dbg_verify_dinode(dentry); ++ } ++ } ++ ++out: ++ return err; ++} ++ ++void au_refresh_dop(struct dentry *dentry, int force_reval) ++{ ++ const struct dentry_operations *dop ++ = force_reval ? &aufs_dop : dentry->d_sb->s_d_op; ++ static const unsigned int mask ++ = DCACHE_OP_REVALIDATE | DCACHE_OP_WEAK_REVALIDATE; ++ ++ BUILD_BUG_ON(sizeof(mask) != sizeof(dentry->d_flags)); ++ ++ if (dentry->d_op == dop) ++ return; ++ ++ AuDbg("%pd\n", dentry); ++ spin_lock(&dentry->d_lock); ++ if (dop == &aufs_dop) ++ dentry->d_flags |= mask; ++ else ++ dentry->d_flags &= ~mask; ++ dentry->d_op = dop; ++ spin_unlock(&dentry->d_lock); ++} ++ ++int au_refresh_dentry(struct dentry *dentry, struct dentry *parent) ++{ ++ int err, ebrange; ++ unsigned int sigen; ++ struct au_dinfo *dinfo, *tmp; ++ struct super_block *sb; ++ struct inode *inode; ++ ++ DiMustWriteLock(dentry); ++ AuDebugOn(IS_ROOT(dentry)); ++ AuDebugOn(!parent->d_inode); ++ ++ sb = dentry->d_sb; ++ inode = dentry->d_inode; ++ sigen = au_sigen(sb); ++ err = au_digen_test(parent, sigen); ++ if (unlikely(err)) ++ goto out; ++ ++ dinfo = au_di(dentry); ++ err = au_di_realloc(dinfo, au_sbend(sb) + 1); ++ if (unlikely(err)) ++ goto out; ++ ebrange = au_dbrange_test(dentry); ++ if (!ebrange) ++ ebrange = au_do_refresh_hdentry(dentry, parent); ++ ++ if (d_unhashed(dentry) || ebrange /* || dinfo->di_tmpfile */) { ++ AuDebugOn(au_dbstart(dentry) < 0 && au_dbend(dentry) >= 0); ++ if (inode) ++ err = au_refresh_hinode_self(inode); ++ au_dbg_verify_dinode(dentry); ++ if (!err) ++ goto out_dgen; /* success */ ++ goto out; ++ } ++ ++ /* temporary dinfo */ ++ AuDbgDentry(dentry); ++ err = -ENOMEM; ++ tmp = au_di_alloc(sb, AuLsc_DI_TMP); ++ if (unlikely(!tmp)) ++ goto out; ++ au_di_swap(tmp, dinfo); ++ /* returns the number of positive dentries */ ++ /* ++ * if current working dir is removed, it returns an error. ++ * but the dentry is legal. ++ */ ++ err = au_lkup_dentry(dentry, /*bstart*/0, /*type*/0); ++ AuDbgDentry(dentry); ++ au_di_swap(tmp, dinfo); ++ if (err == -ENOENT) ++ err = 0; ++ if (err >= 0) { ++ /* compare/refresh by dinfo */ ++ AuDbgDentry(dentry); ++ err = au_refresh_by_dinfo(dentry, dinfo, tmp); ++ au_dbg_verify_dinode(dentry); ++ AuTraceErr(err); ++ } ++ au_rw_write_unlock(&tmp->di_rwsem); ++ au_di_free(tmp); ++ if (unlikely(err)) ++ goto out; ++ ++out_dgen: ++ au_update_digen(dentry); ++out: ++ if (unlikely(err && !(dentry->d_flags & DCACHE_NFSFS_RENAMED))) { ++ AuIOErr("failed refreshing %pd, %d\n", dentry, err); ++ AuDbgDentry(dentry); ++ } ++ AuTraceErr(err); ++ return err; ++} ++ ++static int au_do_h_d_reval(struct dentry *h_dentry, unsigned int flags, ++ struct dentry *dentry, aufs_bindex_t bindex) ++{ ++ int err, valid; ++ ++ err = 0; ++ if (!(h_dentry->d_flags & DCACHE_OP_REVALIDATE)) ++ goto out; ++ ++ AuDbg("b%d\n", bindex); ++ /* ++ * gave up supporting LOOKUP_CREATE/OPEN for lower fs, ++ * due to whiteout and branch permission. ++ */ ++ flags &= ~(/*LOOKUP_PARENT |*/ LOOKUP_OPEN | LOOKUP_CREATE ++ | LOOKUP_FOLLOW | LOOKUP_EXCL); ++ /* it may return tri-state */ ++ valid = h_dentry->d_op->d_revalidate(h_dentry, flags); ++ ++ if (unlikely(valid < 0)) ++ err = valid; ++ else if (!valid) ++ err = -EINVAL; ++ ++out: ++ AuTraceErr(err); ++ return err; ++} ++ ++/* todo: remove this */ ++static int h_d_revalidate(struct dentry *dentry, struct inode *inode, ++ unsigned int flags, int do_udba) ++{ ++ int err; ++ umode_t mode, h_mode; ++ aufs_bindex_t bindex, btail, bstart, ibs, ibe; ++ unsigned char plus, unhashed, is_root, h_plus, h_nfs, tmpfile; ++ struct inode *h_inode, *h_cached_inode; ++ struct dentry *h_dentry; ++ struct qstr *name, *h_name; ++ ++ err = 0; ++ plus = 0; ++ mode = 0; ++ ibs = -1; ++ ibe = -1; ++ unhashed = !!d_unhashed(dentry); ++ is_root = !!IS_ROOT(dentry); ++ name = &dentry->d_name; ++ tmpfile = au_di(dentry)->di_tmpfile; ++ ++ /* ++ * Theoretically, REVAL test should be unnecessary in case of ++ * {FS,I}NOTIFY. ++ * But {fs,i}notify doesn't fire some necessary events, ++ * IN_ATTRIB for atime/nlink/pageio ++ * Let's do REVAL test too. ++ */ ++ if (do_udba && inode) { ++ mode = (inode->i_mode & S_IFMT); ++ plus = (inode->i_nlink > 0); ++ ibs = au_ibstart(inode); ++ ibe = au_ibend(inode); ++ } ++ ++ bstart = au_dbstart(dentry); ++ btail = bstart; ++ if (inode && S_ISDIR(inode->i_mode)) ++ btail = au_dbtaildir(dentry); ++ for (bindex = bstart; bindex <= btail; bindex++) { ++ h_dentry = au_h_dptr(dentry, bindex); ++ if (!h_dentry) ++ continue; ++ ++ AuDbg("b%d, %pd\n", bindex, h_dentry); ++ h_nfs = !!au_test_nfs(h_dentry->d_sb); ++ spin_lock(&h_dentry->d_lock); ++ h_name = &h_dentry->d_name; ++ if (unlikely(do_udba ++ && !is_root ++ && ((!h_nfs ++ && (unhashed != !!d_unhashed(h_dentry) ++ || (!tmpfile ++ && !au_qstreq(name, h_name)) ++ )) ++ || (h_nfs ++ && !(flags & LOOKUP_OPEN) ++ && (h_dentry->d_flags ++ & DCACHE_NFSFS_RENAMED))) ++ )) { ++ int h_unhashed; ++ ++ h_unhashed = d_unhashed(h_dentry); ++ spin_unlock(&h_dentry->d_lock); ++ AuDbg("unhash 0x%x 0x%x, %pd %pd\n", ++ unhashed, h_unhashed, dentry, h_dentry); ++ goto err; ++ } ++ spin_unlock(&h_dentry->d_lock); ++ ++ err = au_do_h_d_reval(h_dentry, flags, dentry, bindex); ++ if (unlikely(err)) ++ /* do not goto err, to keep the errno */ ++ break; ++ ++ /* todo: plink too? */ ++ if (!do_udba) ++ continue; ++ ++ /* UDBA tests */ ++ h_inode = h_dentry->d_inode; ++ if (unlikely(!!inode != !!h_inode)) ++ goto err; ++ ++ h_plus = plus; ++ h_mode = mode; ++ h_cached_inode = h_inode; ++ if (h_inode) { ++ h_mode = (h_inode->i_mode & S_IFMT); ++ h_plus = (h_inode->i_nlink > 0); ++ } ++ if (inode && ibs <= bindex && bindex <= ibe) ++ h_cached_inode = au_h_iptr(inode, bindex); ++ ++ if (!h_nfs) { ++ if (unlikely(plus != h_plus && !tmpfile)) ++ goto err; ++ } else { ++ if (unlikely(!(h_dentry->d_flags & DCACHE_NFSFS_RENAMED) ++ && !is_root ++ && !IS_ROOT(h_dentry) ++ && unhashed != d_unhashed(h_dentry))) ++ goto err; ++ } ++ if (unlikely(mode != h_mode ++ || h_cached_inode != h_inode)) ++ goto err; ++ continue; ++ ++err: ++ err = -EINVAL; ++ break; ++ } ++ ++ AuTraceErr(err); ++ return err; ++} ++ ++/* todo: consolidate with do_refresh() and au_reval_for_attr() */ ++static int simple_reval_dpath(struct dentry *dentry, unsigned int sigen) ++{ ++ int err; ++ struct dentry *parent; ++ ++ if (!au_digen_test(dentry, sigen)) ++ return 0; ++ ++ parent = dget_parent(dentry); ++ di_read_lock_parent(parent, AuLock_IR); ++ AuDebugOn(au_digen_test(parent, sigen)); ++ au_dbg_verify_gen(parent, sigen); ++ err = au_refresh_dentry(dentry, parent); ++ di_read_unlock(parent, AuLock_IR); ++ dput(parent); ++ AuTraceErr(err); ++ return err; ++} ++ ++int au_reval_dpath(struct dentry *dentry, unsigned int sigen) ++{ ++ int err; ++ struct dentry *d, *parent; ++ struct inode *inode; ++ ++ if (!au_ftest_si(au_sbi(dentry->d_sb), FAILED_REFRESH_DIR)) ++ return simple_reval_dpath(dentry, sigen); ++ ++ /* slow loop, keep it simple and stupid */ ++ /* cf: au_cpup_dirs() */ ++ err = 0; ++ parent = NULL; ++ while (au_digen_test(dentry, sigen)) { ++ d = dentry; ++ while (1) { ++ dput(parent); ++ parent = dget_parent(d); ++ if (!au_digen_test(parent, sigen)) ++ break; ++ d = parent; ++ } ++ ++ inode = d->d_inode; ++ if (d != dentry) ++ di_write_lock_child2(d); ++ ++ /* someone might update our dentry while we were sleeping */ ++ if (au_digen_test(d, sigen)) { ++ /* ++ * todo: consolidate with simple_reval_dpath(), ++ * do_refresh() and au_reval_for_attr(). ++ */ ++ di_read_lock_parent(parent, AuLock_IR); ++ err = au_refresh_dentry(d, parent); ++ di_read_unlock(parent, AuLock_IR); ++ } ++ ++ if (d != dentry) ++ di_write_unlock(d); ++ dput(parent); ++ if (unlikely(err)) ++ break; ++ } ++ ++ return err; ++} ++ ++/* ++ * if valid returns 1, otherwise 0. ++ */ ++static int aufs_d_revalidate(struct dentry *dentry, unsigned int flags) ++{ ++ int valid, err; ++ unsigned int sigen; ++ unsigned char do_udba; ++ struct super_block *sb; ++ struct inode *inode; ++ ++ /* todo: support rcu-walk? */ ++ if (flags & LOOKUP_RCU) ++ return -ECHILD; ++ ++ valid = 0; ++ if (unlikely(!au_di(dentry))) ++ goto out; ++ ++ valid = 1; ++ sb = dentry->d_sb; ++ /* ++ * todo: very ugly ++ * i_mutex of parent dir may be held, ++ * but we should not return 'invalid' due to busy. ++ */ ++ err = aufs_read_lock(dentry, AuLock_FLUSH | AuLock_DW | AuLock_NOPLM); ++ if (unlikely(err)) { ++ valid = err; ++ AuTraceErr(err); ++ goto out; ++ } ++ inode = dentry->d_inode; ++ if (unlikely(inode && is_bad_inode(inode))) { ++ err = -EINVAL; ++ AuTraceErr(err); ++ goto out_dgrade; ++ } ++ if (unlikely(au_dbrange_test(dentry))) { ++ err = -EINVAL; ++ AuTraceErr(err); ++ goto out_dgrade; ++ } ++ ++ sigen = au_sigen(sb); ++ if (au_digen_test(dentry, sigen)) { ++ AuDebugOn(IS_ROOT(dentry)); ++ err = au_reval_dpath(dentry, sigen); ++ if (unlikely(err)) { ++ AuTraceErr(err); ++ goto out_dgrade; ++ } ++ } ++ di_downgrade_lock(dentry, AuLock_IR); ++ ++ err = -EINVAL; ++ if (!(flags & (LOOKUP_OPEN | LOOKUP_EMPTY)) ++ && inode ++ && !(inode->i_state && I_LINKABLE) ++ && (IS_DEADDIR(inode) || !inode->i_nlink)) { ++ AuTraceErr(err); ++ goto out_inval; ++ } ++ ++ do_udba = !au_opt_test(au_mntflags(sb), UDBA_NONE); ++ if (do_udba && inode) { ++ aufs_bindex_t bstart = au_ibstart(inode); ++ struct inode *h_inode; ++ ++ if (bstart >= 0) { ++ h_inode = au_h_iptr(inode, bstart); ++ if (h_inode && au_test_higen(inode, h_inode)) { ++ AuTraceErr(err); ++ goto out_inval; ++ } ++ } ++ } ++ ++ err = h_d_revalidate(dentry, inode, flags, do_udba); ++ if (unlikely(!err && do_udba && au_dbstart(dentry) < 0)) { ++ err = -EIO; ++ AuDbg("both of real entry and whiteout found, %p, err %d\n", ++ dentry, err); ++ } ++ goto out_inval; ++ ++out_dgrade: ++ di_downgrade_lock(dentry, AuLock_IR); ++out_inval: ++ aufs_read_unlock(dentry, AuLock_IR); ++ AuTraceErr(err); ++ valid = !err; ++out: ++ if (!valid) { ++ AuDbg("%pd invalid, %d\n", dentry, valid); ++ d_drop(dentry); ++ } ++ return valid; ++} ++ ++static void aufs_d_release(struct dentry *dentry) ++{ ++ if (au_di(dentry)) { ++ au_di_fin(dentry); ++ au_hn_di_reinit(dentry); ++ } ++} ++ ++const struct dentry_operations aufs_dop = { ++ .d_revalidate = aufs_d_revalidate, ++ .d_weak_revalidate = aufs_d_revalidate, ++ .d_release = aufs_d_release ++}; ++ ++/* aufs_dop without d_revalidate */ ++const struct dentry_operations aufs_dop_noreval = { ++ .d_release = aufs_d_release ++}; +diff --git a/fs/aufs/dentry.h b/fs/aufs/dentry.h +new file mode 100644 +index 0000000..4006484 +--- /dev/null ++++ b/fs/aufs/dentry.h +@@ -0,0 +1,234 @@ ++/* ++ * Copyright (C) 2005-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * lookup and dentry operations ++ */ ++ ++#ifndef __AUFS_DENTRY_H__ ++#define __AUFS_DENTRY_H__ ++ ++#ifdef __KERNEL__ ++ ++#include ++#include "rwsem.h" ++ ++struct au_hdentry { ++ struct dentry *hd_dentry; ++ aufs_bindex_t hd_id; ++}; ++ ++struct au_dinfo { ++ atomic_t di_generation; ++ ++ struct au_rwsem di_rwsem; ++ aufs_bindex_t di_bstart, di_bend, di_bwh, di_bdiropq; ++ unsigned char di_tmpfile; /* to allow the different name */ ++ struct au_hdentry *di_hdentry; ++} ____cacheline_aligned_in_smp; ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* dentry.c */ ++extern const struct dentry_operations aufs_dop, aufs_dop_noreval; ++struct au_branch; ++struct dentry *au_sio_lkup_one(struct qstr *name, struct dentry *parent); ++int au_h_verify(struct dentry *h_dentry, unsigned int udba, struct inode *h_dir, ++ struct dentry *h_parent, struct au_branch *br); ++ ++int au_lkup_dentry(struct dentry *dentry, aufs_bindex_t bstart, mode_t type); ++int au_lkup_neg(struct dentry *dentry, aufs_bindex_t bindex, int wh); ++int au_refresh_dentry(struct dentry *dentry, struct dentry *parent); ++int au_reval_dpath(struct dentry *dentry, unsigned int sigen); ++void au_refresh_dop(struct dentry *dentry, int force_reval); ++ ++/* dinfo.c */ ++void au_di_init_once(void *_di); ++struct au_dinfo *au_di_alloc(struct super_block *sb, unsigned int lsc); ++void au_di_free(struct au_dinfo *dinfo); ++void au_di_swap(struct au_dinfo *a, struct au_dinfo *b); ++void au_di_cp(struct au_dinfo *dst, struct au_dinfo *src); ++int au_di_init(struct dentry *dentry); ++void au_di_fin(struct dentry *dentry); ++int au_di_realloc(struct au_dinfo *dinfo, int nbr); ++ ++void di_read_lock(struct dentry *d, int flags, unsigned int lsc); ++void di_read_unlock(struct dentry *d, int flags); ++void di_downgrade_lock(struct dentry *d, int flags); ++void di_write_lock(struct dentry *d, unsigned int lsc); ++void di_write_unlock(struct dentry *d); ++void di_write_lock2_child(struct dentry *d1, struct dentry *d2, int isdir); ++void di_write_lock2_parent(struct dentry *d1, struct dentry *d2, int isdir); ++void di_write_unlock2(struct dentry *d1, struct dentry *d2); ++ ++struct dentry *au_h_dptr(struct dentry *dentry, aufs_bindex_t bindex); ++struct dentry *au_h_d_alias(struct dentry *dentry, aufs_bindex_t bindex); ++aufs_bindex_t au_dbtail(struct dentry *dentry); ++aufs_bindex_t au_dbtaildir(struct dentry *dentry); ++ ++void au_set_h_dptr(struct dentry *dentry, aufs_bindex_t bindex, ++ struct dentry *h_dentry); ++int au_digen_test(struct dentry *dentry, unsigned int sigen); ++int au_dbrange_test(struct dentry *dentry); ++void au_update_digen(struct dentry *dentry); ++void au_update_dbrange(struct dentry *dentry, int do_put_zero); ++void au_update_dbstart(struct dentry *dentry); ++void au_update_dbend(struct dentry *dentry); ++int au_find_dbindex(struct dentry *dentry, struct dentry *h_dentry); ++ ++/* ---------------------------------------------------------------------- */ ++ ++static inline struct au_dinfo *au_di(struct dentry *dentry) ++{ ++ return dentry->d_fsdata; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* lock subclass for dinfo */ ++enum { ++ AuLsc_DI_CHILD, /* child first */ ++ AuLsc_DI_CHILD2, /* rename(2), link(2), and cpup at hnotify */ ++ AuLsc_DI_CHILD3, /* copyup dirs */ ++ AuLsc_DI_PARENT, ++ AuLsc_DI_PARENT2, ++ AuLsc_DI_PARENT3, ++ AuLsc_DI_TMP /* temp for replacing dinfo */ ++}; ++ ++/* ++ * di_read_lock_child, di_write_lock_child, ++ * di_read_lock_child2, di_write_lock_child2, ++ * di_read_lock_child3, di_write_lock_child3, ++ * di_read_lock_parent, di_write_lock_parent, ++ * di_read_lock_parent2, di_write_lock_parent2, ++ * di_read_lock_parent3, di_write_lock_parent3, ++ */ ++#define AuReadLockFunc(name, lsc) \ ++static inline void di_read_lock_##name(struct dentry *d, int flags) \ ++{ di_read_lock(d, flags, AuLsc_DI_##lsc); } ++ ++#define AuWriteLockFunc(name, lsc) \ ++static inline void di_write_lock_##name(struct dentry *d) \ ++{ di_write_lock(d, AuLsc_DI_##lsc); } ++ ++#define AuRWLockFuncs(name, lsc) \ ++ AuReadLockFunc(name, lsc) \ ++ AuWriteLockFunc(name, lsc) ++ ++AuRWLockFuncs(child, CHILD); ++AuRWLockFuncs(child2, CHILD2); ++AuRWLockFuncs(child3, CHILD3); ++AuRWLockFuncs(parent, PARENT); ++AuRWLockFuncs(parent2, PARENT2); ++AuRWLockFuncs(parent3, PARENT3); ++ ++#undef AuReadLockFunc ++#undef AuWriteLockFunc ++#undef AuRWLockFuncs ++ ++#define DiMustNoWaiters(d) AuRwMustNoWaiters(&au_di(d)->di_rwsem) ++#define DiMustAnyLock(d) AuRwMustAnyLock(&au_di(d)->di_rwsem) ++#define DiMustWriteLock(d) AuRwMustWriteLock(&au_di(d)->di_rwsem) ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* todo: memory barrier? */ ++static inline unsigned int au_digen(struct dentry *d) ++{ ++ return atomic_read(&au_di(d)->di_generation); ++} ++ ++static inline void au_h_dentry_init(struct au_hdentry *hdentry) ++{ ++ hdentry->hd_dentry = NULL; ++} ++ ++static inline void au_hdput(struct au_hdentry *hd) ++{ ++ if (hd) ++ dput(hd->hd_dentry); ++} ++ ++static inline aufs_bindex_t au_dbstart(struct dentry *dentry) ++{ ++ DiMustAnyLock(dentry); ++ return au_di(dentry)->di_bstart; ++} ++ ++static inline aufs_bindex_t au_dbend(struct dentry *dentry) ++{ ++ DiMustAnyLock(dentry); ++ return au_di(dentry)->di_bend; ++} ++ ++static inline aufs_bindex_t au_dbwh(struct dentry *dentry) ++{ ++ DiMustAnyLock(dentry); ++ return au_di(dentry)->di_bwh; ++} ++ ++static inline aufs_bindex_t au_dbdiropq(struct dentry *dentry) ++{ ++ DiMustAnyLock(dentry); ++ return au_di(dentry)->di_bdiropq; ++} ++ ++/* todo: hard/soft set? */ ++static inline void au_set_dbstart(struct dentry *dentry, aufs_bindex_t bindex) ++{ ++ DiMustWriteLock(dentry); ++ au_di(dentry)->di_bstart = bindex; ++} ++ ++static inline void au_set_dbend(struct dentry *dentry, aufs_bindex_t bindex) ++{ ++ DiMustWriteLock(dentry); ++ au_di(dentry)->di_bend = bindex; ++} ++ ++static inline void au_set_dbwh(struct dentry *dentry, aufs_bindex_t bindex) ++{ ++ DiMustWriteLock(dentry); ++ /* dbwh can be outside of bstart - bend range */ ++ au_di(dentry)->di_bwh = bindex; ++} ++ ++static inline void au_set_dbdiropq(struct dentry *dentry, aufs_bindex_t bindex) ++{ ++ DiMustWriteLock(dentry); ++ au_di(dentry)->di_bdiropq = bindex; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++#ifdef CONFIG_AUFS_HNOTIFY ++static inline void au_digen_dec(struct dentry *d) ++{ ++ atomic_dec(&au_di(d)->di_generation); ++} ++ ++static inline void au_hn_di_reinit(struct dentry *dentry) ++{ ++ dentry->d_fsdata = NULL; ++} ++#else ++AuStubVoid(au_hn_di_reinit, struct dentry *dentry __maybe_unused) ++#endif /* CONFIG_AUFS_HNOTIFY */ ++ ++#endif /* __KERNEL__ */ ++#endif /* __AUFS_DENTRY_H__ */ +diff --git a/fs/aufs/dinfo.c b/fs/aufs/dinfo.c +new file mode 100644 +index 0000000..28c02b3 +--- /dev/null ++++ b/fs/aufs/dinfo.c +@@ -0,0 +1,544 @@ ++/* ++ * Copyright (C) 2005-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * dentry private data ++ */ ++ ++#include "aufs.h" ++ ++void au_di_init_once(void *_dinfo) ++{ ++ struct au_dinfo *dinfo = _dinfo; ++ static struct lock_class_key aufs_di; ++ ++ au_rw_init(&dinfo->di_rwsem); ++ au_rw_class(&dinfo->di_rwsem, &aufs_di); ++} ++ ++struct au_dinfo *au_di_alloc(struct super_block *sb, unsigned int lsc) ++{ ++ struct au_dinfo *dinfo; ++ int nbr, i; ++ ++ dinfo = au_cache_alloc_dinfo(); ++ if (unlikely(!dinfo)) ++ goto out; ++ ++ nbr = au_sbend(sb) + 1; ++ if (nbr <= 0) ++ nbr = 1; ++ dinfo->di_hdentry = kcalloc(nbr, sizeof(*dinfo->di_hdentry), GFP_NOFS); ++ if (dinfo->di_hdentry) { ++ au_rw_write_lock_nested(&dinfo->di_rwsem, lsc); ++ dinfo->di_bstart = -1; ++ dinfo->di_bend = -1; ++ dinfo->di_bwh = -1; ++ dinfo->di_bdiropq = -1; ++ dinfo->di_tmpfile = 0; ++ for (i = 0; i < nbr; i++) ++ dinfo->di_hdentry[i].hd_id = -1; ++ goto out; ++ } ++ ++ au_cache_free_dinfo(dinfo); ++ dinfo = NULL; ++ ++out: ++ return dinfo; ++} ++ ++void au_di_free(struct au_dinfo *dinfo) ++{ ++ struct au_hdentry *p; ++ aufs_bindex_t bend, bindex; ++ ++ /* dentry may not be revalidated */ ++ bindex = dinfo->di_bstart; ++ if (bindex >= 0) { ++ bend = dinfo->di_bend; ++ p = dinfo->di_hdentry + bindex; ++ while (bindex++ <= bend) ++ au_hdput(p++); ++ } ++ kfree(dinfo->di_hdentry); ++ au_cache_free_dinfo(dinfo); ++} ++ ++void au_di_swap(struct au_dinfo *a, struct au_dinfo *b) ++{ ++ struct au_hdentry *p; ++ aufs_bindex_t bi; ++ ++ AuRwMustWriteLock(&a->di_rwsem); ++ AuRwMustWriteLock(&b->di_rwsem); ++ ++#define DiSwap(v, name) \ ++ do { \ ++ v = a->di_##name; \ ++ a->di_##name = b->di_##name; \ ++ b->di_##name = v; \ ++ } while (0) ++ ++ DiSwap(p, hdentry); ++ DiSwap(bi, bstart); ++ DiSwap(bi, bend); ++ DiSwap(bi, bwh); ++ DiSwap(bi, bdiropq); ++ /* smp_mb(); */ ++ ++#undef DiSwap ++} ++ ++void au_di_cp(struct au_dinfo *dst, struct au_dinfo *src) ++{ ++ AuRwMustWriteLock(&dst->di_rwsem); ++ AuRwMustWriteLock(&src->di_rwsem); ++ ++ dst->di_bstart = src->di_bstart; ++ dst->di_bend = src->di_bend; ++ dst->di_bwh = src->di_bwh; ++ dst->di_bdiropq = src->di_bdiropq; ++ /* smp_mb(); */ ++} ++ ++int au_di_init(struct dentry *dentry) ++{ ++ int err; ++ struct super_block *sb; ++ struct au_dinfo *dinfo; ++ ++ err = 0; ++ sb = dentry->d_sb; ++ dinfo = au_di_alloc(sb, AuLsc_DI_CHILD); ++ if (dinfo) { ++ atomic_set(&dinfo->di_generation, au_sigen(sb)); ++ /* smp_mb(); */ /* atomic_set */ ++ dentry->d_fsdata = dinfo; ++ } else ++ err = -ENOMEM; ++ ++ return err; ++} ++ ++void au_di_fin(struct dentry *dentry) ++{ ++ struct au_dinfo *dinfo; ++ ++ dinfo = au_di(dentry); ++ AuRwDestroy(&dinfo->di_rwsem); ++ au_di_free(dinfo); ++} ++ ++int au_di_realloc(struct au_dinfo *dinfo, int nbr) ++{ ++ int err, sz; ++ struct au_hdentry *hdp; ++ ++ AuRwMustWriteLock(&dinfo->di_rwsem); ++ ++ err = -ENOMEM; ++ sz = sizeof(*hdp) * (dinfo->di_bend + 1); ++ if (!sz) ++ sz = sizeof(*hdp); ++ hdp = au_kzrealloc(dinfo->di_hdentry, sz, sizeof(*hdp) * nbr, GFP_NOFS); ++ if (hdp) { ++ dinfo->di_hdentry = hdp; ++ err = 0; ++ } ++ ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++static void do_ii_write_lock(struct inode *inode, unsigned int lsc) ++{ ++ switch (lsc) { ++ case AuLsc_DI_CHILD: ++ ii_write_lock_child(inode); ++ break; ++ case AuLsc_DI_CHILD2: ++ ii_write_lock_child2(inode); ++ break; ++ case AuLsc_DI_CHILD3: ++ ii_write_lock_child3(inode); ++ break; ++ case AuLsc_DI_PARENT: ++ ii_write_lock_parent(inode); ++ break; ++ case AuLsc_DI_PARENT2: ++ ii_write_lock_parent2(inode); ++ break; ++ case AuLsc_DI_PARENT3: ++ ii_write_lock_parent3(inode); ++ break; ++ default: ++ BUG(); ++ } ++} ++ ++static void do_ii_read_lock(struct inode *inode, unsigned int lsc) ++{ ++ switch (lsc) { ++ case AuLsc_DI_CHILD: ++ ii_read_lock_child(inode); ++ break; ++ case AuLsc_DI_CHILD2: ++ ii_read_lock_child2(inode); ++ break; ++ case AuLsc_DI_CHILD3: ++ ii_read_lock_child3(inode); ++ break; ++ case AuLsc_DI_PARENT: ++ ii_read_lock_parent(inode); ++ break; ++ case AuLsc_DI_PARENT2: ++ ii_read_lock_parent2(inode); ++ break; ++ case AuLsc_DI_PARENT3: ++ ii_read_lock_parent3(inode); ++ break; ++ default: ++ BUG(); ++ } ++} ++ ++void di_read_lock(struct dentry *d, int flags, unsigned int lsc) ++{ ++ au_rw_read_lock_nested(&au_di(d)->di_rwsem, lsc); ++ if (d->d_inode) { ++ if (au_ftest_lock(flags, IW)) ++ do_ii_write_lock(d->d_inode, lsc); ++ else if (au_ftest_lock(flags, IR)) ++ do_ii_read_lock(d->d_inode, lsc); ++ } ++} ++ ++void di_read_unlock(struct dentry *d, int flags) ++{ ++ if (d->d_inode) { ++ if (au_ftest_lock(flags, IW)) { ++ au_dbg_verify_dinode(d); ++ ii_write_unlock(d->d_inode); ++ } else if (au_ftest_lock(flags, IR)) { ++ au_dbg_verify_dinode(d); ++ ii_read_unlock(d->d_inode); ++ } ++ } ++ au_rw_read_unlock(&au_di(d)->di_rwsem); ++} ++ ++void di_downgrade_lock(struct dentry *d, int flags) ++{ ++ if (d->d_inode && au_ftest_lock(flags, IR)) ++ ii_downgrade_lock(d->d_inode); ++ au_rw_dgrade_lock(&au_di(d)->di_rwsem); ++} ++ ++void di_write_lock(struct dentry *d, unsigned int lsc) ++{ ++ au_rw_write_lock_nested(&au_di(d)->di_rwsem, lsc); ++ if (d->d_inode) ++ do_ii_write_lock(d->d_inode, lsc); ++} ++ ++void di_write_unlock(struct dentry *d) ++{ ++ au_dbg_verify_dinode(d); ++ if (d->d_inode) ++ ii_write_unlock(d->d_inode); ++ au_rw_write_unlock(&au_di(d)->di_rwsem); ++} ++ ++void di_write_lock2_child(struct dentry *d1, struct dentry *d2, int isdir) ++{ ++ AuDebugOn(d1 == d2 ++ || d1->d_inode == d2->d_inode ++ || d1->d_sb != d2->d_sb); ++ ++ if (isdir && au_test_subdir(d1, d2)) { ++ di_write_lock_child(d1); ++ di_write_lock_child2(d2); ++ } else { ++ /* there should be no races */ ++ di_write_lock_child(d2); ++ di_write_lock_child2(d1); ++ } ++} ++ ++void di_write_lock2_parent(struct dentry *d1, struct dentry *d2, int isdir) ++{ ++ AuDebugOn(d1 == d2 ++ || d1->d_inode == d2->d_inode ++ || d1->d_sb != d2->d_sb); ++ ++ if (isdir && au_test_subdir(d1, d2)) { ++ di_write_lock_parent(d1); ++ di_write_lock_parent2(d2); ++ } else { ++ /* there should be no races */ ++ di_write_lock_parent(d2); ++ di_write_lock_parent2(d1); ++ } ++} ++ ++void di_write_unlock2(struct dentry *d1, struct dentry *d2) ++{ ++ di_write_unlock(d1); ++ if (d1->d_inode == d2->d_inode) ++ au_rw_write_unlock(&au_di(d2)->di_rwsem); ++ else ++ di_write_unlock(d2); ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++struct dentry *au_h_dptr(struct dentry *dentry, aufs_bindex_t bindex) ++{ ++ struct dentry *d; ++ ++ DiMustAnyLock(dentry); ++ ++ if (au_dbstart(dentry) < 0 || bindex < au_dbstart(dentry)) ++ return NULL; ++ AuDebugOn(bindex < 0); ++ d = au_di(dentry)->di_hdentry[0 + bindex].hd_dentry; ++ AuDebugOn(d && au_dcount(d) <= 0); ++ return d; ++} ++ ++/* ++ * extended version of au_h_dptr(). ++ * returns a hashed and positive (or linkable) h_dentry in bindex, NULL, or ++ * error. ++ */ ++struct dentry *au_h_d_alias(struct dentry *dentry, aufs_bindex_t bindex) ++{ ++ struct dentry *h_dentry; ++ struct inode *inode, *h_inode; ++ ++ inode = dentry->d_inode; ++ AuDebugOn(!inode); ++ ++ h_dentry = NULL; ++ if (au_dbstart(dentry) <= bindex ++ && bindex <= au_dbend(dentry)) ++ h_dentry = au_h_dptr(dentry, bindex); ++ if (h_dentry && !au_d_linkable(h_dentry)) { ++ dget(h_dentry); ++ goto out; /* success */ ++ } ++ ++ AuDebugOn(bindex < au_ibstart(inode)); ++ AuDebugOn(au_ibend(inode) < bindex); ++ h_inode = au_h_iptr(inode, bindex); ++ h_dentry = d_find_alias(h_inode); ++ if (h_dentry) { ++ if (!IS_ERR(h_dentry)) { ++ if (!au_d_linkable(h_dentry)) ++ goto out; /* success */ ++ dput(h_dentry); ++ } else ++ goto out; ++ } ++ ++ if (au_opt_test(au_mntflags(dentry->d_sb), PLINK)) { ++ h_dentry = au_plink_lkup(inode, bindex); ++ AuDebugOn(!h_dentry); ++ if (!IS_ERR(h_dentry)) { ++ if (!au_d_hashed_positive(h_dentry)) ++ goto out; /* success */ ++ dput(h_dentry); ++ h_dentry = NULL; ++ } ++ } ++ ++out: ++ AuDbgDentry(h_dentry); ++ return h_dentry; ++} ++ ++aufs_bindex_t au_dbtail(struct dentry *dentry) ++{ ++ aufs_bindex_t bend, bwh; ++ ++ bend = au_dbend(dentry); ++ if (0 <= bend) { ++ bwh = au_dbwh(dentry); ++ if (!bwh) ++ return bwh; ++ if (0 < bwh && bwh < bend) ++ return bwh - 1; ++ } ++ return bend; ++} ++ ++aufs_bindex_t au_dbtaildir(struct dentry *dentry) ++{ ++ aufs_bindex_t bend, bopq; ++ ++ bend = au_dbtail(dentry); ++ if (0 <= bend) { ++ bopq = au_dbdiropq(dentry); ++ if (0 <= bopq && bopq < bend) ++ bend = bopq; ++ } ++ return bend; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++void au_set_h_dptr(struct dentry *dentry, aufs_bindex_t bindex, ++ struct dentry *h_dentry) ++{ ++ struct au_hdentry *hd = au_di(dentry)->di_hdentry + bindex; ++ struct au_branch *br; ++ ++ DiMustWriteLock(dentry); ++ ++ au_hdput(hd); ++ hd->hd_dentry = h_dentry; ++ if (h_dentry) { ++ br = au_sbr(dentry->d_sb, bindex); ++ hd->hd_id = br->br_id; ++ } ++} ++ ++int au_dbrange_test(struct dentry *dentry) ++{ ++ int err; ++ aufs_bindex_t bstart, bend; ++ ++ err = 0; ++ bstart = au_dbstart(dentry); ++ bend = au_dbend(dentry); ++ if (bstart >= 0) ++ AuDebugOn(bend < 0 && bstart > bend); ++ else { ++ err = -EIO; ++ AuDebugOn(bend >= 0); ++ } ++ ++ return err; ++} ++ ++int au_digen_test(struct dentry *dentry, unsigned int sigen) ++{ ++ int err; ++ ++ err = 0; ++ if (unlikely(au_digen(dentry) != sigen ++ || au_iigen_test(dentry->d_inode, sigen))) ++ err = -EIO; ++ ++ return err; ++} ++ ++void au_update_digen(struct dentry *dentry) ++{ ++ atomic_set(&au_di(dentry)->di_generation, au_sigen(dentry->d_sb)); ++ /* smp_mb(); */ /* atomic_set */ ++} ++ ++void au_update_dbrange(struct dentry *dentry, int do_put_zero) ++{ ++ struct au_dinfo *dinfo; ++ struct dentry *h_d; ++ struct au_hdentry *hdp; ++ ++ DiMustWriteLock(dentry); ++ ++ dinfo = au_di(dentry); ++ if (!dinfo || dinfo->di_bstart < 0) ++ return; ++ ++ hdp = dinfo->di_hdentry; ++ if (do_put_zero) { ++ aufs_bindex_t bindex, bend; ++ ++ bend = dinfo->di_bend; ++ for (bindex = dinfo->di_bstart; bindex <= bend; bindex++) { ++ h_d = hdp[0 + bindex].hd_dentry; ++ if (h_d && !h_d->d_inode) ++ au_set_h_dptr(dentry, bindex, NULL); ++ } ++ } ++ ++ dinfo->di_bstart = -1; ++ while (++dinfo->di_bstart <= dinfo->di_bend) ++ if (hdp[0 + dinfo->di_bstart].hd_dentry) ++ break; ++ if (dinfo->di_bstart > dinfo->di_bend) { ++ dinfo->di_bstart = -1; ++ dinfo->di_bend = -1; ++ return; ++ } ++ ++ dinfo->di_bend++; ++ while (0 <= --dinfo->di_bend) ++ if (hdp[0 + dinfo->di_bend].hd_dentry) ++ break; ++ AuDebugOn(dinfo->di_bstart > dinfo->di_bend || dinfo->di_bend < 0); ++} ++ ++void au_update_dbstart(struct dentry *dentry) ++{ ++ aufs_bindex_t bindex, bend; ++ struct dentry *h_dentry; ++ ++ bend = au_dbend(dentry); ++ for (bindex = au_dbstart(dentry); bindex <= bend; bindex++) { ++ h_dentry = au_h_dptr(dentry, bindex); ++ if (!h_dentry) ++ continue; ++ if (h_dentry->d_inode) { ++ au_set_dbstart(dentry, bindex); ++ return; ++ } ++ au_set_h_dptr(dentry, bindex, NULL); ++ } ++} ++ ++void au_update_dbend(struct dentry *dentry) ++{ ++ aufs_bindex_t bindex, bstart; ++ struct dentry *h_dentry; ++ ++ bstart = au_dbstart(dentry); ++ for (bindex = au_dbend(dentry); bindex >= bstart; bindex--) { ++ h_dentry = au_h_dptr(dentry, bindex); ++ if (!h_dentry) ++ continue; ++ if (h_dentry->d_inode) { ++ au_set_dbend(dentry, bindex); ++ return; ++ } ++ au_set_h_dptr(dentry, bindex, NULL); ++ } ++} ++ ++int au_find_dbindex(struct dentry *dentry, struct dentry *h_dentry) ++{ ++ aufs_bindex_t bindex, bend; ++ ++ bend = au_dbend(dentry); ++ for (bindex = au_dbstart(dentry); bindex <= bend; bindex++) ++ if (au_h_dptr(dentry, bindex) == h_dentry) ++ return bindex; ++ return -1; ++} +diff --git a/fs/aufs/dir.c b/fs/aufs/dir.c +new file mode 100644 +index 0000000..3d61b05 +--- /dev/null ++++ b/fs/aufs/dir.c +@@ -0,0 +1,756 @@ ++/* ++ * Copyright (C) 2005-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * directory operations ++ */ ++ ++#include ++#include "aufs.h" ++ ++void au_add_nlink(struct inode *dir, struct inode *h_dir) ++{ ++ unsigned int nlink; ++ ++ AuDebugOn(!S_ISDIR(dir->i_mode) || !S_ISDIR(h_dir->i_mode)); ++ ++ nlink = dir->i_nlink; ++ nlink += h_dir->i_nlink - 2; ++ if (h_dir->i_nlink < 2) ++ nlink += 2; ++ smp_mb(); /* for i_nlink */ ++ /* 0 can happen in revaliding */ ++ set_nlink(dir, nlink); ++} ++ ++void au_sub_nlink(struct inode *dir, struct inode *h_dir) ++{ ++ unsigned int nlink; ++ ++ AuDebugOn(!S_ISDIR(dir->i_mode) || !S_ISDIR(h_dir->i_mode)); ++ ++ nlink = dir->i_nlink; ++ nlink -= h_dir->i_nlink - 2; ++ if (h_dir->i_nlink < 2) ++ nlink -= 2; ++ smp_mb(); /* for i_nlink */ ++ /* nlink == 0 means the branch-fs is broken */ ++ set_nlink(dir, nlink); ++} ++ ++loff_t au_dir_size(struct file *file, struct dentry *dentry) ++{ ++ loff_t sz; ++ aufs_bindex_t bindex, bend; ++ struct file *h_file; ++ struct dentry *h_dentry; ++ ++ sz = 0; ++ if (file) { ++ AuDebugOn(!d_is_dir(file->f_path.dentry)); ++ ++ bend = au_fbend_dir(file); ++ for (bindex = au_fbstart(file); ++ bindex <= bend && sz < KMALLOC_MAX_SIZE; ++ bindex++) { ++ h_file = au_hf_dir(file, bindex); ++ if (h_file && file_inode(h_file)) ++ sz += vfsub_f_size_read(h_file); ++ } ++ } else { ++ AuDebugOn(!dentry); ++ AuDebugOn(!d_is_dir(dentry)); ++ ++ bend = au_dbtaildir(dentry); ++ for (bindex = au_dbstart(dentry); ++ bindex <= bend && sz < KMALLOC_MAX_SIZE; ++ bindex++) { ++ h_dentry = au_h_dptr(dentry, bindex); ++ if (h_dentry && h_dentry->d_inode) ++ sz += i_size_read(h_dentry->d_inode); ++ } ++ } ++ if (sz < KMALLOC_MAX_SIZE) ++ sz = roundup_pow_of_two(sz); ++ if (sz > KMALLOC_MAX_SIZE) ++ sz = KMALLOC_MAX_SIZE; ++ else if (sz < NAME_MAX) { ++ BUILD_BUG_ON(AUFS_RDBLK_DEF < NAME_MAX); ++ sz = AUFS_RDBLK_DEF; ++ } ++ return sz; ++} ++ ++struct au_dir_ts_arg { ++ struct dentry *dentry; ++ aufs_bindex_t brid; ++}; ++ ++static void au_do_dir_ts(void *arg) ++{ ++ struct au_dir_ts_arg *a = arg; ++ struct au_dtime dt; ++ struct path h_path; ++ struct inode *dir, *h_dir; ++ struct super_block *sb; ++ struct au_branch *br; ++ struct au_hinode *hdir; ++ int err; ++ aufs_bindex_t bstart, bindex; ++ ++ sb = a->dentry->d_sb; ++ dir = a->dentry->d_inode; ++ if (!dir) ++ goto out; ++ /* no dir->i_mutex lock */ ++ aufs_read_lock(a->dentry, AuLock_DW); /* noflush */ ++ ++ bstart = au_ibstart(dir); ++ bindex = au_br_index(sb, a->brid); ++ if (bindex < bstart) ++ goto out_unlock; ++ ++ br = au_sbr(sb, bindex); ++ h_path.dentry = au_h_dptr(a->dentry, bindex); ++ if (!h_path.dentry) ++ goto out_unlock; ++ h_path.mnt = au_br_mnt(br); ++ au_dtime_store(&dt, a->dentry, &h_path); ++ ++ br = au_sbr(sb, bstart); ++ if (!au_br_writable(br->br_perm)) ++ goto out_unlock; ++ h_path.dentry = au_h_dptr(a->dentry, bstart); ++ h_path.mnt = au_br_mnt(br); ++ err = vfsub_mnt_want_write(h_path.mnt); ++ if (err) ++ goto out_unlock; ++ hdir = au_hi(dir, bstart); ++ au_hn_imtx_lock_nested(hdir, AuLsc_I_PARENT); ++ h_dir = au_h_iptr(dir, bstart); ++ if (h_dir->i_nlink ++ && timespec_compare(&h_dir->i_mtime, &dt.dt_mtime) < 0) { ++ dt.dt_h_path = h_path; ++ au_dtime_revert(&dt); ++ } ++ au_hn_imtx_unlock(hdir); ++ vfsub_mnt_drop_write(h_path.mnt); ++ au_cpup_attr_timesizes(dir); ++ ++out_unlock: ++ aufs_read_unlock(a->dentry, AuLock_DW); ++out: ++ dput(a->dentry); ++ au_nwt_done(&au_sbi(sb)->si_nowait); ++ kfree(arg); ++} ++ ++void au_dir_ts(struct inode *dir, aufs_bindex_t bindex) ++{ ++ int perm, wkq_err; ++ aufs_bindex_t bstart; ++ struct au_dir_ts_arg *arg; ++ struct dentry *dentry; ++ struct super_block *sb; ++ ++ IMustLock(dir); ++ ++ dentry = d_find_any_alias(dir); ++ AuDebugOn(!dentry); ++ sb = dentry->d_sb; ++ bstart = au_ibstart(dir); ++ if (bstart == bindex) { ++ au_cpup_attr_timesizes(dir); ++ goto out; ++ } ++ ++ perm = au_sbr_perm(sb, bstart); ++ if (!au_br_writable(perm)) ++ goto out; ++ ++ arg = kmalloc(sizeof(*arg), GFP_NOFS); ++ if (!arg) ++ goto out; ++ ++ arg->dentry = dget(dentry); /* will be dput-ted by au_do_dir_ts() */ ++ arg->brid = au_sbr_id(sb, bindex); ++ wkq_err = au_wkq_nowait(au_do_dir_ts, arg, sb, /*flags*/0); ++ if (unlikely(wkq_err)) { ++ pr_err("wkq %d\n", wkq_err); ++ dput(dentry); ++ kfree(arg); ++ } ++ ++out: ++ dput(dentry); ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++static int reopen_dir(struct file *file) ++{ ++ int err; ++ unsigned int flags; ++ aufs_bindex_t bindex, btail, bstart; ++ struct dentry *dentry, *h_dentry; ++ struct file *h_file; ++ ++ /* open all lower dirs */ ++ dentry = file->f_dentry; ++ bstart = au_dbstart(dentry); ++ for (bindex = au_fbstart(file); bindex < bstart; bindex++) ++ au_set_h_fptr(file, bindex, NULL); ++ au_set_fbstart(file, bstart); ++ ++ btail = au_dbtaildir(dentry); ++ for (bindex = au_fbend_dir(file); btail < bindex; bindex--) ++ au_set_h_fptr(file, bindex, NULL); ++ au_set_fbend_dir(file, btail); ++ ++ flags = vfsub_file_flags(file); ++ for (bindex = bstart; bindex <= btail; bindex++) { ++ h_dentry = au_h_dptr(dentry, bindex); ++ if (!h_dentry) ++ continue; ++ h_file = au_hf_dir(file, bindex); ++ if (h_file) ++ continue; ++ ++ h_file = au_h_open(dentry, bindex, flags, file, /*force_wr*/0); ++ err = PTR_ERR(h_file); ++ if (IS_ERR(h_file)) ++ goto out; /* close all? */ ++ au_set_h_fptr(file, bindex, h_file); ++ } ++ au_update_figen(file); ++ /* todo: necessary? */ ++ /* file->f_ra = h_file->f_ra; */ ++ err = 0; ++ ++out: ++ return err; ++} ++ ++static int do_open_dir(struct file *file, int flags, struct file *h_file) ++{ ++ int err; ++ aufs_bindex_t bindex, btail; ++ struct dentry *dentry, *h_dentry; ++ struct vfsmount *mnt; ++ ++ FiMustWriteLock(file); ++ AuDebugOn(h_file); ++ ++ err = 0; ++ mnt = file->f_path.mnt; ++ dentry = file->f_dentry; ++ file->f_version = dentry->d_inode->i_version; ++ bindex = au_dbstart(dentry); ++ au_set_fbstart(file, bindex); ++ btail = au_dbtaildir(dentry); ++ au_set_fbend_dir(file, btail); ++ for (; !err && bindex <= btail; bindex++) { ++ h_dentry = au_h_dptr(dentry, bindex); ++ if (!h_dentry) ++ continue; ++ ++ err = vfsub_test_mntns(mnt, h_dentry->d_sb); ++ if (unlikely(err)) ++ break; ++ h_file = au_h_open(dentry, bindex, flags, file, /*force_wr*/0); ++ if (IS_ERR(h_file)) { ++ err = PTR_ERR(h_file); ++ break; ++ } ++ au_set_h_fptr(file, bindex, h_file); ++ } ++ au_update_figen(file); ++ /* todo: necessary? */ ++ /* file->f_ra = h_file->f_ra; */ ++ if (!err) ++ return 0; /* success */ ++ ++ /* close all */ ++ for (bindex = au_fbstart(file); bindex <= btail; bindex++) ++ au_set_h_fptr(file, bindex, NULL); ++ au_set_fbstart(file, -1); ++ au_set_fbend_dir(file, -1); ++ ++ return err; ++} ++ ++static int aufs_open_dir(struct inode *inode __maybe_unused, ++ struct file *file) ++{ ++ int err; ++ struct super_block *sb; ++ struct au_fidir *fidir; ++ ++ err = -ENOMEM; ++ sb = file->f_dentry->d_sb; ++ si_read_lock(sb, AuLock_FLUSH); ++ fidir = au_fidir_alloc(sb); ++ if (fidir) { ++ struct au_do_open_args args = { ++ .open = do_open_dir, ++ .fidir = fidir ++ }; ++ err = au_do_open(file, &args); ++ if (unlikely(err)) ++ kfree(fidir); ++ } ++ si_read_unlock(sb); ++ return err; ++} ++ ++static int aufs_release_dir(struct inode *inode __maybe_unused, ++ struct file *file) ++{ ++ struct au_vdir *vdir_cache; ++ struct au_finfo *finfo; ++ struct au_fidir *fidir; ++ aufs_bindex_t bindex, bend; ++ ++ finfo = au_fi(file); ++ fidir = finfo->fi_hdir; ++ if (fidir) { ++ au_sphl_del(&finfo->fi_hlist, ++ &au_sbi(file->f_dentry->d_sb)->si_files); ++ vdir_cache = fidir->fd_vdir_cache; /* lock-free */ ++ if (vdir_cache) ++ au_vdir_free(vdir_cache); ++ ++ bindex = finfo->fi_btop; ++ if (bindex >= 0) { ++ /* ++ * calls fput() instead of filp_close(), ++ * since no dnotify or lock for the lower file. ++ */ ++ bend = fidir->fd_bbot; ++ for (; bindex <= bend; bindex++) ++ au_set_h_fptr(file, bindex, NULL); ++ } ++ kfree(fidir); ++ finfo->fi_hdir = NULL; ++ } ++ au_finfo_fin(file); ++ return 0; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++static int au_do_flush_dir(struct file *file, fl_owner_t id) ++{ ++ int err; ++ aufs_bindex_t bindex, bend; ++ struct file *h_file; ++ ++ err = 0; ++ bend = au_fbend_dir(file); ++ for (bindex = au_fbstart(file); !err && bindex <= bend; bindex++) { ++ h_file = au_hf_dir(file, bindex); ++ if (h_file) ++ err = vfsub_flush(h_file, id); ++ } ++ return err; ++} ++ ++static int aufs_flush_dir(struct file *file, fl_owner_t id) ++{ ++ return au_do_flush(file, id, au_do_flush_dir); ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++static int au_do_fsync_dir_no_file(struct dentry *dentry, int datasync) ++{ ++ int err; ++ aufs_bindex_t bend, bindex; ++ struct inode *inode; ++ struct super_block *sb; ++ ++ err = 0; ++ sb = dentry->d_sb; ++ inode = dentry->d_inode; ++ IMustLock(inode); ++ bend = au_dbend(dentry); ++ for (bindex = au_dbstart(dentry); !err && bindex <= bend; bindex++) { ++ struct path h_path; ++ ++ if (au_test_ro(sb, bindex, inode)) ++ continue; ++ h_path.dentry = au_h_dptr(dentry, bindex); ++ if (!h_path.dentry) ++ continue; ++ ++ h_path.mnt = au_sbr_mnt(sb, bindex); ++ err = vfsub_fsync(NULL, &h_path, datasync); ++ } ++ ++ return err; ++} ++ ++static int au_do_fsync_dir(struct file *file, int datasync) ++{ ++ int err; ++ aufs_bindex_t bend, bindex; ++ struct file *h_file; ++ struct super_block *sb; ++ struct inode *inode; ++ ++ err = au_reval_and_lock_fdi(file, reopen_dir, /*wlock*/1); ++ if (unlikely(err)) ++ goto out; ++ ++ inode = file_inode(file); ++ sb = inode->i_sb; ++ bend = au_fbend_dir(file); ++ for (bindex = au_fbstart(file); !err && bindex <= bend; bindex++) { ++ h_file = au_hf_dir(file, bindex); ++ if (!h_file || au_test_ro(sb, bindex, inode)) ++ continue; ++ ++ err = vfsub_fsync(h_file, &h_file->f_path, datasync); ++ } ++ ++out: ++ return err; ++} ++ ++/* ++ * @file may be NULL ++ */ ++static int aufs_fsync_dir(struct file *file, loff_t start, loff_t end, ++ int datasync) ++{ ++ int err; ++ struct dentry *dentry; ++ struct super_block *sb; ++ struct mutex *mtx; ++ ++ err = 0; ++ dentry = file->f_dentry; ++ mtx = &dentry->d_inode->i_mutex; ++ mutex_lock(mtx); ++ sb = dentry->d_sb; ++ si_noflush_read_lock(sb); ++ if (file) ++ err = au_do_fsync_dir(file, datasync); ++ else { ++ di_write_lock_child(dentry); ++ err = au_do_fsync_dir_no_file(dentry, datasync); ++ } ++ au_cpup_attr_timesizes(dentry->d_inode); ++ di_write_unlock(dentry); ++ if (file) ++ fi_write_unlock(file); ++ ++ si_read_unlock(sb); ++ mutex_unlock(mtx); ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++static int aufs_iterate(struct file *file, struct dir_context *ctx) ++{ ++ int err; ++ struct dentry *dentry; ++ struct inode *inode, *h_inode; ++ struct super_block *sb; ++ ++ AuDbg("%pD, ctx{%pf, %llu}\n", file, ctx->actor, ctx->pos); ++ ++ dentry = file->f_dentry; ++ inode = dentry->d_inode; ++ IMustLock(inode); ++ ++ sb = dentry->d_sb; ++ si_read_lock(sb, AuLock_FLUSH); ++ err = au_reval_and_lock_fdi(file, reopen_dir, /*wlock*/1); ++ if (unlikely(err)) ++ goto out; ++ err = au_alive_dir(dentry); ++ if (!err) ++ err = au_vdir_init(file); ++ di_downgrade_lock(dentry, AuLock_IR); ++ if (unlikely(err)) ++ goto out_unlock; ++ ++ h_inode = au_h_iptr(inode, au_ibstart(inode)); ++ if (!au_test_nfsd()) { ++ err = au_vdir_fill_de(file, ctx); ++ fsstack_copy_attr_atime(inode, h_inode); ++ } else { ++ /* ++ * nfsd filldir may call lookup_one_len(), vfs_getattr(), ++ * encode_fh() and others. ++ */ ++ atomic_inc(&h_inode->i_count); ++ di_read_unlock(dentry, AuLock_IR); ++ si_read_unlock(sb); ++ err = au_vdir_fill_de(file, ctx); ++ fsstack_copy_attr_atime(inode, h_inode); ++ fi_write_unlock(file); ++ iput(h_inode); ++ ++ AuTraceErr(err); ++ return err; ++ } ++ ++out_unlock: ++ di_read_unlock(dentry, AuLock_IR); ++ fi_write_unlock(file); ++out: ++ si_read_unlock(sb); ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++#define AuTestEmpty_WHONLY 1 ++#define AuTestEmpty_CALLED (1 << 1) ++#define AuTestEmpty_SHWH (1 << 2) ++#define au_ftest_testempty(flags, name) ((flags) & AuTestEmpty_##name) ++#define au_fset_testempty(flags, name) \ ++ do { (flags) |= AuTestEmpty_##name; } while (0) ++#define au_fclr_testempty(flags, name) \ ++ do { (flags) &= ~AuTestEmpty_##name; } while (0) ++ ++#ifndef CONFIG_AUFS_SHWH ++#undef AuTestEmpty_SHWH ++#define AuTestEmpty_SHWH 0 ++#endif ++ ++struct test_empty_arg { ++ struct dir_context ctx; ++ struct au_nhash *whlist; ++ unsigned int flags; ++ int err; ++ aufs_bindex_t bindex; ++}; ++ ++static int test_empty_cb(struct dir_context *ctx, const char *__name, ++ int namelen, loff_t offset __maybe_unused, u64 ino, ++ unsigned int d_type) ++{ ++ struct test_empty_arg *arg = container_of(ctx, struct test_empty_arg, ++ ctx); ++ char *name = (void *)__name; ++ ++ arg->err = 0; ++ au_fset_testempty(arg->flags, CALLED); ++ /* smp_mb(); */ ++ if (name[0] == '.' ++ && (namelen == 1 || (name[1] == '.' && namelen == 2))) ++ goto out; /* success */ ++ ++ if (namelen <= AUFS_WH_PFX_LEN ++ || memcmp(name, AUFS_WH_PFX, AUFS_WH_PFX_LEN)) { ++ if (au_ftest_testempty(arg->flags, WHONLY) ++ && !au_nhash_test_known_wh(arg->whlist, name, namelen)) ++ arg->err = -ENOTEMPTY; ++ goto out; ++ } ++ ++ name += AUFS_WH_PFX_LEN; ++ namelen -= AUFS_WH_PFX_LEN; ++ if (!au_nhash_test_known_wh(arg->whlist, name, namelen)) ++ arg->err = au_nhash_append_wh ++ (arg->whlist, name, namelen, ino, d_type, arg->bindex, ++ au_ftest_testempty(arg->flags, SHWH)); ++ ++out: ++ /* smp_mb(); */ ++ AuTraceErr(arg->err); ++ return arg->err; ++} ++ ++static int do_test_empty(struct dentry *dentry, struct test_empty_arg *arg) ++{ ++ int err; ++ struct file *h_file; ++ ++ h_file = au_h_open(dentry, arg->bindex, ++ O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_LARGEFILE, ++ /*file*/NULL, /*force_wr*/0); ++ err = PTR_ERR(h_file); ++ if (IS_ERR(h_file)) ++ goto out; ++ ++ err = 0; ++ if (!au_opt_test(au_mntflags(dentry->d_sb), UDBA_NONE) ++ && !file_inode(h_file)->i_nlink) ++ goto out_put; ++ ++ do { ++ arg->err = 0; ++ au_fclr_testempty(arg->flags, CALLED); ++ /* smp_mb(); */ ++ err = vfsub_iterate_dir(h_file, &arg->ctx); ++ if (err >= 0) ++ err = arg->err; ++ } while (!err && au_ftest_testempty(arg->flags, CALLED)); ++ ++out_put: ++ fput(h_file); ++ au_sbr_put(dentry->d_sb, arg->bindex); ++out: ++ return err; ++} ++ ++struct do_test_empty_args { ++ int *errp; ++ struct dentry *dentry; ++ struct test_empty_arg *arg; ++}; ++ ++static void call_do_test_empty(void *args) ++{ ++ struct do_test_empty_args *a = args; ++ *a->errp = do_test_empty(a->dentry, a->arg); ++} ++ ++static int sio_test_empty(struct dentry *dentry, struct test_empty_arg *arg) ++{ ++ int err, wkq_err; ++ struct dentry *h_dentry; ++ struct inode *h_inode; ++ ++ h_dentry = au_h_dptr(dentry, arg->bindex); ++ h_inode = h_dentry->d_inode; ++ /* todo: i_mode changes anytime? */ ++ mutex_lock_nested(&h_inode->i_mutex, AuLsc_I_CHILD); ++ err = au_test_h_perm_sio(h_inode, MAY_EXEC | MAY_READ); ++ mutex_unlock(&h_inode->i_mutex); ++ if (!err) ++ err = do_test_empty(dentry, arg); ++ else { ++ struct do_test_empty_args args = { ++ .errp = &err, ++ .dentry = dentry, ++ .arg = arg ++ }; ++ unsigned int flags = arg->flags; ++ ++ wkq_err = au_wkq_wait(call_do_test_empty, &args); ++ if (unlikely(wkq_err)) ++ err = wkq_err; ++ arg->flags = flags; ++ } ++ ++ return err; ++} ++ ++int au_test_empty_lower(struct dentry *dentry) ++{ ++ int err; ++ unsigned int rdhash; ++ aufs_bindex_t bindex, bstart, btail; ++ struct au_nhash whlist; ++ struct test_empty_arg arg = { ++ .ctx = { ++ .actor = au_diractor(test_empty_cb) ++ } ++ }; ++ int (*test_empty)(struct dentry *dentry, struct test_empty_arg *arg); ++ ++ SiMustAnyLock(dentry->d_sb); ++ ++ rdhash = au_sbi(dentry->d_sb)->si_rdhash; ++ if (!rdhash) ++ rdhash = au_rdhash_est(au_dir_size(/*file*/NULL, dentry)); ++ err = au_nhash_alloc(&whlist, rdhash, GFP_NOFS); ++ if (unlikely(err)) ++ goto out; ++ ++ arg.flags = 0; ++ arg.whlist = &whlist; ++ bstart = au_dbstart(dentry); ++ if (au_opt_test(au_mntflags(dentry->d_sb), SHWH)) ++ au_fset_testempty(arg.flags, SHWH); ++ test_empty = do_test_empty; ++ if (au_opt_test(au_mntflags(dentry->d_sb), DIRPERM1)) ++ test_empty = sio_test_empty; ++ arg.bindex = bstart; ++ err = test_empty(dentry, &arg); ++ if (unlikely(err)) ++ goto out_whlist; ++ ++ au_fset_testempty(arg.flags, WHONLY); ++ btail = au_dbtaildir(dentry); ++ for (bindex = bstart + 1; !err && bindex <= btail; bindex++) { ++ struct dentry *h_dentry; ++ ++ h_dentry = au_h_dptr(dentry, bindex); ++ if (h_dentry && h_dentry->d_inode) { ++ arg.bindex = bindex; ++ err = test_empty(dentry, &arg); ++ } ++ } ++ ++out_whlist: ++ au_nhash_wh_free(&whlist); ++out: ++ return err; ++} ++ ++int au_test_empty(struct dentry *dentry, struct au_nhash *whlist) ++{ ++ int err; ++ struct test_empty_arg arg = { ++ .ctx = { ++ .actor = au_diractor(test_empty_cb) ++ } ++ }; ++ aufs_bindex_t bindex, btail; ++ ++ err = 0; ++ arg.whlist = whlist; ++ arg.flags = AuTestEmpty_WHONLY; ++ if (au_opt_test(au_mntflags(dentry->d_sb), SHWH)) ++ au_fset_testempty(arg.flags, SHWH); ++ btail = au_dbtaildir(dentry); ++ for (bindex = au_dbstart(dentry); !err && bindex <= btail; bindex++) { ++ struct dentry *h_dentry; ++ ++ h_dentry = au_h_dptr(dentry, bindex); ++ if (h_dentry && h_dentry->d_inode) { ++ arg.bindex = bindex; ++ err = sio_test_empty(dentry, &arg); ++ } ++ } ++ ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++const struct file_operations aufs_dir_fop = { ++ .owner = THIS_MODULE, ++ .llseek = default_llseek, ++ .read = generic_read_dir, ++ .iterate = aufs_iterate, ++ .unlocked_ioctl = aufs_ioctl_dir, ++#ifdef CONFIG_COMPAT ++ .compat_ioctl = aufs_compat_ioctl_dir, ++#endif ++ .open = aufs_open_dir, ++ .release = aufs_release_dir, ++ .flush = aufs_flush_dir, ++ .fsync = aufs_fsync_dir ++}; +diff --git a/fs/aufs/dir.h b/fs/aufs/dir.h +new file mode 100644 +index 0000000..16821f9 +--- /dev/null ++++ b/fs/aufs/dir.h +@@ -0,0 +1,131 @@ ++/* ++ * Copyright (C) 2005-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * directory operations ++ */ ++ ++#ifndef __AUFS_DIR_H__ ++#define __AUFS_DIR_H__ ++ ++#ifdef __KERNEL__ ++ ++#include ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* need to be faster and smaller */ ++ ++struct au_nhash { ++ unsigned int nh_num; ++ struct hlist_head *nh_head; ++}; ++ ++struct au_vdir_destr { ++ unsigned char len; ++ unsigned char name[0]; ++} __packed; ++ ++struct au_vdir_dehstr { ++ struct hlist_node hash; ++ struct au_vdir_destr *str; ++} ____cacheline_aligned_in_smp; ++ ++struct au_vdir_de { ++ ino_t de_ino; ++ unsigned char de_type; ++ /* caution: packed */ ++ struct au_vdir_destr de_str; ++} __packed; ++ ++struct au_vdir_wh { ++ struct hlist_node wh_hash; ++#ifdef CONFIG_AUFS_SHWH ++ ino_t wh_ino; ++ aufs_bindex_t wh_bindex; ++ unsigned char wh_type; ++#else ++ aufs_bindex_t wh_bindex; ++#endif ++ /* caution: packed */ ++ struct au_vdir_destr wh_str; ++} __packed; ++ ++union au_vdir_deblk_p { ++ unsigned char *deblk; ++ struct au_vdir_de *de; ++}; ++ ++struct au_vdir { ++ unsigned char **vd_deblk; ++ unsigned long vd_nblk; ++ struct { ++ unsigned long ul; ++ union au_vdir_deblk_p p; ++ } vd_last; ++ ++ unsigned long vd_version; ++ unsigned int vd_deblk_sz; ++ unsigned long vd_jiffy; ++} ____cacheline_aligned_in_smp; ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* dir.c */ ++extern const struct file_operations aufs_dir_fop; ++void au_add_nlink(struct inode *dir, struct inode *h_dir); ++void au_sub_nlink(struct inode *dir, struct inode *h_dir); ++loff_t au_dir_size(struct file *file, struct dentry *dentry); ++void au_dir_ts(struct inode *dir, aufs_bindex_t bsrc); ++int au_test_empty_lower(struct dentry *dentry); ++int au_test_empty(struct dentry *dentry, struct au_nhash *whlist); ++ ++/* vdir.c */ ++unsigned int au_rdhash_est(loff_t sz); ++int au_nhash_alloc(struct au_nhash *nhash, unsigned int num_hash, gfp_t gfp); ++void au_nhash_wh_free(struct au_nhash *whlist); ++int au_nhash_test_longer_wh(struct au_nhash *whlist, aufs_bindex_t btgt, ++ int limit); ++int au_nhash_test_known_wh(struct au_nhash *whlist, char *name, int nlen); ++int au_nhash_append_wh(struct au_nhash *whlist, char *name, int nlen, ino_t ino, ++ unsigned int d_type, aufs_bindex_t bindex, ++ unsigned char shwh); ++void au_vdir_free(struct au_vdir *vdir); ++int au_vdir_init(struct file *file); ++int au_vdir_fill_de(struct file *file, struct dir_context *ctx); ++ ++/* ioctl.c */ ++long aufs_ioctl_dir(struct file *file, unsigned int cmd, unsigned long arg); ++ ++#ifdef CONFIG_AUFS_RDU ++/* rdu.c */ ++long au_rdu_ioctl(struct file *file, unsigned int cmd, unsigned long arg); ++#ifdef CONFIG_COMPAT ++long au_rdu_compat_ioctl(struct file *file, unsigned int cmd, ++ unsigned long arg); ++#endif ++#else ++AuStub(long, au_rdu_ioctl, return -EINVAL, struct file *file, ++ unsigned int cmd, unsigned long arg) ++#ifdef CONFIG_COMPAT ++AuStub(long, au_rdu_compat_ioctl, return -EINVAL, struct file *file, ++ unsigned int cmd, unsigned long arg) ++#endif ++#endif ++ ++#endif /* __KERNEL__ */ ++#endif /* __AUFS_DIR_H__ */ +diff --git a/fs/aufs/dynop.c b/fs/aufs/dynop.c +new file mode 100644 +index 0000000..d758805 +--- /dev/null ++++ b/fs/aufs/dynop.c +@@ -0,0 +1,379 @@ ++/* ++ * Copyright (C) 2010-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * dynamically customizable operations for regular files ++ */ ++ ++#include "aufs.h" ++ ++#define DyPrSym(key) AuDbgSym(key->dk_op.dy_hop) ++ ++/* ++ * How large will these lists be? ++ * Usually just a few elements, 20-30 at most for each, I guess. ++ */ ++static struct au_splhead dynop[AuDyLast]; ++ ++static struct au_dykey *dy_gfind_get(struct au_splhead *spl, const void *h_op) ++{ ++ struct au_dykey *key, *tmp; ++ struct list_head *head; ++ ++ key = NULL; ++ head = &spl->head; ++ rcu_read_lock(); ++ list_for_each_entry_rcu(tmp, head, dk_list) ++ if (tmp->dk_op.dy_hop == h_op) { ++ key = tmp; ++ kref_get(&key->dk_kref); ++ break; ++ } ++ rcu_read_unlock(); ++ ++ return key; ++} ++ ++static struct au_dykey *dy_bradd(struct au_branch *br, struct au_dykey *key) ++{ ++ struct au_dykey **k, *found; ++ const void *h_op = key->dk_op.dy_hop; ++ int i; ++ ++ found = NULL; ++ k = br->br_dykey; ++ for (i = 0; i < AuBrDynOp; i++) ++ if (k[i]) { ++ if (k[i]->dk_op.dy_hop == h_op) { ++ found = k[i]; ++ break; ++ } ++ } else ++ break; ++ if (!found) { ++ spin_lock(&br->br_dykey_lock); ++ for (; i < AuBrDynOp; i++) ++ if (k[i]) { ++ if (k[i]->dk_op.dy_hop == h_op) { ++ found = k[i]; ++ break; ++ } ++ } else { ++ k[i] = key; ++ break; ++ } ++ spin_unlock(&br->br_dykey_lock); ++ BUG_ON(i == AuBrDynOp); /* expand the array */ ++ } ++ ++ return found; ++} ++ ++/* kref_get() if @key is already added */ ++static struct au_dykey *dy_gadd(struct au_splhead *spl, struct au_dykey *key) ++{ ++ struct au_dykey *tmp, *found; ++ struct list_head *head; ++ const void *h_op = key->dk_op.dy_hop; ++ ++ found = NULL; ++ head = &spl->head; ++ spin_lock(&spl->spin); ++ list_for_each_entry(tmp, head, dk_list) ++ if (tmp->dk_op.dy_hop == h_op) { ++ kref_get(&tmp->dk_kref); ++ found = tmp; ++ break; ++ } ++ if (!found) ++ list_add_rcu(&key->dk_list, head); ++ spin_unlock(&spl->spin); ++ ++ if (!found) ++ DyPrSym(key); ++ return found; ++} ++ ++static void dy_free_rcu(struct rcu_head *rcu) ++{ ++ struct au_dykey *key; ++ ++ key = container_of(rcu, struct au_dykey, dk_rcu); ++ DyPrSym(key); ++ kfree(key); ++} ++ ++static void dy_free(struct kref *kref) ++{ ++ struct au_dykey *key; ++ struct au_splhead *spl; ++ ++ key = container_of(kref, struct au_dykey, dk_kref); ++ spl = dynop + key->dk_op.dy_type; ++ au_spl_del_rcu(&key->dk_list, spl); ++ call_rcu(&key->dk_rcu, dy_free_rcu); ++} ++ ++void au_dy_put(struct au_dykey *key) ++{ ++ kref_put(&key->dk_kref, dy_free); ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++#define DyDbgSize(cnt, op) AuDebugOn(cnt != sizeof(op)/sizeof(void *)) ++ ++#ifdef CONFIG_AUFS_DEBUG ++#define DyDbgDeclare(cnt) unsigned int cnt = 0 ++#define DyDbgInc(cnt) do { cnt++; } while (0) ++#else ++#define DyDbgDeclare(cnt) do {} while (0) ++#define DyDbgInc(cnt) do {} while (0) ++#endif ++ ++#define DySet(func, dst, src, h_op, h_sb) do { \ ++ DyDbgInc(cnt); \ ++ if (h_op->func) { \ ++ if (src.func) \ ++ dst.func = src.func; \ ++ else \ ++ AuDbg("%s %s\n", au_sbtype(h_sb), #func); \ ++ } \ ++} while (0) ++ ++#define DySetForce(func, dst, src) do { \ ++ AuDebugOn(!src.func); \ ++ DyDbgInc(cnt); \ ++ dst.func = src.func; \ ++} while (0) ++ ++#define DySetAop(func) \ ++ DySet(func, dyaop->da_op, aufs_aop, h_aop, h_sb) ++#define DySetAopForce(func) \ ++ DySetForce(func, dyaop->da_op, aufs_aop) ++ ++static void dy_aop(struct au_dykey *key, const void *h_op, ++ struct super_block *h_sb __maybe_unused) ++{ ++ struct au_dyaop *dyaop = (void *)key; ++ const struct address_space_operations *h_aop = h_op; ++ DyDbgDeclare(cnt); ++ ++ AuDbg("%s\n", au_sbtype(h_sb)); ++ ++ DySetAop(writepage); ++ DySetAopForce(readpage); /* force */ ++ DySetAop(writepages); ++ DySetAop(set_page_dirty); ++ DySetAop(readpages); ++ DySetAop(write_begin); ++ DySetAop(write_end); ++ DySetAop(bmap); ++ DySetAop(invalidatepage); ++ DySetAop(releasepage); ++ DySetAop(freepage); ++ /* these two will be changed according to an aufs mount option */ ++ DySetAop(direct_IO); ++ DySetAop(get_xip_mem); ++ DySetAop(migratepage); ++ DySetAop(launder_page); ++ DySetAop(is_partially_uptodate); ++ DySetAop(is_dirty_writeback); ++ DySetAop(error_remove_page); ++ DySetAop(swap_activate); ++ DySetAop(swap_deactivate); ++ ++ DyDbgSize(cnt, *h_aop); ++ dyaop->da_get_xip_mem = h_aop->get_xip_mem; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++static void dy_bug(struct kref *kref) ++{ ++ BUG(); ++} ++ ++static struct au_dykey *dy_get(struct au_dynop *op, struct au_branch *br) ++{ ++ struct au_dykey *key, *old; ++ struct au_splhead *spl; ++ struct op { ++ unsigned int sz; ++ void (*set)(struct au_dykey *key, const void *h_op, ++ struct super_block *h_sb __maybe_unused); ++ }; ++ static const struct op a[] = { ++ [AuDy_AOP] = { ++ .sz = sizeof(struct au_dyaop), ++ .set = dy_aop ++ } ++ }; ++ const struct op *p; ++ ++ spl = dynop + op->dy_type; ++ key = dy_gfind_get(spl, op->dy_hop); ++ if (key) ++ goto out_add; /* success */ ++ ++ p = a + op->dy_type; ++ key = kzalloc(p->sz, GFP_NOFS); ++ if (unlikely(!key)) { ++ key = ERR_PTR(-ENOMEM); ++ goto out; ++ } ++ ++ key->dk_op.dy_hop = op->dy_hop; ++ kref_init(&key->dk_kref); ++ p->set(key, op->dy_hop, au_br_sb(br)); ++ old = dy_gadd(spl, key); ++ if (old) { ++ kfree(key); ++ key = old; ++ } ++ ++out_add: ++ old = dy_bradd(br, key); ++ if (old) ++ /* its ref-count should never be zero here */ ++ kref_put(&key->dk_kref, dy_bug); ++out: ++ return key; ++} ++ ++/* ---------------------------------------------------------------------- */ ++/* ++ * Aufs prohibits O_DIRECT by defaut even if the branch supports it. ++ * This behaviour is necessary to return an error from open(O_DIRECT) instead ++ * of the succeeding I/O. The dio mount option enables O_DIRECT and makes ++ * open(O_DIRECT) always succeed, but the succeeding I/O may return an error. ++ * See the aufs manual in detail. ++ * ++ * To keep this behaviour, aufs has to set NULL to ->get_xip_mem too, and the ++ * performance of fadvise() and madvise() may be affected. ++ */ ++static void dy_adx(struct au_dyaop *dyaop, int do_dx) ++{ ++ if (!do_dx) { ++ dyaop->da_op.direct_IO = NULL; ++ dyaop->da_op.get_xip_mem = NULL; ++ } else { ++ dyaop->da_op.direct_IO = aufs_aop.direct_IO; ++ dyaop->da_op.get_xip_mem = aufs_aop.get_xip_mem; ++ if (!dyaop->da_get_xip_mem) ++ dyaop->da_op.get_xip_mem = NULL; ++ } ++} ++ ++static struct au_dyaop *dy_aget(struct au_branch *br, ++ const struct address_space_operations *h_aop, ++ int do_dx) ++{ ++ struct au_dyaop *dyaop; ++ struct au_dynop op; ++ ++ op.dy_type = AuDy_AOP; ++ op.dy_haop = h_aop; ++ dyaop = (void *)dy_get(&op, br); ++ if (IS_ERR(dyaop)) ++ goto out; ++ dy_adx(dyaop, do_dx); ++ ++out: ++ return dyaop; ++} ++ ++int au_dy_iaop(struct inode *inode, aufs_bindex_t bindex, ++ struct inode *h_inode) ++{ ++ int err, do_dx; ++ struct super_block *sb; ++ struct au_branch *br; ++ struct au_dyaop *dyaop; ++ ++ AuDebugOn(!S_ISREG(h_inode->i_mode)); ++ IiMustWriteLock(inode); ++ ++ sb = inode->i_sb; ++ br = au_sbr(sb, bindex); ++ do_dx = !!au_opt_test(au_mntflags(sb), DIO); ++ dyaop = dy_aget(br, h_inode->i_mapping->a_ops, do_dx); ++ err = PTR_ERR(dyaop); ++ if (IS_ERR(dyaop)) ++ /* unnecessary to call dy_fput() */ ++ goto out; ++ ++ err = 0; ++ inode->i_mapping->a_ops = &dyaop->da_op; ++ ++out: ++ return err; ++} ++ ++/* ++ * Is it safe to replace a_ops during the inode/file is in operation? ++ * Yes, I hope so. ++ */ ++int au_dy_irefresh(struct inode *inode) ++{ ++ int err; ++ aufs_bindex_t bstart; ++ struct inode *h_inode; ++ ++ err = 0; ++ if (S_ISREG(inode->i_mode)) { ++ bstart = au_ibstart(inode); ++ h_inode = au_h_iptr(inode, bstart); ++ err = au_dy_iaop(inode, bstart, h_inode); ++ } ++ return err; ++} ++ ++void au_dy_arefresh(int do_dx) ++{ ++ struct au_splhead *spl; ++ struct list_head *head; ++ struct au_dykey *key; ++ ++ spl = dynop + AuDy_AOP; ++ head = &spl->head; ++ spin_lock(&spl->spin); ++ list_for_each_entry(key, head, dk_list) ++ dy_adx((void *)key, do_dx); ++ spin_unlock(&spl->spin); ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++void __init au_dy_init(void) ++{ ++ int i; ++ ++ /* make sure that 'struct au_dykey *' can be any type */ ++ BUILD_BUG_ON(offsetof(struct au_dyaop, da_key)); ++ ++ for (i = 0; i < AuDyLast; i++) ++ au_spl_init(dynop + i); ++} ++ ++void au_dy_fin(void) ++{ ++ int i; ++ ++ for (i = 0; i < AuDyLast; i++) ++ WARN_ON(!list_empty(&dynop[i].head)); ++} +diff --git a/fs/aufs/dynop.h b/fs/aufs/dynop.h +new file mode 100644 +index 0000000..cdf1499 +--- /dev/null ++++ b/fs/aufs/dynop.h +@@ -0,0 +1,76 @@ ++/* ++ * Copyright (C) 2010-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * dynamically customizable operations (for regular files only) ++ */ ++ ++#ifndef __AUFS_DYNOP_H__ ++#define __AUFS_DYNOP_H__ ++ ++#ifdef __KERNEL__ ++ ++#include ++#include ++ ++enum {AuDy_AOP, AuDyLast}; ++ ++struct au_dynop { ++ int dy_type; ++ union { ++ const void *dy_hop; ++ const struct address_space_operations *dy_haop; ++ }; ++}; ++ ++struct au_dykey { ++ union { ++ struct list_head dk_list; ++ struct rcu_head dk_rcu; ++ }; ++ struct au_dynop dk_op; ++ ++ /* ++ * during I am in the branch local array, kref is gotten. when the ++ * branch is removed, kref is put. ++ */ ++ struct kref dk_kref; ++}; ++ ++/* stop unioning since their sizes are very different from each other */ ++struct au_dyaop { ++ struct au_dykey da_key; ++ struct address_space_operations da_op; /* not const */ ++ int (*da_get_xip_mem)(struct address_space *, pgoff_t, int, ++ void **, unsigned long *); ++}; ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* dynop.c */ ++struct au_branch; ++void au_dy_put(struct au_dykey *key); ++int au_dy_iaop(struct inode *inode, aufs_bindex_t bindex, ++ struct inode *h_inode); ++int au_dy_irefresh(struct inode *inode); ++void au_dy_arefresh(int do_dio); ++ ++void __init au_dy_init(void); ++void au_dy_fin(void); ++ ++#endif /* __KERNEL__ */ ++#endif /* __AUFS_DYNOP_H__ */ +diff --git a/fs/aufs/export.c b/fs/aufs/export.c +new file mode 100644 +index 0000000..c5bfa76 +--- /dev/null ++++ b/fs/aufs/export.c +@@ -0,0 +1,831 @@ ++/* ++ * Copyright (C) 2005-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * export via nfs ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include "../fs/mount.h" ++#include "aufs.h" ++ ++union conv { ++#ifdef CONFIG_AUFS_INO_T_64 ++ __u32 a[2]; ++#else ++ __u32 a[1]; ++#endif ++ ino_t ino; ++}; ++ ++static ino_t decode_ino(__u32 *a) ++{ ++ union conv u; ++ ++ BUILD_BUG_ON(sizeof(u.ino) != sizeof(u.a)); ++ u.a[0] = a[0]; ++#ifdef CONFIG_AUFS_INO_T_64 ++ u.a[1] = a[1]; ++#endif ++ return u.ino; ++} ++ ++static void encode_ino(__u32 *a, ino_t ino) ++{ ++ union conv u; ++ ++ u.ino = ino; ++ a[0] = u.a[0]; ++#ifdef CONFIG_AUFS_INO_T_64 ++ a[1] = u.a[1]; ++#endif ++} ++ ++/* NFS file handle */ ++enum { ++ Fh_br_id, ++ Fh_sigen, ++#ifdef CONFIG_AUFS_INO_T_64 ++ /* support 64bit inode number */ ++ Fh_ino1, ++ Fh_ino2, ++ Fh_dir_ino1, ++ Fh_dir_ino2, ++#else ++ Fh_ino1, ++ Fh_dir_ino1, ++#endif ++ Fh_igen, ++ Fh_h_type, ++ Fh_tail, ++ ++ Fh_ino = Fh_ino1, ++ Fh_dir_ino = Fh_dir_ino1 ++}; ++ ++static int au_test_anon(struct dentry *dentry) ++{ ++ /* note: read d_flags without d_lock */ ++ return !!(dentry->d_flags & DCACHE_DISCONNECTED); ++} ++ ++int au_test_nfsd(void) ++{ ++ int ret; ++ struct task_struct *tsk = current; ++ char comm[sizeof(tsk->comm)]; ++ ++ ret = 0; ++ if (tsk->flags & PF_KTHREAD) { ++ get_task_comm(comm, tsk); ++ ret = !strcmp(comm, "nfsd"); ++ } ++ ++ return ret; ++} ++ ++/* ---------------------------------------------------------------------- */ ++/* inode generation external table */ ++ ++void au_xigen_inc(struct inode *inode) ++{ ++ loff_t pos; ++ ssize_t sz; ++ __u32 igen; ++ struct super_block *sb; ++ struct au_sbinfo *sbinfo; ++ ++ sb = inode->i_sb; ++ AuDebugOn(!au_opt_test(au_mntflags(sb), XINO)); ++ ++ sbinfo = au_sbi(sb); ++ pos = inode->i_ino; ++ pos *= sizeof(igen); ++ igen = inode->i_generation + 1; ++ sz = xino_fwrite(sbinfo->si_xwrite, sbinfo->si_xigen, &igen, ++ sizeof(igen), &pos); ++ if (sz == sizeof(igen)) ++ return; /* success */ ++ ++ if (unlikely(sz >= 0)) ++ AuIOErr("xigen error (%zd)\n", sz); ++} ++ ++int au_xigen_new(struct inode *inode) ++{ ++ int err; ++ loff_t pos; ++ ssize_t sz; ++ struct super_block *sb; ++ struct au_sbinfo *sbinfo; ++ struct file *file; ++ ++ err = 0; ++ /* todo: dirty, at mount time */ ++ if (inode->i_ino == AUFS_ROOT_INO) ++ goto out; ++ sb = inode->i_sb; ++ SiMustAnyLock(sb); ++ if (unlikely(!au_opt_test(au_mntflags(sb), XINO))) ++ goto out; ++ ++ err = -EFBIG; ++ pos = inode->i_ino; ++ if (unlikely(au_loff_max / sizeof(inode->i_generation) - 1 < pos)) { ++ AuIOErr1("too large i%lld\n", pos); ++ goto out; ++ } ++ pos *= sizeof(inode->i_generation); ++ ++ err = 0; ++ sbinfo = au_sbi(sb); ++ file = sbinfo->si_xigen; ++ BUG_ON(!file); ++ ++ if (vfsub_f_size_read(file) ++ < pos + sizeof(inode->i_generation)) { ++ inode->i_generation = atomic_inc_return(&sbinfo->si_xigen_next); ++ sz = xino_fwrite(sbinfo->si_xwrite, file, &inode->i_generation, ++ sizeof(inode->i_generation), &pos); ++ } else ++ sz = xino_fread(sbinfo->si_xread, file, &inode->i_generation, ++ sizeof(inode->i_generation), &pos); ++ if (sz == sizeof(inode->i_generation)) ++ goto out; /* success */ ++ ++ err = sz; ++ if (unlikely(sz >= 0)) { ++ err = -EIO; ++ AuIOErr("xigen error (%zd)\n", sz); ++ } ++ ++out: ++ return err; ++} ++ ++int au_xigen_set(struct super_block *sb, struct file *base) ++{ ++ int err; ++ struct au_sbinfo *sbinfo; ++ struct file *file; ++ ++ SiMustWriteLock(sb); ++ ++ sbinfo = au_sbi(sb); ++ file = au_xino_create2(base, sbinfo->si_xigen); ++ err = PTR_ERR(file); ++ if (IS_ERR(file)) ++ goto out; ++ err = 0; ++ if (sbinfo->si_xigen) ++ fput(sbinfo->si_xigen); ++ sbinfo->si_xigen = file; ++ ++out: ++ return err; ++} ++ ++void au_xigen_clr(struct super_block *sb) ++{ ++ struct au_sbinfo *sbinfo; ++ ++ SiMustWriteLock(sb); ++ ++ sbinfo = au_sbi(sb); ++ if (sbinfo->si_xigen) { ++ fput(sbinfo->si_xigen); ++ sbinfo->si_xigen = NULL; ++ } ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++static struct dentry *decode_by_ino(struct super_block *sb, ino_t ino, ++ ino_t dir_ino) ++{ ++ struct dentry *dentry, *d; ++ struct inode *inode; ++ unsigned int sigen; ++ ++ dentry = NULL; ++ inode = ilookup(sb, ino); ++ if (!inode) ++ goto out; ++ ++ dentry = ERR_PTR(-ESTALE); ++ sigen = au_sigen(sb); ++ if (unlikely(is_bad_inode(inode) ++ || IS_DEADDIR(inode) ++ || sigen != au_iigen(inode, NULL))) ++ goto out_iput; ++ ++ dentry = NULL; ++ if (!dir_ino || S_ISDIR(inode->i_mode)) ++ dentry = d_find_alias(inode); ++ else { ++ spin_lock(&inode->i_lock); ++ hlist_for_each_entry(d, &inode->i_dentry, d_u.d_alias) { ++ spin_lock(&d->d_lock); ++ if (!au_test_anon(d) ++ && d->d_parent->d_inode->i_ino == dir_ino) { ++ dentry = dget_dlock(d); ++ spin_unlock(&d->d_lock); ++ break; ++ } ++ spin_unlock(&d->d_lock); ++ } ++ spin_unlock(&inode->i_lock); ++ } ++ if (unlikely(dentry && au_digen_test(dentry, sigen))) { ++ /* need to refresh */ ++ dput(dentry); ++ dentry = NULL; ++ } ++ ++out_iput: ++ iput(inode); ++out: ++ AuTraceErrPtr(dentry); ++ return dentry; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* todo: dirty? */ ++/* if exportfs_decode_fh() passed vfsmount*, we could be happy */ ++ ++struct au_compare_mnt_args { ++ /* input */ ++ struct super_block *sb; ++ ++ /* output */ ++ struct vfsmount *mnt; ++}; ++ ++static int au_compare_mnt(struct vfsmount *mnt, void *arg) ++{ ++ struct au_compare_mnt_args *a = arg; ++ ++ if (mnt->mnt_sb != a->sb) ++ return 0; ++ a->mnt = mntget(mnt); ++ return 1; ++} ++ ++static struct vfsmount *au_mnt_get(struct super_block *sb) ++{ ++ int err; ++ struct path root; ++ struct au_compare_mnt_args args = { ++ .sb = sb ++ }; ++ ++ get_fs_root(current->fs, &root); ++ rcu_read_lock(); ++ err = iterate_mounts(au_compare_mnt, &args, root.mnt); ++ rcu_read_unlock(); ++ path_put(&root); ++ AuDebugOn(!err); ++ AuDebugOn(!args.mnt); ++ return args.mnt; ++} ++ ++struct au_nfsd_si_lock { ++ unsigned int sigen; ++ aufs_bindex_t bindex, br_id; ++ unsigned char force_lock; ++}; ++ ++static int si_nfsd_read_lock(struct super_block *sb, ++ struct au_nfsd_si_lock *nsi_lock) ++{ ++ int err; ++ aufs_bindex_t bindex; ++ ++ si_read_lock(sb, AuLock_FLUSH); ++ ++ /* branch id may be wrapped around */ ++ err = 0; ++ bindex = au_br_index(sb, nsi_lock->br_id); ++ if (bindex >= 0 && nsi_lock->sigen + AUFS_BRANCH_MAX > au_sigen(sb)) ++ goto out; /* success */ ++ ++ err = -ESTALE; ++ bindex = -1; ++ if (!nsi_lock->force_lock) ++ si_read_unlock(sb); ++ ++out: ++ nsi_lock->bindex = bindex; ++ return err; ++} ++ ++struct find_name_by_ino { ++ struct dir_context ctx; ++ int called, found; ++ ino_t ino; ++ char *name; ++ int namelen; ++}; ++ ++static int ++find_name_by_ino(struct dir_context *ctx, const char *name, int namelen, ++ loff_t offset, u64 ino, unsigned int d_type) ++{ ++ struct find_name_by_ino *a = container_of(ctx, struct find_name_by_ino, ++ ctx); ++ ++ a->called++; ++ if (a->ino != ino) ++ return 0; ++ ++ memcpy(a->name, name, namelen); ++ a->namelen = namelen; ++ a->found = 1; ++ return 1; ++} ++ ++static struct dentry *au_lkup_by_ino(struct path *path, ino_t ino, ++ struct au_nfsd_si_lock *nsi_lock) ++{ ++ struct dentry *dentry, *parent; ++ struct file *file; ++ struct inode *dir; ++ struct find_name_by_ino arg = { ++ .ctx = { ++ .actor = au_diractor(find_name_by_ino) ++ } ++ }; ++ int err; ++ ++ parent = path->dentry; ++ if (nsi_lock) ++ si_read_unlock(parent->d_sb); ++ file = vfsub_dentry_open(path, au_dir_roflags); ++ dentry = (void *)file; ++ if (IS_ERR(file)) ++ goto out; ++ ++ dentry = ERR_PTR(-ENOMEM); ++ arg.name = (void *)__get_free_page(GFP_NOFS); ++ if (unlikely(!arg.name)) ++ goto out_file; ++ arg.ino = ino; ++ arg.found = 0; ++ do { ++ arg.called = 0; ++ /* smp_mb(); */ ++ err = vfsub_iterate_dir(file, &arg.ctx); ++ } while (!err && !arg.found && arg.called); ++ dentry = ERR_PTR(err); ++ if (unlikely(err)) ++ goto out_name; ++ /* instead of ENOENT */ ++ dentry = ERR_PTR(-ESTALE); ++ if (!arg.found) ++ goto out_name; ++ ++ /* do not call vfsub_lkup_one() */ ++ dir = parent->d_inode; ++ mutex_lock(&dir->i_mutex); ++ dentry = vfsub_lookup_one_len(arg.name, parent, arg.namelen); ++ mutex_unlock(&dir->i_mutex); ++ AuTraceErrPtr(dentry); ++ if (IS_ERR(dentry)) ++ goto out_name; ++ AuDebugOn(au_test_anon(dentry)); ++ if (unlikely(!dentry->d_inode)) { ++ dput(dentry); ++ dentry = ERR_PTR(-ENOENT); ++ } ++ ++out_name: ++ free_page((unsigned long)arg.name); ++out_file: ++ fput(file); ++out: ++ if (unlikely(nsi_lock ++ && si_nfsd_read_lock(parent->d_sb, nsi_lock) < 0)) ++ if (!IS_ERR(dentry)) { ++ dput(dentry); ++ dentry = ERR_PTR(-ESTALE); ++ } ++ AuTraceErrPtr(dentry); ++ return dentry; ++} ++ ++static struct dentry *decode_by_dir_ino(struct super_block *sb, ino_t ino, ++ ino_t dir_ino, ++ struct au_nfsd_si_lock *nsi_lock) ++{ ++ struct dentry *dentry; ++ struct path path; ++ ++ if (dir_ino != AUFS_ROOT_INO) { ++ path.dentry = decode_by_ino(sb, dir_ino, 0); ++ dentry = path.dentry; ++ if (!path.dentry || IS_ERR(path.dentry)) ++ goto out; ++ AuDebugOn(au_test_anon(path.dentry)); ++ } else ++ path.dentry = dget(sb->s_root); ++ ++ path.mnt = au_mnt_get(sb); ++ dentry = au_lkup_by_ino(&path, ino, nsi_lock); ++ path_put(&path); ++ ++out: ++ AuTraceErrPtr(dentry); ++ return dentry; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++static int h_acceptable(void *expv, struct dentry *dentry) ++{ ++ return 1; ++} ++ ++static char *au_build_path(struct dentry *h_parent, struct path *h_rootpath, ++ char *buf, int len, struct super_block *sb) ++{ ++ char *p; ++ int n; ++ struct path path; ++ ++ p = d_path(h_rootpath, buf, len); ++ if (IS_ERR(p)) ++ goto out; ++ n = strlen(p); ++ ++ path.mnt = h_rootpath->mnt; ++ path.dentry = h_parent; ++ p = d_path(&path, buf, len); ++ if (IS_ERR(p)) ++ goto out; ++ if (n != 1) ++ p += n; ++ ++ path.mnt = au_mnt_get(sb); ++ path.dentry = sb->s_root; ++ p = d_path(&path, buf, len - strlen(p)); ++ mntput(path.mnt); ++ if (IS_ERR(p)) ++ goto out; ++ if (n != 1) ++ p[strlen(p)] = '/'; ++ ++out: ++ AuTraceErrPtr(p); ++ return p; ++} ++ ++static ++struct dentry *decode_by_path(struct super_block *sb, ino_t ino, __u32 *fh, ++ int fh_len, struct au_nfsd_si_lock *nsi_lock) ++{ ++ struct dentry *dentry, *h_parent, *root; ++ struct super_block *h_sb; ++ char *pathname, *p; ++ struct vfsmount *h_mnt; ++ struct au_branch *br; ++ int err; ++ struct path path; ++ ++ br = au_sbr(sb, nsi_lock->bindex); ++ h_mnt = au_br_mnt(br); ++ h_sb = h_mnt->mnt_sb; ++ /* todo: call lower fh_to_dentry()? fh_to_parent()? */ ++ h_parent = exportfs_decode_fh(h_mnt, (void *)(fh + Fh_tail), ++ fh_len - Fh_tail, fh[Fh_h_type], ++ h_acceptable, /*context*/NULL); ++ dentry = h_parent; ++ if (unlikely(!h_parent || IS_ERR(h_parent))) { ++ AuWarn1("%s decode_fh failed, %ld\n", ++ au_sbtype(h_sb), PTR_ERR(h_parent)); ++ goto out; ++ } ++ dentry = NULL; ++ if (unlikely(au_test_anon(h_parent))) { ++ AuWarn1("%s decode_fh returned a disconnected dentry\n", ++ au_sbtype(h_sb)); ++ goto out_h_parent; ++ } ++ ++ dentry = ERR_PTR(-ENOMEM); ++ pathname = (void *)__get_free_page(GFP_NOFS); ++ if (unlikely(!pathname)) ++ goto out_h_parent; ++ ++ root = sb->s_root; ++ path.mnt = h_mnt; ++ di_read_lock_parent(root, !AuLock_IR); ++ path.dentry = au_h_dptr(root, nsi_lock->bindex); ++ di_read_unlock(root, !AuLock_IR); ++ p = au_build_path(h_parent, &path, pathname, PAGE_SIZE, sb); ++ dentry = (void *)p; ++ if (IS_ERR(p)) ++ goto out_pathname; ++ ++ si_read_unlock(sb); ++ err = vfsub_kern_path(p, LOOKUP_FOLLOW | LOOKUP_DIRECTORY, &path); ++ dentry = ERR_PTR(err); ++ if (unlikely(err)) ++ goto out_relock; ++ ++ dentry = ERR_PTR(-ENOENT); ++ AuDebugOn(au_test_anon(path.dentry)); ++ if (unlikely(!path.dentry->d_inode)) ++ goto out_path; ++ ++ if (ino != path.dentry->d_inode->i_ino) ++ dentry = au_lkup_by_ino(&path, ino, /*nsi_lock*/NULL); ++ else ++ dentry = dget(path.dentry); ++ ++out_path: ++ path_put(&path); ++out_relock: ++ if (unlikely(si_nfsd_read_lock(sb, nsi_lock) < 0)) ++ if (!IS_ERR(dentry)) { ++ dput(dentry); ++ dentry = ERR_PTR(-ESTALE); ++ } ++out_pathname: ++ free_page((unsigned long)pathname); ++out_h_parent: ++ dput(h_parent); ++out: ++ AuTraceErrPtr(dentry); ++ return dentry; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++static struct dentry * ++aufs_fh_to_dentry(struct super_block *sb, struct fid *fid, int fh_len, ++ int fh_type) ++{ ++ struct dentry *dentry; ++ __u32 *fh = fid->raw; ++ struct au_branch *br; ++ ino_t ino, dir_ino; ++ struct au_nfsd_si_lock nsi_lock = { ++ .force_lock = 0 ++ }; ++ ++ dentry = ERR_PTR(-ESTALE); ++ /* it should never happen, but the file handle is unreliable */ ++ if (unlikely(fh_len < Fh_tail)) ++ goto out; ++ nsi_lock.sigen = fh[Fh_sigen]; ++ nsi_lock.br_id = fh[Fh_br_id]; ++ ++ /* branch id may be wrapped around */ ++ br = NULL; ++ if (unlikely(si_nfsd_read_lock(sb, &nsi_lock))) ++ goto out; ++ nsi_lock.force_lock = 1; ++ ++ /* is this inode still cached? */ ++ ino = decode_ino(fh + Fh_ino); ++ /* it should never happen */ ++ if (unlikely(ino == AUFS_ROOT_INO)) ++ goto out_unlock; ++ ++ dir_ino = decode_ino(fh + Fh_dir_ino); ++ dentry = decode_by_ino(sb, ino, dir_ino); ++ if (IS_ERR(dentry)) ++ goto out_unlock; ++ if (dentry) ++ goto accept; ++ ++ /* is the parent dir cached? */ ++ br = au_sbr(sb, nsi_lock.bindex); ++ atomic_inc(&br->br_count); ++ dentry = decode_by_dir_ino(sb, ino, dir_ino, &nsi_lock); ++ if (IS_ERR(dentry)) ++ goto out_unlock; ++ if (dentry) ++ goto accept; ++ ++ /* lookup path */ ++ dentry = decode_by_path(sb, ino, fh, fh_len, &nsi_lock); ++ if (IS_ERR(dentry)) ++ goto out_unlock; ++ if (unlikely(!dentry)) ++ /* todo?: make it ESTALE */ ++ goto out_unlock; ++ ++accept: ++ if (!au_digen_test(dentry, au_sigen(sb)) ++ && dentry->d_inode->i_generation == fh[Fh_igen]) ++ goto out_unlock; /* success */ ++ ++ dput(dentry); ++ dentry = ERR_PTR(-ESTALE); ++out_unlock: ++ if (br) ++ atomic_dec(&br->br_count); ++ si_read_unlock(sb); ++out: ++ AuTraceErrPtr(dentry); ++ return dentry; ++} ++ ++#if 0 /* reserved for future use */ ++/* support subtreecheck option */ ++static struct dentry *aufs_fh_to_parent(struct super_block *sb, struct fid *fid, ++ int fh_len, int fh_type) ++{ ++ struct dentry *parent; ++ __u32 *fh = fid->raw; ++ ino_t dir_ino; ++ ++ dir_ino = decode_ino(fh + Fh_dir_ino); ++ parent = decode_by_ino(sb, dir_ino, 0); ++ if (IS_ERR(parent)) ++ goto out; ++ if (!parent) ++ parent = decode_by_path(sb, au_br_index(sb, fh[Fh_br_id]), ++ dir_ino, fh, fh_len); ++ ++out: ++ AuTraceErrPtr(parent); ++ return parent; ++} ++#endif ++ ++/* ---------------------------------------------------------------------- */ ++ ++static int aufs_encode_fh(struct inode *inode, __u32 *fh, int *max_len, ++ struct inode *dir) ++{ ++ int err; ++ aufs_bindex_t bindex; ++ struct super_block *sb, *h_sb; ++ struct dentry *dentry, *parent, *h_parent; ++ struct inode *h_dir; ++ struct au_branch *br; ++ ++ err = -ENOSPC; ++ if (unlikely(*max_len <= Fh_tail)) { ++ AuWarn1("NFSv2 client (max_len %d)?\n", *max_len); ++ goto out; ++ } ++ ++ err = FILEID_ROOT; ++ if (inode->i_ino == AUFS_ROOT_INO) { ++ AuDebugOn(inode->i_ino != AUFS_ROOT_INO); ++ goto out; ++ } ++ ++ h_parent = NULL; ++ sb = inode->i_sb; ++ err = si_read_lock(sb, AuLock_FLUSH); ++ if (unlikely(err)) ++ goto out; ++ ++#ifdef CONFIG_AUFS_DEBUG ++ if (unlikely(!au_opt_test(au_mntflags(sb), XINO))) ++ AuWarn1("NFS-exporting requires xino\n"); ++#endif ++ err = -EIO; ++ parent = NULL; ++ ii_read_lock_child(inode); ++ bindex = au_ibstart(inode); ++ if (!dir) { ++ dentry = d_find_any_alias(inode); ++ if (unlikely(!dentry)) ++ goto out_unlock; ++ AuDebugOn(au_test_anon(dentry)); ++ parent = dget_parent(dentry); ++ dput(dentry); ++ if (unlikely(!parent)) ++ goto out_unlock; ++ dir = parent->d_inode; ++ } ++ ++ ii_read_lock_parent(dir); ++ h_dir = au_h_iptr(dir, bindex); ++ ii_read_unlock(dir); ++ if (unlikely(!h_dir)) ++ goto out_parent; ++ h_parent = d_find_any_alias(h_dir); ++ if (unlikely(!h_parent)) ++ goto out_hparent; ++ ++ err = -EPERM; ++ br = au_sbr(sb, bindex); ++ h_sb = au_br_sb(br); ++ if (unlikely(!h_sb->s_export_op)) { ++ AuErr1("%s branch is not exportable\n", au_sbtype(h_sb)); ++ goto out_hparent; ++ } ++ ++ fh[Fh_br_id] = br->br_id; ++ fh[Fh_sigen] = au_sigen(sb); ++ encode_ino(fh + Fh_ino, inode->i_ino); ++ encode_ino(fh + Fh_dir_ino, dir->i_ino); ++ fh[Fh_igen] = inode->i_generation; ++ ++ *max_len -= Fh_tail; ++ fh[Fh_h_type] = exportfs_encode_fh(h_parent, (void *)(fh + Fh_tail), ++ max_len, ++ /*connectable or subtreecheck*/0); ++ err = fh[Fh_h_type]; ++ *max_len += Fh_tail; ++ /* todo: macros? */ ++ if (err != FILEID_INVALID) ++ err = 99; ++ else ++ AuWarn1("%s encode_fh failed\n", au_sbtype(h_sb)); ++ ++out_hparent: ++ dput(h_parent); ++out_parent: ++ dput(parent); ++out_unlock: ++ ii_read_unlock(inode); ++ si_read_unlock(sb); ++out: ++ if (unlikely(err < 0)) ++ err = FILEID_INVALID; ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++static int aufs_commit_metadata(struct inode *inode) ++{ ++ int err; ++ aufs_bindex_t bindex; ++ struct super_block *sb; ++ struct inode *h_inode; ++ int (*f)(struct inode *inode); ++ ++ sb = inode->i_sb; ++ si_read_lock(sb, AuLock_FLUSH | AuLock_NOPLMW); ++ ii_write_lock_child(inode); ++ bindex = au_ibstart(inode); ++ AuDebugOn(bindex < 0); ++ h_inode = au_h_iptr(inode, bindex); ++ ++ f = h_inode->i_sb->s_export_op->commit_metadata; ++ if (f) ++ err = f(h_inode); ++ else { ++ struct writeback_control wbc = { ++ .sync_mode = WB_SYNC_ALL, ++ .nr_to_write = 0 /* metadata only */ ++ }; ++ ++ err = sync_inode(h_inode, &wbc); ++ } ++ ++ au_cpup_attr_timesizes(inode); ++ ii_write_unlock(inode); ++ si_read_unlock(sb); ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++static struct export_operations aufs_export_op = { ++ .fh_to_dentry = aufs_fh_to_dentry, ++ /* .fh_to_parent = aufs_fh_to_parent, */ ++ .encode_fh = aufs_encode_fh, ++ .commit_metadata = aufs_commit_metadata ++}; ++ ++void au_export_init(struct super_block *sb) ++{ ++ struct au_sbinfo *sbinfo; ++ __u32 u; ++ ++ sb->s_export_op = &aufs_export_op; ++ sbinfo = au_sbi(sb); ++ sbinfo->si_xigen = NULL; ++ get_random_bytes(&u, sizeof(u)); ++ BUILD_BUG_ON(sizeof(u) != sizeof(int)); ++ atomic_set(&sbinfo->si_xigen_next, u); ++} +diff --git a/fs/aufs/f_op.c b/fs/aufs/f_op.c +new file mode 100644 +index 0000000..b08981a +--- /dev/null ++++ b/fs/aufs/f_op.c +@@ -0,0 +1,781 @@ ++/* ++ * Copyright (C) 2005-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * file and vm operations ++ */ ++ ++#include ++#include ++#include ++#include ++#include "aufs.h" ++ ++int au_do_open_nondir(struct file *file, int flags, struct file *h_file) ++{ ++ int err; ++ aufs_bindex_t bindex; ++ struct dentry *dentry, *h_dentry; ++ struct au_finfo *finfo; ++ struct inode *h_inode; ++ ++ FiMustWriteLock(file); ++ ++ err = 0; ++ dentry = file->f_dentry; ++ AuDebugOn(IS_ERR_OR_NULL(dentry)); ++ finfo = au_fi(file); ++ memset(&finfo->fi_htop, 0, sizeof(finfo->fi_htop)); ++ atomic_set(&finfo->fi_mmapped, 0); ++ bindex = au_dbstart(dentry); ++ if (!h_file) { ++ h_dentry = au_h_dptr(dentry, bindex); ++ err = vfsub_test_mntns(file->f_path.mnt, h_dentry->d_sb); ++ if (unlikely(err)) ++ goto out; ++ h_file = au_h_open(dentry, bindex, flags, file, /*force_wr*/0); ++ } else { ++ h_dentry = h_file->f_dentry; ++ err = vfsub_test_mntns(file->f_path.mnt, h_dentry->d_sb); ++ if (unlikely(err)) ++ goto out; ++ get_file(h_file); ++ } ++ if (IS_ERR(h_file)) ++ err = PTR_ERR(h_file); ++ else { ++ if ((flags & __O_TMPFILE) ++ && !(flags & O_EXCL)) { ++ h_inode = file_inode(h_file); ++ spin_lock(&h_inode->i_lock); ++ h_inode->i_state |= I_LINKABLE; ++ spin_unlock(&h_inode->i_lock); ++ } ++ au_set_fbstart(file, bindex); ++ au_set_h_fptr(file, bindex, h_file); ++ au_update_figen(file); ++ /* todo: necessary? */ ++ /* file->f_ra = h_file->f_ra; */ ++ } ++ ++out: ++ return err; ++} ++ ++static int aufs_open_nondir(struct inode *inode __maybe_unused, ++ struct file *file) ++{ ++ int err; ++ struct super_block *sb; ++ struct au_do_open_args args = { ++ .open = au_do_open_nondir ++ }; ++ ++ AuDbg("%pD, f_flags 0x%x, f_mode 0x%x\n", ++ file, vfsub_file_flags(file), file->f_mode); ++ ++ sb = file->f_dentry->d_sb; ++ si_read_lock(sb, AuLock_FLUSH); ++ err = au_do_open(file, &args); ++ si_read_unlock(sb); ++ return err; ++} ++ ++int aufs_release_nondir(struct inode *inode __maybe_unused, struct file *file) ++{ ++ struct au_finfo *finfo; ++ aufs_bindex_t bindex; ++ ++ finfo = au_fi(file); ++ au_sphl_del(&finfo->fi_hlist, &au_sbi(file->f_dentry->d_sb)->si_files); ++ bindex = finfo->fi_btop; ++ if (bindex >= 0) ++ au_set_h_fptr(file, bindex, NULL); ++ ++ au_finfo_fin(file); ++ return 0; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++static int au_do_flush_nondir(struct file *file, fl_owner_t id) ++{ ++ int err; ++ struct file *h_file; ++ ++ err = 0; ++ h_file = au_hf_top(file); ++ if (h_file) ++ err = vfsub_flush(h_file, id); ++ return err; ++} ++ ++static int aufs_flush_nondir(struct file *file, fl_owner_t id) ++{ ++ return au_do_flush(file, id, au_do_flush_nondir); ++} ++ ++/* ---------------------------------------------------------------------- */ ++/* ++ * read and write functions acquire [fdi]_rwsem once, but release before ++ * mmap_sem. This is because to stop a race condition between mmap(2). ++ * Releasing these aufs-rwsem should be safe, no branch-mamagement (by keeping ++ * si_rwsem), no harmful copy-up should happen. Actually copy-up may happen in ++ * read functions after [fdi]_rwsem are released, but it should be harmless. ++ */ ++ ++/* Callers should call au_read_post() or fput() in the end */ ++struct file *au_read_pre(struct file *file, int keep_fi) ++{ ++ struct file *h_file; ++ int err; ++ ++ err = au_reval_and_lock_fdi(file, au_reopen_nondir, /*wlock*/0); ++ if (!err) { ++ di_read_unlock(file->f_dentry, AuLock_IR); ++ h_file = au_hf_top(file); ++ get_file(h_file); ++ if (!keep_fi) ++ fi_read_unlock(file); ++ } else ++ h_file = ERR_PTR(err); ++ ++ return h_file; ++} ++ ++static void au_read_post(struct inode *inode, struct file *h_file) ++{ ++ /* update without lock, I don't think it a problem */ ++ fsstack_copy_attr_atime(inode, file_inode(h_file)); ++ fput(h_file); ++} ++ ++struct au_write_pre { ++ blkcnt_t blks; ++ aufs_bindex_t bstart; ++}; ++ ++/* ++ * return with iinfo is write-locked ++ * callers should call au_write_post() or iinfo_write_unlock() + fput() in the ++ * end ++ */ ++static struct file *au_write_pre(struct file *file, int do_ready, ++ struct au_write_pre *wpre) ++{ ++ struct file *h_file; ++ struct dentry *dentry; ++ int err; ++ struct au_pin pin; ++ ++ err = au_reval_and_lock_fdi(file, au_reopen_nondir, /*wlock*/1); ++ h_file = ERR_PTR(err); ++ if (unlikely(err)) ++ goto out; ++ ++ dentry = file->f_dentry; ++ if (do_ready) { ++ err = au_ready_to_write(file, -1, &pin); ++ if (unlikely(err)) { ++ h_file = ERR_PTR(err); ++ di_write_unlock(dentry); ++ goto out_fi; ++ } ++ } ++ ++ di_downgrade_lock(dentry, /*flags*/0); ++ if (wpre) ++ wpre->bstart = au_fbstart(file); ++ h_file = au_hf_top(file); ++ get_file(h_file); ++ if (wpre) ++ wpre->blks = file_inode(h_file)->i_blocks; ++ if (do_ready) ++ au_unpin(&pin); ++ di_read_unlock(dentry, /*flags*/0); ++ ++out_fi: ++ fi_write_unlock(file); ++out: ++ return h_file; ++} ++ ++static void au_write_post(struct inode *inode, struct file *h_file, ++ struct au_write_pre *wpre, ssize_t written) ++{ ++ struct inode *h_inode; ++ ++ au_cpup_attr_timesizes(inode); ++ AuDebugOn(au_ibstart(inode) != wpre->bstart); ++ h_inode = file_inode(h_file); ++ inode->i_mode = h_inode->i_mode; ++ ii_write_unlock(inode); ++ fput(h_file); ++ ++ /* AuDbg("blks %llu, %llu\n", (u64)blks, (u64)h_inode->i_blocks); */ ++ if (written > 0) ++ au_fhsm_wrote(inode->i_sb, wpre->bstart, ++ /*force*/h_inode->i_blocks > wpre->blks); ++} ++ ++static ssize_t aufs_read(struct file *file, char __user *buf, size_t count, ++ loff_t *ppos) ++{ ++ ssize_t err; ++ struct inode *inode; ++ struct file *h_file; ++ struct super_block *sb; ++ ++ inode = file_inode(file); ++ sb = inode->i_sb; ++ si_read_lock(sb, AuLock_FLUSH | AuLock_NOPLMW); ++ ++ h_file = au_read_pre(file, /*keep_fi*/0); ++ err = PTR_ERR(h_file); ++ if (IS_ERR(h_file)) ++ goto out; ++ ++ /* filedata may be obsoleted by concurrent copyup, but no problem */ ++ err = vfsub_read_u(h_file, buf, count, ppos); ++ /* todo: necessary? */ ++ /* file->f_ra = h_file->f_ra; */ ++ au_read_post(inode, h_file); ++ ++out: ++ si_read_unlock(sb); ++ return err; ++} ++ ++/* ++ * todo: very ugly ++ * it locks both of i_mutex and si_rwsem for read in safe. ++ * if the plink maintenance mode continues forever (that is the problem), ++ * may loop forever. ++ */ ++static void au_mtx_and_read_lock(struct inode *inode) ++{ ++ int err; ++ struct super_block *sb = inode->i_sb; ++ ++ while (1) { ++ mutex_lock(&inode->i_mutex); ++ err = si_read_lock(sb, AuLock_FLUSH | AuLock_NOPLM); ++ if (!err) ++ break; ++ mutex_unlock(&inode->i_mutex); ++ si_read_lock(sb, AuLock_NOPLMW); ++ si_read_unlock(sb); ++ } ++} ++ ++static ssize_t aufs_write(struct file *file, const char __user *ubuf, ++ size_t count, loff_t *ppos) ++{ ++ ssize_t err; ++ struct au_write_pre wpre; ++ struct inode *inode; ++ struct file *h_file; ++ char __user *buf = (char __user *)ubuf; ++ ++ inode = file_inode(file); ++ au_mtx_and_read_lock(inode); ++ ++ h_file = au_write_pre(file, /*do_ready*/1, &wpre); ++ err = PTR_ERR(h_file); ++ if (IS_ERR(h_file)) ++ goto out; ++ ++ err = vfsub_write_u(h_file, buf, count, ppos); ++ au_write_post(inode, h_file, &wpre, err); ++ ++out: ++ si_read_unlock(inode->i_sb); ++ mutex_unlock(&inode->i_mutex); ++ return err; ++} ++ ++static ssize_t au_do_iter(struct file *h_file, int rw, struct kiocb *kio, ++ struct iov_iter *iov_iter) ++{ ++ ssize_t err; ++ struct file *file; ++ ssize_t (*iter)(struct kiocb *, struct iov_iter *); ++ ssize_t (*aio)(struct kiocb *, const struct iovec *, unsigned long, ++ loff_t); ++ ++ err = security_file_permission(h_file, rw); ++ if (unlikely(err)) ++ goto out; ++ ++ err = -ENOSYS; ++ iter = NULL; ++ aio = NULL; ++ if (rw == MAY_READ) { ++ iter = h_file->f_op->read_iter; ++ aio = h_file->f_op->aio_read; ++ } else if (rw == MAY_WRITE) { ++ iter = h_file->f_op->write_iter; ++ aio = h_file->f_op->aio_write; ++ } ++ ++ file = kio->ki_filp; ++ kio->ki_filp = h_file; ++ if (iter) { ++ lockdep_off(); ++ err = iter(kio, iov_iter); ++ lockdep_on(); ++ } else if (aio) { ++ lockdep_off(); ++ err = aio(kio, iov_iter->iov, iov_iter->nr_segs, kio->ki_pos); ++ lockdep_on(); ++ } else ++ /* currently there is no such fs */ ++ WARN_ON_ONCE(1); ++ kio->ki_filp = file; ++ ++out: ++ return err; ++} ++ ++static ssize_t aufs_read_iter(struct kiocb *kio, struct iov_iter *iov_iter) ++{ ++ ssize_t err; ++ struct file *file, *h_file; ++ struct inode *inode; ++ struct super_block *sb; ++ ++ file = kio->ki_filp; ++ inode = file_inode(file); ++ sb = inode->i_sb; ++ si_read_lock(sb, AuLock_FLUSH | AuLock_NOPLMW); ++ ++ h_file = au_read_pre(file, /*keep_fi*/0); ++ err = PTR_ERR(h_file); ++ if (IS_ERR(h_file)) ++ goto out; ++ ++ err = au_do_iter(h_file, MAY_READ, kio, iov_iter); ++ /* todo: necessary? */ ++ /* file->f_ra = h_file->f_ra; */ ++ au_read_post(inode, h_file); ++ ++out: ++ si_read_unlock(sb); ++ return err; ++} ++ ++static ssize_t aufs_write_iter(struct kiocb *kio, struct iov_iter *iov_iter) ++{ ++ ssize_t err; ++ struct au_write_pre wpre; ++ struct inode *inode; ++ struct file *file, *h_file; ++ ++ file = kio->ki_filp; ++ inode = file_inode(file); ++ au_mtx_and_read_lock(inode); ++ ++ h_file = au_write_pre(file, /*do_ready*/1, &wpre); ++ err = PTR_ERR(h_file); ++ if (IS_ERR(h_file)) ++ goto out; ++ ++ err = au_do_iter(h_file, MAY_WRITE, kio, iov_iter); ++ au_write_post(inode, h_file, &wpre, err); ++ ++out: ++ si_read_unlock(inode->i_sb); ++ mutex_unlock(&inode->i_mutex); ++ return err; ++} ++ ++static ssize_t aufs_splice_read(struct file *file, loff_t *ppos, ++ struct pipe_inode_info *pipe, size_t len, ++ unsigned int flags) ++{ ++ ssize_t err; ++ struct file *h_file; ++ struct inode *inode; ++ struct super_block *sb; ++ ++ inode = file_inode(file); ++ sb = inode->i_sb; ++ si_read_lock(sb, AuLock_FLUSH | AuLock_NOPLMW); ++ ++ h_file = au_read_pre(file, /*keep_fi*/1); ++ err = PTR_ERR(h_file); ++ if (IS_ERR(h_file)) ++ goto out; ++ ++ if (au_test_loopback_kthread()) { ++ au_warn_loopback(h_file->f_dentry->d_sb); ++ if (file->f_mapping != h_file->f_mapping) { ++ file->f_mapping = h_file->f_mapping; ++ smp_mb(); /* unnecessary? */ ++ } ++ } ++ fi_read_unlock(file); ++ ++ err = vfsub_splice_to(h_file, ppos, pipe, len, flags); ++ /* todo: necessasry? */ ++ /* file->f_ra = h_file->f_ra; */ ++ au_read_post(inode, h_file); ++ ++out: ++ si_read_unlock(sb); ++ return err; ++} ++ ++static ssize_t ++aufs_splice_write(struct pipe_inode_info *pipe, struct file *file, loff_t *ppos, ++ size_t len, unsigned int flags) ++{ ++ ssize_t err; ++ struct au_write_pre wpre; ++ struct inode *inode; ++ struct file *h_file; ++ ++ inode = file_inode(file); ++ au_mtx_and_read_lock(inode); ++ ++ h_file = au_write_pre(file, /*do_ready*/1, &wpre); ++ err = PTR_ERR(h_file); ++ if (IS_ERR(h_file)) ++ goto out; ++ ++ err = vfsub_splice_from(pipe, h_file, ppos, len, flags); ++ au_write_post(inode, h_file, &wpre, err); ++ ++out: ++ si_read_unlock(inode->i_sb); ++ mutex_unlock(&inode->i_mutex); ++ return err; ++} ++ ++static long aufs_fallocate(struct file *file, int mode, loff_t offset, ++ loff_t len) ++{ ++ long err; ++ struct au_write_pre wpre; ++ struct inode *inode; ++ struct file *h_file; ++ ++ inode = file_inode(file); ++ au_mtx_and_read_lock(inode); ++ ++ h_file = au_write_pre(file, /*do_ready*/1, &wpre); ++ err = PTR_ERR(h_file); ++ if (IS_ERR(h_file)) ++ goto out; ++ ++ lockdep_off(); ++ err = do_fallocate(h_file, mode, offset, len); ++ lockdep_on(); ++ au_write_post(inode, h_file, &wpre, /*written*/1); ++ ++out: ++ si_read_unlock(inode->i_sb); ++ mutex_unlock(&inode->i_mutex); ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* ++ * The locking order around current->mmap_sem. ++ * - in most and regular cases ++ * file I/O syscall -- aufs_read() or something ++ * -- si_rwsem for read -- mmap_sem ++ * (Note that [fdi]i_rwsem are released before mmap_sem). ++ * - in mmap case ++ * mmap(2) -- mmap_sem -- aufs_mmap() -- si_rwsem for read -- [fdi]i_rwsem ++ * This AB-BA order is definitly bad, but is not a problem since "si_rwsem for ++ * read" allows muliple processes to acquire it and [fdi]i_rwsem are not held in ++ * file I/O. Aufs needs to stop lockdep in aufs_mmap() though. ++ * It means that when aufs acquires si_rwsem for write, the process should never ++ * acquire mmap_sem. ++ * ++ * Actually aufs_iterate() holds [fdi]i_rwsem before mmap_sem, but this is not a ++ * problem either since any directory is not able to be mmap-ed. ++ * The similar scenario is applied to aufs_readlink() too. ++ */ ++ ++#if 0 /* stop calling security_file_mmap() */ ++/* cf. linux/include/linux/mman.h: calc_vm_prot_bits() */ ++#define AuConv_VM_PROT(f, b) _calc_vm_trans(f, VM_##b, PROT_##b) ++ ++static unsigned long au_arch_prot_conv(unsigned long flags) ++{ ++ /* currently ppc64 only */ ++#ifdef CONFIG_PPC64 ++ /* cf. linux/arch/powerpc/include/asm/mman.h */ ++ AuDebugOn(arch_calc_vm_prot_bits(-1) != VM_SAO); ++ return AuConv_VM_PROT(flags, SAO); ++#else ++ AuDebugOn(arch_calc_vm_prot_bits(-1)); ++ return 0; ++#endif ++} ++ ++static unsigned long au_prot_conv(unsigned long flags) ++{ ++ return AuConv_VM_PROT(flags, READ) ++ | AuConv_VM_PROT(flags, WRITE) ++ | AuConv_VM_PROT(flags, EXEC) ++ | au_arch_prot_conv(flags); ++} ++ ++/* cf. linux/include/linux/mman.h: calc_vm_flag_bits() */ ++#define AuConv_VM_MAP(f, b) _calc_vm_trans(f, VM_##b, MAP_##b) ++ ++static unsigned long au_flag_conv(unsigned long flags) ++{ ++ return AuConv_VM_MAP(flags, GROWSDOWN) ++ | AuConv_VM_MAP(flags, DENYWRITE) ++ | AuConv_VM_MAP(flags, LOCKED); ++} ++#endif ++ ++static int aufs_mmap(struct file *file, struct vm_area_struct *vma) ++{ ++ int err; ++ const unsigned char wlock ++ = (file->f_mode & FMODE_WRITE) && (vma->vm_flags & VM_SHARED); ++ struct super_block *sb; ++ struct file *h_file; ++ struct inode *inode; ++ ++ AuDbgVmRegion(file, vma); ++ ++ inode = file_inode(file); ++ sb = inode->i_sb; ++ lockdep_off(); ++ si_read_lock(sb, AuLock_NOPLMW); ++ ++ h_file = au_write_pre(file, wlock, /*wpre*/NULL); ++ lockdep_on(); ++ err = PTR_ERR(h_file); ++ if (IS_ERR(h_file)) ++ goto out; ++ ++ err = 0; ++ au_set_mmapped(file); ++ au_vm_file_reset(vma, h_file); ++ /* ++ * we cannot call security_mmap_file() here since it may acquire ++ * mmap_sem or i_mutex. ++ * ++ * err = security_mmap_file(h_file, au_prot_conv(vma->vm_flags), ++ * au_flag_conv(vma->vm_flags)); ++ */ ++ if (!err) ++ err = h_file->f_op->mmap(h_file, vma); ++ if (!err) { ++ au_vm_prfile_set(vma, file); ++ fsstack_copy_attr_atime(inode, file_inode(h_file)); ++ goto out_fput; /* success */ ++ } ++ au_unset_mmapped(file); ++ au_vm_file_reset(vma, file); ++ ++out_fput: ++ lockdep_off(); ++ ii_write_unlock(inode); ++ lockdep_on(); ++ fput(h_file); ++out: ++ lockdep_off(); ++ si_read_unlock(sb); ++ lockdep_on(); ++ AuTraceErr(err); ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++static int aufs_fsync_nondir(struct file *file, loff_t start, loff_t end, ++ int datasync) ++{ ++ int err; ++ struct au_write_pre wpre; ++ struct inode *inode; ++ struct file *h_file; ++ ++ err = 0; /* -EBADF; */ /* posix? */ ++ if (unlikely(!(file->f_mode & FMODE_WRITE))) ++ goto out; ++ ++ inode = file_inode(file); ++ au_mtx_and_read_lock(inode); ++ ++ h_file = au_write_pre(file, /*do_ready*/1, &wpre); ++ err = PTR_ERR(h_file); ++ if (IS_ERR(h_file)) ++ goto out_unlock; ++ ++ err = vfsub_fsync(h_file, &h_file->f_path, datasync); ++ au_write_post(inode, h_file, &wpre, /*written*/0); ++ ++out_unlock: ++ si_read_unlock(inode->i_sb); ++ mutex_unlock(&inode->i_mutex); ++out: ++ return err; ++} ++ ++/* no one supports this operation, currently */ ++#if 0 ++static int aufs_aio_fsync_nondir(struct kiocb *kio, int datasync) ++{ ++ int err; ++ struct au_write_pre wpre; ++ struct inode *inode; ++ struct file *file, *h_file; ++ ++ err = 0; /* -EBADF; */ /* posix? */ ++ if (unlikely(!(file->f_mode & FMODE_WRITE))) ++ goto out; ++ ++ file = kio->ki_filp; ++ inode = file_inode(file); ++ au_mtx_and_read_lock(inode); ++ ++ h_file = au_write_pre(file, /*do_ready*/1, &wpre); ++ err = PTR_ERR(h_file); ++ if (IS_ERR(h_file)) ++ goto out_unlock; ++ ++ err = -ENOSYS; ++ h_file = au_hf_top(file); ++ if (h_file->f_op->aio_fsync) { ++ struct mutex *h_mtx; ++ ++ h_mtx = &file_inode(h_file)->i_mutex; ++ if (!is_sync_kiocb(kio)) { ++ get_file(h_file); ++ fput(file); ++ } ++ kio->ki_filp = h_file; ++ err = h_file->f_op->aio_fsync(kio, datasync); ++ mutex_lock_nested(h_mtx, AuLsc_I_CHILD); ++ if (!err) ++ vfsub_update_h_iattr(&h_file->f_path, /*did*/NULL); ++ /*ignore*/ ++ mutex_unlock(h_mtx); ++ } ++ au_write_post(inode, h_file, &wpre, /*written*/0); ++ ++out_unlock: ++ si_read_unlock(inode->sb); ++ mutex_unlock(&inode->i_mutex); ++out: ++ return err; ++} ++#endif ++ ++static int aufs_fasync(int fd, struct file *file, int flag) ++{ ++ int err; ++ struct file *h_file; ++ struct super_block *sb; ++ ++ sb = file->f_dentry->d_sb; ++ si_read_lock(sb, AuLock_FLUSH | AuLock_NOPLMW); ++ ++ h_file = au_read_pre(file, /*keep_fi*/0); ++ err = PTR_ERR(h_file); ++ if (IS_ERR(h_file)) ++ goto out; ++ ++ if (h_file->f_op->fasync) ++ err = h_file->f_op->fasync(fd, h_file, flag); ++ fput(h_file); /* instead of au_read_post() */ ++ ++out: ++ si_read_unlock(sb); ++ return err; ++} ++ ++static int aufs_setfl(struct file *file, unsigned long arg) ++{ ++ int err; ++ struct file *h_file; ++ struct super_block *sb; ++ ++ sb = file->f_dentry->d_sb; ++ si_read_lock(sb, AuLock_FLUSH | AuLock_NOPLMW); ++ ++ h_file = au_read_pre(file, /*keep_fi*/0); ++ err = PTR_ERR(h_file); ++ if (IS_ERR(h_file)) ++ goto out; ++ ++ arg |= vfsub_file_flags(file) & FASYNC; /* stop calling h_file->fasync */ ++ err = setfl(/*unused fd*/-1, h_file, arg); ++ fput(h_file); /* instead of au_read_post() */ ++ ++out: ++ si_read_unlock(sb); ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* no one supports this operation, currently */ ++#if 0 ++static ssize_t aufs_sendpage(struct file *file, struct page *page, int offset, ++ size_t len, loff_t *pos, int more) ++{ ++} ++#endif ++ ++/* ---------------------------------------------------------------------- */ ++ ++const struct file_operations aufs_file_fop = { ++ .owner = THIS_MODULE, ++ ++ .llseek = default_llseek, ++ ++ .read = aufs_read, ++ .write = aufs_write, ++ .read_iter = aufs_read_iter, ++ .write_iter = aufs_write_iter, ++ ++#ifdef CONFIG_AUFS_POLL ++ .poll = aufs_poll, ++#endif ++ .unlocked_ioctl = aufs_ioctl_nondir, ++#ifdef CONFIG_COMPAT ++ .compat_ioctl = aufs_compat_ioctl_nondir, ++#endif ++ .mmap = aufs_mmap, ++ .open = aufs_open_nondir, ++ .flush = aufs_flush_nondir, ++ .release = aufs_release_nondir, ++ .fsync = aufs_fsync_nondir, ++ /* .aio_fsync = aufs_aio_fsync_nondir, */ ++ .fasync = aufs_fasync, ++ /* .sendpage = aufs_sendpage, */ ++ .setfl = aufs_setfl, ++ .splice_write = aufs_splice_write, ++ .splice_read = aufs_splice_read, ++#if 0 ++ .aio_splice_write = aufs_aio_splice_write, ++ .aio_splice_read = aufs_aio_splice_read, ++#endif ++ .fallocate = aufs_fallocate ++}; +diff --git a/fs/aufs/fhsm.c b/fs/aufs/fhsm.c +new file mode 100644 +index 0000000..5b3ad74 +--- /dev/null ++++ b/fs/aufs/fhsm.c +@@ -0,0 +1,426 @@ ++/* ++ * Copyright (C) 2011-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, write to the Free Software ++ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ++ */ ++ ++/* ++ * File-based Hierarchy Storage Management ++ */ ++ ++#include ++#include ++#include ++#include ++#include "aufs.h" ++ ++static aufs_bindex_t au_fhsm_bottom(struct super_block *sb) ++{ ++ struct au_sbinfo *sbinfo; ++ struct au_fhsm *fhsm; ++ ++ SiMustAnyLock(sb); ++ ++ sbinfo = au_sbi(sb); ++ fhsm = &sbinfo->si_fhsm; ++ AuDebugOn(!fhsm); ++ return fhsm->fhsm_bottom; ++} ++ ++void au_fhsm_set_bottom(struct super_block *sb, aufs_bindex_t bindex) ++{ ++ struct au_sbinfo *sbinfo; ++ struct au_fhsm *fhsm; ++ ++ SiMustWriteLock(sb); ++ ++ sbinfo = au_sbi(sb); ++ fhsm = &sbinfo->si_fhsm; ++ AuDebugOn(!fhsm); ++ fhsm->fhsm_bottom = bindex; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++static int au_fhsm_test_jiffy(struct au_sbinfo *sbinfo, struct au_branch *br) ++{ ++ struct au_br_fhsm *bf; ++ ++ bf = br->br_fhsm; ++ MtxMustLock(&bf->bf_lock); ++ ++ return !bf->bf_readable ++ || time_after(jiffies, ++ bf->bf_jiffy + sbinfo->si_fhsm.fhsm_expire); ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++static void au_fhsm_notify(struct super_block *sb, int val) ++{ ++ struct au_sbinfo *sbinfo; ++ struct au_fhsm *fhsm; ++ ++ SiMustAnyLock(sb); ++ ++ sbinfo = au_sbi(sb); ++ fhsm = &sbinfo->si_fhsm; ++ if (au_fhsm_pid(fhsm) ++ && atomic_read(&fhsm->fhsm_readable) != -1) { ++ atomic_set(&fhsm->fhsm_readable, val); ++ if (val) ++ wake_up(&fhsm->fhsm_wqh); ++ } ++} ++ ++static int au_fhsm_stfs(struct super_block *sb, aufs_bindex_t bindex, ++ struct aufs_stfs *rstfs, int do_lock, int do_notify) ++{ ++ int err; ++ struct au_branch *br; ++ struct au_br_fhsm *bf; ++ ++ br = au_sbr(sb, bindex); ++ AuDebugOn(au_br_rdonly(br)); ++ bf = br->br_fhsm; ++ AuDebugOn(!bf); ++ ++ if (do_lock) ++ mutex_lock(&bf->bf_lock); ++ else ++ MtxMustLock(&bf->bf_lock); ++ ++ /* sb->s_root for NFS is unreliable */ ++ err = au_br_stfs(br, &bf->bf_stfs); ++ if (unlikely(err)) { ++ AuErr1("FHSM failed (%d), b%d, ignored.\n", bindex, err); ++ goto out; ++ } ++ ++ bf->bf_jiffy = jiffies; ++ bf->bf_readable = 1; ++ if (do_notify) ++ au_fhsm_notify(sb, /*val*/1); ++ if (rstfs) ++ *rstfs = bf->bf_stfs; ++ ++out: ++ if (do_lock) ++ mutex_unlock(&bf->bf_lock); ++ au_fhsm_notify(sb, /*val*/1); ++ ++ return err; ++} ++ ++void au_fhsm_wrote(struct super_block *sb, aufs_bindex_t bindex, int force) ++{ ++ int err; ++ struct au_sbinfo *sbinfo; ++ struct au_fhsm *fhsm; ++ struct au_branch *br; ++ struct au_br_fhsm *bf; ++ ++ AuDbg("b%d, force %d\n", bindex, force); ++ SiMustAnyLock(sb); ++ ++ sbinfo = au_sbi(sb); ++ fhsm = &sbinfo->si_fhsm; ++ if (!au_ftest_si(sbinfo, FHSM) ++ || fhsm->fhsm_bottom == bindex) ++ return; ++ ++ br = au_sbr(sb, bindex); ++ bf = br->br_fhsm; ++ AuDebugOn(!bf); ++ mutex_lock(&bf->bf_lock); ++ if (force ++ || au_fhsm_pid(fhsm) ++ || au_fhsm_test_jiffy(sbinfo, br)) ++ err = au_fhsm_stfs(sb, bindex, /*rstfs*/NULL, /*do_lock*/0, ++ /*do_notify*/1); ++ mutex_unlock(&bf->bf_lock); ++} ++ ++void au_fhsm_wrote_all(struct super_block *sb, int force) ++{ ++ aufs_bindex_t bindex, bend; ++ struct au_branch *br; ++ ++ /* exclude the bottom */ ++ bend = au_fhsm_bottom(sb); ++ for (bindex = 0; bindex < bend; bindex++) { ++ br = au_sbr(sb, bindex); ++ if (au_br_fhsm(br->br_perm)) ++ au_fhsm_wrote(sb, bindex, force); ++ } ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++static unsigned int au_fhsm_poll(struct file *file, ++ struct poll_table_struct *wait) ++{ ++ unsigned int mask; ++ struct au_sbinfo *sbinfo; ++ struct au_fhsm *fhsm; ++ ++ mask = 0; ++ sbinfo = file->private_data; ++ fhsm = &sbinfo->si_fhsm; ++ poll_wait(file, &fhsm->fhsm_wqh, wait); ++ if (atomic_read(&fhsm->fhsm_readable)) ++ mask = POLLIN /* | POLLRDNORM */; ++ ++ AuTraceErr((int)mask); ++ return mask; ++} ++ ++static int au_fhsm_do_read_one(struct aufs_stbr __user *stbr, ++ struct aufs_stfs *stfs, __s16 brid) ++{ ++ int err; ++ ++ err = copy_to_user(&stbr->stfs, stfs, sizeof(*stfs)); ++ if (!err) ++ err = __put_user(brid, &stbr->brid); ++ if (unlikely(err)) ++ err = -EFAULT; ++ ++ return err; ++} ++ ++static ssize_t au_fhsm_do_read(struct super_block *sb, ++ struct aufs_stbr __user *stbr, size_t count) ++{ ++ ssize_t err; ++ int nstbr; ++ aufs_bindex_t bindex, bend; ++ struct au_branch *br; ++ struct au_br_fhsm *bf; ++ ++ /* except the bottom branch */ ++ err = 0; ++ nstbr = 0; ++ bend = au_fhsm_bottom(sb); ++ for (bindex = 0; !err && bindex < bend; bindex++) { ++ br = au_sbr(sb, bindex); ++ if (!au_br_fhsm(br->br_perm)) ++ continue; ++ ++ bf = br->br_fhsm; ++ mutex_lock(&bf->bf_lock); ++ if (bf->bf_readable) { ++ err = -EFAULT; ++ if (count >= sizeof(*stbr)) ++ err = au_fhsm_do_read_one(stbr++, &bf->bf_stfs, ++ br->br_id); ++ if (!err) { ++ bf->bf_readable = 0; ++ count -= sizeof(*stbr); ++ nstbr++; ++ } ++ } ++ mutex_unlock(&bf->bf_lock); ++ } ++ if (!err) ++ err = sizeof(*stbr) * nstbr; ++ ++ return err; ++} ++ ++static ssize_t au_fhsm_read(struct file *file, char __user *buf, size_t count, ++ loff_t *pos) ++{ ++ ssize_t err; ++ int readable; ++ aufs_bindex_t nfhsm, bindex, bend; ++ struct au_sbinfo *sbinfo; ++ struct au_fhsm *fhsm; ++ struct au_branch *br; ++ struct super_block *sb; ++ ++ err = 0; ++ sbinfo = file->private_data; ++ fhsm = &sbinfo->si_fhsm; ++need_data: ++ spin_lock_irq(&fhsm->fhsm_wqh.lock); ++ if (!atomic_read(&fhsm->fhsm_readable)) { ++ if (vfsub_file_flags(file) & O_NONBLOCK) ++ err = -EAGAIN; ++ else ++ err = wait_event_interruptible_locked_irq ++ (fhsm->fhsm_wqh, ++ atomic_read(&fhsm->fhsm_readable)); ++ } ++ spin_unlock_irq(&fhsm->fhsm_wqh.lock); ++ if (unlikely(err)) ++ goto out; ++ ++ /* sb may already be dead */ ++ au_rw_read_lock(&sbinfo->si_rwsem); ++ readable = atomic_read(&fhsm->fhsm_readable); ++ if (readable > 0) { ++ sb = sbinfo->si_sb; ++ AuDebugOn(!sb); ++ /* exclude the bottom branch */ ++ nfhsm = 0; ++ bend = au_fhsm_bottom(sb); ++ for (bindex = 0; bindex < bend; bindex++) { ++ br = au_sbr(sb, bindex); ++ if (au_br_fhsm(br->br_perm)) ++ nfhsm++; ++ } ++ err = -EMSGSIZE; ++ if (nfhsm * sizeof(struct aufs_stbr) <= count) { ++ atomic_set(&fhsm->fhsm_readable, 0); ++ err = au_fhsm_do_read(sbinfo->si_sb, (void __user *)buf, ++ count); ++ } ++ } ++ au_rw_read_unlock(&sbinfo->si_rwsem); ++ if (!readable) ++ goto need_data; ++ ++out: ++ return err; ++} ++ ++static int au_fhsm_release(struct inode *inode, struct file *file) ++{ ++ struct au_sbinfo *sbinfo; ++ struct au_fhsm *fhsm; ++ ++ /* sb may already be dead */ ++ sbinfo = file->private_data; ++ fhsm = &sbinfo->si_fhsm; ++ spin_lock(&fhsm->fhsm_spin); ++ fhsm->fhsm_pid = 0; ++ spin_unlock(&fhsm->fhsm_spin); ++ kobject_put(&sbinfo->si_kobj); ++ ++ return 0; ++} ++ ++static const struct file_operations au_fhsm_fops = { ++ .owner = THIS_MODULE, ++ .llseek = noop_llseek, ++ .read = au_fhsm_read, ++ .poll = au_fhsm_poll, ++ .release = au_fhsm_release ++}; ++ ++int au_fhsm_fd(struct super_block *sb, int oflags) ++{ ++ int err, fd; ++ struct au_sbinfo *sbinfo; ++ struct au_fhsm *fhsm; ++ ++ err = -EPERM; ++ if (unlikely(!capable(CAP_SYS_ADMIN))) ++ goto out; ++ ++ err = -EINVAL; ++ if (unlikely(oflags & ~(O_CLOEXEC | O_NONBLOCK))) ++ goto out; ++ ++ err = 0; ++ sbinfo = au_sbi(sb); ++ fhsm = &sbinfo->si_fhsm; ++ spin_lock(&fhsm->fhsm_spin); ++ if (!fhsm->fhsm_pid) ++ fhsm->fhsm_pid = current->pid; ++ else ++ err = -EBUSY; ++ spin_unlock(&fhsm->fhsm_spin); ++ if (unlikely(err)) ++ goto out; ++ ++ oflags |= O_RDONLY; ++ /* oflags |= FMODE_NONOTIFY; */ ++ fd = anon_inode_getfd("[aufs_fhsm]", &au_fhsm_fops, sbinfo, oflags); ++ err = fd; ++ if (unlikely(fd < 0)) ++ goto out_pid; ++ ++ /* succeed reglardless 'fhsm' status */ ++ kobject_get(&sbinfo->si_kobj); ++ si_noflush_read_lock(sb); ++ if (au_ftest_si(sbinfo, FHSM)) ++ au_fhsm_wrote_all(sb, /*force*/0); ++ si_read_unlock(sb); ++ goto out; /* success */ ++ ++out_pid: ++ spin_lock(&fhsm->fhsm_spin); ++ fhsm->fhsm_pid = 0; ++ spin_unlock(&fhsm->fhsm_spin); ++out: ++ AuTraceErr(err); ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++int au_fhsm_br_alloc(struct au_branch *br) ++{ ++ int err; ++ ++ err = 0; ++ br->br_fhsm = kmalloc(sizeof(*br->br_fhsm), GFP_NOFS); ++ if (br->br_fhsm) ++ au_br_fhsm_init(br->br_fhsm); ++ else ++ err = -ENOMEM; ++ ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++void au_fhsm_fin(struct super_block *sb) ++{ ++ au_fhsm_notify(sb, /*val*/-1); ++} ++ ++void au_fhsm_init(struct au_sbinfo *sbinfo) ++{ ++ struct au_fhsm *fhsm; ++ ++ fhsm = &sbinfo->si_fhsm; ++ spin_lock_init(&fhsm->fhsm_spin); ++ init_waitqueue_head(&fhsm->fhsm_wqh); ++ atomic_set(&fhsm->fhsm_readable, 0); ++ fhsm->fhsm_expire ++ = msecs_to_jiffies(AUFS_FHSM_CACHE_DEF_SEC * MSEC_PER_SEC); ++ fhsm->fhsm_bottom = -1; ++} ++ ++void au_fhsm_set(struct au_sbinfo *sbinfo, unsigned int sec) ++{ ++ sbinfo->si_fhsm.fhsm_expire ++ = msecs_to_jiffies(sec * MSEC_PER_SEC); ++} ++ ++void au_fhsm_show(struct seq_file *seq, struct au_sbinfo *sbinfo) ++{ ++ unsigned int u; ++ ++ if (!au_ftest_si(sbinfo, FHSM)) ++ return; ++ ++ u = jiffies_to_msecs(sbinfo->si_fhsm.fhsm_expire) / MSEC_PER_SEC; ++ if (u != AUFS_FHSM_CACHE_DEF_SEC) ++ seq_printf(seq, ",fhsm_sec=%u", u); ++} +diff --git a/fs/aufs/file.c b/fs/aufs/file.c +new file mode 100644 +index 0000000..12c7620 +--- /dev/null ++++ b/fs/aufs/file.c +@@ -0,0 +1,857 @@ ++/* ++ * Copyright (C) 2005-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * handling file/dir, and address_space operation ++ */ ++ ++#ifdef CONFIG_AUFS_DEBUG ++#include ++#endif ++#include ++#include "aufs.h" ++ ++/* drop flags for writing */ ++unsigned int au_file_roflags(unsigned int flags) ++{ ++ flags &= ~(O_WRONLY | O_RDWR | O_APPEND | O_CREAT | O_TRUNC); ++ flags |= O_RDONLY | O_NOATIME; ++ return flags; ++} ++ ++/* common functions to regular file and dir */ ++struct file *au_h_open(struct dentry *dentry, aufs_bindex_t bindex, int flags, ++ struct file *file, int force_wr) ++{ ++ struct file *h_file; ++ struct dentry *h_dentry; ++ struct inode *h_inode; ++ struct super_block *sb; ++ struct au_branch *br; ++ struct path h_path; ++ int err; ++ ++ /* a race condition can happen between open and unlink/rmdir */ ++ h_file = ERR_PTR(-ENOENT); ++ h_dentry = au_h_dptr(dentry, bindex); ++ if (au_test_nfsd() && !h_dentry) ++ goto out; ++ h_inode = h_dentry->d_inode; ++ if (au_test_nfsd() && !h_inode) ++ goto out; ++ spin_lock(&h_dentry->d_lock); ++ err = (!d_unhashed(dentry) && d_unlinked(h_dentry)) ++ || !h_inode ++ /* || !dentry->d_inode->i_nlink */ ++ ; ++ spin_unlock(&h_dentry->d_lock); ++ if (unlikely(err)) ++ goto out; ++ ++ sb = dentry->d_sb; ++ br = au_sbr(sb, bindex); ++ err = au_br_test_oflag(flags, br); ++ h_file = ERR_PTR(err); ++ if (unlikely(err)) ++ goto out; ++ ++ /* drop flags for writing */ ++ if (au_test_ro(sb, bindex, dentry->d_inode)) { ++ if (force_wr && !(flags & O_WRONLY)) ++ force_wr = 0; ++ flags = au_file_roflags(flags); ++ if (force_wr) { ++ h_file = ERR_PTR(-EROFS); ++ flags = au_file_roflags(flags); ++ if (unlikely(vfsub_native_ro(h_inode) ++ || IS_APPEND(h_inode))) ++ goto out; ++ flags &= ~O_ACCMODE; ++ flags |= O_WRONLY; ++ } ++ } ++ flags &= ~O_CREAT; ++ atomic_inc(&br->br_count); ++ h_path.dentry = h_dentry; ++ h_path.mnt = au_br_mnt(br); ++ h_file = vfsub_dentry_open(&h_path, flags); ++ if (IS_ERR(h_file)) ++ goto out_br; ++ ++ if (flags & __FMODE_EXEC) { ++ err = deny_write_access(h_file); ++ if (unlikely(err)) { ++ fput(h_file); ++ h_file = ERR_PTR(err); ++ goto out_br; ++ } ++ } ++ fsnotify_open(h_file); ++ goto out; /* success */ ++ ++out_br: ++ atomic_dec(&br->br_count); ++out: ++ return h_file; ++} ++ ++static int au_cmoo(struct dentry *dentry) ++{ ++ int err, cmoo; ++ unsigned int udba; ++ struct path h_path; ++ struct au_pin pin; ++ struct au_cp_generic cpg = { ++ .dentry = dentry, ++ .bdst = -1, ++ .bsrc = -1, ++ .len = -1, ++ .pin = &pin, ++ .flags = AuCpup_DTIME | AuCpup_HOPEN ++ }; ++ struct inode *inode, *delegated; ++ struct super_block *sb; ++ struct au_sbinfo *sbinfo; ++ struct au_fhsm *fhsm; ++ pid_t pid; ++ struct au_branch *br; ++ struct dentry *parent; ++ struct au_hinode *hdir; ++ ++ DiMustWriteLock(dentry); ++ inode = dentry->d_inode; ++ IiMustWriteLock(inode); ++ ++ err = 0; ++ if (IS_ROOT(dentry)) ++ goto out; ++ cpg.bsrc = au_dbstart(dentry); ++ if (!cpg.bsrc) ++ goto out; ++ ++ sb = dentry->d_sb; ++ sbinfo = au_sbi(sb); ++ fhsm = &sbinfo->si_fhsm; ++ pid = au_fhsm_pid(fhsm); ++ if (pid ++ && (current->pid == pid ++ || current->real_parent->pid == pid)) ++ goto out; ++ ++ br = au_sbr(sb, cpg.bsrc); ++ cmoo = au_br_cmoo(br->br_perm); ++ if (!cmoo) ++ goto out; ++ if (!S_ISREG(inode->i_mode)) ++ cmoo &= AuBrAttr_COO_ALL; ++ if (!cmoo) ++ goto out; ++ ++ parent = dget_parent(dentry); ++ di_write_lock_parent(parent); ++ err = au_wbr_do_copyup_bu(dentry, cpg.bsrc - 1); ++ cpg.bdst = err; ++ if (unlikely(err < 0)) { ++ err = 0; /* there is no upper writable branch */ ++ goto out_dgrade; ++ } ++ AuDbg("bsrc %d, bdst %d\n", cpg.bsrc, cpg.bdst); ++ ++ /* do not respect the coo attrib for the target branch */ ++ err = au_cpup_dirs(dentry, cpg.bdst); ++ if (unlikely(err)) ++ goto out_dgrade; ++ ++ di_downgrade_lock(parent, AuLock_IR); ++ udba = au_opt_udba(sb); ++ err = au_pin(&pin, dentry, cpg.bdst, udba, ++ AuPin_DI_LOCKED | AuPin_MNT_WRITE); ++ if (unlikely(err)) ++ goto out_parent; ++ ++ err = au_sio_cpup_simple(&cpg); ++ au_unpin(&pin); ++ if (unlikely(err)) ++ goto out_parent; ++ if (!(cmoo & AuBrWAttr_MOO)) ++ goto out_parent; /* success */ ++ ++ err = au_pin(&pin, dentry, cpg.bsrc, udba, ++ AuPin_DI_LOCKED | AuPin_MNT_WRITE); ++ if (unlikely(err)) ++ goto out_parent; ++ ++ h_path.mnt = au_br_mnt(br); ++ h_path.dentry = au_h_dptr(dentry, cpg.bsrc); ++ hdir = au_hi(parent->d_inode, cpg.bsrc); ++ delegated = NULL; ++ err = vfsub_unlink(hdir->hi_inode, &h_path, &delegated, /*force*/1); ++ au_unpin(&pin); ++ /* todo: keep h_dentry or not? */ ++ if (unlikely(err == -EWOULDBLOCK)) { ++ pr_warn("cannot retry for NFSv4 delegation" ++ " for an internal unlink\n"); ++ iput(delegated); ++ } ++ if (unlikely(err)) { ++ pr_err("unlink %pd after coo failed (%d), ignored\n", ++ dentry, err); ++ err = 0; ++ } ++ goto out_parent; /* success */ ++ ++out_dgrade: ++ di_downgrade_lock(parent, AuLock_IR); ++out_parent: ++ di_read_unlock(parent, AuLock_IR); ++ dput(parent); ++out: ++ AuTraceErr(err); ++ return err; ++} ++ ++int au_do_open(struct file *file, struct au_do_open_args *args) ++{ ++ int err, no_lock = args->no_lock; ++ struct dentry *dentry; ++ struct au_finfo *finfo; ++ ++ if (!no_lock) ++ err = au_finfo_init(file, args->fidir); ++ else { ++ lockdep_off(); ++ err = au_finfo_init(file, args->fidir); ++ lockdep_on(); ++ } ++ if (unlikely(err)) ++ goto out; ++ ++ dentry = file->f_dentry; ++ AuDebugOn(IS_ERR_OR_NULL(dentry)); ++ if (!no_lock) { ++ di_write_lock_child(dentry); ++ err = au_cmoo(dentry); ++ di_downgrade_lock(dentry, AuLock_IR); ++ if (!err) ++ err = args->open(file, vfsub_file_flags(file), NULL); ++ di_read_unlock(dentry, AuLock_IR); ++ } else { ++ err = au_cmoo(dentry); ++ if (!err) ++ err = args->open(file, vfsub_file_flags(file), ++ args->h_file); ++ if (!err && au_fbstart(file) != au_dbstart(dentry)) ++ /* ++ * cmoo happens after h_file was opened. ++ * need to refresh file later. ++ */ ++ atomic_dec(&au_fi(file)->fi_generation); ++ } ++ ++ finfo = au_fi(file); ++ if (!err) { ++ finfo->fi_file = file; ++ au_sphl_add(&finfo->fi_hlist, ++ &au_sbi(file->f_dentry->d_sb)->si_files); ++ } ++ if (!no_lock) ++ fi_write_unlock(file); ++ else { ++ lockdep_off(); ++ fi_write_unlock(file); ++ lockdep_on(); ++ } ++ if (unlikely(err)) { ++ finfo->fi_hdir = NULL; ++ au_finfo_fin(file); ++ } ++ ++out: ++ return err; ++} ++ ++int au_reopen_nondir(struct file *file) ++{ ++ int err; ++ aufs_bindex_t bstart; ++ struct dentry *dentry; ++ struct file *h_file, *h_file_tmp; ++ ++ dentry = file->f_dentry; ++ bstart = au_dbstart(dentry); ++ h_file_tmp = NULL; ++ if (au_fbstart(file) == bstart) { ++ h_file = au_hf_top(file); ++ if (file->f_mode == h_file->f_mode) ++ return 0; /* success */ ++ h_file_tmp = h_file; ++ get_file(h_file_tmp); ++ au_set_h_fptr(file, bstart, NULL); ++ } ++ AuDebugOn(au_fi(file)->fi_hdir); ++ /* ++ * it can happen ++ * file exists on both of rw and ro ++ * open --> dbstart and fbstart are both 0 ++ * prepend a branch as rw, "rw" become ro ++ * remove rw/file ++ * delete the top branch, "rw" becomes rw again ++ * --> dbstart is 1, fbstart is still 0 ++ * write --> fbstart is 0 but dbstart is 1 ++ */ ++ /* AuDebugOn(au_fbstart(file) < bstart); */ ++ ++ h_file = au_h_open(dentry, bstart, vfsub_file_flags(file) & ~O_TRUNC, ++ file, /*force_wr*/0); ++ err = PTR_ERR(h_file); ++ if (IS_ERR(h_file)) { ++ if (h_file_tmp) { ++ atomic_inc(&au_sbr(dentry->d_sb, bstart)->br_count); ++ au_set_h_fptr(file, bstart, h_file_tmp); ++ h_file_tmp = NULL; ++ } ++ goto out; /* todo: close all? */ ++ } ++ ++ err = 0; ++ au_set_fbstart(file, bstart); ++ au_set_h_fptr(file, bstart, h_file); ++ au_update_figen(file); ++ /* todo: necessary? */ ++ /* file->f_ra = h_file->f_ra; */ ++ ++out: ++ if (h_file_tmp) ++ fput(h_file_tmp); ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++static int au_reopen_wh(struct file *file, aufs_bindex_t btgt, ++ struct dentry *hi_wh) ++{ ++ int err; ++ aufs_bindex_t bstart; ++ struct au_dinfo *dinfo; ++ struct dentry *h_dentry; ++ struct au_hdentry *hdp; ++ ++ dinfo = au_di(file->f_dentry); ++ AuRwMustWriteLock(&dinfo->di_rwsem); ++ ++ bstart = dinfo->di_bstart; ++ dinfo->di_bstart = btgt; ++ hdp = dinfo->di_hdentry; ++ h_dentry = hdp[0 + btgt].hd_dentry; ++ hdp[0 + btgt].hd_dentry = hi_wh; ++ err = au_reopen_nondir(file); ++ hdp[0 + btgt].hd_dentry = h_dentry; ++ dinfo->di_bstart = bstart; ++ ++ return err; ++} ++ ++static int au_ready_to_write_wh(struct file *file, loff_t len, ++ aufs_bindex_t bcpup, struct au_pin *pin) ++{ ++ int err; ++ struct inode *inode, *h_inode; ++ struct dentry *h_dentry, *hi_wh; ++ struct au_cp_generic cpg = { ++ .dentry = file->f_dentry, ++ .bdst = bcpup, ++ .bsrc = -1, ++ .len = len, ++ .pin = pin ++ }; ++ ++ au_update_dbstart(cpg.dentry); ++ inode = cpg.dentry->d_inode; ++ h_inode = NULL; ++ if (au_dbstart(cpg.dentry) <= bcpup ++ && au_dbend(cpg.dentry) >= bcpup) { ++ h_dentry = au_h_dptr(cpg.dentry, bcpup); ++ if (h_dentry) ++ h_inode = h_dentry->d_inode; ++ } ++ hi_wh = au_hi_wh(inode, bcpup); ++ if (!hi_wh && !h_inode) ++ err = au_sio_cpup_wh(&cpg, file); ++ else ++ /* already copied-up after unlink */ ++ err = au_reopen_wh(file, bcpup, hi_wh); ++ ++ if (!err ++ && (inode->i_nlink > 1 ++ || (inode->i_state & I_LINKABLE)) ++ && au_opt_test(au_mntflags(cpg.dentry->d_sb), PLINK)) ++ au_plink_append(inode, bcpup, au_h_dptr(cpg.dentry, bcpup)); ++ ++ return err; ++} ++ ++/* ++ * prepare the @file for writing. ++ */ ++int au_ready_to_write(struct file *file, loff_t len, struct au_pin *pin) ++{ ++ int err; ++ aufs_bindex_t dbstart; ++ struct dentry *parent; ++ struct inode *inode; ++ struct super_block *sb; ++ struct file *h_file; ++ struct au_cp_generic cpg = { ++ .dentry = file->f_dentry, ++ .bdst = -1, ++ .bsrc = -1, ++ .len = len, ++ .pin = pin, ++ .flags = AuCpup_DTIME ++ }; ++ ++ sb = cpg.dentry->d_sb; ++ inode = cpg.dentry->d_inode; ++ cpg.bsrc = au_fbstart(file); ++ err = au_test_ro(sb, cpg.bsrc, inode); ++ if (!err && (au_hf_top(file)->f_mode & FMODE_WRITE)) { ++ err = au_pin(pin, cpg.dentry, cpg.bsrc, AuOpt_UDBA_NONE, ++ /*flags*/0); ++ goto out; ++ } ++ ++ /* need to cpup or reopen */ ++ parent = dget_parent(cpg.dentry); ++ di_write_lock_parent(parent); ++ err = AuWbrCopyup(au_sbi(sb), cpg.dentry); ++ cpg.bdst = err; ++ if (unlikely(err < 0)) ++ goto out_dgrade; ++ err = 0; ++ ++ if (!d_unhashed(cpg.dentry) && !au_h_dptr(parent, cpg.bdst)) { ++ err = au_cpup_dirs(cpg.dentry, cpg.bdst); ++ if (unlikely(err)) ++ goto out_dgrade; ++ } ++ ++ err = au_pin(pin, cpg.dentry, cpg.bdst, AuOpt_UDBA_NONE, ++ AuPin_DI_LOCKED | AuPin_MNT_WRITE); ++ if (unlikely(err)) ++ goto out_dgrade; ++ ++ dbstart = au_dbstart(cpg.dentry); ++ if (dbstart <= cpg.bdst) ++ cpg.bsrc = cpg.bdst; ++ ++ if (dbstart <= cpg.bdst /* just reopen */ ++ || !d_unhashed(cpg.dentry) /* copyup and reopen */ ++ ) { ++ h_file = au_h_open_pre(cpg.dentry, cpg.bsrc, /*force_wr*/0); ++ if (IS_ERR(h_file)) ++ err = PTR_ERR(h_file); ++ else { ++ di_downgrade_lock(parent, AuLock_IR); ++ if (dbstart > cpg.bdst) ++ err = au_sio_cpup_simple(&cpg); ++ if (!err) ++ err = au_reopen_nondir(file); ++ au_h_open_post(cpg.dentry, cpg.bsrc, h_file); ++ } ++ } else { /* copyup as wh and reopen */ ++ /* ++ * since writable hfsplus branch is not supported, ++ * h_open_pre/post() are unnecessary. ++ */ ++ err = au_ready_to_write_wh(file, len, cpg.bdst, pin); ++ di_downgrade_lock(parent, AuLock_IR); ++ } ++ ++ if (!err) { ++ au_pin_set_parent_lflag(pin, /*lflag*/0); ++ goto out_dput; /* success */ ++ } ++ au_unpin(pin); ++ goto out_unlock; ++ ++out_dgrade: ++ di_downgrade_lock(parent, AuLock_IR); ++out_unlock: ++ di_read_unlock(parent, AuLock_IR); ++out_dput: ++ dput(parent); ++out: ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++int au_do_flush(struct file *file, fl_owner_t id, ++ int (*flush)(struct file *file, fl_owner_t id)) ++{ ++ int err; ++ struct super_block *sb; ++ struct inode *inode; ++ ++ inode = file_inode(file); ++ sb = inode->i_sb; ++ si_noflush_read_lock(sb); ++ fi_read_lock(file); ++ ii_read_lock_child(inode); ++ ++ err = flush(file, id); ++ au_cpup_attr_timesizes(inode); ++ ++ ii_read_unlock(inode); ++ fi_read_unlock(file); ++ si_read_unlock(sb); ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++static int au_file_refresh_by_inode(struct file *file, int *need_reopen) ++{ ++ int err; ++ struct au_pin pin; ++ struct au_finfo *finfo; ++ struct dentry *parent, *hi_wh; ++ struct inode *inode; ++ struct super_block *sb; ++ struct au_cp_generic cpg = { ++ .dentry = file->f_dentry, ++ .bdst = -1, ++ .bsrc = -1, ++ .len = -1, ++ .pin = &pin, ++ .flags = AuCpup_DTIME ++ }; ++ ++ FiMustWriteLock(file); ++ ++ err = 0; ++ finfo = au_fi(file); ++ sb = cpg.dentry->d_sb; ++ inode = cpg.dentry->d_inode; ++ cpg.bdst = au_ibstart(inode); ++ if (cpg.bdst == finfo->fi_btop || IS_ROOT(cpg.dentry)) ++ goto out; ++ ++ parent = dget_parent(cpg.dentry); ++ if (au_test_ro(sb, cpg.bdst, inode)) { ++ di_read_lock_parent(parent, !AuLock_IR); ++ err = AuWbrCopyup(au_sbi(sb), cpg.dentry); ++ cpg.bdst = err; ++ di_read_unlock(parent, !AuLock_IR); ++ if (unlikely(err < 0)) ++ goto out_parent; ++ err = 0; ++ } ++ ++ di_read_lock_parent(parent, AuLock_IR); ++ hi_wh = au_hi_wh(inode, cpg.bdst); ++ if (!S_ISDIR(inode->i_mode) ++ && au_opt_test(au_mntflags(sb), PLINK) ++ && au_plink_test(inode) ++ && !d_unhashed(cpg.dentry) ++ && cpg.bdst < au_dbstart(cpg.dentry)) { ++ err = au_test_and_cpup_dirs(cpg.dentry, cpg.bdst); ++ if (unlikely(err)) ++ goto out_unlock; ++ ++ /* always superio. */ ++ err = au_pin(&pin, cpg.dentry, cpg.bdst, AuOpt_UDBA_NONE, ++ AuPin_DI_LOCKED | AuPin_MNT_WRITE); ++ if (!err) { ++ err = au_sio_cpup_simple(&cpg); ++ au_unpin(&pin); ++ } ++ } else if (hi_wh) { ++ /* already copied-up after unlink */ ++ err = au_reopen_wh(file, cpg.bdst, hi_wh); ++ *need_reopen = 0; ++ } ++ ++out_unlock: ++ di_read_unlock(parent, AuLock_IR); ++out_parent: ++ dput(parent); ++out: ++ return err; ++} ++ ++static void au_do_refresh_dir(struct file *file) ++{ ++ aufs_bindex_t bindex, bend, new_bindex, brid; ++ struct au_hfile *p, tmp, *q; ++ struct au_finfo *finfo; ++ struct super_block *sb; ++ struct au_fidir *fidir; ++ ++ FiMustWriteLock(file); ++ ++ sb = file->f_dentry->d_sb; ++ finfo = au_fi(file); ++ fidir = finfo->fi_hdir; ++ AuDebugOn(!fidir); ++ p = fidir->fd_hfile + finfo->fi_btop; ++ brid = p->hf_br->br_id; ++ bend = fidir->fd_bbot; ++ for (bindex = finfo->fi_btop; bindex <= bend; bindex++, p++) { ++ if (!p->hf_file) ++ continue; ++ ++ new_bindex = au_br_index(sb, p->hf_br->br_id); ++ if (new_bindex == bindex) ++ continue; ++ if (new_bindex < 0) { ++ au_set_h_fptr(file, bindex, NULL); ++ continue; ++ } ++ ++ /* swap two lower inode, and loop again */ ++ q = fidir->fd_hfile + new_bindex; ++ tmp = *q; ++ *q = *p; ++ *p = tmp; ++ if (tmp.hf_file) { ++ bindex--; ++ p--; ++ } ++ } ++ ++ p = fidir->fd_hfile; ++ if (!au_test_mmapped(file) && !d_unlinked(file->f_dentry)) { ++ bend = au_sbend(sb); ++ for (finfo->fi_btop = 0; finfo->fi_btop <= bend; ++ finfo->fi_btop++, p++) ++ if (p->hf_file) { ++ if (file_inode(p->hf_file)) ++ break; ++ au_hfput(p, file); ++ } ++ } else { ++ bend = au_br_index(sb, brid); ++ for (finfo->fi_btop = 0; finfo->fi_btop < bend; ++ finfo->fi_btop++, p++) ++ if (p->hf_file) ++ au_hfput(p, file); ++ bend = au_sbend(sb); ++ } ++ ++ p = fidir->fd_hfile + bend; ++ for (fidir->fd_bbot = bend; fidir->fd_bbot >= finfo->fi_btop; ++ fidir->fd_bbot--, p--) ++ if (p->hf_file) { ++ if (file_inode(p->hf_file)) ++ break; ++ au_hfput(p, file); ++ } ++ AuDebugOn(fidir->fd_bbot < finfo->fi_btop); ++} ++ ++/* ++ * after branch manipulating, refresh the file. ++ */ ++static int refresh_file(struct file *file, int (*reopen)(struct file *file)) ++{ ++ int err, need_reopen; ++ aufs_bindex_t bend, bindex; ++ struct dentry *dentry; ++ struct au_finfo *finfo; ++ struct au_hfile *hfile; ++ ++ dentry = file->f_dentry; ++ finfo = au_fi(file); ++ if (!finfo->fi_hdir) { ++ hfile = &finfo->fi_htop; ++ AuDebugOn(!hfile->hf_file); ++ bindex = au_br_index(dentry->d_sb, hfile->hf_br->br_id); ++ AuDebugOn(bindex < 0); ++ if (bindex != finfo->fi_btop) ++ au_set_fbstart(file, bindex); ++ } else { ++ err = au_fidir_realloc(finfo, au_sbend(dentry->d_sb) + 1); ++ if (unlikely(err)) ++ goto out; ++ au_do_refresh_dir(file); ++ } ++ ++ err = 0; ++ need_reopen = 1; ++ if (!au_test_mmapped(file)) ++ err = au_file_refresh_by_inode(file, &need_reopen); ++ if (!err && need_reopen && !d_unlinked(dentry)) ++ err = reopen(file); ++ if (!err) { ++ au_update_figen(file); ++ goto out; /* success */ ++ } ++ ++ /* error, close all lower files */ ++ if (finfo->fi_hdir) { ++ bend = au_fbend_dir(file); ++ for (bindex = au_fbstart(file); bindex <= bend; bindex++) ++ au_set_h_fptr(file, bindex, NULL); ++ } ++ ++out: ++ return err; ++} ++ ++/* common function to regular file and dir */ ++int au_reval_and_lock_fdi(struct file *file, int (*reopen)(struct file *file), ++ int wlock) ++{ ++ int err; ++ unsigned int sigen, figen; ++ aufs_bindex_t bstart; ++ unsigned char pseudo_link; ++ struct dentry *dentry; ++ struct inode *inode; ++ ++ err = 0; ++ dentry = file->f_dentry; ++ inode = dentry->d_inode; ++ sigen = au_sigen(dentry->d_sb); ++ fi_write_lock(file); ++ figen = au_figen(file); ++ di_write_lock_child(dentry); ++ bstart = au_dbstart(dentry); ++ pseudo_link = (bstart != au_ibstart(inode)); ++ if (sigen == figen && !pseudo_link && au_fbstart(file) == bstart) { ++ if (!wlock) { ++ di_downgrade_lock(dentry, AuLock_IR); ++ fi_downgrade_lock(file); ++ } ++ goto out; /* success */ ++ } ++ ++ AuDbg("sigen %d, figen %d\n", sigen, figen); ++ if (au_digen_test(dentry, sigen)) { ++ err = au_reval_dpath(dentry, sigen); ++ AuDebugOn(!err && au_digen_test(dentry, sigen)); ++ } ++ ++ if (!err) ++ err = refresh_file(file, reopen); ++ if (!err) { ++ if (!wlock) { ++ di_downgrade_lock(dentry, AuLock_IR); ++ fi_downgrade_lock(file); ++ } ++ } else { ++ di_write_unlock(dentry); ++ fi_write_unlock(file); ++ } ++ ++out: ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* cf. aufs_nopage() */ ++/* for madvise(2) */ ++static int aufs_readpage(struct file *file __maybe_unused, struct page *page) ++{ ++ unlock_page(page); ++ return 0; ++} ++ ++/* it will never be called, but necessary to support O_DIRECT */ ++static ssize_t aufs_direct_IO(int rw, struct kiocb *iocb, ++ struct iov_iter *iter, loff_t offset) ++{ BUG(); return 0; } ++ ++/* ++ * it will never be called, but madvise and fadvise behaves differently ++ * when get_xip_mem is defined ++ */ ++static int aufs_get_xip_mem(struct address_space *mapping, pgoff_t pgoff, ++ int create, void **kmem, unsigned long *pfn) ++{ BUG(); return 0; } ++ ++/* they will never be called. */ ++#ifdef CONFIG_AUFS_DEBUG ++static int aufs_write_begin(struct file *file, struct address_space *mapping, ++ loff_t pos, unsigned len, unsigned flags, ++ struct page **pagep, void **fsdata) ++{ AuUnsupport(); return 0; } ++static int aufs_write_end(struct file *file, struct address_space *mapping, ++ loff_t pos, unsigned len, unsigned copied, ++ struct page *page, void *fsdata) ++{ AuUnsupport(); return 0; } ++static int aufs_writepage(struct page *page, struct writeback_control *wbc) ++{ AuUnsupport(); return 0; } ++ ++static int aufs_set_page_dirty(struct page *page) ++{ AuUnsupport(); return 0; } ++static void aufs_invalidatepage(struct page *page, unsigned int offset, ++ unsigned int length) ++{ AuUnsupport(); } ++static int aufs_releasepage(struct page *page, gfp_t gfp) ++{ AuUnsupport(); return 0; } ++#if 0 /* called by memory compaction regardless file */ ++static int aufs_migratepage(struct address_space *mapping, struct page *newpage, ++ struct page *page, enum migrate_mode mode) ++{ AuUnsupport(); return 0; } ++#endif ++static int aufs_launder_page(struct page *page) ++{ AuUnsupport(); return 0; } ++static int aufs_is_partially_uptodate(struct page *page, ++ unsigned long from, ++ unsigned long count) ++{ AuUnsupport(); return 0; } ++static void aufs_is_dirty_writeback(struct page *page, bool *dirty, ++ bool *writeback) ++{ AuUnsupport(); } ++static int aufs_error_remove_page(struct address_space *mapping, ++ struct page *page) ++{ AuUnsupport(); return 0; } ++static int aufs_swap_activate(struct swap_info_struct *sis, struct file *file, ++ sector_t *span) ++{ AuUnsupport(); return 0; } ++static void aufs_swap_deactivate(struct file *file) ++{ AuUnsupport(); } ++#endif /* CONFIG_AUFS_DEBUG */ ++ ++const struct address_space_operations aufs_aop = { ++ .readpage = aufs_readpage, ++ .direct_IO = aufs_direct_IO, ++ .get_xip_mem = aufs_get_xip_mem, ++#ifdef CONFIG_AUFS_DEBUG ++ .writepage = aufs_writepage, ++ /* no writepages, because of writepage */ ++ .set_page_dirty = aufs_set_page_dirty, ++ /* no readpages, because of readpage */ ++ .write_begin = aufs_write_begin, ++ .write_end = aufs_write_end, ++ /* no bmap, no block device */ ++ .invalidatepage = aufs_invalidatepage, ++ .releasepage = aufs_releasepage, ++ /* is fallback_migrate_page ok? */ ++ /* .migratepage = aufs_migratepage, */ ++ .launder_page = aufs_launder_page, ++ .is_partially_uptodate = aufs_is_partially_uptodate, ++ .is_dirty_writeback = aufs_is_dirty_writeback, ++ .error_remove_page = aufs_error_remove_page, ++ .swap_activate = aufs_swap_activate, ++ .swap_deactivate = aufs_swap_deactivate ++#endif /* CONFIG_AUFS_DEBUG */ ++}; +diff --git a/fs/aufs/file.h b/fs/aufs/file.h +new file mode 100644 +index 0000000..564be91 +--- /dev/null ++++ b/fs/aufs/file.h +@@ -0,0 +1,291 @@ ++/* ++ * Copyright (C) 2005-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * file operations ++ */ ++ ++#ifndef __AUFS_FILE_H__ ++#define __AUFS_FILE_H__ ++ ++#ifdef __KERNEL__ ++ ++#include ++#include ++#include ++#include "rwsem.h" ++ ++struct au_branch; ++struct au_hfile { ++ struct file *hf_file; ++ struct au_branch *hf_br; ++}; ++ ++struct au_vdir; ++struct au_fidir { ++ aufs_bindex_t fd_bbot; ++ aufs_bindex_t fd_nent; ++ struct au_vdir *fd_vdir_cache; ++ struct au_hfile fd_hfile[]; ++}; ++ ++static inline int au_fidir_sz(int nent) ++{ ++ AuDebugOn(nent < 0); ++ return sizeof(struct au_fidir) + sizeof(struct au_hfile) * nent; ++} ++ ++struct au_finfo { ++ atomic_t fi_generation; ++ ++ struct au_rwsem fi_rwsem; ++ aufs_bindex_t fi_btop; ++ ++ /* do not union them */ ++ struct { /* for non-dir */ ++ struct au_hfile fi_htop; ++ atomic_t fi_mmapped; ++ }; ++ struct au_fidir *fi_hdir; /* for dir only */ ++ ++ struct hlist_node fi_hlist; ++ struct file *fi_file; /* very ugly */ ++} ____cacheline_aligned_in_smp; ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* file.c */ ++extern const struct address_space_operations aufs_aop; ++unsigned int au_file_roflags(unsigned int flags); ++struct file *au_h_open(struct dentry *dentry, aufs_bindex_t bindex, int flags, ++ struct file *file, int force_wr); ++struct au_do_open_args { ++ int no_lock; ++ int (*open)(struct file *file, int flags, ++ struct file *h_file); ++ struct au_fidir *fidir; ++ struct file *h_file; ++}; ++int au_do_open(struct file *file, struct au_do_open_args *args); ++int au_reopen_nondir(struct file *file); ++struct au_pin; ++int au_ready_to_write(struct file *file, loff_t len, struct au_pin *pin); ++int au_reval_and_lock_fdi(struct file *file, int (*reopen)(struct file *file), ++ int wlock); ++int au_do_flush(struct file *file, fl_owner_t id, ++ int (*flush)(struct file *file, fl_owner_t id)); ++ ++/* poll.c */ ++#ifdef CONFIG_AUFS_POLL ++unsigned int aufs_poll(struct file *file, poll_table *wait); ++#endif ++ ++#ifdef CONFIG_AUFS_BR_HFSPLUS ++/* hfsplus.c */ ++struct file *au_h_open_pre(struct dentry *dentry, aufs_bindex_t bindex, ++ int force_wr); ++void au_h_open_post(struct dentry *dentry, aufs_bindex_t bindex, ++ struct file *h_file); ++#else ++AuStub(struct file *, au_h_open_pre, return NULL, struct dentry *dentry, ++ aufs_bindex_t bindex, int force_wr) ++AuStubVoid(au_h_open_post, struct dentry *dentry, aufs_bindex_t bindex, ++ struct file *h_file); ++#endif ++ ++/* f_op.c */ ++extern const struct file_operations aufs_file_fop; ++int au_do_open_nondir(struct file *file, int flags, struct file *h_file); ++int aufs_release_nondir(struct inode *inode __maybe_unused, struct file *file); ++struct file *au_read_pre(struct file *file, int keep_fi); ++ ++/* finfo.c */ ++void au_hfput(struct au_hfile *hf, struct file *file); ++void au_set_h_fptr(struct file *file, aufs_bindex_t bindex, ++ struct file *h_file); ++ ++void au_update_figen(struct file *file); ++struct au_fidir *au_fidir_alloc(struct super_block *sb); ++int au_fidir_realloc(struct au_finfo *finfo, int nbr); ++ ++void au_fi_init_once(void *_fi); ++void au_finfo_fin(struct file *file); ++int au_finfo_init(struct file *file, struct au_fidir *fidir); ++ ++/* ioctl.c */ ++long aufs_ioctl_nondir(struct file *file, unsigned int cmd, unsigned long arg); ++#ifdef CONFIG_COMPAT ++long aufs_compat_ioctl_dir(struct file *file, unsigned int cmd, ++ unsigned long arg); ++long aufs_compat_ioctl_nondir(struct file *file, unsigned int cmd, ++ unsigned long arg); ++#endif ++ ++/* ---------------------------------------------------------------------- */ ++ ++static inline struct au_finfo *au_fi(struct file *file) ++{ ++ return file->private_data; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* ++ * fi_read_lock, fi_write_lock, ++ * fi_read_unlock, fi_write_unlock, fi_downgrade_lock ++ */ ++AuSimpleRwsemFuncs(fi, struct file *f, &au_fi(f)->fi_rwsem); ++ ++#define FiMustNoWaiters(f) AuRwMustNoWaiters(&au_fi(f)->fi_rwsem) ++#define FiMustAnyLock(f) AuRwMustAnyLock(&au_fi(f)->fi_rwsem) ++#define FiMustWriteLock(f) AuRwMustWriteLock(&au_fi(f)->fi_rwsem) ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* todo: hard/soft set? */ ++static inline aufs_bindex_t au_fbstart(struct file *file) ++{ ++ FiMustAnyLock(file); ++ return au_fi(file)->fi_btop; ++} ++ ++static inline aufs_bindex_t au_fbend_dir(struct file *file) ++{ ++ FiMustAnyLock(file); ++ AuDebugOn(!au_fi(file)->fi_hdir); ++ return au_fi(file)->fi_hdir->fd_bbot; ++} ++ ++static inline struct au_vdir *au_fvdir_cache(struct file *file) ++{ ++ FiMustAnyLock(file); ++ AuDebugOn(!au_fi(file)->fi_hdir); ++ return au_fi(file)->fi_hdir->fd_vdir_cache; ++} ++ ++static inline void au_set_fbstart(struct file *file, aufs_bindex_t bindex) ++{ ++ FiMustWriteLock(file); ++ au_fi(file)->fi_btop = bindex; ++} ++ ++static inline void au_set_fbend_dir(struct file *file, aufs_bindex_t bindex) ++{ ++ FiMustWriteLock(file); ++ AuDebugOn(!au_fi(file)->fi_hdir); ++ au_fi(file)->fi_hdir->fd_bbot = bindex; ++} ++ ++static inline void au_set_fvdir_cache(struct file *file, ++ struct au_vdir *vdir_cache) ++{ ++ FiMustWriteLock(file); ++ AuDebugOn(!au_fi(file)->fi_hdir); ++ au_fi(file)->fi_hdir->fd_vdir_cache = vdir_cache; ++} ++ ++static inline struct file *au_hf_top(struct file *file) ++{ ++ FiMustAnyLock(file); ++ AuDebugOn(au_fi(file)->fi_hdir); ++ return au_fi(file)->fi_htop.hf_file; ++} ++ ++static inline struct file *au_hf_dir(struct file *file, aufs_bindex_t bindex) ++{ ++ FiMustAnyLock(file); ++ AuDebugOn(!au_fi(file)->fi_hdir); ++ return au_fi(file)->fi_hdir->fd_hfile[0 + bindex].hf_file; ++} ++ ++/* todo: memory barrier? */ ++static inline unsigned int au_figen(struct file *f) ++{ ++ return atomic_read(&au_fi(f)->fi_generation); ++} ++ ++static inline void au_set_mmapped(struct file *f) ++{ ++ if (atomic_inc_return(&au_fi(f)->fi_mmapped)) ++ return; ++ pr_warn("fi_mmapped wrapped around\n"); ++ while (!atomic_inc_return(&au_fi(f)->fi_mmapped)) ++ ; ++} ++ ++static inline void au_unset_mmapped(struct file *f) ++{ ++ atomic_dec(&au_fi(f)->fi_mmapped); ++} ++ ++static inline int au_test_mmapped(struct file *f) ++{ ++ return atomic_read(&au_fi(f)->fi_mmapped); ++} ++ ++/* customize vma->vm_file */ ++ ++static inline void au_do_vm_file_reset(struct vm_area_struct *vma, ++ struct file *file) ++{ ++ struct file *f; ++ ++ f = vma->vm_file; ++ get_file(file); ++ vma->vm_file = file; ++ fput(f); ++} ++ ++#ifdef CONFIG_MMU ++#define AuDbgVmRegion(file, vma) do {} while (0) ++ ++static inline void au_vm_file_reset(struct vm_area_struct *vma, ++ struct file *file) ++{ ++ au_do_vm_file_reset(vma, file); ++} ++#else ++#define AuDbgVmRegion(file, vma) \ ++ AuDebugOn((vma)->vm_region && (vma)->vm_region->vm_file != (file)) ++ ++static inline void au_vm_file_reset(struct vm_area_struct *vma, ++ struct file *file) ++{ ++ struct file *f; ++ ++ au_do_vm_file_reset(vma, file); ++ f = vma->vm_region->vm_file; ++ get_file(file); ++ vma->vm_region->vm_file = file; ++ fput(f); ++} ++#endif /* CONFIG_MMU */ ++ ++/* handle vma->vm_prfile */ ++static inline void au_vm_prfile_set(struct vm_area_struct *vma, ++ struct file *file) ++{ ++ get_file(file); ++ vma->vm_prfile = file; ++#ifndef CONFIG_MMU ++ get_file(file); ++ vma->vm_region->vm_prfile = file; ++#endif ++} ++ ++#endif /* __KERNEL__ */ ++#endif /* __AUFS_FILE_H__ */ +diff --git a/fs/aufs/finfo.c b/fs/aufs/finfo.c +new file mode 100644 +index 0000000..7e25db3 +--- /dev/null ++++ b/fs/aufs/finfo.c +@@ -0,0 +1,156 @@ ++/* ++ * Copyright (C) 2005-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * file private data ++ */ ++ ++#include "aufs.h" ++ ++void au_hfput(struct au_hfile *hf, struct file *file) ++{ ++ /* todo: direct access f_flags */ ++ if (vfsub_file_flags(file) & __FMODE_EXEC) ++ allow_write_access(hf->hf_file); ++ fput(hf->hf_file); ++ hf->hf_file = NULL; ++ atomic_dec(&hf->hf_br->br_count); ++ hf->hf_br = NULL; ++} ++ ++void au_set_h_fptr(struct file *file, aufs_bindex_t bindex, struct file *val) ++{ ++ struct au_finfo *finfo = au_fi(file); ++ struct au_hfile *hf; ++ struct au_fidir *fidir; ++ ++ fidir = finfo->fi_hdir; ++ if (!fidir) { ++ AuDebugOn(finfo->fi_btop != bindex); ++ hf = &finfo->fi_htop; ++ } else ++ hf = fidir->fd_hfile + bindex; ++ ++ if (hf && hf->hf_file) ++ au_hfput(hf, file); ++ if (val) { ++ FiMustWriteLock(file); ++ AuDebugOn(IS_ERR_OR_NULL(file->f_dentry)); ++ hf->hf_file = val; ++ hf->hf_br = au_sbr(file->f_dentry->d_sb, bindex); ++ } ++} ++ ++void au_update_figen(struct file *file) ++{ ++ atomic_set(&au_fi(file)->fi_generation, au_digen(file->f_dentry)); ++ /* smp_mb(); */ /* atomic_set */ ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++struct au_fidir *au_fidir_alloc(struct super_block *sb) ++{ ++ struct au_fidir *fidir; ++ int nbr; ++ ++ nbr = au_sbend(sb) + 1; ++ if (nbr < 2) ++ nbr = 2; /* initial allocate for 2 branches */ ++ fidir = kzalloc(au_fidir_sz(nbr), GFP_NOFS); ++ if (fidir) { ++ fidir->fd_bbot = -1; ++ fidir->fd_nent = nbr; ++ } ++ ++ return fidir; ++} ++ ++int au_fidir_realloc(struct au_finfo *finfo, int nbr) ++{ ++ int err; ++ struct au_fidir *fidir, *p; ++ ++ AuRwMustWriteLock(&finfo->fi_rwsem); ++ fidir = finfo->fi_hdir; ++ AuDebugOn(!fidir); ++ ++ err = -ENOMEM; ++ p = au_kzrealloc(fidir, au_fidir_sz(fidir->fd_nent), au_fidir_sz(nbr), ++ GFP_NOFS); ++ if (p) { ++ p->fd_nent = nbr; ++ finfo->fi_hdir = p; ++ err = 0; ++ } ++ ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++void au_finfo_fin(struct file *file) ++{ ++ struct au_finfo *finfo; ++ ++ au_nfiles_dec(file->f_dentry->d_sb); ++ ++ finfo = au_fi(file); ++ AuDebugOn(finfo->fi_hdir); ++ AuRwDestroy(&finfo->fi_rwsem); ++ au_cache_free_finfo(finfo); ++} ++ ++void au_fi_init_once(void *_finfo) ++{ ++ struct au_finfo *finfo = _finfo; ++ static struct lock_class_key aufs_fi; ++ ++ au_rw_init(&finfo->fi_rwsem); ++ au_rw_class(&finfo->fi_rwsem, &aufs_fi); ++} ++ ++int au_finfo_init(struct file *file, struct au_fidir *fidir) ++{ ++ int err; ++ struct au_finfo *finfo; ++ struct dentry *dentry; ++ ++ err = -ENOMEM; ++ dentry = file->f_dentry; ++ finfo = au_cache_alloc_finfo(); ++ if (unlikely(!finfo)) ++ goto out; ++ ++ err = 0; ++ au_nfiles_inc(dentry->d_sb); ++ /* verbose coding for lock class name */ ++ if (!fidir) ++ au_rw_class(&finfo->fi_rwsem, au_lc_key + AuLcNonDir_FIINFO); ++ else ++ au_rw_class(&finfo->fi_rwsem, au_lc_key + AuLcDir_FIINFO); ++ au_rw_write_lock(&finfo->fi_rwsem); ++ finfo->fi_btop = -1; ++ finfo->fi_hdir = fidir; ++ atomic_set(&finfo->fi_generation, au_digen(dentry)); ++ /* smp_mb(); */ /* atomic_set */ ++ ++ file->private_data = finfo; ++ ++out: ++ return err; ++} +diff --git a/fs/aufs/fstype.h b/fs/aufs/fstype.h +new file mode 100644 +index 0000000..2842400 +--- /dev/null ++++ b/fs/aufs/fstype.h +@@ -0,0 +1,400 @@ ++/* ++ * Copyright (C) 2005-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * judging filesystem type ++ */ ++ ++#ifndef __AUFS_FSTYPE_H__ ++#define __AUFS_FSTYPE_H__ ++ ++#ifdef __KERNEL__ ++ ++#include ++#include ++#include ++#include ++ ++static inline int au_test_aufs(struct super_block *sb) ++{ ++ return sb->s_magic == AUFS_SUPER_MAGIC; ++} ++ ++static inline const char *au_sbtype(struct super_block *sb) ++{ ++ return sb->s_type->name; ++} ++ ++static inline int au_test_iso9660(struct super_block *sb __maybe_unused) ++{ ++#if defined(CONFIG_ISO9660_FS) || defined(CONFIG_ISO9660_FS_MODULE) ++ return sb->s_magic == ISOFS_SUPER_MAGIC; ++#else ++ return 0; ++#endif ++} ++ ++static inline int au_test_romfs(struct super_block *sb __maybe_unused) ++{ ++#if defined(CONFIG_ROMFS_FS) || defined(CONFIG_ROMFS_FS_MODULE) ++ return sb->s_magic == ROMFS_MAGIC; ++#else ++ return 0; ++#endif ++} ++ ++static inline int au_test_cramfs(struct super_block *sb __maybe_unused) ++{ ++#if defined(CONFIG_CRAMFS) || defined(CONFIG_CRAMFS_MODULE) ++ return sb->s_magic == CRAMFS_MAGIC; ++#endif ++ return 0; ++} ++ ++static inline int au_test_nfs(struct super_block *sb __maybe_unused) ++{ ++#if defined(CONFIG_NFS_FS) || defined(CONFIG_NFS_FS_MODULE) ++ return sb->s_magic == NFS_SUPER_MAGIC; ++#else ++ return 0; ++#endif ++} ++ ++static inline int au_test_fuse(struct super_block *sb __maybe_unused) ++{ ++#if defined(CONFIG_FUSE_FS) || defined(CONFIG_FUSE_FS_MODULE) ++ return sb->s_magic == FUSE_SUPER_MAGIC; ++#else ++ return 0; ++#endif ++} ++ ++static inline int au_test_xfs(struct super_block *sb __maybe_unused) ++{ ++#if defined(CONFIG_XFS_FS) || defined(CONFIG_XFS_FS_MODULE) ++ return sb->s_magic == XFS_SB_MAGIC; ++#else ++ return 0; ++#endif ++} ++ ++static inline int au_test_tmpfs(struct super_block *sb __maybe_unused) ++{ ++#ifdef CONFIG_TMPFS ++ return sb->s_magic == TMPFS_MAGIC; ++#else ++ return 0; ++#endif ++} ++ ++static inline int au_test_ecryptfs(struct super_block *sb __maybe_unused) ++{ ++#if defined(CONFIG_ECRYPT_FS) || defined(CONFIG_ECRYPT_FS_MODULE) ++ return !strcmp(au_sbtype(sb), "ecryptfs"); ++#else ++ return 0; ++#endif ++} ++ ++static inline int au_test_ramfs(struct super_block *sb) ++{ ++ return sb->s_magic == RAMFS_MAGIC; ++} ++ ++static inline int au_test_ubifs(struct super_block *sb __maybe_unused) ++{ ++#if defined(CONFIG_UBIFS_FS) || defined(CONFIG_UBIFS_FS_MODULE) ++ return sb->s_magic == UBIFS_SUPER_MAGIC; ++#else ++ return 0; ++#endif ++} ++ ++static inline int au_test_procfs(struct super_block *sb __maybe_unused) ++{ ++#ifdef CONFIG_PROC_FS ++ return sb->s_magic == PROC_SUPER_MAGIC; ++#else ++ return 0; ++#endif ++} ++ ++static inline int au_test_sysfs(struct super_block *sb __maybe_unused) ++{ ++#ifdef CONFIG_SYSFS ++ return sb->s_magic == SYSFS_MAGIC; ++#else ++ return 0; ++#endif ++} ++ ++static inline int au_test_configfs(struct super_block *sb __maybe_unused) ++{ ++#if defined(CONFIG_CONFIGFS_FS) || defined(CONFIG_CONFIGFS_FS_MODULE) ++ return sb->s_magic == CONFIGFS_MAGIC; ++#else ++ return 0; ++#endif ++} ++ ++static inline int au_test_minix(struct super_block *sb __maybe_unused) ++{ ++#if defined(CONFIG_MINIX_FS) || defined(CONFIG_MINIX_FS_MODULE) ++ return sb->s_magic == MINIX3_SUPER_MAGIC ++ || sb->s_magic == MINIX2_SUPER_MAGIC ++ || sb->s_magic == MINIX2_SUPER_MAGIC2 ++ || sb->s_magic == MINIX_SUPER_MAGIC ++ || sb->s_magic == MINIX_SUPER_MAGIC2; ++#else ++ return 0; ++#endif ++} ++ ++static inline int au_test_fat(struct super_block *sb __maybe_unused) ++{ ++#if defined(CONFIG_FAT_FS) || defined(CONFIG_FAT_FS_MODULE) ++ return sb->s_magic == MSDOS_SUPER_MAGIC; ++#else ++ return 0; ++#endif ++} ++ ++static inline int au_test_msdos(struct super_block *sb) ++{ ++ return au_test_fat(sb); ++} ++ ++static inline int au_test_vfat(struct super_block *sb) ++{ ++ return au_test_fat(sb); ++} ++ ++static inline int au_test_securityfs(struct super_block *sb __maybe_unused) ++{ ++#ifdef CONFIG_SECURITYFS ++ return sb->s_magic == SECURITYFS_MAGIC; ++#else ++ return 0; ++#endif ++} ++ ++static inline int au_test_squashfs(struct super_block *sb __maybe_unused) ++{ ++#if defined(CONFIG_SQUASHFS) || defined(CONFIG_SQUASHFS_MODULE) ++ return sb->s_magic == SQUASHFS_MAGIC; ++#else ++ return 0; ++#endif ++} ++ ++static inline int au_test_btrfs(struct super_block *sb __maybe_unused) ++{ ++#if defined(CONFIG_BTRFS_FS) || defined(CONFIG_BTRFS_FS_MODULE) ++ return sb->s_magic == BTRFS_SUPER_MAGIC; ++#else ++ return 0; ++#endif ++} ++ ++static inline int au_test_xenfs(struct super_block *sb __maybe_unused) ++{ ++#if defined(CONFIG_XENFS) || defined(CONFIG_XENFS_MODULE) ++ return sb->s_magic == XENFS_SUPER_MAGIC; ++#else ++ return 0; ++#endif ++} ++ ++static inline int au_test_debugfs(struct super_block *sb __maybe_unused) ++{ ++#ifdef CONFIG_DEBUG_FS ++ return sb->s_magic == DEBUGFS_MAGIC; ++#else ++ return 0; ++#endif ++} ++ ++static inline int au_test_nilfs(struct super_block *sb __maybe_unused) ++{ ++#if defined(CONFIG_NILFS) || defined(CONFIG_NILFS_MODULE) ++ return sb->s_magic == NILFS_SUPER_MAGIC; ++#else ++ return 0; ++#endif ++} ++ ++static inline int au_test_hfsplus(struct super_block *sb __maybe_unused) ++{ ++#if defined(CONFIG_HFSPLUS_FS) || defined(CONFIG_HFSPLUS_FS_MODULE) ++ return sb->s_magic == HFSPLUS_SUPER_MAGIC; ++#else ++ return 0; ++#endif ++} ++ ++/* ---------------------------------------------------------------------- */ ++/* ++ * they can't be an aufs branch. ++ */ ++static inline int au_test_fs_unsuppoted(struct super_block *sb) ++{ ++ return ++#ifndef CONFIG_AUFS_BR_RAMFS ++ au_test_ramfs(sb) || ++#endif ++ au_test_procfs(sb) ++ || au_test_sysfs(sb) ++ || au_test_configfs(sb) ++ || au_test_debugfs(sb) ++ || au_test_securityfs(sb) ++ || au_test_xenfs(sb) ++ || au_test_ecryptfs(sb) ++ /* || !strcmp(au_sbtype(sb), "unionfs") */ ++ || au_test_aufs(sb); /* will be supported in next version */ ++} ++ ++static inline int au_test_fs_remote(struct super_block *sb) ++{ ++ return !au_test_tmpfs(sb) ++#ifdef CONFIG_AUFS_BR_RAMFS ++ && !au_test_ramfs(sb) ++#endif ++ && !(sb->s_type->fs_flags & FS_REQUIRES_DEV); ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* ++ * Note: these functions (below) are created after reading ->getattr() in all ++ * filesystems under linux/fs. it means we have to do so in every update... ++ */ ++ ++/* ++ * some filesystems require getattr to refresh the inode attributes before ++ * referencing. ++ * in most cases, we can rely on the inode attribute in NFS (or every remote fs) ++ * and leave the work for d_revalidate() ++ */ ++static inline int au_test_fs_refresh_iattr(struct super_block *sb) ++{ ++ return au_test_nfs(sb) ++ || au_test_fuse(sb) ++ /* || au_test_btrfs(sb) */ /* untested */ ++ ; ++} ++ ++/* ++ * filesystems which don't maintain i_size or i_blocks. ++ */ ++static inline int au_test_fs_bad_iattr_size(struct super_block *sb) ++{ ++ return au_test_xfs(sb) ++ || au_test_btrfs(sb) ++ || au_test_ubifs(sb) ++ || au_test_hfsplus(sb) /* maintained, but incorrect */ ++ /* || au_test_minix(sb) */ /* untested */ ++ ; ++} ++ ++/* ++ * filesystems which don't store the correct value in some of their inode ++ * attributes. ++ */ ++static inline int au_test_fs_bad_iattr(struct super_block *sb) ++{ ++ return au_test_fs_bad_iattr_size(sb) ++ || au_test_fat(sb) ++ || au_test_msdos(sb) ++ || au_test_vfat(sb); ++} ++ ++/* they don't check i_nlink in link(2) */ ++static inline int au_test_fs_no_limit_nlink(struct super_block *sb) ++{ ++ return au_test_tmpfs(sb) ++#ifdef CONFIG_AUFS_BR_RAMFS ++ || au_test_ramfs(sb) ++#endif ++ || au_test_ubifs(sb) ++ || au_test_hfsplus(sb); ++} ++ ++/* ++ * filesystems which sets S_NOATIME and S_NOCMTIME. ++ */ ++static inline int au_test_fs_notime(struct super_block *sb) ++{ ++ return au_test_nfs(sb) ++ || au_test_fuse(sb) ++ || au_test_ubifs(sb) ++ ; ++} ++ ++/* temporary support for i#1 in cramfs */ ++static inline int au_test_fs_unique_ino(struct inode *inode) ++{ ++ if (au_test_cramfs(inode->i_sb)) ++ return inode->i_ino != 1; ++ return 1; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* ++ * the filesystem where the xino files placed must support i/o after unlink and ++ * maintain i_size and i_blocks. ++ */ ++static inline int au_test_fs_bad_xino(struct super_block *sb) ++{ ++ return au_test_fs_remote(sb) ++ || au_test_fs_bad_iattr_size(sb) ++ /* don't want unnecessary work for xino */ ++ || au_test_aufs(sb) ++ || au_test_ecryptfs(sb) ++ || au_test_nilfs(sb); ++} ++ ++static inline int au_test_fs_trunc_xino(struct super_block *sb) ++{ ++ return au_test_tmpfs(sb) ++ || au_test_ramfs(sb); ++} ++ ++/* ++ * test if the @sb is real-readonly. ++ */ ++static inline int au_test_fs_rr(struct super_block *sb) ++{ ++ return au_test_squashfs(sb) ++ || au_test_iso9660(sb) ++ || au_test_cramfs(sb) ++ || au_test_romfs(sb); ++} ++ ++/* ++ * test if the @inode is nfs with 'noacl' option ++ * NFS always sets MS_POSIXACL regardless its mount option 'noacl.' ++ */ ++static inline int au_test_nfs_noacl(struct inode *inode) ++{ ++ return au_test_nfs(inode->i_sb) ++ /* && IS_POSIXACL(inode) */ ++ && !nfs_server_capable(inode, NFS_CAP_ACLS); ++} ++ ++#endif /* __KERNEL__ */ ++#endif /* __AUFS_FSTYPE_H__ */ +diff --git a/fs/aufs/hfsnotify.c b/fs/aufs/hfsnotify.c +new file mode 100644 +index 0000000..6fa79b0 +--- /dev/null ++++ b/fs/aufs/hfsnotify.c +@@ -0,0 +1,288 @@ ++/* ++ * Copyright (C) 2005-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * fsnotify for the lower directories ++ */ ++ ++#include "aufs.h" ++ ++/* FS_IN_IGNORED is unnecessary */ ++static const __u32 AuHfsnMask = (FS_MOVED_TO | FS_MOVED_FROM | FS_DELETE ++ | FS_CREATE | FS_EVENT_ON_CHILD); ++static DECLARE_WAIT_QUEUE_HEAD(au_hfsn_wq); ++static __cacheline_aligned_in_smp atomic64_t au_hfsn_ifree = ATOMIC64_INIT(0); ++ ++static void au_hfsn_free_mark(struct fsnotify_mark *mark) ++{ ++ struct au_hnotify *hn = container_of(mark, struct au_hnotify, ++ hn_mark); ++ AuDbg("here\n"); ++ au_cache_free_hnotify(hn); ++ smp_mb__before_atomic(); ++ if (atomic64_dec_and_test(&au_hfsn_ifree)) ++ wake_up(&au_hfsn_wq); ++} ++ ++static int au_hfsn_alloc(struct au_hinode *hinode) ++{ ++ int err; ++ struct au_hnotify *hn; ++ struct super_block *sb; ++ struct au_branch *br; ++ struct fsnotify_mark *mark; ++ aufs_bindex_t bindex; ++ ++ hn = hinode->hi_notify; ++ sb = hn->hn_aufs_inode->i_sb; ++ bindex = au_br_index(sb, hinode->hi_id); ++ br = au_sbr(sb, bindex); ++ AuDebugOn(!br->br_hfsn); ++ ++ mark = &hn->hn_mark; ++ fsnotify_init_mark(mark, au_hfsn_free_mark); ++ mark->mask = AuHfsnMask; ++ /* ++ * by udba rename or rmdir, aufs assign a new inode to the known ++ * h_inode, so specify 1 to allow dups. ++ */ ++ lockdep_off(); ++ err = fsnotify_add_mark(mark, br->br_hfsn->hfsn_group, hinode->hi_inode, ++ /*mnt*/NULL, /*allow_dups*/1); ++ /* even if err */ ++ fsnotify_put_mark(mark); ++ lockdep_on(); ++ ++ return err; ++} ++ ++static int au_hfsn_free(struct au_hinode *hinode, struct au_hnotify *hn) ++{ ++ struct fsnotify_mark *mark; ++ unsigned long long ull; ++ struct fsnotify_group *group; ++ ++ ull = atomic64_inc_return(&au_hfsn_ifree); ++ BUG_ON(!ull); ++ ++ mark = &hn->hn_mark; ++ spin_lock(&mark->lock); ++ group = mark->group; ++ fsnotify_get_group(group); ++ spin_unlock(&mark->lock); ++ lockdep_off(); ++ fsnotify_destroy_mark(mark, group); ++ fsnotify_put_group(group); ++ lockdep_on(); ++ ++ /* free hn by myself */ ++ return 0; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++static void au_hfsn_ctl(struct au_hinode *hinode, int do_set) ++{ ++ struct fsnotify_mark *mark; ++ ++ mark = &hinode->hi_notify->hn_mark; ++ spin_lock(&mark->lock); ++ if (do_set) { ++ AuDebugOn(mark->mask & AuHfsnMask); ++ mark->mask |= AuHfsnMask; ++ } else { ++ AuDebugOn(!(mark->mask & AuHfsnMask)); ++ mark->mask &= ~AuHfsnMask; ++ } ++ spin_unlock(&mark->lock); ++ /* fsnotify_recalc_inode_mask(hinode->hi_inode); */ ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* #define AuDbgHnotify */ ++#ifdef AuDbgHnotify ++static char *au_hfsn_name(u32 mask) ++{ ++#ifdef CONFIG_AUFS_DEBUG ++#define test_ret(flag) \ ++ do { \ ++ if (mask & flag) \ ++ return #flag; \ ++ } while (0) ++ test_ret(FS_ACCESS); ++ test_ret(FS_MODIFY); ++ test_ret(FS_ATTRIB); ++ test_ret(FS_CLOSE_WRITE); ++ test_ret(FS_CLOSE_NOWRITE); ++ test_ret(FS_OPEN); ++ test_ret(FS_MOVED_FROM); ++ test_ret(FS_MOVED_TO); ++ test_ret(FS_CREATE); ++ test_ret(FS_DELETE); ++ test_ret(FS_DELETE_SELF); ++ test_ret(FS_MOVE_SELF); ++ test_ret(FS_UNMOUNT); ++ test_ret(FS_Q_OVERFLOW); ++ test_ret(FS_IN_IGNORED); ++ test_ret(FS_ISDIR); ++ test_ret(FS_IN_ONESHOT); ++ test_ret(FS_EVENT_ON_CHILD); ++ return ""; ++#undef test_ret ++#else ++ return "??"; ++#endif ++} ++#endif ++ ++/* ---------------------------------------------------------------------- */ ++ ++static void au_hfsn_free_group(struct fsnotify_group *group) ++{ ++ struct au_br_hfsnotify *hfsn = group->private; ++ ++ AuDbg("here\n"); ++ kfree(hfsn); ++} ++ ++static int au_hfsn_handle_event(struct fsnotify_group *group, ++ struct inode *inode, ++ struct fsnotify_mark *inode_mark, ++ struct fsnotify_mark *vfsmount_mark, ++ u32 mask, void *data, int data_type, ++ const unsigned char *file_name, u32 cookie) ++{ ++ int err; ++ struct au_hnotify *hnotify; ++ struct inode *h_dir, *h_inode; ++ struct qstr h_child_qstr = QSTR_INIT(file_name, strlen(file_name)); ++ ++ AuDebugOn(data_type != FSNOTIFY_EVENT_INODE); ++ ++ err = 0; ++ /* if FS_UNMOUNT happens, there must be another bug */ ++ AuDebugOn(mask & FS_UNMOUNT); ++ if (mask & (FS_IN_IGNORED | FS_UNMOUNT)) ++ goto out; ++ ++ h_dir = inode; ++ h_inode = NULL; ++#ifdef AuDbgHnotify ++ au_debug_on(); ++ if (1 || h_child_qstr.len != sizeof(AUFS_XINO_FNAME) - 1 ++ || strncmp(h_child_qstr.name, AUFS_XINO_FNAME, h_child_qstr.len)) { ++ AuDbg("i%lu, mask 0x%x %s, hcname %.*s, hi%lu\n", ++ h_dir->i_ino, mask, au_hfsn_name(mask), ++ AuLNPair(&h_child_qstr), h_inode ? h_inode->i_ino : 0); ++ /* WARN_ON(1); */ ++ } ++ au_debug_off(); ++#endif ++ ++ AuDebugOn(!inode_mark); ++ hnotify = container_of(inode_mark, struct au_hnotify, hn_mark); ++ err = au_hnotify(h_dir, hnotify, mask, &h_child_qstr, h_inode); ++ ++out: ++ return err; ++} ++ ++static struct fsnotify_ops au_hfsn_ops = { ++ .handle_event = au_hfsn_handle_event, ++ .free_group_priv = au_hfsn_free_group ++}; ++ ++/* ---------------------------------------------------------------------- */ ++ ++static void au_hfsn_fin_br(struct au_branch *br) ++{ ++ struct au_br_hfsnotify *hfsn; ++ ++ hfsn = br->br_hfsn; ++ if (hfsn) { ++ lockdep_off(); ++ fsnotify_put_group(hfsn->hfsn_group); ++ lockdep_on(); ++ } ++} ++ ++static int au_hfsn_init_br(struct au_branch *br, int perm) ++{ ++ int err; ++ struct fsnotify_group *group; ++ struct au_br_hfsnotify *hfsn; ++ ++ err = 0; ++ br->br_hfsn = NULL; ++ if (!au_br_hnotifyable(perm)) ++ goto out; ++ ++ err = -ENOMEM; ++ hfsn = kmalloc(sizeof(*hfsn), GFP_NOFS); ++ if (unlikely(!hfsn)) ++ goto out; ++ ++ err = 0; ++ group = fsnotify_alloc_group(&au_hfsn_ops); ++ if (IS_ERR(group)) { ++ err = PTR_ERR(group); ++ pr_err("fsnotify_alloc_group() failed, %d\n", err); ++ goto out_hfsn; ++ } ++ ++ group->private = hfsn; ++ hfsn->hfsn_group = group; ++ br->br_hfsn = hfsn; ++ goto out; /* success */ ++ ++out_hfsn: ++ kfree(hfsn); ++out: ++ return err; ++} ++ ++static int au_hfsn_reset_br(unsigned int udba, struct au_branch *br, int perm) ++{ ++ int err; ++ ++ err = 0; ++ if (!br->br_hfsn) ++ err = au_hfsn_init_br(br, perm); ++ ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++static void au_hfsn_fin(void) ++{ ++ AuDbg("au_hfsn_ifree %lld\n", (long long)atomic64_read(&au_hfsn_ifree)); ++ wait_event(au_hfsn_wq, !atomic64_read(&au_hfsn_ifree)); ++} ++ ++const struct au_hnotify_op au_hnotify_op = { ++ .ctl = au_hfsn_ctl, ++ .alloc = au_hfsn_alloc, ++ .free = au_hfsn_free, ++ ++ .fin = au_hfsn_fin, ++ ++ .reset_br = au_hfsn_reset_br, ++ .fin_br = au_hfsn_fin_br, ++ .init_br = au_hfsn_init_br ++}; +diff --git a/fs/aufs/hfsplus.c b/fs/aufs/hfsplus.c +new file mode 100644 +index 0000000..8a54c82 +--- /dev/null ++++ b/fs/aufs/hfsplus.c +@@ -0,0 +1,56 @@ ++/* ++ * Copyright (C) 2010-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * special support for filesystems which aqucires an inode mutex ++ * at final closing a file, eg, hfsplus. ++ * ++ * This trick is very simple and stupid, just to open the file before really ++ * neceeary open to tell hfsplus that this is not the final closing. ++ * The caller should call au_h_open_pre() after acquiring the inode mutex, ++ * and au_h_open_post() after releasing it. ++ */ ++ ++#include "aufs.h" ++ ++struct file *au_h_open_pre(struct dentry *dentry, aufs_bindex_t bindex, ++ int force_wr) ++{ ++ struct file *h_file; ++ struct dentry *h_dentry; ++ ++ h_dentry = au_h_dptr(dentry, bindex); ++ AuDebugOn(!h_dentry); ++ AuDebugOn(!h_dentry->d_inode); ++ ++ h_file = NULL; ++ if (au_test_hfsplus(h_dentry->d_sb) ++ && S_ISREG(h_dentry->d_inode->i_mode)) ++ h_file = au_h_open(dentry, bindex, ++ O_RDONLY | O_NOATIME | O_LARGEFILE, ++ /*file*/NULL, force_wr); ++ return h_file; ++} ++ ++void au_h_open_post(struct dentry *dentry, aufs_bindex_t bindex, ++ struct file *h_file) ++{ ++ if (h_file) { ++ fput(h_file); ++ au_sbr_put(dentry->d_sb, bindex); ++ } ++} +diff --git a/fs/aufs/hnotify.c b/fs/aufs/hnotify.c +new file mode 100644 +index 0000000..1801420 +--- /dev/null ++++ b/fs/aufs/hnotify.c +@@ -0,0 +1,714 @@ ++/* ++ * Copyright (C) 2005-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * abstraction to notify the direct changes on lower directories ++ */ ++ ++#include "aufs.h" ++ ++int au_hn_alloc(struct au_hinode *hinode, struct inode *inode) ++{ ++ int err; ++ struct au_hnotify *hn; ++ ++ err = -ENOMEM; ++ hn = au_cache_alloc_hnotify(); ++ if (hn) { ++ hn->hn_aufs_inode = inode; ++ hinode->hi_notify = hn; ++ err = au_hnotify_op.alloc(hinode); ++ AuTraceErr(err); ++ if (unlikely(err)) { ++ hinode->hi_notify = NULL; ++ au_cache_free_hnotify(hn); ++ /* ++ * The upper dir was removed by udba, but the same named ++ * dir left. In this case, aufs assignes a new inode ++ * number and set the monitor again. ++ * For the lower dir, the old monitnor is still left. ++ */ ++ if (err == -EEXIST) ++ err = 0; ++ } ++ } ++ ++ AuTraceErr(err); ++ return err; ++} ++ ++void au_hn_free(struct au_hinode *hinode) ++{ ++ struct au_hnotify *hn; ++ ++ hn = hinode->hi_notify; ++ if (hn) { ++ hinode->hi_notify = NULL; ++ if (au_hnotify_op.free(hinode, hn)) ++ au_cache_free_hnotify(hn); ++ } ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++void au_hn_ctl(struct au_hinode *hinode, int do_set) ++{ ++ if (hinode->hi_notify) ++ au_hnotify_op.ctl(hinode, do_set); ++} ++ ++void au_hn_reset(struct inode *inode, unsigned int flags) ++{ ++ aufs_bindex_t bindex, bend; ++ struct inode *hi; ++ struct dentry *iwhdentry; ++ ++ bend = au_ibend(inode); ++ for (bindex = au_ibstart(inode); bindex <= bend; bindex++) { ++ hi = au_h_iptr(inode, bindex); ++ if (!hi) ++ continue; ++ ++ /* mutex_lock_nested(&hi->i_mutex, AuLsc_I_CHILD); */ ++ iwhdentry = au_hi_wh(inode, bindex); ++ if (iwhdentry) ++ dget(iwhdentry); ++ au_igrab(hi); ++ au_set_h_iptr(inode, bindex, NULL, 0); ++ au_set_h_iptr(inode, bindex, au_igrab(hi), ++ flags & ~AuHi_XINO); ++ iput(hi); ++ dput(iwhdentry); ++ /* mutex_unlock(&hi->i_mutex); */ ++ } ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++static int hn_xino(struct inode *inode, struct inode *h_inode) ++{ ++ int err; ++ aufs_bindex_t bindex, bend, bfound, bstart; ++ struct inode *h_i; ++ ++ err = 0; ++ if (unlikely(inode->i_ino == AUFS_ROOT_INO)) { ++ pr_warn("branch root dir was changed\n"); ++ goto out; ++ } ++ ++ bfound = -1; ++ bend = au_ibend(inode); ++ bstart = au_ibstart(inode); ++#if 0 /* reserved for future use */ ++ if (bindex == bend) { ++ /* keep this ino in rename case */ ++ goto out; ++ } ++#endif ++ for (bindex = bstart; bindex <= bend; bindex++) ++ if (au_h_iptr(inode, bindex) == h_inode) { ++ bfound = bindex; ++ break; ++ } ++ if (bfound < 0) ++ goto out; ++ ++ for (bindex = bstart; bindex <= bend; bindex++) { ++ h_i = au_h_iptr(inode, bindex); ++ if (!h_i) ++ continue; ++ ++ err = au_xino_write(inode->i_sb, bindex, h_i->i_ino, /*ino*/0); ++ /* ignore this error */ ++ /* bad action? */ ++ } ++ ++ /* children inode number will be broken */ ++ ++out: ++ AuTraceErr(err); ++ return err; ++} ++ ++static int hn_gen_tree(struct dentry *dentry) ++{ ++ int err, i, j, ndentry; ++ struct au_dcsub_pages dpages; ++ struct au_dpage *dpage; ++ struct dentry **dentries; ++ ++ err = au_dpages_init(&dpages, GFP_NOFS); ++ if (unlikely(err)) ++ goto out; ++ err = au_dcsub_pages(&dpages, dentry, NULL, NULL); ++ if (unlikely(err)) ++ goto out_dpages; ++ ++ for (i = 0; i < dpages.ndpage; i++) { ++ dpage = dpages.dpages + i; ++ dentries = dpage->dentries; ++ ndentry = dpage->ndentry; ++ for (j = 0; j < ndentry; j++) { ++ struct dentry *d; ++ ++ d = dentries[j]; ++ if (IS_ROOT(d)) ++ continue; ++ ++ au_digen_dec(d); ++ if (d->d_inode) ++ /* todo: reset children xino? ++ cached children only? */ ++ au_iigen_dec(d->d_inode); ++ } ++ } ++ ++out_dpages: ++ au_dpages_free(&dpages); ++ ++#if 0 ++ /* discard children */ ++ dentry_unhash(dentry); ++ dput(dentry); ++#endif ++out: ++ return err; ++} ++ ++/* ++ * return 0 if processed. ++ */ ++static int hn_gen_by_inode(char *name, unsigned int nlen, struct inode *inode, ++ const unsigned int isdir) ++{ ++ int err; ++ struct dentry *d; ++ struct qstr *dname; ++ ++ err = 1; ++ if (unlikely(inode->i_ino == AUFS_ROOT_INO)) { ++ pr_warn("branch root dir was changed\n"); ++ err = 0; ++ goto out; ++ } ++ ++ if (!isdir) { ++ AuDebugOn(!name); ++ au_iigen_dec(inode); ++ spin_lock(&inode->i_lock); ++ hlist_for_each_entry(d, &inode->i_dentry, d_u.d_alias) { ++ spin_lock(&d->d_lock); ++ dname = &d->d_name; ++ if (dname->len != nlen ++ && memcmp(dname->name, name, nlen)) { ++ spin_unlock(&d->d_lock); ++ continue; ++ } ++ err = 0; ++ au_digen_dec(d); ++ spin_unlock(&d->d_lock); ++ break; ++ } ++ spin_unlock(&inode->i_lock); ++ } else { ++ au_fset_si(au_sbi(inode->i_sb), FAILED_REFRESH_DIR); ++ d = d_find_any_alias(inode); ++ if (!d) { ++ au_iigen_dec(inode); ++ goto out; ++ } ++ ++ spin_lock(&d->d_lock); ++ dname = &d->d_name; ++ if (dname->len == nlen && !memcmp(dname->name, name, nlen)) { ++ spin_unlock(&d->d_lock); ++ err = hn_gen_tree(d); ++ spin_lock(&d->d_lock); ++ } ++ spin_unlock(&d->d_lock); ++ dput(d); ++ } ++ ++out: ++ AuTraceErr(err); ++ return err; ++} ++ ++static int hn_gen_by_name(struct dentry *dentry, const unsigned int isdir) ++{ ++ int err; ++ struct inode *inode; ++ ++ inode = dentry->d_inode; ++ if (IS_ROOT(dentry) ++ /* || (inode && inode->i_ino == AUFS_ROOT_INO) */ ++ ) { ++ pr_warn("branch root dir was changed\n"); ++ return 0; ++ } ++ ++ err = 0; ++ if (!isdir) { ++ au_digen_dec(dentry); ++ if (inode) ++ au_iigen_dec(inode); ++ } else { ++ au_fset_si(au_sbi(dentry->d_sb), FAILED_REFRESH_DIR); ++ if (inode) ++ err = hn_gen_tree(dentry); ++ } ++ ++ AuTraceErr(err); ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* hnotify job flags */ ++#define AuHnJob_XINO0 1 ++#define AuHnJob_GEN (1 << 1) ++#define AuHnJob_DIRENT (1 << 2) ++#define AuHnJob_ISDIR (1 << 3) ++#define AuHnJob_TRYXINO0 (1 << 4) ++#define AuHnJob_MNTPNT (1 << 5) ++#define au_ftest_hnjob(flags, name) ((flags) & AuHnJob_##name) ++#define au_fset_hnjob(flags, name) \ ++ do { (flags) |= AuHnJob_##name; } while (0) ++#define au_fclr_hnjob(flags, name) \ ++ do { (flags) &= ~AuHnJob_##name; } while (0) ++ ++enum { ++ AuHn_CHILD, ++ AuHn_PARENT, ++ AuHnLast ++}; ++ ++struct au_hnotify_args { ++ struct inode *h_dir, *dir, *h_child_inode; ++ u32 mask; ++ unsigned int flags[AuHnLast]; ++ unsigned int h_child_nlen; ++ char h_child_name[]; ++}; ++ ++struct hn_job_args { ++ unsigned int flags; ++ struct inode *inode, *h_inode, *dir, *h_dir; ++ struct dentry *dentry; ++ char *h_name; ++ int h_nlen; ++}; ++ ++static int hn_job(struct hn_job_args *a) ++{ ++ const unsigned int isdir = au_ftest_hnjob(a->flags, ISDIR); ++ int e; ++ ++ /* reset xino */ ++ if (au_ftest_hnjob(a->flags, XINO0) && a->inode) ++ hn_xino(a->inode, a->h_inode); /* ignore this error */ ++ ++ if (au_ftest_hnjob(a->flags, TRYXINO0) ++ && a->inode ++ && a->h_inode) { ++ mutex_lock_nested(&a->h_inode->i_mutex, AuLsc_I_CHILD); ++ if (!a->h_inode->i_nlink ++ && !(a->h_inode->i_state & I_LINKABLE)) ++ hn_xino(a->inode, a->h_inode); /* ignore this error */ ++ mutex_unlock(&a->h_inode->i_mutex); ++ } ++ ++ /* make the generation obsolete */ ++ if (au_ftest_hnjob(a->flags, GEN)) { ++ e = -1; ++ if (a->inode) ++ e = hn_gen_by_inode(a->h_name, a->h_nlen, a->inode, ++ isdir); ++ if (e && a->dentry) ++ hn_gen_by_name(a->dentry, isdir); ++ /* ignore this error */ ++ } ++ ++ /* make dir entries obsolete */ ++ if (au_ftest_hnjob(a->flags, DIRENT) && a->inode) { ++ struct au_vdir *vdir; ++ ++ vdir = au_ivdir(a->inode); ++ if (vdir) ++ vdir->vd_jiffy = 0; ++ /* IMustLock(a->inode); */ ++ /* a->inode->i_version++; */ ++ } ++ ++ /* can do nothing but warn */ ++ if (au_ftest_hnjob(a->flags, MNTPNT) ++ && a->dentry ++ && d_mountpoint(a->dentry)) ++ pr_warn("mount-point %pd is removed or renamed\n", a->dentry); ++ ++ return 0; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++static struct dentry *lookup_wlock_by_name(char *name, unsigned int nlen, ++ struct inode *dir) ++{ ++ struct dentry *dentry, *d, *parent; ++ struct qstr *dname; ++ ++ parent = d_find_any_alias(dir); ++ if (!parent) ++ return NULL; ++ ++ dentry = NULL; ++ spin_lock(&parent->d_lock); ++ list_for_each_entry(d, &parent->d_subdirs, d_child) { ++ /* AuDbg("%pd\n", d); */ ++ spin_lock_nested(&d->d_lock, DENTRY_D_LOCK_NESTED); ++ dname = &d->d_name; ++ if (dname->len != nlen || memcmp(dname->name, name, nlen)) ++ goto cont_unlock; ++ if (au_di(d)) ++ au_digen_dec(d); ++ else ++ goto cont_unlock; ++ if (au_dcount(d) > 0) { ++ dentry = dget_dlock(d); ++ spin_unlock(&d->d_lock); ++ break; ++ } ++ ++cont_unlock: ++ spin_unlock(&d->d_lock); ++ } ++ spin_unlock(&parent->d_lock); ++ dput(parent); ++ ++ if (dentry) ++ di_write_lock_child(dentry); ++ ++ return dentry; ++} ++ ++static struct inode *lookup_wlock_by_ino(struct super_block *sb, ++ aufs_bindex_t bindex, ino_t h_ino) ++{ ++ struct inode *inode; ++ ino_t ino; ++ int err; ++ ++ inode = NULL; ++ err = au_xino_read(sb, bindex, h_ino, &ino); ++ if (!err && ino) ++ inode = ilookup(sb, ino); ++ if (!inode) ++ goto out; ++ ++ if (unlikely(inode->i_ino == AUFS_ROOT_INO)) { ++ pr_warn("wrong root branch\n"); ++ iput(inode); ++ inode = NULL; ++ goto out; ++ } ++ ++ ii_write_lock_child(inode); ++ ++out: ++ return inode; ++} ++ ++static void au_hn_bh(void *_args) ++{ ++ struct au_hnotify_args *a = _args; ++ struct super_block *sb; ++ aufs_bindex_t bindex, bend, bfound; ++ unsigned char xino, try_iput; ++ int err; ++ struct inode *inode; ++ ino_t h_ino; ++ struct hn_job_args args; ++ struct dentry *dentry; ++ struct au_sbinfo *sbinfo; ++ ++ AuDebugOn(!_args); ++ AuDebugOn(!a->h_dir); ++ AuDebugOn(!a->dir); ++ AuDebugOn(!a->mask); ++ AuDbg("mask 0x%x, i%lu, hi%lu, hci%lu\n", ++ a->mask, a->dir->i_ino, a->h_dir->i_ino, ++ a->h_child_inode ? a->h_child_inode->i_ino : 0); ++ ++ inode = NULL; ++ dentry = NULL; ++ /* ++ * do not lock a->dir->i_mutex here ++ * because of d_revalidate() may cause a deadlock. ++ */ ++ sb = a->dir->i_sb; ++ AuDebugOn(!sb); ++ sbinfo = au_sbi(sb); ++ AuDebugOn(!sbinfo); ++ si_write_lock(sb, AuLock_NOPLMW); ++ ++ ii_read_lock_parent(a->dir); ++ bfound = -1; ++ bend = au_ibend(a->dir); ++ for (bindex = au_ibstart(a->dir); bindex <= bend; bindex++) ++ if (au_h_iptr(a->dir, bindex) == a->h_dir) { ++ bfound = bindex; ++ break; ++ } ++ ii_read_unlock(a->dir); ++ if (unlikely(bfound < 0)) ++ goto out; ++ ++ xino = !!au_opt_test(au_mntflags(sb), XINO); ++ h_ino = 0; ++ if (a->h_child_inode) ++ h_ino = a->h_child_inode->i_ino; ++ ++ if (a->h_child_nlen ++ && (au_ftest_hnjob(a->flags[AuHn_CHILD], GEN) ++ || au_ftest_hnjob(a->flags[AuHn_CHILD], MNTPNT))) ++ dentry = lookup_wlock_by_name(a->h_child_name, a->h_child_nlen, ++ a->dir); ++ try_iput = 0; ++ if (dentry) ++ inode = dentry->d_inode; ++ if (xino && !inode && h_ino ++ && (au_ftest_hnjob(a->flags[AuHn_CHILD], XINO0) ++ || au_ftest_hnjob(a->flags[AuHn_CHILD], TRYXINO0) ++ || au_ftest_hnjob(a->flags[AuHn_CHILD], GEN))) { ++ inode = lookup_wlock_by_ino(sb, bfound, h_ino); ++ try_iput = 1; ++ } ++ ++ args.flags = a->flags[AuHn_CHILD]; ++ args.dentry = dentry; ++ args.inode = inode; ++ args.h_inode = a->h_child_inode; ++ args.dir = a->dir; ++ args.h_dir = a->h_dir; ++ args.h_name = a->h_child_name; ++ args.h_nlen = a->h_child_nlen; ++ err = hn_job(&args); ++ if (dentry) { ++ if (au_di(dentry)) ++ di_write_unlock(dentry); ++ dput(dentry); ++ } ++ if (inode && try_iput) { ++ ii_write_unlock(inode); ++ iput(inode); ++ } ++ ++ ii_write_lock_parent(a->dir); ++ args.flags = a->flags[AuHn_PARENT]; ++ args.dentry = NULL; ++ args.inode = a->dir; ++ args.h_inode = a->h_dir; ++ args.dir = NULL; ++ args.h_dir = NULL; ++ args.h_name = NULL; ++ args.h_nlen = 0; ++ err = hn_job(&args); ++ ii_write_unlock(a->dir); ++ ++out: ++ iput(a->h_child_inode); ++ iput(a->h_dir); ++ iput(a->dir); ++ si_write_unlock(sb); ++ au_nwt_done(&sbinfo->si_nowait); ++ kfree(a); ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++int au_hnotify(struct inode *h_dir, struct au_hnotify *hnotify, u32 mask, ++ struct qstr *h_child_qstr, struct inode *h_child_inode) ++{ ++ int err, len; ++ unsigned int flags[AuHnLast], f; ++ unsigned char isdir, isroot, wh; ++ struct inode *dir; ++ struct au_hnotify_args *args; ++ char *p, *h_child_name; ++ ++ err = 0; ++ AuDebugOn(!hnotify || !hnotify->hn_aufs_inode); ++ dir = igrab(hnotify->hn_aufs_inode); ++ if (!dir) ++ goto out; ++ ++ isroot = (dir->i_ino == AUFS_ROOT_INO); ++ wh = 0; ++ h_child_name = (void *)h_child_qstr->name; ++ len = h_child_qstr->len; ++ if (h_child_name) { ++ if (len > AUFS_WH_PFX_LEN ++ && !memcmp(h_child_name, AUFS_WH_PFX, AUFS_WH_PFX_LEN)) { ++ h_child_name += AUFS_WH_PFX_LEN; ++ len -= AUFS_WH_PFX_LEN; ++ wh = 1; ++ } ++ } ++ ++ isdir = 0; ++ if (h_child_inode) ++ isdir = !!S_ISDIR(h_child_inode->i_mode); ++ flags[AuHn_PARENT] = AuHnJob_ISDIR; ++ flags[AuHn_CHILD] = 0; ++ if (isdir) ++ flags[AuHn_CHILD] = AuHnJob_ISDIR; ++ au_fset_hnjob(flags[AuHn_PARENT], DIRENT); ++ au_fset_hnjob(flags[AuHn_CHILD], GEN); ++ switch (mask & FS_EVENTS_POSS_ON_CHILD) { ++ case FS_MOVED_FROM: ++ case FS_MOVED_TO: ++ au_fset_hnjob(flags[AuHn_CHILD], XINO0); ++ au_fset_hnjob(flags[AuHn_CHILD], MNTPNT); ++ /*FALLTHROUGH*/ ++ case FS_CREATE: ++ AuDebugOn(!h_child_name); ++ break; ++ ++ case FS_DELETE: ++ /* ++ * aufs never be able to get this child inode. ++ * revalidation should be in d_revalidate() ++ * by checking i_nlink, i_generation or d_unhashed(). ++ */ ++ AuDebugOn(!h_child_name); ++ au_fset_hnjob(flags[AuHn_CHILD], TRYXINO0); ++ au_fset_hnjob(flags[AuHn_CHILD], MNTPNT); ++ break; ++ ++ default: ++ AuDebugOn(1); ++ } ++ ++ if (wh) ++ h_child_inode = NULL; ++ ++ err = -ENOMEM; ++ /* iput() and kfree() will be called in au_hnotify() */ ++ args = kmalloc(sizeof(*args) + len + 1, GFP_NOFS); ++ if (unlikely(!args)) { ++ AuErr1("no memory\n"); ++ iput(dir); ++ goto out; ++ } ++ args->flags[AuHn_PARENT] = flags[AuHn_PARENT]; ++ args->flags[AuHn_CHILD] = flags[AuHn_CHILD]; ++ args->mask = mask; ++ args->dir = dir; ++ args->h_dir = igrab(h_dir); ++ if (h_child_inode) ++ h_child_inode = igrab(h_child_inode); /* can be NULL */ ++ args->h_child_inode = h_child_inode; ++ args->h_child_nlen = len; ++ if (len) { ++ p = (void *)args; ++ p += sizeof(*args); ++ memcpy(p, h_child_name, len); ++ p[len] = 0; ++ } ++ ++ /* NFS fires the event for silly-renamed one from kworker */ ++ f = 0; ++ if (!dir->i_nlink ++ || (au_test_nfs(h_dir->i_sb) && (mask & FS_DELETE))) ++ f = AuWkq_NEST; ++ err = au_wkq_nowait(au_hn_bh, args, dir->i_sb, f); ++ if (unlikely(err)) { ++ pr_err("wkq %d\n", err); ++ iput(args->h_child_inode); ++ iput(args->h_dir); ++ iput(args->dir); ++ kfree(args); ++ } ++ ++out: ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++int au_hnotify_reset_br(unsigned int udba, struct au_branch *br, int perm) ++{ ++ int err; ++ ++ AuDebugOn(!(udba & AuOptMask_UDBA)); ++ ++ err = 0; ++ if (au_hnotify_op.reset_br) ++ err = au_hnotify_op.reset_br(udba, br, perm); ++ ++ return err; ++} ++ ++int au_hnotify_init_br(struct au_branch *br, int perm) ++{ ++ int err; ++ ++ err = 0; ++ if (au_hnotify_op.init_br) ++ err = au_hnotify_op.init_br(br, perm); ++ ++ return err; ++} ++ ++void au_hnotify_fin_br(struct au_branch *br) ++{ ++ if (au_hnotify_op.fin_br) ++ au_hnotify_op.fin_br(br); ++} ++ ++static void au_hn_destroy_cache(void) ++{ ++ kmem_cache_destroy(au_cachep[AuCache_HNOTIFY]); ++ au_cachep[AuCache_HNOTIFY] = NULL; ++} ++ ++int __init au_hnotify_init(void) ++{ ++ int err; ++ ++ err = -ENOMEM; ++ au_cachep[AuCache_HNOTIFY] = AuCache(au_hnotify); ++ if (au_cachep[AuCache_HNOTIFY]) { ++ err = 0; ++ if (au_hnotify_op.init) ++ err = au_hnotify_op.init(); ++ if (unlikely(err)) ++ au_hn_destroy_cache(); ++ } ++ AuTraceErr(err); ++ return err; ++} ++ ++void au_hnotify_fin(void) ++{ ++ if (au_hnotify_op.fin) ++ au_hnotify_op.fin(); ++ /* cf. au_cache_fin() */ ++ if (au_cachep[AuCache_HNOTIFY]) ++ au_hn_destroy_cache(); ++} +diff --git a/fs/aufs/i_op.c b/fs/aufs/i_op.c +new file mode 100644 +index 0000000..02dc95a +--- /dev/null ++++ b/fs/aufs/i_op.c +@@ -0,0 +1,1460 @@ ++/* ++ * Copyright (C) 2005-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * inode operations (except add/del/rename) ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include "aufs.h" ++ ++static int h_permission(struct inode *h_inode, int mask, ++ struct vfsmount *h_mnt, int brperm) ++{ ++ int err; ++ const unsigned char write_mask = !!(mask & (MAY_WRITE | MAY_APPEND)); ++ ++ err = -EACCES; ++ if ((write_mask && IS_IMMUTABLE(h_inode)) ++ || ((mask & MAY_EXEC) ++ && S_ISREG(h_inode->i_mode) ++ && ((h_mnt->mnt_flags & MNT_NOEXEC) ++ || !(h_inode->i_mode & S_IXUGO)))) ++ goto out; ++ ++ /* ++ * - skip the lower fs test in the case of write to ro branch. ++ * - nfs dir permission write check is optimized, but a policy for ++ * link/rename requires a real check. ++ * - nfs always sets MS_POSIXACL regardless its mount option 'noacl.' ++ * in this case, generic_permission() returns -EOPNOTSUPP. ++ */ ++ if ((write_mask && !au_br_writable(brperm)) ++ || (au_test_nfs(h_inode->i_sb) && S_ISDIR(h_inode->i_mode) ++ && write_mask && !(mask & MAY_READ)) ++ || !h_inode->i_op->permission) { ++ /* AuLabel(generic_permission); */ ++ /* AuDbg("get_acl %pf\n", h_inode->i_op->get_acl); */ ++ err = generic_permission(h_inode, mask); ++ if (err == -EOPNOTSUPP && au_test_nfs_noacl(h_inode)) ++ err = h_inode->i_op->permission(h_inode, mask); ++ AuTraceErr(err); ++ } else { ++ /* AuLabel(h_inode->permission); */ ++ err = h_inode->i_op->permission(h_inode, mask); ++ AuTraceErr(err); ++ } ++ ++ if (!err) ++ err = devcgroup_inode_permission(h_inode, mask); ++ if (!err) ++ err = security_inode_permission(h_inode, mask); ++ ++#if 0 ++ if (!err) { ++ /* todo: do we need to call ima_path_check()? */ ++ struct path h_path = { ++ .dentry = ++ .mnt = h_mnt ++ }; ++ err = ima_path_check(&h_path, ++ mask & (MAY_READ | MAY_WRITE | MAY_EXEC), ++ IMA_COUNT_LEAVE); ++ } ++#endif ++ ++out: ++ return err; ++} ++ ++static int aufs_permission(struct inode *inode, int mask) ++{ ++ int err; ++ aufs_bindex_t bindex, bend; ++ const unsigned char isdir = !!S_ISDIR(inode->i_mode), ++ write_mask = !!(mask & (MAY_WRITE | MAY_APPEND)); ++ struct inode *h_inode; ++ struct super_block *sb; ++ struct au_branch *br; ++ ++ /* todo: support rcu-walk? */ ++ if (mask & MAY_NOT_BLOCK) ++ return -ECHILD; ++ ++ sb = inode->i_sb; ++ si_read_lock(sb, AuLock_FLUSH); ++ ii_read_lock_child(inode); ++#if 0 ++ err = au_iigen_test(inode, au_sigen(sb)); ++ if (unlikely(err)) ++ goto out; ++#endif ++ ++ if (!isdir ++ || write_mask ++ || au_opt_test(au_mntflags(sb), DIRPERM1)) { ++ err = au_busy_or_stale(); ++ h_inode = au_h_iptr(inode, au_ibstart(inode)); ++ if (unlikely(!h_inode ++ || (h_inode->i_mode & S_IFMT) ++ != (inode->i_mode & S_IFMT))) ++ goto out; ++ ++ err = 0; ++ bindex = au_ibstart(inode); ++ br = au_sbr(sb, bindex); ++ err = h_permission(h_inode, mask, au_br_mnt(br), br->br_perm); ++ if (write_mask ++ && !err ++ && !special_file(h_inode->i_mode)) { ++ /* test whether the upper writable branch exists */ ++ err = -EROFS; ++ for (; bindex >= 0; bindex--) ++ if (!au_br_rdonly(au_sbr(sb, bindex))) { ++ err = 0; ++ break; ++ } ++ } ++ goto out; ++ } ++ ++ /* non-write to dir */ ++ err = 0; ++ bend = au_ibend(inode); ++ for (bindex = au_ibstart(inode); !err && bindex <= bend; bindex++) { ++ h_inode = au_h_iptr(inode, bindex); ++ if (h_inode) { ++ err = au_busy_or_stale(); ++ if (unlikely(!S_ISDIR(h_inode->i_mode))) ++ break; ++ ++ br = au_sbr(sb, bindex); ++ err = h_permission(h_inode, mask, au_br_mnt(br), ++ br->br_perm); ++ } ++ } ++ ++out: ++ ii_read_unlock(inode); ++ si_read_unlock(sb); ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++static struct dentry *aufs_lookup(struct inode *dir, struct dentry *dentry, ++ unsigned int flags) ++{ ++ struct dentry *ret, *parent; ++ struct inode *inode; ++ struct super_block *sb; ++ int err, npositive; ++ ++ IMustLock(dir); ++ ++ /* todo: support rcu-walk? */ ++ ret = ERR_PTR(-ECHILD); ++ if (flags & LOOKUP_RCU) ++ goto out; ++ ++ ret = ERR_PTR(-ENAMETOOLONG); ++ if (unlikely(dentry->d_name.len > AUFS_MAX_NAMELEN)) ++ goto out; ++ ++ sb = dir->i_sb; ++ err = si_read_lock(sb, AuLock_FLUSH | AuLock_NOPLM); ++ ret = ERR_PTR(err); ++ if (unlikely(err)) ++ goto out; ++ ++ err = au_di_init(dentry); ++ ret = ERR_PTR(err); ++ if (unlikely(err)) ++ goto out_si; ++ ++ inode = NULL; ++ npositive = 0; /* suppress a warning */ ++ parent = dentry->d_parent; /* dir inode is locked */ ++ di_read_lock_parent(parent, AuLock_IR); ++ err = au_alive_dir(parent); ++ if (!err) ++ err = au_digen_test(parent, au_sigen(sb)); ++ if (!err) { ++ npositive = au_lkup_dentry(dentry, au_dbstart(parent), ++ /*type*/0); ++ err = npositive; ++ } ++ di_read_unlock(parent, AuLock_IR); ++ ret = ERR_PTR(err); ++ if (unlikely(err < 0)) ++ goto out_unlock; ++ ++ if (npositive) { ++ inode = au_new_inode(dentry, /*must_new*/0); ++ if (IS_ERR(inode)) { ++ ret = (void *)inode; ++ inode = NULL; ++ goto out_unlock; ++ } ++ } ++ ++ if (inode) ++ atomic_inc(&inode->i_count); ++ ret = d_splice_alias(inode, dentry); ++ if (IS_ERR(ret) ++ && PTR_ERR(ret) == -EIO ++ && inode ++ && S_ISDIR(inode->i_mode)) { ++ atomic_inc(&inode->i_count); ++ ret = d_materialise_unique(dentry, inode); ++ if (!IS_ERR(ret)) ++ ii_write_unlock(inode); ++ } ++#if 0 ++ if (unlikely(d_need_lookup(dentry))) { ++ spin_lock(&dentry->d_lock); ++ dentry->d_flags &= ~DCACHE_NEED_LOOKUP; ++ spin_unlock(&dentry->d_lock); ++ } else ++#endif ++ if (inode) { ++ if (!IS_ERR(ret)) ++ iput(inode); ++ else { ++ ii_write_unlock(inode); ++ iput(inode); ++ inode = NULL; ++ } ++ } ++ ++out_unlock: ++ di_write_unlock(dentry); ++ if (inode) { ++ /* verbose coding for lock class name */ ++ if (unlikely(S_ISLNK(inode->i_mode))) ++ au_rw_class(&au_di(dentry)->di_rwsem, ++ au_lc_key + AuLcSymlink_DIINFO); ++ else if (unlikely(S_ISDIR(inode->i_mode))) ++ au_rw_class(&au_di(dentry)->di_rwsem, ++ au_lc_key + AuLcDir_DIINFO); ++ else /* likely */ ++ au_rw_class(&au_di(dentry)->di_rwsem, ++ au_lc_key + AuLcNonDir_DIINFO); ++ } ++out_si: ++ si_read_unlock(sb); ++out: ++ return ret; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++struct aopen_node { ++ struct hlist_node hlist; ++ struct file *file, *h_file; ++}; ++ ++static int au_do_aopen(struct inode *inode, struct file *file) ++{ ++ struct au_sphlhead *aopen; ++ struct aopen_node *node; ++ struct au_do_open_args args = { ++ .no_lock = 1, ++ .open = au_do_open_nondir ++ }; ++ ++ aopen = &au_sbi(inode->i_sb)->si_aopen; ++ spin_lock(&aopen->spin); ++ hlist_for_each_entry(node, &aopen->head, hlist) ++ if (node->file == file) { ++ args.h_file = node->h_file; ++ break; ++ } ++ spin_unlock(&aopen->spin); ++ /* AuDebugOn(!args.h_file); */ ++ ++ return au_do_open(file, &args); ++} ++ ++static int aufs_atomic_open(struct inode *dir, struct dentry *dentry, ++ struct file *file, unsigned int open_flag, ++ umode_t create_mode, int *opened) ++{ ++ int err, h_opened = *opened; ++ struct dentry *parent; ++ struct dentry *d; ++ struct au_sphlhead *aopen; ++ struct vfsub_aopen_args args = { ++ .open_flag = open_flag, ++ .create_mode = create_mode, ++ .opened = &h_opened ++ }; ++ struct aopen_node aopen_node = { ++ .file = file ++ }; ++ ++ IMustLock(dir); ++ AuDbg("open_flag 0x%x\n", open_flag); ++ AuDbgDentry(dentry); ++ ++ err = 0; ++ if (!au_di(dentry)) { ++ d = aufs_lookup(dir, dentry, /*flags*/0); ++ if (IS_ERR(d)) { ++ err = PTR_ERR(d); ++ goto out; ++ } else if (d) { ++ /* ++ * obsoleted dentry found. ++ * another error will be returned later. ++ */ ++ d_drop(d); ++ dput(d); ++ AuDbgDentry(d); ++ } ++ AuDbgDentry(dentry); ++ } ++ ++ if (d_is_positive(dentry) ++ || d_unhashed(dentry) ++ || d_unlinked(dentry) ++ || !(open_flag & O_CREAT)) ++ goto out_no_open; ++ ++ err = aufs_read_lock(dentry, AuLock_DW | AuLock_FLUSH | AuLock_GEN); ++ if (unlikely(err)) ++ goto out; ++ ++ parent = dentry->d_parent; /* dir is locked */ ++ di_write_lock_parent(parent); ++ err = au_lkup_dentry(dentry, /*bstart*/0, /*type*/0); ++ if (unlikely(err)) ++ goto out_unlock; ++ ++ AuDbgDentry(dentry); ++ if (d_is_positive(dentry)) ++ goto out_unlock; ++ ++ args.file = get_empty_filp(); ++ err = PTR_ERR(args.file); ++ if (IS_ERR(args.file)) ++ goto out_unlock; ++ ++ args.file->f_flags = file->f_flags; ++ err = au_aopen_or_create(dir, dentry, &args); ++ AuTraceErr(err); ++ AuDbgFile(args.file); ++ if (unlikely(err < 0)) { ++ if (h_opened & FILE_OPENED) ++ fput(args.file); ++ else ++ put_filp(args.file); ++ goto out_unlock; ++ } ++ ++ /* some filesystems don't set FILE_CREATED while succeeded? */ ++ *opened |= FILE_CREATED; ++ if (h_opened & FILE_OPENED) ++ aopen_node.h_file = args.file; ++ else { ++ put_filp(args.file); ++ args.file = NULL; ++ } ++ aopen = &au_sbi(dir->i_sb)->si_aopen; ++ au_sphl_add(&aopen_node.hlist, aopen); ++ err = finish_open(file, dentry, au_do_aopen, opened); ++ au_sphl_del(&aopen_node.hlist, aopen); ++ AuTraceErr(err); ++ AuDbgFile(file); ++ if (aopen_node.h_file) ++ fput(aopen_node.h_file); ++ ++out_unlock: ++ di_write_unlock(parent); ++ aufs_read_unlock(dentry, AuLock_DW); ++ AuDbgDentry(dentry); ++ if (unlikely(err)) ++ goto out; ++out_no_open: ++ if (!err && !(*opened & FILE_CREATED)) { ++ AuLabel(out_no_open); ++ dget(dentry); ++ err = finish_no_open(file, dentry); ++ } ++out: ++ AuDbg("%pd%s%s\n", dentry, ++ (*opened & FILE_CREATED) ? " created" : "", ++ (*opened & FILE_OPENED) ? " opened" : ""); ++ AuTraceErr(err); ++ return err; ++} ++ ++ ++/* ---------------------------------------------------------------------- */ ++ ++static int au_wr_dir_cpup(struct dentry *dentry, struct dentry *parent, ++ const unsigned char add_entry, aufs_bindex_t bcpup, ++ aufs_bindex_t bstart) ++{ ++ int err; ++ struct dentry *h_parent; ++ struct inode *h_dir; ++ ++ if (add_entry) ++ IMustLock(parent->d_inode); ++ else ++ di_write_lock_parent(parent); ++ ++ err = 0; ++ if (!au_h_dptr(parent, bcpup)) { ++ if (bstart > bcpup) ++ err = au_cpup_dirs(dentry, bcpup); ++ else if (bstart < bcpup) ++ err = au_cpdown_dirs(dentry, bcpup); ++ else ++ BUG(); ++ } ++ if (!err && add_entry && !au_ftest_wrdir(add_entry, TMPFILE)) { ++ h_parent = au_h_dptr(parent, bcpup); ++ h_dir = h_parent->d_inode; ++ mutex_lock_nested(&h_dir->i_mutex, AuLsc_I_PARENT); ++ err = au_lkup_neg(dentry, bcpup, /*wh*/0); ++ /* todo: no unlock here */ ++ mutex_unlock(&h_dir->i_mutex); ++ ++ AuDbg("bcpup %d\n", bcpup); ++ if (!err) { ++ if (!dentry->d_inode) ++ au_set_h_dptr(dentry, bstart, NULL); ++ au_update_dbrange(dentry, /*do_put_zero*/0); ++ } ++ } ++ ++ if (!add_entry) ++ di_write_unlock(parent); ++ if (!err) ++ err = bcpup; /* success */ ++ ++ AuTraceErr(err); ++ return err; ++} ++ ++/* ++ * decide the branch and the parent dir where we will create a new entry. ++ * returns new bindex or an error. ++ * copyup the parent dir if needed. ++ */ ++int au_wr_dir(struct dentry *dentry, struct dentry *src_dentry, ++ struct au_wr_dir_args *args) ++{ ++ int err; ++ unsigned int flags; ++ aufs_bindex_t bcpup, bstart, src_bstart; ++ const unsigned char add_entry ++ = au_ftest_wrdir(args->flags, ADD_ENTRY) ++ | au_ftest_wrdir(args->flags, TMPFILE); ++ struct super_block *sb; ++ struct dentry *parent; ++ struct au_sbinfo *sbinfo; ++ ++ sb = dentry->d_sb; ++ sbinfo = au_sbi(sb); ++ parent = dget_parent(dentry); ++ bstart = au_dbstart(dentry); ++ bcpup = bstart; ++ if (args->force_btgt < 0) { ++ if (src_dentry) { ++ src_bstart = au_dbstart(src_dentry); ++ if (src_bstart < bstart) ++ bcpup = src_bstart; ++ } else if (add_entry) { ++ flags = 0; ++ if (au_ftest_wrdir(args->flags, ISDIR)) ++ au_fset_wbr(flags, DIR); ++ err = AuWbrCreate(sbinfo, dentry, flags); ++ bcpup = err; ++ } ++ ++ if (bcpup < 0 || au_test_ro(sb, bcpup, dentry->d_inode)) { ++ if (add_entry) ++ err = AuWbrCopyup(sbinfo, dentry); ++ else { ++ if (!IS_ROOT(dentry)) { ++ di_read_lock_parent(parent, !AuLock_IR); ++ err = AuWbrCopyup(sbinfo, dentry); ++ di_read_unlock(parent, !AuLock_IR); ++ } else ++ err = AuWbrCopyup(sbinfo, dentry); ++ } ++ bcpup = err; ++ if (unlikely(err < 0)) ++ goto out; ++ } ++ } else { ++ bcpup = args->force_btgt; ++ AuDebugOn(au_test_ro(sb, bcpup, dentry->d_inode)); ++ } ++ ++ AuDbg("bstart %d, bcpup %d\n", bstart, bcpup); ++ err = bcpup; ++ if (bcpup == bstart) ++ goto out; /* success */ ++ ++ /* copyup the new parent into the branch we process */ ++ err = au_wr_dir_cpup(dentry, parent, add_entry, bcpup, bstart); ++ if (err >= 0) { ++ if (!dentry->d_inode) { ++ au_set_h_dptr(dentry, bstart, NULL); ++ au_set_dbstart(dentry, bcpup); ++ au_set_dbend(dentry, bcpup); ++ } ++ AuDebugOn(add_entry ++ && !au_ftest_wrdir(args->flags, TMPFILE) ++ && !au_h_dptr(dentry, bcpup)); ++ } ++ ++out: ++ dput(parent); ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++void au_pin_hdir_unlock(struct au_pin *p) ++{ ++ if (p->hdir) ++ au_hn_imtx_unlock(p->hdir); ++} ++ ++int au_pin_hdir_lock(struct au_pin *p) ++{ ++ int err; ++ ++ err = 0; ++ if (!p->hdir) ++ goto out; ++ ++ /* even if an error happens later, keep this lock */ ++ au_hn_imtx_lock_nested(p->hdir, p->lsc_hi); ++ ++ err = -EBUSY; ++ if (unlikely(p->hdir->hi_inode != p->h_parent->d_inode)) ++ goto out; ++ ++ err = 0; ++ if (p->h_dentry) ++ err = au_h_verify(p->h_dentry, p->udba, p->hdir->hi_inode, ++ p->h_parent, p->br); ++ ++out: ++ return err; ++} ++ ++int au_pin_hdir_relock(struct au_pin *p) ++{ ++ int err, i; ++ struct inode *h_i; ++ struct dentry *h_d[] = { ++ p->h_dentry, ++ p->h_parent ++ }; ++ ++ err = au_pin_hdir_lock(p); ++ if (unlikely(err)) ++ goto out; ++ ++ for (i = 0; !err && i < sizeof(h_d)/sizeof(*h_d); i++) { ++ if (!h_d[i]) ++ continue; ++ h_i = h_d[i]->d_inode; ++ if (h_i) ++ err = !h_i->i_nlink; ++ } ++ ++out: ++ return err; ++} ++ ++void au_pin_hdir_set_owner(struct au_pin *p, struct task_struct *task) ++{ ++#if defined(CONFIG_DEBUG_MUTEXES) || defined(CONFIG_SMP) ++ p->hdir->hi_inode->i_mutex.owner = task; ++#endif ++} ++ ++void au_pin_hdir_acquire_nest(struct au_pin *p) ++{ ++ if (p->hdir) { ++ mutex_acquire_nest(&p->hdir->hi_inode->i_mutex.dep_map, ++ p->lsc_hi, 0, NULL, _RET_IP_); ++ au_pin_hdir_set_owner(p, current); ++ } ++} ++ ++void au_pin_hdir_release(struct au_pin *p) ++{ ++ if (p->hdir) { ++ au_pin_hdir_set_owner(p, p->task); ++ mutex_release(&p->hdir->hi_inode->i_mutex.dep_map, 1, _RET_IP_); ++ } ++} ++ ++struct dentry *au_pinned_h_parent(struct au_pin *pin) ++{ ++ if (pin && pin->parent) ++ return au_h_dptr(pin->parent, pin->bindex); ++ return NULL; ++} ++ ++void au_unpin(struct au_pin *p) ++{ ++ if (p->hdir) ++ au_pin_hdir_unlock(p); ++ if (p->h_mnt && au_ftest_pin(p->flags, MNT_WRITE)) ++ vfsub_mnt_drop_write(p->h_mnt); ++ if (!p->hdir) ++ return; ++ ++ if (!au_ftest_pin(p->flags, DI_LOCKED)) ++ di_read_unlock(p->parent, AuLock_IR); ++ iput(p->hdir->hi_inode); ++ dput(p->parent); ++ p->parent = NULL; ++ p->hdir = NULL; ++ p->h_mnt = NULL; ++ /* do not clear p->task */ ++} ++ ++int au_do_pin(struct au_pin *p) ++{ ++ int err; ++ struct super_block *sb; ++ struct inode *h_dir; ++ ++ err = 0; ++ sb = p->dentry->d_sb; ++ p->br = au_sbr(sb, p->bindex); ++ if (IS_ROOT(p->dentry)) { ++ if (au_ftest_pin(p->flags, MNT_WRITE)) { ++ p->h_mnt = au_br_mnt(p->br); ++ err = vfsub_mnt_want_write(p->h_mnt); ++ if (unlikely(err)) { ++ au_fclr_pin(p->flags, MNT_WRITE); ++ goto out_err; ++ } ++ } ++ goto out; ++ } ++ ++ p->h_dentry = NULL; ++ if (p->bindex <= au_dbend(p->dentry)) ++ p->h_dentry = au_h_dptr(p->dentry, p->bindex); ++ ++ p->parent = dget_parent(p->dentry); ++ if (!au_ftest_pin(p->flags, DI_LOCKED)) ++ di_read_lock(p->parent, AuLock_IR, p->lsc_di); ++ ++ h_dir = NULL; ++ p->h_parent = au_h_dptr(p->parent, p->bindex); ++ p->hdir = au_hi(p->parent->d_inode, p->bindex); ++ if (p->hdir) ++ h_dir = p->hdir->hi_inode; ++ ++ /* ++ * udba case, or ++ * if DI_LOCKED is not set, then p->parent may be different ++ * and h_parent can be NULL. ++ */ ++ if (unlikely(!p->hdir || !h_dir || !p->h_parent)) { ++ err = -EBUSY; ++ if (!au_ftest_pin(p->flags, DI_LOCKED)) ++ di_read_unlock(p->parent, AuLock_IR); ++ dput(p->parent); ++ p->parent = NULL; ++ goto out_err; ++ } ++ ++ if (au_ftest_pin(p->flags, MNT_WRITE)) { ++ p->h_mnt = au_br_mnt(p->br); ++ err = vfsub_mnt_want_write(p->h_mnt); ++ if (unlikely(err)) { ++ au_fclr_pin(p->flags, MNT_WRITE); ++ if (!au_ftest_pin(p->flags, DI_LOCKED)) ++ di_read_unlock(p->parent, AuLock_IR); ++ dput(p->parent); ++ p->parent = NULL; ++ goto out_err; ++ } ++ } ++ ++ au_igrab(h_dir); ++ err = au_pin_hdir_lock(p); ++ if (!err) ++ goto out; /* success */ ++ ++ au_unpin(p); ++ ++out_err: ++ pr_err("err %d\n", err); ++ err = au_busy_or_stale(); ++out: ++ return err; ++} ++ ++void au_pin_init(struct au_pin *p, struct dentry *dentry, ++ aufs_bindex_t bindex, int lsc_di, int lsc_hi, ++ unsigned int udba, unsigned char flags) ++{ ++ p->dentry = dentry; ++ p->udba = udba; ++ p->lsc_di = lsc_di; ++ p->lsc_hi = lsc_hi; ++ p->flags = flags; ++ p->bindex = bindex; ++ ++ p->parent = NULL; ++ p->hdir = NULL; ++ p->h_mnt = NULL; ++ ++ p->h_dentry = NULL; ++ p->h_parent = NULL; ++ p->br = NULL; ++ p->task = current; ++} ++ ++int au_pin(struct au_pin *pin, struct dentry *dentry, aufs_bindex_t bindex, ++ unsigned int udba, unsigned char flags) ++{ ++ au_pin_init(pin, dentry, bindex, AuLsc_DI_PARENT, AuLsc_I_PARENT2, ++ udba, flags); ++ return au_do_pin(pin); ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* ++ * ->setattr() and ->getattr() are called in various cases. ++ * chmod, stat: dentry is revalidated. ++ * fchmod, fstat: file and dentry are not revalidated, additionally they may be ++ * unhashed. ++ * for ->setattr(), ia->ia_file is passed from ftruncate only. ++ */ ++/* todo: consolidate with do_refresh() and simple_reval_dpath() */ ++int au_reval_for_attr(struct dentry *dentry, unsigned int sigen) ++{ ++ int err; ++ struct inode *inode; ++ struct dentry *parent; ++ ++ err = 0; ++ inode = dentry->d_inode; ++ if (au_digen_test(dentry, sigen)) { ++ parent = dget_parent(dentry); ++ di_read_lock_parent(parent, AuLock_IR); ++ err = au_refresh_dentry(dentry, parent); ++ di_read_unlock(parent, AuLock_IR); ++ dput(parent); ++ } ++ ++ AuTraceErr(err); ++ return err; ++} ++ ++int au_pin_and_icpup(struct dentry *dentry, struct iattr *ia, ++ struct au_icpup_args *a) ++{ ++ int err; ++ loff_t sz; ++ aufs_bindex_t bstart, ibstart; ++ struct dentry *hi_wh, *parent; ++ struct inode *inode; ++ struct au_wr_dir_args wr_dir_args = { ++ .force_btgt = -1, ++ .flags = 0 ++ }; ++ ++ if (d_is_dir(dentry)) ++ au_fset_wrdir(wr_dir_args.flags, ISDIR); ++ /* plink or hi_wh() case */ ++ bstart = au_dbstart(dentry); ++ inode = dentry->d_inode; ++ ibstart = au_ibstart(inode); ++ if (bstart != ibstart && !au_test_ro(inode->i_sb, ibstart, inode)) ++ wr_dir_args.force_btgt = ibstart; ++ err = au_wr_dir(dentry, /*src_dentry*/NULL, &wr_dir_args); ++ if (unlikely(err < 0)) ++ goto out; ++ a->btgt = err; ++ if (err != bstart) ++ au_fset_icpup(a->flags, DID_CPUP); ++ ++ err = 0; ++ a->pin_flags = AuPin_MNT_WRITE; ++ parent = NULL; ++ if (!IS_ROOT(dentry)) { ++ au_fset_pin(a->pin_flags, DI_LOCKED); ++ parent = dget_parent(dentry); ++ di_write_lock_parent(parent); ++ } ++ ++ err = au_pin(&a->pin, dentry, a->btgt, a->udba, a->pin_flags); ++ if (unlikely(err)) ++ goto out_parent; ++ ++ a->h_path.dentry = au_h_dptr(dentry, bstart); ++ a->h_inode = a->h_path.dentry->d_inode; ++ sz = -1; ++ if (ia && (ia->ia_valid & ATTR_SIZE)) { ++ mutex_lock_nested(&a->h_inode->i_mutex, AuLsc_I_CHILD); ++ if (ia->ia_size < i_size_read(a->h_inode)) ++ sz = ia->ia_size; ++ mutex_unlock(&a->h_inode->i_mutex); ++ } ++ ++ hi_wh = NULL; ++ if (au_ftest_icpup(a->flags, DID_CPUP) && d_unlinked(dentry)) { ++ hi_wh = au_hi_wh(inode, a->btgt); ++ if (!hi_wh) { ++ struct au_cp_generic cpg = { ++ .dentry = dentry, ++ .bdst = a->btgt, ++ .bsrc = -1, ++ .len = sz, ++ .pin = &a->pin ++ }; ++ err = au_sio_cpup_wh(&cpg, /*file*/NULL); ++ if (unlikely(err)) ++ goto out_unlock; ++ hi_wh = au_hi_wh(inode, a->btgt); ++ /* todo: revalidate hi_wh? */ ++ } ++ } ++ ++ if (parent) { ++ au_pin_set_parent_lflag(&a->pin, /*lflag*/0); ++ di_downgrade_lock(parent, AuLock_IR); ++ dput(parent); ++ parent = NULL; ++ } ++ if (!au_ftest_icpup(a->flags, DID_CPUP)) ++ goto out; /* success */ ++ ++ if (!d_unhashed(dentry)) { ++ struct au_cp_generic cpg = { ++ .dentry = dentry, ++ .bdst = a->btgt, ++ .bsrc = bstart, ++ .len = sz, ++ .pin = &a->pin, ++ .flags = AuCpup_DTIME | AuCpup_HOPEN ++ }; ++ err = au_sio_cpup_simple(&cpg); ++ if (!err) ++ a->h_path.dentry = au_h_dptr(dentry, a->btgt); ++ } else if (!hi_wh) ++ a->h_path.dentry = au_h_dptr(dentry, a->btgt); ++ else ++ a->h_path.dentry = hi_wh; /* do not dget here */ ++ ++out_unlock: ++ a->h_inode = a->h_path.dentry->d_inode; ++ if (!err) ++ goto out; /* success */ ++ au_unpin(&a->pin); ++out_parent: ++ if (parent) { ++ di_write_unlock(parent); ++ dput(parent); ++ } ++out: ++ if (!err) ++ mutex_lock_nested(&a->h_inode->i_mutex, AuLsc_I_CHILD); ++ return err; ++} ++ ++static int aufs_setattr(struct dentry *dentry, struct iattr *ia) ++{ ++ int err; ++ struct inode *inode, *delegated; ++ struct super_block *sb; ++ struct file *file; ++ struct au_icpup_args *a; ++ ++ inode = dentry->d_inode; ++ IMustLock(inode); ++ ++ err = -ENOMEM; ++ a = kzalloc(sizeof(*a), GFP_NOFS); ++ if (unlikely(!a)) ++ goto out; ++ ++ if (ia->ia_valid & (ATTR_KILL_SUID | ATTR_KILL_SGID)) ++ ia->ia_valid &= ~ATTR_MODE; ++ ++ file = NULL; ++ sb = dentry->d_sb; ++ err = si_read_lock(sb, AuLock_FLUSH | AuLock_NOPLM); ++ if (unlikely(err)) ++ goto out_kfree; ++ ++ if (ia->ia_valid & ATTR_FILE) { ++ /* currently ftruncate(2) only */ ++ AuDebugOn(!S_ISREG(inode->i_mode)); ++ file = ia->ia_file; ++ err = au_reval_and_lock_fdi(file, au_reopen_nondir, /*wlock*/1); ++ if (unlikely(err)) ++ goto out_si; ++ ia->ia_file = au_hf_top(file); ++ a->udba = AuOpt_UDBA_NONE; ++ } else { ++ /* fchmod() doesn't pass ia_file */ ++ a->udba = au_opt_udba(sb); ++ di_write_lock_child(dentry); ++ /* no d_unlinked(), to set UDBA_NONE for root */ ++ if (d_unhashed(dentry)) ++ a->udba = AuOpt_UDBA_NONE; ++ if (a->udba != AuOpt_UDBA_NONE) { ++ AuDebugOn(IS_ROOT(dentry)); ++ err = au_reval_for_attr(dentry, au_sigen(sb)); ++ if (unlikely(err)) ++ goto out_dentry; ++ } ++ } ++ ++ err = au_pin_and_icpup(dentry, ia, a); ++ if (unlikely(err < 0)) ++ goto out_dentry; ++ if (au_ftest_icpup(a->flags, DID_CPUP)) { ++ ia->ia_file = NULL; ++ ia->ia_valid &= ~ATTR_FILE; ++ } ++ ++ a->h_path.mnt = au_sbr_mnt(sb, a->btgt); ++ if ((ia->ia_valid & (ATTR_MODE | ATTR_CTIME)) ++ == (ATTR_MODE | ATTR_CTIME)) { ++ err = security_path_chmod(&a->h_path, ia->ia_mode); ++ if (unlikely(err)) ++ goto out_unlock; ++ } else if ((ia->ia_valid & (ATTR_UID | ATTR_GID)) ++ && (ia->ia_valid & ATTR_CTIME)) { ++ err = security_path_chown(&a->h_path, ia->ia_uid, ia->ia_gid); ++ if (unlikely(err)) ++ goto out_unlock; ++ } ++ ++ if (ia->ia_valid & ATTR_SIZE) { ++ struct file *f; ++ ++ if (ia->ia_size < i_size_read(inode)) ++ /* unmap only */ ++ truncate_setsize(inode, ia->ia_size); ++ ++ f = NULL; ++ if (ia->ia_valid & ATTR_FILE) ++ f = ia->ia_file; ++ mutex_unlock(&a->h_inode->i_mutex); ++ err = vfsub_trunc(&a->h_path, ia->ia_size, ia->ia_valid, f); ++ mutex_lock_nested(&a->h_inode->i_mutex, AuLsc_I_CHILD); ++ } else { ++ delegated = NULL; ++ while (1) { ++ err = vfsub_notify_change(&a->h_path, ia, &delegated); ++ if (delegated) { ++ err = break_deleg_wait(&delegated); ++ if (!err) ++ continue; ++ } ++ break; ++ } ++ } ++ /* ++ * regardless aufs 'acl' option setting. ++ * why don't all acl-aware fs call this func from their ->setattr()? ++ */ ++ if (!err && (ia->ia_valid & ATTR_MODE)) ++ err = vfsub_acl_chmod(a->h_inode, ia->ia_mode); ++ if (!err) ++ au_cpup_attr_changeable(inode); ++ ++out_unlock: ++ mutex_unlock(&a->h_inode->i_mutex); ++ au_unpin(&a->pin); ++ if (unlikely(err)) ++ au_update_dbstart(dentry); ++out_dentry: ++ di_write_unlock(dentry); ++ if (file) { ++ fi_write_unlock(file); ++ ia->ia_file = file; ++ ia->ia_valid |= ATTR_FILE; ++ } ++out_si: ++ si_read_unlock(sb); ++out_kfree: ++ kfree(a); ++out: ++ AuTraceErr(err); ++ return err; ++} ++ ++#if IS_ENABLED(CONFIG_AUFS_XATTR) || IS_ENABLED(CONFIG_FS_POSIX_ACL) ++static int au_h_path_to_set_attr(struct dentry *dentry, ++ struct au_icpup_args *a, struct path *h_path) ++{ ++ int err; ++ struct super_block *sb; ++ ++ sb = dentry->d_sb; ++ a->udba = au_opt_udba(sb); ++ /* no d_unlinked(), to set UDBA_NONE for root */ ++ if (d_unhashed(dentry)) ++ a->udba = AuOpt_UDBA_NONE; ++ if (a->udba != AuOpt_UDBA_NONE) { ++ AuDebugOn(IS_ROOT(dentry)); ++ err = au_reval_for_attr(dentry, au_sigen(sb)); ++ if (unlikely(err)) ++ goto out; ++ } ++ err = au_pin_and_icpup(dentry, /*ia*/NULL, a); ++ if (unlikely(err < 0)) ++ goto out; ++ ++ h_path->dentry = a->h_path.dentry; ++ h_path->mnt = au_sbr_mnt(sb, a->btgt); ++ ++out: ++ return err; ++} ++ ++ssize_t au_srxattr(struct dentry *dentry, struct au_srxattr *arg) ++{ ++ int err; ++ struct path h_path; ++ struct super_block *sb; ++ struct au_icpup_args *a; ++ struct inode *inode, *h_inode; ++ ++ inode = dentry->d_inode; ++ IMustLock(inode); ++ ++ err = -ENOMEM; ++ a = kzalloc(sizeof(*a), GFP_NOFS); ++ if (unlikely(!a)) ++ goto out; ++ ++ sb = dentry->d_sb; ++ err = si_read_lock(sb, AuLock_FLUSH | AuLock_NOPLM); ++ if (unlikely(err)) ++ goto out_kfree; ++ ++ h_path.dentry = NULL; /* silence gcc */ ++ di_write_lock_child(dentry); ++ err = au_h_path_to_set_attr(dentry, a, &h_path); ++ if (unlikely(err)) ++ goto out_di; ++ ++ mutex_unlock(&a->h_inode->i_mutex); ++ switch (arg->type) { ++ case AU_XATTR_SET: ++ err = vfsub_setxattr(h_path.dentry, ++ arg->u.set.name, arg->u.set.value, ++ arg->u.set.size, arg->u.set.flags); ++ break; ++ case AU_XATTR_REMOVE: ++ err = vfsub_removexattr(h_path.dentry, arg->u.remove.name); ++ break; ++ case AU_ACL_SET: ++ err = -EOPNOTSUPP; ++ h_inode = h_path.dentry->d_inode; ++ if (h_inode->i_op->set_acl) ++ err = h_inode->i_op->set_acl(h_inode, ++ arg->u.acl_set.acl, ++ arg->u.acl_set.type); ++ break; ++ } ++ if (!err) ++ au_cpup_attr_timesizes(inode); ++ ++ au_unpin(&a->pin); ++ if (unlikely(err)) ++ au_update_dbstart(dentry); ++ ++out_di: ++ di_write_unlock(dentry); ++ si_read_unlock(sb); ++out_kfree: ++ kfree(a); ++out: ++ AuTraceErr(err); ++ return err; ++} ++#endif ++ ++static void au_refresh_iattr(struct inode *inode, struct kstat *st, ++ unsigned int nlink) ++{ ++ unsigned int n; ++ ++ inode->i_mode = st->mode; ++ /* don't i_[ug]id_write() here */ ++ inode->i_uid = st->uid; ++ inode->i_gid = st->gid; ++ inode->i_atime = st->atime; ++ inode->i_mtime = st->mtime; ++ inode->i_ctime = st->ctime; ++ ++ au_cpup_attr_nlink(inode, /*force*/0); ++ if (S_ISDIR(inode->i_mode)) { ++ n = inode->i_nlink; ++ n -= nlink; ++ n += st->nlink; ++ smp_mb(); /* for i_nlink */ ++ /* 0 can happen */ ++ set_nlink(inode, n); ++ } ++ ++ spin_lock(&inode->i_lock); ++ inode->i_blocks = st->blocks; ++ i_size_write(inode, st->size); ++ spin_unlock(&inode->i_lock); ++} ++ ++/* ++ * common routine for aufs_getattr() and aufs_getxattr(). ++ * returns zero or negative (an error). ++ * @dentry will be read-locked in success. ++ */ ++int au_h_path_getattr(struct dentry *dentry, int force, struct path *h_path) ++{ ++ int err; ++ unsigned int mnt_flags, sigen; ++ unsigned char udba_none; ++ aufs_bindex_t bindex; ++ struct super_block *sb, *h_sb; ++ struct inode *inode; ++ ++ h_path->mnt = NULL; ++ h_path->dentry = NULL; ++ ++ err = 0; ++ sb = dentry->d_sb; ++ mnt_flags = au_mntflags(sb); ++ udba_none = !!au_opt_test(mnt_flags, UDBA_NONE); ++ ++ /* support fstat(2) */ ++ if (!d_unlinked(dentry) && !udba_none) { ++ sigen = au_sigen(sb); ++ err = au_digen_test(dentry, sigen); ++ if (!err) { ++ di_read_lock_child(dentry, AuLock_IR); ++ err = au_dbrange_test(dentry); ++ if (unlikely(err)) { ++ di_read_unlock(dentry, AuLock_IR); ++ goto out; ++ } ++ } else { ++ AuDebugOn(IS_ROOT(dentry)); ++ di_write_lock_child(dentry); ++ err = au_dbrange_test(dentry); ++ if (!err) ++ err = au_reval_for_attr(dentry, sigen); ++ if (!err) ++ di_downgrade_lock(dentry, AuLock_IR); ++ else { ++ di_write_unlock(dentry); ++ goto out; ++ } ++ } ++ } else ++ di_read_lock_child(dentry, AuLock_IR); ++ ++ inode = dentry->d_inode; ++ bindex = au_ibstart(inode); ++ h_path->mnt = au_sbr_mnt(sb, bindex); ++ h_sb = h_path->mnt->mnt_sb; ++ if (!force ++ && !au_test_fs_bad_iattr(h_sb) ++ && udba_none) ++ goto out; /* success */ ++ ++ if (au_dbstart(dentry) == bindex) ++ h_path->dentry = au_h_dptr(dentry, bindex); ++ else if (au_opt_test(mnt_flags, PLINK) && au_plink_test(inode)) { ++ h_path->dentry = au_plink_lkup(inode, bindex); ++ if (IS_ERR(h_path->dentry)) ++ /* pretending success */ ++ h_path->dentry = NULL; ++ else ++ dput(h_path->dentry); ++ } ++ ++out: ++ return err; ++} ++ ++static int aufs_getattr(struct vfsmount *mnt __maybe_unused, ++ struct dentry *dentry, struct kstat *st) ++{ ++ int err; ++ unsigned char positive; ++ struct path h_path; ++ struct inode *inode; ++ struct super_block *sb; ++ ++ inode = dentry->d_inode; ++ sb = dentry->d_sb; ++ err = si_read_lock(sb, AuLock_FLUSH | AuLock_NOPLM); ++ if (unlikely(err)) ++ goto out; ++ err = au_h_path_getattr(dentry, /*force*/0, &h_path); ++ if (unlikely(err)) ++ goto out_si; ++ if (unlikely(!h_path.dentry)) ++ /* illegally overlapped or something */ ++ goto out_fill; /* pretending success */ ++ ++ positive = !!h_path.dentry->d_inode; ++ if (positive) ++ err = vfs_getattr(&h_path, st); ++ if (!err) { ++ if (positive) ++ au_refresh_iattr(inode, st, ++ h_path.dentry->d_inode->i_nlink); ++ goto out_fill; /* success */ ++ } ++ AuTraceErr(err); ++ goto out_di; ++ ++out_fill: ++ generic_fillattr(inode, st); ++out_di: ++ di_read_unlock(dentry, AuLock_IR); ++out_si: ++ si_read_unlock(sb); ++out: ++ AuTraceErr(err); ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++static int h_readlink(struct dentry *dentry, int bindex, char __user *buf, ++ int bufsiz) ++{ ++ int err; ++ struct super_block *sb; ++ struct dentry *h_dentry; ++ ++ err = -EINVAL; ++ h_dentry = au_h_dptr(dentry, bindex); ++ if (unlikely(!h_dentry->d_inode->i_op->readlink)) ++ goto out; ++ ++ err = security_inode_readlink(h_dentry); ++ if (unlikely(err)) ++ goto out; ++ ++ sb = dentry->d_sb; ++ if (!au_test_ro(sb, bindex, dentry->d_inode)) { ++ vfsub_touch_atime(au_sbr_mnt(sb, bindex), h_dentry); ++ fsstack_copy_attr_atime(dentry->d_inode, h_dentry->d_inode); ++ } ++ err = h_dentry->d_inode->i_op->readlink(h_dentry, buf, bufsiz); ++ ++out: ++ return err; ++} ++ ++static int aufs_readlink(struct dentry *dentry, char __user *buf, int bufsiz) ++{ ++ int err; ++ ++ err = aufs_read_lock(dentry, AuLock_IR | AuLock_GEN); ++ if (unlikely(err)) ++ goto out; ++ err = au_d_hashed_positive(dentry); ++ if (!err) ++ err = h_readlink(dentry, au_dbstart(dentry), buf, bufsiz); ++ aufs_read_unlock(dentry, AuLock_IR); ++ ++out: ++ return err; ++} ++ ++static void *aufs_follow_link(struct dentry *dentry, struct nameidata *nd) ++{ ++ int err; ++ mm_segment_t old_fs; ++ union { ++ char *k; ++ char __user *u; ++ } buf; ++ ++ err = -ENOMEM; ++ buf.k = (void *)__get_free_page(GFP_NOFS); ++ if (unlikely(!buf.k)) ++ goto out; ++ ++ err = aufs_read_lock(dentry, AuLock_IR | AuLock_GEN); ++ if (unlikely(err)) ++ goto out_name; ++ ++ err = au_d_hashed_positive(dentry); ++ if (!err) { ++ old_fs = get_fs(); ++ set_fs(KERNEL_DS); ++ err = h_readlink(dentry, au_dbstart(dentry), buf.u, PATH_MAX); ++ set_fs(old_fs); ++ } ++ aufs_read_unlock(dentry, AuLock_IR); ++ ++ if (err >= 0) { ++ buf.k[err] = 0; ++ /* will be freed by put_link */ ++ nd_set_link(nd, buf.k); ++ return NULL; /* success */ ++ } ++ ++out_name: ++ free_page((unsigned long)buf.k); ++out: ++ AuTraceErr(err); ++ return ERR_PTR(err); ++} ++ ++static void aufs_put_link(struct dentry *dentry __maybe_unused, ++ struct nameidata *nd, void *cookie __maybe_unused) ++{ ++ char *p; ++ ++ p = nd_get_link(nd); ++ if (!IS_ERR_OR_NULL(p)) ++ free_page((unsigned long)p); ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++static int aufs_update_time(struct inode *inode, struct timespec *ts, int flags) ++{ ++ int err; ++ struct super_block *sb; ++ struct inode *h_inode; ++ ++ sb = inode->i_sb; ++ /* mmap_sem might be acquired already, cf. aufs_mmap() */ ++ lockdep_off(); ++ si_read_lock(sb, AuLock_FLUSH); ++ ii_write_lock_child(inode); ++ lockdep_on(); ++ h_inode = au_h_iptr(inode, au_ibstart(inode)); ++ err = vfsub_update_time(h_inode, ts, flags); ++ lockdep_off(); ++ if (!err) ++ au_cpup_attr_timesizes(inode); ++ ii_write_unlock(inode); ++ si_read_unlock(sb); ++ lockdep_on(); ++ ++ if (!err && (flags & S_VERSION)) ++ inode_inc_iversion(inode); ++ ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* no getattr version will be set by module.c:aufs_init() */ ++struct inode_operations aufs_iop_nogetattr[AuIop_Last], ++ aufs_iop[] = { ++ [AuIop_SYMLINK] = { ++ .permission = aufs_permission, ++#ifdef CONFIG_FS_POSIX_ACL ++ .get_acl = aufs_get_acl, ++ .set_acl = aufs_set_acl, /* unsupport for symlink? */ ++#endif ++ ++ .setattr = aufs_setattr, ++ .getattr = aufs_getattr, ++ ++#ifdef CONFIG_AUFS_XATTR ++ .setxattr = aufs_setxattr, ++ .getxattr = aufs_getxattr, ++ .listxattr = aufs_listxattr, ++ .removexattr = aufs_removexattr, ++#endif ++ ++ .readlink = aufs_readlink, ++ .follow_link = aufs_follow_link, ++ .put_link = aufs_put_link, ++ ++ /* .update_time = aufs_update_time */ ++ }, ++ [AuIop_DIR] = { ++ .create = aufs_create, ++ .lookup = aufs_lookup, ++ .link = aufs_link, ++ .unlink = aufs_unlink, ++ .symlink = aufs_symlink, ++ .mkdir = aufs_mkdir, ++ .rmdir = aufs_rmdir, ++ .mknod = aufs_mknod, ++ .rename = aufs_rename, ++ ++ .permission = aufs_permission, ++#ifdef CONFIG_FS_POSIX_ACL ++ .get_acl = aufs_get_acl, ++ .set_acl = aufs_set_acl, ++#endif ++ ++ .setattr = aufs_setattr, ++ .getattr = aufs_getattr, ++ ++#ifdef CONFIG_AUFS_XATTR ++ .setxattr = aufs_setxattr, ++ .getxattr = aufs_getxattr, ++ .listxattr = aufs_listxattr, ++ .removexattr = aufs_removexattr, ++#endif ++ ++ .update_time = aufs_update_time, ++ .atomic_open = aufs_atomic_open, ++ .tmpfile = aufs_tmpfile ++ }, ++ [AuIop_OTHER] = { ++ .permission = aufs_permission, ++#ifdef CONFIG_FS_POSIX_ACL ++ .get_acl = aufs_get_acl, ++ .set_acl = aufs_set_acl, ++#endif ++ ++ .setattr = aufs_setattr, ++ .getattr = aufs_getattr, ++ ++#ifdef CONFIG_AUFS_XATTR ++ .setxattr = aufs_setxattr, ++ .getxattr = aufs_getxattr, ++ .listxattr = aufs_listxattr, ++ .removexattr = aufs_removexattr, ++#endif ++ ++ .update_time = aufs_update_time ++ } ++}; +diff --git a/fs/aufs/i_op_add.c b/fs/aufs/i_op_add.c +new file mode 100644 +index 0000000..9e4f65c +--- /dev/null ++++ b/fs/aufs/i_op_add.c +@@ -0,0 +1,930 @@ ++/* ++ * Copyright (C) 2005-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * inode operations (add entry) ++ */ ++ ++#include "aufs.h" ++ ++/* ++ * final procedure of adding a new entry, except link(2). ++ * remove whiteout, instantiate, copyup the parent dir's times and size ++ * and update version. ++ * if it failed, re-create the removed whiteout. ++ */ ++static int epilog(struct inode *dir, aufs_bindex_t bindex, ++ struct dentry *wh_dentry, struct dentry *dentry) ++{ ++ int err, rerr; ++ aufs_bindex_t bwh; ++ struct path h_path; ++ struct super_block *sb; ++ struct inode *inode, *h_dir; ++ struct dentry *wh; ++ ++ bwh = -1; ++ sb = dir->i_sb; ++ if (wh_dentry) { ++ h_dir = wh_dentry->d_parent->d_inode; /* dir inode is locked */ ++ IMustLock(h_dir); ++ AuDebugOn(au_h_iptr(dir, bindex) != h_dir); ++ bwh = au_dbwh(dentry); ++ h_path.dentry = wh_dentry; ++ h_path.mnt = au_sbr_mnt(sb, bindex); ++ err = au_wh_unlink_dentry(au_h_iptr(dir, bindex), &h_path, ++ dentry); ++ if (unlikely(err)) ++ goto out; ++ } ++ ++ inode = au_new_inode(dentry, /*must_new*/1); ++ if (!IS_ERR(inode)) { ++ d_instantiate(dentry, inode); ++ dir = dentry->d_parent->d_inode; /* dir inode is locked */ ++ IMustLock(dir); ++ au_dir_ts(dir, bindex); ++ dir->i_version++; ++ au_fhsm_wrote(sb, bindex, /*force*/0); ++ return 0; /* success */ ++ } ++ ++ err = PTR_ERR(inode); ++ if (!wh_dentry) ++ goto out; ++ ++ /* revert */ ++ /* dir inode is locked */ ++ wh = au_wh_create(dentry, bwh, wh_dentry->d_parent); ++ rerr = PTR_ERR(wh); ++ if (IS_ERR(wh)) { ++ AuIOErr("%pd reverting whiteout failed(%d, %d)\n", ++ dentry, err, rerr); ++ err = -EIO; ++ } else ++ dput(wh); ++ ++out: ++ return err; ++} ++ ++static int au_d_may_add(struct dentry *dentry) ++{ ++ int err; ++ ++ err = 0; ++ if (unlikely(d_unhashed(dentry))) ++ err = -ENOENT; ++ if (unlikely(dentry->d_inode)) ++ err = -EEXIST; ++ return err; ++} ++ ++/* ++ * simple tests for the adding inode operations. ++ * following the checks in vfs, plus the parent-child relationship. ++ */ ++int au_may_add(struct dentry *dentry, aufs_bindex_t bindex, ++ struct dentry *h_parent, int isdir) ++{ ++ int err; ++ umode_t h_mode; ++ struct dentry *h_dentry; ++ struct inode *h_inode; ++ ++ err = -ENAMETOOLONG; ++ if (unlikely(dentry->d_name.len > AUFS_MAX_NAMELEN)) ++ goto out; ++ ++ h_dentry = au_h_dptr(dentry, bindex); ++ h_inode = h_dentry->d_inode; ++ if (!dentry->d_inode) { ++ err = -EEXIST; ++ if (unlikely(h_inode)) ++ goto out; ++ } else { ++ /* rename(2) case */ ++ err = -EIO; ++ if (unlikely(!h_inode || !h_inode->i_nlink)) ++ goto out; ++ ++ h_mode = h_inode->i_mode; ++ if (!isdir) { ++ err = -EISDIR; ++ if (unlikely(S_ISDIR(h_mode))) ++ goto out; ++ } else if (unlikely(!S_ISDIR(h_mode))) { ++ err = -ENOTDIR; ++ goto out; ++ } ++ } ++ ++ err = 0; ++ /* expected parent dir is locked */ ++ if (unlikely(h_parent != h_dentry->d_parent)) ++ err = -EIO; ++ ++out: ++ AuTraceErr(err); ++ return err; ++} ++ ++/* ++ * initial procedure of adding a new entry. ++ * prepare writable branch and the parent dir, lock it, ++ * and lookup whiteout for the new entry. ++ */ ++static struct dentry* ++lock_hdir_lkup_wh(struct dentry *dentry, struct au_dtime *dt, ++ struct dentry *src_dentry, struct au_pin *pin, ++ struct au_wr_dir_args *wr_dir_args) ++{ ++ struct dentry *wh_dentry, *h_parent; ++ struct super_block *sb; ++ struct au_branch *br; ++ int err; ++ unsigned int udba; ++ aufs_bindex_t bcpup; ++ ++ AuDbg("%pd\n", dentry); ++ ++ err = au_wr_dir(dentry, src_dentry, wr_dir_args); ++ bcpup = err; ++ wh_dentry = ERR_PTR(err); ++ if (unlikely(err < 0)) ++ goto out; ++ ++ sb = dentry->d_sb; ++ udba = au_opt_udba(sb); ++ err = au_pin(pin, dentry, bcpup, udba, ++ AuPin_DI_LOCKED | AuPin_MNT_WRITE); ++ wh_dentry = ERR_PTR(err); ++ if (unlikely(err)) ++ goto out; ++ ++ h_parent = au_pinned_h_parent(pin); ++ if (udba != AuOpt_UDBA_NONE ++ && au_dbstart(dentry) == bcpup) ++ err = au_may_add(dentry, bcpup, h_parent, ++ au_ftest_wrdir(wr_dir_args->flags, ISDIR)); ++ else if (unlikely(dentry->d_name.len > AUFS_MAX_NAMELEN)) ++ err = -ENAMETOOLONG; ++ wh_dentry = ERR_PTR(err); ++ if (unlikely(err)) ++ goto out_unpin; ++ ++ br = au_sbr(sb, bcpup); ++ if (dt) { ++ struct path tmp = { ++ .dentry = h_parent, ++ .mnt = au_br_mnt(br) ++ }; ++ au_dtime_store(dt, au_pinned_parent(pin), &tmp); ++ } ++ ++ wh_dentry = NULL; ++ if (bcpup != au_dbwh(dentry)) ++ goto out; /* success */ ++ ++ /* ++ * ENAMETOOLONG here means that if we allowed create such name, then it ++ * would not be able to removed in the future. So we don't allow such ++ * name here and we don't handle ENAMETOOLONG differently here. ++ */ ++ wh_dentry = au_wh_lkup(h_parent, &dentry->d_name, br); ++ ++out_unpin: ++ if (IS_ERR(wh_dentry)) ++ au_unpin(pin); ++out: ++ return wh_dentry; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++enum { Mknod, Symlink, Creat }; ++struct simple_arg { ++ int type; ++ union { ++ struct { ++ umode_t mode; ++ bool want_excl; ++ bool try_aopen; ++ struct vfsub_aopen_args *aopen; ++ } c; ++ struct { ++ const char *symname; ++ } s; ++ struct { ++ umode_t mode; ++ dev_t dev; ++ } m; ++ } u; ++}; ++ ++static int add_simple(struct inode *dir, struct dentry *dentry, ++ struct simple_arg *arg) ++{ ++ int err, rerr; ++ aufs_bindex_t bstart; ++ unsigned char created; ++ const unsigned char try_aopen ++ = (arg->type == Creat && arg->u.c.try_aopen); ++ struct dentry *wh_dentry, *parent; ++ struct inode *h_dir; ++ struct super_block *sb; ++ struct au_branch *br; ++ /* to reuduce stack size */ ++ struct { ++ struct au_dtime dt; ++ struct au_pin pin; ++ struct path h_path; ++ struct au_wr_dir_args wr_dir_args; ++ } *a; ++ ++ AuDbg("%pd\n", dentry); ++ IMustLock(dir); ++ ++ err = -ENOMEM; ++ a = kmalloc(sizeof(*a), GFP_NOFS); ++ if (unlikely(!a)) ++ goto out; ++ a->wr_dir_args.force_btgt = -1; ++ a->wr_dir_args.flags = AuWrDir_ADD_ENTRY; ++ ++ parent = dentry->d_parent; /* dir inode is locked */ ++ if (!try_aopen) { ++ err = aufs_read_lock(dentry, AuLock_DW | AuLock_GEN); ++ if (unlikely(err)) ++ goto out_free; ++ } ++ err = au_d_may_add(dentry); ++ if (unlikely(err)) ++ goto out_unlock; ++ if (!try_aopen) ++ di_write_lock_parent(parent); ++ wh_dentry = lock_hdir_lkup_wh(dentry, &a->dt, /*src_dentry*/NULL, ++ &a->pin, &a->wr_dir_args); ++ err = PTR_ERR(wh_dentry); ++ if (IS_ERR(wh_dentry)) ++ goto out_parent; ++ ++ bstart = au_dbstart(dentry); ++ sb = dentry->d_sb; ++ br = au_sbr(sb, bstart); ++ a->h_path.dentry = au_h_dptr(dentry, bstart); ++ a->h_path.mnt = au_br_mnt(br); ++ h_dir = au_pinned_h_dir(&a->pin); ++ switch (arg->type) { ++ case Creat: ++ err = 0; ++ if (!try_aopen || !h_dir->i_op->atomic_open) ++ err = vfsub_create(h_dir, &a->h_path, arg->u.c.mode, ++ arg->u.c.want_excl); ++ else ++ err = vfsub_atomic_open(h_dir, a->h_path.dentry, ++ arg->u.c.aopen, br); ++ break; ++ case Symlink: ++ err = vfsub_symlink(h_dir, &a->h_path, arg->u.s.symname); ++ break; ++ case Mknod: ++ err = vfsub_mknod(h_dir, &a->h_path, arg->u.m.mode, ++ arg->u.m.dev); ++ break; ++ default: ++ BUG(); ++ } ++ created = !err; ++ if (!err) ++ err = epilog(dir, bstart, wh_dentry, dentry); ++ ++ /* revert */ ++ if (unlikely(created && err && a->h_path.dentry->d_inode)) { ++ /* no delegation since it is just created */ ++ rerr = vfsub_unlink(h_dir, &a->h_path, /*delegated*/NULL, ++ /*force*/0); ++ if (rerr) { ++ AuIOErr("%pd revert failure(%d, %d)\n", ++ dentry, err, rerr); ++ err = -EIO; ++ } ++ au_dtime_revert(&a->dt); ++ } ++ ++ if (!err && try_aopen && !h_dir->i_op->atomic_open) ++ *arg->u.c.aopen->opened |= FILE_CREATED; ++ ++ au_unpin(&a->pin); ++ dput(wh_dentry); ++ ++out_parent: ++ if (!try_aopen) ++ di_write_unlock(parent); ++out_unlock: ++ if (unlikely(err)) { ++ au_update_dbstart(dentry); ++ d_drop(dentry); ++ } ++ if (!try_aopen) ++ aufs_read_unlock(dentry, AuLock_DW); ++out_free: ++ kfree(a); ++out: ++ return err; ++} ++ ++int aufs_mknod(struct inode *dir, struct dentry *dentry, umode_t mode, ++ dev_t dev) ++{ ++ struct simple_arg arg = { ++ .type = Mknod, ++ .u.m = { ++ .mode = mode, ++ .dev = dev ++ } ++ }; ++ return add_simple(dir, dentry, &arg); ++} ++ ++int aufs_symlink(struct inode *dir, struct dentry *dentry, const char *symname) ++{ ++ struct simple_arg arg = { ++ .type = Symlink, ++ .u.s.symname = symname ++ }; ++ return add_simple(dir, dentry, &arg); ++} ++ ++int aufs_create(struct inode *dir, struct dentry *dentry, umode_t mode, ++ bool want_excl) ++{ ++ struct simple_arg arg = { ++ .type = Creat, ++ .u.c = { ++ .mode = mode, ++ .want_excl = want_excl ++ } ++ }; ++ return add_simple(dir, dentry, &arg); ++} ++ ++int au_aopen_or_create(struct inode *dir, struct dentry *dentry, ++ struct vfsub_aopen_args *aopen_args) ++{ ++ struct simple_arg arg = { ++ .type = Creat, ++ .u.c = { ++ .mode = aopen_args->create_mode, ++ .want_excl = aopen_args->open_flag & O_EXCL, ++ .try_aopen = true, ++ .aopen = aopen_args ++ } ++ }; ++ return add_simple(dir, dentry, &arg); ++} ++ ++int aufs_tmpfile(struct inode *dir, struct dentry *dentry, umode_t mode) ++{ ++ int err; ++ aufs_bindex_t bindex; ++ struct super_block *sb; ++ struct dentry *parent, *h_parent, *h_dentry; ++ struct inode *h_dir, *inode; ++ struct vfsmount *h_mnt; ++ struct au_wr_dir_args wr_dir_args = { ++ .force_btgt = -1, ++ .flags = AuWrDir_TMPFILE ++ }; ++ ++ /* copy-up may happen */ ++ mutex_lock(&dir->i_mutex); ++ ++ sb = dir->i_sb; ++ err = si_read_lock(sb, AuLock_FLUSH | AuLock_NOPLM); ++ if (unlikely(err)) ++ goto out; ++ ++ err = au_di_init(dentry); ++ if (unlikely(err)) ++ goto out_si; ++ ++ err = -EBUSY; ++ parent = d_find_any_alias(dir); ++ AuDebugOn(!parent); ++ di_write_lock_parent(parent); ++ if (unlikely(parent->d_inode != dir)) ++ goto out_parent; ++ ++ err = au_digen_test(parent, au_sigen(sb)); ++ if (unlikely(err)) ++ goto out_parent; ++ ++ bindex = au_dbstart(parent); ++ au_set_dbstart(dentry, bindex); ++ au_set_dbend(dentry, bindex); ++ err = au_wr_dir(dentry, /*src_dentry*/NULL, &wr_dir_args); ++ bindex = err; ++ if (unlikely(err < 0)) ++ goto out_parent; ++ ++ err = -EOPNOTSUPP; ++ h_dir = au_h_iptr(dir, bindex); ++ if (unlikely(!h_dir->i_op->tmpfile)) ++ goto out_parent; ++ ++ h_mnt = au_sbr_mnt(sb, bindex); ++ err = vfsub_mnt_want_write(h_mnt); ++ if (unlikely(err)) ++ goto out_parent; ++ ++ h_parent = au_h_dptr(parent, bindex); ++ err = inode_permission(h_parent->d_inode, MAY_WRITE | MAY_EXEC); ++ if (unlikely(err)) ++ goto out_mnt; ++ ++ err = -ENOMEM; ++ h_dentry = d_alloc(h_parent, &dentry->d_name); ++ if (unlikely(!h_dentry)) ++ goto out_mnt; ++ ++ err = h_dir->i_op->tmpfile(h_dir, h_dentry, mode); ++ if (unlikely(err)) ++ goto out_dentry; ++ ++ au_set_dbstart(dentry, bindex); ++ au_set_dbend(dentry, bindex); ++ au_set_h_dptr(dentry, bindex, dget(h_dentry)); ++ inode = au_new_inode(dentry, /*must_new*/1); ++ if (IS_ERR(inode)) { ++ err = PTR_ERR(inode); ++ au_set_h_dptr(dentry, bindex, NULL); ++ au_set_dbstart(dentry, -1); ++ au_set_dbend(dentry, -1); ++ } else { ++ if (!inode->i_nlink) ++ set_nlink(inode, 1); ++ d_tmpfile(dentry, inode); ++ au_di(dentry)->di_tmpfile = 1; ++ ++ /* update without i_mutex */ ++ if (au_ibstart(dir) == au_dbstart(dentry)) ++ au_cpup_attr_timesizes(dir); ++ } ++ ++out_dentry: ++ dput(h_dentry); ++out_mnt: ++ vfsub_mnt_drop_write(h_mnt); ++out_parent: ++ di_write_unlock(parent); ++ dput(parent); ++ di_write_unlock(dentry); ++ if (!err) ++#if 0 ++ /* verbose coding for lock class name */ ++ au_rw_class(&au_di(dentry)->di_rwsem, ++ au_lc_key + AuLcNonDir_DIINFO); ++#else ++ ; ++#endif ++ else { ++ au_di_fin(dentry); ++ dentry->d_fsdata = NULL; ++ } ++out_si: ++ si_read_unlock(sb); ++out: ++ mutex_unlock(&dir->i_mutex); ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++struct au_link_args { ++ aufs_bindex_t bdst, bsrc; ++ struct au_pin pin; ++ struct path h_path; ++ struct dentry *src_parent, *parent; ++}; ++ ++static int au_cpup_before_link(struct dentry *src_dentry, ++ struct au_link_args *a) ++{ ++ int err; ++ struct dentry *h_src_dentry; ++ struct au_cp_generic cpg = { ++ .dentry = src_dentry, ++ .bdst = a->bdst, ++ .bsrc = a->bsrc, ++ .len = -1, ++ .pin = &a->pin, ++ .flags = AuCpup_DTIME | AuCpup_HOPEN /* | AuCpup_KEEPLINO */ ++ }; ++ ++ di_read_lock_parent(a->src_parent, AuLock_IR); ++ err = au_test_and_cpup_dirs(src_dentry, a->bdst); ++ if (unlikely(err)) ++ goto out; ++ ++ h_src_dentry = au_h_dptr(src_dentry, a->bsrc); ++ err = au_pin(&a->pin, src_dentry, a->bdst, ++ au_opt_udba(src_dentry->d_sb), ++ AuPin_DI_LOCKED | AuPin_MNT_WRITE); ++ if (unlikely(err)) ++ goto out; ++ ++ err = au_sio_cpup_simple(&cpg); ++ au_unpin(&a->pin); ++ ++out: ++ di_read_unlock(a->src_parent, AuLock_IR); ++ return err; ++} ++ ++static int au_cpup_or_link(struct dentry *src_dentry, struct dentry *dentry, ++ struct au_link_args *a) ++{ ++ int err; ++ unsigned char plink; ++ aufs_bindex_t bend; ++ struct dentry *h_src_dentry; ++ struct inode *h_inode, *inode, *delegated; ++ struct super_block *sb; ++ struct file *h_file; ++ ++ plink = 0; ++ h_inode = NULL; ++ sb = src_dentry->d_sb; ++ inode = src_dentry->d_inode; ++ if (au_ibstart(inode) <= a->bdst) ++ h_inode = au_h_iptr(inode, a->bdst); ++ if (!h_inode || !h_inode->i_nlink) { ++ /* copyup src_dentry as the name of dentry. */ ++ bend = au_dbend(dentry); ++ if (bend < a->bsrc) ++ au_set_dbend(dentry, a->bsrc); ++ au_set_h_dptr(dentry, a->bsrc, ++ dget(au_h_dptr(src_dentry, a->bsrc))); ++ dget(a->h_path.dentry); ++ au_set_h_dptr(dentry, a->bdst, NULL); ++ AuDbg("temporary d_inode...\n"); ++ spin_lock(&dentry->d_lock); ++ dentry->d_inode = src_dentry->d_inode; /* tmp */ ++ spin_unlock(&dentry->d_lock); ++ h_file = au_h_open_pre(dentry, a->bsrc, /*force_wr*/0); ++ if (IS_ERR(h_file)) ++ err = PTR_ERR(h_file); ++ else { ++ struct au_cp_generic cpg = { ++ .dentry = dentry, ++ .bdst = a->bdst, ++ .bsrc = -1, ++ .len = -1, ++ .pin = &a->pin, ++ .flags = AuCpup_KEEPLINO ++ }; ++ err = au_sio_cpup_simple(&cpg); ++ au_h_open_post(dentry, a->bsrc, h_file); ++ if (!err) { ++ dput(a->h_path.dentry); ++ a->h_path.dentry = au_h_dptr(dentry, a->bdst); ++ } else ++ au_set_h_dptr(dentry, a->bdst, ++ a->h_path.dentry); ++ } ++ spin_lock(&dentry->d_lock); ++ dentry->d_inode = NULL; /* restore */ ++ spin_unlock(&dentry->d_lock); ++ AuDbg("temporary d_inode...done\n"); ++ au_set_h_dptr(dentry, a->bsrc, NULL); ++ au_set_dbend(dentry, bend); ++ } else { ++ /* the inode of src_dentry already exists on a.bdst branch */ ++ h_src_dentry = d_find_alias(h_inode); ++ if (!h_src_dentry && au_plink_test(inode)) { ++ plink = 1; ++ h_src_dentry = au_plink_lkup(inode, a->bdst); ++ err = PTR_ERR(h_src_dentry); ++ if (IS_ERR(h_src_dentry)) ++ goto out; ++ ++ if (unlikely(!h_src_dentry->d_inode)) { ++ dput(h_src_dentry); ++ h_src_dentry = NULL; ++ } ++ ++ } ++ if (h_src_dentry) { ++ delegated = NULL; ++ err = vfsub_link(h_src_dentry, au_pinned_h_dir(&a->pin), ++ &a->h_path, &delegated); ++ if (unlikely(err == -EWOULDBLOCK)) { ++ pr_warn("cannot retry for NFSv4 delegation" ++ " for an internal link\n"); ++ iput(delegated); ++ } ++ dput(h_src_dentry); ++ } else { ++ AuIOErr("no dentry found for hi%lu on b%d\n", ++ h_inode->i_ino, a->bdst); ++ err = -EIO; ++ } ++ } ++ ++ if (!err && !plink) ++ au_plink_append(inode, a->bdst, a->h_path.dentry); ++ ++out: ++ AuTraceErr(err); ++ return err; ++} ++ ++int aufs_link(struct dentry *src_dentry, struct inode *dir, ++ struct dentry *dentry) ++{ ++ int err, rerr; ++ struct au_dtime dt; ++ struct au_link_args *a; ++ struct dentry *wh_dentry, *h_src_dentry; ++ struct inode *inode, *delegated; ++ struct super_block *sb; ++ struct au_wr_dir_args wr_dir_args = { ++ /* .force_btgt = -1, */ ++ .flags = AuWrDir_ADD_ENTRY ++ }; ++ ++ IMustLock(dir); ++ inode = src_dentry->d_inode; ++ IMustLock(inode); ++ ++ err = -ENOMEM; ++ a = kzalloc(sizeof(*a), GFP_NOFS); ++ if (unlikely(!a)) ++ goto out; ++ ++ a->parent = dentry->d_parent; /* dir inode is locked */ ++ err = aufs_read_and_write_lock2(dentry, src_dentry, ++ AuLock_NOPLM | AuLock_GEN); ++ if (unlikely(err)) ++ goto out_kfree; ++ err = au_d_linkable(src_dentry); ++ if (unlikely(err)) ++ goto out_unlock; ++ err = au_d_may_add(dentry); ++ if (unlikely(err)) ++ goto out_unlock; ++ ++ a->src_parent = dget_parent(src_dentry); ++ wr_dir_args.force_btgt = au_ibstart(inode); ++ ++ di_write_lock_parent(a->parent); ++ wr_dir_args.force_btgt = au_wbr(dentry, wr_dir_args.force_btgt); ++ wh_dentry = lock_hdir_lkup_wh(dentry, &dt, src_dentry, &a->pin, ++ &wr_dir_args); ++ err = PTR_ERR(wh_dentry); ++ if (IS_ERR(wh_dentry)) ++ goto out_parent; ++ ++ err = 0; ++ sb = dentry->d_sb; ++ a->bdst = au_dbstart(dentry); ++ a->h_path.dentry = au_h_dptr(dentry, a->bdst); ++ a->h_path.mnt = au_sbr_mnt(sb, a->bdst); ++ a->bsrc = au_ibstart(inode); ++ h_src_dentry = au_h_d_alias(src_dentry, a->bsrc); ++ if (!h_src_dentry && au_di(src_dentry)->di_tmpfile) ++ h_src_dentry = dget(au_hi_wh(inode, a->bsrc)); ++ if (!h_src_dentry) { ++ a->bsrc = au_dbstart(src_dentry); ++ h_src_dentry = au_h_d_alias(src_dentry, a->bsrc); ++ AuDebugOn(!h_src_dentry); ++ } else if (IS_ERR(h_src_dentry)) { ++ err = PTR_ERR(h_src_dentry); ++ goto out_parent; ++ } ++ ++ if (au_opt_test(au_mntflags(sb), PLINK)) { ++ if (a->bdst < a->bsrc ++ /* && h_src_dentry->d_sb != a->h_path.dentry->d_sb */) ++ err = au_cpup_or_link(src_dentry, dentry, a); ++ else { ++ delegated = NULL; ++ err = vfsub_link(h_src_dentry, au_pinned_h_dir(&a->pin), ++ &a->h_path, &delegated); ++ if (unlikely(err == -EWOULDBLOCK)) { ++ pr_warn("cannot retry for NFSv4 delegation" ++ " for an internal link\n"); ++ iput(delegated); ++ } ++ } ++ dput(h_src_dentry); ++ } else { ++ /* ++ * copyup src_dentry to the branch we process, ++ * and then link(2) to it. ++ */ ++ dput(h_src_dentry); ++ if (a->bdst < a->bsrc ++ /* && h_src_dentry->d_sb != a->h_path.dentry->d_sb */) { ++ au_unpin(&a->pin); ++ di_write_unlock(a->parent); ++ err = au_cpup_before_link(src_dentry, a); ++ di_write_lock_parent(a->parent); ++ if (!err) ++ err = au_pin(&a->pin, dentry, a->bdst, ++ au_opt_udba(sb), ++ AuPin_DI_LOCKED | AuPin_MNT_WRITE); ++ if (unlikely(err)) ++ goto out_wh; ++ } ++ if (!err) { ++ h_src_dentry = au_h_dptr(src_dentry, a->bdst); ++ err = -ENOENT; ++ if (h_src_dentry && h_src_dentry->d_inode) { ++ delegated = NULL; ++ err = vfsub_link(h_src_dentry, ++ au_pinned_h_dir(&a->pin), ++ &a->h_path, &delegated); ++ if (unlikely(err == -EWOULDBLOCK)) { ++ pr_warn("cannot retry" ++ " for NFSv4 delegation" ++ " for an internal link\n"); ++ iput(delegated); ++ } ++ } ++ } ++ } ++ if (unlikely(err)) ++ goto out_unpin; ++ ++ if (wh_dentry) { ++ a->h_path.dentry = wh_dentry; ++ err = au_wh_unlink_dentry(au_pinned_h_dir(&a->pin), &a->h_path, ++ dentry); ++ if (unlikely(err)) ++ goto out_revert; ++ } ++ ++ au_dir_ts(dir, a->bdst); ++ dir->i_version++; ++ inc_nlink(inode); ++ inode->i_ctime = dir->i_ctime; ++ d_instantiate(dentry, au_igrab(inode)); ++ if (d_unhashed(a->h_path.dentry)) ++ /* some filesystem calls d_drop() */ ++ d_drop(dentry); ++ /* some filesystems consume an inode even hardlink */ ++ au_fhsm_wrote(sb, a->bdst, /*force*/0); ++ goto out_unpin; /* success */ ++ ++out_revert: ++ /* no delegation since it is just created */ ++ rerr = vfsub_unlink(au_pinned_h_dir(&a->pin), &a->h_path, ++ /*delegated*/NULL, /*force*/0); ++ if (unlikely(rerr)) { ++ AuIOErr("%pd reverting failed(%d, %d)\n", dentry, err, rerr); ++ err = -EIO; ++ } ++ au_dtime_revert(&dt); ++out_unpin: ++ au_unpin(&a->pin); ++out_wh: ++ dput(wh_dentry); ++out_parent: ++ di_write_unlock(a->parent); ++ dput(a->src_parent); ++out_unlock: ++ if (unlikely(err)) { ++ au_update_dbstart(dentry); ++ d_drop(dentry); ++ } ++ aufs_read_and_write_unlock2(dentry, src_dentry); ++out_kfree: ++ kfree(a); ++out: ++ AuTraceErr(err); ++ return err; ++} ++ ++int aufs_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) ++{ ++ int err, rerr; ++ aufs_bindex_t bindex; ++ unsigned char diropq; ++ struct path h_path; ++ struct dentry *wh_dentry, *parent, *opq_dentry; ++ struct mutex *h_mtx; ++ struct super_block *sb; ++ struct { ++ struct au_pin pin; ++ struct au_dtime dt; ++ } *a; /* reduce the stack usage */ ++ struct au_wr_dir_args wr_dir_args = { ++ .force_btgt = -1, ++ .flags = AuWrDir_ADD_ENTRY | AuWrDir_ISDIR ++ }; ++ ++ IMustLock(dir); ++ ++ err = -ENOMEM; ++ a = kmalloc(sizeof(*a), GFP_NOFS); ++ if (unlikely(!a)) ++ goto out; ++ ++ err = aufs_read_lock(dentry, AuLock_DW | AuLock_GEN); ++ if (unlikely(err)) ++ goto out_free; ++ err = au_d_may_add(dentry); ++ if (unlikely(err)) ++ goto out_unlock; ++ ++ parent = dentry->d_parent; /* dir inode is locked */ ++ di_write_lock_parent(parent); ++ wh_dentry = lock_hdir_lkup_wh(dentry, &a->dt, /*src_dentry*/NULL, ++ &a->pin, &wr_dir_args); ++ err = PTR_ERR(wh_dentry); ++ if (IS_ERR(wh_dentry)) ++ goto out_parent; ++ ++ sb = dentry->d_sb; ++ bindex = au_dbstart(dentry); ++ h_path.dentry = au_h_dptr(dentry, bindex); ++ h_path.mnt = au_sbr_mnt(sb, bindex); ++ err = vfsub_mkdir(au_pinned_h_dir(&a->pin), &h_path, mode); ++ if (unlikely(err)) ++ goto out_unpin; ++ ++ /* make the dir opaque */ ++ diropq = 0; ++ h_mtx = &h_path.dentry->d_inode->i_mutex; ++ if (wh_dentry ++ || au_opt_test(au_mntflags(sb), ALWAYS_DIROPQ)) { ++ mutex_lock_nested(h_mtx, AuLsc_I_CHILD); ++ opq_dentry = au_diropq_create(dentry, bindex); ++ mutex_unlock(h_mtx); ++ err = PTR_ERR(opq_dentry); ++ if (IS_ERR(opq_dentry)) ++ goto out_dir; ++ dput(opq_dentry); ++ diropq = 1; ++ } ++ ++ err = epilog(dir, bindex, wh_dentry, dentry); ++ if (!err) { ++ inc_nlink(dir); ++ goto out_unpin; /* success */ ++ } ++ ++ /* revert */ ++ if (diropq) { ++ AuLabel(revert opq); ++ mutex_lock_nested(h_mtx, AuLsc_I_CHILD); ++ rerr = au_diropq_remove(dentry, bindex); ++ mutex_unlock(h_mtx); ++ if (rerr) { ++ AuIOErr("%pd reverting diropq failed(%d, %d)\n", ++ dentry, err, rerr); ++ err = -EIO; ++ } ++ } ++ ++out_dir: ++ AuLabel(revert dir); ++ rerr = vfsub_rmdir(au_pinned_h_dir(&a->pin), &h_path); ++ if (rerr) { ++ AuIOErr("%pd reverting dir failed(%d, %d)\n", ++ dentry, err, rerr); ++ err = -EIO; ++ } ++ au_dtime_revert(&a->dt); ++out_unpin: ++ au_unpin(&a->pin); ++ dput(wh_dentry); ++out_parent: ++ di_write_unlock(parent); ++out_unlock: ++ if (unlikely(err)) { ++ au_update_dbstart(dentry); ++ d_drop(dentry); ++ } ++ aufs_read_unlock(dentry, AuLock_DW); ++out_free: ++ kfree(a); ++out: ++ return err; ++} +diff --git a/fs/aufs/i_op_del.c b/fs/aufs/i_op_del.c +new file mode 100644 +index 0000000..b4dd686 +--- /dev/null ++++ b/fs/aufs/i_op_del.c +@@ -0,0 +1,506 @@ ++/* ++ * Copyright (C) 2005-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * inode operations (del entry) ++ */ ++ ++#include "aufs.h" ++ ++/* ++ * decide if a new whiteout for @dentry is necessary or not. ++ * when it is necessary, prepare the parent dir for the upper branch whose ++ * branch index is @bcpup for creation. the actual creation of the whiteout will ++ * be done by caller. ++ * return value: ++ * 0: wh is unnecessary ++ * plus: wh is necessary ++ * minus: error ++ */ ++int au_wr_dir_need_wh(struct dentry *dentry, int isdir, aufs_bindex_t *bcpup) ++{ ++ int need_wh, err; ++ aufs_bindex_t bstart; ++ struct super_block *sb; ++ ++ sb = dentry->d_sb; ++ bstart = au_dbstart(dentry); ++ if (*bcpup < 0) { ++ *bcpup = bstart; ++ if (au_test_ro(sb, bstart, dentry->d_inode)) { ++ err = AuWbrCopyup(au_sbi(sb), dentry); ++ *bcpup = err; ++ if (unlikely(err < 0)) ++ goto out; ++ } ++ } else ++ AuDebugOn(bstart < *bcpup ++ || au_test_ro(sb, *bcpup, dentry->d_inode)); ++ AuDbg("bcpup %d, bstart %d\n", *bcpup, bstart); ++ ++ if (*bcpup != bstart) { ++ err = au_cpup_dirs(dentry, *bcpup); ++ if (unlikely(err)) ++ goto out; ++ need_wh = 1; ++ } else { ++ struct au_dinfo *dinfo, *tmp; ++ ++ need_wh = -ENOMEM; ++ dinfo = au_di(dentry); ++ tmp = au_di_alloc(sb, AuLsc_DI_TMP); ++ if (tmp) { ++ au_di_cp(tmp, dinfo); ++ au_di_swap(tmp, dinfo); ++ /* returns the number of positive dentries */ ++ need_wh = au_lkup_dentry(dentry, bstart + 1, /*type*/0); ++ au_di_swap(tmp, dinfo); ++ au_rw_write_unlock(&tmp->di_rwsem); ++ au_di_free(tmp); ++ } ++ } ++ AuDbg("need_wh %d\n", need_wh); ++ err = need_wh; ++ ++out: ++ return err; ++} ++ ++/* ++ * simple tests for the del-entry operations. ++ * following the checks in vfs, plus the parent-child relationship. ++ */ ++int au_may_del(struct dentry *dentry, aufs_bindex_t bindex, ++ struct dentry *h_parent, int isdir) ++{ ++ int err; ++ umode_t h_mode; ++ struct dentry *h_dentry, *h_latest; ++ struct inode *h_inode; ++ ++ h_dentry = au_h_dptr(dentry, bindex); ++ h_inode = h_dentry->d_inode; ++ if (dentry->d_inode) { ++ err = -ENOENT; ++ if (unlikely(!h_inode || !h_inode->i_nlink)) ++ goto out; ++ ++ h_mode = h_inode->i_mode; ++ if (!isdir) { ++ err = -EISDIR; ++ if (unlikely(S_ISDIR(h_mode))) ++ goto out; ++ } else if (unlikely(!S_ISDIR(h_mode))) { ++ err = -ENOTDIR; ++ goto out; ++ } ++ } else { ++ /* rename(2) case */ ++ err = -EIO; ++ if (unlikely(h_inode)) ++ goto out; ++ } ++ ++ err = -ENOENT; ++ /* expected parent dir is locked */ ++ if (unlikely(h_parent != h_dentry->d_parent)) ++ goto out; ++ err = 0; ++ ++ /* ++ * rmdir a dir may break the consistency on some filesystem. ++ * let's try heavy test. ++ */ ++ err = -EACCES; ++ if (unlikely(!au_opt_test(au_mntflags(dentry->d_sb), DIRPERM1) ++ && au_test_h_perm(h_parent->d_inode, ++ MAY_EXEC | MAY_WRITE))) ++ goto out; ++ ++ h_latest = au_sio_lkup_one(&dentry->d_name, h_parent); ++ err = -EIO; ++ if (IS_ERR(h_latest)) ++ goto out; ++ if (h_latest == h_dentry) ++ err = 0; ++ dput(h_latest); ++ ++out: ++ return err; ++} ++ ++/* ++ * decide the branch where we operate for @dentry. the branch index will be set ++ * @rbcpup. after diciding it, 'pin' it and store the timestamps of the parent ++ * dir for reverting. ++ * when a new whiteout is necessary, create it. ++ */ ++static struct dentry* ++lock_hdir_create_wh(struct dentry *dentry, int isdir, aufs_bindex_t *rbcpup, ++ struct au_dtime *dt, struct au_pin *pin) ++{ ++ struct dentry *wh_dentry; ++ struct super_block *sb; ++ struct path h_path; ++ int err, need_wh; ++ unsigned int udba; ++ aufs_bindex_t bcpup; ++ ++ need_wh = au_wr_dir_need_wh(dentry, isdir, rbcpup); ++ wh_dentry = ERR_PTR(need_wh); ++ if (unlikely(need_wh < 0)) ++ goto out; ++ ++ sb = dentry->d_sb; ++ udba = au_opt_udba(sb); ++ bcpup = *rbcpup; ++ err = au_pin(pin, dentry, bcpup, udba, ++ AuPin_DI_LOCKED | AuPin_MNT_WRITE); ++ wh_dentry = ERR_PTR(err); ++ if (unlikely(err)) ++ goto out; ++ ++ h_path.dentry = au_pinned_h_parent(pin); ++ if (udba != AuOpt_UDBA_NONE ++ && au_dbstart(dentry) == bcpup) { ++ err = au_may_del(dentry, bcpup, h_path.dentry, isdir); ++ wh_dentry = ERR_PTR(err); ++ if (unlikely(err)) ++ goto out_unpin; ++ } ++ ++ h_path.mnt = au_sbr_mnt(sb, bcpup); ++ au_dtime_store(dt, au_pinned_parent(pin), &h_path); ++ wh_dentry = NULL; ++ if (!need_wh) ++ goto out; /* success, no need to create whiteout */ ++ ++ wh_dentry = au_wh_create(dentry, bcpup, h_path.dentry); ++ if (IS_ERR(wh_dentry)) ++ goto out_unpin; ++ ++ /* returns with the parent is locked and wh_dentry is dget-ed */ ++ goto out; /* success */ ++ ++out_unpin: ++ au_unpin(pin); ++out: ++ return wh_dentry; ++} ++ ++/* ++ * when removing a dir, rename it to a unique temporary whiteout-ed name first ++ * in order to be revertible and save time for removing many child whiteouts ++ * under the dir. ++ * returns 1 when there are too many child whiteout and caller should remove ++ * them asynchronously. returns 0 when the number of children is enough small to ++ * remove now or the branch fs is a remote fs. ++ * otherwise return an error. ++ */ ++static int renwh_and_rmdir(struct dentry *dentry, aufs_bindex_t bindex, ++ struct au_nhash *whlist, struct inode *dir) ++{ ++ int rmdir_later, err, dirwh; ++ struct dentry *h_dentry; ++ struct super_block *sb; ++ ++ sb = dentry->d_sb; ++ SiMustAnyLock(sb); ++ h_dentry = au_h_dptr(dentry, bindex); ++ err = au_whtmp_ren(h_dentry, au_sbr(sb, bindex)); ++ if (unlikely(err)) ++ goto out; ++ ++ /* stop monitoring */ ++ au_hn_free(au_hi(dentry->d_inode, bindex)); ++ ++ if (!au_test_fs_remote(h_dentry->d_sb)) { ++ dirwh = au_sbi(sb)->si_dirwh; ++ rmdir_later = (dirwh <= 1); ++ if (!rmdir_later) ++ rmdir_later = au_nhash_test_longer_wh(whlist, bindex, ++ dirwh); ++ if (rmdir_later) ++ return rmdir_later; ++ } ++ ++ err = au_whtmp_rmdir(dir, bindex, h_dentry, whlist); ++ if (unlikely(err)) { ++ AuIOErr("rmdir %pd, b%d failed, %d. ignored\n", ++ h_dentry, bindex, err); ++ err = 0; ++ } ++ ++out: ++ AuTraceErr(err); ++ return err; ++} ++ ++/* ++ * final procedure for deleting a entry. ++ * maintain dentry and iattr. ++ */ ++static void epilog(struct inode *dir, struct dentry *dentry, ++ aufs_bindex_t bindex) ++{ ++ struct inode *inode; ++ ++ inode = dentry->d_inode; ++ d_drop(dentry); ++ inode->i_ctime = dir->i_ctime; ++ ++ au_dir_ts(dir, bindex); ++ dir->i_version++; ++} ++ ++/* ++ * when an error happened, remove the created whiteout and revert everything. ++ */ ++static int do_revert(int err, struct inode *dir, aufs_bindex_t bindex, ++ aufs_bindex_t bwh, struct dentry *wh_dentry, ++ struct dentry *dentry, struct au_dtime *dt) ++{ ++ int rerr; ++ struct path h_path = { ++ .dentry = wh_dentry, ++ .mnt = au_sbr_mnt(dir->i_sb, bindex) ++ }; ++ ++ rerr = au_wh_unlink_dentry(au_h_iptr(dir, bindex), &h_path, dentry); ++ if (!rerr) { ++ au_set_dbwh(dentry, bwh); ++ au_dtime_revert(dt); ++ return 0; ++ } ++ ++ AuIOErr("%pd reverting whiteout failed(%d, %d)\n", dentry, err, rerr); ++ return -EIO; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++int aufs_unlink(struct inode *dir, struct dentry *dentry) ++{ ++ int err; ++ aufs_bindex_t bwh, bindex, bstart; ++ struct inode *inode, *h_dir, *delegated; ++ struct dentry *parent, *wh_dentry; ++ /* to reuduce stack size */ ++ struct { ++ struct au_dtime dt; ++ struct au_pin pin; ++ struct path h_path; ++ } *a; ++ ++ IMustLock(dir); ++ ++ err = -ENOMEM; ++ a = kmalloc(sizeof(*a), GFP_NOFS); ++ if (unlikely(!a)) ++ goto out; ++ ++ err = aufs_read_lock(dentry, AuLock_DW | AuLock_GEN); ++ if (unlikely(err)) ++ goto out_free; ++ err = au_d_hashed_positive(dentry); ++ if (unlikely(err)) ++ goto out_unlock; ++ inode = dentry->d_inode; ++ IMustLock(inode); ++ err = -EISDIR; ++ if (unlikely(d_is_dir(dentry))) ++ goto out_unlock; /* possible? */ ++ ++ bstart = au_dbstart(dentry); ++ bwh = au_dbwh(dentry); ++ bindex = -1; ++ parent = dentry->d_parent; /* dir inode is locked */ ++ di_write_lock_parent(parent); ++ wh_dentry = lock_hdir_create_wh(dentry, /*isdir*/0, &bindex, &a->dt, ++ &a->pin); ++ err = PTR_ERR(wh_dentry); ++ if (IS_ERR(wh_dentry)) ++ goto out_parent; ++ ++ a->h_path.mnt = au_sbr_mnt(dentry->d_sb, bstart); ++ a->h_path.dentry = au_h_dptr(dentry, bstart); ++ dget(a->h_path.dentry); ++ if (bindex == bstart) { ++ h_dir = au_pinned_h_dir(&a->pin); ++ delegated = NULL; ++ err = vfsub_unlink(h_dir, &a->h_path, &delegated, /*force*/0); ++ if (unlikely(err == -EWOULDBLOCK)) { ++ pr_warn("cannot retry for NFSv4 delegation" ++ " for an internal unlink\n"); ++ iput(delegated); ++ } ++ } else { ++ /* dir inode is locked */ ++ h_dir = wh_dentry->d_parent->d_inode; ++ IMustLock(h_dir); ++ err = 0; ++ } ++ ++ if (!err) { ++ vfsub_drop_nlink(inode); ++ epilog(dir, dentry, bindex); ++ ++ /* update target timestamps */ ++ if (bindex == bstart) { ++ vfsub_update_h_iattr(&a->h_path, /*did*/NULL); ++ /*ignore*/ ++ inode->i_ctime = a->h_path.dentry->d_inode->i_ctime; ++ } else ++ /* todo: this timestamp may be reverted later */ ++ inode->i_ctime = h_dir->i_ctime; ++ goto out_unpin; /* success */ ++ } ++ ++ /* revert */ ++ if (wh_dentry) { ++ int rerr; ++ ++ rerr = do_revert(err, dir, bindex, bwh, wh_dentry, dentry, ++ &a->dt); ++ if (rerr) ++ err = rerr; ++ } ++ ++out_unpin: ++ au_unpin(&a->pin); ++ dput(wh_dentry); ++ dput(a->h_path.dentry); ++out_parent: ++ di_write_unlock(parent); ++out_unlock: ++ aufs_read_unlock(dentry, AuLock_DW); ++out_free: ++ kfree(a); ++out: ++ return err; ++} ++ ++int aufs_rmdir(struct inode *dir, struct dentry *dentry) ++{ ++ int err, rmdir_later; ++ aufs_bindex_t bwh, bindex, bstart; ++ struct inode *inode; ++ struct dentry *parent, *wh_dentry, *h_dentry; ++ struct au_whtmp_rmdir *args; ++ /* to reuduce stack size */ ++ struct { ++ struct au_dtime dt; ++ struct au_pin pin; ++ } *a; ++ ++ IMustLock(dir); ++ ++ err = -ENOMEM; ++ a = kmalloc(sizeof(*a), GFP_NOFS); ++ if (unlikely(!a)) ++ goto out; ++ ++ err = aufs_read_lock(dentry, AuLock_DW | AuLock_FLUSH | AuLock_GEN); ++ if (unlikely(err)) ++ goto out_free; ++ err = au_alive_dir(dentry); ++ if (unlikely(err)) ++ goto out_unlock; ++ inode = dentry->d_inode; ++ IMustLock(inode); ++ err = -ENOTDIR; ++ if (unlikely(!d_is_dir(dentry))) ++ goto out_unlock; /* possible? */ ++ ++ err = -ENOMEM; ++ args = au_whtmp_rmdir_alloc(dir->i_sb, GFP_NOFS); ++ if (unlikely(!args)) ++ goto out_unlock; ++ ++ parent = dentry->d_parent; /* dir inode is locked */ ++ di_write_lock_parent(parent); ++ err = au_test_empty(dentry, &args->whlist); ++ if (unlikely(err)) ++ goto out_parent; ++ ++ bstart = au_dbstart(dentry); ++ bwh = au_dbwh(dentry); ++ bindex = -1; ++ wh_dentry = lock_hdir_create_wh(dentry, /*isdir*/1, &bindex, &a->dt, ++ &a->pin); ++ err = PTR_ERR(wh_dentry); ++ if (IS_ERR(wh_dentry)) ++ goto out_parent; ++ ++ h_dentry = au_h_dptr(dentry, bstart); ++ dget(h_dentry); ++ rmdir_later = 0; ++ if (bindex == bstart) { ++ err = renwh_and_rmdir(dentry, bstart, &args->whlist, dir); ++ if (err > 0) { ++ rmdir_later = err; ++ err = 0; ++ } ++ } else { ++ /* stop monitoring */ ++ au_hn_free(au_hi(inode, bstart)); ++ ++ /* dir inode is locked */ ++ IMustLock(wh_dentry->d_parent->d_inode); ++ err = 0; ++ } ++ ++ if (!err) { ++ vfsub_dead_dir(inode); ++ au_set_dbdiropq(dentry, -1); ++ epilog(dir, dentry, bindex); ++ ++ if (rmdir_later) { ++ au_whtmp_kick_rmdir(dir, bstart, h_dentry, args); ++ args = NULL; ++ } ++ ++ goto out_unpin; /* success */ ++ } ++ ++ /* revert */ ++ AuLabel(revert); ++ if (wh_dentry) { ++ int rerr; ++ ++ rerr = do_revert(err, dir, bindex, bwh, wh_dentry, dentry, ++ &a->dt); ++ if (rerr) ++ err = rerr; ++ } ++ ++out_unpin: ++ au_unpin(&a->pin); ++ dput(wh_dentry); ++ dput(h_dentry); ++out_parent: ++ di_write_unlock(parent); ++ if (args) ++ au_whtmp_rmdir_free(args); ++out_unlock: ++ aufs_read_unlock(dentry, AuLock_DW); ++out_free: ++ kfree(a); ++out: ++ AuTraceErr(err); ++ return err; ++} +diff --git a/fs/aufs/i_op_ren.c b/fs/aufs/i_op_ren.c +new file mode 100644 +index 0000000..6ce2ed6 +--- /dev/null ++++ b/fs/aufs/i_op_ren.c +@@ -0,0 +1,1013 @@ ++/* ++ * Copyright (C) 2005-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * inode operation (rename entry) ++ * todo: this is crazy monster ++ */ ++ ++#include "aufs.h" ++ ++enum { AuSRC, AuDST, AuSrcDst }; ++enum { AuPARENT, AuCHILD, AuParentChild }; ++ ++#define AuRen_ISDIR 1 ++#define AuRen_ISSAMEDIR (1 << 1) ++#define AuRen_WHSRC (1 << 2) ++#define AuRen_WHDST (1 << 3) ++#define AuRen_MNT_WRITE (1 << 4) ++#define AuRen_DT_DSTDIR (1 << 5) ++#define AuRen_DIROPQ (1 << 6) ++#define au_ftest_ren(flags, name) ((flags) & AuRen_##name) ++#define au_fset_ren(flags, name) \ ++ do { (flags) |= AuRen_##name; } while (0) ++#define au_fclr_ren(flags, name) \ ++ do { (flags) &= ~AuRen_##name; } while (0) ++ ++struct au_ren_args { ++ struct { ++ struct dentry *dentry, *h_dentry, *parent, *h_parent, ++ *wh_dentry; ++ struct inode *dir, *inode; ++ struct au_hinode *hdir; ++ struct au_dtime dt[AuParentChild]; ++ aufs_bindex_t bstart; ++ } sd[AuSrcDst]; ++ ++#define src_dentry sd[AuSRC].dentry ++#define src_dir sd[AuSRC].dir ++#define src_inode sd[AuSRC].inode ++#define src_h_dentry sd[AuSRC].h_dentry ++#define src_parent sd[AuSRC].parent ++#define src_h_parent sd[AuSRC].h_parent ++#define src_wh_dentry sd[AuSRC].wh_dentry ++#define src_hdir sd[AuSRC].hdir ++#define src_h_dir sd[AuSRC].hdir->hi_inode ++#define src_dt sd[AuSRC].dt ++#define src_bstart sd[AuSRC].bstart ++ ++#define dst_dentry sd[AuDST].dentry ++#define dst_dir sd[AuDST].dir ++#define dst_inode sd[AuDST].inode ++#define dst_h_dentry sd[AuDST].h_dentry ++#define dst_parent sd[AuDST].parent ++#define dst_h_parent sd[AuDST].h_parent ++#define dst_wh_dentry sd[AuDST].wh_dentry ++#define dst_hdir sd[AuDST].hdir ++#define dst_h_dir sd[AuDST].hdir->hi_inode ++#define dst_dt sd[AuDST].dt ++#define dst_bstart sd[AuDST].bstart ++ ++ struct dentry *h_trap; ++ struct au_branch *br; ++ struct au_hinode *src_hinode; ++ struct path h_path; ++ struct au_nhash whlist; ++ aufs_bindex_t btgt, src_bwh, src_bdiropq; ++ ++ unsigned int flags; ++ ++ struct au_whtmp_rmdir *thargs; ++ struct dentry *h_dst; ++}; ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* ++ * functions for reverting. ++ * when an error happened in a single rename systemcall, we should revert ++ * everything as if nothing happened. ++ * we don't need to revert the copied-up/down the parent dir since they are ++ * harmless. ++ */ ++ ++#define RevertFailure(fmt, ...) do { \ ++ AuIOErr("revert failure: " fmt " (%d, %d)\n", \ ++ ##__VA_ARGS__, err, rerr); \ ++ err = -EIO; \ ++} while (0) ++ ++static void au_ren_rev_diropq(int err, struct au_ren_args *a) ++{ ++ int rerr; ++ ++ au_hn_imtx_lock_nested(a->src_hinode, AuLsc_I_CHILD); ++ rerr = au_diropq_remove(a->src_dentry, a->btgt); ++ au_hn_imtx_unlock(a->src_hinode); ++ au_set_dbdiropq(a->src_dentry, a->src_bdiropq); ++ if (rerr) ++ RevertFailure("remove diropq %pd", a->src_dentry); ++} ++ ++static void au_ren_rev_rename(int err, struct au_ren_args *a) ++{ ++ int rerr; ++ struct inode *delegated; ++ ++ a->h_path.dentry = vfsub_lkup_one(&a->src_dentry->d_name, ++ a->src_h_parent); ++ rerr = PTR_ERR(a->h_path.dentry); ++ if (IS_ERR(a->h_path.dentry)) { ++ RevertFailure("lkup one %pd", a->src_dentry); ++ return; ++ } ++ ++ delegated = NULL; ++ rerr = vfsub_rename(a->dst_h_dir, ++ au_h_dptr(a->src_dentry, a->btgt), ++ a->src_h_dir, &a->h_path, &delegated); ++ if (unlikely(rerr == -EWOULDBLOCK)) { ++ pr_warn("cannot retry for NFSv4 delegation" ++ " for an internal rename\n"); ++ iput(delegated); ++ } ++ d_drop(a->h_path.dentry); ++ dput(a->h_path.dentry); ++ /* au_set_h_dptr(a->src_dentry, a->btgt, NULL); */ ++ if (rerr) ++ RevertFailure("rename %pd", a->src_dentry); ++} ++ ++static void au_ren_rev_whtmp(int err, struct au_ren_args *a) ++{ ++ int rerr; ++ struct inode *delegated; ++ ++ a->h_path.dentry = vfsub_lkup_one(&a->dst_dentry->d_name, ++ a->dst_h_parent); ++ rerr = PTR_ERR(a->h_path.dentry); ++ if (IS_ERR(a->h_path.dentry)) { ++ RevertFailure("lkup one %pd", a->dst_dentry); ++ return; ++ } ++ if (a->h_path.dentry->d_inode) { ++ d_drop(a->h_path.dentry); ++ dput(a->h_path.dentry); ++ return; ++ } ++ ++ delegated = NULL; ++ rerr = vfsub_rename(a->dst_h_dir, a->h_dst, a->dst_h_dir, &a->h_path, ++ &delegated); ++ if (unlikely(rerr == -EWOULDBLOCK)) { ++ pr_warn("cannot retry for NFSv4 delegation" ++ " for an internal rename\n"); ++ iput(delegated); ++ } ++ d_drop(a->h_path.dentry); ++ dput(a->h_path.dentry); ++ if (!rerr) ++ au_set_h_dptr(a->dst_dentry, a->btgt, dget(a->h_dst)); ++ else ++ RevertFailure("rename %pd", a->h_dst); ++} ++ ++static void au_ren_rev_whsrc(int err, struct au_ren_args *a) ++{ ++ int rerr; ++ ++ a->h_path.dentry = a->src_wh_dentry; ++ rerr = au_wh_unlink_dentry(a->src_h_dir, &a->h_path, a->src_dentry); ++ au_set_dbwh(a->src_dentry, a->src_bwh); ++ if (rerr) ++ RevertFailure("unlink %pd", a->src_wh_dentry); ++} ++#undef RevertFailure ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* ++ * when we have to copyup the renaming entry, do it with the rename-target name ++ * in order to minimize the cost (the later actual rename is unnecessary). ++ * otherwise rename it on the target branch. ++ */ ++static int au_ren_or_cpup(struct au_ren_args *a) ++{ ++ int err; ++ struct dentry *d; ++ struct inode *delegated; ++ ++ d = a->src_dentry; ++ if (au_dbstart(d) == a->btgt) { ++ a->h_path.dentry = a->dst_h_dentry; ++ if (au_ftest_ren(a->flags, DIROPQ) ++ && au_dbdiropq(d) == a->btgt) ++ au_fclr_ren(a->flags, DIROPQ); ++ AuDebugOn(au_dbstart(d) != a->btgt); ++ delegated = NULL; ++ err = vfsub_rename(a->src_h_dir, au_h_dptr(d, a->btgt), ++ a->dst_h_dir, &a->h_path, &delegated); ++ if (unlikely(err == -EWOULDBLOCK)) { ++ pr_warn("cannot retry for NFSv4 delegation" ++ " for an internal rename\n"); ++ iput(delegated); ++ } ++ } else ++ BUG(); ++ ++ if (!err && a->h_dst) ++ /* it will be set to dinfo later */ ++ dget(a->h_dst); ++ ++ return err; ++} ++ ++/* cf. aufs_rmdir() */ ++static int au_ren_del_whtmp(struct au_ren_args *a) ++{ ++ int err; ++ struct inode *dir; ++ ++ dir = a->dst_dir; ++ SiMustAnyLock(dir->i_sb); ++ if (!au_nhash_test_longer_wh(&a->whlist, a->btgt, ++ au_sbi(dir->i_sb)->si_dirwh) ++ || au_test_fs_remote(a->h_dst->d_sb)) { ++ err = au_whtmp_rmdir(dir, a->btgt, a->h_dst, &a->whlist); ++ if (unlikely(err)) ++ pr_warn("failed removing whtmp dir %pd (%d), " ++ "ignored.\n", a->h_dst, err); ++ } else { ++ au_nhash_wh_free(&a->thargs->whlist); ++ a->thargs->whlist = a->whlist; ++ a->whlist.nh_num = 0; ++ au_whtmp_kick_rmdir(dir, a->btgt, a->h_dst, a->thargs); ++ dput(a->h_dst); ++ a->thargs = NULL; ++ } ++ ++ return 0; ++} ++ ++/* make it 'opaque' dir. */ ++static int au_ren_diropq(struct au_ren_args *a) ++{ ++ int err; ++ struct dentry *diropq; ++ ++ err = 0; ++ a->src_bdiropq = au_dbdiropq(a->src_dentry); ++ a->src_hinode = au_hi(a->src_inode, a->btgt); ++ au_hn_imtx_lock_nested(a->src_hinode, AuLsc_I_CHILD); ++ diropq = au_diropq_create(a->src_dentry, a->btgt); ++ au_hn_imtx_unlock(a->src_hinode); ++ if (IS_ERR(diropq)) ++ err = PTR_ERR(diropq); ++ else ++ dput(diropq); ++ ++ return err; ++} ++ ++static int do_rename(struct au_ren_args *a) ++{ ++ int err; ++ struct dentry *d, *h_d; ++ ++ /* prepare workqueue args for asynchronous rmdir */ ++ h_d = a->dst_h_dentry; ++ if (au_ftest_ren(a->flags, ISDIR) && h_d->d_inode) { ++ err = -ENOMEM; ++ a->thargs = au_whtmp_rmdir_alloc(a->src_dentry->d_sb, GFP_NOFS); ++ if (unlikely(!a->thargs)) ++ goto out; ++ a->h_dst = dget(h_d); ++ } ++ ++ /* create whiteout for src_dentry */ ++ if (au_ftest_ren(a->flags, WHSRC)) { ++ a->src_bwh = au_dbwh(a->src_dentry); ++ AuDebugOn(a->src_bwh >= 0); ++ a->src_wh_dentry ++ = au_wh_create(a->src_dentry, a->btgt, a->src_h_parent); ++ err = PTR_ERR(a->src_wh_dentry); ++ if (IS_ERR(a->src_wh_dentry)) ++ goto out_thargs; ++ } ++ ++ /* lookup whiteout for dentry */ ++ if (au_ftest_ren(a->flags, WHDST)) { ++ h_d = au_wh_lkup(a->dst_h_parent, &a->dst_dentry->d_name, ++ a->br); ++ err = PTR_ERR(h_d); ++ if (IS_ERR(h_d)) ++ goto out_whsrc; ++ if (!h_d->d_inode) ++ dput(h_d); ++ else ++ a->dst_wh_dentry = h_d; ++ } ++ ++ /* rename dentry to tmpwh */ ++ if (a->thargs) { ++ err = au_whtmp_ren(a->dst_h_dentry, a->br); ++ if (unlikely(err)) ++ goto out_whdst; ++ ++ d = a->dst_dentry; ++ au_set_h_dptr(d, a->btgt, NULL); ++ err = au_lkup_neg(d, a->btgt, /*wh*/0); ++ if (unlikely(err)) ++ goto out_whtmp; ++ a->dst_h_dentry = au_h_dptr(d, a->btgt); ++ } ++ ++ BUG_ON(a->dst_h_dentry->d_inode && a->src_bstart != a->btgt); ++ ++ /* rename by vfs_rename or cpup */ ++ d = a->dst_dentry; ++ if (au_ftest_ren(a->flags, ISDIR) ++ && (a->dst_wh_dentry ++ || au_dbdiropq(d) == a->btgt ++ /* hide the lower to keep xino */ ++ || a->btgt < au_dbend(d) ++ || au_opt_test(au_mntflags(d->d_sb), ALWAYS_DIROPQ))) ++ au_fset_ren(a->flags, DIROPQ); ++ err = au_ren_or_cpup(a); ++ if (unlikely(err)) ++ /* leave the copied-up one */ ++ goto out_whtmp; ++ ++ /* make dir opaque */ ++ if (au_ftest_ren(a->flags, DIROPQ)) { ++ err = au_ren_diropq(a); ++ if (unlikely(err)) ++ goto out_rename; ++ } ++ ++ /* update target timestamps */ ++ AuDebugOn(au_dbstart(a->src_dentry) != a->btgt); ++ a->h_path.dentry = au_h_dptr(a->src_dentry, a->btgt); ++ vfsub_update_h_iattr(&a->h_path, /*did*/NULL); /*ignore*/ ++ a->src_inode->i_ctime = a->h_path.dentry->d_inode->i_ctime; ++ ++ /* remove whiteout for dentry */ ++ if (a->dst_wh_dentry) { ++ a->h_path.dentry = a->dst_wh_dentry; ++ err = au_wh_unlink_dentry(a->dst_h_dir, &a->h_path, ++ a->dst_dentry); ++ if (unlikely(err)) ++ goto out_diropq; ++ } ++ ++ /* remove whtmp */ ++ if (a->thargs) ++ au_ren_del_whtmp(a); /* ignore this error */ ++ ++ au_fhsm_wrote(a->src_dentry->d_sb, a->btgt, /*force*/0); ++ err = 0; ++ goto out_success; ++ ++out_diropq: ++ if (au_ftest_ren(a->flags, DIROPQ)) ++ au_ren_rev_diropq(err, a); ++out_rename: ++ au_ren_rev_rename(err, a); ++ dput(a->h_dst); ++out_whtmp: ++ if (a->thargs) ++ au_ren_rev_whtmp(err, a); ++out_whdst: ++ dput(a->dst_wh_dentry); ++ a->dst_wh_dentry = NULL; ++out_whsrc: ++ if (a->src_wh_dentry) ++ au_ren_rev_whsrc(err, a); ++out_success: ++ dput(a->src_wh_dentry); ++ dput(a->dst_wh_dentry); ++out_thargs: ++ if (a->thargs) { ++ dput(a->h_dst); ++ au_whtmp_rmdir_free(a->thargs); ++ a->thargs = NULL; ++ } ++out: ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* ++ * test if @dentry dir can be rename destination or not. ++ * success means, it is a logically empty dir. ++ */ ++static int may_rename_dstdir(struct dentry *dentry, struct au_nhash *whlist) ++{ ++ return au_test_empty(dentry, whlist); ++} ++ ++/* ++ * test if @dentry dir can be rename source or not. ++ * if it can, return 0 and @children is filled. ++ * success means, ++ * - it is a logically empty dir. ++ * - or, it exists on writable branch and has no children including whiteouts ++ * on the lower branch. ++ */ ++static int may_rename_srcdir(struct dentry *dentry, aufs_bindex_t btgt) ++{ ++ int err; ++ unsigned int rdhash; ++ aufs_bindex_t bstart; ++ ++ bstart = au_dbstart(dentry); ++ if (bstart != btgt) { ++ struct au_nhash whlist; ++ ++ SiMustAnyLock(dentry->d_sb); ++ rdhash = au_sbi(dentry->d_sb)->si_rdhash; ++ if (!rdhash) ++ rdhash = au_rdhash_est(au_dir_size(/*file*/NULL, ++ dentry)); ++ err = au_nhash_alloc(&whlist, rdhash, GFP_NOFS); ++ if (unlikely(err)) ++ goto out; ++ err = au_test_empty(dentry, &whlist); ++ au_nhash_wh_free(&whlist); ++ goto out; ++ } ++ ++ if (bstart == au_dbtaildir(dentry)) ++ return 0; /* success */ ++ ++ err = au_test_empty_lower(dentry); ++ ++out: ++ if (err == -ENOTEMPTY) { ++ AuWarn1("renaming dir who has child(ren) on multiple branches," ++ " is not supported\n"); ++ err = -EXDEV; ++ } ++ return err; ++} ++ ++/* side effect: sets whlist and h_dentry */ ++static int au_ren_may_dir(struct au_ren_args *a) ++{ ++ int err; ++ unsigned int rdhash; ++ struct dentry *d; ++ ++ d = a->dst_dentry; ++ SiMustAnyLock(d->d_sb); ++ ++ err = 0; ++ if (au_ftest_ren(a->flags, ISDIR) && a->dst_inode) { ++ rdhash = au_sbi(d->d_sb)->si_rdhash; ++ if (!rdhash) ++ rdhash = au_rdhash_est(au_dir_size(/*file*/NULL, d)); ++ err = au_nhash_alloc(&a->whlist, rdhash, GFP_NOFS); ++ if (unlikely(err)) ++ goto out; ++ ++ au_set_dbstart(d, a->dst_bstart); ++ err = may_rename_dstdir(d, &a->whlist); ++ au_set_dbstart(d, a->btgt); ++ } ++ a->dst_h_dentry = au_h_dptr(d, au_dbstart(d)); ++ if (unlikely(err)) ++ goto out; ++ ++ d = a->src_dentry; ++ a->src_h_dentry = au_h_dptr(d, au_dbstart(d)); ++ if (au_ftest_ren(a->flags, ISDIR)) { ++ err = may_rename_srcdir(d, a->btgt); ++ if (unlikely(err)) { ++ au_nhash_wh_free(&a->whlist); ++ a->whlist.nh_num = 0; ++ } ++ } ++out: ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* ++ * simple tests for rename. ++ * following the checks in vfs, plus the parent-child relationship. ++ */ ++static int au_may_ren(struct au_ren_args *a) ++{ ++ int err, isdir; ++ struct inode *h_inode; ++ ++ if (a->src_bstart == a->btgt) { ++ err = au_may_del(a->src_dentry, a->btgt, a->src_h_parent, ++ au_ftest_ren(a->flags, ISDIR)); ++ if (unlikely(err)) ++ goto out; ++ err = -EINVAL; ++ if (unlikely(a->src_h_dentry == a->h_trap)) ++ goto out; ++ } ++ ++ err = 0; ++ if (a->dst_bstart != a->btgt) ++ goto out; ++ ++ err = -ENOTEMPTY; ++ if (unlikely(a->dst_h_dentry == a->h_trap)) ++ goto out; ++ ++ err = -EIO; ++ h_inode = a->dst_h_dentry->d_inode; ++ isdir = !!au_ftest_ren(a->flags, ISDIR); ++ if (!a->dst_dentry->d_inode) { ++ if (unlikely(h_inode)) ++ goto out; ++ err = au_may_add(a->dst_dentry, a->btgt, a->dst_h_parent, ++ isdir); ++ } else { ++ if (unlikely(!h_inode || !h_inode->i_nlink)) ++ goto out; ++ err = au_may_del(a->dst_dentry, a->btgt, a->dst_h_parent, ++ isdir); ++ if (unlikely(err)) ++ goto out; ++ } ++ ++out: ++ if (unlikely(err == -ENOENT || err == -EEXIST)) ++ err = -EIO; ++ AuTraceErr(err); ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* ++ * locking order ++ * (VFS) ++ * - src_dir and dir by lock_rename() ++ * - inode if exitsts ++ * (aufs) ++ * - lock all ++ * + src_dentry and dentry by aufs_read_and_write_lock2() which calls, ++ * + si_read_lock ++ * + di_write_lock2_child() ++ * + di_write_lock_child() ++ * + ii_write_lock_child() ++ * + di_write_lock_child2() ++ * + ii_write_lock_child2() ++ * + src_parent and parent ++ * + di_write_lock_parent() ++ * + ii_write_lock_parent() ++ * + di_write_lock_parent2() ++ * + ii_write_lock_parent2() ++ * + lower src_dir and dir by vfsub_lock_rename() ++ * + verify the every relationships between child and parent. if any ++ * of them failed, unlock all and return -EBUSY. ++ */ ++static void au_ren_unlock(struct au_ren_args *a) ++{ ++ vfsub_unlock_rename(a->src_h_parent, a->src_hdir, ++ a->dst_h_parent, a->dst_hdir); ++ if (au_ftest_ren(a->flags, MNT_WRITE)) ++ vfsub_mnt_drop_write(au_br_mnt(a->br)); ++} ++ ++static int au_ren_lock(struct au_ren_args *a) ++{ ++ int err; ++ unsigned int udba; ++ ++ err = 0; ++ a->src_h_parent = au_h_dptr(a->src_parent, a->btgt); ++ a->src_hdir = au_hi(a->src_dir, a->btgt); ++ a->dst_h_parent = au_h_dptr(a->dst_parent, a->btgt); ++ a->dst_hdir = au_hi(a->dst_dir, a->btgt); ++ ++ err = vfsub_mnt_want_write(au_br_mnt(a->br)); ++ if (unlikely(err)) ++ goto out; ++ au_fset_ren(a->flags, MNT_WRITE); ++ a->h_trap = vfsub_lock_rename(a->src_h_parent, a->src_hdir, ++ a->dst_h_parent, a->dst_hdir); ++ udba = au_opt_udba(a->src_dentry->d_sb); ++ if (unlikely(a->src_hdir->hi_inode != a->src_h_parent->d_inode ++ || a->dst_hdir->hi_inode != a->dst_h_parent->d_inode)) ++ err = au_busy_or_stale(); ++ if (!err && au_dbstart(a->src_dentry) == a->btgt) ++ err = au_h_verify(a->src_h_dentry, udba, ++ a->src_h_parent->d_inode, a->src_h_parent, ++ a->br); ++ if (!err && au_dbstart(a->dst_dentry) == a->btgt) ++ err = au_h_verify(a->dst_h_dentry, udba, ++ a->dst_h_parent->d_inode, a->dst_h_parent, ++ a->br); ++ if (!err) ++ goto out; /* success */ ++ ++ err = au_busy_or_stale(); ++ au_ren_unlock(a); ++ ++out: ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++static void au_ren_refresh_dir(struct au_ren_args *a) ++{ ++ struct inode *dir; ++ ++ dir = a->dst_dir; ++ dir->i_version++; ++ if (au_ftest_ren(a->flags, ISDIR)) { ++ /* is this updating defined in POSIX? */ ++ au_cpup_attr_timesizes(a->src_inode); ++ au_cpup_attr_nlink(dir, /*force*/1); ++ } ++ ++ au_dir_ts(dir, a->btgt); ++ ++ if (au_ftest_ren(a->flags, ISSAMEDIR)) ++ return; ++ ++ dir = a->src_dir; ++ dir->i_version++; ++ if (au_ftest_ren(a->flags, ISDIR)) ++ au_cpup_attr_nlink(dir, /*force*/1); ++ au_dir_ts(dir, a->btgt); ++} ++ ++static void au_ren_refresh(struct au_ren_args *a) ++{ ++ aufs_bindex_t bend, bindex; ++ struct dentry *d, *h_d; ++ struct inode *i, *h_i; ++ struct super_block *sb; ++ ++ d = a->dst_dentry; ++ d_drop(d); ++ if (a->h_dst) ++ /* already dget-ed by au_ren_or_cpup() */ ++ au_set_h_dptr(d, a->btgt, a->h_dst); ++ ++ i = a->dst_inode; ++ if (i) { ++ if (!au_ftest_ren(a->flags, ISDIR)) ++ vfsub_drop_nlink(i); ++ else { ++ vfsub_dead_dir(i); ++ au_cpup_attr_timesizes(i); ++ } ++ au_update_dbrange(d, /*do_put_zero*/1); ++ } else { ++ bend = a->btgt; ++ for (bindex = au_dbstart(d); bindex < bend; bindex++) ++ au_set_h_dptr(d, bindex, NULL); ++ bend = au_dbend(d); ++ for (bindex = a->btgt + 1; bindex <= bend; bindex++) ++ au_set_h_dptr(d, bindex, NULL); ++ au_update_dbrange(d, /*do_put_zero*/0); ++ } ++ ++ d = a->src_dentry; ++ au_set_dbwh(d, -1); ++ bend = au_dbend(d); ++ for (bindex = a->btgt + 1; bindex <= bend; bindex++) { ++ h_d = au_h_dptr(d, bindex); ++ if (h_d) ++ au_set_h_dptr(d, bindex, NULL); ++ } ++ au_set_dbend(d, a->btgt); ++ ++ sb = d->d_sb; ++ i = a->src_inode; ++ if (au_opt_test(au_mntflags(sb), PLINK) && au_plink_test(i)) ++ return; /* success */ ++ ++ bend = au_ibend(i); ++ for (bindex = a->btgt + 1; bindex <= bend; bindex++) { ++ h_i = au_h_iptr(i, bindex); ++ if (h_i) { ++ au_xino_write(sb, bindex, h_i->i_ino, /*ino*/0); ++ /* ignore this error */ ++ au_set_h_iptr(i, bindex, NULL, 0); ++ } ++ } ++ au_set_ibend(i, a->btgt); ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* mainly for link(2) and rename(2) */ ++int au_wbr(struct dentry *dentry, aufs_bindex_t btgt) ++{ ++ aufs_bindex_t bdiropq, bwh; ++ struct dentry *parent; ++ struct au_branch *br; ++ ++ parent = dentry->d_parent; ++ IMustLock(parent->d_inode); /* dir is locked */ ++ ++ bdiropq = au_dbdiropq(parent); ++ bwh = au_dbwh(dentry); ++ br = au_sbr(dentry->d_sb, btgt); ++ if (au_br_rdonly(br) ++ || (0 <= bdiropq && bdiropq < btgt) ++ || (0 <= bwh && bwh < btgt)) ++ btgt = -1; ++ ++ AuDbg("btgt %d\n", btgt); ++ return btgt; ++} ++ ++/* sets src_bstart, dst_bstart and btgt */ ++static int au_ren_wbr(struct au_ren_args *a) ++{ ++ int err; ++ struct au_wr_dir_args wr_dir_args = { ++ /* .force_btgt = -1, */ ++ .flags = AuWrDir_ADD_ENTRY ++ }; ++ ++ a->src_bstart = au_dbstart(a->src_dentry); ++ a->dst_bstart = au_dbstart(a->dst_dentry); ++ if (au_ftest_ren(a->flags, ISDIR)) ++ au_fset_wrdir(wr_dir_args.flags, ISDIR); ++ wr_dir_args.force_btgt = a->src_bstart; ++ if (a->dst_inode && a->dst_bstart < a->src_bstart) ++ wr_dir_args.force_btgt = a->dst_bstart; ++ wr_dir_args.force_btgt = au_wbr(a->dst_dentry, wr_dir_args.force_btgt); ++ err = au_wr_dir(a->dst_dentry, a->src_dentry, &wr_dir_args); ++ a->btgt = err; ++ ++ return err; ++} ++ ++static void au_ren_dt(struct au_ren_args *a) ++{ ++ a->h_path.dentry = a->src_h_parent; ++ au_dtime_store(a->src_dt + AuPARENT, a->src_parent, &a->h_path); ++ if (!au_ftest_ren(a->flags, ISSAMEDIR)) { ++ a->h_path.dentry = a->dst_h_parent; ++ au_dtime_store(a->dst_dt + AuPARENT, a->dst_parent, &a->h_path); ++ } ++ ++ au_fclr_ren(a->flags, DT_DSTDIR); ++ if (!au_ftest_ren(a->flags, ISDIR)) ++ return; ++ ++ a->h_path.dentry = a->src_h_dentry; ++ au_dtime_store(a->src_dt + AuCHILD, a->src_dentry, &a->h_path); ++ if (a->dst_h_dentry->d_inode) { ++ au_fset_ren(a->flags, DT_DSTDIR); ++ a->h_path.dentry = a->dst_h_dentry; ++ au_dtime_store(a->dst_dt + AuCHILD, a->dst_dentry, &a->h_path); ++ } ++} ++ ++static void au_ren_rev_dt(int err, struct au_ren_args *a) ++{ ++ struct dentry *h_d; ++ struct mutex *h_mtx; ++ ++ au_dtime_revert(a->src_dt + AuPARENT); ++ if (!au_ftest_ren(a->flags, ISSAMEDIR)) ++ au_dtime_revert(a->dst_dt + AuPARENT); ++ ++ if (au_ftest_ren(a->flags, ISDIR) && err != -EIO) { ++ h_d = a->src_dt[AuCHILD].dt_h_path.dentry; ++ h_mtx = &h_d->d_inode->i_mutex; ++ mutex_lock_nested(h_mtx, AuLsc_I_CHILD); ++ au_dtime_revert(a->src_dt + AuCHILD); ++ mutex_unlock(h_mtx); ++ ++ if (au_ftest_ren(a->flags, DT_DSTDIR)) { ++ h_d = a->dst_dt[AuCHILD].dt_h_path.dentry; ++ h_mtx = &h_d->d_inode->i_mutex; ++ mutex_lock_nested(h_mtx, AuLsc_I_CHILD); ++ au_dtime_revert(a->dst_dt + AuCHILD); ++ mutex_unlock(h_mtx); ++ } ++ } ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++int aufs_rename(struct inode *_src_dir, struct dentry *_src_dentry, ++ struct inode *_dst_dir, struct dentry *_dst_dentry) ++{ ++ int err, flags; ++ /* reduce stack space */ ++ struct au_ren_args *a; ++ ++ AuDbg("%pd, %pd\n", _src_dentry, _dst_dentry); ++ IMustLock(_src_dir); ++ IMustLock(_dst_dir); ++ ++ err = -ENOMEM; ++ BUILD_BUG_ON(sizeof(*a) > PAGE_SIZE); ++ a = kzalloc(sizeof(*a), GFP_NOFS); ++ if (unlikely(!a)) ++ goto out; ++ ++ a->src_dir = _src_dir; ++ a->src_dentry = _src_dentry; ++ a->src_inode = a->src_dentry->d_inode; ++ a->src_parent = a->src_dentry->d_parent; /* dir inode is locked */ ++ a->dst_dir = _dst_dir; ++ a->dst_dentry = _dst_dentry; ++ a->dst_inode = a->dst_dentry->d_inode; ++ a->dst_parent = a->dst_dentry->d_parent; /* dir inode is locked */ ++ if (a->dst_inode) { ++ IMustLock(a->dst_inode); ++ au_igrab(a->dst_inode); ++ } ++ ++ err = -ENOTDIR; ++ flags = AuLock_FLUSH | AuLock_NOPLM | AuLock_GEN; ++ if (d_is_dir(a->src_dentry)) { ++ au_fset_ren(a->flags, ISDIR); ++ if (unlikely(d_is_positive(a->dst_dentry) ++ && !d_is_dir(a->dst_dentry))) ++ goto out_free; ++ flags |= AuLock_DIRS; ++ } ++ err = aufs_read_and_write_lock2(a->dst_dentry, a->src_dentry, flags); ++ if (unlikely(err)) ++ goto out_free; ++ ++ err = au_d_hashed_positive(a->src_dentry); ++ if (unlikely(err)) ++ goto out_unlock; ++ err = -ENOENT; ++ if (a->dst_inode) { ++ /* ++ * If it is a dir, VFS unhash dst_dentry before this ++ * function. It means we cannot rely upon d_unhashed(). ++ */ ++ if (unlikely(!a->dst_inode->i_nlink)) ++ goto out_unlock; ++ if (!S_ISDIR(a->dst_inode->i_mode)) { ++ err = au_d_hashed_positive(a->dst_dentry); ++ if (unlikely(err)) ++ goto out_unlock; ++ } else if (unlikely(IS_DEADDIR(a->dst_inode))) ++ goto out_unlock; ++ } else if (unlikely(d_unhashed(a->dst_dentry))) ++ goto out_unlock; ++ ++ /* ++ * is it possible? ++ * yes, it happened (in linux-3.3-rcN) but I don't know why. ++ * there may exist a problem somewhere else. ++ */ ++ err = -EINVAL; ++ if (unlikely(a->dst_parent->d_inode == a->src_dentry->d_inode)) ++ goto out_unlock; ++ ++ au_fset_ren(a->flags, ISSAMEDIR); /* temporary */ ++ di_write_lock_parent(a->dst_parent); ++ ++ /* which branch we process */ ++ err = au_ren_wbr(a); ++ if (unlikely(err < 0)) ++ goto out_parent; ++ a->br = au_sbr(a->dst_dentry->d_sb, a->btgt); ++ a->h_path.mnt = au_br_mnt(a->br); ++ ++ /* are they available to be renamed */ ++ err = au_ren_may_dir(a); ++ if (unlikely(err)) ++ goto out_children; ++ ++ /* prepare the writable parent dir on the same branch */ ++ if (a->dst_bstart == a->btgt) { ++ au_fset_ren(a->flags, WHDST); ++ } else { ++ err = au_cpup_dirs(a->dst_dentry, a->btgt); ++ if (unlikely(err)) ++ goto out_children; ++ } ++ ++ if (a->src_dir != a->dst_dir) { ++ /* ++ * this temporary unlock is safe, ++ * because both dir->i_mutex are locked. ++ */ ++ di_write_unlock(a->dst_parent); ++ di_write_lock_parent(a->src_parent); ++ err = au_wr_dir_need_wh(a->src_dentry, ++ au_ftest_ren(a->flags, ISDIR), ++ &a->btgt); ++ di_write_unlock(a->src_parent); ++ di_write_lock2_parent(a->src_parent, a->dst_parent, /*isdir*/1); ++ au_fclr_ren(a->flags, ISSAMEDIR); ++ } else ++ err = au_wr_dir_need_wh(a->src_dentry, ++ au_ftest_ren(a->flags, ISDIR), ++ &a->btgt); ++ if (unlikely(err < 0)) ++ goto out_children; ++ if (err) ++ au_fset_ren(a->flags, WHSRC); ++ ++ /* cpup src */ ++ if (a->src_bstart != a->btgt) { ++ struct au_pin pin; ++ ++ err = au_pin(&pin, a->src_dentry, a->btgt, ++ au_opt_udba(a->src_dentry->d_sb), ++ AuPin_DI_LOCKED | AuPin_MNT_WRITE); ++ if (!err) { ++ struct au_cp_generic cpg = { ++ .dentry = a->src_dentry, ++ .bdst = a->btgt, ++ .bsrc = a->src_bstart, ++ .len = -1, ++ .pin = &pin, ++ .flags = AuCpup_DTIME | AuCpup_HOPEN ++ }; ++ AuDebugOn(au_dbstart(a->src_dentry) != a->src_bstart); ++ err = au_sio_cpup_simple(&cpg); ++ au_unpin(&pin); ++ } ++ if (unlikely(err)) ++ goto out_children; ++ a->src_bstart = a->btgt; ++ a->src_h_dentry = au_h_dptr(a->src_dentry, a->btgt); ++ au_fset_ren(a->flags, WHSRC); ++ } ++ ++ /* lock them all */ ++ err = au_ren_lock(a); ++ if (unlikely(err)) ++ /* leave the copied-up one */ ++ goto out_children; ++ ++ if (!au_opt_test(au_mntflags(a->dst_dir->i_sb), UDBA_NONE)) ++ err = au_may_ren(a); ++ else if (unlikely(a->dst_dentry->d_name.len > AUFS_MAX_NAMELEN)) ++ err = -ENAMETOOLONG; ++ if (unlikely(err)) ++ goto out_hdir; ++ ++ /* store timestamps to be revertible */ ++ au_ren_dt(a); ++ ++ /* here we go */ ++ err = do_rename(a); ++ if (unlikely(err)) ++ goto out_dt; ++ ++ /* update dir attributes */ ++ au_ren_refresh_dir(a); ++ ++ /* dput/iput all lower dentries */ ++ au_ren_refresh(a); ++ ++ goto out_hdir; /* success */ ++ ++out_dt: ++ au_ren_rev_dt(err, a); ++out_hdir: ++ au_ren_unlock(a); ++out_children: ++ au_nhash_wh_free(&a->whlist); ++ if (err && a->dst_inode && a->dst_bstart != a->btgt) { ++ AuDbg("bstart %d, btgt %d\n", a->dst_bstart, a->btgt); ++ au_set_h_dptr(a->dst_dentry, a->btgt, NULL); ++ au_set_dbstart(a->dst_dentry, a->dst_bstart); ++ } ++out_parent: ++ if (!err) ++ d_move(a->src_dentry, a->dst_dentry); ++ else { ++ au_update_dbstart(a->dst_dentry); ++ if (!a->dst_inode) ++ d_drop(a->dst_dentry); ++ } ++ if (au_ftest_ren(a->flags, ISSAMEDIR)) ++ di_write_unlock(a->dst_parent); ++ else ++ di_write_unlock2(a->src_parent, a->dst_parent); ++out_unlock: ++ aufs_read_and_write_unlock2(a->dst_dentry, a->src_dentry); ++out_free: ++ iput(a->dst_inode); ++ if (a->thargs) ++ au_whtmp_rmdir_free(a->thargs); ++ kfree(a); ++out: ++ AuTraceErr(err); ++ return err; ++} +diff --git a/fs/aufs/iinfo.c b/fs/aufs/iinfo.c +new file mode 100644 +index 0000000..f889aba +--- /dev/null ++++ b/fs/aufs/iinfo.c +@@ -0,0 +1,277 @@ ++/* ++ * Copyright (C) 2005-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * inode private data ++ */ ++ ++#include "aufs.h" ++ ++struct inode *au_h_iptr(struct inode *inode, aufs_bindex_t bindex) ++{ ++ struct inode *h_inode; ++ ++ IiMustAnyLock(inode); ++ ++ h_inode = au_ii(inode)->ii_hinode[0 + bindex].hi_inode; ++ AuDebugOn(h_inode && atomic_read(&h_inode->i_count) <= 0); ++ return h_inode; ++} ++ ++/* todo: hard/soft set? */ ++void au_hiput(struct au_hinode *hinode) ++{ ++ au_hn_free(hinode); ++ dput(hinode->hi_whdentry); ++ iput(hinode->hi_inode); ++} ++ ++unsigned int au_hi_flags(struct inode *inode, int isdir) ++{ ++ unsigned int flags; ++ const unsigned int mnt_flags = au_mntflags(inode->i_sb); ++ ++ flags = 0; ++ if (au_opt_test(mnt_flags, XINO)) ++ au_fset_hi(flags, XINO); ++ if (isdir && au_opt_test(mnt_flags, UDBA_HNOTIFY)) ++ au_fset_hi(flags, HNOTIFY); ++ return flags; ++} ++ ++void au_set_h_iptr(struct inode *inode, aufs_bindex_t bindex, ++ struct inode *h_inode, unsigned int flags) ++{ ++ struct au_hinode *hinode; ++ struct inode *hi; ++ struct au_iinfo *iinfo = au_ii(inode); ++ ++ IiMustWriteLock(inode); ++ ++ hinode = iinfo->ii_hinode + bindex; ++ hi = hinode->hi_inode; ++ AuDebugOn(h_inode && atomic_read(&h_inode->i_count) <= 0); ++ ++ if (hi) ++ au_hiput(hinode); ++ hinode->hi_inode = h_inode; ++ if (h_inode) { ++ int err; ++ struct super_block *sb = inode->i_sb; ++ struct au_branch *br; ++ ++ AuDebugOn(inode->i_mode ++ && (h_inode->i_mode & S_IFMT) ++ != (inode->i_mode & S_IFMT)); ++ if (bindex == iinfo->ii_bstart) ++ au_cpup_igen(inode, h_inode); ++ br = au_sbr(sb, bindex); ++ hinode->hi_id = br->br_id; ++ if (au_ftest_hi(flags, XINO)) { ++ err = au_xino_write(sb, bindex, h_inode->i_ino, ++ inode->i_ino); ++ if (unlikely(err)) ++ AuIOErr1("failed au_xino_write() %d\n", err); ++ } ++ ++ if (au_ftest_hi(flags, HNOTIFY) ++ && au_br_hnotifyable(br->br_perm)) { ++ err = au_hn_alloc(hinode, inode); ++ if (unlikely(err)) ++ AuIOErr1("au_hn_alloc() %d\n", err); ++ } ++ } ++} ++ ++void au_set_hi_wh(struct inode *inode, aufs_bindex_t bindex, ++ struct dentry *h_wh) ++{ ++ struct au_hinode *hinode; ++ ++ IiMustWriteLock(inode); ++ ++ hinode = au_ii(inode)->ii_hinode + bindex; ++ AuDebugOn(hinode->hi_whdentry); ++ hinode->hi_whdentry = h_wh; ++} ++ ++void au_update_iigen(struct inode *inode, int half) ++{ ++ struct au_iinfo *iinfo; ++ struct au_iigen *iigen; ++ unsigned int sigen; ++ ++ sigen = au_sigen(inode->i_sb); ++ iinfo = au_ii(inode); ++ iigen = &iinfo->ii_generation; ++ spin_lock(&iigen->ig_spin); ++ iigen->ig_generation = sigen; ++ if (half) ++ au_ig_fset(iigen->ig_flags, HALF_REFRESHED); ++ else ++ au_ig_fclr(iigen->ig_flags, HALF_REFRESHED); ++ spin_unlock(&iigen->ig_spin); ++} ++ ++/* it may be called at remount time, too */ ++void au_update_ibrange(struct inode *inode, int do_put_zero) ++{ ++ struct au_iinfo *iinfo; ++ aufs_bindex_t bindex, bend; ++ ++ iinfo = au_ii(inode); ++ if (!iinfo) ++ return; ++ ++ IiMustWriteLock(inode); ++ ++ if (do_put_zero && iinfo->ii_bstart >= 0) { ++ for (bindex = iinfo->ii_bstart; bindex <= iinfo->ii_bend; ++ bindex++) { ++ struct inode *h_i; ++ ++ h_i = iinfo->ii_hinode[0 + bindex].hi_inode; ++ if (h_i ++ && !h_i->i_nlink ++ && !(h_i->i_state & I_LINKABLE)) ++ au_set_h_iptr(inode, bindex, NULL, 0); ++ } ++ } ++ ++ iinfo->ii_bstart = -1; ++ iinfo->ii_bend = -1; ++ bend = au_sbend(inode->i_sb); ++ for (bindex = 0; bindex <= bend; bindex++) ++ if (iinfo->ii_hinode[0 + bindex].hi_inode) { ++ iinfo->ii_bstart = bindex; ++ break; ++ } ++ if (iinfo->ii_bstart >= 0) ++ for (bindex = bend; bindex >= iinfo->ii_bstart; bindex--) ++ if (iinfo->ii_hinode[0 + bindex].hi_inode) { ++ iinfo->ii_bend = bindex; ++ break; ++ } ++ AuDebugOn(iinfo->ii_bstart > iinfo->ii_bend); ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++void au_icntnr_init_once(void *_c) ++{ ++ struct au_icntnr *c = _c; ++ struct au_iinfo *iinfo = &c->iinfo; ++ static struct lock_class_key aufs_ii; ++ ++ spin_lock_init(&iinfo->ii_generation.ig_spin); ++ au_rw_init(&iinfo->ii_rwsem); ++ au_rw_class(&iinfo->ii_rwsem, &aufs_ii); ++ inode_init_once(&c->vfs_inode); ++} ++ ++int au_iinfo_init(struct inode *inode) ++{ ++ struct au_iinfo *iinfo; ++ struct super_block *sb; ++ int nbr, i; ++ ++ sb = inode->i_sb; ++ iinfo = &(container_of(inode, struct au_icntnr, vfs_inode)->iinfo); ++ nbr = au_sbend(sb) + 1; ++ if (unlikely(nbr <= 0)) ++ nbr = 1; ++ iinfo->ii_hinode = kcalloc(nbr, sizeof(*iinfo->ii_hinode), GFP_NOFS); ++ if (iinfo->ii_hinode) { ++ au_ninodes_inc(sb); ++ for (i = 0; i < nbr; i++) ++ iinfo->ii_hinode[i].hi_id = -1; ++ ++ iinfo->ii_generation.ig_generation = au_sigen(sb); ++ iinfo->ii_bstart = -1; ++ iinfo->ii_bend = -1; ++ iinfo->ii_vdir = NULL; ++ return 0; ++ } ++ return -ENOMEM; ++} ++ ++int au_ii_realloc(struct au_iinfo *iinfo, int nbr) ++{ ++ int err, sz; ++ struct au_hinode *hip; ++ ++ AuRwMustWriteLock(&iinfo->ii_rwsem); ++ ++ err = -ENOMEM; ++ sz = sizeof(*hip) * (iinfo->ii_bend + 1); ++ if (!sz) ++ sz = sizeof(*hip); ++ hip = au_kzrealloc(iinfo->ii_hinode, sz, sizeof(*hip) * nbr, GFP_NOFS); ++ if (hip) { ++ iinfo->ii_hinode = hip; ++ err = 0; ++ } ++ ++ return err; ++} ++ ++void au_iinfo_fin(struct inode *inode) ++{ ++ struct au_iinfo *iinfo; ++ struct au_hinode *hi; ++ struct super_block *sb; ++ aufs_bindex_t bindex, bend; ++ const unsigned char unlinked = !inode->i_nlink; ++ ++ iinfo = au_ii(inode); ++ /* bad_inode case */ ++ if (!iinfo) ++ return; ++ ++ sb = inode->i_sb; ++ au_ninodes_dec(sb); ++ if (si_pid_test(sb)) ++ au_xino_delete_inode(inode, unlinked); ++ else { ++ /* ++ * it is safe to hide the dependency between sbinfo and ++ * sb->s_umount. ++ */ ++ lockdep_off(); ++ si_noflush_read_lock(sb); ++ au_xino_delete_inode(inode, unlinked); ++ si_read_unlock(sb); ++ lockdep_on(); ++ } ++ ++ if (iinfo->ii_vdir) ++ au_vdir_free(iinfo->ii_vdir); ++ ++ bindex = iinfo->ii_bstart; ++ if (bindex >= 0) { ++ hi = iinfo->ii_hinode + bindex; ++ bend = iinfo->ii_bend; ++ while (bindex++ <= bend) { ++ if (hi->hi_inode) ++ au_hiput(hi); ++ hi++; ++ } ++ } ++ kfree(iinfo->ii_hinode); ++ iinfo->ii_hinode = NULL; ++ AuRwDestroy(&iinfo->ii_rwsem); ++} +diff --git a/fs/aufs/inode.c b/fs/aufs/inode.c +new file mode 100644 +index 0000000..75ec2e5 +--- /dev/null ++++ b/fs/aufs/inode.c +@@ -0,0 +1,522 @@ ++/* ++ * Copyright (C) 2005-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * inode functions ++ */ ++ ++#include "aufs.h" ++ ++struct inode *au_igrab(struct inode *inode) ++{ ++ if (inode) { ++ AuDebugOn(!atomic_read(&inode->i_count)); ++ ihold(inode); ++ } ++ return inode; ++} ++ ++static void au_refresh_hinode_attr(struct inode *inode, int do_version) ++{ ++ au_cpup_attr_all(inode, /*force*/0); ++ au_update_iigen(inode, /*half*/1); ++ if (do_version) ++ inode->i_version++; ++} ++ ++static int au_ii_refresh(struct inode *inode, int *update) ++{ ++ int err, e; ++ umode_t type; ++ aufs_bindex_t bindex, new_bindex; ++ struct super_block *sb; ++ struct au_iinfo *iinfo; ++ struct au_hinode *p, *q, tmp; ++ ++ IiMustWriteLock(inode); ++ ++ *update = 0; ++ sb = inode->i_sb; ++ type = inode->i_mode & S_IFMT; ++ iinfo = au_ii(inode); ++ err = au_ii_realloc(iinfo, au_sbend(sb) + 1); ++ if (unlikely(err)) ++ goto out; ++ ++ AuDebugOn(iinfo->ii_bstart < 0); ++ p = iinfo->ii_hinode + iinfo->ii_bstart; ++ for (bindex = iinfo->ii_bstart; bindex <= iinfo->ii_bend; ++ bindex++, p++) { ++ if (!p->hi_inode) ++ continue; ++ ++ AuDebugOn(type != (p->hi_inode->i_mode & S_IFMT)); ++ new_bindex = au_br_index(sb, p->hi_id); ++ if (new_bindex == bindex) ++ continue; ++ ++ if (new_bindex < 0) { ++ *update = 1; ++ au_hiput(p); ++ p->hi_inode = NULL; ++ continue; ++ } ++ ++ if (new_bindex < iinfo->ii_bstart) ++ iinfo->ii_bstart = new_bindex; ++ if (iinfo->ii_bend < new_bindex) ++ iinfo->ii_bend = new_bindex; ++ /* swap two lower inode, and loop again */ ++ q = iinfo->ii_hinode + new_bindex; ++ tmp = *q; ++ *q = *p; ++ *p = tmp; ++ if (tmp.hi_inode) { ++ bindex--; ++ p--; ++ } ++ } ++ au_update_ibrange(inode, /*do_put_zero*/0); ++ e = au_dy_irefresh(inode); ++ if (unlikely(e && !err)) ++ err = e; ++ ++out: ++ AuTraceErr(err); ++ return err; ++} ++ ++void au_refresh_iop(struct inode *inode, int force_getattr) ++{ ++ int type; ++ struct au_sbinfo *sbi = au_sbi(inode->i_sb); ++ const struct inode_operations *iop ++ = force_getattr ? aufs_iop : sbi->si_iop_array; ++ ++ if (inode->i_op == iop) ++ return; ++ ++ switch (inode->i_mode & S_IFMT) { ++ case S_IFDIR: ++ type = AuIop_DIR; ++ break; ++ case S_IFLNK: ++ type = AuIop_SYMLINK; ++ break; ++ default: ++ type = AuIop_OTHER; ++ break; ++ } ++ ++ inode->i_op = iop + type; ++ /* unnecessary smp_wmb() */ ++} ++ ++int au_refresh_hinode_self(struct inode *inode) ++{ ++ int err, update; ++ ++ err = au_ii_refresh(inode, &update); ++ if (!err) ++ au_refresh_hinode_attr(inode, update && S_ISDIR(inode->i_mode)); ++ ++ AuTraceErr(err); ++ return err; ++} ++ ++int au_refresh_hinode(struct inode *inode, struct dentry *dentry) ++{ ++ int err, e, update; ++ unsigned int flags; ++ umode_t mode; ++ aufs_bindex_t bindex, bend; ++ unsigned char isdir; ++ struct au_hinode *p; ++ struct au_iinfo *iinfo; ++ ++ err = au_ii_refresh(inode, &update); ++ if (unlikely(err)) ++ goto out; ++ ++ update = 0; ++ iinfo = au_ii(inode); ++ p = iinfo->ii_hinode + iinfo->ii_bstart; ++ mode = (inode->i_mode & S_IFMT); ++ isdir = S_ISDIR(mode); ++ flags = au_hi_flags(inode, isdir); ++ bend = au_dbend(dentry); ++ for (bindex = au_dbstart(dentry); bindex <= bend; bindex++) { ++ struct inode *h_i; ++ struct dentry *h_d; ++ ++ h_d = au_h_dptr(dentry, bindex); ++ if (!h_d || !h_d->d_inode) ++ continue; ++ ++ AuDebugOn(mode != (h_d->d_inode->i_mode & S_IFMT)); ++ if (iinfo->ii_bstart <= bindex && bindex <= iinfo->ii_bend) { ++ h_i = au_h_iptr(inode, bindex); ++ if (h_i) { ++ if (h_i == h_d->d_inode) ++ continue; ++ err = -EIO; ++ break; ++ } ++ } ++ if (bindex < iinfo->ii_bstart) ++ iinfo->ii_bstart = bindex; ++ if (iinfo->ii_bend < bindex) ++ iinfo->ii_bend = bindex; ++ au_set_h_iptr(inode, bindex, au_igrab(h_d->d_inode), flags); ++ update = 1; ++ } ++ au_update_ibrange(inode, /*do_put_zero*/0); ++ e = au_dy_irefresh(inode); ++ if (unlikely(e && !err)) ++ err = e; ++ if (!err) ++ au_refresh_hinode_attr(inode, update && isdir); ++ ++out: ++ AuTraceErr(err); ++ return err; ++} ++ ++static int set_inode(struct inode *inode, struct dentry *dentry) ++{ ++ int err; ++ unsigned int flags; ++ umode_t mode; ++ aufs_bindex_t bindex, bstart, btail; ++ unsigned char isdir; ++ struct dentry *h_dentry; ++ struct inode *h_inode; ++ struct au_iinfo *iinfo; ++ struct inode_operations *iop; ++ ++ IiMustWriteLock(inode); ++ ++ err = 0; ++ isdir = 0; ++ iop = au_sbi(inode->i_sb)->si_iop_array; ++ bstart = au_dbstart(dentry); ++ h_inode = au_h_dptr(dentry, bstart)->d_inode; ++ mode = h_inode->i_mode; ++ switch (mode & S_IFMT) { ++ case S_IFREG: ++ btail = au_dbtail(dentry); ++ inode->i_op = iop + AuIop_OTHER; ++ inode->i_fop = &aufs_file_fop; ++ err = au_dy_iaop(inode, bstart, h_inode); ++ if (unlikely(err)) ++ goto out; ++ break; ++ case S_IFDIR: ++ isdir = 1; ++ btail = au_dbtaildir(dentry); ++ inode->i_op = iop + AuIop_DIR; ++ inode->i_fop = &aufs_dir_fop; ++ break; ++ case S_IFLNK: ++ btail = au_dbtail(dentry); ++ inode->i_op = iop + AuIop_SYMLINK; ++ break; ++ case S_IFBLK: ++ case S_IFCHR: ++ case S_IFIFO: ++ case S_IFSOCK: ++ btail = au_dbtail(dentry); ++ inode->i_op = iop + AuIop_OTHER; ++ init_special_inode(inode, mode, h_inode->i_rdev); ++ break; ++ default: ++ AuIOErr("Unknown file type 0%o\n", mode); ++ err = -EIO; ++ goto out; ++ } ++ ++ /* do not set hnotify for whiteouted dirs (SHWH mode) */ ++ flags = au_hi_flags(inode, isdir); ++ if (au_opt_test(au_mntflags(dentry->d_sb), SHWH) ++ && au_ftest_hi(flags, HNOTIFY) ++ && dentry->d_name.len > AUFS_WH_PFX_LEN ++ && !memcmp(dentry->d_name.name, AUFS_WH_PFX, AUFS_WH_PFX_LEN)) ++ au_fclr_hi(flags, HNOTIFY); ++ iinfo = au_ii(inode); ++ iinfo->ii_bstart = bstart; ++ iinfo->ii_bend = btail; ++ for (bindex = bstart; bindex <= btail; bindex++) { ++ h_dentry = au_h_dptr(dentry, bindex); ++ if (h_dentry) ++ au_set_h_iptr(inode, bindex, ++ au_igrab(h_dentry->d_inode), flags); ++ } ++ au_cpup_attr_all(inode, /*force*/1); ++ /* ++ * to force calling aufs_get_acl() every time, ++ * do not call cache_no_acl() for aufs inode. ++ */ ++ ++out: ++ return err; ++} ++ ++/* ++ * successful returns with iinfo write_locked ++ * minus: errno ++ * zero: success, matched ++ * plus: no error, but unmatched ++ */ ++static int reval_inode(struct inode *inode, struct dentry *dentry) ++{ ++ int err; ++ unsigned int gen, igflags; ++ aufs_bindex_t bindex, bend; ++ struct inode *h_inode, *h_dinode; ++ ++ /* ++ * before this function, if aufs got any iinfo lock, it must be only ++ * one, the parent dir. ++ * it can happen by UDBA and the obsoleted inode number. ++ */ ++ err = -EIO; ++ if (unlikely(inode->i_ino == parent_ino(dentry))) ++ goto out; ++ ++ err = 1; ++ ii_write_lock_new_child(inode); ++ h_dinode = au_h_dptr(dentry, au_dbstart(dentry))->d_inode; ++ bend = au_ibend(inode); ++ for (bindex = au_ibstart(inode); bindex <= bend; bindex++) { ++ h_inode = au_h_iptr(inode, bindex); ++ if (!h_inode || h_inode != h_dinode) ++ continue; ++ ++ err = 0; ++ gen = au_iigen(inode, &igflags); ++ if (gen == au_digen(dentry) ++ && !au_ig_ftest(igflags, HALF_REFRESHED)) ++ break; ++ ++ /* fully refresh inode using dentry */ ++ err = au_refresh_hinode(inode, dentry); ++ if (!err) ++ au_update_iigen(inode, /*half*/0); ++ break; ++ } ++ ++ if (unlikely(err)) ++ ii_write_unlock(inode); ++out: ++ return err; ++} ++ ++int au_ino(struct super_block *sb, aufs_bindex_t bindex, ino_t h_ino, ++ unsigned int d_type, ino_t *ino) ++{ ++ int err; ++ struct mutex *mtx; ++ ++ /* prevent hardlinked inode number from race condition */ ++ mtx = NULL; ++ if (d_type != DT_DIR) { ++ mtx = &au_sbr(sb, bindex)->br_xino.xi_nondir_mtx; ++ mutex_lock(mtx); ++ } ++ err = au_xino_read(sb, bindex, h_ino, ino); ++ if (unlikely(err)) ++ goto out; ++ ++ if (!*ino) { ++ err = -EIO; ++ *ino = au_xino_new_ino(sb); ++ if (unlikely(!*ino)) ++ goto out; ++ err = au_xino_write(sb, bindex, h_ino, *ino); ++ if (unlikely(err)) ++ goto out; ++ } ++ ++out: ++ if (mtx) ++ mutex_unlock(mtx); ++ return err; ++} ++ ++/* successful returns with iinfo write_locked */ ++/* todo: return with unlocked? */ ++struct inode *au_new_inode(struct dentry *dentry, int must_new) ++{ ++ struct inode *inode; ++ struct dentry *h_dentry; ++ struct super_block *sb; ++ struct mutex *mtx; ++ ino_t h_ino, ino; ++ int err; ++ aufs_bindex_t bstart; ++ ++ sb = dentry->d_sb; ++ bstart = au_dbstart(dentry); ++ h_dentry = au_h_dptr(dentry, bstart); ++ h_ino = h_dentry->d_inode->i_ino; ++ ++ /* ++ * stop 'race'-ing between hardlinks under different ++ * parents. ++ */ ++ mtx = NULL; ++ if (!d_is_dir(h_dentry)) ++ mtx = &au_sbr(sb, bstart)->br_xino.xi_nondir_mtx; ++ ++new_ino: ++ if (mtx) ++ mutex_lock(mtx); ++ err = au_xino_read(sb, bstart, h_ino, &ino); ++ inode = ERR_PTR(err); ++ if (unlikely(err)) ++ goto out; ++ ++ if (!ino) { ++ ino = au_xino_new_ino(sb); ++ if (unlikely(!ino)) { ++ inode = ERR_PTR(-EIO); ++ goto out; ++ } ++ } ++ ++ AuDbg("i%lu\n", (unsigned long)ino); ++ inode = au_iget_locked(sb, ino); ++ err = PTR_ERR(inode); ++ if (IS_ERR(inode)) ++ goto out; ++ ++ AuDbg("%lx, new %d\n", inode->i_state, !!(inode->i_state & I_NEW)); ++ if (inode->i_state & I_NEW) { ++ /* verbose coding for lock class name */ ++ if (unlikely(d_is_symlink(h_dentry))) ++ au_rw_class(&au_ii(inode)->ii_rwsem, ++ au_lc_key + AuLcSymlink_IIINFO); ++ else if (unlikely(d_is_dir(h_dentry))) ++ au_rw_class(&au_ii(inode)->ii_rwsem, ++ au_lc_key + AuLcDir_IIINFO); ++ else /* likely */ ++ au_rw_class(&au_ii(inode)->ii_rwsem, ++ au_lc_key + AuLcNonDir_IIINFO); ++ ++ ii_write_lock_new_child(inode); ++ err = set_inode(inode, dentry); ++ if (!err) { ++ unlock_new_inode(inode); ++ goto out; /* success */ ++ } ++ ++ /* ++ * iget_failed() calls iput(), but we need to call ++ * ii_write_unlock() after iget_failed(). so dirty hack for ++ * i_count. ++ */ ++ atomic_inc(&inode->i_count); ++ iget_failed(inode); ++ ii_write_unlock(inode); ++ au_xino_write(sb, bstart, h_ino, /*ino*/0); ++ /* ignore this error */ ++ goto out_iput; ++ } else if (!must_new && !IS_DEADDIR(inode) && inode->i_nlink) { ++ /* ++ * horrible race condition between lookup, readdir and copyup ++ * (or something). ++ */ ++ if (mtx) ++ mutex_unlock(mtx); ++ err = reval_inode(inode, dentry); ++ if (unlikely(err < 0)) { ++ mtx = NULL; ++ goto out_iput; ++ } ++ ++ if (!err) { ++ mtx = NULL; ++ goto out; /* success */ ++ } else if (mtx) ++ mutex_lock(mtx); ++ } ++ ++ if (unlikely(au_test_fs_unique_ino(h_dentry->d_inode))) ++ AuWarn1("Warning: Un-notified UDBA or repeatedly renamed dir," ++ " b%d, %s, %pd, hi%lu, i%lu.\n", ++ bstart, au_sbtype(h_dentry->d_sb), dentry, ++ (unsigned long)h_ino, (unsigned long)ino); ++ ino = 0; ++ err = au_xino_write(sb, bstart, h_ino, /*ino*/0); ++ if (!err) { ++ iput(inode); ++ if (mtx) ++ mutex_unlock(mtx); ++ goto new_ino; ++ } ++ ++out_iput: ++ iput(inode); ++ inode = ERR_PTR(err); ++out: ++ if (mtx) ++ mutex_unlock(mtx); ++ return inode; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++int au_test_ro(struct super_block *sb, aufs_bindex_t bindex, ++ struct inode *inode) ++{ ++ int err; ++ struct inode *hi; ++ ++ err = au_br_rdonly(au_sbr(sb, bindex)); ++ ++ /* pseudo-link after flushed may happen out of bounds */ ++ if (!err ++ && inode ++ && au_ibstart(inode) <= bindex ++ && bindex <= au_ibend(inode)) { ++ /* ++ * permission check is unnecessary since vfsub routine ++ * will be called later ++ */ ++ hi = au_h_iptr(inode, bindex); ++ if (hi) ++ err = IS_IMMUTABLE(hi) ? -EROFS : 0; ++ } ++ ++ return err; ++} ++ ++int au_test_h_perm(struct inode *h_inode, int mask) ++{ ++ if (uid_eq(current_fsuid(), GLOBAL_ROOT_UID)) ++ return 0; ++ return inode_permission(h_inode, mask); ++} ++ ++int au_test_h_perm_sio(struct inode *h_inode, int mask) ++{ ++ if (au_test_nfs(h_inode->i_sb) ++ && (mask & MAY_WRITE) ++ && S_ISDIR(h_inode->i_mode)) ++ mask |= MAY_READ; /* force permission check */ ++ return au_test_h_perm(h_inode, mask); ++} +diff --git a/fs/aufs/inode.h b/fs/aufs/inode.h +new file mode 100644 +index 0000000..49d53a2 +--- /dev/null ++++ b/fs/aufs/inode.h +@@ -0,0 +1,686 @@ ++/* ++ * Copyright (C) 2005-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * inode operations ++ */ ++ ++#ifndef __AUFS_INODE_H__ ++#define __AUFS_INODE_H__ ++ ++#ifdef __KERNEL__ ++ ++#include ++#include "rwsem.h" ++ ++struct vfsmount; ++ ++struct au_hnotify { ++#ifdef CONFIG_AUFS_HNOTIFY ++#ifdef CONFIG_AUFS_HFSNOTIFY ++ /* never use fsnotify_add_vfsmount_mark() */ ++ struct fsnotify_mark hn_mark; ++#endif ++ struct inode *hn_aufs_inode; /* no get/put */ ++#endif ++} ____cacheline_aligned_in_smp; ++ ++struct au_hinode { ++ struct inode *hi_inode; ++ aufs_bindex_t hi_id; ++#ifdef CONFIG_AUFS_HNOTIFY ++ struct au_hnotify *hi_notify; ++#endif ++ ++ /* reference to the copied-up whiteout with get/put */ ++ struct dentry *hi_whdentry; ++}; ++ ++/* ig_flags */ ++#define AuIG_HALF_REFRESHED 1 ++#define au_ig_ftest(flags, name) ((flags) & AuIG_##name) ++#define au_ig_fset(flags, name) \ ++ do { (flags) |= AuIG_##name; } while (0) ++#define au_ig_fclr(flags, name) \ ++ do { (flags) &= ~AuIG_##name; } while (0) ++ ++struct au_iigen { ++ spinlock_t ig_spin; ++ __u32 ig_generation, ig_flags; ++}; ++ ++struct au_vdir; ++struct au_iinfo { ++ struct au_iigen ii_generation; ++ struct super_block *ii_hsb1; /* no get/put */ ++ ++ struct au_rwsem ii_rwsem; ++ aufs_bindex_t ii_bstart, ii_bend; ++ __u32 ii_higen; ++ struct au_hinode *ii_hinode; ++ struct au_vdir *ii_vdir; ++}; ++ ++struct au_icntnr { ++ struct au_iinfo iinfo; ++ struct inode vfs_inode; ++ struct hlist_node plink; ++} ____cacheline_aligned_in_smp; ++ ++/* au_pin flags */ ++#define AuPin_DI_LOCKED 1 ++#define AuPin_MNT_WRITE (1 << 1) ++#define au_ftest_pin(flags, name) ((flags) & AuPin_##name) ++#define au_fset_pin(flags, name) \ ++ do { (flags) |= AuPin_##name; } while (0) ++#define au_fclr_pin(flags, name) \ ++ do { (flags) &= ~AuPin_##name; } while (0) ++ ++struct au_pin { ++ /* input */ ++ struct dentry *dentry; ++ unsigned int udba; ++ unsigned char lsc_di, lsc_hi, flags; ++ aufs_bindex_t bindex; ++ ++ /* output */ ++ struct dentry *parent; ++ struct au_hinode *hdir; ++ struct vfsmount *h_mnt; ++ ++ /* temporary unlock/relock for copyup */ ++ struct dentry *h_dentry, *h_parent; ++ struct au_branch *br; ++ struct task_struct *task; ++}; ++ ++void au_pin_hdir_unlock(struct au_pin *p); ++int au_pin_hdir_lock(struct au_pin *p); ++int au_pin_hdir_relock(struct au_pin *p); ++void au_pin_hdir_set_owner(struct au_pin *p, struct task_struct *task); ++void au_pin_hdir_acquire_nest(struct au_pin *p); ++void au_pin_hdir_release(struct au_pin *p); ++ ++/* ---------------------------------------------------------------------- */ ++ ++static inline struct au_iinfo *au_ii(struct inode *inode) ++{ ++ struct au_iinfo *iinfo; ++ ++ iinfo = &(container_of(inode, struct au_icntnr, vfs_inode)->iinfo); ++ if (iinfo->ii_hinode) ++ return iinfo; ++ return NULL; /* debugging bad_inode case */ ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* inode.c */ ++struct inode *au_igrab(struct inode *inode); ++void au_refresh_iop(struct inode *inode, int force_getattr); ++int au_refresh_hinode_self(struct inode *inode); ++int au_refresh_hinode(struct inode *inode, struct dentry *dentry); ++int au_ino(struct super_block *sb, aufs_bindex_t bindex, ino_t h_ino, ++ unsigned int d_type, ino_t *ino); ++struct inode *au_new_inode(struct dentry *dentry, int must_new); ++int au_test_ro(struct super_block *sb, aufs_bindex_t bindex, ++ struct inode *inode); ++int au_test_h_perm(struct inode *h_inode, int mask); ++int au_test_h_perm_sio(struct inode *h_inode, int mask); ++ ++static inline int au_wh_ino(struct super_block *sb, aufs_bindex_t bindex, ++ ino_t h_ino, unsigned int d_type, ino_t *ino) ++{ ++#ifdef CONFIG_AUFS_SHWH ++ return au_ino(sb, bindex, h_ino, d_type, ino); ++#else ++ return 0; ++#endif ++} ++ ++/* i_op.c */ ++enum { ++ AuIop_SYMLINK, ++ AuIop_DIR, ++ AuIop_OTHER, ++ AuIop_Last ++}; ++extern struct inode_operations aufs_iop[AuIop_Last], ++ aufs_iop_nogetattr[AuIop_Last]; ++ ++/* au_wr_dir flags */ ++#define AuWrDir_ADD_ENTRY 1 ++#define AuWrDir_ISDIR (1 << 1) ++#define AuWrDir_TMPFILE (1 << 2) ++#define au_ftest_wrdir(flags, name) ((flags) & AuWrDir_##name) ++#define au_fset_wrdir(flags, name) \ ++ do { (flags) |= AuWrDir_##name; } while (0) ++#define au_fclr_wrdir(flags, name) \ ++ do { (flags) &= ~AuWrDir_##name; } while (0) ++ ++struct au_wr_dir_args { ++ aufs_bindex_t force_btgt; ++ unsigned char flags; ++}; ++int au_wr_dir(struct dentry *dentry, struct dentry *src_dentry, ++ struct au_wr_dir_args *args); ++ ++struct dentry *au_pinned_h_parent(struct au_pin *pin); ++void au_pin_init(struct au_pin *pin, struct dentry *dentry, ++ aufs_bindex_t bindex, int lsc_di, int lsc_hi, ++ unsigned int udba, unsigned char flags); ++int au_pin(struct au_pin *pin, struct dentry *dentry, aufs_bindex_t bindex, ++ unsigned int udba, unsigned char flags) __must_check; ++int au_do_pin(struct au_pin *pin) __must_check; ++void au_unpin(struct au_pin *pin); ++int au_reval_for_attr(struct dentry *dentry, unsigned int sigen); ++ ++#define AuIcpup_DID_CPUP 1 ++#define au_ftest_icpup(flags, name) ((flags) & AuIcpup_##name) ++#define au_fset_icpup(flags, name) \ ++ do { (flags) |= AuIcpup_##name; } while (0) ++#define au_fclr_icpup(flags, name) \ ++ do { (flags) &= ~AuIcpup_##name; } while (0) ++ ++struct au_icpup_args { ++ unsigned char flags; ++ unsigned char pin_flags; ++ aufs_bindex_t btgt; ++ unsigned int udba; ++ struct au_pin pin; ++ struct path h_path; ++ struct inode *h_inode; ++}; ++ ++int au_pin_and_icpup(struct dentry *dentry, struct iattr *ia, ++ struct au_icpup_args *a); ++ ++int au_h_path_getattr(struct dentry *dentry, int force, struct path *h_path); ++ ++/* i_op_add.c */ ++int au_may_add(struct dentry *dentry, aufs_bindex_t bindex, ++ struct dentry *h_parent, int isdir); ++int aufs_mknod(struct inode *dir, struct dentry *dentry, umode_t mode, ++ dev_t dev); ++int aufs_symlink(struct inode *dir, struct dentry *dentry, const char *symname); ++int aufs_create(struct inode *dir, struct dentry *dentry, umode_t mode, ++ bool want_excl); ++struct vfsub_aopen_args; ++int au_aopen_or_create(struct inode *dir, struct dentry *dentry, ++ struct vfsub_aopen_args *args); ++int aufs_tmpfile(struct inode *dir, struct dentry *dentry, umode_t mode); ++int aufs_link(struct dentry *src_dentry, struct inode *dir, ++ struct dentry *dentry); ++int aufs_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode); ++ ++/* i_op_del.c */ ++int au_wr_dir_need_wh(struct dentry *dentry, int isdir, aufs_bindex_t *bcpup); ++int au_may_del(struct dentry *dentry, aufs_bindex_t bindex, ++ struct dentry *h_parent, int isdir); ++int aufs_unlink(struct inode *dir, struct dentry *dentry); ++int aufs_rmdir(struct inode *dir, struct dentry *dentry); ++ ++/* i_op_ren.c */ ++int au_wbr(struct dentry *dentry, aufs_bindex_t btgt); ++int aufs_rename(struct inode *src_dir, struct dentry *src_dentry, ++ struct inode *dir, struct dentry *dentry); ++ ++/* iinfo.c */ ++struct inode *au_h_iptr(struct inode *inode, aufs_bindex_t bindex); ++void au_hiput(struct au_hinode *hinode); ++void au_set_hi_wh(struct inode *inode, aufs_bindex_t bindex, ++ struct dentry *h_wh); ++unsigned int au_hi_flags(struct inode *inode, int isdir); ++ ++/* hinode flags */ ++#define AuHi_XINO 1 ++#define AuHi_HNOTIFY (1 << 1) ++#define au_ftest_hi(flags, name) ((flags) & AuHi_##name) ++#define au_fset_hi(flags, name) \ ++ do { (flags) |= AuHi_##name; } while (0) ++#define au_fclr_hi(flags, name) \ ++ do { (flags) &= ~AuHi_##name; } while (0) ++ ++#ifndef CONFIG_AUFS_HNOTIFY ++#undef AuHi_HNOTIFY ++#define AuHi_HNOTIFY 0 ++#endif ++ ++void au_set_h_iptr(struct inode *inode, aufs_bindex_t bindex, ++ struct inode *h_inode, unsigned int flags); ++ ++void au_update_iigen(struct inode *inode, int half); ++void au_update_ibrange(struct inode *inode, int do_put_zero); ++ ++void au_icntnr_init_once(void *_c); ++int au_iinfo_init(struct inode *inode); ++void au_iinfo_fin(struct inode *inode); ++int au_ii_realloc(struct au_iinfo *iinfo, int nbr); ++ ++#ifdef CONFIG_PROC_FS ++/* plink.c */ ++int au_plink_maint(struct super_block *sb, int flags); ++struct au_sbinfo; ++void au_plink_maint_leave(struct au_sbinfo *sbinfo); ++int au_plink_maint_enter(struct super_block *sb); ++#ifdef CONFIG_AUFS_DEBUG ++void au_plink_list(struct super_block *sb); ++#else ++AuStubVoid(au_plink_list, struct super_block *sb) ++#endif ++int au_plink_test(struct inode *inode); ++struct dentry *au_plink_lkup(struct inode *inode, aufs_bindex_t bindex); ++void au_plink_append(struct inode *inode, aufs_bindex_t bindex, ++ struct dentry *h_dentry); ++void au_plink_put(struct super_block *sb, int verbose); ++void au_plink_clean(struct super_block *sb, int verbose); ++void au_plink_half_refresh(struct super_block *sb, aufs_bindex_t br_id); ++#else ++AuStubInt0(au_plink_maint, struct super_block *sb, int flags); ++AuStubVoid(au_plink_maint_leave, struct au_sbinfo *sbinfo); ++AuStubInt0(au_plink_maint_enter, struct super_block *sb); ++AuStubVoid(au_plink_list, struct super_block *sb); ++AuStubInt0(au_plink_test, struct inode *inode); ++AuStub(struct dentry *, au_plink_lkup, return NULL, ++ struct inode *inode, aufs_bindex_t bindex); ++AuStubVoid(au_plink_append, struct inode *inode, aufs_bindex_t bindex, ++ struct dentry *h_dentry); ++AuStubVoid(au_plink_put, struct super_block *sb, int verbose); ++AuStubVoid(au_plink_clean, struct super_block *sb, int verbose); ++AuStubVoid(au_plink_half_refresh, struct super_block *sb, aufs_bindex_t br_id); ++#endif /* CONFIG_PROC_FS */ ++ ++#ifdef CONFIG_AUFS_XATTR ++/* xattr.c */ ++int au_cpup_xattr(struct dentry *h_dst, struct dentry *h_src, int ignore_flags, ++ unsigned int verbose); ++ssize_t aufs_listxattr(struct dentry *dentry, char *list, size_t size); ++ssize_t aufs_getxattr(struct dentry *dentry, const char *name, void *value, ++ size_t size); ++int aufs_setxattr(struct dentry *dentry, const char *name, const void *value, ++ size_t size, int flags); ++int aufs_removexattr(struct dentry *dentry, const char *name); ++ ++/* void au_xattr_init(struct super_block *sb); */ ++#else ++AuStubInt0(au_cpup_xattr, struct dentry *h_dst, struct dentry *h_src, ++ int ignore_flags, unsigned int verbose); ++/* AuStubVoid(au_xattr_init, struct super_block *sb); */ ++#endif ++ ++#ifdef CONFIG_FS_POSIX_ACL ++struct posix_acl *aufs_get_acl(struct inode *inode, int type); ++int aufs_set_acl(struct inode *inode, struct posix_acl *acl, int type); ++#endif ++ ++#if IS_ENABLED(CONFIG_AUFS_XATTR) || IS_ENABLED(CONFIG_FS_POSIX_ACL) ++enum { ++ AU_XATTR_SET, ++ AU_XATTR_REMOVE, ++ AU_ACL_SET ++}; ++ ++struct au_srxattr { ++ int type; ++ union { ++ struct { ++ const char *name; ++ const void *value; ++ size_t size; ++ int flags; ++ } set; ++ struct { ++ const char *name; ++ } remove; ++ struct { ++ struct posix_acl *acl; ++ int type; ++ } acl_set; ++ } u; ++}; ++ssize_t au_srxattr(struct dentry *dentry, struct au_srxattr *arg); ++#endif ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* lock subclass for iinfo */ ++enum { ++ AuLsc_II_CHILD, /* child first */ ++ AuLsc_II_CHILD2, /* rename(2), link(2), and cpup at hnotify */ ++ AuLsc_II_CHILD3, /* copyup dirs */ ++ AuLsc_II_PARENT, /* see AuLsc_I_PARENT in vfsub.h */ ++ AuLsc_II_PARENT2, ++ AuLsc_II_PARENT3, /* copyup dirs */ ++ AuLsc_II_NEW_CHILD ++}; ++ ++/* ++ * ii_read_lock_child, ii_write_lock_child, ++ * ii_read_lock_child2, ii_write_lock_child2, ++ * ii_read_lock_child3, ii_write_lock_child3, ++ * ii_read_lock_parent, ii_write_lock_parent, ++ * ii_read_lock_parent2, ii_write_lock_parent2, ++ * ii_read_lock_parent3, ii_write_lock_parent3, ++ * ii_read_lock_new_child, ii_write_lock_new_child, ++ */ ++#define AuReadLockFunc(name, lsc) \ ++static inline void ii_read_lock_##name(struct inode *i) \ ++{ \ ++ au_rw_read_lock_nested(&au_ii(i)->ii_rwsem, AuLsc_II_##lsc); \ ++} ++ ++#define AuWriteLockFunc(name, lsc) \ ++static inline void ii_write_lock_##name(struct inode *i) \ ++{ \ ++ au_rw_write_lock_nested(&au_ii(i)->ii_rwsem, AuLsc_II_##lsc); \ ++} ++ ++#define AuRWLockFuncs(name, lsc) \ ++ AuReadLockFunc(name, lsc) \ ++ AuWriteLockFunc(name, lsc) ++ ++AuRWLockFuncs(child, CHILD); ++AuRWLockFuncs(child2, CHILD2); ++AuRWLockFuncs(child3, CHILD3); ++AuRWLockFuncs(parent, PARENT); ++AuRWLockFuncs(parent2, PARENT2); ++AuRWLockFuncs(parent3, PARENT3); ++AuRWLockFuncs(new_child, NEW_CHILD); ++ ++#undef AuReadLockFunc ++#undef AuWriteLockFunc ++#undef AuRWLockFuncs ++ ++/* ++ * ii_read_unlock, ii_write_unlock, ii_downgrade_lock ++ */ ++AuSimpleUnlockRwsemFuncs(ii, struct inode *i, &au_ii(i)->ii_rwsem); ++ ++#define IiMustNoWaiters(i) AuRwMustNoWaiters(&au_ii(i)->ii_rwsem) ++#define IiMustAnyLock(i) AuRwMustAnyLock(&au_ii(i)->ii_rwsem) ++#define IiMustWriteLock(i) AuRwMustWriteLock(&au_ii(i)->ii_rwsem) ++ ++/* ---------------------------------------------------------------------- */ ++ ++static inline void au_icntnr_init(struct au_icntnr *c) ++{ ++#ifdef CONFIG_AUFS_DEBUG ++ c->vfs_inode.i_mode = 0; ++#endif ++} ++ ++static inline unsigned int au_iigen(struct inode *inode, unsigned int *igflags) ++{ ++ unsigned int gen; ++ struct au_iinfo *iinfo; ++ struct au_iigen *iigen; ++ ++ iinfo = au_ii(inode); ++ iigen = &iinfo->ii_generation; ++ spin_lock(&iigen->ig_spin); ++ if (igflags) ++ *igflags = iigen->ig_flags; ++ gen = iigen->ig_generation; ++ spin_unlock(&iigen->ig_spin); ++ ++ return gen; ++} ++ ++/* tiny test for inode number */ ++/* tmpfs generation is too rough */ ++static inline int au_test_higen(struct inode *inode, struct inode *h_inode) ++{ ++ struct au_iinfo *iinfo; ++ ++ iinfo = au_ii(inode); ++ AuRwMustAnyLock(&iinfo->ii_rwsem); ++ return !(iinfo->ii_hsb1 == h_inode->i_sb ++ && iinfo->ii_higen == h_inode->i_generation); ++} ++ ++static inline void au_iigen_dec(struct inode *inode) ++{ ++ struct au_iinfo *iinfo; ++ struct au_iigen *iigen; ++ ++ iinfo = au_ii(inode); ++ iigen = &iinfo->ii_generation; ++ spin_lock(&iigen->ig_spin); ++ iigen->ig_generation--; ++ spin_unlock(&iigen->ig_spin); ++} ++ ++static inline int au_iigen_test(struct inode *inode, unsigned int sigen) ++{ ++ int err; ++ ++ err = 0; ++ if (unlikely(inode && au_iigen(inode, NULL) != sigen)) ++ err = -EIO; ++ ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++static inline aufs_bindex_t au_ii_br_id(struct inode *inode, ++ aufs_bindex_t bindex) ++{ ++ IiMustAnyLock(inode); ++ return au_ii(inode)->ii_hinode[0 + bindex].hi_id; ++} ++ ++static inline aufs_bindex_t au_ibstart(struct inode *inode) ++{ ++ IiMustAnyLock(inode); ++ return au_ii(inode)->ii_bstart; ++} ++ ++static inline aufs_bindex_t au_ibend(struct inode *inode) ++{ ++ IiMustAnyLock(inode); ++ return au_ii(inode)->ii_bend; ++} ++ ++static inline struct au_vdir *au_ivdir(struct inode *inode) ++{ ++ IiMustAnyLock(inode); ++ return au_ii(inode)->ii_vdir; ++} ++ ++static inline struct dentry *au_hi_wh(struct inode *inode, aufs_bindex_t bindex) ++{ ++ IiMustAnyLock(inode); ++ return au_ii(inode)->ii_hinode[0 + bindex].hi_whdentry; ++} ++ ++static inline void au_set_ibstart(struct inode *inode, aufs_bindex_t bindex) ++{ ++ IiMustWriteLock(inode); ++ au_ii(inode)->ii_bstart = bindex; ++} ++ ++static inline void au_set_ibend(struct inode *inode, aufs_bindex_t bindex) ++{ ++ IiMustWriteLock(inode); ++ au_ii(inode)->ii_bend = bindex; ++} ++ ++static inline void au_set_ivdir(struct inode *inode, struct au_vdir *vdir) ++{ ++ IiMustWriteLock(inode); ++ au_ii(inode)->ii_vdir = vdir; ++} ++ ++static inline struct au_hinode *au_hi(struct inode *inode, aufs_bindex_t bindex) ++{ ++ IiMustAnyLock(inode); ++ return au_ii(inode)->ii_hinode + bindex; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++static inline struct dentry *au_pinned_parent(struct au_pin *pin) ++{ ++ if (pin) ++ return pin->parent; ++ return NULL; ++} ++ ++static inline struct inode *au_pinned_h_dir(struct au_pin *pin) ++{ ++ if (pin && pin->hdir) ++ return pin->hdir->hi_inode; ++ return NULL; ++} ++ ++static inline struct au_hinode *au_pinned_hdir(struct au_pin *pin) ++{ ++ if (pin) ++ return pin->hdir; ++ return NULL; ++} ++ ++static inline void au_pin_set_dentry(struct au_pin *pin, struct dentry *dentry) ++{ ++ if (pin) ++ pin->dentry = dentry; ++} ++ ++static inline void au_pin_set_parent_lflag(struct au_pin *pin, ++ unsigned char lflag) ++{ ++ if (pin) { ++ if (lflag) ++ au_fset_pin(pin->flags, DI_LOCKED); ++ else ++ au_fclr_pin(pin->flags, DI_LOCKED); ++ } ++} ++ ++#if 0 /* reserved */ ++static inline void au_pin_set_parent(struct au_pin *pin, struct dentry *parent) ++{ ++ if (pin) { ++ dput(pin->parent); ++ pin->parent = dget(parent); ++ } ++} ++#endif ++ ++/* ---------------------------------------------------------------------- */ ++ ++struct au_branch; ++#ifdef CONFIG_AUFS_HNOTIFY ++struct au_hnotify_op { ++ void (*ctl)(struct au_hinode *hinode, int do_set); ++ int (*alloc)(struct au_hinode *hinode); ++ ++ /* ++ * if it returns true, the the caller should free hinode->hi_notify, ++ * otherwise ->free() frees it. ++ */ ++ int (*free)(struct au_hinode *hinode, ++ struct au_hnotify *hn) __must_check; ++ ++ void (*fin)(void); ++ int (*init)(void); ++ ++ int (*reset_br)(unsigned int udba, struct au_branch *br, int perm); ++ void (*fin_br)(struct au_branch *br); ++ int (*init_br)(struct au_branch *br, int perm); ++}; ++ ++/* hnotify.c */ ++int au_hn_alloc(struct au_hinode *hinode, struct inode *inode); ++void au_hn_free(struct au_hinode *hinode); ++void au_hn_ctl(struct au_hinode *hinode, int do_set); ++void au_hn_reset(struct inode *inode, unsigned int flags); ++int au_hnotify(struct inode *h_dir, struct au_hnotify *hnotify, u32 mask, ++ struct qstr *h_child_qstr, struct inode *h_child_inode); ++int au_hnotify_reset_br(unsigned int udba, struct au_branch *br, int perm); ++int au_hnotify_init_br(struct au_branch *br, int perm); ++void au_hnotify_fin_br(struct au_branch *br); ++int __init au_hnotify_init(void); ++void au_hnotify_fin(void); ++ ++/* hfsnotify.c */ ++extern const struct au_hnotify_op au_hnotify_op; ++ ++static inline ++void au_hn_init(struct au_hinode *hinode) ++{ ++ hinode->hi_notify = NULL; ++} ++ ++static inline struct au_hnotify *au_hn(struct au_hinode *hinode) ++{ ++ return hinode->hi_notify; ++} ++ ++#else ++AuStub(int, au_hn_alloc, return -EOPNOTSUPP, ++ struct au_hinode *hinode __maybe_unused, ++ struct inode *inode __maybe_unused) ++AuStub(struct au_hnotify *, au_hn, return NULL, struct au_hinode *hinode) ++AuStubVoid(au_hn_free, struct au_hinode *hinode __maybe_unused) ++AuStubVoid(au_hn_ctl, struct au_hinode *hinode __maybe_unused, ++ int do_set __maybe_unused) ++AuStubVoid(au_hn_reset, struct inode *inode __maybe_unused, ++ unsigned int flags __maybe_unused) ++AuStubInt0(au_hnotify_reset_br, unsigned int udba __maybe_unused, ++ struct au_branch *br __maybe_unused, ++ int perm __maybe_unused) ++AuStubInt0(au_hnotify_init_br, struct au_branch *br __maybe_unused, ++ int perm __maybe_unused) ++AuStubVoid(au_hnotify_fin_br, struct au_branch *br __maybe_unused) ++AuStubInt0(__init au_hnotify_init, void) ++AuStubVoid(au_hnotify_fin, void) ++AuStubVoid(au_hn_init, struct au_hinode *hinode __maybe_unused) ++#endif /* CONFIG_AUFS_HNOTIFY */ ++ ++static inline void au_hn_suspend(struct au_hinode *hdir) ++{ ++ au_hn_ctl(hdir, /*do_set*/0); ++} ++ ++static inline void au_hn_resume(struct au_hinode *hdir) ++{ ++ au_hn_ctl(hdir, /*do_set*/1); ++} ++ ++static inline void au_hn_imtx_lock(struct au_hinode *hdir) ++{ ++ mutex_lock(&hdir->hi_inode->i_mutex); ++ au_hn_suspend(hdir); ++} ++ ++static inline void au_hn_imtx_lock_nested(struct au_hinode *hdir, ++ unsigned int sc __maybe_unused) ++{ ++ mutex_lock_nested(&hdir->hi_inode->i_mutex, sc); ++ au_hn_suspend(hdir); ++} ++ ++static inline void au_hn_imtx_unlock(struct au_hinode *hdir) ++{ ++ au_hn_resume(hdir); ++ mutex_unlock(&hdir->hi_inode->i_mutex); ++} ++ ++#endif /* __KERNEL__ */ ++#endif /* __AUFS_INODE_H__ */ +diff --git a/fs/aufs/ioctl.c b/fs/aufs/ioctl.c +new file mode 100644 +index 0000000..10e2315 +--- /dev/null ++++ b/fs/aufs/ioctl.c +@@ -0,0 +1,219 @@ ++/* ++ * Copyright (C) 2005-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * ioctl ++ * plink-management and readdir in userspace. ++ * assist the pathconf(3) wrapper library. ++ * move-down ++ * File-based Hierarchical Storage Management. ++ */ ++ ++#include ++#include ++#include "aufs.h" ++ ++static int au_wbr_fd(struct path *path, struct aufs_wbr_fd __user *arg) ++{ ++ int err, fd; ++ aufs_bindex_t wbi, bindex, bend; ++ struct file *h_file; ++ struct super_block *sb; ++ struct dentry *root; ++ struct au_branch *br; ++ struct aufs_wbr_fd wbrfd = { ++ .oflags = au_dir_roflags, ++ .brid = -1 ++ }; ++ const int valid = O_RDONLY | O_NONBLOCK | O_LARGEFILE | O_DIRECTORY ++ | O_NOATIME | O_CLOEXEC; ++ ++ AuDebugOn(wbrfd.oflags & ~valid); ++ ++ if (arg) { ++ err = copy_from_user(&wbrfd, arg, sizeof(wbrfd)); ++ if (unlikely(err)) { ++ err = -EFAULT; ++ goto out; ++ } ++ ++ err = -EINVAL; ++ AuDbg("wbrfd{0%o, %d}\n", wbrfd.oflags, wbrfd.brid); ++ wbrfd.oflags |= au_dir_roflags; ++ AuDbg("0%o\n", wbrfd.oflags); ++ if (unlikely(wbrfd.oflags & ~valid)) ++ goto out; ++ } ++ ++ fd = get_unused_fd(); ++ err = fd; ++ if (unlikely(fd < 0)) ++ goto out; ++ ++ h_file = ERR_PTR(-EINVAL); ++ wbi = 0; ++ br = NULL; ++ sb = path->dentry->d_sb; ++ root = sb->s_root; ++ aufs_read_lock(root, AuLock_IR); ++ bend = au_sbend(sb); ++ if (wbrfd.brid >= 0) { ++ wbi = au_br_index(sb, wbrfd.brid); ++ if (unlikely(wbi < 0 || wbi > bend)) ++ goto out_unlock; ++ } ++ ++ h_file = ERR_PTR(-ENOENT); ++ br = au_sbr(sb, wbi); ++ if (!au_br_writable(br->br_perm)) { ++ if (arg) ++ goto out_unlock; ++ ++ bindex = wbi + 1; ++ wbi = -1; ++ for (; bindex <= bend; bindex++) { ++ br = au_sbr(sb, bindex); ++ if (au_br_writable(br->br_perm)) { ++ wbi = bindex; ++ br = au_sbr(sb, wbi); ++ break; ++ } ++ } ++ } ++ AuDbg("wbi %d\n", wbi); ++ if (wbi >= 0) ++ h_file = au_h_open(root, wbi, wbrfd.oflags, NULL, ++ /*force_wr*/0); ++ ++out_unlock: ++ aufs_read_unlock(root, AuLock_IR); ++ err = PTR_ERR(h_file); ++ if (IS_ERR(h_file)) ++ goto out_fd; ++ ++ atomic_dec(&br->br_count); /* cf. au_h_open() */ ++ fd_install(fd, h_file); ++ err = fd; ++ goto out; /* success */ ++ ++out_fd: ++ put_unused_fd(fd); ++out: ++ AuTraceErr(err); ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++long aufs_ioctl_dir(struct file *file, unsigned int cmd, unsigned long arg) ++{ ++ long err; ++ struct dentry *dentry; ++ ++ switch (cmd) { ++ case AUFS_CTL_RDU: ++ case AUFS_CTL_RDU_INO: ++ err = au_rdu_ioctl(file, cmd, arg); ++ break; ++ ++ case AUFS_CTL_WBR_FD: ++ err = au_wbr_fd(&file->f_path, (void __user *)arg); ++ break; ++ ++ case AUFS_CTL_IBUSY: ++ err = au_ibusy_ioctl(file, arg); ++ break; ++ ++ case AUFS_CTL_BRINFO: ++ err = au_brinfo_ioctl(file, arg); ++ break; ++ ++ case AUFS_CTL_FHSM_FD: ++ dentry = file->f_dentry; ++ if (IS_ROOT(dentry)) ++ err = au_fhsm_fd(dentry->d_sb, arg); ++ else ++ err = -ENOTTY; ++ break; ++ ++ default: ++ /* do not call the lower */ ++ AuDbg("0x%x\n", cmd); ++ err = -ENOTTY; ++ } ++ ++ AuTraceErr(err); ++ return err; ++} ++ ++long aufs_ioctl_nondir(struct file *file, unsigned int cmd, unsigned long arg) ++{ ++ long err; ++ ++ switch (cmd) { ++ case AUFS_CTL_MVDOWN: ++ err = au_mvdown(file->f_dentry, (void __user *)arg); ++ break; ++ ++ case AUFS_CTL_WBR_FD: ++ err = au_wbr_fd(&file->f_path, (void __user *)arg); ++ break; ++ ++ default: ++ /* do not call the lower */ ++ AuDbg("0x%x\n", cmd); ++ err = -ENOTTY; ++ } ++ ++ AuTraceErr(err); ++ return err; ++} ++ ++#ifdef CONFIG_COMPAT ++long aufs_compat_ioctl_dir(struct file *file, unsigned int cmd, ++ unsigned long arg) ++{ ++ long err; ++ ++ switch (cmd) { ++ case AUFS_CTL_RDU: ++ case AUFS_CTL_RDU_INO: ++ err = au_rdu_compat_ioctl(file, cmd, arg); ++ break; ++ ++ case AUFS_CTL_IBUSY: ++ err = au_ibusy_compat_ioctl(file, arg); ++ break; ++ ++ case AUFS_CTL_BRINFO: ++ err = au_brinfo_compat_ioctl(file, arg); ++ break; ++ ++ default: ++ err = aufs_ioctl_dir(file, cmd, arg); ++ } ++ ++ AuTraceErr(err); ++ return err; ++} ++ ++long aufs_compat_ioctl_nondir(struct file *file, unsigned int cmd, ++ unsigned long arg) ++{ ++ return aufs_ioctl_nondir(file, cmd, (unsigned long)compat_ptr(arg)); ++} ++#endif +diff --git a/fs/aufs/loop.c b/fs/aufs/loop.c +new file mode 100644 +index 0000000..1eaf59f +--- /dev/null ++++ b/fs/aufs/loop.c +@@ -0,0 +1,146 @@ ++/* ++ * Copyright (C) 2005-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * support for loopback block device as a branch ++ */ ++ ++#include "aufs.h" ++ ++/* added into drivers/block/loop.c */ ++static struct file *(*backing_file_func)(struct super_block *sb); ++ ++/* ++ * test if two lower dentries have overlapping branches. ++ */ ++int au_test_loopback_overlap(struct super_block *sb, struct dentry *h_adding) ++{ ++ struct super_block *h_sb; ++ struct file *backing_file; ++ ++ if (unlikely(!backing_file_func)) { ++ /* don't load "loop" module here */ ++ backing_file_func = symbol_get(loop_backing_file); ++ if (unlikely(!backing_file_func)) ++ /* "loop" module is not loaded */ ++ return 0; ++ } ++ ++ h_sb = h_adding->d_sb; ++ backing_file = backing_file_func(h_sb); ++ if (!backing_file) ++ return 0; ++ ++ h_adding = backing_file->f_dentry; ++ /* ++ * h_adding can be local NFS. ++ * in this case aufs cannot detect the loop. ++ */ ++ if (unlikely(h_adding->d_sb == sb)) ++ return 1; ++ return !!au_test_subdir(h_adding, sb->s_root); ++} ++ ++/* true if a kernel thread named 'loop[0-9].*' accesses a file */ ++int au_test_loopback_kthread(void) ++{ ++ int ret; ++ struct task_struct *tsk = current; ++ char c, comm[sizeof(tsk->comm)]; ++ ++ ret = 0; ++ if (tsk->flags & PF_KTHREAD) { ++ get_task_comm(comm, tsk); ++ c = comm[4]; ++ ret = ('0' <= c && c <= '9' ++ && !strncmp(comm, "loop", 4)); ++ } ++ ++ return ret; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++#define au_warn_loopback_step 16 ++static int au_warn_loopback_nelem = au_warn_loopback_step; ++static unsigned long *au_warn_loopback_array; ++ ++void au_warn_loopback(struct super_block *h_sb) ++{ ++ int i, new_nelem; ++ unsigned long *a, magic; ++ static DEFINE_SPINLOCK(spin); ++ ++ magic = h_sb->s_magic; ++ spin_lock(&spin); ++ a = au_warn_loopback_array; ++ for (i = 0; i < au_warn_loopback_nelem && *a; i++) ++ if (a[i] == magic) { ++ spin_unlock(&spin); ++ return; ++ } ++ ++ /* h_sb is new to us, print it */ ++ if (i < au_warn_loopback_nelem) { ++ a[i] = magic; ++ goto pr; ++ } ++ ++ /* expand the array */ ++ new_nelem = au_warn_loopback_nelem + au_warn_loopback_step; ++ a = au_kzrealloc(au_warn_loopback_array, ++ au_warn_loopback_nelem * sizeof(unsigned long), ++ new_nelem * sizeof(unsigned long), GFP_ATOMIC); ++ if (a) { ++ au_warn_loopback_nelem = new_nelem; ++ au_warn_loopback_array = a; ++ a[i] = magic; ++ goto pr; ++ } ++ ++ spin_unlock(&spin); ++ AuWarn1("realloc failed, ignored\n"); ++ return; ++ ++pr: ++ spin_unlock(&spin); ++ pr_warn("you may want to try another patch for loopback file " ++ "on %s(0x%lx) branch\n", au_sbtype(h_sb), magic); ++} ++ ++int au_loopback_init(void) ++{ ++ int err; ++ struct super_block *sb __maybe_unused; ++ ++ BUILD_BUG_ON(sizeof(sb->s_magic) != sizeof(unsigned long)); ++ ++ err = 0; ++ au_warn_loopback_array = kcalloc(au_warn_loopback_step, ++ sizeof(unsigned long), GFP_NOFS); ++ if (unlikely(!au_warn_loopback_array)) ++ err = -ENOMEM; ++ ++ return err; ++} ++ ++void au_loopback_fin(void) ++{ ++ if (backing_file_func) ++ symbol_put(loop_backing_file); ++ kfree(au_warn_loopback_array); ++} +diff --git a/fs/aufs/loop.h b/fs/aufs/loop.h +new file mode 100644 +index 0000000..35f7446 +--- /dev/null ++++ b/fs/aufs/loop.h +@@ -0,0 +1,52 @@ ++/* ++ * Copyright (C) 2005-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * support for loopback mount as a branch ++ */ ++ ++#ifndef __AUFS_LOOP_H__ ++#define __AUFS_LOOP_H__ ++ ++#ifdef __KERNEL__ ++ ++struct dentry; ++struct super_block; ++ ++#ifdef CONFIG_AUFS_BDEV_LOOP ++/* drivers/block/loop.c */ ++struct file *loop_backing_file(struct super_block *sb); ++ ++/* loop.c */ ++int au_test_loopback_overlap(struct super_block *sb, struct dentry *h_adding); ++int au_test_loopback_kthread(void); ++void au_warn_loopback(struct super_block *h_sb); ++ ++int au_loopback_init(void); ++void au_loopback_fin(void); ++#else ++AuStubInt0(au_test_loopback_overlap, struct super_block *sb, ++ struct dentry *h_adding) ++AuStubInt0(au_test_loopback_kthread, void) ++AuStubVoid(au_warn_loopback, struct super_block *h_sb) ++ ++AuStubInt0(au_loopback_init, void) ++AuStubVoid(au_loopback_fin, void) ++#endif /* BLK_DEV_LOOP */ ++ ++#endif /* __KERNEL__ */ ++#endif /* __AUFS_LOOP_H__ */ +diff --git a/fs/aufs/magic.mk b/fs/aufs/magic.mk +new file mode 100644 +index 0000000..4f83bdf +--- /dev/null ++++ b/fs/aufs/magic.mk +@@ -0,0 +1,30 @@ ++ ++# defined in ${srctree}/fs/fuse/inode.c ++# tristate ++ifdef CONFIG_FUSE_FS ++ccflags-y += -DFUSE_SUPER_MAGIC=0x65735546 ++endif ++ ++# defined in ${srctree}/fs/xfs/xfs_sb.h ++# tristate ++ifdef CONFIG_XFS_FS ++ccflags-y += -DXFS_SB_MAGIC=0x58465342 ++endif ++ ++# defined in ${srctree}/fs/configfs/mount.c ++# tristate ++ifdef CONFIG_CONFIGFS_FS ++ccflags-y += -DCONFIGFS_MAGIC=0x62656570 ++endif ++ ++# defined in ${srctree}/fs/ubifs/ubifs.h ++# tristate ++ifdef CONFIG_UBIFS_FS ++ccflags-y += -DUBIFS_SUPER_MAGIC=0x24051905 ++endif ++ ++# defined in ${srctree}/fs/hfsplus/hfsplus_raw.h ++# tristate ++ifdef CONFIG_HFSPLUS_FS ++ccflags-y += -DHFSPLUS_SUPER_MAGIC=0x482b ++endif +diff --git a/fs/aufs/module.c b/fs/aufs/module.c +new file mode 100644 +index 0000000..e4e04aa +--- /dev/null ++++ b/fs/aufs/module.c +@@ -0,0 +1,222 @@ ++/* ++ * Copyright (C) 2005-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * module global variables and operations ++ */ ++ ++#include ++#include ++#include "aufs.h" ++ ++void *au_kzrealloc(void *p, unsigned int nused, unsigned int new_sz, gfp_t gfp) ++{ ++ if (new_sz <= nused) ++ return p; ++ ++ p = krealloc(p, new_sz, gfp); ++ if (p) ++ memset(p + nused, 0, new_sz - nused); ++ return p; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* ++ * aufs caches ++ */ ++struct kmem_cache *au_cachep[AuCache_Last]; ++static int __init au_cache_init(void) ++{ ++ au_cachep[AuCache_DINFO] = AuCacheCtor(au_dinfo, au_di_init_once); ++ if (au_cachep[AuCache_DINFO]) ++ /* SLAB_DESTROY_BY_RCU */ ++ au_cachep[AuCache_ICNTNR] = AuCacheCtor(au_icntnr, ++ au_icntnr_init_once); ++ if (au_cachep[AuCache_ICNTNR]) ++ au_cachep[AuCache_FINFO] = AuCacheCtor(au_finfo, ++ au_fi_init_once); ++ if (au_cachep[AuCache_FINFO]) ++ au_cachep[AuCache_VDIR] = AuCache(au_vdir); ++ if (au_cachep[AuCache_VDIR]) ++ au_cachep[AuCache_DEHSTR] = AuCache(au_vdir_dehstr); ++ if (au_cachep[AuCache_DEHSTR]) ++ return 0; ++ ++ return -ENOMEM; ++} ++ ++static void au_cache_fin(void) ++{ ++ int i; ++ ++ /* ++ * Make sure all delayed rcu free inodes are flushed before we ++ * destroy cache. ++ */ ++ rcu_barrier(); ++ ++ /* excluding AuCache_HNOTIFY */ ++ BUILD_BUG_ON(AuCache_HNOTIFY + 1 != AuCache_Last); ++ for (i = 0; i < AuCache_HNOTIFY; i++) ++ if (au_cachep[i]) { ++ kmem_cache_destroy(au_cachep[i]); ++ au_cachep[i] = NULL; ++ } ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++int au_dir_roflags; ++ ++#ifdef CONFIG_AUFS_SBILIST ++/* ++ * iterate_supers_type() doesn't protect us from ++ * remounting (branch management) ++ */ ++struct au_sphlhead au_sbilist; ++#endif ++ ++struct lock_class_key au_lc_key[AuLcKey_Last]; ++ ++/* ++ * functions for module interface. ++ */ ++MODULE_LICENSE("GPL"); ++/* MODULE_LICENSE("GPL v2"); */ ++MODULE_AUTHOR("Junjiro R. Okajima "); ++MODULE_DESCRIPTION(AUFS_NAME ++ " -- Advanced multi layered unification filesystem"); ++MODULE_VERSION(AUFS_VERSION); ++MODULE_ALIAS_FS(AUFS_NAME); ++ ++/* this module parameter has no meaning when SYSFS is disabled */ ++int sysaufs_brs = 1; ++MODULE_PARM_DESC(brs, "use /fs/aufs/si_*/brN"); ++module_param_named(brs, sysaufs_brs, int, S_IRUGO); ++ ++/* this module parameter has no meaning when USER_NS is disabled */ ++bool au_userns; ++MODULE_PARM_DESC(allow_userns, "allow unprivileged to mount under userns"); ++module_param_named(allow_userns, au_userns, bool, S_IRUGO); ++ ++/* ---------------------------------------------------------------------- */ ++ ++static char au_esc_chars[0x20 + 3]; /* 0x01-0x20, backslash, del, and NULL */ ++ ++int au_seq_path(struct seq_file *seq, struct path *path) ++{ ++ int err; ++ ++ err = seq_path(seq, path, au_esc_chars); ++ if (err > 0) ++ err = 0; ++ else if (err < 0) ++ err = -ENOMEM; ++ ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++static int __init aufs_init(void) ++{ ++ int err, i; ++ char *p; ++ ++ p = au_esc_chars; ++ for (i = 1; i <= ' '; i++) ++ *p++ = i; ++ *p++ = '\\'; ++ *p++ = '\x7f'; ++ *p = 0; ++ ++ au_dir_roflags = au_file_roflags(O_DIRECTORY | O_LARGEFILE); ++ ++ memcpy(aufs_iop_nogetattr, aufs_iop, sizeof(aufs_iop)); ++ for (i = 0; i < AuIop_Last; i++) ++ aufs_iop_nogetattr[i].getattr = NULL; ++ ++ au_sbilist_init(); ++ sysaufs_brs_init(); ++ au_debug_init(); ++ au_dy_init(); ++ err = sysaufs_init(); ++ if (unlikely(err)) ++ goto out; ++ err = au_procfs_init(); ++ if (unlikely(err)) ++ goto out_sysaufs; ++ err = au_wkq_init(); ++ if (unlikely(err)) ++ goto out_procfs; ++ err = au_loopback_init(); ++ if (unlikely(err)) ++ goto out_wkq; ++ err = au_hnotify_init(); ++ if (unlikely(err)) ++ goto out_loopback; ++ err = au_sysrq_init(); ++ if (unlikely(err)) ++ goto out_hin; ++ err = au_cache_init(); ++ if (unlikely(err)) ++ goto out_sysrq; ++ ++ aufs_fs_type.fs_flags |= au_userns ? FS_USERNS_MOUNT : 0; ++ err = register_filesystem(&aufs_fs_type); ++ if (unlikely(err)) ++ goto out_cache; ++ ++ /* since we define pr_fmt, call printk directly */ ++ printk(KERN_INFO AUFS_NAME " " AUFS_VERSION "\n"); ++ goto out; /* success */ ++ ++out_cache: ++ au_cache_fin(); ++out_sysrq: ++ au_sysrq_fin(); ++out_hin: ++ au_hnotify_fin(); ++out_loopback: ++ au_loopback_fin(); ++out_wkq: ++ au_wkq_fin(); ++out_procfs: ++ au_procfs_fin(); ++out_sysaufs: ++ sysaufs_fin(); ++ au_dy_fin(); ++out: ++ return err; ++} ++ ++static void __exit aufs_exit(void) ++{ ++ unregister_filesystem(&aufs_fs_type); ++ au_cache_fin(); ++ au_sysrq_fin(); ++ au_hnotify_fin(); ++ au_loopback_fin(); ++ au_wkq_fin(); ++ au_procfs_fin(); ++ sysaufs_fin(); ++ au_dy_fin(); ++} ++ ++module_init(aufs_init); ++module_exit(aufs_exit); +diff --git a/fs/aufs/module.h b/fs/aufs/module.h +new file mode 100644 +index 0000000..90c3c8f +--- /dev/null ++++ b/fs/aufs/module.h +@@ -0,0 +1,105 @@ ++/* ++ * Copyright (C) 2005-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * module initialization and module-global ++ */ ++ ++#ifndef __AUFS_MODULE_H__ ++#define __AUFS_MODULE_H__ ++ ++#ifdef __KERNEL__ ++ ++#include ++ ++struct path; ++struct seq_file; ++ ++/* module parameters */ ++extern int sysaufs_brs; ++extern bool au_userns; ++ ++/* ---------------------------------------------------------------------- */ ++ ++extern int au_dir_roflags; ++ ++enum { ++ AuLcNonDir_FIINFO, ++ AuLcNonDir_DIINFO, ++ AuLcNonDir_IIINFO, ++ ++ AuLcDir_FIINFO, ++ AuLcDir_DIINFO, ++ AuLcDir_IIINFO, ++ ++ AuLcSymlink_DIINFO, ++ AuLcSymlink_IIINFO, ++ ++ AuLcKey_Last ++}; ++extern struct lock_class_key au_lc_key[AuLcKey_Last]; ++ ++void *au_kzrealloc(void *p, unsigned int nused, unsigned int new_sz, gfp_t gfp); ++int au_seq_path(struct seq_file *seq, struct path *path); ++ ++#ifdef CONFIG_PROC_FS ++/* procfs.c */ ++int __init au_procfs_init(void); ++void au_procfs_fin(void); ++#else ++AuStubInt0(au_procfs_init, void); ++AuStubVoid(au_procfs_fin, void); ++#endif ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* kmem cache */ ++enum { ++ AuCache_DINFO, ++ AuCache_ICNTNR, ++ AuCache_FINFO, ++ AuCache_VDIR, ++ AuCache_DEHSTR, ++ AuCache_HNOTIFY, /* must be last */ ++ AuCache_Last ++}; ++ ++#define AuCacheFlags (SLAB_RECLAIM_ACCOUNT | SLAB_MEM_SPREAD) ++#define AuCache(type) KMEM_CACHE(type, AuCacheFlags) ++#define AuCacheCtor(type, ctor) \ ++ kmem_cache_create(#type, sizeof(struct type), \ ++ __alignof__(struct type), AuCacheFlags, ctor) ++ ++extern struct kmem_cache *au_cachep[]; ++ ++#define AuCacheFuncs(name, index) \ ++static inline struct au_##name *au_cache_alloc_##name(void) \ ++{ return kmem_cache_alloc(au_cachep[AuCache_##index], GFP_NOFS); } \ ++static inline void au_cache_free_##name(struct au_##name *p) \ ++{ kmem_cache_free(au_cachep[AuCache_##index], p); } ++ ++AuCacheFuncs(dinfo, DINFO); ++AuCacheFuncs(icntnr, ICNTNR); ++AuCacheFuncs(finfo, FINFO); ++AuCacheFuncs(vdir, VDIR); ++AuCacheFuncs(vdir_dehstr, DEHSTR); ++#ifdef CONFIG_AUFS_HNOTIFY ++AuCacheFuncs(hnotify, HNOTIFY); ++#endif ++ ++#endif /* __KERNEL__ */ ++#endif /* __AUFS_MODULE_H__ */ +diff --git a/fs/aufs/mvdown.c b/fs/aufs/mvdown.c +new file mode 100644 +index 0000000..e660c8f +--- /dev/null ++++ b/fs/aufs/mvdown.c +@@ -0,0 +1,703 @@ ++/* ++ * Copyright (C) 2011-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * move-down, opposite of copy-up ++ */ ++ ++#include "aufs.h" ++ ++struct au_mvd_args { ++ struct { ++ struct super_block *h_sb; ++ struct dentry *h_parent; ++ struct au_hinode *hdir; ++ struct inode *h_dir, *h_inode; ++ struct au_pin pin; ++ } info[AUFS_MVDOWN_NARRAY]; ++ ++ struct aufs_mvdown mvdown; ++ struct dentry *dentry, *parent; ++ struct inode *inode, *dir; ++ struct super_block *sb; ++ aufs_bindex_t bopq, bwh, bfound; ++ unsigned char rename_lock; ++}; ++ ++#define mvd_errno mvdown.au_errno ++#define mvd_bsrc mvdown.stbr[AUFS_MVDOWN_UPPER].bindex ++#define mvd_src_brid mvdown.stbr[AUFS_MVDOWN_UPPER].brid ++#define mvd_bdst mvdown.stbr[AUFS_MVDOWN_LOWER].bindex ++#define mvd_dst_brid mvdown.stbr[AUFS_MVDOWN_LOWER].brid ++ ++#define mvd_h_src_sb info[AUFS_MVDOWN_UPPER].h_sb ++#define mvd_h_src_parent info[AUFS_MVDOWN_UPPER].h_parent ++#define mvd_hdir_src info[AUFS_MVDOWN_UPPER].hdir ++#define mvd_h_src_dir info[AUFS_MVDOWN_UPPER].h_dir ++#define mvd_h_src_inode info[AUFS_MVDOWN_UPPER].h_inode ++#define mvd_pin_src info[AUFS_MVDOWN_UPPER].pin ++ ++#define mvd_h_dst_sb info[AUFS_MVDOWN_LOWER].h_sb ++#define mvd_h_dst_parent info[AUFS_MVDOWN_LOWER].h_parent ++#define mvd_hdir_dst info[AUFS_MVDOWN_LOWER].hdir ++#define mvd_h_dst_dir info[AUFS_MVDOWN_LOWER].h_dir ++#define mvd_h_dst_inode info[AUFS_MVDOWN_LOWER].h_inode ++#define mvd_pin_dst info[AUFS_MVDOWN_LOWER].pin ++ ++#define AU_MVD_PR(flag, ...) do { \ ++ if (flag) \ ++ pr_err(__VA_ARGS__); \ ++ } while (0) ++ ++static int find_lower_writable(struct au_mvd_args *a) ++{ ++ struct super_block *sb; ++ aufs_bindex_t bindex, bend; ++ struct au_branch *br; ++ ++ sb = a->sb; ++ bindex = a->mvd_bsrc; ++ bend = au_sbend(sb); ++ if (a->mvdown.flags & AUFS_MVDOWN_FHSM_LOWER) ++ for (bindex++; bindex <= bend; bindex++) { ++ br = au_sbr(sb, bindex); ++ if (au_br_fhsm(br->br_perm) ++ && (!(au_br_sb(br)->s_flags & MS_RDONLY))) ++ return bindex; ++ } ++ else if (!(a->mvdown.flags & AUFS_MVDOWN_ROLOWER)) ++ for (bindex++; bindex <= bend; bindex++) { ++ br = au_sbr(sb, bindex); ++ if (!au_br_rdonly(br)) ++ return bindex; ++ } ++ else ++ for (bindex++; bindex <= bend; bindex++) { ++ br = au_sbr(sb, bindex); ++ if (!(au_br_sb(br)->s_flags & MS_RDONLY)) { ++ if (au_br_rdonly(br)) ++ a->mvdown.flags ++ |= AUFS_MVDOWN_ROLOWER_R; ++ return bindex; ++ } ++ } ++ ++ return -1; ++} ++ ++/* make the parent dir on bdst */ ++static int au_do_mkdir(const unsigned char dmsg, struct au_mvd_args *a) ++{ ++ int err; ++ ++ err = 0; ++ a->mvd_hdir_src = au_hi(a->dir, a->mvd_bsrc); ++ a->mvd_hdir_dst = au_hi(a->dir, a->mvd_bdst); ++ a->mvd_h_src_parent = au_h_dptr(a->parent, a->mvd_bsrc); ++ a->mvd_h_dst_parent = NULL; ++ if (au_dbend(a->parent) >= a->mvd_bdst) ++ a->mvd_h_dst_parent = au_h_dptr(a->parent, a->mvd_bdst); ++ if (!a->mvd_h_dst_parent) { ++ err = au_cpdown_dirs(a->dentry, a->mvd_bdst); ++ if (unlikely(err)) { ++ AU_MVD_PR(dmsg, "cpdown_dirs failed\n"); ++ goto out; ++ } ++ a->mvd_h_dst_parent = au_h_dptr(a->parent, a->mvd_bdst); ++ } ++ ++out: ++ AuTraceErr(err); ++ return err; ++} ++ ++/* lock them all */ ++static int au_do_lock(const unsigned char dmsg, struct au_mvd_args *a) ++{ ++ int err; ++ struct dentry *h_trap; ++ ++ a->mvd_h_src_sb = au_sbr_sb(a->sb, a->mvd_bsrc); ++ a->mvd_h_dst_sb = au_sbr_sb(a->sb, a->mvd_bdst); ++ err = au_pin(&a->mvd_pin_dst, a->dentry, a->mvd_bdst, ++ au_opt_udba(a->sb), ++ AuPin_MNT_WRITE | AuPin_DI_LOCKED); ++ AuTraceErr(err); ++ if (unlikely(err)) { ++ AU_MVD_PR(dmsg, "pin_dst failed\n"); ++ goto out; ++ } ++ ++ if (a->mvd_h_src_sb != a->mvd_h_dst_sb) { ++ a->rename_lock = 0; ++ au_pin_init(&a->mvd_pin_src, a->dentry, a->mvd_bsrc, ++ AuLsc_DI_PARENT, AuLsc_I_PARENT3, ++ au_opt_udba(a->sb), ++ AuPin_MNT_WRITE | AuPin_DI_LOCKED); ++ err = au_do_pin(&a->mvd_pin_src); ++ AuTraceErr(err); ++ a->mvd_h_src_dir = a->mvd_h_src_parent->d_inode; ++ if (unlikely(err)) { ++ AU_MVD_PR(dmsg, "pin_src failed\n"); ++ goto out_dst; ++ } ++ goto out; /* success */ ++ } ++ ++ a->rename_lock = 1; ++ au_pin_hdir_unlock(&a->mvd_pin_dst); ++ err = au_pin(&a->mvd_pin_src, a->dentry, a->mvd_bsrc, ++ au_opt_udba(a->sb), ++ AuPin_MNT_WRITE | AuPin_DI_LOCKED); ++ AuTraceErr(err); ++ a->mvd_h_src_dir = a->mvd_h_src_parent->d_inode; ++ if (unlikely(err)) { ++ AU_MVD_PR(dmsg, "pin_src failed\n"); ++ au_pin_hdir_lock(&a->mvd_pin_dst); ++ goto out_dst; ++ } ++ au_pin_hdir_unlock(&a->mvd_pin_src); ++ h_trap = vfsub_lock_rename(a->mvd_h_src_parent, a->mvd_hdir_src, ++ a->mvd_h_dst_parent, a->mvd_hdir_dst); ++ if (h_trap) { ++ err = (h_trap != a->mvd_h_src_parent); ++ if (err) ++ err = (h_trap != a->mvd_h_dst_parent); ++ } ++ BUG_ON(err); /* it should never happen */ ++ if (unlikely(a->mvd_h_src_dir != au_pinned_h_dir(&a->mvd_pin_src))) { ++ err = -EBUSY; ++ AuTraceErr(err); ++ vfsub_unlock_rename(a->mvd_h_src_parent, a->mvd_hdir_src, ++ a->mvd_h_dst_parent, a->mvd_hdir_dst); ++ au_pin_hdir_lock(&a->mvd_pin_src); ++ au_unpin(&a->mvd_pin_src); ++ au_pin_hdir_lock(&a->mvd_pin_dst); ++ goto out_dst; ++ } ++ goto out; /* success */ ++ ++out_dst: ++ au_unpin(&a->mvd_pin_dst); ++out: ++ AuTraceErr(err); ++ return err; ++} ++ ++static void au_do_unlock(const unsigned char dmsg, struct au_mvd_args *a) ++{ ++ if (!a->rename_lock) ++ au_unpin(&a->mvd_pin_src); ++ else { ++ vfsub_unlock_rename(a->mvd_h_src_parent, a->mvd_hdir_src, ++ a->mvd_h_dst_parent, a->mvd_hdir_dst); ++ au_pin_hdir_lock(&a->mvd_pin_src); ++ au_unpin(&a->mvd_pin_src); ++ au_pin_hdir_lock(&a->mvd_pin_dst); ++ } ++ au_unpin(&a->mvd_pin_dst); ++} ++ ++/* copy-down the file */ ++static int au_do_cpdown(const unsigned char dmsg, struct au_mvd_args *a) ++{ ++ int err; ++ struct au_cp_generic cpg = { ++ .dentry = a->dentry, ++ .bdst = a->mvd_bdst, ++ .bsrc = a->mvd_bsrc, ++ .len = -1, ++ .pin = &a->mvd_pin_dst, ++ .flags = AuCpup_DTIME | AuCpup_HOPEN ++ }; ++ ++ AuDbg("b%d, b%d\n", cpg.bsrc, cpg.bdst); ++ if (a->mvdown.flags & AUFS_MVDOWN_OWLOWER) ++ au_fset_cpup(cpg.flags, OVERWRITE); ++ if (a->mvdown.flags & AUFS_MVDOWN_ROLOWER) ++ au_fset_cpup(cpg.flags, RWDST); ++ err = au_sio_cpdown_simple(&cpg); ++ if (unlikely(err)) ++ AU_MVD_PR(dmsg, "cpdown failed\n"); ++ ++ AuTraceErr(err); ++ return err; ++} ++ ++/* ++ * unlink the whiteout on bdst if exist which may be created by UDBA while we ++ * were sleeping ++ */ ++static int au_do_unlink_wh(const unsigned char dmsg, struct au_mvd_args *a) ++{ ++ int err; ++ struct path h_path; ++ struct au_branch *br; ++ struct inode *delegated; ++ ++ br = au_sbr(a->sb, a->mvd_bdst); ++ h_path.dentry = au_wh_lkup(a->mvd_h_dst_parent, &a->dentry->d_name, br); ++ err = PTR_ERR(h_path.dentry); ++ if (IS_ERR(h_path.dentry)) { ++ AU_MVD_PR(dmsg, "wh_lkup failed\n"); ++ goto out; ++ } ++ ++ err = 0; ++ if (h_path.dentry->d_inode) { ++ h_path.mnt = au_br_mnt(br); ++ delegated = NULL; ++ err = vfsub_unlink(a->mvd_h_dst_parent->d_inode, &h_path, ++ &delegated, /*force*/0); ++ if (unlikely(err == -EWOULDBLOCK)) { ++ pr_warn("cannot retry for NFSv4 delegation" ++ " for an internal unlink\n"); ++ iput(delegated); ++ } ++ if (unlikely(err)) ++ AU_MVD_PR(dmsg, "wh_unlink failed\n"); ++ } ++ dput(h_path.dentry); ++ ++out: ++ AuTraceErr(err); ++ return err; ++} ++ ++/* ++ * unlink the topmost h_dentry ++ */ ++static int au_do_unlink(const unsigned char dmsg, struct au_mvd_args *a) ++{ ++ int err; ++ struct path h_path; ++ struct inode *delegated; ++ ++ h_path.mnt = au_sbr_mnt(a->sb, a->mvd_bsrc); ++ h_path.dentry = au_h_dptr(a->dentry, a->mvd_bsrc); ++ delegated = NULL; ++ err = vfsub_unlink(a->mvd_h_src_dir, &h_path, &delegated, /*force*/0); ++ if (unlikely(err == -EWOULDBLOCK)) { ++ pr_warn("cannot retry for NFSv4 delegation" ++ " for an internal unlink\n"); ++ iput(delegated); ++ } ++ if (unlikely(err)) ++ AU_MVD_PR(dmsg, "unlink failed\n"); ++ ++ AuTraceErr(err); ++ return err; ++} ++ ++/* Since mvdown succeeded, we ignore an error of this function */ ++static void au_do_stfs(const unsigned char dmsg, struct au_mvd_args *a) ++{ ++ int err; ++ struct au_branch *br; ++ ++ a->mvdown.flags |= AUFS_MVDOWN_STFS_FAILED; ++ br = au_sbr(a->sb, a->mvd_bsrc); ++ err = au_br_stfs(br, &a->mvdown.stbr[AUFS_MVDOWN_UPPER].stfs); ++ if (!err) { ++ br = au_sbr(a->sb, a->mvd_bdst); ++ a->mvdown.stbr[AUFS_MVDOWN_LOWER].brid = br->br_id; ++ err = au_br_stfs(br, &a->mvdown.stbr[AUFS_MVDOWN_LOWER].stfs); ++ } ++ if (!err) ++ a->mvdown.flags &= ~AUFS_MVDOWN_STFS_FAILED; ++ else ++ AU_MVD_PR(dmsg, "statfs failed (%d), ignored\n", err); ++} ++ ++/* ++ * copy-down the file and unlink the bsrc file. ++ * - unlink the bdst whout if exist ++ * - copy-down the file (with whtmp name and rename) ++ * - unlink the bsrc file ++ */ ++static int au_do_mvdown(const unsigned char dmsg, struct au_mvd_args *a) ++{ ++ int err; ++ ++ err = au_do_mkdir(dmsg, a); ++ if (!err) ++ err = au_do_lock(dmsg, a); ++ if (unlikely(err)) ++ goto out; ++ ++ /* ++ * do not revert the activities we made on bdst since they should be ++ * harmless in aufs. ++ */ ++ ++ err = au_do_cpdown(dmsg, a); ++ if (!err) ++ err = au_do_unlink_wh(dmsg, a); ++ if (!err && !(a->mvdown.flags & AUFS_MVDOWN_KUPPER)) ++ err = au_do_unlink(dmsg, a); ++ if (unlikely(err)) ++ goto out_unlock; ++ ++ AuDbg("%pd2, 0x%x, %d --> %d\n", ++ a->dentry, a->mvdown.flags, a->mvd_bsrc, a->mvd_bdst); ++ if (find_lower_writable(a) < 0) ++ a->mvdown.flags |= AUFS_MVDOWN_BOTTOM; ++ ++ if (a->mvdown.flags & AUFS_MVDOWN_STFS) ++ au_do_stfs(dmsg, a); ++ ++ /* maintain internal array */ ++ if (!(a->mvdown.flags & AUFS_MVDOWN_KUPPER)) { ++ au_set_h_dptr(a->dentry, a->mvd_bsrc, NULL); ++ au_set_dbstart(a->dentry, a->mvd_bdst); ++ au_set_h_iptr(a->inode, a->mvd_bsrc, NULL, /*flags*/0); ++ au_set_ibstart(a->inode, a->mvd_bdst); ++ } else { ++ /* hide the lower */ ++ au_set_h_dptr(a->dentry, a->mvd_bdst, NULL); ++ au_set_dbend(a->dentry, a->mvd_bsrc); ++ au_set_h_iptr(a->inode, a->mvd_bdst, NULL, /*flags*/0); ++ au_set_ibend(a->inode, a->mvd_bsrc); ++ } ++ if (au_dbend(a->dentry) < a->mvd_bdst) ++ au_set_dbend(a->dentry, a->mvd_bdst); ++ if (au_ibend(a->inode) < a->mvd_bdst) ++ au_set_ibend(a->inode, a->mvd_bdst); ++ ++out_unlock: ++ au_do_unlock(dmsg, a); ++out: ++ AuTraceErr(err); ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* make sure the file is idle */ ++static int au_mvd_args_busy(const unsigned char dmsg, struct au_mvd_args *a) ++{ ++ int err, plinked; ++ ++ err = 0; ++ plinked = !!au_opt_test(au_mntflags(a->sb), PLINK); ++ if (au_dbstart(a->dentry) == a->mvd_bsrc ++ && au_dcount(a->dentry) == 1 ++ && atomic_read(&a->inode->i_count) == 1 ++ /* && a->mvd_h_src_inode->i_nlink == 1 */ ++ && (!plinked || !au_plink_test(a->inode)) ++ && a->inode->i_nlink == 1) ++ goto out; ++ ++ err = -EBUSY; ++ AU_MVD_PR(dmsg, ++ "b%d, d{b%d, c%d?}, i{c%d?, l%u}, hi{l%u}, p{%d, %d}\n", ++ a->mvd_bsrc, au_dbstart(a->dentry), au_dcount(a->dentry), ++ atomic_read(&a->inode->i_count), a->inode->i_nlink, ++ a->mvd_h_src_inode->i_nlink, ++ plinked, plinked ? au_plink_test(a->inode) : 0); ++ ++out: ++ AuTraceErr(err); ++ return err; ++} ++ ++/* make sure the parent dir is fine */ ++static int au_mvd_args_parent(const unsigned char dmsg, ++ struct au_mvd_args *a) ++{ ++ int err; ++ aufs_bindex_t bindex; ++ ++ err = 0; ++ if (unlikely(au_alive_dir(a->parent))) { ++ err = -ENOENT; ++ AU_MVD_PR(dmsg, "parent dir is dead\n"); ++ goto out; ++ } ++ ++ a->bopq = au_dbdiropq(a->parent); ++ bindex = au_wbr_nonopq(a->dentry, a->mvd_bdst); ++ AuDbg("b%d\n", bindex); ++ if (unlikely((bindex >= 0 && bindex < a->mvd_bdst) ++ || (a->bopq != -1 && a->bopq < a->mvd_bdst))) { ++ err = -EINVAL; ++ a->mvd_errno = EAU_MVDOWN_OPAQUE; ++ AU_MVD_PR(dmsg, "ancestor is opaque b%d, b%d\n", ++ a->bopq, a->mvd_bdst); ++ } ++ ++out: ++ AuTraceErr(err); ++ return err; ++} ++ ++static int au_mvd_args_intermediate(const unsigned char dmsg, ++ struct au_mvd_args *a) ++{ ++ int err; ++ struct au_dinfo *dinfo, *tmp; ++ ++ /* lookup the next lower positive entry */ ++ err = -ENOMEM; ++ tmp = au_di_alloc(a->sb, AuLsc_DI_TMP); ++ if (unlikely(!tmp)) ++ goto out; ++ ++ a->bfound = -1; ++ a->bwh = -1; ++ dinfo = au_di(a->dentry); ++ au_di_cp(tmp, dinfo); ++ au_di_swap(tmp, dinfo); ++ ++ /* returns the number of positive dentries */ ++ err = au_lkup_dentry(a->dentry, a->mvd_bsrc + 1, /*type*/0); ++ if (!err) ++ a->bwh = au_dbwh(a->dentry); ++ else if (err > 0) ++ a->bfound = au_dbstart(a->dentry); ++ ++ au_di_swap(tmp, dinfo); ++ au_rw_write_unlock(&tmp->di_rwsem); ++ au_di_free(tmp); ++ if (unlikely(err < 0)) ++ AU_MVD_PR(dmsg, "failed look-up lower\n"); ++ ++ /* ++ * here, we have these cases. ++ * bfound == -1 ++ * no positive dentry under bsrc. there are more sub-cases. ++ * bwh < 0 ++ * there no whiteout, we can safely move-down. ++ * bwh <= bsrc ++ * impossible ++ * bsrc < bwh && bwh < bdst ++ * there is a whiteout on RO branch. cannot proceed. ++ * bwh == bdst ++ * there is a whiteout on the RW target branch. it should ++ * be removed. ++ * bdst < bwh ++ * there is a whiteout somewhere unrelated branch. ++ * -1 < bfound && bfound <= bsrc ++ * impossible. ++ * bfound < bdst ++ * found, but it is on RO branch between bsrc and bdst. cannot ++ * proceed. ++ * bfound == bdst ++ * found, replace it if AUFS_MVDOWN_FORCE is set. otherwise return ++ * error. ++ * bdst < bfound ++ * found, after we create the file on bdst, it will be hidden. ++ */ ++ ++ AuDebugOn(a->bfound == -1 ++ && a->bwh != -1 ++ && a->bwh <= a->mvd_bsrc); ++ AuDebugOn(-1 < a->bfound ++ && a->bfound <= a->mvd_bsrc); ++ ++ err = -EINVAL; ++ if (a->bfound == -1 ++ && a->mvd_bsrc < a->bwh ++ && a->bwh != -1 ++ && a->bwh < a->mvd_bdst) { ++ a->mvd_errno = EAU_MVDOWN_WHITEOUT; ++ AU_MVD_PR(dmsg, "bsrc %d, bdst %d, bfound %d, bwh %d\n", ++ a->mvd_bsrc, a->mvd_bdst, a->bfound, a->bwh); ++ goto out; ++ } else if (a->bfound != -1 && a->bfound < a->mvd_bdst) { ++ a->mvd_errno = EAU_MVDOWN_UPPER; ++ AU_MVD_PR(dmsg, "bdst %d, bfound %d\n", ++ a->mvd_bdst, a->bfound); ++ goto out; ++ } ++ ++ err = 0; /* success */ ++ ++out: ++ AuTraceErr(err); ++ return err; ++} ++ ++static int au_mvd_args_exist(const unsigned char dmsg, struct au_mvd_args *a) ++{ ++ int err; ++ ++ err = 0; ++ if (!(a->mvdown.flags & AUFS_MVDOWN_OWLOWER) ++ && a->bfound == a->mvd_bdst) ++ err = -EEXIST; ++ AuTraceErr(err); ++ return err; ++} ++ ++static int au_mvd_args(const unsigned char dmsg, struct au_mvd_args *a) ++{ ++ int err; ++ struct au_branch *br; ++ ++ err = -EISDIR; ++ if (unlikely(S_ISDIR(a->inode->i_mode))) ++ goto out; ++ ++ err = -EINVAL; ++ if (!(a->mvdown.flags & AUFS_MVDOWN_BRID_UPPER)) ++ a->mvd_bsrc = au_ibstart(a->inode); ++ else { ++ a->mvd_bsrc = au_br_index(a->sb, a->mvd_src_brid); ++ if (unlikely(a->mvd_bsrc < 0 ++ || (a->mvd_bsrc < au_dbstart(a->dentry) ++ || au_dbend(a->dentry) < a->mvd_bsrc ++ || !au_h_dptr(a->dentry, a->mvd_bsrc)) ++ || (a->mvd_bsrc < au_ibstart(a->inode) ++ || au_ibend(a->inode) < a->mvd_bsrc ++ || !au_h_iptr(a->inode, a->mvd_bsrc)))) { ++ a->mvd_errno = EAU_MVDOWN_NOUPPER; ++ AU_MVD_PR(dmsg, "no upper\n"); ++ goto out; ++ } ++ } ++ if (unlikely(a->mvd_bsrc == au_sbend(a->sb))) { ++ a->mvd_errno = EAU_MVDOWN_BOTTOM; ++ AU_MVD_PR(dmsg, "on the bottom\n"); ++ goto out; ++ } ++ a->mvd_h_src_inode = au_h_iptr(a->inode, a->mvd_bsrc); ++ br = au_sbr(a->sb, a->mvd_bsrc); ++ err = au_br_rdonly(br); ++ if (!(a->mvdown.flags & AUFS_MVDOWN_ROUPPER)) { ++ if (unlikely(err)) ++ goto out; ++ } else if (!(vfsub_native_ro(a->mvd_h_src_inode) ++ || IS_APPEND(a->mvd_h_src_inode))) { ++ if (err) ++ a->mvdown.flags |= AUFS_MVDOWN_ROUPPER_R; ++ /* go on */ ++ } else ++ goto out; ++ ++ err = -EINVAL; ++ if (!(a->mvdown.flags & AUFS_MVDOWN_BRID_LOWER)) { ++ a->mvd_bdst = find_lower_writable(a); ++ if (unlikely(a->mvd_bdst < 0)) { ++ a->mvd_errno = EAU_MVDOWN_BOTTOM; ++ AU_MVD_PR(dmsg, "no writable lower branch\n"); ++ goto out; ++ } ++ } else { ++ a->mvd_bdst = au_br_index(a->sb, a->mvd_dst_brid); ++ if (unlikely(a->mvd_bdst < 0 ++ || au_sbend(a->sb) < a->mvd_bdst)) { ++ a->mvd_errno = EAU_MVDOWN_NOLOWERBR; ++ AU_MVD_PR(dmsg, "no lower brid\n"); ++ goto out; ++ } ++ } ++ ++ err = au_mvd_args_busy(dmsg, a); ++ if (!err) ++ err = au_mvd_args_parent(dmsg, a); ++ if (!err) ++ err = au_mvd_args_intermediate(dmsg, a); ++ if (!err) ++ err = au_mvd_args_exist(dmsg, a); ++ if (!err) ++ AuDbg("b%d, b%d\n", a->mvd_bsrc, a->mvd_bdst); ++ ++out: ++ AuTraceErr(err); ++ return err; ++} ++ ++int au_mvdown(struct dentry *dentry, struct aufs_mvdown __user *uarg) ++{ ++ int err, e; ++ unsigned char dmsg; ++ struct au_mvd_args *args; ++ struct inode *inode; ++ ++ inode = dentry->d_inode; ++ err = -EPERM; ++ if (unlikely(!capable(CAP_SYS_ADMIN))) ++ goto out; ++ ++ err = -ENOMEM; ++ args = kmalloc(sizeof(*args), GFP_NOFS); ++ if (unlikely(!args)) ++ goto out; ++ ++ err = copy_from_user(&args->mvdown, uarg, sizeof(args->mvdown)); ++ if (!err) ++ err = !access_ok(VERIFY_WRITE, uarg, sizeof(*uarg)); ++ if (unlikely(err)) { ++ err = -EFAULT; ++ AuTraceErr(err); ++ goto out_free; ++ } ++ AuDbg("flags 0x%x\n", args->mvdown.flags); ++ args->mvdown.flags &= ~(AUFS_MVDOWN_ROLOWER_R | AUFS_MVDOWN_ROUPPER_R); ++ args->mvdown.au_errno = 0; ++ args->dentry = dentry; ++ args->inode = inode; ++ args->sb = dentry->d_sb; ++ ++ err = -ENOENT; ++ dmsg = !!(args->mvdown.flags & AUFS_MVDOWN_DMSG); ++ args->parent = dget_parent(dentry); ++ args->dir = args->parent->d_inode; ++ mutex_lock_nested(&args->dir->i_mutex, I_MUTEX_PARENT); ++ dput(args->parent); ++ if (unlikely(args->parent != dentry->d_parent)) { ++ AU_MVD_PR(dmsg, "parent dir is moved\n"); ++ goto out_dir; ++ } ++ ++ mutex_lock_nested(&inode->i_mutex, I_MUTEX_CHILD); ++ err = aufs_read_lock(dentry, AuLock_DW | AuLock_FLUSH | AuLock_NOPLMW); ++ if (unlikely(err)) ++ goto out_inode; ++ ++ di_write_lock_parent(args->parent); ++ err = au_mvd_args(dmsg, args); ++ if (unlikely(err)) ++ goto out_parent; ++ ++ err = au_do_mvdown(dmsg, args); ++ if (unlikely(err)) ++ goto out_parent; ++ ++ au_cpup_attr_timesizes(args->dir); ++ au_cpup_attr_timesizes(inode); ++ if (!(args->mvdown.flags & AUFS_MVDOWN_KUPPER)) ++ au_cpup_igen(inode, au_h_iptr(inode, args->mvd_bdst)); ++ /* au_digen_dec(dentry); */ ++ ++out_parent: ++ di_write_unlock(args->parent); ++ aufs_read_unlock(dentry, AuLock_DW); ++out_inode: ++ mutex_unlock(&inode->i_mutex); ++out_dir: ++ mutex_unlock(&args->dir->i_mutex); ++out_free: ++ e = copy_to_user(uarg, &args->mvdown, sizeof(args->mvdown)); ++ if (unlikely(e)) ++ err = -EFAULT; ++ kfree(args); ++out: ++ AuTraceErr(err); ++ return err; ++} +diff --git a/fs/aufs/opts.c b/fs/aufs/opts.c +new file mode 100644 +index 0000000..0363f67 +--- /dev/null ++++ b/fs/aufs/opts.c +@@ -0,0 +1,1878 @@ ++/* ++ * Copyright (C) 2005-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * mount options/flags ++ */ ++ ++#include ++#include /* a distribution requires */ ++#include ++#include "aufs.h" ++ ++/* ---------------------------------------------------------------------- */ ++ ++enum { ++ Opt_br, ++ Opt_add, Opt_del, Opt_mod, Opt_append, Opt_prepend, ++ Opt_idel, Opt_imod, ++ Opt_dirwh, Opt_rdcache, Opt_rdblk, Opt_rdhash, ++ Opt_rdblk_def, Opt_rdhash_def, ++ Opt_xino, Opt_noxino, ++ Opt_trunc_xino, Opt_trunc_xino_v, Opt_notrunc_xino, ++ Opt_trunc_xino_path, Opt_itrunc_xino, ++ Opt_trunc_xib, Opt_notrunc_xib, ++ Opt_shwh, Opt_noshwh, ++ Opt_plink, Opt_noplink, Opt_list_plink, ++ Opt_udba, ++ Opt_dio, Opt_nodio, ++ Opt_diropq_a, Opt_diropq_w, ++ Opt_warn_perm, Opt_nowarn_perm, ++ Opt_wbr_copyup, Opt_wbr_create, ++ Opt_fhsm_sec, ++ Opt_refrof, Opt_norefrof, ++ Opt_verbose, Opt_noverbose, ++ Opt_sum, Opt_nosum, Opt_wsum, ++ Opt_dirperm1, Opt_nodirperm1, ++ Opt_acl, Opt_noacl, ++ Opt_tail, Opt_ignore, Opt_ignore_silent, Opt_err ++}; ++ ++static match_table_t options = { ++ {Opt_br, "br=%s"}, ++ {Opt_br, "br:%s"}, ++ ++ {Opt_add, "add=%d:%s"}, ++ {Opt_add, "add:%d:%s"}, ++ {Opt_add, "ins=%d:%s"}, ++ {Opt_add, "ins:%d:%s"}, ++ {Opt_append, "append=%s"}, ++ {Opt_append, "append:%s"}, ++ {Opt_prepend, "prepend=%s"}, ++ {Opt_prepend, "prepend:%s"}, ++ ++ {Opt_del, "del=%s"}, ++ {Opt_del, "del:%s"}, ++ /* {Opt_idel, "idel:%d"}, */ ++ {Opt_mod, "mod=%s"}, ++ {Opt_mod, "mod:%s"}, ++ /* {Opt_imod, "imod:%d:%s"}, */ ++ ++ {Opt_dirwh, "dirwh=%d"}, ++ ++ {Opt_xino, "xino=%s"}, ++ {Opt_noxino, "noxino"}, ++ {Opt_trunc_xino, "trunc_xino"}, ++ {Opt_trunc_xino_v, "trunc_xino_v=%d:%d"}, ++ {Opt_notrunc_xino, "notrunc_xino"}, ++ {Opt_trunc_xino_path, "trunc_xino=%s"}, ++ {Opt_itrunc_xino, "itrunc_xino=%d"}, ++ /* {Opt_zxino, "zxino=%s"}, */ ++ {Opt_trunc_xib, "trunc_xib"}, ++ {Opt_notrunc_xib, "notrunc_xib"}, ++ ++#ifdef CONFIG_PROC_FS ++ {Opt_plink, "plink"}, ++#else ++ {Opt_ignore_silent, "plink"}, ++#endif ++ ++ {Opt_noplink, "noplink"}, ++ ++#ifdef CONFIG_AUFS_DEBUG ++ {Opt_list_plink, "list_plink"}, ++#endif ++ ++ {Opt_udba, "udba=%s"}, ++ ++ {Opt_dio, "dio"}, ++ {Opt_nodio, "nodio"}, ++ ++#ifdef CONFIG_AUFS_FHSM ++ {Opt_fhsm_sec, "fhsm_sec=%d"}, ++#else ++ {Opt_ignore_silent, "fhsm_sec=%d"}, ++#endif ++ ++ {Opt_diropq_a, "diropq=always"}, ++ {Opt_diropq_a, "diropq=a"}, ++ {Opt_diropq_w, "diropq=whiteouted"}, ++ {Opt_diropq_w, "diropq=w"}, ++ ++ {Opt_warn_perm, "warn_perm"}, ++ {Opt_nowarn_perm, "nowarn_perm"}, ++ ++ /* keep them temporary */ ++ {Opt_ignore_silent, "nodlgt"}, ++ {Opt_ignore_silent, "clean_plink"}, ++ ++#ifdef CONFIG_AUFS_SHWH ++ {Opt_shwh, "shwh"}, ++#endif ++ {Opt_noshwh, "noshwh"}, ++ ++ {Opt_dirperm1, "dirperm1"}, ++ {Opt_nodirperm1, "nodirperm1"}, ++ ++ {Opt_refrof, "refrof"}, ++ {Opt_norefrof, "norefrof"}, ++ ++ {Opt_verbose, "verbose"}, ++ {Opt_verbose, "v"}, ++ {Opt_noverbose, "noverbose"}, ++ {Opt_noverbose, "quiet"}, ++ {Opt_noverbose, "q"}, ++ {Opt_noverbose, "silent"}, ++ ++ {Opt_sum, "sum"}, ++ {Opt_nosum, "nosum"}, ++ {Opt_wsum, "wsum"}, ++ ++ {Opt_rdcache, "rdcache=%d"}, ++ {Opt_rdblk, "rdblk=%d"}, ++ {Opt_rdblk_def, "rdblk=def"}, ++ {Opt_rdhash, "rdhash=%d"}, ++ {Opt_rdhash_def, "rdhash=def"}, ++ ++ {Opt_wbr_create, "create=%s"}, ++ {Opt_wbr_create, "create_policy=%s"}, ++ {Opt_wbr_copyup, "cpup=%s"}, ++ {Opt_wbr_copyup, "copyup=%s"}, ++ {Opt_wbr_copyup, "copyup_policy=%s"}, ++ ++ /* generic VFS flag */ ++#ifdef CONFIG_FS_POSIX_ACL ++ {Opt_acl, "acl"}, ++ {Opt_noacl, "noacl"}, ++#else ++ {Opt_ignore_silent, "acl"}, ++ {Opt_ignore_silent, "noacl"}, ++#endif ++ ++ /* internal use for the scripts */ ++ {Opt_ignore_silent, "si=%s"}, ++ ++ {Opt_br, "dirs=%s"}, ++ {Opt_ignore, "debug=%d"}, ++ {Opt_ignore, "delete=whiteout"}, ++ {Opt_ignore, "delete=all"}, ++ {Opt_ignore, "imap=%s"}, ++ ++ /* temporary workaround, due to old mount(8)? */ ++ {Opt_ignore_silent, "relatime"}, ++ ++ {Opt_err, NULL} ++}; ++ ++/* ---------------------------------------------------------------------- */ ++ ++static const char *au_parser_pattern(int val, match_table_t tbl) ++{ ++ struct match_token *p; ++ ++ p = tbl; ++ while (p->pattern) { ++ if (p->token == val) ++ return p->pattern; ++ p++; ++ } ++ BUG(); ++ return "??"; ++} ++ ++static const char *au_optstr(int *val, match_table_t tbl) ++{ ++ struct match_token *p; ++ int v; ++ ++ v = *val; ++ if (!v) ++ goto out; ++ p = tbl; ++ while (p->pattern) { ++ if (p->token ++ && (v & p->token) == p->token) { ++ *val &= ~p->token; ++ return p->pattern; ++ } ++ p++; ++ } ++ ++out: ++ return NULL; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++static match_table_t brperm = { ++ {AuBrPerm_RO, AUFS_BRPERM_RO}, ++ {AuBrPerm_RR, AUFS_BRPERM_RR}, ++ {AuBrPerm_RW, AUFS_BRPERM_RW}, ++ {0, NULL} ++}; ++ ++static match_table_t brattr = { ++ /* general */ ++ {AuBrAttr_COO_REG, AUFS_BRATTR_COO_REG}, ++ {AuBrAttr_COO_ALL, AUFS_BRATTR_COO_ALL}, ++ /* 'unpin' attrib is meaningless since linux-3.18-rc1 */ ++ {AuBrAttr_UNPIN, AUFS_BRATTR_UNPIN}, ++#ifdef CONFIG_AUFS_FHSM ++ {AuBrAttr_FHSM, AUFS_BRATTR_FHSM}, ++#endif ++#ifdef CONFIG_AUFS_XATTR ++ {AuBrAttr_ICEX, AUFS_BRATTR_ICEX}, ++ {AuBrAttr_ICEX_SEC, AUFS_BRATTR_ICEX_SEC}, ++ {AuBrAttr_ICEX_SYS, AUFS_BRATTR_ICEX_SYS}, ++ {AuBrAttr_ICEX_TR, AUFS_BRATTR_ICEX_TR}, ++ {AuBrAttr_ICEX_USR, AUFS_BRATTR_ICEX_USR}, ++ {AuBrAttr_ICEX_OTH, AUFS_BRATTR_ICEX_OTH}, ++#endif ++ ++ /* ro/rr branch */ ++ {AuBrRAttr_WH, AUFS_BRRATTR_WH}, ++ ++ /* rw branch */ ++ {AuBrWAttr_MOO, AUFS_BRWATTR_MOO}, ++ {AuBrWAttr_NoLinkWH, AUFS_BRWATTR_NLWH}, ++ ++ {0, NULL} ++}; ++ ++static int br_attr_val(char *str, match_table_t table, substring_t args[]) ++{ ++ int attr, v; ++ char *p; ++ ++ attr = 0; ++ do { ++ p = strchr(str, '+'); ++ if (p) ++ *p = 0; ++ v = match_token(str, table, args); ++ if (v) { ++ if (v & AuBrAttr_CMOO_Mask) ++ attr &= ~AuBrAttr_CMOO_Mask; ++ attr |= v; ++ } else { ++ if (p) ++ *p = '+'; ++ pr_warn("ignored branch attribute %s\n", str); ++ break; ++ } ++ if (p) ++ str = p + 1; ++ } while (p); ++ ++ return attr; ++} ++ ++static int au_do_optstr_br_attr(au_br_perm_str_t *str, int perm) ++{ ++ int sz; ++ const char *p; ++ char *q; ++ ++ q = str->a; ++ *q = 0; ++ p = au_optstr(&perm, brattr); ++ if (p) { ++ sz = strlen(p); ++ memcpy(q, p, sz + 1); ++ q += sz; ++ } else ++ goto out; ++ ++ do { ++ p = au_optstr(&perm, brattr); ++ if (p) { ++ *q++ = '+'; ++ sz = strlen(p); ++ memcpy(q, p, sz + 1); ++ q += sz; ++ } ++ } while (p); ++ ++out: ++ return q - str->a; ++} ++ ++static int noinline_for_stack br_perm_val(char *perm) ++{ ++ int val, bad, sz; ++ char *p; ++ substring_t args[MAX_OPT_ARGS]; ++ au_br_perm_str_t attr; ++ ++ p = strchr(perm, '+'); ++ if (p) ++ *p = 0; ++ val = match_token(perm, brperm, args); ++ if (!val) { ++ if (p) ++ *p = '+'; ++ pr_warn("ignored branch permission %s\n", perm); ++ val = AuBrPerm_RO; ++ goto out; ++ } ++ if (!p) ++ goto out; ++ ++ val |= br_attr_val(p + 1, brattr, args); ++ ++ bad = 0; ++ switch (val & AuBrPerm_Mask) { ++ case AuBrPerm_RO: ++ case AuBrPerm_RR: ++ bad = val & AuBrWAttr_Mask; ++ val &= ~AuBrWAttr_Mask; ++ break; ++ case AuBrPerm_RW: ++ bad = val & AuBrRAttr_Mask; ++ val &= ~AuBrRAttr_Mask; ++ break; ++ } ++ ++ /* ++ * 'unpin' attrib becomes meaningless since linux-3.18-rc1, but aufs ++ * does not treat it as an error, just warning. ++ * this is a tiny guard for the user operation. ++ */ ++ if (val & AuBrAttr_UNPIN) { ++ bad |= AuBrAttr_UNPIN; ++ val &= ~AuBrAttr_UNPIN; ++ } ++ ++ if (unlikely(bad)) { ++ sz = au_do_optstr_br_attr(&attr, bad); ++ AuDebugOn(!sz); ++ pr_warn("ignored branch attribute %s\n", attr.a); ++ } ++ ++out: ++ return val; ++} ++ ++void au_optstr_br_perm(au_br_perm_str_t *str, int perm) ++{ ++ au_br_perm_str_t attr; ++ const char *p; ++ char *q; ++ int sz; ++ ++ q = str->a; ++ p = au_optstr(&perm, brperm); ++ AuDebugOn(!p || !*p); ++ sz = strlen(p); ++ memcpy(q, p, sz + 1); ++ q += sz; ++ ++ sz = au_do_optstr_br_attr(&attr, perm); ++ if (sz) { ++ *q++ = '+'; ++ memcpy(q, attr.a, sz + 1); ++ } ++ ++ AuDebugOn(strlen(str->a) >= sizeof(str->a)); ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++static match_table_t udbalevel = { ++ {AuOpt_UDBA_REVAL, "reval"}, ++ {AuOpt_UDBA_NONE, "none"}, ++#ifdef CONFIG_AUFS_HNOTIFY ++ {AuOpt_UDBA_HNOTIFY, "notify"}, /* abstraction */ ++#ifdef CONFIG_AUFS_HFSNOTIFY ++ {AuOpt_UDBA_HNOTIFY, "fsnotify"}, ++#endif ++#endif ++ {-1, NULL} ++}; ++ ++static int noinline_for_stack udba_val(char *str) ++{ ++ substring_t args[MAX_OPT_ARGS]; ++ ++ return match_token(str, udbalevel, args); ++} ++ ++const char *au_optstr_udba(int udba) ++{ ++ return au_parser_pattern(udba, udbalevel); ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++static match_table_t au_wbr_create_policy = { ++ {AuWbrCreate_TDP, "tdp"}, ++ {AuWbrCreate_TDP, "top-down-parent"}, ++ {AuWbrCreate_RR, "rr"}, ++ {AuWbrCreate_RR, "round-robin"}, ++ {AuWbrCreate_MFS, "mfs"}, ++ {AuWbrCreate_MFS, "most-free-space"}, ++ {AuWbrCreate_MFSV, "mfs:%d"}, ++ {AuWbrCreate_MFSV, "most-free-space:%d"}, ++ ++ {AuWbrCreate_MFSRR, "mfsrr:%d"}, ++ {AuWbrCreate_MFSRRV, "mfsrr:%d:%d"}, ++ {AuWbrCreate_PMFS, "pmfs"}, ++ {AuWbrCreate_PMFSV, "pmfs:%d"}, ++ {AuWbrCreate_PMFSRR, "pmfsrr:%d"}, ++ {AuWbrCreate_PMFSRRV, "pmfsrr:%d:%d"}, ++ ++ {-1, NULL} ++}; ++ ++/* ++ * cf. linux/lib/parser.c and cmdline.c ++ * gave up calling memparse() since it uses simple_strtoull() instead of ++ * kstrto...(). ++ */ ++static int noinline_for_stack ++au_match_ull(substring_t *s, unsigned long long *result) ++{ ++ int err; ++ unsigned int len; ++ char a[32]; ++ ++ err = -ERANGE; ++ len = s->to - s->from; ++ if (len + 1 <= sizeof(a)) { ++ memcpy(a, s->from, len); ++ a[len] = '\0'; ++ err = kstrtoull(a, 0, result); ++ } ++ return err; ++} ++ ++static int au_wbr_mfs_wmark(substring_t *arg, char *str, ++ struct au_opt_wbr_create *create) ++{ ++ int err; ++ unsigned long long ull; ++ ++ err = 0; ++ if (!au_match_ull(arg, &ull)) ++ create->mfsrr_watermark = ull; ++ else { ++ pr_err("bad integer in %s\n", str); ++ err = -EINVAL; ++ } ++ ++ return err; ++} ++ ++static int au_wbr_mfs_sec(substring_t *arg, char *str, ++ struct au_opt_wbr_create *create) ++{ ++ int n, err; ++ ++ err = 0; ++ if (!match_int(arg, &n) && 0 <= n && n <= AUFS_MFS_MAX_SEC) ++ create->mfs_second = n; ++ else { ++ pr_err("bad integer in %s\n", str); ++ err = -EINVAL; ++ } ++ ++ return err; ++} ++ ++static int noinline_for_stack ++au_wbr_create_val(char *str, struct au_opt_wbr_create *create) ++{ ++ int err, e; ++ substring_t args[MAX_OPT_ARGS]; ++ ++ err = match_token(str, au_wbr_create_policy, args); ++ create->wbr_create = err; ++ switch (err) { ++ case AuWbrCreate_MFSRRV: ++ case AuWbrCreate_PMFSRRV: ++ e = au_wbr_mfs_wmark(&args[0], str, create); ++ if (!e) ++ e = au_wbr_mfs_sec(&args[1], str, create); ++ if (unlikely(e)) ++ err = e; ++ break; ++ case AuWbrCreate_MFSRR: ++ case AuWbrCreate_PMFSRR: ++ e = au_wbr_mfs_wmark(&args[0], str, create); ++ if (unlikely(e)) { ++ err = e; ++ break; ++ } ++ /*FALLTHROUGH*/ ++ case AuWbrCreate_MFS: ++ case AuWbrCreate_PMFS: ++ create->mfs_second = AUFS_MFS_DEF_SEC; ++ break; ++ case AuWbrCreate_MFSV: ++ case AuWbrCreate_PMFSV: ++ e = au_wbr_mfs_sec(&args[0], str, create); ++ if (unlikely(e)) ++ err = e; ++ break; ++ } ++ ++ return err; ++} ++ ++const char *au_optstr_wbr_create(int wbr_create) ++{ ++ return au_parser_pattern(wbr_create, au_wbr_create_policy); ++} ++ ++static match_table_t au_wbr_copyup_policy = { ++ {AuWbrCopyup_TDP, "tdp"}, ++ {AuWbrCopyup_TDP, "top-down-parent"}, ++ {AuWbrCopyup_BUP, "bup"}, ++ {AuWbrCopyup_BUP, "bottom-up-parent"}, ++ {AuWbrCopyup_BU, "bu"}, ++ {AuWbrCopyup_BU, "bottom-up"}, ++ {-1, NULL} ++}; ++ ++static int noinline_for_stack au_wbr_copyup_val(char *str) ++{ ++ substring_t args[MAX_OPT_ARGS]; ++ ++ return match_token(str, au_wbr_copyup_policy, args); ++} ++ ++const char *au_optstr_wbr_copyup(int wbr_copyup) ++{ ++ return au_parser_pattern(wbr_copyup, au_wbr_copyup_policy); ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++static const int lkup_dirflags = LOOKUP_FOLLOW | LOOKUP_DIRECTORY; ++ ++static void dump_opts(struct au_opts *opts) ++{ ++#ifdef CONFIG_AUFS_DEBUG ++ /* reduce stack space */ ++ union { ++ struct au_opt_add *add; ++ struct au_opt_del *del; ++ struct au_opt_mod *mod; ++ struct au_opt_xino *xino; ++ struct au_opt_xino_itrunc *xino_itrunc; ++ struct au_opt_wbr_create *create; ++ } u; ++ struct au_opt *opt; ++ ++ opt = opts->opt; ++ while (opt->type != Opt_tail) { ++ switch (opt->type) { ++ case Opt_add: ++ u.add = &opt->add; ++ AuDbg("add {b%d, %s, 0x%x, %p}\n", ++ u.add->bindex, u.add->pathname, u.add->perm, ++ u.add->path.dentry); ++ break; ++ case Opt_del: ++ case Opt_idel: ++ u.del = &opt->del; ++ AuDbg("del {%s, %p}\n", ++ u.del->pathname, u.del->h_path.dentry); ++ break; ++ case Opt_mod: ++ case Opt_imod: ++ u.mod = &opt->mod; ++ AuDbg("mod {%s, 0x%x, %p}\n", ++ u.mod->path, u.mod->perm, u.mod->h_root); ++ break; ++ case Opt_append: ++ u.add = &opt->add; ++ AuDbg("append {b%d, %s, 0x%x, %p}\n", ++ u.add->bindex, u.add->pathname, u.add->perm, ++ u.add->path.dentry); ++ break; ++ case Opt_prepend: ++ u.add = &opt->add; ++ AuDbg("prepend {b%d, %s, 0x%x, %p}\n", ++ u.add->bindex, u.add->pathname, u.add->perm, ++ u.add->path.dentry); ++ break; ++ case Opt_dirwh: ++ AuDbg("dirwh %d\n", opt->dirwh); ++ break; ++ case Opt_rdcache: ++ AuDbg("rdcache %d\n", opt->rdcache); ++ break; ++ case Opt_rdblk: ++ AuDbg("rdblk %u\n", opt->rdblk); ++ break; ++ case Opt_rdblk_def: ++ AuDbg("rdblk_def\n"); ++ break; ++ case Opt_rdhash: ++ AuDbg("rdhash %u\n", opt->rdhash); ++ break; ++ case Opt_rdhash_def: ++ AuDbg("rdhash_def\n"); ++ break; ++ case Opt_xino: ++ u.xino = &opt->xino; ++ AuDbg("xino {%s %pD}\n", u.xino->path, u.xino->file); ++ break; ++ case Opt_trunc_xino: ++ AuLabel(trunc_xino); ++ break; ++ case Opt_notrunc_xino: ++ AuLabel(notrunc_xino); ++ break; ++ case Opt_trunc_xino_path: ++ case Opt_itrunc_xino: ++ u.xino_itrunc = &opt->xino_itrunc; ++ AuDbg("trunc_xino %d\n", u.xino_itrunc->bindex); ++ break; ++ case Opt_noxino: ++ AuLabel(noxino); ++ break; ++ case Opt_trunc_xib: ++ AuLabel(trunc_xib); ++ break; ++ case Opt_notrunc_xib: ++ AuLabel(notrunc_xib); ++ break; ++ case Opt_shwh: ++ AuLabel(shwh); ++ break; ++ case Opt_noshwh: ++ AuLabel(noshwh); ++ break; ++ case Opt_dirperm1: ++ AuLabel(dirperm1); ++ break; ++ case Opt_nodirperm1: ++ AuLabel(nodirperm1); ++ break; ++ case Opt_plink: ++ AuLabel(plink); ++ break; ++ case Opt_noplink: ++ AuLabel(noplink); ++ break; ++ case Opt_list_plink: ++ AuLabel(list_plink); ++ break; ++ case Opt_udba: ++ AuDbg("udba %d, %s\n", ++ opt->udba, au_optstr_udba(opt->udba)); ++ break; ++ case Opt_dio: ++ AuLabel(dio); ++ break; ++ case Opt_nodio: ++ AuLabel(nodio); ++ break; ++ case Opt_diropq_a: ++ AuLabel(diropq_a); ++ break; ++ case Opt_diropq_w: ++ AuLabel(diropq_w); ++ break; ++ case Opt_warn_perm: ++ AuLabel(warn_perm); ++ break; ++ case Opt_nowarn_perm: ++ AuLabel(nowarn_perm); ++ break; ++ case Opt_refrof: ++ AuLabel(refrof); ++ break; ++ case Opt_norefrof: ++ AuLabel(norefrof); ++ break; ++ case Opt_verbose: ++ AuLabel(verbose); ++ break; ++ case Opt_noverbose: ++ AuLabel(noverbose); ++ break; ++ case Opt_sum: ++ AuLabel(sum); ++ break; ++ case Opt_nosum: ++ AuLabel(nosum); ++ break; ++ case Opt_wsum: ++ AuLabel(wsum); ++ break; ++ case Opt_wbr_create: ++ u.create = &opt->wbr_create; ++ AuDbg("create %d, %s\n", u.create->wbr_create, ++ au_optstr_wbr_create(u.create->wbr_create)); ++ switch (u.create->wbr_create) { ++ case AuWbrCreate_MFSV: ++ case AuWbrCreate_PMFSV: ++ AuDbg("%d sec\n", u.create->mfs_second); ++ break; ++ case AuWbrCreate_MFSRR: ++ AuDbg("%llu watermark\n", ++ u.create->mfsrr_watermark); ++ break; ++ case AuWbrCreate_MFSRRV: ++ case AuWbrCreate_PMFSRRV: ++ AuDbg("%llu watermark, %d sec\n", ++ u.create->mfsrr_watermark, ++ u.create->mfs_second); ++ break; ++ } ++ break; ++ case Opt_wbr_copyup: ++ AuDbg("copyup %d, %s\n", opt->wbr_copyup, ++ au_optstr_wbr_copyup(opt->wbr_copyup)); ++ break; ++ case Opt_fhsm_sec: ++ AuDbg("fhsm_sec %u\n", opt->fhsm_second); ++ break; ++ case Opt_acl: ++ AuLabel(acl); ++ break; ++ case Opt_noacl: ++ AuLabel(noacl); ++ break; ++ default: ++ BUG(); ++ } ++ opt++; ++ } ++#endif ++} ++ ++void au_opts_free(struct au_opts *opts) ++{ ++ struct au_opt *opt; ++ ++ opt = opts->opt; ++ while (opt->type != Opt_tail) { ++ switch (opt->type) { ++ case Opt_add: ++ case Opt_append: ++ case Opt_prepend: ++ path_put(&opt->add.path); ++ break; ++ case Opt_del: ++ case Opt_idel: ++ path_put(&opt->del.h_path); ++ break; ++ case Opt_mod: ++ case Opt_imod: ++ dput(opt->mod.h_root); ++ break; ++ case Opt_xino: ++ fput(opt->xino.file); ++ break; ++ } ++ opt++; ++ } ++} ++ ++static int opt_add(struct au_opt *opt, char *opt_str, unsigned long sb_flags, ++ aufs_bindex_t bindex) ++{ ++ int err; ++ struct au_opt_add *add = &opt->add; ++ char *p; ++ ++ add->bindex = bindex; ++ add->perm = AuBrPerm_RO; ++ add->pathname = opt_str; ++ p = strchr(opt_str, '='); ++ if (p) { ++ *p++ = 0; ++ if (*p) ++ add->perm = br_perm_val(p); ++ } ++ ++ err = vfsub_kern_path(add->pathname, lkup_dirflags, &add->path); ++ if (!err) { ++ if (!p) { ++ add->perm = AuBrPerm_RO; ++ if (au_test_fs_rr(add->path.dentry->d_sb)) ++ add->perm = AuBrPerm_RR; ++ else if (!bindex && !(sb_flags & MS_RDONLY)) ++ add->perm = AuBrPerm_RW; ++ } ++ opt->type = Opt_add; ++ goto out; ++ } ++ pr_err("lookup failed %s (%d)\n", add->pathname, err); ++ err = -EINVAL; ++ ++out: ++ return err; ++} ++ ++static int au_opts_parse_del(struct au_opt_del *del, substring_t args[]) ++{ ++ int err; ++ ++ del->pathname = args[0].from; ++ AuDbg("del path %s\n", del->pathname); ++ ++ err = vfsub_kern_path(del->pathname, lkup_dirflags, &del->h_path); ++ if (unlikely(err)) ++ pr_err("lookup failed %s (%d)\n", del->pathname, err); ++ ++ return err; ++} ++ ++#if 0 /* reserved for future use */ ++static int au_opts_parse_idel(struct super_block *sb, aufs_bindex_t bindex, ++ struct au_opt_del *del, substring_t args[]) ++{ ++ int err; ++ struct dentry *root; ++ ++ err = -EINVAL; ++ root = sb->s_root; ++ aufs_read_lock(root, AuLock_FLUSH); ++ if (bindex < 0 || au_sbend(sb) < bindex) { ++ pr_err("out of bounds, %d\n", bindex); ++ goto out; ++ } ++ ++ err = 0; ++ del->h_path.dentry = dget(au_h_dptr(root, bindex)); ++ del->h_path.mnt = mntget(au_sbr_mnt(sb, bindex)); ++ ++out: ++ aufs_read_unlock(root, !AuLock_IR); ++ return err; ++} ++#endif ++ ++static int noinline_for_stack ++au_opts_parse_mod(struct au_opt_mod *mod, substring_t args[]) ++{ ++ int err; ++ struct path path; ++ char *p; ++ ++ err = -EINVAL; ++ mod->path = args[0].from; ++ p = strchr(mod->path, '='); ++ if (unlikely(!p)) { ++ pr_err("no permssion %s\n", args[0].from); ++ goto out; ++ } ++ ++ *p++ = 0; ++ err = vfsub_kern_path(mod->path, lkup_dirflags, &path); ++ if (unlikely(err)) { ++ pr_err("lookup failed %s (%d)\n", mod->path, err); ++ goto out; ++ } ++ ++ mod->perm = br_perm_val(p); ++ AuDbg("mod path %s, perm 0x%x, %s\n", mod->path, mod->perm, p); ++ mod->h_root = dget(path.dentry); ++ path_put(&path); ++ ++out: ++ return err; ++} ++ ++#if 0 /* reserved for future use */ ++static int au_opts_parse_imod(struct super_block *sb, aufs_bindex_t bindex, ++ struct au_opt_mod *mod, substring_t args[]) ++{ ++ int err; ++ struct dentry *root; ++ ++ err = -EINVAL; ++ root = sb->s_root; ++ aufs_read_lock(root, AuLock_FLUSH); ++ if (bindex < 0 || au_sbend(sb) < bindex) { ++ pr_err("out of bounds, %d\n", bindex); ++ goto out; ++ } ++ ++ err = 0; ++ mod->perm = br_perm_val(args[1].from); ++ AuDbg("mod path %s, perm 0x%x, %s\n", ++ mod->path, mod->perm, args[1].from); ++ mod->h_root = dget(au_h_dptr(root, bindex)); ++ ++out: ++ aufs_read_unlock(root, !AuLock_IR); ++ return err; ++} ++#endif ++ ++static int au_opts_parse_xino(struct super_block *sb, struct au_opt_xino *xino, ++ substring_t args[]) ++{ ++ int err; ++ struct file *file; ++ ++ file = au_xino_create(sb, args[0].from, /*silent*/0); ++ err = PTR_ERR(file); ++ if (IS_ERR(file)) ++ goto out; ++ ++ err = -EINVAL; ++ if (unlikely(file->f_dentry->d_sb == sb)) { ++ fput(file); ++ pr_err("%s must be outside\n", args[0].from); ++ goto out; ++ } ++ ++ err = 0; ++ xino->file = file; ++ xino->path = args[0].from; ++ ++out: ++ return err; ++} ++ ++static int noinline_for_stack ++au_opts_parse_xino_itrunc_path(struct super_block *sb, ++ struct au_opt_xino_itrunc *xino_itrunc, ++ substring_t args[]) ++{ ++ int err; ++ aufs_bindex_t bend, bindex; ++ struct path path; ++ struct dentry *root; ++ ++ err = vfsub_kern_path(args[0].from, lkup_dirflags, &path); ++ if (unlikely(err)) { ++ pr_err("lookup failed %s (%d)\n", args[0].from, err); ++ goto out; ++ } ++ ++ xino_itrunc->bindex = -1; ++ root = sb->s_root; ++ aufs_read_lock(root, AuLock_FLUSH); ++ bend = au_sbend(sb); ++ for (bindex = 0; bindex <= bend; bindex++) { ++ if (au_h_dptr(root, bindex) == path.dentry) { ++ xino_itrunc->bindex = bindex; ++ break; ++ } ++ } ++ aufs_read_unlock(root, !AuLock_IR); ++ path_put(&path); ++ ++ if (unlikely(xino_itrunc->bindex < 0)) { ++ pr_err("no such branch %s\n", args[0].from); ++ err = -EINVAL; ++ } ++ ++out: ++ return err; ++} ++ ++/* called without aufs lock */ ++int au_opts_parse(struct super_block *sb, char *str, struct au_opts *opts) ++{ ++ int err, n, token; ++ aufs_bindex_t bindex; ++ unsigned char skipped; ++ struct dentry *root; ++ struct au_opt *opt, *opt_tail; ++ char *opt_str; ++ /* reduce the stack space */ ++ union { ++ struct au_opt_xino_itrunc *xino_itrunc; ++ struct au_opt_wbr_create *create; ++ } u; ++ struct { ++ substring_t args[MAX_OPT_ARGS]; ++ } *a; ++ ++ err = -ENOMEM; ++ a = kmalloc(sizeof(*a), GFP_NOFS); ++ if (unlikely(!a)) ++ goto out; ++ ++ root = sb->s_root; ++ err = 0; ++ bindex = 0; ++ opt = opts->opt; ++ opt_tail = opt + opts->max_opt - 1; ++ opt->type = Opt_tail; ++ while (!err && (opt_str = strsep(&str, ",")) && *opt_str) { ++ err = -EINVAL; ++ skipped = 0; ++ token = match_token(opt_str, options, a->args); ++ switch (token) { ++ case Opt_br: ++ err = 0; ++ while (!err && (opt_str = strsep(&a->args[0].from, ":")) ++ && *opt_str) { ++ err = opt_add(opt, opt_str, opts->sb_flags, ++ bindex++); ++ if (unlikely(!err && ++opt > opt_tail)) { ++ err = -E2BIG; ++ break; ++ } ++ opt->type = Opt_tail; ++ skipped = 1; ++ } ++ break; ++ case Opt_add: ++ if (unlikely(match_int(&a->args[0], &n))) { ++ pr_err("bad integer in %s\n", opt_str); ++ break; ++ } ++ bindex = n; ++ err = opt_add(opt, a->args[1].from, opts->sb_flags, ++ bindex); ++ if (!err) ++ opt->type = token; ++ break; ++ case Opt_append: ++ err = opt_add(opt, a->args[0].from, opts->sb_flags, ++ /*dummy bindex*/1); ++ if (!err) ++ opt->type = token; ++ break; ++ case Opt_prepend: ++ err = opt_add(opt, a->args[0].from, opts->sb_flags, ++ /*bindex*/0); ++ if (!err) ++ opt->type = token; ++ break; ++ case Opt_del: ++ err = au_opts_parse_del(&opt->del, a->args); ++ if (!err) ++ opt->type = token; ++ break; ++#if 0 /* reserved for future use */ ++ case Opt_idel: ++ del->pathname = "(indexed)"; ++ if (unlikely(match_int(&args[0], &n))) { ++ pr_err("bad integer in %s\n", opt_str); ++ break; ++ } ++ err = au_opts_parse_idel(sb, n, &opt->del, a->args); ++ if (!err) ++ opt->type = token; ++ break; ++#endif ++ case Opt_mod: ++ err = au_opts_parse_mod(&opt->mod, a->args); ++ if (!err) ++ opt->type = token; ++ break; ++#ifdef IMOD /* reserved for future use */ ++ case Opt_imod: ++ u.mod->path = "(indexed)"; ++ if (unlikely(match_int(&a->args[0], &n))) { ++ pr_err("bad integer in %s\n", opt_str); ++ break; ++ } ++ err = au_opts_parse_imod(sb, n, &opt->mod, a->args); ++ if (!err) ++ opt->type = token; ++ break; ++#endif ++ case Opt_xino: ++ err = au_opts_parse_xino(sb, &opt->xino, a->args); ++ if (!err) ++ opt->type = token; ++ break; ++ ++ case Opt_trunc_xino_path: ++ err = au_opts_parse_xino_itrunc_path ++ (sb, &opt->xino_itrunc, a->args); ++ if (!err) ++ opt->type = token; ++ break; ++ ++ case Opt_itrunc_xino: ++ u.xino_itrunc = &opt->xino_itrunc; ++ if (unlikely(match_int(&a->args[0], &n))) { ++ pr_err("bad integer in %s\n", opt_str); ++ break; ++ } ++ u.xino_itrunc->bindex = n; ++ aufs_read_lock(root, AuLock_FLUSH); ++ if (n < 0 || au_sbend(sb) < n) { ++ pr_err("out of bounds, %d\n", n); ++ aufs_read_unlock(root, !AuLock_IR); ++ break; ++ } ++ aufs_read_unlock(root, !AuLock_IR); ++ err = 0; ++ opt->type = token; ++ break; ++ ++ case Opt_dirwh: ++ if (unlikely(match_int(&a->args[0], &opt->dirwh))) ++ break; ++ err = 0; ++ opt->type = token; ++ break; ++ ++ case Opt_rdcache: ++ if (unlikely(match_int(&a->args[0], &n))) { ++ pr_err("bad integer in %s\n", opt_str); ++ break; ++ } ++ if (unlikely(n > AUFS_RDCACHE_MAX)) { ++ pr_err("rdcache must be smaller than %d\n", ++ AUFS_RDCACHE_MAX); ++ break; ++ } ++ opt->rdcache = n; ++ err = 0; ++ opt->type = token; ++ break; ++ case Opt_rdblk: ++ if (unlikely(match_int(&a->args[0], &n) ++ || n < 0 ++ || n > KMALLOC_MAX_SIZE)) { ++ pr_err("bad integer in %s\n", opt_str); ++ break; ++ } ++ if (unlikely(n && n < NAME_MAX)) { ++ pr_err("rdblk must be larger than %d\n", ++ NAME_MAX); ++ break; ++ } ++ opt->rdblk = n; ++ err = 0; ++ opt->type = token; ++ break; ++ case Opt_rdhash: ++ if (unlikely(match_int(&a->args[0], &n) ++ || n < 0 ++ || n * sizeof(struct hlist_head) ++ > KMALLOC_MAX_SIZE)) { ++ pr_err("bad integer in %s\n", opt_str); ++ break; ++ } ++ opt->rdhash = n; ++ err = 0; ++ opt->type = token; ++ break; ++ ++ case Opt_trunc_xino: ++ case Opt_notrunc_xino: ++ case Opt_noxino: ++ case Opt_trunc_xib: ++ case Opt_notrunc_xib: ++ case Opt_shwh: ++ case Opt_noshwh: ++ case Opt_dirperm1: ++ case Opt_nodirperm1: ++ case Opt_plink: ++ case Opt_noplink: ++ case Opt_list_plink: ++ case Opt_dio: ++ case Opt_nodio: ++ case Opt_diropq_a: ++ case Opt_diropq_w: ++ case Opt_warn_perm: ++ case Opt_nowarn_perm: ++ case Opt_refrof: ++ case Opt_norefrof: ++ case Opt_verbose: ++ case Opt_noverbose: ++ case Opt_sum: ++ case Opt_nosum: ++ case Opt_wsum: ++ case Opt_rdblk_def: ++ case Opt_rdhash_def: ++ case Opt_acl: ++ case Opt_noacl: ++ err = 0; ++ opt->type = token; ++ break; ++ ++ case Opt_udba: ++ opt->udba = udba_val(a->args[0].from); ++ if (opt->udba >= 0) { ++ err = 0; ++ opt->type = token; ++ } else ++ pr_err("wrong value, %s\n", opt_str); ++ break; ++ ++ case Opt_wbr_create: ++ u.create = &opt->wbr_create; ++ u.create->wbr_create ++ = au_wbr_create_val(a->args[0].from, u.create); ++ if (u.create->wbr_create >= 0) { ++ err = 0; ++ opt->type = token; ++ } else ++ pr_err("wrong value, %s\n", opt_str); ++ break; ++ case Opt_wbr_copyup: ++ opt->wbr_copyup = au_wbr_copyup_val(a->args[0].from); ++ if (opt->wbr_copyup >= 0) { ++ err = 0; ++ opt->type = token; ++ } else ++ pr_err("wrong value, %s\n", opt_str); ++ break; ++ ++ case Opt_fhsm_sec: ++ if (unlikely(match_int(&a->args[0], &n) ++ || n < 0)) { ++ pr_err("bad integer in %s\n", opt_str); ++ break; ++ } ++ if (sysaufs_brs) { ++ opt->fhsm_second = n; ++ opt->type = token; ++ } else ++ pr_warn("ignored %s\n", opt_str); ++ err = 0; ++ break; ++ ++ case Opt_ignore: ++ pr_warn("ignored %s\n", opt_str); ++ /*FALLTHROUGH*/ ++ case Opt_ignore_silent: ++ skipped = 1; ++ err = 0; ++ break; ++ case Opt_err: ++ pr_err("unknown option %s\n", opt_str); ++ break; ++ } ++ ++ if (!err && !skipped) { ++ if (unlikely(++opt > opt_tail)) { ++ err = -E2BIG; ++ opt--; ++ opt->type = Opt_tail; ++ break; ++ } ++ opt->type = Opt_tail; ++ } ++ } ++ ++ kfree(a); ++ dump_opts(opts); ++ if (unlikely(err)) ++ au_opts_free(opts); ++ ++out: ++ return err; ++} ++ ++static int au_opt_wbr_create(struct super_block *sb, ++ struct au_opt_wbr_create *create) ++{ ++ int err; ++ struct au_sbinfo *sbinfo; ++ ++ SiMustWriteLock(sb); ++ ++ err = 1; /* handled */ ++ sbinfo = au_sbi(sb); ++ if (sbinfo->si_wbr_create_ops->fin) { ++ err = sbinfo->si_wbr_create_ops->fin(sb); ++ if (!err) ++ err = 1; ++ } ++ ++ sbinfo->si_wbr_create = create->wbr_create; ++ sbinfo->si_wbr_create_ops = au_wbr_create_ops + create->wbr_create; ++ switch (create->wbr_create) { ++ case AuWbrCreate_MFSRRV: ++ case AuWbrCreate_MFSRR: ++ case AuWbrCreate_PMFSRR: ++ case AuWbrCreate_PMFSRRV: ++ sbinfo->si_wbr_mfs.mfsrr_watermark = create->mfsrr_watermark; ++ /*FALLTHROUGH*/ ++ case AuWbrCreate_MFS: ++ case AuWbrCreate_MFSV: ++ case AuWbrCreate_PMFS: ++ case AuWbrCreate_PMFSV: ++ sbinfo->si_wbr_mfs.mfs_expire ++ = msecs_to_jiffies(create->mfs_second * MSEC_PER_SEC); ++ break; ++ } ++ ++ if (sbinfo->si_wbr_create_ops->init) ++ sbinfo->si_wbr_create_ops->init(sb); /* ignore */ ++ ++ return err; ++} ++ ++/* ++ * returns, ++ * plus: processed without an error ++ * zero: unprocessed ++ */ ++static int au_opt_simple(struct super_block *sb, struct au_opt *opt, ++ struct au_opts *opts) ++{ ++ int err; ++ struct au_sbinfo *sbinfo; ++ ++ SiMustWriteLock(sb); ++ ++ err = 1; /* handled */ ++ sbinfo = au_sbi(sb); ++ switch (opt->type) { ++ case Opt_udba: ++ sbinfo->si_mntflags &= ~AuOptMask_UDBA; ++ sbinfo->si_mntflags |= opt->udba; ++ opts->given_udba |= opt->udba; ++ break; ++ ++ case Opt_plink: ++ au_opt_set(sbinfo->si_mntflags, PLINK); ++ break; ++ case Opt_noplink: ++ if (au_opt_test(sbinfo->si_mntflags, PLINK)) ++ au_plink_put(sb, /*verbose*/1); ++ au_opt_clr(sbinfo->si_mntflags, PLINK); ++ break; ++ case Opt_list_plink: ++ if (au_opt_test(sbinfo->si_mntflags, PLINK)) ++ au_plink_list(sb); ++ break; ++ ++ case Opt_dio: ++ au_opt_set(sbinfo->si_mntflags, DIO); ++ au_fset_opts(opts->flags, REFRESH_DYAOP); ++ break; ++ case Opt_nodio: ++ au_opt_clr(sbinfo->si_mntflags, DIO); ++ au_fset_opts(opts->flags, REFRESH_DYAOP); ++ break; ++ ++ case Opt_fhsm_sec: ++ au_fhsm_set(sbinfo, opt->fhsm_second); ++ break; ++ ++ case Opt_diropq_a: ++ au_opt_set(sbinfo->si_mntflags, ALWAYS_DIROPQ); ++ break; ++ case Opt_diropq_w: ++ au_opt_clr(sbinfo->si_mntflags, ALWAYS_DIROPQ); ++ break; ++ ++ case Opt_warn_perm: ++ au_opt_set(sbinfo->si_mntflags, WARN_PERM); ++ break; ++ case Opt_nowarn_perm: ++ au_opt_clr(sbinfo->si_mntflags, WARN_PERM); ++ break; ++ ++ case Opt_refrof: ++ au_opt_set(sbinfo->si_mntflags, REFROF); ++ break; ++ case Opt_norefrof: ++ au_opt_clr(sbinfo->si_mntflags, REFROF); ++ break; ++ ++ case Opt_verbose: ++ au_opt_set(sbinfo->si_mntflags, VERBOSE); ++ break; ++ case Opt_noverbose: ++ au_opt_clr(sbinfo->si_mntflags, VERBOSE); ++ break; ++ ++ case Opt_sum: ++ au_opt_set(sbinfo->si_mntflags, SUM); ++ break; ++ case Opt_wsum: ++ au_opt_clr(sbinfo->si_mntflags, SUM); ++ au_opt_set(sbinfo->si_mntflags, SUM_W); ++ case Opt_nosum: ++ au_opt_clr(sbinfo->si_mntflags, SUM); ++ au_opt_clr(sbinfo->si_mntflags, SUM_W); ++ break; ++ ++ case Opt_wbr_create: ++ err = au_opt_wbr_create(sb, &opt->wbr_create); ++ break; ++ case Opt_wbr_copyup: ++ sbinfo->si_wbr_copyup = opt->wbr_copyup; ++ sbinfo->si_wbr_copyup_ops = au_wbr_copyup_ops + opt->wbr_copyup; ++ break; ++ ++ case Opt_dirwh: ++ sbinfo->si_dirwh = opt->dirwh; ++ break; ++ ++ case Opt_rdcache: ++ sbinfo->si_rdcache ++ = msecs_to_jiffies(opt->rdcache * MSEC_PER_SEC); ++ break; ++ case Opt_rdblk: ++ sbinfo->si_rdblk = opt->rdblk; ++ break; ++ case Opt_rdblk_def: ++ sbinfo->si_rdblk = AUFS_RDBLK_DEF; ++ break; ++ case Opt_rdhash: ++ sbinfo->si_rdhash = opt->rdhash; ++ break; ++ case Opt_rdhash_def: ++ sbinfo->si_rdhash = AUFS_RDHASH_DEF; ++ break; ++ ++ case Opt_shwh: ++ au_opt_set(sbinfo->si_mntflags, SHWH); ++ break; ++ case Opt_noshwh: ++ au_opt_clr(sbinfo->si_mntflags, SHWH); ++ break; ++ ++ case Opt_dirperm1: ++ au_opt_set(sbinfo->si_mntflags, DIRPERM1); ++ break; ++ case Opt_nodirperm1: ++ au_opt_clr(sbinfo->si_mntflags, DIRPERM1); ++ break; ++ ++ case Opt_trunc_xino: ++ au_opt_set(sbinfo->si_mntflags, TRUNC_XINO); ++ break; ++ case Opt_notrunc_xino: ++ au_opt_clr(sbinfo->si_mntflags, TRUNC_XINO); ++ break; ++ ++ case Opt_trunc_xino_path: ++ case Opt_itrunc_xino: ++ err = au_xino_trunc(sb, opt->xino_itrunc.bindex); ++ if (!err) ++ err = 1; ++ break; ++ ++ case Opt_trunc_xib: ++ au_fset_opts(opts->flags, TRUNC_XIB); ++ break; ++ case Opt_notrunc_xib: ++ au_fclr_opts(opts->flags, TRUNC_XIB); ++ break; ++ ++ case Opt_acl: ++ sb->s_flags |= MS_POSIXACL; ++ break; ++ case Opt_noacl: ++ sb->s_flags &= ~MS_POSIXACL; ++ break; ++ ++ default: ++ err = 0; ++ break; ++ } ++ ++ return err; ++} ++ ++/* ++ * returns tri-state. ++ * plus: processed without an error ++ * zero: unprocessed ++ * minus: error ++ */ ++static int au_opt_br(struct super_block *sb, struct au_opt *opt, ++ struct au_opts *opts) ++{ ++ int err, do_refresh; ++ ++ err = 0; ++ switch (opt->type) { ++ case Opt_append: ++ opt->add.bindex = au_sbend(sb) + 1; ++ if (opt->add.bindex < 0) ++ opt->add.bindex = 0; ++ goto add; ++ case Opt_prepend: ++ opt->add.bindex = 0; ++ add: /* indented label */ ++ case Opt_add: ++ err = au_br_add(sb, &opt->add, ++ au_ftest_opts(opts->flags, REMOUNT)); ++ if (!err) { ++ err = 1; ++ au_fset_opts(opts->flags, REFRESH); ++ } ++ break; ++ ++ case Opt_del: ++ case Opt_idel: ++ err = au_br_del(sb, &opt->del, ++ au_ftest_opts(opts->flags, REMOUNT)); ++ if (!err) { ++ err = 1; ++ au_fset_opts(opts->flags, TRUNC_XIB); ++ au_fset_opts(opts->flags, REFRESH); ++ } ++ break; ++ ++ case Opt_mod: ++ case Opt_imod: ++ err = au_br_mod(sb, &opt->mod, ++ au_ftest_opts(opts->flags, REMOUNT), ++ &do_refresh); ++ if (!err) { ++ err = 1; ++ if (do_refresh) ++ au_fset_opts(opts->flags, REFRESH); ++ } ++ break; ++ } ++ ++ return err; ++} ++ ++static int au_opt_xino(struct super_block *sb, struct au_opt *opt, ++ struct au_opt_xino **opt_xino, ++ struct au_opts *opts) ++{ ++ int err; ++ aufs_bindex_t bend, bindex; ++ struct dentry *root, *parent, *h_root; ++ ++ err = 0; ++ switch (opt->type) { ++ case Opt_xino: ++ err = au_xino_set(sb, &opt->xino, ++ !!au_ftest_opts(opts->flags, REMOUNT)); ++ if (unlikely(err)) ++ break; ++ ++ *opt_xino = &opt->xino; ++ au_xino_brid_set(sb, -1); ++ ++ /* safe d_parent access */ ++ parent = opt->xino.file->f_dentry->d_parent; ++ root = sb->s_root; ++ bend = au_sbend(sb); ++ for (bindex = 0; bindex <= bend; bindex++) { ++ h_root = au_h_dptr(root, bindex); ++ if (h_root == parent) { ++ au_xino_brid_set(sb, au_sbr_id(sb, bindex)); ++ break; ++ } ++ } ++ break; ++ ++ case Opt_noxino: ++ au_xino_clr(sb); ++ au_xino_brid_set(sb, -1); ++ *opt_xino = (void *)-1; ++ break; ++ } ++ ++ return err; ++} ++ ++int au_opts_verify(struct super_block *sb, unsigned long sb_flags, ++ unsigned int pending) ++{ ++ int err, fhsm; ++ aufs_bindex_t bindex, bend; ++ unsigned char do_plink, skip, do_free, can_no_dreval; ++ struct au_branch *br; ++ struct au_wbr *wbr; ++ struct dentry *root, *dentry; ++ struct inode *dir, *h_dir; ++ struct au_sbinfo *sbinfo; ++ struct au_hinode *hdir; ++ ++ SiMustAnyLock(sb); ++ ++ sbinfo = au_sbi(sb); ++ AuDebugOn(!(sbinfo->si_mntflags & AuOptMask_UDBA)); ++ ++ if (!(sb_flags & MS_RDONLY)) { ++ if (unlikely(!au_br_writable(au_sbr_perm(sb, 0)))) ++ pr_warn("first branch should be rw\n"); ++ if (unlikely(au_opt_test(sbinfo->si_mntflags, SHWH))) ++ pr_warn_once("shwh should be used with ro\n"); ++ } ++ ++ if (au_opt_test((sbinfo->si_mntflags | pending), UDBA_HNOTIFY) ++ && !au_opt_test(sbinfo->si_mntflags, XINO)) ++ pr_warn_once("udba=*notify requires xino\n"); ++ ++ if (au_opt_test(sbinfo->si_mntflags, DIRPERM1)) ++ pr_warn_once("dirperm1 breaks the protection" ++ " by the permission bits on the lower branch\n"); ++ ++ err = 0; ++ fhsm = 0; ++ root = sb->s_root; ++ dir = root->d_inode; ++ do_plink = !!au_opt_test(sbinfo->si_mntflags, PLINK); ++ can_no_dreval = !!au_opt_test((sbinfo->si_mntflags | pending), ++ UDBA_NONE); ++ bend = au_sbend(sb); ++ for (bindex = 0; !err && bindex <= bend; bindex++) { ++ skip = 0; ++ h_dir = au_h_iptr(dir, bindex); ++ br = au_sbr(sb, bindex); ++ ++ if ((br->br_perm & AuBrAttr_ICEX) ++ && !h_dir->i_op->listxattr) ++ br->br_perm &= ~AuBrAttr_ICEX; ++#if 0 ++ if ((br->br_perm & AuBrAttr_ICEX_SEC) ++ && (au_br_sb(br)->s_flags & MS_NOSEC)) ++ br->br_perm &= ~AuBrAttr_ICEX_SEC; ++#endif ++ ++ do_free = 0; ++ wbr = br->br_wbr; ++ if (wbr) ++ wbr_wh_read_lock(wbr); ++ ++ if (!au_br_writable(br->br_perm)) { ++ do_free = !!wbr; ++ skip = (!wbr ++ || (!wbr->wbr_whbase ++ && !wbr->wbr_plink ++ && !wbr->wbr_orph)); ++ } else if (!au_br_wh_linkable(br->br_perm)) { ++ /* skip = (!br->br_whbase && !br->br_orph); */ ++ skip = (!wbr || !wbr->wbr_whbase); ++ if (skip && wbr) { ++ if (do_plink) ++ skip = !!wbr->wbr_plink; ++ else ++ skip = !wbr->wbr_plink; ++ } ++ } else { ++ /* skip = (br->br_whbase && br->br_ohph); */ ++ skip = (wbr && wbr->wbr_whbase); ++ if (skip) { ++ if (do_plink) ++ skip = !!wbr->wbr_plink; ++ else ++ skip = !wbr->wbr_plink; ++ } ++ } ++ if (wbr) ++ wbr_wh_read_unlock(wbr); ++ ++ if (can_no_dreval) { ++ dentry = br->br_path.dentry; ++ spin_lock(&dentry->d_lock); ++ if (dentry->d_flags & ++ (DCACHE_OP_REVALIDATE | DCACHE_OP_WEAK_REVALIDATE)) ++ can_no_dreval = 0; ++ spin_unlock(&dentry->d_lock); ++ } ++ ++ if (au_br_fhsm(br->br_perm)) { ++ fhsm++; ++ AuDebugOn(!br->br_fhsm); ++ } ++ ++ if (skip) ++ continue; ++ ++ hdir = au_hi(dir, bindex); ++ au_hn_imtx_lock_nested(hdir, AuLsc_I_PARENT); ++ if (wbr) ++ wbr_wh_write_lock(wbr); ++ err = au_wh_init(br, sb); ++ if (wbr) ++ wbr_wh_write_unlock(wbr); ++ au_hn_imtx_unlock(hdir); ++ ++ if (!err && do_free) { ++ kfree(wbr); ++ br->br_wbr = NULL; ++ } ++ } ++ ++ if (can_no_dreval) ++ au_fset_si(sbinfo, NO_DREVAL); ++ else ++ au_fclr_si(sbinfo, NO_DREVAL); ++ ++ if (fhsm >= 2) { ++ au_fset_si(sbinfo, FHSM); ++ for (bindex = bend; bindex >= 0; bindex--) { ++ br = au_sbr(sb, bindex); ++ if (au_br_fhsm(br->br_perm)) { ++ au_fhsm_set_bottom(sb, bindex); ++ break; ++ } ++ } ++ } else { ++ au_fclr_si(sbinfo, FHSM); ++ au_fhsm_set_bottom(sb, -1); ++ } ++ ++ return err; ++} ++ ++int au_opts_mount(struct super_block *sb, struct au_opts *opts) ++{ ++ int err; ++ unsigned int tmp; ++ aufs_bindex_t bindex, bend; ++ struct au_opt *opt; ++ struct au_opt_xino *opt_xino, xino; ++ struct au_sbinfo *sbinfo; ++ struct au_branch *br; ++ struct inode *dir; ++ ++ SiMustWriteLock(sb); ++ ++ err = 0; ++ opt_xino = NULL; ++ opt = opts->opt; ++ while (err >= 0 && opt->type != Opt_tail) ++ err = au_opt_simple(sb, opt++, opts); ++ if (err > 0) ++ err = 0; ++ else if (unlikely(err < 0)) ++ goto out; ++ ++ /* disable xino and udba temporary */ ++ sbinfo = au_sbi(sb); ++ tmp = sbinfo->si_mntflags; ++ au_opt_clr(sbinfo->si_mntflags, XINO); ++ au_opt_set_udba(sbinfo->si_mntflags, UDBA_REVAL); ++ ++ opt = opts->opt; ++ while (err >= 0 && opt->type != Opt_tail) ++ err = au_opt_br(sb, opt++, opts); ++ if (err > 0) ++ err = 0; ++ else if (unlikely(err < 0)) ++ goto out; ++ ++ bend = au_sbend(sb); ++ if (unlikely(bend < 0)) { ++ err = -EINVAL; ++ pr_err("no branches\n"); ++ goto out; ++ } ++ ++ if (au_opt_test(tmp, XINO)) ++ au_opt_set(sbinfo->si_mntflags, XINO); ++ opt = opts->opt; ++ while (!err && opt->type != Opt_tail) ++ err = au_opt_xino(sb, opt++, &opt_xino, opts); ++ if (unlikely(err)) ++ goto out; ++ ++ err = au_opts_verify(sb, sb->s_flags, tmp); ++ if (unlikely(err)) ++ goto out; ++ ++ /* restore xino */ ++ if (au_opt_test(tmp, XINO) && !opt_xino) { ++ xino.file = au_xino_def(sb); ++ err = PTR_ERR(xino.file); ++ if (IS_ERR(xino.file)) ++ goto out; ++ ++ err = au_xino_set(sb, &xino, /*remount*/0); ++ fput(xino.file); ++ if (unlikely(err)) ++ goto out; ++ } ++ ++ /* restore udba */ ++ tmp &= AuOptMask_UDBA; ++ sbinfo->si_mntflags &= ~AuOptMask_UDBA; ++ sbinfo->si_mntflags |= tmp; ++ bend = au_sbend(sb); ++ for (bindex = 0; bindex <= bend; bindex++) { ++ br = au_sbr(sb, bindex); ++ err = au_hnotify_reset_br(tmp, br, br->br_perm); ++ if (unlikely(err)) ++ AuIOErr("hnotify failed on br %d, %d, ignored\n", ++ bindex, err); ++ /* go on even if err */ ++ } ++ if (au_opt_test(tmp, UDBA_HNOTIFY)) { ++ dir = sb->s_root->d_inode; ++ au_hn_reset(dir, au_hi_flags(dir, /*isdir*/1) & ~AuHi_XINO); ++ } ++ ++out: ++ return err; ++} ++ ++int au_opts_remount(struct super_block *sb, struct au_opts *opts) ++{ ++ int err, rerr; ++ unsigned char no_dreval; ++ struct inode *dir; ++ struct au_opt_xino *opt_xino; ++ struct au_opt *opt; ++ struct au_sbinfo *sbinfo; ++ ++ SiMustWriteLock(sb); ++ ++ err = 0; ++ dir = sb->s_root->d_inode; ++ sbinfo = au_sbi(sb); ++ opt_xino = NULL; ++ opt = opts->opt; ++ while (err >= 0 && opt->type != Opt_tail) { ++ err = au_opt_simple(sb, opt, opts); ++ if (!err) ++ err = au_opt_br(sb, opt, opts); ++ if (!err) ++ err = au_opt_xino(sb, opt, &opt_xino, opts); ++ opt++; ++ } ++ if (err > 0) ++ err = 0; ++ AuTraceErr(err); ++ /* go on even err */ ++ ++ no_dreval = !!au_ftest_si(sbinfo, NO_DREVAL); ++ rerr = au_opts_verify(sb, opts->sb_flags, /*pending*/0); ++ if (unlikely(rerr && !err)) ++ err = rerr; ++ ++ if (no_dreval != !!au_ftest_si(sbinfo, NO_DREVAL)) ++ au_fset_opts(opts->flags, REFRESH_IDOP); ++ ++ if (au_ftest_opts(opts->flags, TRUNC_XIB)) { ++ rerr = au_xib_trunc(sb); ++ if (unlikely(rerr && !err)) ++ err = rerr; ++ } ++ ++ /* will be handled by the caller */ ++ if (!au_ftest_opts(opts->flags, REFRESH) ++ && (opts->given_udba ++ || au_opt_test(sbinfo->si_mntflags, XINO) ++ || au_ftest_opts(opts->flags, REFRESH_IDOP) ++ )) ++ au_fset_opts(opts->flags, REFRESH); ++ ++ AuDbg("status 0x%x\n", opts->flags); ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++unsigned int au_opt_udba(struct super_block *sb) ++{ ++ return au_mntflags(sb) & AuOptMask_UDBA; ++} +diff --git a/fs/aufs/opts.h b/fs/aufs/opts.h +new file mode 100644 +index 0000000..50949a0 +--- /dev/null ++++ b/fs/aufs/opts.h +@@ -0,0 +1,212 @@ ++/* ++ * Copyright (C) 2005-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * mount options/flags ++ */ ++ ++#ifndef __AUFS_OPTS_H__ ++#define __AUFS_OPTS_H__ ++ ++#ifdef __KERNEL__ ++ ++#include ++ ++struct file; ++struct super_block; ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* mount flags */ ++#define AuOpt_XINO 1 /* external inode number bitmap ++ and translation table */ ++#define AuOpt_TRUNC_XINO (1 << 1) /* truncate xino files */ ++#define AuOpt_UDBA_NONE (1 << 2) /* users direct branch access */ ++#define AuOpt_UDBA_REVAL (1 << 3) ++#define AuOpt_UDBA_HNOTIFY (1 << 4) ++#define AuOpt_SHWH (1 << 5) /* show whiteout */ ++#define AuOpt_PLINK (1 << 6) /* pseudo-link */ ++#define AuOpt_DIRPERM1 (1 << 7) /* ignore the lower dir's perm ++ bits */ ++#define AuOpt_REFROF (1 << 8) /* unimplemented */ ++#define AuOpt_ALWAYS_DIROPQ (1 << 9) /* policy to creating diropq */ ++#define AuOpt_SUM (1 << 10) /* summation for statfs(2) */ ++#define AuOpt_SUM_W (1 << 11) /* unimplemented */ ++#define AuOpt_WARN_PERM (1 << 12) /* warn when add-branch */ ++#define AuOpt_VERBOSE (1 << 13) /* busy inode when del-branch */ ++#define AuOpt_DIO (1 << 14) /* direct io */ ++ ++#ifndef CONFIG_AUFS_HNOTIFY ++#undef AuOpt_UDBA_HNOTIFY ++#define AuOpt_UDBA_HNOTIFY 0 ++#endif ++#ifndef CONFIG_AUFS_SHWH ++#undef AuOpt_SHWH ++#define AuOpt_SHWH 0 ++#endif ++ ++#define AuOpt_Def (AuOpt_XINO \ ++ | AuOpt_UDBA_REVAL \ ++ | AuOpt_PLINK \ ++ /* | AuOpt_DIRPERM1 */ \ ++ | AuOpt_WARN_PERM) ++#define AuOptMask_UDBA (AuOpt_UDBA_NONE \ ++ | AuOpt_UDBA_REVAL \ ++ | AuOpt_UDBA_HNOTIFY) ++ ++#define au_opt_test(flags, name) (flags & AuOpt_##name) ++#define au_opt_set(flags, name) do { \ ++ BUILD_BUG_ON(AuOpt_##name & AuOptMask_UDBA); \ ++ ((flags) |= AuOpt_##name); \ ++} while (0) ++#define au_opt_set_udba(flags, name) do { \ ++ (flags) &= ~AuOptMask_UDBA; \ ++ ((flags) |= AuOpt_##name); \ ++} while (0) ++#define au_opt_clr(flags, name) do { \ ++ ((flags) &= ~AuOpt_##name); \ ++} while (0) ++ ++static inline unsigned int au_opts_plink(unsigned int mntflags) ++{ ++#ifdef CONFIG_PROC_FS ++ return mntflags; ++#else ++ return mntflags & ~AuOpt_PLINK; ++#endif ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* policies to select one among multiple writable branches */ ++enum { ++ AuWbrCreate_TDP, /* top down parent */ ++ AuWbrCreate_RR, /* round robin */ ++ AuWbrCreate_MFS, /* most free space */ ++ AuWbrCreate_MFSV, /* mfs with seconds */ ++ AuWbrCreate_MFSRR, /* mfs then rr */ ++ AuWbrCreate_MFSRRV, /* mfs then rr with seconds */ ++ AuWbrCreate_PMFS, /* parent and mfs */ ++ AuWbrCreate_PMFSV, /* parent and mfs with seconds */ ++ AuWbrCreate_PMFSRR, /* parent, mfs and round-robin */ ++ AuWbrCreate_PMFSRRV, /* plus seconds */ ++ ++ AuWbrCreate_Def = AuWbrCreate_TDP ++}; ++ ++enum { ++ AuWbrCopyup_TDP, /* top down parent */ ++ AuWbrCopyup_BUP, /* bottom up parent */ ++ AuWbrCopyup_BU, /* bottom up */ ++ ++ AuWbrCopyup_Def = AuWbrCopyup_TDP ++}; ++ ++/* ---------------------------------------------------------------------- */ ++ ++struct au_opt_add { ++ aufs_bindex_t bindex; ++ char *pathname; ++ int perm; ++ struct path path; ++}; ++ ++struct au_opt_del { ++ char *pathname; ++ struct path h_path; ++}; ++ ++struct au_opt_mod { ++ char *path; ++ int perm; ++ struct dentry *h_root; ++}; ++ ++struct au_opt_xino { ++ char *path; ++ struct file *file; ++}; ++ ++struct au_opt_xino_itrunc { ++ aufs_bindex_t bindex; ++}; ++ ++struct au_opt_wbr_create { ++ int wbr_create; ++ int mfs_second; ++ unsigned long long mfsrr_watermark; ++}; ++ ++struct au_opt { ++ int type; ++ union { ++ struct au_opt_xino xino; ++ struct au_opt_xino_itrunc xino_itrunc; ++ struct au_opt_add add; ++ struct au_opt_del del; ++ struct au_opt_mod mod; ++ int dirwh; ++ int rdcache; ++ unsigned int rdblk; ++ unsigned int rdhash; ++ int udba; ++ struct au_opt_wbr_create wbr_create; ++ int wbr_copyup; ++ unsigned int fhsm_second; ++ }; ++}; ++ ++/* opts flags */ ++#define AuOpts_REMOUNT 1 ++#define AuOpts_REFRESH (1 << 1) ++#define AuOpts_TRUNC_XIB (1 << 2) ++#define AuOpts_REFRESH_DYAOP (1 << 3) ++#define AuOpts_REFRESH_IDOP (1 << 4) ++#define au_ftest_opts(flags, name) ((flags) & AuOpts_##name) ++#define au_fset_opts(flags, name) \ ++ do { (flags) |= AuOpts_##name; } while (0) ++#define au_fclr_opts(flags, name) \ ++ do { (flags) &= ~AuOpts_##name; } while (0) ++ ++struct au_opts { ++ struct au_opt *opt; ++ int max_opt; ++ ++ unsigned int given_udba; ++ unsigned int flags; ++ unsigned long sb_flags; ++}; ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* opts.c */ ++void au_optstr_br_perm(au_br_perm_str_t *str, int perm); ++const char *au_optstr_udba(int udba); ++const char *au_optstr_wbr_copyup(int wbr_copyup); ++const char *au_optstr_wbr_create(int wbr_create); ++ ++void au_opts_free(struct au_opts *opts); ++int au_opts_parse(struct super_block *sb, char *str, struct au_opts *opts); ++int au_opts_verify(struct super_block *sb, unsigned long sb_flags, ++ unsigned int pending); ++int au_opts_mount(struct super_block *sb, struct au_opts *opts); ++int au_opts_remount(struct super_block *sb, struct au_opts *opts); ++ ++unsigned int au_opt_udba(struct super_block *sb); ++ ++#endif /* __KERNEL__ */ ++#endif /* __AUFS_OPTS_H__ */ +diff --git a/fs/aufs/plink.c b/fs/aufs/plink.c +new file mode 100644 +index 0000000..4f372ec +--- /dev/null ++++ b/fs/aufs/plink.c +@@ -0,0 +1,506 @@ ++/* ++ * Copyright (C) 2005-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * pseudo-link ++ */ ++ ++#include "aufs.h" ++ ++/* ++ * the pseudo-link maintenance mode. ++ * during a user process maintains the pseudo-links, ++ * prohibit adding a new plink and branch manipulation. ++ * ++ * Flags ++ * NOPLM: ++ * For entry functions which will handle plink, and i_mutex is already held ++ * in VFS. ++ * They cannot wait and should return an error at once. ++ * Callers has to check the error. ++ * NOPLMW: ++ * For entry functions which will handle plink, but i_mutex is not held ++ * in VFS. ++ * They can wait the plink maintenance mode to finish. ++ * ++ * They behave like F_SETLK and F_SETLKW. ++ * If the caller never handle plink, then both flags are unnecessary. ++ */ ++ ++int au_plink_maint(struct super_block *sb, int flags) ++{ ++ int err; ++ pid_t pid, ppid; ++ struct au_sbinfo *sbi; ++ ++ SiMustAnyLock(sb); ++ ++ err = 0; ++ if (!au_opt_test(au_mntflags(sb), PLINK)) ++ goto out; ++ ++ sbi = au_sbi(sb); ++ pid = sbi->si_plink_maint_pid; ++ if (!pid || pid == current->pid) ++ goto out; ++ ++ /* todo: it highly depends upon /sbin/mount.aufs */ ++ rcu_read_lock(); ++ ppid = task_pid_vnr(rcu_dereference(current->real_parent)); ++ rcu_read_unlock(); ++ if (pid == ppid) ++ goto out; ++ ++ if (au_ftest_lock(flags, NOPLMW)) { ++ /* if there is no i_mutex lock in VFS, we don't need to wait */ ++ /* AuDebugOn(!lockdep_depth(current)); */ ++ while (sbi->si_plink_maint_pid) { ++ si_read_unlock(sb); ++ /* gave up wake_up_bit() */ ++ wait_event(sbi->si_plink_wq, !sbi->si_plink_maint_pid); ++ ++ if (au_ftest_lock(flags, FLUSH)) ++ au_nwt_flush(&sbi->si_nowait); ++ si_noflush_read_lock(sb); ++ } ++ } else if (au_ftest_lock(flags, NOPLM)) { ++ AuDbg("ppid %d, pid %d\n", ppid, pid); ++ err = -EAGAIN; ++ } ++ ++out: ++ return err; ++} ++ ++void au_plink_maint_leave(struct au_sbinfo *sbinfo) ++{ ++ spin_lock(&sbinfo->si_plink_maint_lock); ++ sbinfo->si_plink_maint_pid = 0; ++ spin_unlock(&sbinfo->si_plink_maint_lock); ++ wake_up_all(&sbinfo->si_plink_wq); ++} ++ ++int au_plink_maint_enter(struct super_block *sb) ++{ ++ int err; ++ struct au_sbinfo *sbinfo; ++ ++ err = 0; ++ sbinfo = au_sbi(sb); ++ /* make sure i am the only one in this fs */ ++ si_write_lock(sb, AuLock_FLUSH); ++ if (au_opt_test(au_mntflags(sb), PLINK)) { ++ spin_lock(&sbinfo->si_plink_maint_lock); ++ if (!sbinfo->si_plink_maint_pid) ++ sbinfo->si_plink_maint_pid = current->pid; ++ else ++ err = -EBUSY; ++ spin_unlock(&sbinfo->si_plink_maint_lock); ++ } ++ si_write_unlock(sb); ++ ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++#ifdef CONFIG_AUFS_DEBUG ++void au_plink_list(struct super_block *sb) ++{ ++ int i; ++ struct au_sbinfo *sbinfo; ++ struct hlist_head *plink_hlist; ++ struct au_icntnr *icntnr; ++ ++ SiMustAnyLock(sb); ++ ++ sbinfo = au_sbi(sb); ++ AuDebugOn(!au_opt_test(au_mntflags(sb), PLINK)); ++ AuDebugOn(au_plink_maint(sb, AuLock_NOPLM)); ++ ++ for (i = 0; i < AuPlink_NHASH; i++) { ++ plink_hlist = &sbinfo->si_plink[i].head; ++ rcu_read_lock(); ++ hlist_for_each_entry_rcu(icntnr, plink_hlist, plink) ++ AuDbg("%lu\n", icntnr->vfs_inode.i_ino); ++ rcu_read_unlock(); ++ } ++} ++#endif ++ ++/* is the inode pseudo-linked? */ ++int au_plink_test(struct inode *inode) ++{ ++ int found, i; ++ struct au_sbinfo *sbinfo; ++ struct hlist_head *plink_hlist; ++ struct au_icntnr *icntnr; ++ ++ sbinfo = au_sbi(inode->i_sb); ++ AuRwMustAnyLock(&sbinfo->si_rwsem); ++ AuDebugOn(!au_opt_test(au_mntflags(inode->i_sb), PLINK)); ++ AuDebugOn(au_plink_maint(inode->i_sb, AuLock_NOPLM)); ++ ++ found = 0; ++ i = au_plink_hash(inode->i_ino); ++ plink_hlist = &sbinfo->si_plink[i].head; ++ rcu_read_lock(); ++ hlist_for_each_entry_rcu(icntnr, plink_hlist, plink) ++ if (&icntnr->vfs_inode == inode) { ++ found = 1; ++ break; ++ } ++ rcu_read_unlock(); ++ return found; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* ++ * generate a name for plink. ++ * the file will be stored under AUFS_WH_PLINKDIR. ++ */ ++/* 20 is max digits length of ulong 64 */ ++#define PLINK_NAME_LEN ((20 + 1) * 2) ++ ++static int plink_name(char *name, int len, struct inode *inode, ++ aufs_bindex_t bindex) ++{ ++ int rlen; ++ struct inode *h_inode; ++ ++ h_inode = au_h_iptr(inode, bindex); ++ rlen = snprintf(name, len, "%lu.%lu", inode->i_ino, h_inode->i_ino); ++ return rlen; ++} ++ ++struct au_do_plink_lkup_args { ++ struct dentry **errp; ++ struct qstr *tgtname; ++ struct dentry *h_parent; ++ struct au_branch *br; ++}; ++ ++static struct dentry *au_do_plink_lkup(struct qstr *tgtname, ++ struct dentry *h_parent, ++ struct au_branch *br) ++{ ++ struct dentry *h_dentry; ++ struct mutex *h_mtx; ++ ++ h_mtx = &h_parent->d_inode->i_mutex; ++ mutex_lock_nested(h_mtx, AuLsc_I_CHILD2); ++ h_dentry = vfsub_lkup_one(tgtname, h_parent); ++ mutex_unlock(h_mtx); ++ return h_dentry; ++} ++ ++static void au_call_do_plink_lkup(void *args) ++{ ++ struct au_do_plink_lkup_args *a = args; ++ *a->errp = au_do_plink_lkup(a->tgtname, a->h_parent, a->br); ++} ++ ++/* lookup the plink-ed @inode under the branch at @bindex */ ++struct dentry *au_plink_lkup(struct inode *inode, aufs_bindex_t bindex) ++{ ++ struct dentry *h_dentry, *h_parent; ++ struct au_branch *br; ++ struct inode *h_dir; ++ int wkq_err; ++ char a[PLINK_NAME_LEN]; ++ struct qstr tgtname = QSTR_INIT(a, 0); ++ ++ AuDebugOn(au_plink_maint(inode->i_sb, AuLock_NOPLM)); ++ ++ br = au_sbr(inode->i_sb, bindex); ++ h_parent = br->br_wbr->wbr_plink; ++ h_dir = h_parent->d_inode; ++ tgtname.len = plink_name(a, sizeof(a), inode, bindex); ++ ++ if (!uid_eq(current_fsuid(), GLOBAL_ROOT_UID)) { ++ struct au_do_plink_lkup_args args = { ++ .errp = &h_dentry, ++ .tgtname = &tgtname, ++ .h_parent = h_parent, ++ .br = br ++ }; ++ ++ wkq_err = au_wkq_wait(au_call_do_plink_lkup, &args); ++ if (unlikely(wkq_err)) ++ h_dentry = ERR_PTR(wkq_err); ++ } else ++ h_dentry = au_do_plink_lkup(&tgtname, h_parent, br); ++ ++ return h_dentry; ++} ++ ++/* create a pseudo-link */ ++static int do_whplink(struct qstr *tgt, struct dentry *h_parent, ++ struct dentry *h_dentry, struct au_branch *br) ++{ ++ int err; ++ struct path h_path = { ++ .mnt = au_br_mnt(br) ++ }; ++ struct inode *h_dir, *delegated; ++ ++ h_dir = h_parent->d_inode; ++ mutex_lock_nested(&h_dir->i_mutex, AuLsc_I_CHILD2); ++again: ++ h_path.dentry = vfsub_lkup_one(tgt, h_parent); ++ err = PTR_ERR(h_path.dentry); ++ if (IS_ERR(h_path.dentry)) ++ goto out; ++ ++ err = 0; ++ /* wh.plink dir is not monitored */ ++ /* todo: is it really safe? */ ++ if (h_path.dentry->d_inode ++ && h_path.dentry->d_inode != h_dentry->d_inode) { ++ delegated = NULL; ++ err = vfsub_unlink(h_dir, &h_path, &delegated, /*force*/0); ++ if (unlikely(err == -EWOULDBLOCK)) { ++ pr_warn("cannot retry for NFSv4 delegation" ++ " for an internal unlink\n"); ++ iput(delegated); ++ } ++ dput(h_path.dentry); ++ h_path.dentry = NULL; ++ if (!err) ++ goto again; ++ } ++ if (!err && !h_path.dentry->d_inode) { ++ delegated = NULL; ++ err = vfsub_link(h_dentry, h_dir, &h_path, &delegated); ++ if (unlikely(err == -EWOULDBLOCK)) { ++ pr_warn("cannot retry for NFSv4 delegation" ++ " for an internal link\n"); ++ iput(delegated); ++ } ++ } ++ dput(h_path.dentry); ++ ++out: ++ mutex_unlock(&h_dir->i_mutex); ++ return err; ++} ++ ++struct do_whplink_args { ++ int *errp; ++ struct qstr *tgt; ++ struct dentry *h_parent; ++ struct dentry *h_dentry; ++ struct au_branch *br; ++}; ++ ++static void call_do_whplink(void *args) ++{ ++ struct do_whplink_args *a = args; ++ *a->errp = do_whplink(a->tgt, a->h_parent, a->h_dentry, a->br); ++} ++ ++static int whplink(struct dentry *h_dentry, struct inode *inode, ++ aufs_bindex_t bindex, struct au_branch *br) ++{ ++ int err, wkq_err; ++ struct au_wbr *wbr; ++ struct dentry *h_parent; ++ struct inode *h_dir; ++ char a[PLINK_NAME_LEN]; ++ struct qstr tgtname = QSTR_INIT(a, 0); ++ ++ wbr = au_sbr(inode->i_sb, bindex)->br_wbr; ++ h_parent = wbr->wbr_plink; ++ h_dir = h_parent->d_inode; ++ tgtname.len = plink_name(a, sizeof(a), inode, bindex); ++ ++ /* always superio. */ ++ if (!uid_eq(current_fsuid(), GLOBAL_ROOT_UID)) { ++ struct do_whplink_args args = { ++ .errp = &err, ++ .tgt = &tgtname, ++ .h_parent = h_parent, ++ .h_dentry = h_dentry, ++ .br = br ++ }; ++ wkq_err = au_wkq_wait(call_do_whplink, &args); ++ if (unlikely(wkq_err)) ++ err = wkq_err; ++ } else ++ err = do_whplink(&tgtname, h_parent, h_dentry, br); ++ ++ return err; ++} ++ ++/* ++ * create a new pseudo-link for @h_dentry on @bindex. ++ * the linked inode is held in aufs @inode. ++ */ ++void au_plink_append(struct inode *inode, aufs_bindex_t bindex, ++ struct dentry *h_dentry) ++{ ++ struct super_block *sb; ++ struct au_sbinfo *sbinfo; ++ struct hlist_head *plink_hlist; ++ struct au_icntnr *icntnr; ++ struct au_sphlhead *sphl; ++ int found, err, cnt, i; ++ ++ sb = inode->i_sb; ++ sbinfo = au_sbi(sb); ++ AuDebugOn(!au_opt_test(au_mntflags(sb), PLINK)); ++ AuDebugOn(au_plink_maint(sb, AuLock_NOPLM)); ++ ++ found = au_plink_test(inode); ++ if (found) ++ return; ++ ++ i = au_plink_hash(inode->i_ino); ++ sphl = sbinfo->si_plink + i; ++ plink_hlist = &sphl->head; ++ au_igrab(inode); ++ ++ spin_lock(&sphl->spin); ++ hlist_for_each_entry(icntnr, plink_hlist, plink) { ++ if (&icntnr->vfs_inode == inode) { ++ found = 1; ++ break; ++ } ++ } ++ if (!found) { ++ icntnr = container_of(inode, struct au_icntnr, vfs_inode); ++ hlist_add_head_rcu(&icntnr->plink, plink_hlist); ++ } ++ spin_unlock(&sphl->spin); ++ if (!found) { ++ cnt = au_sphl_count(sphl); ++#define msg "unexpectedly unblanced or too many pseudo-links" ++ if (cnt > AUFS_PLINK_WARN) ++ AuWarn1(msg ", %d\n", cnt); ++#undef msg ++ err = whplink(h_dentry, inode, bindex, au_sbr(sb, bindex)); ++ if (unlikely(err)) { ++ pr_warn("err %d, damaged pseudo link.\n", err); ++ au_sphl_del_rcu(&icntnr->plink, sphl); ++ iput(&icntnr->vfs_inode); ++ } ++ } else ++ iput(&icntnr->vfs_inode); ++} ++ ++/* free all plinks */ ++void au_plink_put(struct super_block *sb, int verbose) ++{ ++ int i, warned; ++ struct au_sbinfo *sbinfo; ++ struct hlist_head *plink_hlist; ++ struct hlist_node *tmp; ++ struct au_icntnr *icntnr; ++ ++ SiMustWriteLock(sb); ++ ++ sbinfo = au_sbi(sb); ++ AuDebugOn(!au_opt_test(au_mntflags(sb), PLINK)); ++ AuDebugOn(au_plink_maint(sb, AuLock_NOPLM)); ++ ++ /* no spin_lock since sbinfo is write-locked */ ++ warned = 0; ++ for (i = 0; i < AuPlink_NHASH; i++) { ++ plink_hlist = &sbinfo->si_plink[i].head; ++ if (!warned && verbose && !hlist_empty(plink_hlist)) { ++ pr_warn("pseudo-link is not flushed"); ++ warned = 1; ++ } ++ hlist_for_each_entry_safe(icntnr, tmp, plink_hlist, plink) ++ iput(&icntnr->vfs_inode); ++ INIT_HLIST_HEAD(plink_hlist); ++ } ++} ++ ++void au_plink_clean(struct super_block *sb, int verbose) ++{ ++ struct dentry *root; ++ ++ root = sb->s_root; ++ aufs_write_lock(root); ++ if (au_opt_test(au_mntflags(sb), PLINK)) ++ au_plink_put(sb, verbose); ++ aufs_write_unlock(root); ++} ++ ++static int au_plink_do_half_refresh(struct inode *inode, aufs_bindex_t br_id) ++{ ++ int do_put; ++ aufs_bindex_t bstart, bend, bindex; ++ ++ do_put = 0; ++ bstart = au_ibstart(inode); ++ bend = au_ibend(inode); ++ if (bstart >= 0) { ++ for (bindex = bstart; bindex <= bend; bindex++) { ++ if (!au_h_iptr(inode, bindex) ++ || au_ii_br_id(inode, bindex) != br_id) ++ continue; ++ au_set_h_iptr(inode, bindex, NULL, 0); ++ do_put = 1; ++ break; ++ } ++ if (do_put) ++ for (bindex = bstart; bindex <= bend; bindex++) ++ if (au_h_iptr(inode, bindex)) { ++ do_put = 0; ++ break; ++ } ++ } else ++ do_put = 1; ++ ++ return do_put; ++} ++ ++/* free the plinks on a branch specified by @br_id */ ++void au_plink_half_refresh(struct super_block *sb, aufs_bindex_t br_id) ++{ ++ struct au_sbinfo *sbinfo; ++ struct hlist_head *plink_hlist; ++ struct hlist_node *tmp; ++ struct au_icntnr *icntnr; ++ struct inode *inode; ++ int i, do_put; ++ ++ SiMustWriteLock(sb); ++ ++ sbinfo = au_sbi(sb); ++ AuDebugOn(!au_opt_test(au_mntflags(sb), PLINK)); ++ AuDebugOn(au_plink_maint(sb, AuLock_NOPLM)); ++ ++ /* no spin_lock since sbinfo is write-locked */ ++ for (i = 0; i < AuPlink_NHASH; i++) { ++ plink_hlist = &sbinfo->si_plink[i].head; ++ hlist_for_each_entry_safe(icntnr, tmp, plink_hlist, plink) { ++ inode = au_igrab(&icntnr->vfs_inode); ++ ii_write_lock_child(inode); ++ do_put = au_plink_do_half_refresh(inode, br_id); ++ if (do_put) { ++ hlist_del(&icntnr->plink); ++ iput(inode); ++ } ++ ii_write_unlock(inode); ++ iput(inode); ++ } ++ } ++} +diff --git a/fs/aufs/poll.c b/fs/aufs/poll.c +new file mode 100644 +index 0000000..eea19e7 +--- /dev/null ++++ b/fs/aufs/poll.c +@@ -0,0 +1,52 @@ ++/* ++ * Copyright (C) 2005-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * poll operation ++ * There is only one filesystem which implements ->poll operation, currently. ++ */ ++ ++#include "aufs.h" ++ ++unsigned int aufs_poll(struct file *file, poll_table *wait) ++{ ++ unsigned int mask; ++ int err; ++ struct file *h_file; ++ struct super_block *sb; ++ ++ /* We should pretend an error happened. */ ++ mask = POLLERR /* | POLLIN | POLLOUT */; ++ sb = file->f_dentry->d_sb; ++ si_read_lock(sb, AuLock_FLUSH | AuLock_NOPLMW); ++ ++ h_file = au_read_pre(file, /*keep_fi*/0); ++ err = PTR_ERR(h_file); ++ if (IS_ERR(h_file)) ++ goto out; ++ ++ /* it is not an error if h_file has no operation */ ++ mask = DEFAULT_POLLMASK; ++ if (h_file->f_op->poll) ++ mask = h_file->f_op->poll(h_file, wait); ++ fput(h_file); /* instead of au_read_post() */ ++ ++out: ++ si_read_unlock(sb); ++ AuTraceErr((int)mask); ++ return mask; ++} +diff --git a/fs/aufs/posix_acl.c b/fs/aufs/posix_acl.c +new file mode 100644 +index 0000000..89b4127 +--- /dev/null ++++ b/fs/aufs/posix_acl.c +@@ -0,0 +1,98 @@ ++/* ++ * Copyright (C) 2014-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * posix acl operations ++ */ ++ ++#include ++#include "aufs.h" ++ ++struct posix_acl *aufs_get_acl(struct inode *inode, int type) ++{ ++ struct posix_acl *acl; ++ int err; ++ aufs_bindex_t bindex; ++ struct inode *h_inode; ++ struct super_block *sb; ++ ++ acl = NULL; ++ sb = inode->i_sb; ++ si_read_lock(sb, AuLock_FLUSH); ++ ii_read_lock_child(inode); ++ if (!(sb->s_flags & MS_POSIXACL)) ++ goto out; ++ ++ bindex = au_ibstart(inode); ++ h_inode = au_h_iptr(inode, bindex); ++ if (unlikely(!h_inode ++ || ((h_inode->i_mode & S_IFMT) ++ != (inode->i_mode & S_IFMT)))) { ++ err = au_busy_or_stale(); ++ acl = ERR_PTR(err); ++ goto out; ++ } ++ ++ /* always topmost only */ ++ acl = get_acl(h_inode, type); ++ ++out: ++ ii_read_unlock(inode); ++ si_read_unlock(sb); ++ ++ AuTraceErrPtr(acl); ++ return acl; ++} ++ ++int aufs_set_acl(struct inode *inode, struct posix_acl *acl, int type) ++{ ++ int err; ++ ssize_t ssz; ++ struct dentry *dentry; ++ struct au_srxattr arg = { ++ .type = AU_ACL_SET, ++ .u.acl_set = { ++ .acl = acl, ++ .type = type ++ }, ++ }; ++ ++ mutex_lock(&inode->i_mutex); ++ if (inode->i_ino == AUFS_ROOT_INO) ++ dentry = dget(inode->i_sb->s_root); ++ else { ++ dentry = d_find_alias(inode); ++ if (!dentry) ++ dentry = d_find_any_alias(inode); ++ if (!dentry) { ++ pr_warn("cannot handle this inode, " ++ "please report to aufs-users ML\n"); ++ err = -ENOENT; ++ goto out; ++ } ++ } ++ ++ ssz = au_srxattr(dentry, &arg); ++ dput(dentry); ++ err = ssz; ++ if (ssz >= 0) ++ err = 0; ++ ++out: ++ mutex_unlock(&inode->i_mutex); ++ return err; ++} +diff --git a/fs/aufs/procfs.c b/fs/aufs/procfs.c +new file mode 100644 +index 0000000..a334330 +--- /dev/null ++++ b/fs/aufs/procfs.c +@@ -0,0 +1,169 @@ ++/* ++ * Copyright (C) 2010-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * procfs interfaces ++ */ ++ ++#include ++#include "aufs.h" ++ ++static int au_procfs_plm_release(struct inode *inode, struct file *file) ++{ ++ struct au_sbinfo *sbinfo; ++ ++ sbinfo = file->private_data; ++ if (sbinfo) { ++ au_plink_maint_leave(sbinfo); ++ kobject_put(&sbinfo->si_kobj); ++ } ++ ++ return 0; ++} ++ ++static void au_procfs_plm_write_clean(struct file *file) ++{ ++ struct au_sbinfo *sbinfo; ++ ++ sbinfo = file->private_data; ++ if (sbinfo) ++ au_plink_clean(sbinfo->si_sb, /*verbose*/0); ++} ++ ++static int au_procfs_plm_write_si(struct file *file, unsigned long id) ++{ ++ int err; ++ struct super_block *sb; ++ struct au_sbinfo *sbinfo; ++ ++ err = -EBUSY; ++ if (unlikely(file->private_data)) ++ goto out; ++ ++ sb = NULL; ++ /* don't use au_sbilist_lock() here */ ++ spin_lock(&au_sbilist.spin); ++ hlist_for_each_entry(sbinfo, &au_sbilist.head, si_list) ++ if (id == sysaufs_si_id(sbinfo)) { ++ kobject_get(&sbinfo->si_kobj); ++ sb = sbinfo->si_sb; ++ break; ++ } ++ spin_unlock(&au_sbilist.spin); ++ ++ err = -EINVAL; ++ if (unlikely(!sb)) ++ goto out; ++ ++ err = au_plink_maint_enter(sb); ++ if (!err) ++ /* keep kobject_get() */ ++ file->private_data = sbinfo; ++ else ++ kobject_put(&sbinfo->si_kobj); ++out: ++ return err; ++} ++ ++/* ++ * Accept a valid "si=xxxx" only. ++ * Once it is accepted successfully, accept "clean" too. ++ */ ++static ssize_t au_procfs_plm_write(struct file *file, const char __user *ubuf, ++ size_t count, loff_t *ppos) ++{ ++ ssize_t err; ++ unsigned long id; ++ /* last newline is allowed */ ++ char buf[3 + sizeof(unsigned long) * 2 + 1]; ++ ++ err = -EACCES; ++ if (unlikely(!capable(CAP_SYS_ADMIN))) ++ goto out; ++ ++ err = -EINVAL; ++ if (unlikely(count > sizeof(buf))) ++ goto out; ++ ++ err = copy_from_user(buf, ubuf, count); ++ if (unlikely(err)) { ++ err = -EFAULT; ++ goto out; ++ } ++ buf[count] = 0; ++ ++ err = -EINVAL; ++ if (!strcmp("clean", buf)) { ++ au_procfs_plm_write_clean(file); ++ goto out_success; ++ } else if (unlikely(strncmp("si=", buf, 3))) ++ goto out; ++ ++ err = kstrtoul(buf + 3, 16, &id); ++ if (unlikely(err)) ++ goto out; ++ ++ err = au_procfs_plm_write_si(file, id); ++ if (unlikely(err)) ++ goto out; ++ ++out_success: ++ err = count; /* success */ ++out: ++ return err; ++} ++ ++static const struct file_operations au_procfs_plm_fop = { ++ .write = au_procfs_plm_write, ++ .release = au_procfs_plm_release, ++ .owner = THIS_MODULE ++}; ++ ++/* ---------------------------------------------------------------------- */ ++ ++static struct proc_dir_entry *au_procfs_dir; ++ ++void au_procfs_fin(void) ++{ ++ remove_proc_entry(AUFS_PLINK_MAINT_NAME, au_procfs_dir); ++ remove_proc_entry(AUFS_PLINK_MAINT_DIR, NULL); ++} ++ ++int __init au_procfs_init(void) ++{ ++ int err; ++ struct proc_dir_entry *entry; ++ ++ err = -ENOMEM; ++ au_procfs_dir = proc_mkdir(AUFS_PLINK_MAINT_DIR, NULL); ++ if (unlikely(!au_procfs_dir)) ++ goto out; ++ ++ entry = proc_create(AUFS_PLINK_MAINT_NAME, S_IFREG | S_IWUSR, ++ au_procfs_dir, &au_procfs_plm_fop); ++ if (unlikely(!entry)) ++ goto out_dir; ++ ++ err = 0; ++ goto out; /* success */ ++ ++ ++out_dir: ++ remove_proc_entry(AUFS_PLINK_MAINT_DIR, NULL); ++out: ++ return err; ++} +diff --git a/fs/aufs/rdu.c b/fs/aufs/rdu.c +new file mode 100644 +index 0000000..d22b2f8 +--- /dev/null ++++ b/fs/aufs/rdu.c +@@ -0,0 +1,388 @@ ++/* ++ * Copyright (C) 2005-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * readdir in userspace. ++ */ ++ ++#include ++#include ++#include ++#include "aufs.h" ++ ++/* bits for struct aufs_rdu.flags */ ++#define AuRdu_CALLED 1 ++#define AuRdu_CONT (1 << 1) ++#define AuRdu_FULL (1 << 2) ++#define au_ftest_rdu(flags, name) ((flags) & AuRdu_##name) ++#define au_fset_rdu(flags, name) \ ++ do { (flags) |= AuRdu_##name; } while (0) ++#define au_fclr_rdu(flags, name) \ ++ do { (flags) &= ~AuRdu_##name; } while (0) ++ ++struct au_rdu_arg { ++ struct dir_context ctx; ++ struct aufs_rdu *rdu; ++ union au_rdu_ent_ul ent; ++ unsigned long end; ++ ++ struct super_block *sb; ++ int err; ++}; ++ ++static int au_rdu_fill(struct dir_context *ctx, const char *name, int nlen, ++ loff_t offset, u64 h_ino, unsigned int d_type) ++{ ++ int err, len; ++ struct au_rdu_arg *arg = container_of(ctx, struct au_rdu_arg, ctx); ++ struct aufs_rdu *rdu = arg->rdu; ++ struct au_rdu_ent ent; ++ ++ err = 0; ++ arg->err = 0; ++ au_fset_rdu(rdu->cookie.flags, CALLED); ++ len = au_rdu_len(nlen); ++ if (arg->ent.ul + len < arg->end) { ++ ent.ino = h_ino; ++ ent.bindex = rdu->cookie.bindex; ++ ent.type = d_type; ++ ent.nlen = nlen; ++ if (unlikely(nlen > AUFS_MAX_NAMELEN)) ++ ent.type = DT_UNKNOWN; ++ ++ /* unnecessary to support mmap_sem since this is a dir */ ++ err = -EFAULT; ++ if (copy_to_user(arg->ent.e, &ent, sizeof(ent))) ++ goto out; ++ if (copy_to_user(arg->ent.e->name, name, nlen)) ++ goto out; ++ /* the terminating NULL */ ++ if (__put_user(0, arg->ent.e->name + nlen)) ++ goto out; ++ err = 0; ++ /* AuDbg("%p, %.*s\n", arg->ent.p, nlen, name); */ ++ arg->ent.ul += len; ++ rdu->rent++; ++ } else { ++ err = -EFAULT; ++ au_fset_rdu(rdu->cookie.flags, FULL); ++ rdu->full = 1; ++ rdu->tail = arg->ent; ++ } ++ ++out: ++ /* AuTraceErr(err); */ ++ return err; ++} ++ ++static int au_rdu_do(struct file *h_file, struct au_rdu_arg *arg) ++{ ++ int err; ++ loff_t offset; ++ struct au_rdu_cookie *cookie = &arg->rdu->cookie; ++ ++ /* we don't have to care (FMODE_32BITHASH | FMODE_64BITHASH) for ext4 */ ++ offset = vfsub_llseek(h_file, cookie->h_pos, SEEK_SET); ++ err = offset; ++ if (unlikely(offset != cookie->h_pos)) ++ goto out; ++ ++ err = 0; ++ do { ++ arg->err = 0; ++ au_fclr_rdu(cookie->flags, CALLED); ++ /* smp_mb(); */ ++ err = vfsub_iterate_dir(h_file, &arg->ctx); ++ if (err >= 0) ++ err = arg->err; ++ } while (!err ++ && au_ftest_rdu(cookie->flags, CALLED) ++ && !au_ftest_rdu(cookie->flags, FULL)); ++ cookie->h_pos = h_file->f_pos; ++ ++out: ++ AuTraceErr(err); ++ return err; ++} ++ ++static int au_rdu(struct file *file, struct aufs_rdu *rdu) ++{ ++ int err; ++ aufs_bindex_t bend; ++ struct au_rdu_arg arg = { ++ .ctx = { ++ .actor = au_diractor(au_rdu_fill) ++ } ++ }; ++ struct dentry *dentry; ++ struct inode *inode; ++ struct file *h_file; ++ struct au_rdu_cookie *cookie = &rdu->cookie; ++ ++ err = !access_ok(VERIFY_WRITE, rdu->ent.e, rdu->sz); ++ if (unlikely(err)) { ++ err = -EFAULT; ++ AuTraceErr(err); ++ goto out; ++ } ++ rdu->rent = 0; ++ rdu->tail = rdu->ent; ++ rdu->full = 0; ++ arg.rdu = rdu; ++ arg.ent = rdu->ent; ++ arg.end = arg.ent.ul; ++ arg.end += rdu->sz; ++ ++ err = -ENOTDIR; ++ if (unlikely(!file->f_op->iterate)) ++ goto out; ++ ++ err = security_file_permission(file, MAY_READ); ++ AuTraceErr(err); ++ if (unlikely(err)) ++ goto out; ++ ++ dentry = file->f_dentry; ++ inode = dentry->d_inode; ++#if 1 ++ mutex_lock(&inode->i_mutex); ++#else ++ err = mutex_lock_killable(&inode->i_mutex); ++ AuTraceErr(err); ++ if (unlikely(err)) ++ goto out; ++#endif ++ ++ arg.sb = inode->i_sb; ++ err = si_read_lock(arg.sb, AuLock_FLUSH | AuLock_NOPLM); ++ if (unlikely(err)) ++ goto out_mtx; ++ err = au_alive_dir(dentry); ++ if (unlikely(err)) ++ goto out_si; ++ /* todo: reval? */ ++ fi_read_lock(file); ++ ++ err = -EAGAIN; ++ if (unlikely(au_ftest_rdu(cookie->flags, CONT) ++ && cookie->generation != au_figen(file))) ++ goto out_unlock; ++ ++ err = 0; ++ if (!rdu->blk) { ++ rdu->blk = au_sbi(arg.sb)->si_rdblk; ++ if (!rdu->blk) ++ rdu->blk = au_dir_size(file, /*dentry*/NULL); ++ } ++ bend = au_fbstart(file); ++ if (cookie->bindex < bend) ++ cookie->bindex = bend; ++ bend = au_fbend_dir(file); ++ /* AuDbg("b%d, b%d\n", cookie->bindex, bend); */ ++ for (; !err && cookie->bindex <= bend; ++ cookie->bindex++, cookie->h_pos = 0) { ++ h_file = au_hf_dir(file, cookie->bindex); ++ if (!h_file) ++ continue; ++ ++ au_fclr_rdu(cookie->flags, FULL); ++ err = au_rdu_do(h_file, &arg); ++ AuTraceErr(err); ++ if (unlikely(au_ftest_rdu(cookie->flags, FULL) || err)) ++ break; ++ } ++ AuDbg("rent %llu\n", rdu->rent); ++ ++ if (!err && !au_ftest_rdu(cookie->flags, CONT)) { ++ rdu->shwh = !!au_opt_test(au_sbi(arg.sb)->si_mntflags, SHWH); ++ au_fset_rdu(cookie->flags, CONT); ++ cookie->generation = au_figen(file); ++ } ++ ++ ii_read_lock_child(inode); ++ fsstack_copy_attr_atime(inode, au_h_iptr(inode, au_ibstart(inode))); ++ ii_read_unlock(inode); ++ ++out_unlock: ++ fi_read_unlock(file); ++out_si: ++ si_read_unlock(arg.sb); ++out_mtx: ++ mutex_unlock(&inode->i_mutex); ++out: ++ AuTraceErr(err); ++ return err; ++} ++ ++static int au_rdu_ino(struct file *file, struct aufs_rdu *rdu) ++{ ++ int err; ++ ino_t ino; ++ unsigned long long nent; ++ union au_rdu_ent_ul *u; ++ struct au_rdu_ent ent; ++ struct super_block *sb; ++ ++ err = 0; ++ nent = rdu->nent; ++ u = &rdu->ent; ++ sb = file->f_dentry->d_sb; ++ si_read_lock(sb, AuLock_FLUSH); ++ while (nent-- > 0) { ++ /* unnecessary to support mmap_sem since this is a dir */ ++ err = copy_from_user(&ent, u->e, sizeof(ent)); ++ if (!err) ++ err = !access_ok(VERIFY_WRITE, &u->e->ino, sizeof(ino)); ++ if (unlikely(err)) { ++ err = -EFAULT; ++ AuTraceErr(err); ++ break; ++ } ++ ++ /* AuDbg("b%d, i%llu\n", ent.bindex, ent.ino); */ ++ if (!ent.wh) ++ err = au_ino(sb, ent.bindex, ent.ino, ent.type, &ino); ++ else ++ err = au_wh_ino(sb, ent.bindex, ent.ino, ent.type, ++ &ino); ++ if (unlikely(err)) { ++ AuTraceErr(err); ++ break; ++ } ++ ++ err = __put_user(ino, &u->e->ino); ++ if (unlikely(err)) { ++ err = -EFAULT; ++ AuTraceErr(err); ++ break; ++ } ++ u->ul += au_rdu_len(ent.nlen); ++ } ++ si_read_unlock(sb); ++ ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++static int au_rdu_verify(struct aufs_rdu *rdu) ++{ ++ AuDbg("rdu{%llu, %p, %u | %u | %llu, %u, %u | " ++ "%llu, b%d, 0x%x, g%u}\n", ++ rdu->sz, rdu->ent.e, rdu->verify[AufsCtlRduV_SZ], ++ rdu->blk, ++ rdu->rent, rdu->shwh, rdu->full, ++ rdu->cookie.h_pos, rdu->cookie.bindex, rdu->cookie.flags, ++ rdu->cookie.generation); ++ ++ if (rdu->verify[AufsCtlRduV_SZ] == sizeof(*rdu)) ++ return 0; ++ ++ AuDbg("%u:%u\n", ++ rdu->verify[AufsCtlRduV_SZ], (unsigned int)sizeof(*rdu)); ++ return -EINVAL; ++} ++ ++long au_rdu_ioctl(struct file *file, unsigned int cmd, unsigned long arg) ++{ ++ long err, e; ++ struct aufs_rdu rdu; ++ void __user *p = (void __user *)arg; ++ ++ err = copy_from_user(&rdu, p, sizeof(rdu)); ++ if (unlikely(err)) { ++ err = -EFAULT; ++ AuTraceErr(err); ++ goto out; ++ } ++ err = au_rdu_verify(&rdu); ++ if (unlikely(err)) ++ goto out; ++ ++ switch (cmd) { ++ case AUFS_CTL_RDU: ++ err = au_rdu(file, &rdu); ++ if (unlikely(err)) ++ break; ++ ++ e = copy_to_user(p, &rdu, sizeof(rdu)); ++ if (unlikely(e)) { ++ err = -EFAULT; ++ AuTraceErr(err); ++ } ++ break; ++ case AUFS_CTL_RDU_INO: ++ err = au_rdu_ino(file, &rdu); ++ break; ++ ++ default: ++ /* err = -ENOTTY; */ ++ err = -EINVAL; ++ } ++ ++out: ++ AuTraceErr(err); ++ return err; ++} ++ ++#ifdef CONFIG_COMPAT ++long au_rdu_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg) ++{ ++ long err, e; ++ struct aufs_rdu rdu; ++ void __user *p = compat_ptr(arg); ++ ++ /* todo: get_user()? */ ++ err = copy_from_user(&rdu, p, sizeof(rdu)); ++ if (unlikely(err)) { ++ err = -EFAULT; ++ AuTraceErr(err); ++ goto out; ++ } ++ rdu.ent.e = compat_ptr(rdu.ent.ul); ++ err = au_rdu_verify(&rdu); ++ if (unlikely(err)) ++ goto out; ++ ++ switch (cmd) { ++ case AUFS_CTL_RDU: ++ err = au_rdu(file, &rdu); ++ if (unlikely(err)) ++ break; ++ ++ rdu.ent.ul = ptr_to_compat(rdu.ent.e); ++ rdu.tail.ul = ptr_to_compat(rdu.tail.e); ++ e = copy_to_user(p, &rdu, sizeof(rdu)); ++ if (unlikely(e)) { ++ err = -EFAULT; ++ AuTraceErr(err); ++ } ++ break; ++ case AUFS_CTL_RDU_INO: ++ err = au_rdu_ino(file, &rdu); ++ break; ++ ++ default: ++ /* err = -ENOTTY; */ ++ err = -EINVAL; ++ } ++ ++out: ++ AuTraceErr(err); ++ return err; ++} ++#endif +diff --git a/fs/aufs/rwsem.h b/fs/aufs/rwsem.h +new file mode 100644 +index 0000000..09ed5a0 +--- /dev/null ++++ b/fs/aufs/rwsem.h +@@ -0,0 +1,191 @@ ++/* ++ * Copyright (C) 2005-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * simple read-write semaphore wrappers ++ */ ++ ++#ifndef __AUFS_RWSEM_H__ ++#define __AUFS_RWSEM_H__ ++ ++#ifdef __KERNEL__ ++ ++#include "debug.h" ++ ++struct au_rwsem { ++ struct rw_semaphore rwsem; ++#ifdef CONFIG_AUFS_DEBUG ++ /* just for debugging, not almighty counter */ ++ atomic_t rcnt, wcnt; ++#endif ++}; ++ ++#ifdef CONFIG_AUFS_DEBUG ++#define AuDbgCntInit(rw) do { \ ++ atomic_set(&(rw)->rcnt, 0); \ ++ atomic_set(&(rw)->wcnt, 0); \ ++ smp_mb(); /* atomic set */ \ ++} while (0) ++ ++#define AuDbgRcntInc(rw) atomic_inc(&(rw)->rcnt) ++#define AuDbgRcntDec(rw) WARN_ON(atomic_dec_return(&(rw)->rcnt) < 0) ++#define AuDbgWcntInc(rw) atomic_inc(&(rw)->wcnt) ++#define AuDbgWcntDec(rw) WARN_ON(atomic_dec_return(&(rw)->wcnt) < 0) ++#else ++#define AuDbgCntInit(rw) do {} while (0) ++#define AuDbgRcntInc(rw) do {} while (0) ++#define AuDbgRcntDec(rw) do {} while (0) ++#define AuDbgWcntInc(rw) do {} while (0) ++#define AuDbgWcntDec(rw) do {} while (0) ++#endif /* CONFIG_AUFS_DEBUG */ ++ ++/* to debug easier, do not make them inlined functions */ ++#define AuRwMustNoWaiters(rw) AuDebugOn(!list_empty(&(rw)->rwsem.wait_list)) ++/* rwsem_is_locked() is unusable */ ++#define AuRwMustReadLock(rw) AuDebugOn(atomic_read(&(rw)->rcnt) <= 0) ++#define AuRwMustWriteLock(rw) AuDebugOn(atomic_read(&(rw)->wcnt) <= 0) ++#define AuRwMustAnyLock(rw) AuDebugOn(atomic_read(&(rw)->rcnt) <= 0 \ ++ && atomic_read(&(rw)->wcnt) <= 0) ++#define AuRwDestroy(rw) AuDebugOn(atomic_read(&(rw)->rcnt) \ ++ || atomic_read(&(rw)->wcnt)) ++ ++#define au_rw_class(rw, key) lockdep_set_class(&(rw)->rwsem, key) ++ ++static inline void au_rw_init(struct au_rwsem *rw) ++{ ++ AuDbgCntInit(rw); ++ init_rwsem(&rw->rwsem); ++} ++ ++static inline void au_rw_init_wlock(struct au_rwsem *rw) ++{ ++ au_rw_init(rw); ++ down_write(&rw->rwsem); ++ AuDbgWcntInc(rw); ++} ++ ++static inline void au_rw_init_wlock_nested(struct au_rwsem *rw, ++ unsigned int lsc) ++{ ++ au_rw_init(rw); ++ down_write_nested(&rw->rwsem, lsc); ++ AuDbgWcntInc(rw); ++} ++ ++static inline void au_rw_read_lock(struct au_rwsem *rw) ++{ ++ down_read(&rw->rwsem); ++ AuDbgRcntInc(rw); ++} ++ ++static inline void au_rw_read_lock_nested(struct au_rwsem *rw, unsigned int lsc) ++{ ++ down_read_nested(&rw->rwsem, lsc); ++ AuDbgRcntInc(rw); ++} ++ ++static inline void au_rw_read_unlock(struct au_rwsem *rw) ++{ ++ AuRwMustReadLock(rw); ++ AuDbgRcntDec(rw); ++ up_read(&rw->rwsem); ++} ++ ++static inline void au_rw_dgrade_lock(struct au_rwsem *rw) ++{ ++ AuRwMustWriteLock(rw); ++ AuDbgRcntInc(rw); ++ AuDbgWcntDec(rw); ++ downgrade_write(&rw->rwsem); ++} ++ ++static inline void au_rw_write_lock(struct au_rwsem *rw) ++{ ++ down_write(&rw->rwsem); ++ AuDbgWcntInc(rw); ++} ++ ++static inline void au_rw_write_lock_nested(struct au_rwsem *rw, ++ unsigned int lsc) ++{ ++ down_write_nested(&rw->rwsem, lsc); ++ AuDbgWcntInc(rw); ++} ++ ++static inline void au_rw_write_unlock(struct au_rwsem *rw) ++{ ++ AuRwMustWriteLock(rw); ++ AuDbgWcntDec(rw); ++ up_write(&rw->rwsem); ++} ++ ++/* why is not _nested version defined */ ++static inline int au_rw_read_trylock(struct au_rwsem *rw) ++{ ++ int ret; ++ ++ ret = down_read_trylock(&rw->rwsem); ++ if (ret) ++ AuDbgRcntInc(rw); ++ return ret; ++} ++ ++static inline int au_rw_write_trylock(struct au_rwsem *rw) ++{ ++ int ret; ++ ++ ret = down_write_trylock(&rw->rwsem); ++ if (ret) ++ AuDbgWcntInc(rw); ++ return ret; ++} ++ ++#undef AuDbgCntInit ++#undef AuDbgRcntInc ++#undef AuDbgRcntDec ++#undef AuDbgWcntInc ++#undef AuDbgWcntDec ++ ++#define AuSimpleLockRwsemFuncs(prefix, param, rwsem) \ ++static inline void prefix##_read_lock(param) \ ++{ au_rw_read_lock(rwsem); } \ ++static inline void prefix##_write_lock(param) \ ++{ au_rw_write_lock(rwsem); } \ ++static inline int prefix##_read_trylock(param) \ ++{ return au_rw_read_trylock(rwsem); } \ ++static inline int prefix##_write_trylock(param) \ ++{ return au_rw_write_trylock(rwsem); } ++/* why is not _nested version defined */ ++/* static inline void prefix##_read_trylock_nested(param, lsc) ++{ au_rw_read_trylock_nested(rwsem, lsc)); } ++static inline void prefix##_write_trylock_nestd(param, lsc) ++{ au_rw_write_trylock_nested(rwsem, lsc); } */ ++ ++#define AuSimpleUnlockRwsemFuncs(prefix, param, rwsem) \ ++static inline void prefix##_read_unlock(param) \ ++{ au_rw_read_unlock(rwsem); } \ ++static inline void prefix##_write_unlock(param) \ ++{ au_rw_write_unlock(rwsem); } \ ++static inline void prefix##_downgrade_lock(param) \ ++{ au_rw_dgrade_lock(rwsem); } ++ ++#define AuSimpleRwsemFuncs(prefix, param, rwsem) \ ++ AuSimpleLockRwsemFuncs(prefix, param, rwsem) \ ++ AuSimpleUnlockRwsemFuncs(prefix, param, rwsem) ++ ++#endif /* __KERNEL__ */ ++#endif /* __AUFS_RWSEM_H__ */ +diff --git a/fs/aufs/sbinfo.c b/fs/aufs/sbinfo.c +new file mode 100644 +index 0000000..ff13c9f +--- /dev/null ++++ b/fs/aufs/sbinfo.c +@@ -0,0 +1,348 @@ ++/* ++ * Copyright (C) 2005-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * superblock private data ++ */ ++ ++#include "aufs.h" ++ ++/* ++ * they are necessary regardless sysfs is disabled. ++ */ ++void au_si_free(struct kobject *kobj) ++{ ++ int i; ++ struct au_sbinfo *sbinfo; ++ char *locked __maybe_unused; /* debug only */ ++ ++ sbinfo = container_of(kobj, struct au_sbinfo, si_kobj); ++ for (i = 0; i < AuPlink_NHASH; i++) ++ AuDebugOn(!hlist_empty(&sbinfo->si_plink[i].head)); ++ AuDebugOn(atomic_read(&sbinfo->si_nowait.nw_len)); ++ ++ au_rw_write_lock(&sbinfo->si_rwsem); ++ au_br_free(sbinfo); ++ au_rw_write_unlock(&sbinfo->si_rwsem); ++ ++ kfree(sbinfo->si_branch); ++ for (i = 0; i < AU_NPIDMAP; i++) ++ kfree(sbinfo->au_si_pid.pid_bitmap[i]); ++ mutex_destroy(&sbinfo->au_si_pid.pid_mtx); ++ mutex_destroy(&sbinfo->si_xib_mtx); ++ AuRwDestroy(&sbinfo->si_rwsem); ++ ++ kfree(sbinfo); ++} ++ ++int au_si_alloc(struct super_block *sb) ++{ ++ int err, i; ++ struct au_sbinfo *sbinfo; ++ static struct lock_class_key aufs_si; ++ ++ err = -ENOMEM; ++ sbinfo = kzalloc(sizeof(*sbinfo), GFP_NOFS); ++ if (unlikely(!sbinfo)) ++ goto out; ++ ++ /* will be reallocated separately */ ++ sbinfo->si_branch = kzalloc(sizeof(*sbinfo->si_branch), GFP_NOFS); ++ if (unlikely(!sbinfo->si_branch)) ++ goto out_sbinfo; ++ ++ err = sysaufs_si_init(sbinfo); ++ if (unlikely(err)) ++ goto out_br; ++ ++ au_nwt_init(&sbinfo->si_nowait); ++ au_rw_init_wlock(&sbinfo->si_rwsem); ++ au_rw_class(&sbinfo->si_rwsem, &aufs_si); ++ mutex_init(&sbinfo->au_si_pid.pid_mtx); ++ ++ atomic_long_set(&sbinfo->si_ninodes, 0); ++ atomic_long_set(&sbinfo->si_nfiles, 0); ++ ++ sbinfo->si_bend = -1; ++ sbinfo->si_last_br_id = AUFS_BRANCH_MAX / 2; ++ ++ sbinfo->si_wbr_copyup = AuWbrCopyup_Def; ++ sbinfo->si_wbr_create = AuWbrCreate_Def; ++ sbinfo->si_wbr_copyup_ops = au_wbr_copyup_ops + sbinfo->si_wbr_copyup; ++ sbinfo->si_wbr_create_ops = au_wbr_create_ops + sbinfo->si_wbr_create; ++ ++ au_fhsm_init(sbinfo); ++ ++ sbinfo->si_mntflags = au_opts_plink(AuOpt_Def); ++ ++ sbinfo->si_xino_jiffy = jiffies; ++ sbinfo->si_xino_expire ++ = msecs_to_jiffies(AUFS_XINO_DEF_SEC * MSEC_PER_SEC); ++ mutex_init(&sbinfo->si_xib_mtx); ++ sbinfo->si_xino_brid = -1; ++ /* leave si_xib_last_pindex and si_xib_next_bit */ ++ ++ au_sphl_init(&sbinfo->si_aopen); ++ ++ sbinfo->si_rdcache = msecs_to_jiffies(AUFS_RDCACHE_DEF * MSEC_PER_SEC); ++ sbinfo->si_rdblk = AUFS_RDBLK_DEF; ++ sbinfo->si_rdhash = AUFS_RDHASH_DEF; ++ sbinfo->si_dirwh = AUFS_DIRWH_DEF; ++ ++ for (i = 0; i < AuPlink_NHASH; i++) ++ au_sphl_init(sbinfo->si_plink + i); ++ init_waitqueue_head(&sbinfo->si_plink_wq); ++ spin_lock_init(&sbinfo->si_plink_maint_lock); ++ ++ au_sphl_init(&sbinfo->si_files); ++ ++ /* with getattr by default */ ++ sbinfo->si_iop_array = aufs_iop; ++ ++ /* leave other members for sysaufs and si_mnt. */ ++ sbinfo->si_sb = sb; ++ sb->s_fs_info = sbinfo; ++ si_pid_set(sb); ++ return 0; /* success */ ++ ++out_br: ++ kfree(sbinfo->si_branch); ++out_sbinfo: ++ kfree(sbinfo); ++out: ++ return err; ++} ++ ++int au_sbr_realloc(struct au_sbinfo *sbinfo, int nbr) ++{ ++ int err, sz; ++ struct au_branch **brp; ++ ++ AuRwMustWriteLock(&sbinfo->si_rwsem); ++ ++ err = -ENOMEM; ++ sz = sizeof(*brp) * (sbinfo->si_bend + 1); ++ if (unlikely(!sz)) ++ sz = sizeof(*brp); ++ brp = au_kzrealloc(sbinfo->si_branch, sz, sizeof(*brp) * nbr, GFP_NOFS); ++ if (brp) { ++ sbinfo->si_branch = brp; ++ err = 0; ++ } ++ ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++unsigned int au_sigen_inc(struct super_block *sb) ++{ ++ unsigned int gen; ++ ++ SiMustWriteLock(sb); ++ ++ gen = ++au_sbi(sb)->si_generation; ++ au_update_digen(sb->s_root); ++ au_update_iigen(sb->s_root->d_inode, /*half*/0); ++ sb->s_root->d_inode->i_version++; ++ return gen; ++} ++ ++aufs_bindex_t au_new_br_id(struct super_block *sb) ++{ ++ aufs_bindex_t br_id; ++ int i; ++ struct au_sbinfo *sbinfo; ++ ++ SiMustWriteLock(sb); ++ ++ sbinfo = au_sbi(sb); ++ for (i = 0; i <= AUFS_BRANCH_MAX; i++) { ++ br_id = ++sbinfo->si_last_br_id; ++ AuDebugOn(br_id < 0); ++ if (br_id && au_br_index(sb, br_id) < 0) ++ return br_id; ++ } ++ ++ return -1; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* it is ok that new 'nwt' tasks are appended while we are sleeping */ ++int si_read_lock(struct super_block *sb, int flags) ++{ ++ int err; ++ ++ err = 0; ++ if (au_ftest_lock(flags, FLUSH)) ++ au_nwt_flush(&au_sbi(sb)->si_nowait); ++ ++ si_noflush_read_lock(sb); ++ err = au_plink_maint(sb, flags); ++ if (unlikely(err)) ++ si_read_unlock(sb); ++ ++ return err; ++} ++ ++int si_write_lock(struct super_block *sb, int flags) ++{ ++ int err; ++ ++ if (au_ftest_lock(flags, FLUSH)) ++ au_nwt_flush(&au_sbi(sb)->si_nowait); ++ ++ si_noflush_write_lock(sb); ++ err = au_plink_maint(sb, flags); ++ if (unlikely(err)) ++ si_write_unlock(sb); ++ ++ return err; ++} ++ ++/* dentry and super_block lock. call at entry point */ ++int aufs_read_lock(struct dentry *dentry, int flags) ++{ ++ int err; ++ struct super_block *sb; ++ ++ sb = dentry->d_sb; ++ err = si_read_lock(sb, flags); ++ if (unlikely(err)) ++ goto out; ++ ++ if (au_ftest_lock(flags, DW)) ++ di_write_lock_child(dentry); ++ else ++ di_read_lock_child(dentry, flags); ++ ++ if (au_ftest_lock(flags, GEN)) { ++ err = au_digen_test(dentry, au_sigen(sb)); ++ if (!au_opt_test(au_mntflags(sb), UDBA_NONE)) ++ AuDebugOn(!err && au_dbrange_test(dentry)); ++ else if (!err) ++ err = au_dbrange_test(dentry); ++ if (unlikely(err)) ++ aufs_read_unlock(dentry, flags); ++ } ++ ++out: ++ return err; ++} ++ ++void aufs_read_unlock(struct dentry *dentry, int flags) ++{ ++ if (au_ftest_lock(flags, DW)) ++ di_write_unlock(dentry); ++ else ++ di_read_unlock(dentry, flags); ++ si_read_unlock(dentry->d_sb); ++} ++ ++void aufs_write_lock(struct dentry *dentry) ++{ ++ si_write_lock(dentry->d_sb, AuLock_FLUSH | AuLock_NOPLMW); ++ di_write_lock_child(dentry); ++} ++ ++void aufs_write_unlock(struct dentry *dentry) ++{ ++ di_write_unlock(dentry); ++ si_write_unlock(dentry->d_sb); ++} ++ ++int aufs_read_and_write_lock2(struct dentry *d1, struct dentry *d2, int flags) ++{ ++ int err; ++ unsigned int sigen; ++ struct super_block *sb; ++ ++ sb = d1->d_sb; ++ err = si_read_lock(sb, flags); ++ if (unlikely(err)) ++ goto out; ++ ++ di_write_lock2_child(d1, d2, au_ftest_lock(flags, DIRS)); ++ ++ if (au_ftest_lock(flags, GEN)) { ++ sigen = au_sigen(sb); ++ err = au_digen_test(d1, sigen); ++ AuDebugOn(!err && au_dbrange_test(d1)); ++ if (!err) { ++ err = au_digen_test(d2, sigen); ++ AuDebugOn(!err && au_dbrange_test(d2)); ++ } ++ if (unlikely(err)) ++ aufs_read_and_write_unlock2(d1, d2); ++ } ++ ++out: ++ return err; ++} ++ ++void aufs_read_and_write_unlock2(struct dentry *d1, struct dentry *d2) ++{ ++ di_write_unlock2(d1, d2); ++ si_read_unlock(d1->d_sb); ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++static void si_pid_alloc(struct au_si_pid *au_si_pid, int idx) ++{ ++ unsigned long *p; ++ ++ BUILD_BUG_ON(sizeof(unsigned long) != ++ sizeof(*au_si_pid->pid_bitmap)); ++ ++ mutex_lock(&au_si_pid->pid_mtx); ++ p = au_si_pid->pid_bitmap[idx]; ++ while (!p) { ++ /* ++ * bad approach. ++ * but keeping 'si_pid_set()' void is more important. ++ */ ++ p = kcalloc(BITS_TO_LONGS(AU_PIDSTEP), ++ sizeof(*au_si_pid->pid_bitmap), ++ GFP_NOFS); ++ if (p) ++ break; ++ cond_resched(); ++ } ++ au_si_pid->pid_bitmap[idx] = p; ++ mutex_unlock(&au_si_pid->pid_mtx); ++} ++ ++void si_pid_set(struct super_block *sb) ++{ ++ pid_t bit; ++ int idx; ++ unsigned long *bitmap; ++ struct au_si_pid *au_si_pid; ++ ++ si_pid_idx_bit(&idx, &bit); ++ au_si_pid = &au_sbi(sb)->au_si_pid; ++ bitmap = au_si_pid->pid_bitmap[idx]; ++ if (!bitmap) { ++ si_pid_alloc(au_si_pid, idx); ++ bitmap = au_si_pid->pid_bitmap[idx]; ++ } ++ AuDebugOn(test_bit(bit, bitmap)); ++ set_bit(bit, bitmap); ++ /* smp_mb(); */ ++} +diff --git a/fs/aufs/spl.h b/fs/aufs/spl.h +new file mode 100644 +index 0000000..945343a +--- /dev/null ++++ b/fs/aufs/spl.h +@@ -0,0 +1,111 @@ ++/* ++ * Copyright (C) 2005-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * simple list protected by a spinlock ++ */ ++ ++#ifndef __AUFS_SPL_H__ ++#define __AUFS_SPL_H__ ++ ++#ifdef __KERNEL__ ++ ++struct au_splhead { ++ spinlock_t spin; ++ struct list_head head; ++}; ++ ++static inline void au_spl_init(struct au_splhead *spl) ++{ ++ spin_lock_init(&spl->spin); ++ INIT_LIST_HEAD(&spl->head); ++} ++ ++static inline void au_spl_add(struct list_head *list, struct au_splhead *spl) ++{ ++ spin_lock(&spl->spin); ++ list_add(list, &spl->head); ++ spin_unlock(&spl->spin); ++} ++ ++static inline void au_spl_del(struct list_head *list, struct au_splhead *spl) ++{ ++ spin_lock(&spl->spin); ++ list_del(list); ++ spin_unlock(&spl->spin); ++} ++ ++static inline void au_spl_del_rcu(struct list_head *list, ++ struct au_splhead *spl) ++{ ++ spin_lock(&spl->spin); ++ list_del_rcu(list); ++ spin_unlock(&spl->spin); ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++struct au_sphlhead { ++ spinlock_t spin; ++ struct hlist_head head; ++}; ++ ++static inline void au_sphl_init(struct au_sphlhead *sphl) ++{ ++ spin_lock_init(&sphl->spin); ++ INIT_HLIST_HEAD(&sphl->head); ++} ++ ++static inline void au_sphl_add(struct hlist_node *hlist, ++ struct au_sphlhead *sphl) ++{ ++ spin_lock(&sphl->spin); ++ hlist_add_head(hlist, &sphl->head); ++ spin_unlock(&sphl->spin); ++} ++ ++static inline void au_sphl_del(struct hlist_node *hlist, ++ struct au_sphlhead *sphl) ++{ ++ spin_lock(&sphl->spin); ++ hlist_del(hlist); ++ spin_unlock(&sphl->spin); ++} ++ ++static inline void au_sphl_del_rcu(struct hlist_node *hlist, ++ struct au_sphlhead *sphl) ++{ ++ spin_lock(&sphl->spin); ++ hlist_del_rcu(hlist); ++ spin_unlock(&sphl->spin); ++} ++ ++static inline unsigned long au_sphl_count(struct au_sphlhead *sphl) ++{ ++ unsigned long cnt; ++ struct hlist_node *pos; ++ ++ cnt = 0; ++ spin_lock(&sphl->spin); ++ hlist_for_each(pos, &sphl->head) ++ cnt++; ++ spin_unlock(&sphl->spin); ++ return cnt; ++} ++ ++#endif /* __KERNEL__ */ ++#endif /* __AUFS_SPL_H__ */ +diff --git a/fs/aufs/super.c b/fs/aufs/super.c +new file mode 100644 +index 0000000..64a6bb4 +--- /dev/null ++++ b/fs/aufs/super.c +@@ -0,0 +1,1041 @@ ++/* ++ * Copyright (C) 2005-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * mount and super_block operations ++ */ ++ ++#include ++#include ++#include ++#include ++#include "aufs.h" ++ ++/* ++ * super_operations ++ */ ++static struct inode *aufs_alloc_inode(struct super_block *sb __maybe_unused) ++{ ++ struct au_icntnr *c; ++ ++ c = au_cache_alloc_icntnr(); ++ if (c) { ++ au_icntnr_init(c); ++ c->vfs_inode.i_version = 1; /* sigen(sb); */ ++ c->iinfo.ii_hinode = NULL; ++ return &c->vfs_inode; ++ } ++ return NULL; ++} ++ ++static void aufs_destroy_inode_cb(struct rcu_head *head) ++{ ++ struct inode *inode = container_of(head, struct inode, i_rcu); ++ ++ INIT_HLIST_HEAD(&inode->i_dentry); ++ au_cache_free_icntnr(container_of(inode, struct au_icntnr, vfs_inode)); ++} ++ ++static void aufs_destroy_inode(struct inode *inode) ++{ ++ au_iinfo_fin(inode); ++ call_rcu(&inode->i_rcu, aufs_destroy_inode_cb); ++} ++ ++struct inode *au_iget_locked(struct super_block *sb, ino_t ino) ++{ ++ struct inode *inode; ++ int err; ++ ++ inode = iget_locked(sb, ino); ++ if (unlikely(!inode)) { ++ inode = ERR_PTR(-ENOMEM); ++ goto out; ++ } ++ if (!(inode->i_state & I_NEW)) ++ goto out; ++ ++ err = au_xigen_new(inode); ++ if (!err) ++ err = au_iinfo_init(inode); ++ if (!err) ++ inode->i_version++; ++ else { ++ iget_failed(inode); ++ inode = ERR_PTR(err); ++ } ++ ++out: ++ /* never return NULL */ ++ AuDebugOn(!inode); ++ AuTraceErrPtr(inode); ++ return inode; ++} ++ ++/* lock free root dinfo */ ++static int au_show_brs(struct seq_file *seq, struct super_block *sb) ++{ ++ int err; ++ aufs_bindex_t bindex, bend; ++ struct path path; ++ struct au_hdentry *hdp; ++ struct au_branch *br; ++ au_br_perm_str_t perm; ++ ++ err = 0; ++ bend = au_sbend(sb); ++ hdp = au_di(sb->s_root)->di_hdentry; ++ for (bindex = 0; !err && bindex <= bend; bindex++) { ++ br = au_sbr(sb, bindex); ++ path.mnt = au_br_mnt(br); ++ path.dentry = hdp[bindex].hd_dentry; ++ err = au_seq_path(seq, &path); ++ if (!err) { ++ au_optstr_br_perm(&perm, br->br_perm); ++ err = seq_printf(seq, "=%s", perm.a); ++ if (err == -1) ++ err = -E2BIG; ++ } ++ if (!err && bindex != bend) ++ err = seq_putc(seq, ':'); ++ } ++ ++ return err; ++} ++ ++static void au_show_wbr_create(struct seq_file *m, int v, ++ struct au_sbinfo *sbinfo) ++{ ++ const char *pat; ++ ++ AuRwMustAnyLock(&sbinfo->si_rwsem); ++ ++ seq_puts(m, ",create="); ++ pat = au_optstr_wbr_create(v); ++ switch (v) { ++ case AuWbrCreate_TDP: ++ case AuWbrCreate_RR: ++ case AuWbrCreate_MFS: ++ case AuWbrCreate_PMFS: ++ seq_puts(m, pat); ++ break; ++ case AuWbrCreate_MFSV: ++ seq_printf(m, /*pat*/"mfs:%lu", ++ jiffies_to_msecs(sbinfo->si_wbr_mfs.mfs_expire) ++ / MSEC_PER_SEC); ++ break; ++ case AuWbrCreate_PMFSV: ++ seq_printf(m, /*pat*/"pmfs:%lu", ++ jiffies_to_msecs(sbinfo->si_wbr_mfs.mfs_expire) ++ / MSEC_PER_SEC); ++ break; ++ case AuWbrCreate_MFSRR: ++ seq_printf(m, /*pat*/"mfsrr:%llu", ++ sbinfo->si_wbr_mfs.mfsrr_watermark); ++ break; ++ case AuWbrCreate_MFSRRV: ++ seq_printf(m, /*pat*/"mfsrr:%llu:%lu", ++ sbinfo->si_wbr_mfs.mfsrr_watermark, ++ jiffies_to_msecs(sbinfo->si_wbr_mfs.mfs_expire) ++ / MSEC_PER_SEC); ++ break; ++ case AuWbrCreate_PMFSRR: ++ seq_printf(m, /*pat*/"pmfsrr:%llu", ++ sbinfo->si_wbr_mfs.mfsrr_watermark); ++ break; ++ case AuWbrCreate_PMFSRRV: ++ seq_printf(m, /*pat*/"pmfsrr:%llu:%lu", ++ sbinfo->si_wbr_mfs.mfsrr_watermark, ++ jiffies_to_msecs(sbinfo->si_wbr_mfs.mfs_expire) ++ / MSEC_PER_SEC); ++ break; ++ } ++} ++ ++static int au_show_xino(struct seq_file *seq, struct super_block *sb) ++{ ++#ifdef CONFIG_SYSFS ++ return 0; ++#else ++ int err; ++ const int len = sizeof(AUFS_XINO_FNAME) - 1; ++ aufs_bindex_t bindex, brid; ++ struct qstr *name; ++ struct file *f; ++ struct dentry *d, *h_root; ++ struct au_hdentry *hdp; ++ ++ AuRwMustAnyLock(&sbinfo->si_rwsem); ++ ++ err = 0; ++ f = au_sbi(sb)->si_xib; ++ if (!f) ++ goto out; ++ ++ /* stop printing the default xino path on the first writable branch */ ++ h_root = NULL; ++ brid = au_xino_brid(sb); ++ if (brid >= 0) { ++ bindex = au_br_index(sb, brid); ++ hdp = au_di(sb->s_root)->di_hdentry; ++ h_root = hdp[0 + bindex].hd_dentry; ++ } ++ d = f->f_dentry; ++ name = &d->d_name; ++ /* safe ->d_parent because the file is unlinked */ ++ if (d->d_parent == h_root ++ && name->len == len ++ && !memcmp(name->name, AUFS_XINO_FNAME, len)) ++ goto out; ++ ++ seq_puts(seq, ",xino="); ++ err = au_xino_path(seq, f); ++ ++out: ++ return err; ++#endif ++} ++ ++/* seq_file will re-call me in case of too long string */ ++static int aufs_show_options(struct seq_file *m, struct dentry *dentry) ++{ ++ int err; ++ unsigned int mnt_flags, v; ++ struct super_block *sb; ++ struct au_sbinfo *sbinfo; ++ ++#define AuBool(name, str) do { \ ++ v = au_opt_test(mnt_flags, name); \ ++ if (v != au_opt_test(AuOpt_Def, name)) \ ++ seq_printf(m, ",%s" #str, v ? "" : "no"); \ ++} while (0) ++ ++#define AuStr(name, str) do { \ ++ v = mnt_flags & AuOptMask_##name; \ ++ if (v != (AuOpt_Def & AuOptMask_##name)) \ ++ seq_printf(m, "," #str "=%s", au_optstr_##str(v)); \ ++} while (0) ++ ++#define AuUInt(name, str, val) do { \ ++ if (val != AUFS_##name##_DEF) \ ++ seq_printf(m, "," #str "=%u", val); \ ++} while (0) ++ ++ sb = dentry->d_sb; ++ if (sb->s_flags & MS_POSIXACL) ++ seq_puts(m, ",acl"); ++ ++ /* lock free root dinfo */ ++ si_noflush_read_lock(sb); ++ sbinfo = au_sbi(sb); ++ seq_printf(m, ",si=%lx", sysaufs_si_id(sbinfo)); ++ ++ mnt_flags = au_mntflags(sb); ++ if (au_opt_test(mnt_flags, XINO)) { ++ err = au_show_xino(m, sb); ++ if (unlikely(err)) ++ goto out; ++ } else ++ seq_puts(m, ",noxino"); ++ ++ AuBool(TRUNC_XINO, trunc_xino); ++ AuStr(UDBA, udba); ++ AuBool(SHWH, shwh); ++ AuBool(PLINK, plink); ++ AuBool(DIO, dio); ++ AuBool(DIRPERM1, dirperm1); ++ /* AuBool(REFROF, refrof); */ ++ ++ v = sbinfo->si_wbr_create; ++ if (v != AuWbrCreate_Def) ++ au_show_wbr_create(m, v, sbinfo); ++ ++ v = sbinfo->si_wbr_copyup; ++ if (v != AuWbrCopyup_Def) ++ seq_printf(m, ",cpup=%s", au_optstr_wbr_copyup(v)); ++ ++ v = au_opt_test(mnt_flags, ALWAYS_DIROPQ); ++ if (v != au_opt_test(AuOpt_Def, ALWAYS_DIROPQ)) ++ seq_printf(m, ",diropq=%c", v ? 'a' : 'w'); ++ ++ AuUInt(DIRWH, dirwh, sbinfo->si_dirwh); ++ ++ v = jiffies_to_msecs(sbinfo->si_rdcache) / MSEC_PER_SEC; ++ AuUInt(RDCACHE, rdcache, v); ++ ++ AuUInt(RDBLK, rdblk, sbinfo->si_rdblk); ++ AuUInt(RDHASH, rdhash, sbinfo->si_rdhash); ++ ++ au_fhsm_show(m, sbinfo); ++ ++ AuBool(SUM, sum); ++ /* AuBool(SUM_W, wsum); */ ++ AuBool(WARN_PERM, warn_perm); ++ AuBool(VERBOSE, verbose); ++ ++out: ++ /* be sure to print "br:" last */ ++ if (!sysaufs_brs) { ++ seq_puts(m, ",br:"); ++ au_show_brs(m, sb); ++ } ++ si_read_unlock(sb); ++ return 0; ++ ++#undef AuBool ++#undef AuStr ++#undef AuUInt ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* sum mode which returns the summation for statfs(2) */ ++ ++static u64 au_add_till_max(u64 a, u64 b) ++{ ++ u64 old; ++ ++ old = a; ++ a += b; ++ if (old <= a) ++ return a; ++ return ULLONG_MAX; ++} ++ ++static u64 au_mul_till_max(u64 a, long mul) ++{ ++ u64 old; ++ ++ old = a; ++ a *= mul; ++ if (old <= a) ++ return a; ++ return ULLONG_MAX; ++} ++ ++static int au_statfs_sum(struct super_block *sb, struct kstatfs *buf) ++{ ++ int err; ++ long bsize, factor; ++ u64 blocks, bfree, bavail, files, ffree; ++ aufs_bindex_t bend, bindex, i; ++ unsigned char shared; ++ struct path h_path; ++ struct super_block *h_sb; ++ ++ err = 0; ++ bsize = LONG_MAX; ++ files = 0; ++ ffree = 0; ++ blocks = 0; ++ bfree = 0; ++ bavail = 0; ++ bend = au_sbend(sb); ++ for (bindex = 0; bindex <= bend; bindex++) { ++ h_path.mnt = au_sbr_mnt(sb, bindex); ++ h_sb = h_path.mnt->mnt_sb; ++ shared = 0; ++ for (i = 0; !shared && i < bindex; i++) ++ shared = (au_sbr_sb(sb, i) == h_sb); ++ if (shared) ++ continue; ++ ++ /* sb->s_root for NFS is unreliable */ ++ h_path.dentry = h_path.mnt->mnt_root; ++ err = vfs_statfs(&h_path, buf); ++ if (unlikely(err)) ++ goto out; ++ ++ if (bsize > buf->f_bsize) { ++ /* ++ * we will reduce bsize, so we have to expand blocks ++ * etc. to match them again ++ */ ++ factor = (bsize / buf->f_bsize); ++ blocks = au_mul_till_max(blocks, factor); ++ bfree = au_mul_till_max(bfree, factor); ++ bavail = au_mul_till_max(bavail, factor); ++ bsize = buf->f_bsize; ++ } ++ ++ factor = (buf->f_bsize / bsize); ++ blocks = au_add_till_max(blocks, ++ au_mul_till_max(buf->f_blocks, factor)); ++ bfree = au_add_till_max(bfree, ++ au_mul_till_max(buf->f_bfree, factor)); ++ bavail = au_add_till_max(bavail, ++ au_mul_till_max(buf->f_bavail, factor)); ++ files = au_add_till_max(files, buf->f_files); ++ ffree = au_add_till_max(ffree, buf->f_ffree); ++ } ++ ++ buf->f_bsize = bsize; ++ buf->f_blocks = blocks; ++ buf->f_bfree = bfree; ++ buf->f_bavail = bavail; ++ buf->f_files = files; ++ buf->f_ffree = ffree; ++ buf->f_frsize = 0; ++ ++out: ++ return err; ++} ++ ++static int aufs_statfs(struct dentry *dentry, struct kstatfs *buf) ++{ ++ int err; ++ struct path h_path; ++ struct super_block *sb; ++ ++ /* lock free root dinfo */ ++ sb = dentry->d_sb; ++ si_noflush_read_lock(sb); ++ if (!au_opt_test(au_mntflags(sb), SUM)) { ++ /* sb->s_root for NFS is unreliable */ ++ h_path.mnt = au_sbr_mnt(sb, 0); ++ h_path.dentry = h_path.mnt->mnt_root; ++ err = vfs_statfs(&h_path, buf); ++ } else ++ err = au_statfs_sum(sb, buf); ++ si_read_unlock(sb); ++ ++ if (!err) { ++ buf->f_type = AUFS_SUPER_MAGIC; ++ buf->f_namelen = AUFS_MAX_NAMELEN; ++ memset(&buf->f_fsid, 0, sizeof(buf->f_fsid)); ++ } ++ /* buf->f_bsize = buf->f_blocks = buf->f_bfree = buf->f_bavail = -1; */ ++ ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++static int aufs_sync_fs(struct super_block *sb, int wait) ++{ ++ int err, e; ++ aufs_bindex_t bend, bindex; ++ struct au_branch *br; ++ struct super_block *h_sb; ++ ++ err = 0; ++ si_noflush_read_lock(sb); ++ bend = au_sbend(sb); ++ for (bindex = 0; bindex <= bend; bindex++) { ++ br = au_sbr(sb, bindex); ++ if (!au_br_writable(br->br_perm)) ++ continue; ++ ++ h_sb = au_sbr_sb(sb, bindex); ++ if (h_sb->s_op->sync_fs) { ++ e = h_sb->s_op->sync_fs(h_sb, wait); ++ if (unlikely(e && !err)) ++ err = e; ++ /* go on even if an error happens */ ++ } ++ } ++ si_read_unlock(sb); ++ ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* final actions when unmounting a file system */ ++static void aufs_put_super(struct super_block *sb) ++{ ++ struct au_sbinfo *sbinfo; ++ ++ sbinfo = au_sbi(sb); ++ if (!sbinfo) ++ return; ++ ++ dbgaufs_si_fin(sbinfo); ++ kobject_put(&sbinfo->si_kobj); ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++void *au_array_alloc(unsigned long long *hint, au_arraycb_t cb, void *arg) ++{ ++ void *array; ++ unsigned long long n, sz; ++ ++ array = NULL; ++ n = 0; ++ if (!*hint) ++ goto out; ++ ++ if (*hint > ULLONG_MAX / sizeof(array)) { ++ array = ERR_PTR(-EMFILE); ++ pr_err("hint %llu\n", *hint); ++ goto out; ++ } ++ ++ sz = sizeof(array) * *hint; ++ array = kzalloc(sz, GFP_NOFS); ++ if (unlikely(!array)) ++ array = vzalloc(sz); ++ if (unlikely(!array)) { ++ array = ERR_PTR(-ENOMEM); ++ goto out; ++ } ++ ++ n = cb(array, *hint, arg); ++ AuDebugOn(n > *hint); ++ ++out: ++ *hint = n; ++ return array; ++} ++ ++static unsigned long long au_iarray_cb(void *a, ++ unsigned long long max __maybe_unused, ++ void *arg) ++{ ++ unsigned long long n; ++ struct inode **p, *inode; ++ struct list_head *head; ++ ++ n = 0; ++ p = a; ++ head = arg; ++ spin_lock(&inode_sb_list_lock); ++ list_for_each_entry(inode, head, i_sb_list) { ++ if (!is_bad_inode(inode) ++ && au_ii(inode)->ii_bstart >= 0) { ++ spin_lock(&inode->i_lock); ++ if (atomic_read(&inode->i_count)) { ++ au_igrab(inode); ++ *p++ = inode; ++ n++; ++ AuDebugOn(n > max); ++ } ++ spin_unlock(&inode->i_lock); ++ } ++ } ++ spin_unlock(&inode_sb_list_lock); ++ ++ return n; ++} ++ ++struct inode **au_iarray_alloc(struct super_block *sb, unsigned long long *max) ++{ ++ *max = atomic_long_read(&au_sbi(sb)->si_ninodes); ++ return au_array_alloc(max, au_iarray_cb, &sb->s_inodes); ++} ++ ++void au_iarray_free(struct inode **a, unsigned long long max) ++{ ++ unsigned long long ull; ++ ++ for (ull = 0; ull < max; ull++) ++ iput(a[ull]); ++ kvfree(a); ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* ++ * refresh dentry and inode at remount time. ++ */ ++/* todo: consolidate with simple_reval_dpath() and au_reval_for_attr() */ ++static int au_do_refresh(struct dentry *dentry, unsigned int dir_flags, ++ struct dentry *parent) ++{ ++ int err; ++ ++ di_write_lock_child(dentry); ++ di_read_lock_parent(parent, AuLock_IR); ++ err = au_refresh_dentry(dentry, parent); ++ if (!err && dir_flags) ++ au_hn_reset(dentry->d_inode, dir_flags); ++ di_read_unlock(parent, AuLock_IR); ++ di_write_unlock(dentry); ++ ++ return err; ++} ++ ++static int au_do_refresh_d(struct dentry *dentry, unsigned int sigen, ++ struct au_sbinfo *sbinfo, ++ const unsigned int dir_flags, unsigned int do_idop) ++{ ++ int err; ++ struct dentry *parent; ++ struct inode *inode; ++ ++ err = 0; ++ parent = dget_parent(dentry); ++ if (!au_digen_test(parent, sigen) && au_digen_test(dentry, sigen)) { ++ inode = dentry->d_inode; ++ if (inode) { ++ if (!S_ISDIR(inode->i_mode)) ++ err = au_do_refresh(dentry, /*dir_flags*/0, ++ parent); ++ else { ++ err = au_do_refresh(dentry, dir_flags, parent); ++ if (unlikely(err)) ++ au_fset_si(sbinfo, FAILED_REFRESH_DIR); ++ } ++ } else ++ err = au_do_refresh(dentry, /*dir_flags*/0, parent); ++ AuDbgDentry(dentry); ++ } ++ dput(parent); ++ ++ if (!err) { ++ if (do_idop) ++ au_refresh_dop(dentry, /*force_reval*/0); ++ } else ++ au_refresh_dop(dentry, /*force_reval*/1); ++ ++ AuTraceErr(err); ++ return err; ++} ++ ++static int au_refresh_d(struct super_block *sb, unsigned int do_idop) ++{ ++ int err, i, j, ndentry, e; ++ unsigned int sigen; ++ struct au_dcsub_pages dpages; ++ struct au_dpage *dpage; ++ struct dentry **dentries, *d; ++ struct au_sbinfo *sbinfo; ++ struct dentry *root = sb->s_root; ++ const unsigned int dir_flags = au_hi_flags(root->d_inode, /*isdir*/1); ++ ++ if (do_idop) ++ au_refresh_dop(root, /*force_reval*/0); ++ ++ err = au_dpages_init(&dpages, GFP_NOFS); ++ if (unlikely(err)) ++ goto out; ++ err = au_dcsub_pages(&dpages, root, NULL, NULL); ++ if (unlikely(err)) ++ goto out_dpages; ++ ++ sigen = au_sigen(sb); ++ sbinfo = au_sbi(sb); ++ for (i = 0; i < dpages.ndpage; i++) { ++ dpage = dpages.dpages + i; ++ dentries = dpage->dentries; ++ ndentry = dpage->ndentry; ++ for (j = 0; j < ndentry; j++) { ++ d = dentries[j]; ++ e = au_do_refresh_d(d, sigen, sbinfo, dir_flags, ++ do_idop); ++ if (unlikely(e && !err)) ++ err = e; ++ /* go on even err */ ++ } ++ } ++ ++out_dpages: ++ au_dpages_free(&dpages); ++out: ++ return err; ++} ++ ++static int au_refresh_i(struct super_block *sb, unsigned int do_idop) ++{ ++ int err, e; ++ unsigned int sigen; ++ unsigned long long max, ull; ++ struct inode *inode, **array; ++ ++ array = au_iarray_alloc(sb, &max); ++ err = PTR_ERR(array); ++ if (IS_ERR(array)) ++ goto out; ++ ++ err = 0; ++ sigen = au_sigen(sb); ++ for (ull = 0; ull < max; ull++) { ++ inode = array[ull]; ++ if (unlikely(!inode)) ++ break; ++ ++ e = 0; ++ ii_write_lock_child(inode); ++ if (au_iigen(inode, NULL) != sigen) { ++ e = au_refresh_hinode_self(inode); ++ if (unlikely(e)) { ++ au_refresh_iop(inode, /*force_getattr*/1); ++ pr_err("error %d, i%lu\n", e, inode->i_ino); ++ if (!err) ++ err = e; ++ /* go on even if err */ ++ } ++ } ++ if (!e && do_idop) ++ au_refresh_iop(inode, /*force_getattr*/0); ++ ii_write_unlock(inode); ++ } ++ ++ au_iarray_free(array, max); ++ ++out: ++ return err; ++} ++ ++static void au_remount_refresh(struct super_block *sb, unsigned int do_idop) ++{ ++ int err, e; ++ unsigned int udba; ++ aufs_bindex_t bindex, bend; ++ struct dentry *root; ++ struct inode *inode; ++ struct au_branch *br; ++ struct au_sbinfo *sbi; ++ ++ au_sigen_inc(sb); ++ sbi = au_sbi(sb); ++ au_fclr_si(sbi, FAILED_REFRESH_DIR); ++ ++ root = sb->s_root; ++ DiMustNoWaiters(root); ++ inode = root->d_inode; ++ IiMustNoWaiters(inode); ++ ++ udba = au_opt_udba(sb); ++ bend = au_sbend(sb); ++ for (bindex = 0; bindex <= bend; bindex++) { ++ br = au_sbr(sb, bindex); ++ err = au_hnotify_reset_br(udba, br, br->br_perm); ++ if (unlikely(err)) ++ AuIOErr("hnotify failed on br %d, %d, ignored\n", ++ bindex, err); ++ /* go on even if err */ ++ } ++ au_hn_reset(inode, au_hi_flags(inode, /*isdir*/1)); ++ ++ if (do_idop) { ++ if (au_ftest_si(sbi, NO_DREVAL)) { ++ AuDebugOn(sb->s_d_op == &aufs_dop_noreval); ++ sb->s_d_op = &aufs_dop_noreval; ++ AuDebugOn(sbi->si_iop_array == aufs_iop_nogetattr); ++ sbi->si_iop_array = aufs_iop_nogetattr; ++ } else { ++ AuDebugOn(sb->s_d_op == &aufs_dop); ++ sb->s_d_op = &aufs_dop; ++ AuDebugOn(sbi->si_iop_array == aufs_iop); ++ sbi->si_iop_array = aufs_iop; ++ } ++ pr_info("reset to %pf and %pf\n", ++ sb->s_d_op, sbi->si_iop_array); ++ } ++ ++ di_write_unlock(root); ++ err = au_refresh_d(sb, do_idop); ++ e = au_refresh_i(sb, do_idop); ++ if (unlikely(e && !err)) ++ err = e; ++ /* aufs_write_lock() calls ..._child() */ ++ di_write_lock_child(root); ++ ++ au_cpup_attr_all(inode, /*force*/1); ++ ++ if (unlikely(err)) ++ AuIOErr("refresh failed, ignored, %d\n", err); ++} ++ ++/* stop extra interpretation of errno in mount(8), and strange error messages */ ++static int cvt_err(int err) ++{ ++ AuTraceErr(err); ++ ++ switch (err) { ++ case -ENOENT: ++ case -ENOTDIR: ++ case -EEXIST: ++ case -EIO: ++ err = -EINVAL; ++ } ++ return err; ++} ++ ++static int aufs_remount_fs(struct super_block *sb, int *flags, char *data) ++{ ++ int err, do_dx; ++ unsigned int mntflags; ++ struct au_opts opts = { ++ .opt = NULL ++ }; ++ struct dentry *root; ++ struct inode *inode; ++ struct au_sbinfo *sbinfo; ++ ++ err = 0; ++ root = sb->s_root; ++ if (!data || !*data) { ++ err = si_write_lock(sb, AuLock_FLUSH | AuLock_NOPLM); ++ if (!err) { ++ di_write_lock_child(root); ++ err = au_opts_verify(sb, *flags, /*pending*/0); ++ aufs_write_unlock(root); ++ } ++ goto out; ++ } ++ ++ err = -ENOMEM; ++ opts.opt = (void *)__get_free_page(GFP_NOFS); ++ if (unlikely(!opts.opt)) ++ goto out; ++ opts.max_opt = PAGE_SIZE / sizeof(*opts.opt); ++ opts.flags = AuOpts_REMOUNT; ++ opts.sb_flags = *flags; ++ ++ /* parse it before aufs lock */ ++ err = au_opts_parse(sb, data, &opts); ++ if (unlikely(err)) ++ goto out_opts; ++ ++ sbinfo = au_sbi(sb); ++ inode = root->d_inode; ++ mutex_lock(&inode->i_mutex); ++ err = si_write_lock(sb, AuLock_FLUSH | AuLock_NOPLM); ++ if (unlikely(err)) ++ goto out_mtx; ++ di_write_lock_child(root); ++ ++ /* au_opts_remount() may return an error */ ++ err = au_opts_remount(sb, &opts); ++ au_opts_free(&opts); ++ ++ if (au_ftest_opts(opts.flags, REFRESH)) ++ au_remount_refresh(sb, au_ftest_opts(opts.flags, REFRESH_IDOP)); ++ ++ if (au_ftest_opts(opts.flags, REFRESH_DYAOP)) { ++ mntflags = au_mntflags(sb); ++ do_dx = !!au_opt_test(mntflags, DIO); ++ au_dy_arefresh(do_dx); ++ } ++ ++ au_fhsm_wrote_all(sb, /*force*/1); /* ?? */ ++ aufs_write_unlock(root); ++ ++out_mtx: ++ mutex_unlock(&inode->i_mutex); ++out_opts: ++ free_page((unsigned long)opts.opt); ++out: ++ err = cvt_err(err); ++ AuTraceErr(err); ++ return err; ++} ++ ++static const struct super_operations aufs_sop = { ++ .alloc_inode = aufs_alloc_inode, ++ .destroy_inode = aufs_destroy_inode, ++ /* always deleting, no clearing */ ++ .drop_inode = generic_delete_inode, ++ .show_options = aufs_show_options, ++ .statfs = aufs_statfs, ++ .put_super = aufs_put_super, ++ .sync_fs = aufs_sync_fs, ++ .remount_fs = aufs_remount_fs ++}; ++ ++/* ---------------------------------------------------------------------- */ ++ ++static int alloc_root(struct super_block *sb) ++{ ++ int err; ++ struct inode *inode; ++ struct dentry *root; ++ ++ err = -ENOMEM; ++ inode = au_iget_locked(sb, AUFS_ROOT_INO); ++ err = PTR_ERR(inode); ++ if (IS_ERR(inode)) ++ goto out; ++ ++ inode->i_op = aufs_iop + AuIop_DIR; /* with getattr by default */ ++ inode->i_fop = &aufs_dir_fop; ++ inode->i_mode = S_IFDIR; ++ set_nlink(inode, 2); ++ unlock_new_inode(inode); ++ ++ root = d_make_root(inode); ++ if (unlikely(!root)) ++ goto out; ++ err = PTR_ERR(root); ++ if (IS_ERR(root)) ++ goto out; ++ ++ err = au_di_init(root); ++ if (!err) { ++ sb->s_root = root; ++ return 0; /* success */ ++ } ++ dput(root); ++ ++out: ++ return err; ++} ++ ++static int aufs_fill_super(struct super_block *sb, void *raw_data, ++ int silent __maybe_unused) ++{ ++ int err; ++ struct au_opts opts = { ++ .opt = NULL ++ }; ++ struct au_sbinfo *sbinfo; ++ struct dentry *root; ++ struct inode *inode; ++ char *arg = raw_data; ++ ++ if (unlikely(!arg || !*arg)) { ++ err = -EINVAL; ++ pr_err("no arg\n"); ++ goto out; ++ } ++ ++ err = -ENOMEM; ++ opts.opt = (void *)__get_free_page(GFP_NOFS); ++ if (unlikely(!opts.opt)) ++ goto out; ++ opts.max_opt = PAGE_SIZE / sizeof(*opts.opt); ++ opts.sb_flags = sb->s_flags; ++ ++ err = au_si_alloc(sb); ++ if (unlikely(err)) ++ goto out_opts; ++ sbinfo = au_sbi(sb); ++ ++ /* all timestamps always follow the ones on the branch */ ++ sb->s_flags |= MS_NOATIME | MS_NODIRATIME; ++ sb->s_op = &aufs_sop; ++ sb->s_d_op = &aufs_dop; ++ sb->s_magic = AUFS_SUPER_MAGIC; ++ sb->s_maxbytes = 0; ++ sb->s_stack_depth = 1; ++ au_export_init(sb); ++ /* au_xattr_init(sb); */ ++ ++ err = alloc_root(sb); ++ if (unlikely(err)) { ++ si_write_unlock(sb); ++ goto out_info; ++ } ++ root = sb->s_root; ++ inode = root->d_inode; ++ ++ /* ++ * actually we can parse options regardless aufs lock here. ++ * but at remount time, parsing must be done before aufs lock. ++ * so we follow the same rule. ++ */ ++ ii_write_lock_parent(inode); ++ aufs_write_unlock(root); ++ err = au_opts_parse(sb, arg, &opts); ++ if (unlikely(err)) ++ goto out_root; ++ ++ /* lock vfs_inode first, then aufs. */ ++ mutex_lock(&inode->i_mutex); ++ aufs_write_lock(root); ++ err = au_opts_mount(sb, &opts); ++ au_opts_free(&opts); ++ if (!err && au_ftest_si(sbinfo, NO_DREVAL)) { ++ sb->s_d_op = &aufs_dop_noreval; ++ pr_info("%pf\n", sb->s_d_op); ++ au_refresh_dop(root, /*force_reval*/0); ++ sbinfo->si_iop_array = aufs_iop_nogetattr; ++ au_refresh_iop(inode, /*force_getattr*/0); ++ } ++ aufs_write_unlock(root); ++ mutex_unlock(&inode->i_mutex); ++ if (!err) ++ goto out_opts; /* success */ ++ ++out_root: ++ dput(root); ++ sb->s_root = NULL; ++out_info: ++ dbgaufs_si_fin(sbinfo); ++ kobject_put(&sbinfo->si_kobj); ++ sb->s_fs_info = NULL; ++out_opts: ++ free_page((unsigned long)opts.opt); ++out: ++ AuTraceErr(err); ++ err = cvt_err(err); ++ AuTraceErr(err); ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++static struct dentry *aufs_mount(struct file_system_type *fs_type, int flags, ++ const char *dev_name __maybe_unused, ++ void *raw_data) ++{ ++ struct dentry *root; ++ struct super_block *sb; ++ ++ /* all timestamps always follow the ones on the branch */ ++ /* mnt->mnt_flags |= MNT_NOATIME | MNT_NODIRATIME; */ ++ root = mount_nodev(fs_type, flags, raw_data, aufs_fill_super); ++ if (IS_ERR(root)) ++ goto out; ++ ++ sb = root->d_sb; ++ si_write_lock(sb, !AuLock_FLUSH); ++ sysaufs_brs_add(sb, 0); ++ si_write_unlock(sb); ++ au_sbilist_add(sb); ++ ++out: ++ return root; ++} ++ ++static void aufs_kill_sb(struct super_block *sb) ++{ ++ struct au_sbinfo *sbinfo; ++ ++ sbinfo = au_sbi(sb); ++ if (sbinfo) { ++ au_sbilist_del(sb); ++ aufs_write_lock(sb->s_root); ++ au_fhsm_fin(sb); ++ if (sbinfo->si_wbr_create_ops->fin) ++ sbinfo->si_wbr_create_ops->fin(sb); ++ if (au_opt_test(sbinfo->si_mntflags, UDBA_HNOTIFY)) { ++ au_opt_set_udba(sbinfo->si_mntflags, UDBA_NONE); ++ au_remount_refresh(sb, /*do_idop*/0); ++ } ++ if (au_opt_test(sbinfo->si_mntflags, PLINK)) ++ au_plink_put(sb, /*verbose*/1); ++ au_xino_clr(sb); ++ sbinfo->si_sb = NULL; ++ aufs_write_unlock(sb->s_root); ++ au_nwt_flush(&sbinfo->si_nowait); ++ } ++ kill_anon_super(sb); ++} ++ ++struct file_system_type aufs_fs_type = { ++ .name = AUFS_FSTYPE, ++ /* a race between rename and others */ ++ .fs_flags = FS_RENAME_DOES_D_MOVE, ++ .mount = aufs_mount, ++ .kill_sb = aufs_kill_sb, ++ /* no need to __module_get() and module_put(). */ ++ .owner = THIS_MODULE, ++}; +diff --git a/fs/aufs/super.h b/fs/aufs/super.h +new file mode 100644 +index 0000000..ecd364b +--- /dev/null ++++ b/fs/aufs/super.h +@@ -0,0 +1,626 @@ ++/* ++ * Copyright (C) 2005-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * super_block operations ++ */ ++ ++#ifndef __AUFS_SUPER_H__ ++#define __AUFS_SUPER_H__ ++ ++#ifdef __KERNEL__ ++ ++#include ++#include "rwsem.h" ++#include "spl.h" ++#include "wkq.h" ++ ++typedef ssize_t (*au_readf_t)(struct file *, char __user *, size_t, loff_t *); ++typedef ssize_t (*au_writef_t)(struct file *, const char __user *, size_t, ++ loff_t *); ++ ++/* policies to select one among multiple writable branches */ ++struct au_wbr_copyup_operations { ++ int (*copyup)(struct dentry *dentry); ++}; ++ ++#define AuWbr_DIR 1 /* target is a dir */ ++#define AuWbr_PARENT (1 << 1) /* always require a parent */ ++ ++#define au_ftest_wbr(flags, name) ((flags) & AuWbr_##name) ++#define au_fset_wbr(flags, name) { (flags) |= AuWbr_##name; } ++#define au_fclr_wbr(flags, name) { (flags) &= ~AuWbr_##name; } ++ ++struct au_wbr_create_operations { ++ int (*create)(struct dentry *dentry, unsigned int flags); ++ int (*init)(struct super_block *sb); ++ int (*fin)(struct super_block *sb); ++}; ++ ++struct au_wbr_mfs { ++ struct mutex mfs_lock; /* protect this structure */ ++ unsigned long mfs_jiffy; ++ unsigned long mfs_expire; ++ aufs_bindex_t mfs_bindex; ++ ++ unsigned long long mfsrr_bytes; ++ unsigned long long mfsrr_watermark; ++}; ++ ++#define AuPlink_NHASH 100 ++static inline int au_plink_hash(ino_t ino) ++{ ++ return ino % AuPlink_NHASH; ++} ++ ++/* File-based Hierarchical Storage Management */ ++struct au_fhsm { ++#ifdef CONFIG_AUFS_FHSM ++ /* allow only one process who can receive the notification */ ++ spinlock_t fhsm_spin; ++ pid_t fhsm_pid; ++ wait_queue_head_t fhsm_wqh; ++ atomic_t fhsm_readable; ++ ++ /* these are protected by si_rwsem */ ++ unsigned long fhsm_expire; ++ aufs_bindex_t fhsm_bottom; ++#endif ++}; ++ ++#define AU_PIDSTEP (int)(BITS_TO_LONGS(PID_MAX_DEFAULT) * BITS_PER_LONG) ++#define AU_NPIDMAP (int)DIV_ROUND_UP(PID_MAX_LIMIT, AU_PIDSTEP) ++struct au_si_pid { ++ unsigned long *pid_bitmap[AU_NPIDMAP]; ++ struct mutex pid_mtx; ++}; ++ ++struct au_branch; ++struct au_sbinfo { ++ /* nowait tasks in the system-wide workqueue */ ++ struct au_nowait_tasks si_nowait; ++ ++ /* ++ * tried sb->s_umount, but failed due to the dependecy between i_mutex. ++ * rwsem for au_sbinfo is necessary. ++ */ ++ struct au_rwsem si_rwsem; ++ ++ /* prevent recursive locking in deleting inode */ ++ struct au_si_pid au_si_pid; ++ ++ /* ++ * dirty approach to protect sb->sb_inodes and ->s_files (gone) from ++ * remount. ++ */ ++ atomic_long_t si_ninodes, si_nfiles; ++ ++ /* branch management */ ++ unsigned int si_generation; ++ ++ /* see AuSi_ flags */ ++ unsigned char au_si_status; ++ ++ aufs_bindex_t si_bend; ++ ++ /* dirty trick to keep br_id plus */ ++ unsigned int si_last_br_id : ++ sizeof(aufs_bindex_t) * BITS_PER_BYTE - 1; ++ struct au_branch **si_branch; ++ ++ /* policy to select a writable branch */ ++ unsigned char si_wbr_copyup; ++ unsigned char si_wbr_create; ++ struct au_wbr_copyup_operations *si_wbr_copyup_ops; ++ struct au_wbr_create_operations *si_wbr_create_ops; ++ ++ /* round robin */ ++ atomic_t si_wbr_rr_next; ++ ++ /* most free space */ ++ struct au_wbr_mfs si_wbr_mfs; ++ ++ /* File-based Hierarchical Storage Management */ ++ struct au_fhsm si_fhsm; ++ ++ /* mount flags */ ++ /* include/asm-ia64/siginfo.h defines a macro named si_flags */ ++ unsigned int si_mntflags; ++ ++ /* external inode number (bitmap and translation table) */ ++ au_readf_t si_xread; ++ au_writef_t si_xwrite; ++ struct file *si_xib; ++ struct mutex si_xib_mtx; /* protect xib members */ ++ unsigned long *si_xib_buf; ++ unsigned long si_xib_last_pindex; ++ int si_xib_next_bit; ++ aufs_bindex_t si_xino_brid; ++ unsigned long si_xino_jiffy; ++ unsigned long si_xino_expire; ++ /* reserved for future use */ ++ /* unsigned long long si_xib_limit; */ /* Max xib file size */ ++ ++#ifdef CONFIG_AUFS_EXPORT ++ /* i_generation */ ++ struct file *si_xigen; ++ atomic_t si_xigen_next; ++#endif ++ ++ /* dirty trick to suppoer atomic_open */ ++ struct au_sphlhead si_aopen; ++ ++ /* vdir parameters */ ++ unsigned long si_rdcache; /* max cache time in jiffies */ ++ unsigned int si_rdblk; /* deblk size */ ++ unsigned int si_rdhash; /* hash size */ ++ ++ /* ++ * If the number of whiteouts are larger than si_dirwh, leave all of ++ * them after au_whtmp_ren to reduce the cost of rmdir(2). ++ * future fsck.aufs or kernel thread will remove them later. ++ * Otherwise, remove all whiteouts and the dir in rmdir(2). ++ */ ++ unsigned int si_dirwh; ++ ++ /* pseudo_link list */ ++ struct au_sphlhead si_plink[AuPlink_NHASH]; ++ wait_queue_head_t si_plink_wq; ++ spinlock_t si_plink_maint_lock; ++ pid_t si_plink_maint_pid; ++ ++ /* file list */ ++ struct au_sphlhead si_files; ++ ++ /* with/without getattr, brother of sb->s_d_op */ ++ struct inode_operations *si_iop_array; ++ ++ /* ++ * sysfs and lifetime management. ++ * this is not a small structure and it may be a waste of memory in case ++ * of sysfs is disabled, particulary when many aufs-es are mounted. ++ * but using sysfs is majority. ++ */ ++ struct kobject si_kobj; ++#ifdef CONFIG_DEBUG_FS ++ struct dentry *si_dbgaufs; ++ struct dentry *si_dbgaufs_plink; ++ struct dentry *si_dbgaufs_xib; ++#ifdef CONFIG_AUFS_EXPORT ++ struct dentry *si_dbgaufs_xigen; ++#endif ++#endif ++ ++#ifdef CONFIG_AUFS_SBILIST ++ struct hlist_node si_list; ++#endif ++ ++ /* dirty, necessary for unmounting, sysfs and sysrq */ ++ struct super_block *si_sb; ++}; ++ ++/* sbinfo status flags */ ++/* ++ * set true when refresh_dirs() failed at remount time. ++ * then try refreshing dirs at access time again. ++ * if it is false, refreshing dirs at access time is unnecesary ++ */ ++#define AuSi_FAILED_REFRESH_DIR 1 ++#define AuSi_FHSM (1 << 1) /* fhsm is active now */ ++#define AuSi_NO_DREVAL (1 << 2) /* disable all d_revalidate */ ++ ++#ifndef CONFIG_AUFS_FHSM ++#undef AuSi_FHSM ++#define AuSi_FHSM 0 ++#endif ++ ++static inline unsigned char au_do_ftest_si(struct au_sbinfo *sbi, ++ unsigned int flag) ++{ ++ AuRwMustAnyLock(&sbi->si_rwsem); ++ return sbi->au_si_status & flag; ++} ++#define au_ftest_si(sbinfo, name) au_do_ftest_si(sbinfo, AuSi_##name) ++#define au_fset_si(sbinfo, name) do { \ ++ AuRwMustWriteLock(&(sbinfo)->si_rwsem); \ ++ (sbinfo)->au_si_status |= AuSi_##name; \ ++} while (0) ++#define au_fclr_si(sbinfo, name) do { \ ++ AuRwMustWriteLock(&(sbinfo)->si_rwsem); \ ++ (sbinfo)->au_si_status &= ~AuSi_##name; \ ++} while (0) ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* policy to select one among writable branches */ ++#define AuWbrCopyup(sbinfo, ...) \ ++ ((sbinfo)->si_wbr_copyup_ops->copyup(__VA_ARGS__)) ++#define AuWbrCreate(sbinfo, ...) \ ++ ((sbinfo)->si_wbr_create_ops->create(__VA_ARGS__)) ++ ++/* flags for si_read_lock()/aufs_read_lock()/di_read_lock() */ ++#define AuLock_DW 1 /* write-lock dentry */ ++#define AuLock_IR (1 << 1) /* read-lock inode */ ++#define AuLock_IW (1 << 2) /* write-lock inode */ ++#define AuLock_FLUSH (1 << 3) /* wait for 'nowait' tasks */ ++#define AuLock_DIRS (1 << 4) /* target is a pair of dirs */ ++#define AuLock_NOPLM (1 << 5) /* return err in plm mode */ ++#define AuLock_NOPLMW (1 << 6) /* wait for plm mode ends */ ++#define AuLock_GEN (1 << 7) /* test digen/iigen */ ++#define au_ftest_lock(flags, name) ((flags) & AuLock_##name) ++#define au_fset_lock(flags, name) \ ++ do { (flags) |= AuLock_##name; } while (0) ++#define au_fclr_lock(flags, name) \ ++ do { (flags) &= ~AuLock_##name; } while (0) ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* super.c */ ++extern struct file_system_type aufs_fs_type; ++struct inode *au_iget_locked(struct super_block *sb, ino_t ino); ++typedef unsigned long long (*au_arraycb_t)(void *array, unsigned long long max, ++ void *arg); ++void *au_array_alloc(unsigned long long *hint, au_arraycb_t cb, void *arg); ++struct inode **au_iarray_alloc(struct super_block *sb, unsigned long long *max); ++void au_iarray_free(struct inode **a, unsigned long long max); ++ ++/* sbinfo.c */ ++void au_si_free(struct kobject *kobj); ++int au_si_alloc(struct super_block *sb); ++int au_sbr_realloc(struct au_sbinfo *sbinfo, int nbr); ++ ++unsigned int au_sigen_inc(struct super_block *sb); ++aufs_bindex_t au_new_br_id(struct super_block *sb); ++ ++int si_read_lock(struct super_block *sb, int flags); ++int si_write_lock(struct super_block *sb, int flags); ++int aufs_read_lock(struct dentry *dentry, int flags); ++void aufs_read_unlock(struct dentry *dentry, int flags); ++void aufs_write_lock(struct dentry *dentry); ++void aufs_write_unlock(struct dentry *dentry); ++int aufs_read_and_write_lock2(struct dentry *d1, struct dentry *d2, int flags); ++void aufs_read_and_write_unlock2(struct dentry *d1, struct dentry *d2); ++ ++/* wbr_policy.c */ ++extern struct au_wbr_copyup_operations au_wbr_copyup_ops[]; ++extern struct au_wbr_create_operations au_wbr_create_ops[]; ++int au_cpdown_dirs(struct dentry *dentry, aufs_bindex_t bdst); ++int au_wbr_nonopq(struct dentry *dentry, aufs_bindex_t bindex); ++int au_wbr_do_copyup_bu(struct dentry *dentry, aufs_bindex_t bstart); ++ ++/* mvdown.c */ ++int au_mvdown(struct dentry *dentry, struct aufs_mvdown __user *arg); ++ ++#ifdef CONFIG_AUFS_FHSM ++/* fhsm.c */ ++ ++static inline pid_t au_fhsm_pid(struct au_fhsm *fhsm) ++{ ++ pid_t pid; ++ ++ spin_lock(&fhsm->fhsm_spin); ++ pid = fhsm->fhsm_pid; ++ spin_unlock(&fhsm->fhsm_spin); ++ ++ return pid; ++} ++ ++void au_fhsm_wrote(struct super_block *sb, aufs_bindex_t bindex, int force); ++void au_fhsm_wrote_all(struct super_block *sb, int force); ++int au_fhsm_fd(struct super_block *sb, int oflags); ++int au_fhsm_br_alloc(struct au_branch *br); ++void au_fhsm_set_bottom(struct super_block *sb, aufs_bindex_t bindex); ++void au_fhsm_fin(struct super_block *sb); ++void au_fhsm_init(struct au_sbinfo *sbinfo); ++void au_fhsm_set(struct au_sbinfo *sbinfo, unsigned int sec); ++void au_fhsm_show(struct seq_file *seq, struct au_sbinfo *sbinfo); ++#else ++AuStubVoid(au_fhsm_wrote, struct super_block *sb, aufs_bindex_t bindex, ++ int force) ++AuStubVoid(au_fhsm_wrote_all, struct super_block *sb, int force) ++AuStub(int, au_fhsm_fd, return -EOPNOTSUPP, struct super_block *sb, int oflags) ++AuStub(pid_t, au_fhsm_pid, return 0, struct au_fhsm *fhsm) ++AuStubInt0(au_fhsm_br_alloc, struct au_branch *br) ++AuStubVoid(au_fhsm_set_bottom, struct super_block *sb, aufs_bindex_t bindex) ++AuStubVoid(au_fhsm_fin, struct super_block *sb) ++AuStubVoid(au_fhsm_init, struct au_sbinfo *sbinfo) ++AuStubVoid(au_fhsm_set, struct au_sbinfo *sbinfo, unsigned int sec) ++AuStubVoid(au_fhsm_show, struct seq_file *seq, struct au_sbinfo *sbinfo) ++#endif ++ ++/* ---------------------------------------------------------------------- */ ++ ++static inline struct au_sbinfo *au_sbi(struct super_block *sb) ++{ ++ return sb->s_fs_info; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++#ifdef CONFIG_AUFS_EXPORT ++int au_test_nfsd(void); ++void au_export_init(struct super_block *sb); ++void au_xigen_inc(struct inode *inode); ++int au_xigen_new(struct inode *inode); ++int au_xigen_set(struct super_block *sb, struct file *base); ++void au_xigen_clr(struct super_block *sb); ++ ++static inline int au_busy_or_stale(void) ++{ ++ if (!au_test_nfsd()) ++ return -EBUSY; ++ return -ESTALE; ++} ++#else ++AuStubInt0(au_test_nfsd, void) ++AuStubVoid(au_export_init, struct super_block *sb) ++AuStubVoid(au_xigen_inc, struct inode *inode) ++AuStubInt0(au_xigen_new, struct inode *inode) ++AuStubInt0(au_xigen_set, struct super_block *sb, struct file *base) ++AuStubVoid(au_xigen_clr, struct super_block *sb) ++AuStub(int, au_busy_or_stale, return -EBUSY, void) ++#endif /* CONFIG_AUFS_EXPORT */ ++ ++/* ---------------------------------------------------------------------- */ ++ ++#ifdef CONFIG_AUFS_SBILIST ++/* module.c */ ++extern struct au_sphlhead au_sbilist; ++ ++static inline void au_sbilist_init(void) ++{ ++ au_sphl_init(&au_sbilist); ++} ++ ++static inline void au_sbilist_add(struct super_block *sb) ++{ ++ au_sphl_add(&au_sbi(sb)->si_list, &au_sbilist); ++} ++ ++static inline void au_sbilist_del(struct super_block *sb) ++{ ++ au_sphl_del(&au_sbi(sb)->si_list, &au_sbilist); ++} ++ ++#ifdef CONFIG_AUFS_MAGIC_SYSRQ ++static inline void au_sbilist_lock(void) ++{ ++ spin_lock(&au_sbilist.spin); ++} ++ ++static inline void au_sbilist_unlock(void) ++{ ++ spin_unlock(&au_sbilist.spin); ++} ++#define AuGFP_SBILIST GFP_ATOMIC ++#else ++AuStubVoid(au_sbilist_lock, void) ++AuStubVoid(au_sbilist_unlock, void) ++#define AuGFP_SBILIST GFP_NOFS ++#endif /* CONFIG_AUFS_MAGIC_SYSRQ */ ++#else ++AuStubVoid(au_sbilist_init, void) ++AuStubVoid(au_sbilist_add, struct super_block *sb) ++AuStubVoid(au_sbilist_del, struct super_block *sb) ++AuStubVoid(au_sbilist_lock, void) ++AuStubVoid(au_sbilist_unlock, void) ++#define AuGFP_SBILIST GFP_NOFS ++#endif ++ ++/* ---------------------------------------------------------------------- */ ++ ++static inline void dbgaufs_si_null(struct au_sbinfo *sbinfo) ++{ ++ /* ++ * This function is a dynamic '__init' function actually, ++ * so the tiny check for si_rwsem is unnecessary. ++ */ ++ /* AuRwMustWriteLock(&sbinfo->si_rwsem); */ ++#ifdef CONFIG_DEBUG_FS ++ sbinfo->si_dbgaufs = NULL; ++ sbinfo->si_dbgaufs_plink = NULL; ++ sbinfo->si_dbgaufs_xib = NULL; ++#ifdef CONFIG_AUFS_EXPORT ++ sbinfo->si_dbgaufs_xigen = NULL; ++#endif ++#endif ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++static inline void si_pid_idx_bit(int *idx, pid_t *bit) ++{ ++ /* the origin of pid is 1, but the bitmap's is 0 */ ++ *bit = current->pid - 1; ++ *idx = *bit / AU_PIDSTEP; ++ *bit %= AU_PIDSTEP; ++} ++ ++static inline int si_pid_test(struct super_block *sb) ++{ ++ pid_t bit; ++ int idx; ++ unsigned long *bitmap; ++ ++ si_pid_idx_bit(&idx, &bit); ++ bitmap = au_sbi(sb)->au_si_pid.pid_bitmap[idx]; ++ if (bitmap) ++ return test_bit(bit, bitmap); ++ return 0; ++} ++ ++static inline void si_pid_clr(struct super_block *sb) ++{ ++ pid_t bit; ++ int idx; ++ unsigned long *bitmap; ++ ++ si_pid_idx_bit(&idx, &bit); ++ bitmap = au_sbi(sb)->au_si_pid.pid_bitmap[idx]; ++ BUG_ON(!bitmap); ++ AuDebugOn(!test_bit(bit, bitmap)); ++ clear_bit(bit, bitmap); ++ /* smp_mb(); */ ++} ++ ++void si_pid_set(struct super_block *sb); ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* lock superblock. mainly for entry point functions */ ++/* ++ * __si_read_lock, __si_write_lock, ++ * __si_read_unlock, __si_write_unlock, __si_downgrade_lock ++ */ ++AuSimpleRwsemFuncs(__si, struct super_block *sb, &au_sbi(sb)->si_rwsem); ++ ++#define SiMustNoWaiters(sb) AuRwMustNoWaiters(&au_sbi(sb)->si_rwsem) ++#define SiMustAnyLock(sb) AuRwMustAnyLock(&au_sbi(sb)->si_rwsem) ++#define SiMustWriteLock(sb) AuRwMustWriteLock(&au_sbi(sb)->si_rwsem) ++ ++static inline void si_noflush_read_lock(struct super_block *sb) ++{ ++ __si_read_lock(sb); ++ si_pid_set(sb); ++} ++ ++static inline int si_noflush_read_trylock(struct super_block *sb) ++{ ++ int locked; ++ ++ locked = __si_read_trylock(sb); ++ if (locked) ++ si_pid_set(sb); ++ return locked; ++} ++ ++static inline void si_noflush_write_lock(struct super_block *sb) ++{ ++ __si_write_lock(sb); ++ si_pid_set(sb); ++} ++ ++static inline int si_noflush_write_trylock(struct super_block *sb) ++{ ++ int locked; ++ ++ locked = __si_write_trylock(sb); ++ if (locked) ++ si_pid_set(sb); ++ return locked; ++} ++ ++#if 0 /* reserved */ ++static inline int si_read_trylock(struct super_block *sb, int flags) ++{ ++ if (au_ftest_lock(flags, FLUSH)) ++ au_nwt_flush(&au_sbi(sb)->si_nowait); ++ return si_noflush_read_trylock(sb); ++} ++#endif ++ ++static inline void si_read_unlock(struct super_block *sb) ++{ ++ si_pid_clr(sb); ++ __si_read_unlock(sb); ++} ++ ++#if 0 /* reserved */ ++static inline int si_write_trylock(struct super_block *sb, int flags) ++{ ++ if (au_ftest_lock(flags, FLUSH)) ++ au_nwt_flush(&au_sbi(sb)->si_nowait); ++ return si_noflush_write_trylock(sb); ++} ++#endif ++ ++static inline void si_write_unlock(struct super_block *sb) ++{ ++ si_pid_clr(sb); ++ __si_write_unlock(sb); ++} ++ ++#if 0 /* reserved */ ++static inline void si_downgrade_lock(struct super_block *sb) ++{ ++ __si_downgrade_lock(sb); ++} ++#endif ++ ++/* ---------------------------------------------------------------------- */ ++ ++static inline aufs_bindex_t au_sbend(struct super_block *sb) ++{ ++ SiMustAnyLock(sb); ++ return au_sbi(sb)->si_bend; ++} ++ ++static inline unsigned int au_mntflags(struct super_block *sb) ++{ ++ SiMustAnyLock(sb); ++ return au_sbi(sb)->si_mntflags; ++} ++ ++static inline unsigned int au_sigen(struct super_block *sb) ++{ ++ SiMustAnyLock(sb); ++ return au_sbi(sb)->si_generation; ++} ++ ++static inline void au_ninodes_inc(struct super_block *sb) ++{ ++ atomic_long_inc(&au_sbi(sb)->si_ninodes); ++} ++ ++static inline void au_ninodes_dec(struct super_block *sb) ++{ ++ AuDebugOn(!atomic_long_read(&au_sbi(sb)->si_ninodes)); ++ atomic_long_dec(&au_sbi(sb)->si_ninodes); ++} ++ ++static inline void au_nfiles_inc(struct super_block *sb) ++{ ++ atomic_long_inc(&au_sbi(sb)->si_nfiles); ++} ++ ++static inline void au_nfiles_dec(struct super_block *sb) ++{ ++ AuDebugOn(!atomic_long_read(&au_sbi(sb)->si_nfiles)); ++ atomic_long_dec(&au_sbi(sb)->si_nfiles); ++} ++ ++static inline struct au_branch *au_sbr(struct super_block *sb, ++ aufs_bindex_t bindex) ++{ ++ SiMustAnyLock(sb); ++ return au_sbi(sb)->si_branch[0 + bindex]; ++} ++ ++static inline void au_xino_brid_set(struct super_block *sb, aufs_bindex_t brid) ++{ ++ SiMustWriteLock(sb); ++ au_sbi(sb)->si_xino_brid = brid; ++} ++ ++static inline aufs_bindex_t au_xino_brid(struct super_block *sb) ++{ ++ SiMustAnyLock(sb); ++ return au_sbi(sb)->si_xino_brid; ++} ++ ++#endif /* __KERNEL__ */ ++#endif /* __AUFS_SUPER_H__ */ +diff --git a/fs/aufs/sysaufs.c b/fs/aufs/sysaufs.c +new file mode 100644 +index 0000000..75c9c24 +--- /dev/null ++++ b/fs/aufs/sysaufs.c +@@ -0,0 +1,104 @@ ++/* ++ * Copyright (C) 2005-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * sysfs interface and lifetime management ++ * they are necessary regardless sysfs is disabled. ++ */ ++ ++#include ++#include "aufs.h" ++ ++unsigned long sysaufs_si_mask; ++struct kset *sysaufs_kset; ++ ++#define AuSiAttr(_name) { \ ++ .attr = { .name = __stringify(_name), .mode = 0444 }, \ ++ .show = sysaufs_si_##_name, \ ++} ++ ++static struct sysaufs_si_attr sysaufs_si_attr_xi_path = AuSiAttr(xi_path); ++struct attribute *sysaufs_si_attrs[] = { ++ &sysaufs_si_attr_xi_path.attr, ++ NULL, ++}; ++ ++static const struct sysfs_ops au_sbi_ops = { ++ .show = sysaufs_si_show ++}; ++ ++static struct kobj_type au_sbi_ktype = { ++ .release = au_si_free, ++ .sysfs_ops = &au_sbi_ops, ++ .default_attrs = sysaufs_si_attrs ++}; ++ ++/* ---------------------------------------------------------------------- */ ++ ++int sysaufs_si_init(struct au_sbinfo *sbinfo) ++{ ++ int err; ++ ++ sbinfo->si_kobj.kset = sysaufs_kset; ++ /* cf. sysaufs_name() */ ++ err = kobject_init_and_add ++ (&sbinfo->si_kobj, &au_sbi_ktype, /*&sysaufs_kset->kobj*/NULL, ++ SysaufsSiNamePrefix "%lx", sysaufs_si_id(sbinfo)); ++ ++ dbgaufs_si_null(sbinfo); ++ if (!err) { ++ err = dbgaufs_si_init(sbinfo); ++ if (unlikely(err)) ++ kobject_put(&sbinfo->si_kobj); ++ } ++ return err; ++} ++ ++void sysaufs_fin(void) ++{ ++ dbgaufs_fin(); ++ sysfs_remove_group(&sysaufs_kset->kobj, sysaufs_attr_group); ++ kset_unregister(sysaufs_kset); ++} ++ ++int __init sysaufs_init(void) ++{ ++ int err; ++ ++ do { ++ get_random_bytes(&sysaufs_si_mask, sizeof(sysaufs_si_mask)); ++ } while (!sysaufs_si_mask); ++ ++ err = -EINVAL; ++ sysaufs_kset = kset_create_and_add(AUFS_NAME, NULL, fs_kobj); ++ if (unlikely(!sysaufs_kset)) ++ goto out; ++ err = PTR_ERR(sysaufs_kset); ++ if (IS_ERR(sysaufs_kset)) ++ goto out; ++ err = sysfs_create_group(&sysaufs_kset->kobj, sysaufs_attr_group); ++ if (unlikely(err)) { ++ kset_unregister(sysaufs_kset); ++ goto out; ++ } ++ ++ err = dbgaufs_init(); ++ if (unlikely(err)) ++ sysaufs_fin(); ++out: ++ return err; ++} +diff --git a/fs/aufs/sysaufs.h b/fs/aufs/sysaufs.h +new file mode 100644 +index 0000000..14975c9 +--- /dev/null ++++ b/fs/aufs/sysaufs.h +@@ -0,0 +1,101 @@ ++/* ++ * Copyright (C) 2005-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * sysfs interface and mount lifetime management ++ */ ++ ++#ifndef __SYSAUFS_H__ ++#define __SYSAUFS_H__ ++ ++#ifdef __KERNEL__ ++ ++#include ++#include "module.h" ++ ++struct super_block; ++struct au_sbinfo; ++ ++struct sysaufs_si_attr { ++ struct attribute attr; ++ int (*show)(struct seq_file *seq, struct super_block *sb); ++}; ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* sysaufs.c */ ++extern unsigned long sysaufs_si_mask; ++extern struct kset *sysaufs_kset; ++extern struct attribute *sysaufs_si_attrs[]; ++int sysaufs_si_init(struct au_sbinfo *sbinfo); ++int __init sysaufs_init(void); ++void sysaufs_fin(void); ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* some people doesn't like to show a pointer in kernel */ ++static inline unsigned long sysaufs_si_id(struct au_sbinfo *sbinfo) ++{ ++ return sysaufs_si_mask ^ (unsigned long)sbinfo; ++} ++ ++#define SysaufsSiNamePrefix "si_" ++#define SysaufsSiNameLen (sizeof(SysaufsSiNamePrefix) + 16) ++static inline void sysaufs_name(struct au_sbinfo *sbinfo, char *name) ++{ ++ snprintf(name, SysaufsSiNameLen, SysaufsSiNamePrefix "%lx", ++ sysaufs_si_id(sbinfo)); ++} ++ ++struct au_branch; ++#ifdef CONFIG_SYSFS ++/* sysfs.c */ ++extern struct attribute_group *sysaufs_attr_group; ++ ++int sysaufs_si_xi_path(struct seq_file *seq, struct super_block *sb); ++ssize_t sysaufs_si_show(struct kobject *kobj, struct attribute *attr, ++ char *buf); ++long au_brinfo_ioctl(struct file *file, unsigned long arg); ++#ifdef CONFIG_COMPAT ++long au_brinfo_compat_ioctl(struct file *file, unsigned long arg); ++#endif ++ ++void sysaufs_br_init(struct au_branch *br); ++void sysaufs_brs_add(struct super_block *sb, aufs_bindex_t bindex); ++void sysaufs_brs_del(struct super_block *sb, aufs_bindex_t bindex); ++ ++#define sysaufs_brs_init() do {} while (0) ++ ++#else ++#define sysaufs_attr_group NULL ++ ++AuStubInt0(sysaufs_si_xi_path, struct seq_file *seq, struct super_block *sb) ++AuStub(ssize_t, sysaufs_si_show, return 0, struct kobject *kobj, ++ struct attribute *attr, char *buf) ++AuStubVoid(sysaufs_br_init, struct au_branch *br) ++AuStubVoid(sysaufs_brs_add, struct super_block *sb, aufs_bindex_t bindex) ++AuStubVoid(sysaufs_brs_del, struct super_block *sb, aufs_bindex_t bindex) ++ ++static inline void sysaufs_brs_init(void) ++{ ++ sysaufs_brs = 0; ++} ++ ++#endif /* CONFIG_SYSFS */ ++ ++#endif /* __KERNEL__ */ ++#endif /* __SYSAUFS_H__ */ +diff --git a/fs/aufs/sysfs.c b/fs/aufs/sysfs.c +new file mode 100644 +index 0000000..b2d1888 +--- /dev/null ++++ b/fs/aufs/sysfs.c +@@ -0,0 +1,376 @@ ++/* ++ * Copyright (C) 2005-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * sysfs interface ++ */ ++ ++#include ++#include ++#include "aufs.h" ++ ++#ifdef CONFIG_AUFS_FS_MODULE ++/* this entry violates the "one line per file" policy of sysfs */ ++static ssize_t config_show(struct kobject *kobj, struct kobj_attribute *attr, ++ char *buf) ++{ ++ ssize_t err; ++ static char *conf = ++/* this file is generated at compiling */ ++#include "conf.str" ++ ; ++ ++ err = snprintf(buf, PAGE_SIZE, conf); ++ if (unlikely(err >= PAGE_SIZE)) ++ err = -EFBIG; ++ return err; ++} ++ ++static struct kobj_attribute au_config_attr = __ATTR_RO(config); ++#endif ++ ++static struct attribute *au_attr[] = { ++#ifdef CONFIG_AUFS_FS_MODULE ++ &au_config_attr.attr, ++#endif ++ NULL, /* need to NULL terminate the list of attributes */ ++}; ++ ++static struct attribute_group sysaufs_attr_group_body = { ++ .attrs = au_attr ++}; ++ ++struct attribute_group *sysaufs_attr_group = &sysaufs_attr_group_body; ++ ++/* ---------------------------------------------------------------------- */ ++ ++int sysaufs_si_xi_path(struct seq_file *seq, struct super_block *sb) ++{ ++ int err; ++ ++ SiMustAnyLock(sb); ++ ++ err = 0; ++ if (au_opt_test(au_mntflags(sb), XINO)) { ++ err = au_xino_path(seq, au_sbi(sb)->si_xib); ++ seq_putc(seq, '\n'); ++ } ++ return err; ++} ++ ++/* ++ * the lifetime of branch is independent from the entry under sysfs. ++ * sysfs handles the lifetime of the entry, and never call ->show() after it is ++ * unlinked. ++ */ ++static int sysaufs_si_br(struct seq_file *seq, struct super_block *sb, ++ aufs_bindex_t bindex, int idx) ++{ ++ int err; ++ struct path path; ++ struct dentry *root; ++ struct au_branch *br; ++ au_br_perm_str_t perm; ++ ++ AuDbg("b%d\n", bindex); ++ ++ err = 0; ++ root = sb->s_root; ++ di_read_lock_parent(root, !AuLock_IR); ++ br = au_sbr(sb, bindex); ++ ++ switch (idx) { ++ case AuBrSysfs_BR: ++ path.mnt = au_br_mnt(br); ++ path.dentry = au_h_dptr(root, bindex); ++ err = au_seq_path(seq, &path); ++ if (!err) { ++ au_optstr_br_perm(&perm, br->br_perm); ++ err = seq_printf(seq, "=%s\n", perm.a); ++ } ++ break; ++ case AuBrSysfs_BRID: ++ err = seq_printf(seq, "%d\n", br->br_id); ++ break; ++ } ++ di_read_unlock(root, !AuLock_IR); ++ if (err == -1) ++ err = -E2BIG; ++ ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++static struct seq_file *au_seq(char *p, ssize_t len) ++{ ++ struct seq_file *seq; ++ ++ seq = kzalloc(sizeof(*seq), GFP_NOFS); ++ if (seq) { ++ /* mutex_init(&seq.lock); */ ++ seq->buf = p; ++ seq->size = len; ++ return seq; /* success */ ++ } ++ ++ seq = ERR_PTR(-ENOMEM); ++ return seq; ++} ++ ++#define SysaufsBr_PREFIX "br" ++#define SysaufsBrid_PREFIX "brid" ++ ++/* todo: file size may exceed PAGE_SIZE */ ++ssize_t sysaufs_si_show(struct kobject *kobj, struct attribute *attr, ++ char *buf) ++{ ++ ssize_t err; ++ int idx; ++ long l; ++ aufs_bindex_t bend; ++ struct au_sbinfo *sbinfo; ++ struct super_block *sb; ++ struct seq_file *seq; ++ char *name; ++ struct attribute **cattr; ++ ++ sbinfo = container_of(kobj, struct au_sbinfo, si_kobj); ++ sb = sbinfo->si_sb; ++ ++ /* ++ * prevent a race condition between sysfs and aufs. ++ * for instance, sysfs_file_read() calls sysfs_get_active_two() which ++ * prohibits maintaining the sysfs entries. ++ * hew we acquire read lock after sysfs_get_active_two(). ++ * on the other hand, the remount process may maintain the sysfs/aufs ++ * entries after acquiring write lock. ++ * it can cause a deadlock. ++ * simply we gave up processing read here. ++ */ ++ err = -EBUSY; ++ if (unlikely(!si_noflush_read_trylock(sb))) ++ goto out; ++ ++ seq = au_seq(buf, PAGE_SIZE); ++ err = PTR_ERR(seq); ++ if (IS_ERR(seq)) ++ goto out_unlock; ++ ++ name = (void *)attr->name; ++ cattr = sysaufs_si_attrs; ++ while (*cattr) { ++ if (!strcmp(name, (*cattr)->name)) { ++ err = container_of(*cattr, struct sysaufs_si_attr, attr) ++ ->show(seq, sb); ++ goto out_seq; ++ } ++ cattr++; ++ } ++ ++ if (!strncmp(name, SysaufsBrid_PREFIX, ++ sizeof(SysaufsBrid_PREFIX) - 1)) { ++ idx = AuBrSysfs_BRID; ++ name += sizeof(SysaufsBrid_PREFIX) - 1; ++ } else if (!strncmp(name, SysaufsBr_PREFIX, ++ sizeof(SysaufsBr_PREFIX) - 1)) { ++ idx = AuBrSysfs_BR; ++ name += sizeof(SysaufsBr_PREFIX) - 1; ++ } else ++ BUG(); ++ ++ err = kstrtol(name, 10, &l); ++ if (!err) { ++ bend = au_sbend(sb); ++ if (l <= bend) ++ err = sysaufs_si_br(seq, sb, (aufs_bindex_t)l, idx); ++ else ++ err = -ENOENT; ++ } ++ ++out_seq: ++ if (!err) { ++ err = seq->count; ++ /* sysfs limit */ ++ if (unlikely(err == PAGE_SIZE)) ++ err = -EFBIG; ++ } ++ kfree(seq); ++out_unlock: ++ si_read_unlock(sb); ++out: ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++static int au_brinfo(struct super_block *sb, union aufs_brinfo __user *arg) ++{ ++ int err; ++ int16_t brid; ++ aufs_bindex_t bindex, bend; ++ size_t sz; ++ char *buf; ++ struct seq_file *seq; ++ struct au_branch *br; ++ ++ si_read_lock(sb, AuLock_FLUSH); ++ bend = au_sbend(sb); ++ err = bend + 1; ++ if (!arg) ++ goto out; ++ ++ err = -ENOMEM; ++ buf = (void *)__get_free_page(GFP_NOFS); ++ if (unlikely(!buf)) ++ goto out; ++ ++ seq = au_seq(buf, PAGE_SIZE); ++ err = PTR_ERR(seq); ++ if (IS_ERR(seq)) ++ goto out_buf; ++ ++ sz = sizeof(*arg) - offsetof(union aufs_brinfo, path); ++ for (bindex = 0; bindex <= bend; bindex++, arg++) { ++ err = !access_ok(VERIFY_WRITE, arg, sizeof(*arg)); ++ if (unlikely(err)) ++ break; ++ ++ br = au_sbr(sb, bindex); ++ brid = br->br_id; ++ BUILD_BUG_ON(sizeof(brid) != sizeof(arg->id)); ++ err = __put_user(brid, &arg->id); ++ if (unlikely(err)) ++ break; ++ ++ BUILD_BUG_ON(sizeof(br->br_perm) != sizeof(arg->perm)); ++ err = __put_user(br->br_perm, &arg->perm); ++ if (unlikely(err)) ++ break; ++ ++ err = au_seq_path(seq, &br->br_path); ++ if (unlikely(err)) ++ break; ++ err = seq_putc(seq, '\0'); ++ if (!err && seq->count <= sz) { ++ err = copy_to_user(arg->path, seq->buf, seq->count); ++ seq->count = 0; ++ if (unlikely(err)) ++ break; ++ } else { ++ err = -E2BIG; ++ goto out_seq; ++ } ++ } ++ if (unlikely(err)) ++ err = -EFAULT; ++ ++out_seq: ++ kfree(seq); ++out_buf: ++ free_page((unsigned long)buf); ++out: ++ si_read_unlock(sb); ++ return err; ++} ++ ++long au_brinfo_ioctl(struct file *file, unsigned long arg) ++{ ++ return au_brinfo(file->f_dentry->d_sb, (void __user *)arg); ++} ++ ++#ifdef CONFIG_COMPAT ++long au_brinfo_compat_ioctl(struct file *file, unsigned long arg) ++{ ++ return au_brinfo(file->f_dentry->d_sb, compat_ptr(arg)); ++} ++#endif ++ ++/* ---------------------------------------------------------------------- */ ++ ++void sysaufs_br_init(struct au_branch *br) ++{ ++ int i; ++ struct au_brsysfs *br_sysfs; ++ struct attribute *attr; ++ ++ br_sysfs = br->br_sysfs; ++ for (i = 0; i < ARRAY_SIZE(br->br_sysfs); i++) { ++ attr = &br_sysfs->attr; ++ sysfs_attr_init(attr); ++ attr->name = br_sysfs->name; ++ attr->mode = S_IRUGO; ++ br_sysfs++; ++ } ++} ++ ++void sysaufs_brs_del(struct super_block *sb, aufs_bindex_t bindex) ++{ ++ struct au_branch *br; ++ struct kobject *kobj; ++ struct au_brsysfs *br_sysfs; ++ int i; ++ aufs_bindex_t bend; ++ ++ dbgaufs_brs_del(sb, bindex); ++ ++ if (!sysaufs_brs) ++ return; ++ ++ kobj = &au_sbi(sb)->si_kobj; ++ bend = au_sbend(sb); ++ for (; bindex <= bend; bindex++) { ++ br = au_sbr(sb, bindex); ++ br_sysfs = br->br_sysfs; ++ for (i = 0; i < ARRAY_SIZE(br->br_sysfs); i++) { ++ sysfs_remove_file(kobj, &br_sysfs->attr); ++ br_sysfs++; ++ } ++ } ++} ++ ++void sysaufs_brs_add(struct super_block *sb, aufs_bindex_t bindex) ++{ ++ int err, i; ++ aufs_bindex_t bend; ++ struct kobject *kobj; ++ struct au_branch *br; ++ struct au_brsysfs *br_sysfs; ++ ++ dbgaufs_brs_add(sb, bindex); ++ ++ if (!sysaufs_brs) ++ return; ++ ++ kobj = &au_sbi(sb)->si_kobj; ++ bend = au_sbend(sb); ++ for (; bindex <= bend; bindex++) { ++ br = au_sbr(sb, bindex); ++ br_sysfs = br->br_sysfs; ++ snprintf(br_sysfs[AuBrSysfs_BR].name, sizeof(br_sysfs->name), ++ SysaufsBr_PREFIX "%d", bindex); ++ snprintf(br_sysfs[AuBrSysfs_BRID].name, sizeof(br_sysfs->name), ++ SysaufsBrid_PREFIX "%d", bindex); ++ for (i = 0; i < ARRAY_SIZE(br->br_sysfs); i++) { ++ err = sysfs_create_file(kobj, &br_sysfs->attr); ++ if (unlikely(err)) ++ pr_warn("failed %s under sysfs(%d)\n", ++ br_sysfs->name, err); ++ br_sysfs++; ++ } ++ } ++} +diff --git a/fs/aufs/sysrq.c b/fs/aufs/sysrq.c +new file mode 100644 +index 0000000..057c23e +--- /dev/null ++++ b/fs/aufs/sysrq.c +@@ -0,0 +1,157 @@ ++/* ++ * Copyright (C) 2005-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * magic sysrq hanlder ++ */ ++ ++/* #include */ ++#include ++#include "aufs.h" ++ ++/* ---------------------------------------------------------------------- */ ++ ++static void sysrq_sb(struct super_block *sb) ++{ ++ char *plevel; ++ struct au_sbinfo *sbinfo; ++ struct file *file; ++ struct au_sphlhead *files; ++ struct au_finfo *finfo; ++ ++ plevel = au_plevel; ++ au_plevel = KERN_WARNING; ++ ++ /* since we define pr_fmt, call printk directly */ ++#define pr(str) printk(KERN_WARNING AUFS_NAME ": " str) ++ ++ sbinfo = au_sbi(sb); ++ printk(KERN_WARNING "si=%lx\n", sysaufs_si_id(sbinfo)); ++ pr("superblock\n"); ++ au_dpri_sb(sb); ++ ++#if 0 ++ pr("root dentry\n"); ++ au_dpri_dentry(sb->s_root); ++ pr("root inode\n"); ++ au_dpri_inode(sb->s_root->d_inode); ++#endif ++ ++#if 0 ++ do { ++ int err, i, j, ndentry; ++ struct au_dcsub_pages dpages; ++ struct au_dpage *dpage; ++ ++ err = au_dpages_init(&dpages, GFP_ATOMIC); ++ if (unlikely(err)) ++ break; ++ err = au_dcsub_pages(&dpages, sb->s_root, NULL, NULL); ++ if (!err) ++ for (i = 0; i < dpages.ndpage; i++) { ++ dpage = dpages.dpages + i; ++ ndentry = dpage->ndentry; ++ for (j = 0; j < ndentry; j++) ++ au_dpri_dentry(dpage->dentries[j]); ++ } ++ au_dpages_free(&dpages); ++ } while (0); ++#endif ++ ++#if 1 ++ { ++ struct inode *i; ++ ++ pr("isolated inode\n"); ++ spin_lock(&inode_sb_list_lock); ++ list_for_each_entry(i, &sb->s_inodes, i_sb_list) { ++ spin_lock(&i->i_lock); ++ if (1 || hlist_empty(&i->i_dentry)) ++ au_dpri_inode(i); ++ spin_unlock(&i->i_lock); ++ } ++ spin_unlock(&inode_sb_list_lock); ++ } ++#endif ++ pr("files\n"); ++ files = &au_sbi(sb)->si_files; ++ spin_lock(&files->spin); ++ hlist_for_each_entry(finfo, &files->head, fi_hlist) { ++ umode_t mode; ++ ++ file = finfo->fi_file; ++ mode = file_inode(file)->i_mode; ++ if (!special_file(mode)) ++ au_dpri_file(file); ++ } ++ spin_unlock(&files->spin); ++ pr("done\n"); ++ ++#undef pr ++ au_plevel = plevel; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* module parameter */ ++static char *aufs_sysrq_key = "a"; ++module_param_named(sysrq, aufs_sysrq_key, charp, S_IRUGO); ++MODULE_PARM_DESC(sysrq, "MagicSysRq key for " AUFS_NAME); ++ ++static void au_sysrq(int key __maybe_unused) ++{ ++ struct au_sbinfo *sbinfo; ++ ++ lockdep_off(); ++ au_sbilist_lock(); ++ hlist_for_each_entry(sbinfo, &au_sbilist.head, si_list) ++ sysrq_sb(sbinfo->si_sb); ++ au_sbilist_unlock(); ++ lockdep_on(); ++} ++ ++static struct sysrq_key_op au_sysrq_op = { ++ .handler = au_sysrq, ++ .help_msg = "Aufs", ++ .action_msg = "Aufs", ++ .enable_mask = SYSRQ_ENABLE_DUMP ++}; ++ ++/* ---------------------------------------------------------------------- */ ++ ++int __init au_sysrq_init(void) ++{ ++ int err; ++ char key; ++ ++ err = -1; ++ key = *aufs_sysrq_key; ++ if ('a' <= key && key <= 'z') ++ err = register_sysrq_key(key, &au_sysrq_op); ++ if (unlikely(err)) ++ pr_err("err %d, sysrq=%c\n", err, key); ++ return err; ++} ++ ++void au_sysrq_fin(void) ++{ ++ int err; ++ ++ err = unregister_sysrq_key(*aufs_sysrq_key, &au_sysrq_op); ++ if (unlikely(err)) ++ pr_err("err %d (ignored)\n", err); ++} +diff --git a/fs/aufs/vdir.c b/fs/aufs/vdir.c +new file mode 100644 +index 0000000..f942d16 +--- /dev/null ++++ b/fs/aufs/vdir.c +@@ -0,0 +1,888 @@ ++/* ++ * Copyright (C) 2005-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * virtual or vertical directory ++ */ ++ ++#include "aufs.h" ++ ++static unsigned int calc_size(int nlen) ++{ ++ return ALIGN(sizeof(struct au_vdir_de) + nlen, sizeof(ino_t)); ++} ++ ++static int set_deblk_end(union au_vdir_deblk_p *p, ++ union au_vdir_deblk_p *deblk_end) ++{ ++ if (calc_size(0) <= deblk_end->deblk - p->deblk) { ++ p->de->de_str.len = 0; ++ /* smp_mb(); */ ++ return 0; ++ } ++ return -1; /* error */ ++} ++ ++/* returns true or false */ ++static int is_deblk_end(union au_vdir_deblk_p *p, ++ union au_vdir_deblk_p *deblk_end) ++{ ++ if (calc_size(0) <= deblk_end->deblk - p->deblk) ++ return !p->de->de_str.len; ++ return 1; ++} ++ ++static unsigned char *last_deblk(struct au_vdir *vdir) ++{ ++ return vdir->vd_deblk[vdir->vd_nblk - 1]; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* estimate the appropriate size for name hash table */ ++unsigned int au_rdhash_est(loff_t sz) ++{ ++ unsigned int n; ++ ++ n = UINT_MAX; ++ sz >>= 10; ++ if (sz < n) ++ n = sz; ++ if (sz < AUFS_RDHASH_DEF) ++ n = AUFS_RDHASH_DEF; ++ /* pr_info("n %u\n", n); */ ++ return n; ++} ++ ++/* ++ * the allocated memory has to be freed by ++ * au_nhash_wh_free() or au_nhash_de_free(). ++ */ ++int au_nhash_alloc(struct au_nhash *nhash, unsigned int num_hash, gfp_t gfp) ++{ ++ struct hlist_head *head; ++ unsigned int u; ++ size_t sz; ++ ++ sz = sizeof(*nhash->nh_head) * num_hash; ++ head = kmalloc(sz, gfp); ++ if (head) { ++ nhash->nh_num = num_hash; ++ nhash->nh_head = head; ++ for (u = 0; u < num_hash; u++) ++ INIT_HLIST_HEAD(head++); ++ return 0; /* success */ ++ } ++ ++ return -ENOMEM; ++} ++ ++static void nhash_count(struct hlist_head *head) ++{ ++#if 0 ++ unsigned long n; ++ struct hlist_node *pos; ++ ++ n = 0; ++ hlist_for_each(pos, head) ++ n++; ++ pr_info("%lu\n", n); ++#endif ++} ++ ++static void au_nhash_wh_do_free(struct hlist_head *head) ++{ ++ struct au_vdir_wh *pos; ++ struct hlist_node *node; ++ ++ hlist_for_each_entry_safe(pos, node, head, wh_hash) ++ kfree(pos); ++} ++ ++static void au_nhash_de_do_free(struct hlist_head *head) ++{ ++ struct au_vdir_dehstr *pos; ++ struct hlist_node *node; ++ ++ hlist_for_each_entry_safe(pos, node, head, hash) ++ au_cache_free_vdir_dehstr(pos); ++} ++ ++static void au_nhash_do_free(struct au_nhash *nhash, ++ void (*free)(struct hlist_head *head)) ++{ ++ unsigned int n; ++ struct hlist_head *head; ++ ++ n = nhash->nh_num; ++ if (!n) ++ return; ++ ++ head = nhash->nh_head; ++ while (n-- > 0) { ++ nhash_count(head); ++ free(head++); ++ } ++ kfree(nhash->nh_head); ++} ++ ++void au_nhash_wh_free(struct au_nhash *whlist) ++{ ++ au_nhash_do_free(whlist, au_nhash_wh_do_free); ++} ++ ++static void au_nhash_de_free(struct au_nhash *delist) ++{ ++ au_nhash_do_free(delist, au_nhash_de_do_free); ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++int au_nhash_test_longer_wh(struct au_nhash *whlist, aufs_bindex_t btgt, ++ int limit) ++{ ++ int num; ++ unsigned int u, n; ++ struct hlist_head *head; ++ struct au_vdir_wh *pos; ++ ++ num = 0; ++ n = whlist->nh_num; ++ head = whlist->nh_head; ++ for (u = 0; u < n; u++, head++) ++ hlist_for_each_entry(pos, head, wh_hash) ++ if (pos->wh_bindex == btgt && ++num > limit) ++ return 1; ++ return 0; ++} ++ ++static struct hlist_head *au_name_hash(struct au_nhash *nhash, ++ unsigned char *name, ++ unsigned int len) ++{ ++ unsigned int v; ++ /* const unsigned int magic_bit = 12; */ ++ ++ AuDebugOn(!nhash->nh_num || !nhash->nh_head); ++ ++ v = 0; ++ while (len--) ++ v += *name++; ++ /* v = hash_long(v, magic_bit); */ ++ v %= nhash->nh_num; ++ return nhash->nh_head + v; ++} ++ ++static int au_nhash_test_name(struct au_vdir_destr *str, const char *name, ++ int nlen) ++{ ++ return str->len == nlen && !memcmp(str->name, name, nlen); ++} ++ ++/* returns found or not */ ++int au_nhash_test_known_wh(struct au_nhash *whlist, char *name, int nlen) ++{ ++ struct hlist_head *head; ++ struct au_vdir_wh *pos; ++ struct au_vdir_destr *str; ++ ++ head = au_name_hash(whlist, name, nlen); ++ hlist_for_each_entry(pos, head, wh_hash) { ++ str = &pos->wh_str; ++ AuDbg("%.*s\n", str->len, str->name); ++ if (au_nhash_test_name(str, name, nlen)) ++ return 1; ++ } ++ return 0; ++} ++ ++/* returns found(true) or not */ ++static int test_known(struct au_nhash *delist, char *name, int nlen) ++{ ++ struct hlist_head *head; ++ struct au_vdir_dehstr *pos; ++ struct au_vdir_destr *str; ++ ++ head = au_name_hash(delist, name, nlen); ++ hlist_for_each_entry(pos, head, hash) { ++ str = pos->str; ++ AuDbg("%.*s\n", str->len, str->name); ++ if (au_nhash_test_name(str, name, nlen)) ++ return 1; ++ } ++ return 0; ++} ++ ++static void au_shwh_init_wh(struct au_vdir_wh *wh, ino_t ino, ++ unsigned char d_type) ++{ ++#ifdef CONFIG_AUFS_SHWH ++ wh->wh_ino = ino; ++ wh->wh_type = d_type; ++#endif ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++int au_nhash_append_wh(struct au_nhash *whlist, char *name, int nlen, ino_t ino, ++ unsigned int d_type, aufs_bindex_t bindex, ++ unsigned char shwh) ++{ ++ int err; ++ struct au_vdir_destr *str; ++ struct au_vdir_wh *wh; ++ ++ AuDbg("%.*s\n", nlen, name); ++ AuDebugOn(!whlist->nh_num || !whlist->nh_head); ++ ++ err = -ENOMEM; ++ wh = kmalloc(sizeof(*wh) + nlen, GFP_NOFS); ++ if (unlikely(!wh)) ++ goto out; ++ ++ err = 0; ++ wh->wh_bindex = bindex; ++ if (shwh) ++ au_shwh_init_wh(wh, ino, d_type); ++ str = &wh->wh_str; ++ str->len = nlen; ++ memcpy(str->name, name, nlen); ++ hlist_add_head(&wh->wh_hash, au_name_hash(whlist, name, nlen)); ++ /* smp_mb(); */ ++ ++out: ++ return err; ++} ++ ++static int append_deblk(struct au_vdir *vdir) ++{ ++ int err; ++ unsigned long ul; ++ const unsigned int deblk_sz = vdir->vd_deblk_sz; ++ union au_vdir_deblk_p p, deblk_end; ++ unsigned char **o; ++ ++ err = -ENOMEM; ++ o = krealloc(vdir->vd_deblk, sizeof(*o) * (vdir->vd_nblk + 1), ++ GFP_NOFS); ++ if (unlikely(!o)) ++ goto out; ++ ++ vdir->vd_deblk = o; ++ p.deblk = kmalloc(deblk_sz, GFP_NOFS); ++ if (p.deblk) { ++ ul = vdir->vd_nblk++; ++ vdir->vd_deblk[ul] = p.deblk; ++ vdir->vd_last.ul = ul; ++ vdir->vd_last.p.deblk = p.deblk; ++ deblk_end.deblk = p.deblk + deblk_sz; ++ err = set_deblk_end(&p, &deblk_end); ++ } ++ ++out: ++ return err; ++} ++ ++static int append_de(struct au_vdir *vdir, char *name, int nlen, ino_t ino, ++ unsigned int d_type, struct au_nhash *delist) ++{ ++ int err; ++ unsigned int sz; ++ const unsigned int deblk_sz = vdir->vd_deblk_sz; ++ union au_vdir_deblk_p p, *room, deblk_end; ++ struct au_vdir_dehstr *dehstr; ++ ++ p.deblk = last_deblk(vdir); ++ deblk_end.deblk = p.deblk + deblk_sz; ++ room = &vdir->vd_last.p; ++ AuDebugOn(room->deblk < p.deblk || deblk_end.deblk <= room->deblk ++ || !is_deblk_end(room, &deblk_end)); ++ ++ sz = calc_size(nlen); ++ if (unlikely(sz > deblk_end.deblk - room->deblk)) { ++ err = append_deblk(vdir); ++ if (unlikely(err)) ++ goto out; ++ ++ p.deblk = last_deblk(vdir); ++ deblk_end.deblk = p.deblk + deblk_sz; ++ /* smp_mb(); */ ++ AuDebugOn(room->deblk != p.deblk); ++ } ++ ++ err = -ENOMEM; ++ dehstr = au_cache_alloc_vdir_dehstr(); ++ if (unlikely(!dehstr)) ++ goto out; ++ ++ dehstr->str = &room->de->de_str; ++ hlist_add_head(&dehstr->hash, au_name_hash(delist, name, nlen)); ++ room->de->de_ino = ino; ++ room->de->de_type = d_type; ++ room->de->de_str.len = nlen; ++ memcpy(room->de->de_str.name, name, nlen); ++ ++ err = 0; ++ room->deblk += sz; ++ if (unlikely(set_deblk_end(room, &deblk_end))) ++ err = append_deblk(vdir); ++ /* smp_mb(); */ ++ ++out: ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++void au_vdir_free(struct au_vdir *vdir) ++{ ++ unsigned char **deblk; ++ ++ deblk = vdir->vd_deblk; ++ while (vdir->vd_nblk--) ++ kfree(*deblk++); ++ kfree(vdir->vd_deblk); ++ au_cache_free_vdir(vdir); ++} ++ ++static struct au_vdir *alloc_vdir(struct file *file) ++{ ++ struct au_vdir *vdir; ++ struct super_block *sb; ++ int err; ++ ++ sb = file->f_dentry->d_sb; ++ SiMustAnyLock(sb); ++ ++ err = -ENOMEM; ++ vdir = au_cache_alloc_vdir(); ++ if (unlikely(!vdir)) ++ goto out; ++ ++ vdir->vd_deblk = kzalloc(sizeof(*vdir->vd_deblk), GFP_NOFS); ++ if (unlikely(!vdir->vd_deblk)) ++ goto out_free; ++ ++ vdir->vd_deblk_sz = au_sbi(sb)->si_rdblk; ++ if (!vdir->vd_deblk_sz) { ++ /* estimate the appropriate size for deblk */ ++ vdir->vd_deblk_sz = au_dir_size(file, /*dentry*/NULL); ++ /* pr_info("vd_deblk_sz %u\n", vdir->vd_deblk_sz); */ ++ } ++ vdir->vd_nblk = 0; ++ vdir->vd_version = 0; ++ vdir->vd_jiffy = 0; ++ err = append_deblk(vdir); ++ if (!err) ++ return vdir; /* success */ ++ ++ kfree(vdir->vd_deblk); ++ ++out_free: ++ au_cache_free_vdir(vdir); ++out: ++ vdir = ERR_PTR(err); ++ return vdir; ++} ++ ++static int reinit_vdir(struct au_vdir *vdir) ++{ ++ int err; ++ union au_vdir_deblk_p p, deblk_end; ++ ++ while (vdir->vd_nblk > 1) { ++ kfree(vdir->vd_deblk[vdir->vd_nblk - 1]); ++ /* vdir->vd_deblk[vdir->vd_nblk - 1] = NULL; */ ++ vdir->vd_nblk--; ++ } ++ p.deblk = vdir->vd_deblk[0]; ++ deblk_end.deblk = p.deblk + vdir->vd_deblk_sz; ++ err = set_deblk_end(&p, &deblk_end); ++ /* keep vd_dblk_sz */ ++ vdir->vd_last.ul = 0; ++ vdir->vd_last.p.deblk = vdir->vd_deblk[0]; ++ vdir->vd_version = 0; ++ vdir->vd_jiffy = 0; ++ /* smp_mb(); */ ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++#define AuFillVdir_CALLED 1 ++#define AuFillVdir_WHABLE (1 << 1) ++#define AuFillVdir_SHWH (1 << 2) ++#define au_ftest_fillvdir(flags, name) ((flags) & AuFillVdir_##name) ++#define au_fset_fillvdir(flags, name) \ ++ do { (flags) |= AuFillVdir_##name; } while (0) ++#define au_fclr_fillvdir(flags, name) \ ++ do { (flags) &= ~AuFillVdir_##name; } while (0) ++ ++#ifndef CONFIG_AUFS_SHWH ++#undef AuFillVdir_SHWH ++#define AuFillVdir_SHWH 0 ++#endif ++ ++struct fillvdir_arg { ++ struct dir_context ctx; ++ struct file *file; ++ struct au_vdir *vdir; ++ struct au_nhash delist; ++ struct au_nhash whlist; ++ aufs_bindex_t bindex; ++ unsigned int flags; ++ int err; ++}; ++ ++static int fillvdir(struct dir_context *ctx, const char *__name, int nlen, ++ loff_t offset __maybe_unused, u64 h_ino, ++ unsigned int d_type) ++{ ++ struct fillvdir_arg *arg = container_of(ctx, struct fillvdir_arg, ctx); ++ char *name = (void *)__name; ++ struct super_block *sb; ++ ino_t ino; ++ const unsigned char shwh = !!au_ftest_fillvdir(arg->flags, SHWH); ++ ++ arg->err = 0; ++ sb = arg->file->f_dentry->d_sb; ++ au_fset_fillvdir(arg->flags, CALLED); ++ /* smp_mb(); */ ++ if (nlen <= AUFS_WH_PFX_LEN ++ || memcmp(name, AUFS_WH_PFX, AUFS_WH_PFX_LEN)) { ++ if (test_known(&arg->delist, name, nlen) ++ || au_nhash_test_known_wh(&arg->whlist, name, nlen)) ++ goto out; /* already exists or whiteouted */ ++ ++ arg->err = au_ino(sb, arg->bindex, h_ino, d_type, &ino); ++ if (!arg->err) { ++ if (unlikely(nlen > AUFS_MAX_NAMELEN)) ++ d_type = DT_UNKNOWN; ++ arg->err = append_de(arg->vdir, name, nlen, ino, ++ d_type, &arg->delist); ++ } ++ } else if (au_ftest_fillvdir(arg->flags, WHABLE)) { ++ name += AUFS_WH_PFX_LEN; ++ nlen -= AUFS_WH_PFX_LEN; ++ if (au_nhash_test_known_wh(&arg->whlist, name, nlen)) ++ goto out; /* already whiteouted */ ++ ++ if (shwh) ++ arg->err = au_wh_ino(sb, arg->bindex, h_ino, d_type, ++ &ino); ++ if (!arg->err) { ++ if (nlen <= AUFS_MAX_NAMELEN + AUFS_WH_PFX_LEN) ++ d_type = DT_UNKNOWN; ++ arg->err = au_nhash_append_wh ++ (&arg->whlist, name, nlen, ino, d_type, ++ arg->bindex, shwh); ++ } ++ } ++ ++out: ++ if (!arg->err) ++ arg->vdir->vd_jiffy = jiffies; ++ /* smp_mb(); */ ++ AuTraceErr(arg->err); ++ return arg->err; ++} ++ ++static int au_handle_shwh(struct super_block *sb, struct au_vdir *vdir, ++ struct au_nhash *whlist, struct au_nhash *delist) ++{ ++#ifdef CONFIG_AUFS_SHWH ++ int err; ++ unsigned int nh, u; ++ struct hlist_head *head; ++ struct au_vdir_wh *pos; ++ struct hlist_node *n; ++ char *p, *o; ++ struct au_vdir_destr *destr; ++ ++ AuDebugOn(!au_opt_test(au_mntflags(sb), SHWH)); ++ ++ err = -ENOMEM; ++ o = p = (void *)__get_free_page(GFP_NOFS); ++ if (unlikely(!p)) ++ goto out; ++ ++ err = 0; ++ nh = whlist->nh_num; ++ memcpy(p, AUFS_WH_PFX, AUFS_WH_PFX_LEN); ++ p += AUFS_WH_PFX_LEN; ++ for (u = 0; u < nh; u++) { ++ head = whlist->nh_head + u; ++ hlist_for_each_entry_safe(pos, n, head, wh_hash) { ++ destr = &pos->wh_str; ++ memcpy(p, destr->name, destr->len); ++ err = append_de(vdir, o, destr->len + AUFS_WH_PFX_LEN, ++ pos->wh_ino, pos->wh_type, delist); ++ if (unlikely(err)) ++ break; ++ } ++ } ++ ++ free_page((unsigned long)o); ++ ++out: ++ AuTraceErr(err); ++ return err; ++#else ++ return 0; ++#endif ++} ++ ++static int au_do_read_vdir(struct fillvdir_arg *arg) ++{ ++ int err; ++ unsigned int rdhash; ++ loff_t offset; ++ aufs_bindex_t bend, bindex, bstart; ++ unsigned char shwh; ++ struct file *hf, *file; ++ struct super_block *sb; ++ ++ file = arg->file; ++ sb = file->f_dentry->d_sb; ++ SiMustAnyLock(sb); ++ ++ rdhash = au_sbi(sb)->si_rdhash; ++ if (!rdhash) ++ rdhash = au_rdhash_est(au_dir_size(file, /*dentry*/NULL)); ++ err = au_nhash_alloc(&arg->delist, rdhash, GFP_NOFS); ++ if (unlikely(err)) ++ goto out; ++ err = au_nhash_alloc(&arg->whlist, rdhash, GFP_NOFS); ++ if (unlikely(err)) ++ goto out_delist; ++ ++ err = 0; ++ arg->flags = 0; ++ shwh = 0; ++ if (au_opt_test(au_mntflags(sb), SHWH)) { ++ shwh = 1; ++ au_fset_fillvdir(arg->flags, SHWH); ++ } ++ bstart = au_fbstart(file); ++ bend = au_fbend_dir(file); ++ for (bindex = bstart; !err && bindex <= bend; bindex++) { ++ hf = au_hf_dir(file, bindex); ++ if (!hf) ++ continue; ++ ++ offset = vfsub_llseek(hf, 0, SEEK_SET); ++ err = offset; ++ if (unlikely(offset)) ++ break; ++ ++ arg->bindex = bindex; ++ au_fclr_fillvdir(arg->flags, WHABLE); ++ if (shwh ++ || (bindex != bend ++ && au_br_whable(au_sbr_perm(sb, bindex)))) ++ au_fset_fillvdir(arg->flags, WHABLE); ++ do { ++ arg->err = 0; ++ au_fclr_fillvdir(arg->flags, CALLED); ++ /* smp_mb(); */ ++ err = vfsub_iterate_dir(hf, &arg->ctx); ++ if (err >= 0) ++ err = arg->err; ++ } while (!err && au_ftest_fillvdir(arg->flags, CALLED)); ++ ++ /* ++ * dir_relax() may be good for concurrency, but aufs should not ++ * use it since it will cause a lockdep problem. ++ */ ++ } ++ ++ if (!err && shwh) ++ err = au_handle_shwh(sb, arg->vdir, &arg->whlist, &arg->delist); ++ ++ au_nhash_wh_free(&arg->whlist); ++ ++out_delist: ++ au_nhash_de_free(&arg->delist); ++out: ++ return err; ++} ++ ++static int read_vdir(struct file *file, int may_read) ++{ ++ int err; ++ unsigned long expire; ++ unsigned char do_read; ++ struct fillvdir_arg arg = { ++ .ctx = { ++ .actor = au_diractor(fillvdir) ++ } ++ }; ++ struct inode *inode; ++ struct au_vdir *vdir, *allocated; ++ ++ err = 0; ++ inode = file_inode(file); ++ IMustLock(inode); ++ SiMustAnyLock(inode->i_sb); ++ ++ allocated = NULL; ++ do_read = 0; ++ expire = au_sbi(inode->i_sb)->si_rdcache; ++ vdir = au_ivdir(inode); ++ if (!vdir) { ++ do_read = 1; ++ vdir = alloc_vdir(file); ++ err = PTR_ERR(vdir); ++ if (IS_ERR(vdir)) ++ goto out; ++ err = 0; ++ allocated = vdir; ++ } else if (may_read ++ && (inode->i_version != vdir->vd_version ++ || time_after(jiffies, vdir->vd_jiffy + expire))) { ++ do_read = 1; ++ err = reinit_vdir(vdir); ++ if (unlikely(err)) ++ goto out; ++ } ++ ++ if (!do_read) ++ return 0; /* success */ ++ ++ arg.file = file; ++ arg.vdir = vdir; ++ err = au_do_read_vdir(&arg); ++ if (!err) { ++ /* file->f_pos = 0; */ /* todo: ctx->pos? */ ++ vdir->vd_version = inode->i_version; ++ vdir->vd_last.ul = 0; ++ vdir->vd_last.p.deblk = vdir->vd_deblk[0]; ++ if (allocated) ++ au_set_ivdir(inode, allocated); ++ } else if (allocated) ++ au_vdir_free(allocated); ++ ++out: ++ return err; ++} ++ ++static int copy_vdir(struct au_vdir *tgt, struct au_vdir *src) ++{ ++ int err, rerr; ++ unsigned long ul, n; ++ const unsigned int deblk_sz = src->vd_deblk_sz; ++ ++ AuDebugOn(tgt->vd_nblk != 1); ++ ++ err = -ENOMEM; ++ if (tgt->vd_nblk < src->vd_nblk) { ++ unsigned char **p; ++ ++ p = krealloc(tgt->vd_deblk, sizeof(*p) * src->vd_nblk, ++ GFP_NOFS); ++ if (unlikely(!p)) ++ goto out; ++ tgt->vd_deblk = p; ++ } ++ ++ if (tgt->vd_deblk_sz != deblk_sz) { ++ unsigned char *p; ++ ++ tgt->vd_deblk_sz = deblk_sz; ++ p = krealloc(tgt->vd_deblk[0], deblk_sz, GFP_NOFS); ++ if (unlikely(!p)) ++ goto out; ++ tgt->vd_deblk[0] = p; ++ } ++ memcpy(tgt->vd_deblk[0], src->vd_deblk[0], deblk_sz); ++ tgt->vd_version = src->vd_version; ++ tgt->vd_jiffy = src->vd_jiffy; ++ ++ n = src->vd_nblk; ++ for (ul = 1; ul < n; ul++) { ++ tgt->vd_deblk[ul] = kmemdup(src->vd_deblk[ul], deblk_sz, ++ GFP_NOFS); ++ if (unlikely(!tgt->vd_deblk[ul])) ++ goto out; ++ tgt->vd_nblk++; ++ } ++ tgt->vd_nblk = n; ++ tgt->vd_last.ul = tgt->vd_last.ul; ++ tgt->vd_last.p.deblk = tgt->vd_deblk[tgt->vd_last.ul]; ++ tgt->vd_last.p.deblk += src->vd_last.p.deblk ++ - src->vd_deblk[src->vd_last.ul]; ++ /* smp_mb(); */ ++ return 0; /* success */ ++ ++out: ++ rerr = reinit_vdir(tgt); ++ BUG_ON(rerr); ++ return err; ++} ++ ++int au_vdir_init(struct file *file) ++{ ++ int err; ++ struct inode *inode; ++ struct au_vdir *vdir_cache, *allocated; ++ ++ /* test file->f_pos here instead of ctx->pos */ ++ err = read_vdir(file, !file->f_pos); ++ if (unlikely(err)) ++ goto out; ++ ++ allocated = NULL; ++ vdir_cache = au_fvdir_cache(file); ++ if (!vdir_cache) { ++ vdir_cache = alloc_vdir(file); ++ err = PTR_ERR(vdir_cache); ++ if (IS_ERR(vdir_cache)) ++ goto out; ++ allocated = vdir_cache; ++ } else if (!file->f_pos && vdir_cache->vd_version != file->f_version) { ++ /* test file->f_pos here instead of ctx->pos */ ++ err = reinit_vdir(vdir_cache); ++ if (unlikely(err)) ++ goto out; ++ } else ++ return 0; /* success */ ++ ++ inode = file_inode(file); ++ err = copy_vdir(vdir_cache, au_ivdir(inode)); ++ if (!err) { ++ file->f_version = inode->i_version; ++ if (allocated) ++ au_set_fvdir_cache(file, allocated); ++ } else if (allocated) ++ au_vdir_free(allocated); ++ ++out: ++ return err; ++} ++ ++static loff_t calc_offset(struct au_vdir *vdir) ++{ ++ loff_t offset; ++ union au_vdir_deblk_p p; ++ ++ p.deblk = vdir->vd_deblk[vdir->vd_last.ul]; ++ offset = vdir->vd_last.p.deblk - p.deblk; ++ offset += vdir->vd_deblk_sz * vdir->vd_last.ul; ++ return offset; ++} ++ ++/* returns true or false */ ++static int seek_vdir(struct file *file, struct dir_context *ctx) ++{ ++ int valid; ++ unsigned int deblk_sz; ++ unsigned long ul, n; ++ loff_t offset; ++ union au_vdir_deblk_p p, deblk_end; ++ struct au_vdir *vdir_cache; ++ ++ valid = 1; ++ vdir_cache = au_fvdir_cache(file); ++ offset = calc_offset(vdir_cache); ++ AuDbg("offset %lld\n", offset); ++ if (ctx->pos == offset) ++ goto out; ++ ++ vdir_cache->vd_last.ul = 0; ++ vdir_cache->vd_last.p.deblk = vdir_cache->vd_deblk[0]; ++ if (!ctx->pos) ++ goto out; ++ ++ valid = 0; ++ deblk_sz = vdir_cache->vd_deblk_sz; ++ ul = div64_u64(ctx->pos, deblk_sz); ++ AuDbg("ul %lu\n", ul); ++ if (ul >= vdir_cache->vd_nblk) ++ goto out; ++ ++ n = vdir_cache->vd_nblk; ++ for (; ul < n; ul++) { ++ p.deblk = vdir_cache->vd_deblk[ul]; ++ deblk_end.deblk = p.deblk + deblk_sz; ++ offset = ul; ++ offset *= deblk_sz; ++ while (!is_deblk_end(&p, &deblk_end) && offset < ctx->pos) { ++ unsigned int l; ++ ++ l = calc_size(p.de->de_str.len); ++ offset += l; ++ p.deblk += l; ++ } ++ if (!is_deblk_end(&p, &deblk_end)) { ++ valid = 1; ++ vdir_cache->vd_last.ul = ul; ++ vdir_cache->vd_last.p = p; ++ break; ++ } ++ } ++ ++out: ++ /* smp_mb(); */ ++ AuTraceErr(!valid); ++ return valid; ++} ++ ++int au_vdir_fill_de(struct file *file, struct dir_context *ctx) ++{ ++ unsigned int l, deblk_sz; ++ union au_vdir_deblk_p deblk_end; ++ struct au_vdir *vdir_cache; ++ struct au_vdir_de *de; ++ ++ vdir_cache = au_fvdir_cache(file); ++ if (!seek_vdir(file, ctx)) ++ return 0; ++ ++ deblk_sz = vdir_cache->vd_deblk_sz; ++ while (1) { ++ deblk_end.deblk = vdir_cache->vd_deblk[vdir_cache->vd_last.ul]; ++ deblk_end.deblk += deblk_sz; ++ while (!is_deblk_end(&vdir_cache->vd_last.p, &deblk_end)) { ++ de = vdir_cache->vd_last.p.de; ++ AuDbg("%.*s, off%lld, i%lu, dt%d\n", ++ de->de_str.len, de->de_str.name, ctx->pos, ++ (unsigned long)de->de_ino, de->de_type); ++ if (unlikely(!dir_emit(ctx, de->de_str.name, ++ de->de_str.len, de->de_ino, ++ de->de_type))) { ++ /* todo: ignore the error caused by udba? */ ++ /* return err; */ ++ return 0; ++ } ++ ++ l = calc_size(de->de_str.len); ++ vdir_cache->vd_last.p.deblk += l; ++ ctx->pos += l; ++ } ++ if (vdir_cache->vd_last.ul < vdir_cache->vd_nblk - 1) { ++ vdir_cache->vd_last.ul++; ++ vdir_cache->vd_last.p.deblk ++ = vdir_cache->vd_deblk[vdir_cache->vd_last.ul]; ++ ctx->pos = deblk_sz * vdir_cache->vd_last.ul; ++ continue; ++ } ++ break; ++ } ++ ++ /* smp_mb(); */ ++ return 0; ++} +diff --git a/fs/aufs/vfsub.c b/fs/aufs/vfsub.c +new file mode 100644 +index 0000000..5fd008c +--- /dev/null ++++ b/fs/aufs/vfsub.c +@@ -0,0 +1,864 @@ ++/* ++ * Copyright (C) 2005-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * sub-routines for VFS ++ */ ++ ++#include ++#include ++#include ++#include ++#include "../fs/mount.h" ++#include "aufs.h" ++ ++#ifdef CONFIG_AUFS_BR_FUSE ++int vfsub_test_mntns(struct vfsmount *mnt, struct super_block *h_sb) ++{ ++ struct nsproxy *ns; ++ ++ if (!au_test_fuse(h_sb) || !au_userns) ++ return 0; ++ ++ ns = current->nsproxy; ++ /* no {get,put}_nsproxy(ns) */ ++ return real_mount(mnt)->mnt_ns == ns->mnt_ns ? 0 : -EACCES; ++} ++#endif ++ ++/* ---------------------------------------------------------------------- */ ++ ++int vfsub_update_h_iattr(struct path *h_path, int *did) ++{ ++ int err; ++ struct kstat st; ++ struct super_block *h_sb; ++ ++ /* for remote fs, leave work for its getattr or d_revalidate */ ++ /* for bad i_attr fs, handle them in aufs_getattr() */ ++ /* still some fs may acquire i_mutex. we need to skip them */ ++ err = 0; ++ if (!did) ++ did = &err; ++ h_sb = h_path->dentry->d_sb; ++ *did = (!au_test_fs_remote(h_sb) && au_test_fs_refresh_iattr(h_sb)); ++ if (*did) ++ err = vfs_getattr(h_path, &st); ++ ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++struct file *vfsub_dentry_open(struct path *path, int flags) ++{ ++ struct file *file; ++ ++ file = dentry_open(path, flags /* | __FMODE_NONOTIFY */, ++ current_cred()); ++ if (!IS_ERR_OR_NULL(file) ++ && (file->f_mode & (FMODE_READ | FMODE_WRITE)) == FMODE_READ) ++ i_readcount_inc(path->dentry->d_inode); ++ ++ return file; ++} ++ ++struct file *vfsub_filp_open(const char *path, int oflags, int mode) ++{ ++ struct file *file; ++ ++ lockdep_off(); ++ file = filp_open(path, ++ oflags /* | __FMODE_NONOTIFY */, ++ mode); ++ lockdep_on(); ++ if (IS_ERR(file)) ++ goto out; ++ vfsub_update_h_iattr(&file->f_path, /*did*/NULL); /*ignore*/ ++ ++out: ++ return file; ++} ++ ++/* ++ * Ideally this function should call VFS:do_last() in order to keep all its ++ * checkings. But it is very hard for aufs to regenerate several VFS internal ++ * structure such as nameidata. This is a second (or third) best approach. ++ * cf. linux/fs/namei.c:do_last(), lookup_open() and atomic_open(). ++ */ ++int vfsub_atomic_open(struct inode *dir, struct dentry *dentry, ++ struct vfsub_aopen_args *args, struct au_branch *br) ++{ ++ int err; ++ struct file *file = args->file; ++ /* copied from linux/fs/namei.c:atomic_open() */ ++ struct dentry *const DENTRY_NOT_SET = (void *)-1UL; ++ ++ IMustLock(dir); ++ AuDebugOn(!dir->i_op->atomic_open); ++ ++ err = au_br_test_oflag(args->open_flag, br); ++ if (unlikely(err)) ++ goto out; ++ ++ args->file->f_path.dentry = DENTRY_NOT_SET; ++ args->file->f_path.mnt = au_br_mnt(br); ++ err = dir->i_op->atomic_open(dir, dentry, file, args->open_flag, ++ args->create_mode, args->opened); ++ if (err >= 0) { ++ /* some filesystems don't set FILE_CREATED while succeeded? */ ++ if (*args->opened & FILE_CREATED) ++ fsnotify_create(dir, dentry); ++ } else ++ goto out; ++ ++ ++ if (!err) { ++ /* todo: call VFS:may_open() here */ ++ err = open_check_o_direct(file); ++ /* todo: ima_file_check() too? */ ++ if (!err && (args->open_flag & __FMODE_EXEC)) ++ err = deny_write_access(file); ++ if (unlikely(err)) ++ /* note that the file is created and still opened */ ++ goto out; ++ } ++ ++ atomic_inc(&br->br_count); ++ fsnotify_open(file); ++ ++out: ++ return err; ++} ++ ++int vfsub_kern_path(const char *name, unsigned int flags, struct path *path) ++{ ++ int err; ++ ++ err = kern_path(name, flags, path); ++ if (!err && path->dentry->d_inode) ++ vfsub_update_h_iattr(path, /*did*/NULL); /*ignore*/ ++ return err; ++} ++ ++struct dentry *vfsub_lookup_one_len(const char *name, struct dentry *parent, ++ int len) ++{ ++ struct path path = { ++ .mnt = NULL ++ }; ++ ++ /* VFS checks it too, but by WARN_ON_ONCE() */ ++ IMustLock(parent->d_inode); ++ ++ path.dentry = lookup_one_len(name, parent, len); ++ if (IS_ERR(path.dentry)) ++ goto out; ++ if (path.dentry->d_inode) ++ vfsub_update_h_iattr(&path, /*did*/NULL); /*ignore*/ ++ ++out: ++ AuTraceErrPtr(path.dentry); ++ return path.dentry; ++} ++ ++void vfsub_call_lkup_one(void *args) ++{ ++ struct vfsub_lkup_one_args *a = args; ++ *a->errp = vfsub_lkup_one(a->name, a->parent); ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++struct dentry *vfsub_lock_rename(struct dentry *d1, struct au_hinode *hdir1, ++ struct dentry *d2, struct au_hinode *hdir2) ++{ ++ struct dentry *d; ++ ++ lockdep_off(); ++ d = lock_rename(d1, d2); ++ lockdep_on(); ++ au_hn_suspend(hdir1); ++ if (hdir1 != hdir2) ++ au_hn_suspend(hdir2); ++ ++ return d; ++} ++ ++void vfsub_unlock_rename(struct dentry *d1, struct au_hinode *hdir1, ++ struct dentry *d2, struct au_hinode *hdir2) ++{ ++ au_hn_resume(hdir1); ++ if (hdir1 != hdir2) ++ au_hn_resume(hdir2); ++ lockdep_off(); ++ unlock_rename(d1, d2); ++ lockdep_on(); ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++int vfsub_create(struct inode *dir, struct path *path, int mode, bool want_excl) ++{ ++ int err; ++ struct dentry *d; ++ ++ IMustLock(dir); ++ ++ d = path->dentry; ++ path->dentry = d->d_parent; ++ err = security_path_mknod(path, d, mode, 0); ++ path->dentry = d; ++ if (unlikely(err)) ++ goto out; ++ ++ lockdep_off(); ++ err = vfs_create(dir, path->dentry, mode, want_excl); ++ lockdep_on(); ++ if (!err) { ++ struct path tmp = *path; ++ int did; ++ ++ vfsub_update_h_iattr(&tmp, &did); ++ if (did) { ++ tmp.dentry = path->dentry->d_parent; ++ vfsub_update_h_iattr(&tmp, /*did*/NULL); ++ } ++ /*ignore*/ ++ } ++ ++out: ++ return err; ++} ++ ++int vfsub_symlink(struct inode *dir, struct path *path, const char *symname) ++{ ++ int err; ++ struct dentry *d; ++ ++ IMustLock(dir); ++ ++ d = path->dentry; ++ path->dentry = d->d_parent; ++ err = security_path_symlink(path, d, symname); ++ path->dentry = d; ++ if (unlikely(err)) ++ goto out; ++ ++ lockdep_off(); ++ err = vfs_symlink(dir, path->dentry, symname); ++ lockdep_on(); ++ if (!err) { ++ struct path tmp = *path; ++ int did; ++ ++ vfsub_update_h_iattr(&tmp, &did); ++ if (did) { ++ tmp.dentry = path->dentry->d_parent; ++ vfsub_update_h_iattr(&tmp, /*did*/NULL); ++ } ++ /*ignore*/ ++ } ++ ++out: ++ return err; ++} ++ ++int vfsub_mknod(struct inode *dir, struct path *path, int mode, dev_t dev) ++{ ++ int err; ++ struct dentry *d; ++ ++ IMustLock(dir); ++ ++ d = path->dentry; ++ path->dentry = d->d_parent; ++ err = security_path_mknod(path, d, mode, new_encode_dev(dev)); ++ path->dentry = d; ++ if (unlikely(err)) ++ goto out; ++ ++ lockdep_off(); ++ err = vfs_mknod(dir, path->dentry, mode, dev); ++ lockdep_on(); ++ if (!err) { ++ struct path tmp = *path; ++ int did; ++ ++ vfsub_update_h_iattr(&tmp, &did); ++ if (did) { ++ tmp.dentry = path->dentry->d_parent; ++ vfsub_update_h_iattr(&tmp, /*did*/NULL); ++ } ++ /*ignore*/ ++ } ++ ++out: ++ return err; ++} ++ ++static int au_test_nlink(struct inode *inode) ++{ ++ const unsigned int link_max = UINT_MAX >> 1; /* rough margin */ ++ ++ if (!au_test_fs_no_limit_nlink(inode->i_sb) ++ || inode->i_nlink < link_max) ++ return 0; ++ return -EMLINK; ++} ++ ++int vfsub_link(struct dentry *src_dentry, struct inode *dir, struct path *path, ++ struct inode **delegated_inode) ++{ ++ int err; ++ struct dentry *d; ++ ++ IMustLock(dir); ++ ++ err = au_test_nlink(src_dentry->d_inode); ++ if (unlikely(err)) ++ return err; ++ ++ /* we don't call may_linkat() */ ++ d = path->dentry; ++ path->dentry = d->d_parent; ++ err = security_path_link(src_dentry, path, d); ++ path->dentry = d; ++ if (unlikely(err)) ++ goto out; ++ ++ lockdep_off(); ++ err = vfs_link(src_dentry, dir, path->dentry, delegated_inode); ++ lockdep_on(); ++ if (!err) { ++ struct path tmp = *path; ++ int did; ++ ++ /* fuse has different memory inode for the same inumber */ ++ vfsub_update_h_iattr(&tmp, &did); ++ if (did) { ++ tmp.dentry = path->dentry->d_parent; ++ vfsub_update_h_iattr(&tmp, /*did*/NULL); ++ tmp.dentry = src_dentry; ++ vfsub_update_h_iattr(&tmp, /*did*/NULL); ++ } ++ /*ignore*/ ++ } ++ ++out: ++ return err; ++} ++ ++int vfsub_rename(struct inode *src_dir, struct dentry *src_dentry, ++ struct inode *dir, struct path *path, ++ struct inode **delegated_inode) ++{ ++ int err; ++ struct path tmp = { ++ .mnt = path->mnt ++ }; ++ struct dentry *d; ++ ++ IMustLock(dir); ++ IMustLock(src_dir); ++ ++ d = path->dentry; ++ path->dentry = d->d_parent; ++ tmp.dentry = src_dentry->d_parent; ++ err = security_path_rename(&tmp, src_dentry, path, d, /*flags*/0); ++ path->dentry = d; ++ if (unlikely(err)) ++ goto out; ++ ++ lockdep_off(); ++ err = vfs_rename(src_dir, src_dentry, dir, path->dentry, ++ delegated_inode, /*flags*/0); ++ lockdep_on(); ++ if (!err) { ++ int did; ++ ++ tmp.dentry = d->d_parent; ++ vfsub_update_h_iattr(&tmp, &did); ++ if (did) { ++ tmp.dentry = src_dentry; ++ vfsub_update_h_iattr(&tmp, /*did*/NULL); ++ tmp.dentry = src_dentry->d_parent; ++ vfsub_update_h_iattr(&tmp, /*did*/NULL); ++ } ++ /*ignore*/ ++ } ++ ++out: ++ return err; ++} ++ ++int vfsub_mkdir(struct inode *dir, struct path *path, int mode) ++{ ++ int err; ++ struct dentry *d; ++ ++ IMustLock(dir); ++ ++ d = path->dentry; ++ path->dentry = d->d_parent; ++ err = security_path_mkdir(path, d, mode); ++ path->dentry = d; ++ if (unlikely(err)) ++ goto out; ++ ++ lockdep_off(); ++ err = vfs_mkdir(dir, path->dentry, mode); ++ lockdep_on(); ++ if (!err) { ++ struct path tmp = *path; ++ int did; ++ ++ vfsub_update_h_iattr(&tmp, &did); ++ if (did) { ++ tmp.dentry = path->dentry->d_parent; ++ vfsub_update_h_iattr(&tmp, /*did*/NULL); ++ } ++ /*ignore*/ ++ } ++ ++out: ++ return err; ++} ++ ++int vfsub_rmdir(struct inode *dir, struct path *path) ++{ ++ int err; ++ struct dentry *d; ++ ++ IMustLock(dir); ++ ++ d = path->dentry; ++ path->dentry = d->d_parent; ++ err = security_path_rmdir(path, d); ++ path->dentry = d; ++ if (unlikely(err)) ++ goto out; ++ ++ lockdep_off(); ++ err = vfs_rmdir(dir, path->dentry); ++ lockdep_on(); ++ if (!err) { ++ struct path tmp = { ++ .dentry = path->dentry->d_parent, ++ .mnt = path->mnt ++ }; ++ ++ vfsub_update_h_iattr(&tmp, /*did*/NULL); /*ignore*/ ++ } ++ ++out: ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* todo: support mmap_sem? */ ++ssize_t vfsub_read_u(struct file *file, char __user *ubuf, size_t count, ++ loff_t *ppos) ++{ ++ ssize_t err; ++ ++ lockdep_off(); ++ err = vfs_read(file, ubuf, count, ppos); ++ lockdep_on(); ++ if (err >= 0) ++ vfsub_update_h_iattr(&file->f_path, /*did*/NULL); /*ignore*/ ++ return err; ++} ++ ++/* todo: kernel_read()? */ ++ssize_t vfsub_read_k(struct file *file, void *kbuf, size_t count, ++ loff_t *ppos) ++{ ++ ssize_t err; ++ mm_segment_t oldfs; ++ union { ++ void *k; ++ char __user *u; ++ } buf; ++ ++ buf.k = kbuf; ++ oldfs = get_fs(); ++ set_fs(KERNEL_DS); ++ err = vfsub_read_u(file, buf.u, count, ppos); ++ set_fs(oldfs); ++ return err; ++} ++ ++ssize_t vfsub_write_u(struct file *file, const char __user *ubuf, size_t count, ++ loff_t *ppos) ++{ ++ ssize_t err; ++ ++ lockdep_off(); ++ err = vfs_write(file, ubuf, count, ppos); ++ lockdep_on(); ++ if (err >= 0) ++ vfsub_update_h_iattr(&file->f_path, /*did*/NULL); /*ignore*/ ++ return err; ++} ++ ++ssize_t vfsub_write_k(struct file *file, void *kbuf, size_t count, loff_t *ppos) ++{ ++ ssize_t err; ++ mm_segment_t oldfs; ++ union { ++ void *k; ++ const char __user *u; ++ } buf; ++ ++ buf.k = kbuf; ++ oldfs = get_fs(); ++ set_fs(KERNEL_DS); ++ err = vfsub_write_u(file, buf.u, count, ppos); ++ set_fs(oldfs); ++ return err; ++} ++ ++int vfsub_flush(struct file *file, fl_owner_t id) ++{ ++ int err; ++ ++ err = 0; ++ if (file->f_op->flush) { ++ if (!au_test_nfs(file->f_dentry->d_sb)) ++ err = file->f_op->flush(file, id); ++ else { ++ lockdep_off(); ++ err = file->f_op->flush(file, id); ++ lockdep_on(); ++ } ++ if (!err) ++ vfsub_update_h_iattr(&file->f_path, /*did*/NULL); ++ /*ignore*/ ++ } ++ return err; ++} ++ ++int vfsub_iterate_dir(struct file *file, struct dir_context *ctx) ++{ ++ int err; ++ ++ AuDbg("%pD, ctx{%pf, %llu}\n", file, ctx->actor, ctx->pos); ++ ++ lockdep_off(); ++ err = iterate_dir(file, ctx); ++ lockdep_on(); ++ if (err >= 0) ++ vfsub_update_h_iattr(&file->f_path, /*did*/NULL); /*ignore*/ ++ return err; ++} ++ ++long vfsub_splice_to(struct file *in, loff_t *ppos, ++ struct pipe_inode_info *pipe, size_t len, ++ unsigned int flags) ++{ ++ long err; ++ ++ lockdep_off(); ++ err = do_splice_to(in, ppos, pipe, len, flags); ++ lockdep_on(); ++ file_accessed(in); ++ if (err >= 0) ++ vfsub_update_h_iattr(&in->f_path, /*did*/NULL); /*ignore*/ ++ return err; ++} ++ ++long vfsub_splice_from(struct pipe_inode_info *pipe, struct file *out, ++ loff_t *ppos, size_t len, unsigned int flags) ++{ ++ long err; ++ ++ lockdep_off(); ++ err = do_splice_from(pipe, out, ppos, len, flags); ++ lockdep_on(); ++ if (err >= 0) ++ vfsub_update_h_iattr(&out->f_path, /*did*/NULL); /*ignore*/ ++ return err; ++} ++ ++int vfsub_fsync(struct file *file, struct path *path, int datasync) ++{ ++ int err; ++ ++ /* file can be NULL */ ++ lockdep_off(); ++ err = vfs_fsync(file, datasync); ++ lockdep_on(); ++ if (!err) { ++ if (!path) { ++ AuDebugOn(!file); ++ path = &file->f_path; ++ } ++ vfsub_update_h_iattr(path, /*did*/NULL); /*ignore*/ ++ } ++ return err; ++} ++ ++/* cf. open.c:do_sys_truncate() and do_sys_ftruncate() */ ++int vfsub_trunc(struct path *h_path, loff_t length, unsigned int attr, ++ struct file *h_file) ++{ ++ int err; ++ struct inode *h_inode; ++ struct super_block *h_sb; ++ ++ if (!h_file) { ++ err = vfsub_truncate(h_path, length); ++ goto out; ++ } ++ ++ h_inode = h_path->dentry->d_inode; ++ h_sb = h_inode->i_sb; ++ lockdep_off(); ++ sb_start_write(h_sb); ++ lockdep_on(); ++ err = locks_verify_truncate(h_inode, h_file, length); ++ if (!err) ++ err = security_path_truncate(h_path); ++ if (!err) { ++ lockdep_off(); ++ err = do_truncate(h_path->dentry, length, attr, h_file); ++ lockdep_on(); ++ } ++ lockdep_off(); ++ sb_end_write(h_sb); ++ lockdep_on(); ++ ++out: ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++struct au_vfsub_mkdir_args { ++ int *errp; ++ struct inode *dir; ++ struct path *path; ++ int mode; ++}; ++ ++static void au_call_vfsub_mkdir(void *args) ++{ ++ struct au_vfsub_mkdir_args *a = args; ++ *a->errp = vfsub_mkdir(a->dir, a->path, a->mode); ++} ++ ++int vfsub_sio_mkdir(struct inode *dir, struct path *path, int mode) ++{ ++ int err, do_sio, wkq_err; ++ ++ do_sio = au_test_h_perm_sio(dir, MAY_EXEC | MAY_WRITE); ++ if (!do_sio) { ++ lockdep_off(); ++ err = vfsub_mkdir(dir, path, mode); ++ lockdep_on(); ++ } else { ++ struct au_vfsub_mkdir_args args = { ++ .errp = &err, ++ .dir = dir, ++ .path = path, ++ .mode = mode ++ }; ++ wkq_err = au_wkq_wait(au_call_vfsub_mkdir, &args); ++ if (unlikely(wkq_err)) ++ err = wkq_err; ++ } ++ ++ return err; ++} ++ ++struct au_vfsub_rmdir_args { ++ int *errp; ++ struct inode *dir; ++ struct path *path; ++}; ++ ++static void au_call_vfsub_rmdir(void *args) ++{ ++ struct au_vfsub_rmdir_args *a = args; ++ *a->errp = vfsub_rmdir(a->dir, a->path); ++} ++ ++int vfsub_sio_rmdir(struct inode *dir, struct path *path) ++{ ++ int err, do_sio, wkq_err; ++ ++ do_sio = au_test_h_perm_sio(dir, MAY_EXEC | MAY_WRITE); ++ if (!do_sio) { ++ lockdep_off(); ++ err = vfsub_rmdir(dir, path); ++ lockdep_on(); ++ } else { ++ struct au_vfsub_rmdir_args args = { ++ .errp = &err, ++ .dir = dir, ++ .path = path ++ }; ++ wkq_err = au_wkq_wait(au_call_vfsub_rmdir, &args); ++ if (unlikely(wkq_err)) ++ err = wkq_err; ++ } ++ ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++struct notify_change_args { ++ int *errp; ++ struct path *path; ++ struct iattr *ia; ++ struct inode **delegated_inode; ++}; ++ ++static void call_notify_change(void *args) ++{ ++ struct notify_change_args *a = args; ++ struct inode *h_inode; ++ ++ h_inode = a->path->dentry->d_inode; ++ IMustLock(h_inode); ++ ++ *a->errp = -EPERM; ++ if (!IS_IMMUTABLE(h_inode) && !IS_APPEND(h_inode)) { ++ lockdep_off(); ++ *a->errp = notify_change(a->path->dentry, a->ia, ++ a->delegated_inode); ++ lockdep_on(); ++ if (!*a->errp) ++ vfsub_update_h_iattr(a->path, /*did*/NULL); /*ignore*/ ++ } ++ AuTraceErr(*a->errp); ++} ++ ++int vfsub_notify_change(struct path *path, struct iattr *ia, ++ struct inode **delegated_inode) ++{ ++ int err; ++ struct notify_change_args args = { ++ .errp = &err, ++ .path = path, ++ .ia = ia, ++ .delegated_inode = delegated_inode ++ }; ++ ++ call_notify_change(&args); ++ ++ return err; ++} ++ ++int vfsub_sio_notify_change(struct path *path, struct iattr *ia, ++ struct inode **delegated_inode) ++{ ++ int err, wkq_err; ++ struct notify_change_args args = { ++ .errp = &err, ++ .path = path, ++ .ia = ia, ++ .delegated_inode = delegated_inode ++ }; ++ ++ wkq_err = au_wkq_wait(call_notify_change, &args); ++ if (unlikely(wkq_err)) ++ err = wkq_err; ++ ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++struct unlink_args { ++ int *errp; ++ struct inode *dir; ++ struct path *path; ++ struct inode **delegated_inode; ++}; ++ ++static void call_unlink(void *args) ++{ ++ struct unlink_args *a = args; ++ struct dentry *d = a->path->dentry; ++ struct inode *h_inode; ++ const int stop_sillyrename = (au_test_nfs(d->d_sb) ++ && au_dcount(d) == 1); ++ ++ IMustLock(a->dir); ++ ++ a->path->dentry = d->d_parent; ++ *a->errp = security_path_unlink(a->path, d); ++ a->path->dentry = d; ++ if (unlikely(*a->errp)) ++ return; ++ ++ if (!stop_sillyrename) ++ dget(d); ++ h_inode = d->d_inode; ++ if (h_inode) ++ ihold(h_inode); ++ ++ lockdep_off(); ++ *a->errp = vfs_unlink(a->dir, d, a->delegated_inode); ++ lockdep_on(); ++ if (!*a->errp) { ++ struct path tmp = { ++ .dentry = d->d_parent, ++ .mnt = a->path->mnt ++ }; ++ vfsub_update_h_iattr(&tmp, /*did*/NULL); /*ignore*/ ++ } ++ ++ if (!stop_sillyrename) ++ dput(d); ++ if (h_inode) ++ iput(h_inode); ++ ++ AuTraceErr(*a->errp); ++} ++ ++/* ++ * @dir: must be locked. ++ * @dentry: target dentry. ++ */ ++int vfsub_unlink(struct inode *dir, struct path *path, ++ struct inode **delegated_inode, int force) ++{ ++ int err; ++ struct unlink_args args = { ++ .errp = &err, ++ .dir = dir, ++ .path = path, ++ .delegated_inode = delegated_inode ++ }; ++ ++ if (!force) ++ call_unlink(&args); ++ else { ++ int wkq_err; ++ ++ wkq_err = au_wkq_wait(call_unlink, &args); ++ if (unlikely(wkq_err)) ++ err = wkq_err; ++ } ++ ++ return err; ++} +diff --git a/fs/aufs/vfsub.h b/fs/aufs/vfsub.h +new file mode 100644 +index 0000000..2c33298 +--- /dev/null ++++ b/fs/aufs/vfsub.h +@@ -0,0 +1,315 @@ ++/* ++ * Copyright (C) 2005-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * sub-routines for VFS ++ */ ++ ++#ifndef __AUFS_VFSUB_H__ ++#define __AUFS_VFSUB_H__ ++ ++#ifdef __KERNEL__ ++ ++#include ++#include ++#include ++#include ++#include "debug.h" ++ ++/* copied from linux/fs/internal.h */ ++/* todo: BAD approach!! */ ++extern void __mnt_drop_write(struct vfsmount *); ++extern spinlock_t inode_sb_list_lock; ++extern int open_check_o_direct(struct file *f); ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* lock subclass for lower inode */ ++/* default MAX_LOCKDEP_SUBCLASSES(8) is not enough */ ++/* reduce? gave up. */ ++enum { ++ AuLsc_I_Begin = I_MUTEX_PARENT2, /* 5 */ ++ AuLsc_I_PARENT, /* lower inode, parent first */ ++ AuLsc_I_PARENT2, /* copyup dirs */ ++ AuLsc_I_PARENT3, /* copyup wh */ ++ AuLsc_I_CHILD, ++ AuLsc_I_CHILD2, ++ AuLsc_I_End ++}; ++ ++/* to debug easier, do not make them inlined functions */ ++#define MtxMustLock(mtx) AuDebugOn(!mutex_is_locked(mtx)) ++#define IMustLock(i) MtxMustLock(&(i)->i_mutex) ++ ++/* ---------------------------------------------------------------------- */ ++ ++static inline void vfsub_drop_nlink(struct inode *inode) ++{ ++ AuDebugOn(!inode->i_nlink); ++ drop_nlink(inode); ++} ++ ++static inline void vfsub_dead_dir(struct inode *inode) ++{ ++ AuDebugOn(!S_ISDIR(inode->i_mode)); ++ inode->i_flags |= S_DEAD; ++ clear_nlink(inode); ++} ++ ++static inline int vfsub_native_ro(struct inode *inode) ++{ ++ return (inode->i_sb->s_flags & MS_RDONLY) ++ || IS_RDONLY(inode) ++ /* || IS_APPEND(inode) */ ++ || IS_IMMUTABLE(inode); ++} ++ ++#ifdef CONFIG_AUFS_BR_FUSE ++int vfsub_test_mntns(struct vfsmount *mnt, struct super_block *h_sb); ++#else ++AuStubInt0(vfsub_test_mntns, struct vfsmount *mnt, struct super_block *h_sb); ++#endif ++ ++/* ---------------------------------------------------------------------- */ ++ ++int vfsub_update_h_iattr(struct path *h_path, int *did); ++struct file *vfsub_dentry_open(struct path *path, int flags); ++struct file *vfsub_filp_open(const char *path, int oflags, int mode); ++struct vfsub_aopen_args { ++ struct file *file; ++ unsigned int open_flag; ++ umode_t create_mode; ++ int *opened; ++}; ++struct au_branch; ++int vfsub_atomic_open(struct inode *dir, struct dentry *dentry, ++ struct vfsub_aopen_args *args, struct au_branch *br); ++int vfsub_kern_path(const char *name, unsigned int flags, struct path *path); ++ ++struct dentry *vfsub_lookup_one_len(const char *name, struct dentry *parent, ++ int len); ++ ++struct vfsub_lkup_one_args { ++ struct dentry **errp; ++ struct qstr *name; ++ struct dentry *parent; ++}; ++ ++static inline struct dentry *vfsub_lkup_one(struct qstr *name, ++ struct dentry *parent) ++{ ++ return vfsub_lookup_one_len(name->name, parent, name->len); ++} ++ ++void vfsub_call_lkup_one(void *args); ++ ++/* ---------------------------------------------------------------------- */ ++ ++static inline int vfsub_mnt_want_write(struct vfsmount *mnt) ++{ ++ int err; ++ ++ lockdep_off(); ++ err = mnt_want_write(mnt); ++ lockdep_on(); ++ return err; ++} ++ ++static inline void vfsub_mnt_drop_write(struct vfsmount *mnt) ++{ ++ lockdep_off(); ++ mnt_drop_write(mnt); ++ lockdep_on(); ++} ++ ++#if 0 /* reserved */ ++static inline void vfsub_mnt_drop_write_file(struct file *file) ++{ ++ lockdep_off(); ++ mnt_drop_write_file(file); ++ lockdep_on(); ++} ++#endif ++ ++/* ---------------------------------------------------------------------- */ ++ ++struct au_hinode; ++struct dentry *vfsub_lock_rename(struct dentry *d1, struct au_hinode *hdir1, ++ struct dentry *d2, struct au_hinode *hdir2); ++void vfsub_unlock_rename(struct dentry *d1, struct au_hinode *hdir1, ++ struct dentry *d2, struct au_hinode *hdir2); ++ ++int vfsub_create(struct inode *dir, struct path *path, int mode, ++ bool want_excl); ++int vfsub_symlink(struct inode *dir, struct path *path, ++ const char *symname); ++int vfsub_mknod(struct inode *dir, struct path *path, int mode, dev_t dev); ++int vfsub_link(struct dentry *src_dentry, struct inode *dir, ++ struct path *path, struct inode **delegated_inode); ++int vfsub_rename(struct inode *src_hdir, struct dentry *src_dentry, ++ struct inode *hdir, struct path *path, ++ struct inode **delegated_inode); ++int vfsub_mkdir(struct inode *dir, struct path *path, int mode); ++int vfsub_rmdir(struct inode *dir, struct path *path); ++ ++/* ---------------------------------------------------------------------- */ ++ ++ssize_t vfsub_read_u(struct file *file, char __user *ubuf, size_t count, ++ loff_t *ppos); ++ssize_t vfsub_read_k(struct file *file, void *kbuf, size_t count, ++ loff_t *ppos); ++ssize_t vfsub_write_u(struct file *file, const char __user *ubuf, size_t count, ++ loff_t *ppos); ++ssize_t vfsub_write_k(struct file *file, void *kbuf, size_t count, ++ loff_t *ppos); ++int vfsub_flush(struct file *file, fl_owner_t id); ++int vfsub_iterate_dir(struct file *file, struct dir_context *ctx); ++ ++/* just for type-check */ ++static inline filldir_t au_diractor(int (*func)(struct dir_context *, ++ const char *, int, loff_t, u64, ++ unsigned)) ++{ ++ return (filldir_t)func; ++} ++ ++static inline loff_t vfsub_f_size_read(struct file *file) ++{ ++ return i_size_read(file_inode(file)); ++} ++ ++static inline unsigned int vfsub_file_flags(struct file *file) ++{ ++ unsigned int flags; ++ ++ spin_lock(&file->f_lock); ++ flags = file->f_flags; ++ spin_unlock(&file->f_lock); ++ ++ return flags; ++} ++ ++#if 0 /* reserved */ ++static inline void vfsub_file_accessed(struct file *h_file) ++{ ++ file_accessed(h_file); ++ vfsub_update_h_iattr(&h_file->f_path, /*did*/NULL); /*ignore*/ ++} ++#endif ++ ++static inline void vfsub_touch_atime(struct vfsmount *h_mnt, ++ struct dentry *h_dentry) ++{ ++ struct path h_path = { ++ .dentry = h_dentry, ++ .mnt = h_mnt ++ }; ++ touch_atime(&h_path); ++ vfsub_update_h_iattr(&h_path, /*did*/NULL); /*ignore*/ ++} ++ ++static inline int vfsub_update_time(struct inode *h_inode, struct timespec *ts, ++ int flags) ++{ ++ return update_time(h_inode, ts, flags); ++ /* no vfsub_update_h_iattr() since we don't have struct path */ ++} ++ ++#ifdef CONFIG_FS_POSIX_ACL ++static inline int vfsub_acl_chmod(struct inode *h_inode, umode_t h_mode) ++{ ++ int err; ++ ++ err = posix_acl_chmod(h_inode, h_mode); ++ if (err == -EOPNOTSUPP) ++ err = 0; ++ return err; ++} ++#else ++AuStubInt0(vfsub_acl_chmod, struct inode *h_inode, umode_t h_mode); ++#endif ++ ++long vfsub_splice_to(struct file *in, loff_t *ppos, ++ struct pipe_inode_info *pipe, size_t len, ++ unsigned int flags); ++long vfsub_splice_from(struct pipe_inode_info *pipe, struct file *out, ++ loff_t *ppos, size_t len, unsigned int flags); ++ ++static inline long vfsub_truncate(struct path *path, loff_t length) ++{ ++ long err; ++ ++ lockdep_off(); ++ err = vfs_truncate(path, length); ++ lockdep_on(); ++ return err; ++} ++ ++int vfsub_trunc(struct path *h_path, loff_t length, unsigned int attr, ++ struct file *h_file); ++int vfsub_fsync(struct file *file, struct path *path, int datasync); ++ ++/* ---------------------------------------------------------------------- */ ++ ++static inline loff_t vfsub_llseek(struct file *file, loff_t offset, int origin) ++{ ++ loff_t err; ++ ++ lockdep_off(); ++ err = vfs_llseek(file, offset, origin); ++ lockdep_on(); ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++int vfsub_sio_mkdir(struct inode *dir, struct path *path, int mode); ++int vfsub_sio_rmdir(struct inode *dir, struct path *path); ++int vfsub_sio_notify_change(struct path *path, struct iattr *ia, ++ struct inode **delegated_inode); ++int vfsub_notify_change(struct path *path, struct iattr *ia, ++ struct inode **delegated_inode); ++int vfsub_unlink(struct inode *dir, struct path *path, ++ struct inode **delegated_inode, int force); ++ ++/* ---------------------------------------------------------------------- */ ++ ++static inline int vfsub_setxattr(struct dentry *dentry, const char *name, ++ const void *value, size_t size, int flags) ++{ ++ int err; ++ ++ lockdep_off(); ++ err = vfs_setxattr(dentry, name, value, size, flags); ++ lockdep_on(); ++ ++ return err; ++} ++ ++static inline int vfsub_removexattr(struct dentry *dentry, const char *name) ++{ ++ int err; ++ ++ lockdep_off(); ++ err = vfs_removexattr(dentry, name); ++ lockdep_on(); ++ ++ return err; ++} ++ ++#endif /* __KERNEL__ */ ++#endif /* __AUFS_VFSUB_H__ */ +diff --git a/fs/aufs/wbr_policy.c b/fs/aufs/wbr_policy.c +new file mode 100644 +index 0000000..64cd9fe +--- /dev/null ++++ b/fs/aufs/wbr_policy.c +@@ -0,0 +1,765 @@ ++/* ++ * Copyright (C) 2005-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * policies for selecting one among multiple writable branches ++ */ ++ ++#include ++#include "aufs.h" ++ ++/* subset of cpup_attr() */ ++static noinline_for_stack ++int au_cpdown_attr(struct path *h_path, struct dentry *h_src) ++{ ++ int err, sbits; ++ struct iattr ia; ++ struct inode *h_isrc; ++ ++ h_isrc = h_src->d_inode; ++ ia.ia_valid = ATTR_FORCE | ATTR_MODE | ATTR_UID | ATTR_GID; ++ ia.ia_mode = h_isrc->i_mode; ++ ia.ia_uid = h_isrc->i_uid; ++ ia.ia_gid = h_isrc->i_gid; ++ sbits = !!(ia.ia_mode & (S_ISUID | S_ISGID)); ++ au_cpup_attr_flags(h_path->dentry->d_inode, h_isrc->i_flags); ++ /* no delegation since it is just created */ ++ err = vfsub_sio_notify_change(h_path, &ia, /*delegated*/NULL); ++ ++ /* is this nfs only? */ ++ if (!err && sbits && au_test_nfs(h_path->dentry->d_sb)) { ++ ia.ia_valid = ATTR_FORCE | ATTR_MODE; ++ ia.ia_mode = h_isrc->i_mode; ++ err = vfsub_sio_notify_change(h_path, &ia, /*delegated*/NULL); ++ } ++ ++ return err; ++} ++ ++#define AuCpdown_PARENT_OPQ 1 ++#define AuCpdown_WHED (1 << 1) ++#define AuCpdown_MADE_DIR (1 << 2) ++#define AuCpdown_DIROPQ (1 << 3) ++#define au_ftest_cpdown(flags, name) ((flags) & AuCpdown_##name) ++#define au_fset_cpdown(flags, name) \ ++ do { (flags) |= AuCpdown_##name; } while (0) ++#define au_fclr_cpdown(flags, name) \ ++ do { (flags) &= ~AuCpdown_##name; } while (0) ++ ++static int au_cpdown_dir_opq(struct dentry *dentry, aufs_bindex_t bdst, ++ unsigned int *flags) ++{ ++ int err; ++ struct dentry *opq_dentry; ++ ++ opq_dentry = au_diropq_create(dentry, bdst); ++ err = PTR_ERR(opq_dentry); ++ if (IS_ERR(opq_dentry)) ++ goto out; ++ dput(opq_dentry); ++ au_fset_cpdown(*flags, DIROPQ); ++ ++out: ++ return err; ++} ++ ++static int au_cpdown_dir_wh(struct dentry *dentry, struct dentry *h_parent, ++ struct inode *dir, aufs_bindex_t bdst) ++{ ++ int err; ++ struct path h_path; ++ struct au_branch *br; ++ ++ br = au_sbr(dentry->d_sb, bdst); ++ h_path.dentry = au_wh_lkup(h_parent, &dentry->d_name, br); ++ err = PTR_ERR(h_path.dentry); ++ if (IS_ERR(h_path.dentry)) ++ goto out; ++ ++ err = 0; ++ if (h_path.dentry->d_inode) { ++ h_path.mnt = au_br_mnt(br); ++ err = au_wh_unlink_dentry(au_h_iptr(dir, bdst), &h_path, ++ dentry); ++ } ++ dput(h_path.dentry); ++ ++out: ++ return err; ++} ++ ++static int au_cpdown_dir(struct dentry *dentry, aufs_bindex_t bdst, ++ struct au_pin *pin, ++ struct dentry *h_parent, void *arg) ++{ ++ int err, rerr; ++ aufs_bindex_t bopq, bstart; ++ struct path h_path; ++ struct dentry *parent; ++ struct inode *h_dir, *h_inode, *inode, *dir; ++ unsigned int *flags = arg; ++ ++ bstart = au_dbstart(dentry); ++ /* dentry is di-locked */ ++ parent = dget_parent(dentry); ++ dir = parent->d_inode; ++ h_dir = h_parent->d_inode; ++ AuDebugOn(h_dir != au_h_iptr(dir, bdst)); ++ IMustLock(h_dir); ++ ++ err = au_lkup_neg(dentry, bdst, /*wh*/0); ++ if (unlikely(err < 0)) ++ goto out; ++ h_path.dentry = au_h_dptr(dentry, bdst); ++ h_path.mnt = au_sbr_mnt(dentry->d_sb, bdst); ++ err = vfsub_sio_mkdir(au_h_iptr(dir, bdst), &h_path, ++ S_IRWXU | S_IRUGO | S_IXUGO); ++ if (unlikely(err)) ++ goto out_put; ++ au_fset_cpdown(*flags, MADE_DIR); ++ ++ bopq = au_dbdiropq(dentry); ++ au_fclr_cpdown(*flags, WHED); ++ au_fclr_cpdown(*flags, DIROPQ); ++ if (au_dbwh(dentry) == bdst) ++ au_fset_cpdown(*flags, WHED); ++ if (!au_ftest_cpdown(*flags, PARENT_OPQ) && bopq <= bdst) ++ au_fset_cpdown(*flags, PARENT_OPQ); ++ h_inode = h_path.dentry->d_inode; ++ mutex_lock_nested(&h_inode->i_mutex, AuLsc_I_CHILD); ++ if (au_ftest_cpdown(*flags, WHED)) { ++ err = au_cpdown_dir_opq(dentry, bdst, flags); ++ if (unlikely(err)) { ++ mutex_unlock(&h_inode->i_mutex); ++ goto out_dir; ++ } ++ } ++ ++ err = au_cpdown_attr(&h_path, au_h_dptr(dentry, bstart)); ++ mutex_unlock(&h_inode->i_mutex); ++ if (unlikely(err)) ++ goto out_opq; ++ ++ if (au_ftest_cpdown(*flags, WHED)) { ++ err = au_cpdown_dir_wh(dentry, h_parent, dir, bdst); ++ if (unlikely(err)) ++ goto out_opq; ++ } ++ ++ inode = dentry->d_inode; ++ if (au_ibend(inode) < bdst) ++ au_set_ibend(inode, bdst); ++ au_set_h_iptr(inode, bdst, au_igrab(h_inode), ++ au_hi_flags(inode, /*isdir*/1)); ++ au_fhsm_wrote(dentry->d_sb, bdst, /*force*/0); ++ goto out; /* success */ ++ ++ /* revert */ ++out_opq: ++ if (au_ftest_cpdown(*flags, DIROPQ)) { ++ mutex_lock_nested(&h_inode->i_mutex, AuLsc_I_CHILD); ++ rerr = au_diropq_remove(dentry, bdst); ++ mutex_unlock(&h_inode->i_mutex); ++ if (unlikely(rerr)) { ++ AuIOErr("failed removing diropq for %pd b%d (%d)\n", ++ dentry, bdst, rerr); ++ err = -EIO; ++ goto out; ++ } ++ } ++out_dir: ++ if (au_ftest_cpdown(*flags, MADE_DIR)) { ++ rerr = vfsub_sio_rmdir(au_h_iptr(dir, bdst), &h_path); ++ if (unlikely(rerr)) { ++ AuIOErr("failed removing %pd b%d (%d)\n", ++ dentry, bdst, rerr); ++ err = -EIO; ++ } ++ } ++out_put: ++ au_set_h_dptr(dentry, bdst, NULL); ++ if (au_dbend(dentry) == bdst) ++ au_update_dbend(dentry); ++out: ++ dput(parent); ++ return err; ++} ++ ++int au_cpdown_dirs(struct dentry *dentry, aufs_bindex_t bdst) ++{ ++ int err; ++ unsigned int flags; ++ ++ flags = 0; ++ err = au_cp_dirs(dentry, bdst, au_cpdown_dir, &flags); ++ ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* policies for create */ ++ ++int au_wbr_nonopq(struct dentry *dentry, aufs_bindex_t bindex) ++{ ++ int err, i, j, ndentry; ++ aufs_bindex_t bopq; ++ struct au_dcsub_pages dpages; ++ struct au_dpage *dpage; ++ struct dentry **dentries, *parent, *d; ++ ++ err = au_dpages_init(&dpages, GFP_NOFS); ++ if (unlikely(err)) ++ goto out; ++ parent = dget_parent(dentry); ++ err = au_dcsub_pages_rev_aufs(&dpages, parent, /*do_include*/0); ++ if (unlikely(err)) ++ goto out_free; ++ ++ err = bindex; ++ for (i = 0; i < dpages.ndpage; i++) { ++ dpage = dpages.dpages + i; ++ dentries = dpage->dentries; ++ ndentry = dpage->ndentry; ++ for (j = 0; j < ndentry; j++) { ++ d = dentries[j]; ++ di_read_lock_parent2(d, !AuLock_IR); ++ bopq = au_dbdiropq(d); ++ di_read_unlock(d, !AuLock_IR); ++ if (bopq >= 0 && bopq < err) ++ err = bopq; ++ } ++ } ++ ++out_free: ++ dput(parent); ++ au_dpages_free(&dpages); ++out: ++ return err; ++} ++ ++static int au_wbr_bu(struct super_block *sb, aufs_bindex_t bindex) ++{ ++ for (; bindex >= 0; bindex--) ++ if (!au_br_rdonly(au_sbr(sb, bindex))) ++ return bindex; ++ return -EROFS; ++} ++ ++/* top down parent */ ++static int au_wbr_create_tdp(struct dentry *dentry, ++ unsigned int flags __maybe_unused) ++{ ++ int err; ++ aufs_bindex_t bstart, bindex; ++ struct super_block *sb; ++ struct dentry *parent, *h_parent; ++ ++ sb = dentry->d_sb; ++ bstart = au_dbstart(dentry); ++ err = bstart; ++ if (!au_br_rdonly(au_sbr(sb, bstart))) ++ goto out; ++ ++ err = -EROFS; ++ parent = dget_parent(dentry); ++ for (bindex = au_dbstart(parent); bindex < bstart; bindex++) { ++ h_parent = au_h_dptr(parent, bindex); ++ if (!h_parent || !h_parent->d_inode) ++ continue; ++ ++ if (!au_br_rdonly(au_sbr(sb, bindex))) { ++ err = bindex; ++ break; ++ } ++ } ++ dput(parent); ++ ++ /* bottom up here */ ++ if (unlikely(err < 0)) { ++ err = au_wbr_bu(sb, bstart - 1); ++ if (err >= 0) ++ err = au_wbr_nonopq(dentry, err); ++ } ++ ++out: ++ AuDbg("b%d\n", err); ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* an exception for the policy other than tdp */ ++static int au_wbr_create_exp(struct dentry *dentry) ++{ ++ int err; ++ aufs_bindex_t bwh, bdiropq; ++ struct dentry *parent; ++ ++ err = -1; ++ bwh = au_dbwh(dentry); ++ parent = dget_parent(dentry); ++ bdiropq = au_dbdiropq(parent); ++ if (bwh >= 0) { ++ if (bdiropq >= 0) ++ err = min(bdiropq, bwh); ++ else ++ err = bwh; ++ AuDbg("%d\n", err); ++ } else if (bdiropq >= 0) { ++ err = bdiropq; ++ AuDbg("%d\n", err); ++ } ++ dput(parent); ++ ++ if (err >= 0) ++ err = au_wbr_nonopq(dentry, err); ++ ++ if (err >= 0 && au_br_rdonly(au_sbr(dentry->d_sb, err))) ++ err = -1; ++ ++ AuDbg("%d\n", err); ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* round robin */ ++static int au_wbr_create_init_rr(struct super_block *sb) ++{ ++ int err; ++ ++ err = au_wbr_bu(sb, au_sbend(sb)); ++ atomic_set(&au_sbi(sb)->si_wbr_rr_next, -err); /* less important */ ++ /* smp_mb(); */ ++ ++ AuDbg("b%d\n", err); ++ return err; ++} ++ ++static int au_wbr_create_rr(struct dentry *dentry, unsigned int flags) ++{ ++ int err, nbr; ++ unsigned int u; ++ aufs_bindex_t bindex, bend; ++ struct super_block *sb; ++ atomic_t *next; ++ ++ err = au_wbr_create_exp(dentry); ++ if (err >= 0) ++ goto out; ++ ++ sb = dentry->d_sb; ++ next = &au_sbi(sb)->si_wbr_rr_next; ++ bend = au_sbend(sb); ++ nbr = bend + 1; ++ for (bindex = 0; bindex <= bend; bindex++) { ++ if (!au_ftest_wbr(flags, DIR)) { ++ err = atomic_dec_return(next) + 1; ++ /* modulo for 0 is meaningless */ ++ if (unlikely(!err)) ++ err = atomic_dec_return(next) + 1; ++ } else ++ err = atomic_read(next); ++ AuDbg("%d\n", err); ++ u = err; ++ err = u % nbr; ++ AuDbg("%d\n", err); ++ if (!au_br_rdonly(au_sbr(sb, err))) ++ break; ++ err = -EROFS; ++ } ++ ++ if (err >= 0) ++ err = au_wbr_nonopq(dentry, err); ++ ++out: ++ AuDbg("%d\n", err); ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* most free space */ ++static void au_mfs(struct dentry *dentry, struct dentry *parent) ++{ ++ struct super_block *sb; ++ struct au_branch *br; ++ struct au_wbr_mfs *mfs; ++ struct dentry *h_parent; ++ aufs_bindex_t bindex, bend; ++ int err; ++ unsigned long long b, bavail; ++ struct path h_path; ++ /* reduce the stack usage */ ++ struct kstatfs *st; ++ ++ st = kmalloc(sizeof(*st), GFP_NOFS); ++ if (unlikely(!st)) { ++ AuWarn1("failed updating mfs(%d), ignored\n", -ENOMEM); ++ return; ++ } ++ ++ bavail = 0; ++ sb = dentry->d_sb; ++ mfs = &au_sbi(sb)->si_wbr_mfs; ++ MtxMustLock(&mfs->mfs_lock); ++ mfs->mfs_bindex = -EROFS; ++ mfs->mfsrr_bytes = 0; ++ if (!parent) { ++ bindex = 0; ++ bend = au_sbend(sb); ++ } else { ++ bindex = au_dbstart(parent); ++ bend = au_dbtaildir(parent); ++ } ++ ++ for (; bindex <= bend; bindex++) { ++ if (parent) { ++ h_parent = au_h_dptr(parent, bindex); ++ if (!h_parent || !h_parent->d_inode) ++ continue; ++ } ++ br = au_sbr(sb, bindex); ++ if (au_br_rdonly(br)) ++ continue; ++ ++ /* sb->s_root for NFS is unreliable */ ++ h_path.mnt = au_br_mnt(br); ++ h_path.dentry = h_path.mnt->mnt_root; ++ err = vfs_statfs(&h_path, st); ++ if (unlikely(err)) { ++ AuWarn1("failed statfs, b%d, %d\n", bindex, err); ++ continue; ++ } ++ ++ /* when the available size is equal, select the lower one */ ++ BUILD_BUG_ON(sizeof(b) < sizeof(st->f_bavail) ++ || sizeof(b) < sizeof(st->f_bsize)); ++ b = st->f_bavail * st->f_bsize; ++ br->br_wbr->wbr_bytes = b; ++ if (b >= bavail) { ++ bavail = b; ++ mfs->mfs_bindex = bindex; ++ mfs->mfs_jiffy = jiffies; ++ } ++ } ++ ++ mfs->mfsrr_bytes = bavail; ++ AuDbg("b%d\n", mfs->mfs_bindex); ++ kfree(st); ++} ++ ++static int au_wbr_create_mfs(struct dentry *dentry, unsigned int flags) ++{ ++ int err; ++ struct dentry *parent; ++ struct super_block *sb; ++ struct au_wbr_mfs *mfs; ++ ++ err = au_wbr_create_exp(dentry); ++ if (err >= 0) ++ goto out; ++ ++ sb = dentry->d_sb; ++ parent = NULL; ++ if (au_ftest_wbr(flags, PARENT)) ++ parent = dget_parent(dentry); ++ mfs = &au_sbi(sb)->si_wbr_mfs; ++ mutex_lock(&mfs->mfs_lock); ++ if (time_after(jiffies, mfs->mfs_jiffy + mfs->mfs_expire) ++ || mfs->mfs_bindex < 0 ++ || au_br_rdonly(au_sbr(sb, mfs->mfs_bindex))) ++ au_mfs(dentry, parent); ++ mutex_unlock(&mfs->mfs_lock); ++ err = mfs->mfs_bindex; ++ dput(parent); ++ ++ if (err >= 0) ++ err = au_wbr_nonopq(dentry, err); ++ ++out: ++ AuDbg("b%d\n", err); ++ return err; ++} ++ ++static int au_wbr_create_init_mfs(struct super_block *sb) ++{ ++ struct au_wbr_mfs *mfs; ++ ++ mfs = &au_sbi(sb)->si_wbr_mfs; ++ mutex_init(&mfs->mfs_lock); ++ mfs->mfs_jiffy = 0; ++ mfs->mfs_bindex = -EROFS; ++ ++ return 0; ++} ++ ++static int au_wbr_create_fin_mfs(struct super_block *sb __maybe_unused) ++{ ++ mutex_destroy(&au_sbi(sb)->si_wbr_mfs.mfs_lock); ++ return 0; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* most free space and then round robin */ ++static int au_wbr_create_mfsrr(struct dentry *dentry, unsigned int flags) ++{ ++ int err; ++ struct au_wbr_mfs *mfs; ++ ++ err = au_wbr_create_mfs(dentry, flags); ++ if (err >= 0) { ++ mfs = &au_sbi(dentry->d_sb)->si_wbr_mfs; ++ mutex_lock(&mfs->mfs_lock); ++ if (mfs->mfsrr_bytes < mfs->mfsrr_watermark) ++ err = au_wbr_create_rr(dentry, flags); ++ mutex_unlock(&mfs->mfs_lock); ++ } ++ ++ AuDbg("b%d\n", err); ++ return err; ++} ++ ++static int au_wbr_create_init_mfsrr(struct super_block *sb) ++{ ++ int err; ++ ++ au_wbr_create_init_mfs(sb); /* ignore */ ++ err = au_wbr_create_init_rr(sb); ++ ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* top down parent and most free space */ ++static int au_wbr_create_pmfs(struct dentry *dentry, unsigned int flags) ++{ ++ int err, e2; ++ unsigned long long b; ++ aufs_bindex_t bindex, bstart, bend; ++ struct super_block *sb; ++ struct dentry *parent, *h_parent; ++ struct au_branch *br; ++ ++ err = au_wbr_create_tdp(dentry, flags); ++ if (unlikely(err < 0)) ++ goto out; ++ parent = dget_parent(dentry); ++ bstart = au_dbstart(parent); ++ bend = au_dbtaildir(parent); ++ if (bstart == bend) ++ goto out_parent; /* success */ ++ ++ e2 = au_wbr_create_mfs(dentry, flags); ++ if (e2 < 0) ++ goto out_parent; /* success */ ++ ++ /* when the available size is equal, select upper one */ ++ sb = dentry->d_sb; ++ br = au_sbr(sb, err); ++ b = br->br_wbr->wbr_bytes; ++ AuDbg("b%d, %llu\n", err, b); ++ ++ for (bindex = bstart; bindex <= bend; bindex++) { ++ h_parent = au_h_dptr(parent, bindex); ++ if (!h_parent || !h_parent->d_inode) ++ continue; ++ ++ br = au_sbr(sb, bindex); ++ if (!au_br_rdonly(br) && br->br_wbr->wbr_bytes > b) { ++ b = br->br_wbr->wbr_bytes; ++ err = bindex; ++ AuDbg("b%d, %llu\n", err, b); ++ } ++ } ++ ++ if (err >= 0) ++ err = au_wbr_nonopq(dentry, err); ++ ++out_parent: ++ dput(parent); ++out: ++ AuDbg("b%d\n", err); ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* ++ * - top down parent ++ * - most free space with parent ++ * - most free space round-robin regardless parent ++ */ ++static int au_wbr_create_pmfsrr(struct dentry *dentry, unsigned int flags) ++{ ++ int err; ++ unsigned long long watermark; ++ struct super_block *sb; ++ struct au_branch *br; ++ struct au_wbr_mfs *mfs; ++ ++ err = au_wbr_create_pmfs(dentry, flags | AuWbr_PARENT); ++ if (unlikely(err < 0)) ++ goto out; ++ ++ sb = dentry->d_sb; ++ br = au_sbr(sb, err); ++ mfs = &au_sbi(sb)->si_wbr_mfs; ++ mutex_lock(&mfs->mfs_lock); ++ watermark = mfs->mfsrr_watermark; ++ mutex_unlock(&mfs->mfs_lock); ++ if (br->br_wbr->wbr_bytes < watermark) ++ /* regardless the parent dir */ ++ err = au_wbr_create_mfsrr(dentry, flags); ++ ++out: ++ AuDbg("b%d\n", err); ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* policies for copyup */ ++ ++/* top down parent */ ++static int au_wbr_copyup_tdp(struct dentry *dentry) ++{ ++ return au_wbr_create_tdp(dentry, /*flags, anything is ok*/0); ++} ++ ++/* bottom up parent */ ++static int au_wbr_copyup_bup(struct dentry *dentry) ++{ ++ int err; ++ aufs_bindex_t bindex, bstart; ++ struct dentry *parent, *h_parent; ++ struct super_block *sb; ++ ++ err = -EROFS; ++ sb = dentry->d_sb; ++ parent = dget_parent(dentry); ++ bstart = au_dbstart(parent); ++ for (bindex = au_dbstart(dentry); bindex >= bstart; bindex--) { ++ h_parent = au_h_dptr(parent, bindex); ++ if (!h_parent || !h_parent->d_inode) ++ continue; ++ ++ if (!au_br_rdonly(au_sbr(sb, bindex))) { ++ err = bindex; ++ break; ++ } ++ } ++ dput(parent); ++ ++ /* bottom up here */ ++ if (unlikely(err < 0)) ++ err = au_wbr_bu(sb, bstart - 1); ++ ++ AuDbg("b%d\n", err); ++ return err; ++} ++ ++/* bottom up */ ++int au_wbr_do_copyup_bu(struct dentry *dentry, aufs_bindex_t bstart) ++{ ++ int err; ++ ++ err = au_wbr_bu(dentry->d_sb, bstart); ++ AuDbg("b%d\n", err); ++ if (err > bstart) ++ err = au_wbr_nonopq(dentry, err); ++ ++ AuDbg("b%d\n", err); ++ return err; ++} ++ ++static int au_wbr_copyup_bu(struct dentry *dentry) ++{ ++ int err; ++ aufs_bindex_t bstart; ++ ++ bstart = au_dbstart(dentry); ++ err = au_wbr_do_copyup_bu(dentry, bstart); ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++struct au_wbr_copyup_operations au_wbr_copyup_ops[] = { ++ [AuWbrCopyup_TDP] = { ++ .copyup = au_wbr_copyup_tdp ++ }, ++ [AuWbrCopyup_BUP] = { ++ .copyup = au_wbr_copyup_bup ++ }, ++ [AuWbrCopyup_BU] = { ++ .copyup = au_wbr_copyup_bu ++ } ++}; ++ ++struct au_wbr_create_operations au_wbr_create_ops[] = { ++ [AuWbrCreate_TDP] = { ++ .create = au_wbr_create_tdp ++ }, ++ [AuWbrCreate_RR] = { ++ .create = au_wbr_create_rr, ++ .init = au_wbr_create_init_rr ++ }, ++ [AuWbrCreate_MFS] = { ++ .create = au_wbr_create_mfs, ++ .init = au_wbr_create_init_mfs, ++ .fin = au_wbr_create_fin_mfs ++ }, ++ [AuWbrCreate_MFSV] = { ++ .create = au_wbr_create_mfs, ++ .init = au_wbr_create_init_mfs, ++ .fin = au_wbr_create_fin_mfs ++ }, ++ [AuWbrCreate_MFSRR] = { ++ .create = au_wbr_create_mfsrr, ++ .init = au_wbr_create_init_mfsrr, ++ .fin = au_wbr_create_fin_mfs ++ }, ++ [AuWbrCreate_MFSRRV] = { ++ .create = au_wbr_create_mfsrr, ++ .init = au_wbr_create_init_mfsrr, ++ .fin = au_wbr_create_fin_mfs ++ }, ++ [AuWbrCreate_PMFS] = { ++ .create = au_wbr_create_pmfs, ++ .init = au_wbr_create_init_mfs, ++ .fin = au_wbr_create_fin_mfs ++ }, ++ [AuWbrCreate_PMFSV] = { ++ .create = au_wbr_create_pmfs, ++ .init = au_wbr_create_init_mfs, ++ .fin = au_wbr_create_fin_mfs ++ }, ++ [AuWbrCreate_PMFSRR] = { ++ .create = au_wbr_create_pmfsrr, ++ .init = au_wbr_create_init_mfsrr, ++ .fin = au_wbr_create_fin_mfs ++ }, ++ [AuWbrCreate_PMFSRRV] = { ++ .create = au_wbr_create_pmfsrr, ++ .init = au_wbr_create_init_mfsrr, ++ .fin = au_wbr_create_fin_mfs ++ } ++}; +diff --git a/fs/aufs/whout.c b/fs/aufs/whout.c +new file mode 100644 +index 0000000..fb667ee +--- /dev/null ++++ b/fs/aufs/whout.c +@@ -0,0 +1,1061 @@ ++/* ++ * Copyright (C) 2005-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * whiteout for logical deletion and opaque directory ++ */ ++ ++#include "aufs.h" ++ ++#define WH_MASK S_IRUGO ++ ++/* ++ * If a directory contains this file, then it is opaque. We start with the ++ * .wh. flag so that it is blocked by lookup. ++ */ ++static struct qstr diropq_name = QSTR_INIT(AUFS_WH_DIROPQ, ++ sizeof(AUFS_WH_DIROPQ) - 1); ++ ++/* ++ * generate whiteout name, which is NOT terminated by NULL. ++ * @name: original d_name.name ++ * @len: original d_name.len ++ * @wh: whiteout qstr ++ * returns zero when succeeds, otherwise error. ++ * succeeded value as wh->name should be freed by kfree(). ++ */ ++int au_wh_name_alloc(struct qstr *wh, const struct qstr *name) ++{ ++ char *p; ++ ++ if (unlikely(name->len > PATH_MAX - AUFS_WH_PFX_LEN)) ++ return -ENAMETOOLONG; ++ ++ wh->len = name->len + AUFS_WH_PFX_LEN; ++ p = kmalloc(wh->len, GFP_NOFS); ++ wh->name = p; ++ if (p) { ++ memcpy(p, AUFS_WH_PFX, AUFS_WH_PFX_LEN); ++ memcpy(p + AUFS_WH_PFX_LEN, name->name, name->len); ++ /* smp_mb(); */ ++ return 0; ++ } ++ return -ENOMEM; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* ++ * test if the @wh_name exists under @h_parent. ++ * @try_sio specifies the necessary of super-io. ++ */ ++int au_wh_test(struct dentry *h_parent, struct qstr *wh_name, int try_sio) ++{ ++ int err; ++ struct dentry *wh_dentry; ++ ++ if (!try_sio) ++ wh_dentry = vfsub_lkup_one(wh_name, h_parent); ++ else ++ wh_dentry = au_sio_lkup_one(wh_name, h_parent); ++ err = PTR_ERR(wh_dentry); ++ if (IS_ERR(wh_dentry)) { ++ if (err == -ENAMETOOLONG) ++ err = 0; ++ goto out; ++ } ++ ++ err = 0; ++ if (!wh_dentry->d_inode) ++ goto out_wh; /* success */ ++ ++ err = 1; ++ if (S_ISREG(wh_dentry->d_inode->i_mode)) ++ goto out_wh; /* success */ ++ ++ err = -EIO; ++ AuIOErr("%pd Invalid whiteout entry type 0%o.\n", ++ wh_dentry, wh_dentry->d_inode->i_mode); ++ ++out_wh: ++ dput(wh_dentry); ++out: ++ return err; ++} ++ ++/* ++ * test if the @h_dentry sets opaque or not. ++ */ ++int au_diropq_test(struct dentry *h_dentry) ++{ ++ int err; ++ struct inode *h_dir; ++ ++ h_dir = h_dentry->d_inode; ++ err = au_wh_test(h_dentry, &diropq_name, ++ au_test_h_perm_sio(h_dir, MAY_EXEC)); ++ return err; ++} ++ ++/* ++ * returns a negative dentry whose name is unique and temporary. ++ */ ++struct dentry *au_whtmp_lkup(struct dentry *h_parent, struct au_branch *br, ++ struct qstr *prefix) ++{ ++ struct dentry *dentry; ++ int i; ++ char defname[NAME_MAX - AUFS_MAX_NAMELEN + DNAME_INLINE_LEN + 1], ++ *name, *p; ++ /* strict atomic_t is unnecessary here */ ++ static unsigned short cnt; ++ struct qstr qs; ++ ++ BUILD_BUG_ON(sizeof(cnt) * 2 > AUFS_WH_TMP_LEN); ++ ++ name = defname; ++ qs.len = sizeof(defname) - DNAME_INLINE_LEN + prefix->len - 1; ++ if (unlikely(prefix->len > DNAME_INLINE_LEN)) { ++ dentry = ERR_PTR(-ENAMETOOLONG); ++ if (unlikely(qs.len > NAME_MAX)) ++ goto out; ++ dentry = ERR_PTR(-ENOMEM); ++ name = kmalloc(qs.len + 1, GFP_NOFS); ++ if (unlikely(!name)) ++ goto out; ++ } ++ ++ /* doubly whiteout-ed */ ++ memcpy(name, AUFS_WH_PFX AUFS_WH_PFX, AUFS_WH_PFX_LEN * 2); ++ p = name + AUFS_WH_PFX_LEN * 2; ++ memcpy(p, prefix->name, prefix->len); ++ p += prefix->len; ++ *p++ = '.'; ++ AuDebugOn(name + qs.len + 1 - p <= AUFS_WH_TMP_LEN); ++ ++ qs.name = name; ++ for (i = 0; i < 3; i++) { ++ sprintf(p, "%.*x", AUFS_WH_TMP_LEN, cnt++); ++ dentry = au_sio_lkup_one(&qs, h_parent); ++ if (IS_ERR(dentry) || !dentry->d_inode) ++ goto out_name; ++ dput(dentry); ++ } ++ /* pr_warn("could not get random name\n"); */ ++ dentry = ERR_PTR(-EEXIST); ++ AuDbg("%.*s\n", AuLNPair(&qs)); ++ BUG(); ++ ++out_name: ++ if (name != defname) ++ kfree(name); ++out: ++ AuTraceErrPtr(dentry); ++ return dentry; ++} ++ ++/* ++ * rename the @h_dentry on @br to the whiteouted temporary name. ++ */ ++int au_whtmp_ren(struct dentry *h_dentry, struct au_branch *br) ++{ ++ int err; ++ struct path h_path = { ++ .mnt = au_br_mnt(br) ++ }; ++ struct inode *h_dir, *delegated; ++ struct dentry *h_parent; ++ ++ h_parent = h_dentry->d_parent; /* dir inode is locked */ ++ h_dir = h_parent->d_inode; ++ IMustLock(h_dir); ++ ++ h_path.dentry = au_whtmp_lkup(h_parent, br, &h_dentry->d_name); ++ err = PTR_ERR(h_path.dentry); ++ if (IS_ERR(h_path.dentry)) ++ goto out; ++ ++ /* under the same dir, no need to lock_rename() */ ++ delegated = NULL; ++ err = vfsub_rename(h_dir, h_dentry, h_dir, &h_path, &delegated); ++ AuTraceErr(err); ++ if (unlikely(err == -EWOULDBLOCK)) { ++ pr_warn("cannot retry for NFSv4 delegation" ++ " for an internal rename\n"); ++ iput(delegated); ++ } ++ dput(h_path.dentry); ++ ++out: ++ AuTraceErr(err); ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++/* ++ * functions for removing a whiteout ++ */ ++ ++static int do_unlink_wh(struct inode *h_dir, struct path *h_path) ++{ ++ int err, force; ++ struct inode *delegated; ++ ++ /* ++ * forces superio when the dir has a sticky bit. ++ * this may be a violation of unix fs semantics. ++ */ ++ force = (h_dir->i_mode & S_ISVTX) ++ && !uid_eq(current_fsuid(), h_path->dentry->d_inode->i_uid); ++ delegated = NULL; ++ err = vfsub_unlink(h_dir, h_path, &delegated, force); ++ if (unlikely(err == -EWOULDBLOCK)) { ++ pr_warn("cannot retry for NFSv4 delegation" ++ " for an internal unlink\n"); ++ iput(delegated); ++ } ++ return err; ++} ++ ++int au_wh_unlink_dentry(struct inode *h_dir, struct path *h_path, ++ struct dentry *dentry) ++{ ++ int err; ++ ++ err = do_unlink_wh(h_dir, h_path); ++ if (!err && dentry) ++ au_set_dbwh(dentry, -1); ++ ++ return err; ++} ++ ++static int unlink_wh_name(struct dentry *h_parent, struct qstr *wh, ++ struct au_branch *br) ++{ ++ int err; ++ struct path h_path = { ++ .mnt = au_br_mnt(br) ++ }; ++ ++ err = 0; ++ h_path.dentry = vfsub_lkup_one(wh, h_parent); ++ if (IS_ERR(h_path.dentry)) ++ err = PTR_ERR(h_path.dentry); ++ else { ++ if (h_path.dentry->d_inode ++ && S_ISREG(h_path.dentry->d_inode->i_mode)) ++ err = do_unlink_wh(h_parent->d_inode, &h_path); ++ dput(h_path.dentry); ++ } ++ ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++/* ++ * initialize/clean whiteout for a branch ++ */ ++ ++static void au_wh_clean(struct inode *h_dir, struct path *whpath, ++ const int isdir) ++{ ++ int err; ++ struct inode *delegated; ++ ++ if (!whpath->dentry->d_inode) ++ return; ++ ++ if (isdir) ++ err = vfsub_rmdir(h_dir, whpath); ++ else { ++ delegated = NULL; ++ err = vfsub_unlink(h_dir, whpath, &delegated, /*force*/0); ++ if (unlikely(err == -EWOULDBLOCK)) { ++ pr_warn("cannot retry for NFSv4 delegation" ++ " for an internal unlink\n"); ++ iput(delegated); ++ } ++ } ++ if (unlikely(err)) ++ pr_warn("failed removing %pd (%d), ignored.\n", ++ whpath->dentry, err); ++} ++ ++static int test_linkable(struct dentry *h_root) ++{ ++ struct inode *h_dir = h_root->d_inode; ++ ++ if (h_dir->i_op->link) ++ return 0; ++ ++ pr_err("%pd (%s) doesn't support link(2), use noplink and rw+nolwh\n", ++ h_root, au_sbtype(h_root->d_sb)); ++ return -ENOSYS; ++} ++ ++/* todo: should this mkdir be done in /sbin/mount.aufs helper? */ ++static int au_whdir(struct inode *h_dir, struct path *path) ++{ ++ int err; ++ ++ err = -EEXIST; ++ if (!path->dentry->d_inode) { ++ int mode = S_IRWXU; ++ ++ if (au_test_nfs(path->dentry->d_sb)) ++ mode |= S_IXUGO; ++ err = vfsub_mkdir(h_dir, path, mode); ++ } else if (d_is_dir(path->dentry)) ++ err = 0; ++ else ++ pr_err("unknown %pd exists\n", path->dentry); ++ ++ return err; ++} ++ ++struct au_wh_base { ++ const struct qstr *name; ++ struct dentry *dentry; ++}; ++ ++static void au_wh_init_ro(struct inode *h_dir, struct au_wh_base base[], ++ struct path *h_path) ++{ ++ h_path->dentry = base[AuBrWh_BASE].dentry; ++ au_wh_clean(h_dir, h_path, /*isdir*/0); ++ h_path->dentry = base[AuBrWh_PLINK].dentry; ++ au_wh_clean(h_dir, h_path, /*isdir*/1); ++ h_path->dentry = base[AuBrWh_ORPH].dentry; ++ au_wh_clean(h_dir, h_path, /*isdir*/1); ++} ++ ++/* ++ * returns tri-state, ++ * minus: error, caller should print the message ++ * zero: succuess ++ * plus: error, caller should NOT print the message ++ */ ++static int au_wh_init_rw_nolink(struct dentry *h_root, struct au_wbr *wbr, ++ int do_plink, struct au_wh_base base[], ++ struct path *h_path) ++{ ++ int err; ++ struct inode *h_dir; ++ ++ h_dir = h_root->d_inode; ++ h_path->dentry = base[AuBrWh_BASE].dentry; ++ au_wh_clean(h_dir, h_path, /*isdir*/0); ++ h_path->dentry = base[AuBrWh_PLINK].dentry; ++ if (do_plink) { ++ err = test_linkable(h_root); ++ if (unlikely(err)) { ++ err = 1; ++ goto out; ++ } ++ ++ err = au_whdir(h_dir, h_path); ++ if (unlikely(err)) ++ goto out; ++ wbr->wbr_plink = dget(base[AuBrWh_PLINK].dentry); ++ } else ++ au_wh_clean(h_dir, h_path, /*isdir*/1); ++ h_path->dentry = base[AuBrWh_ORPH].dentry; ++ err = au_whdir(h_dir, h_path); ++ if (unlikely(err)) ++ goto out; ++ wbr->wbr_orph = dget(base[AuBrWh_ORPH].dentry); ++ ++out: ++ return err; ++} ++ ++/* ++ * for the moment, aufs supports the branch filesystem which does not support ++ * link(2). testing on FAT which does not support i_op->setattr() fully either, ++ * copyup failed. finally, such filesystem will not be used as the writable ++ * branch. ++ * ++ * returns tri-state, see above. ++ */ ++static int au_wh_init_rw(struct dentry *h_root, struct au_wbr *wbr, ++ int do_plink, struct au_wh_base base[], ++ struct path *h_path) ++{ ++ int err; ++ struct inode *h_dir; ++ ++ WbrWhMustWriteLock(wbr); ++ ++ err = test_linkable(h_root); ++ if (unlikely(err)) { ++ err = 1; ++ goto out; ++ } ++ ++ /* ++ * todo: should this create be done in /sbin/mount.aufs helper? ++ */ ++ err = -EEXIST; ++ h_dir = h_root->d_inode; ++ if (!base[AuBrWh_BASE].dentry->d_inode) { ++ h_path->dentry = base[AuBrWh_BASE].dentry; ++ err = vfsub_create(h_dir, h_path, WH_MASK, /*want_excl*/true); ++ } else if (S_ISREG(base[AuBrWh_BASE].dentry->d_inode->i_mode)) ++ err = 0; ++ else ++ pr_err("unknown %pd2 exists\n", base[AuBrWh_BASE].dentry); ++ if (unlikely(err)) ++ goto out; ++ ++ h_path->dentry = base[AuBrWh_PLINK].dentry; ++ if (do_plink) { ++ err = au_whdir(h_dir, h_path); ++ if (unlikely(err)) ++ goto out; ++ wbr->wbr_plink = dget(base[AuBrWh_PLINK].dentry); ++ } else ++ au_wh_clean(h_dir, h_path, /*isdir*/1); ++ wbr->wbr_whbase = dget(base[AuBrWh_BASE].dentry); ++ ++ h_path->dentry = base[AuBrWh_ORPH].dentry; ++ err = au_whdir(h_dir, h_path); ++ if (unlikely(err)) ++ goto out; ++ wbr->wbr_orph = dget(base[AuBrWh_ORPH].dentry); ++ ++out: ++ return err; ++} ++ ++/* ++ * initialize the whiteout base file/dir for @br. ++ */ ++int au_wh_init(struct au_branch *br, struct super_block *sb) ++{ ++ int err, i; ++ const unsigned char do_plink ++ = !!au_opt_test(au_mntflags(sb), PLINK); ++ struct inode *h_dir; ++ struct path path = br->br_path; ++ struct dentry *h_root = path.dentry; ++ struct au_wbr *wbr = br->br_wbr; ++ static const struct qstr base_name[] = { ++ [AuBrWh_BASE] = QSTR_INIT(AUFS_BASE_NAME, ++ sizeof(AUFS_BASE_NAME) - 1), ++ [AuBrWh_PLINK] = QSTR_INIT(AUFS_PLINKDIR_NAME, ++ sizeof(AUFS_PLINKDIR_NAME) - 1), ++ [AuBrWh_ORPH] = QSTR_INIT(AUFS_ORPHDIR_NAME, ++ sizeof(AUFS_ORPHDIR_NAME) - 1) ++ }; ++ struct au_wh_base base[] = { ++ [AuBrWh_BASE] = { ++ .name = base_name + AuBrWh_BASE, ++ .dentry = NULL ++ }, ++ [AuBrWh_PLINK] = { ++ .name = base_name + AuBrWh_PLINK, ++ .dentry = NULL ++ }, ++ [AuBrWh_ORPH] = { ++ .name = base_name + AuBrWh_ORPH, ++ .dentry = NULL ++ } ++ }; ++ ++ if (wbr) ++ WbrWhMustWriteLock(wbr); ++ ++ for (i = 0; i < AuBrWh_Last; i++) { ++ /* doubly whiteouted */ ++ struct dentry *d; ++ ++ d = au_wh_lkup(h_root, (void *)base[i].name, br); ++ err = PTR_ERR(d); ++ if (IS_ERR(d)) ++ goto out; ++ ++ base[i].dentry = d; ++ AuDebugOn(wbr ++ && wbr->wbr_wh[i] ++ && wbr->wbr_wh[i] != base[i].dentry); ++ } ++ ++ if (wbr) ++ for (i = 0; i < AuBrWh_Last; i++) { ++ dput(wbr->wbr_wh[i]); ++ wbr->wbr_wh[i] = NULL; ++ } ++ ++ err = 0; ++ if (!au_br_writable(br->br_perm)) { ++ h_dir = h_root->d_inode; ++ au_wh_init_ro(h_dir, base, &path); ++ } else if (!au_br_wh_linkable(br->br_perm)) { ++ err = au_wh_init_rw_nolink(h_root, wbr, do_plink, base, &path); ++ if (err > 0) ++ goto out; ++ else if (err) ++ goto out_err; ++ } else { ++ err = au_wh_init_rw(h_root, wbr, do_plink, base, &path); ++ if (err > 0) ++ goto out; ++ else if (err) ++ goto out_err; ++ } ++ goto out; /* success */ ++ ++out_err: ++ pr_err("an error(%d) on the writable branch %pd(%s)\n", ++ err, h_root, au_sbtype(h_root->d_sb)); ++out: ++ for (i = 0; i < AuBrWh_Last; i++) ++ dput(base[i].dentry); ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++/* ++ * whiteouts are all hard-linked usually. ++ * when its link count reaches a ceiling, we create a new whiteout base ++ * asynchronously. ++ */ ++ ++struct reinit_br_wh { ++ struct super_block *sb; ++ struct au_branch *br; ++}; ++ ++static void reinit_br_wh(void *arg) ++{ ++ int err; ++ aufs_bindex_t bindex; ++ struct path h_path; ++ struct reinit_br_wh *a = arg; ++ struct au_wbr *wbr; ++ struct inode *dir, *delegated; ++ struct dentry *h_root; ++ struct au_hinode *hdir; ++ ++ err = 0; ++ wbr = a->br->br_wbr; ++ /* big aufs lock */ ++ si_noflush_write_lock(a->sb); ++ if (!au_br_writable(a->br->br_perm)) ++ goto out; ++ bindex = au_br_index(a->sb, a->br->br_id); ++ if (unlikely(bindex < 0)) ++ goto out; ++ ++ di_read_lock_parent(a->sb->s_root, AuLock_IR); ++ dir = a->sb->s_root->d_inode; ++ hdir = au_hi(dir, bindex); ++ h_root = au_h_dptr(a->sb->s_root, bindex); ++ AuDebugOn(h_root != au_br_dentry(a->br)); ++ ++ au_hn_imtx_lock_nested(hdir, AuLsc_I_PARENT); ++ wbr_wh_write_lock(wbr); ++ err = au_h_verify(wbr->wbr_whbase, au_opt_udba(a->sb), hdir->hi_inode, ++ h_root, a->br); ++ if (!err) { ++ h_path.dentry = wbr->wbr_whbase; ++ h_path.mnt = au_br_mnt(a->br); ++ delegated = NULL; ++ err = vfsub_unlink(hdir->hi_inode, &h_path, &delegated, ++ /*force*/0); ++ if (unlikely(err == -EWOULDBLOCK)) { ++ pr_warn("cannot retry for NFSv4 delegation" ++ " for an internal unlink\n"); ++ iput(delegated); ++ } ++ } else { ++ pr_warn("%pd is moved, ignored\n", wbr->wbr_whbase); ++ err = 0; ++ } ++ dput(wbr->wbr_whbase); ++ wbr->wbr_whbase = NULL; ++ if (!err) ++ err = au_wh_init(a->br, a->sb); ++ wbr_wh_write_unlock(wbr); ++ au_hn_imtx_unlock(hdir); ++ di_read_unlock(a->sb->s_root, AuLock_IR); ++ if (!err) ++ au_fhsm_wrote(a->sb, bindex, /*force*/0); ++ ++out: ++ if (wbr) ++ atomic_dec(&wbr->wbr_wh_running); ++ atomic_dec(&a->br->br_count); ++ si_write_unlock(a->sb); ++ au_nwt_done(&au_sbi(a->sb)->si_nowait); ++ kfree(arg); ++ if (unlikely(err)) ++ AuIOErr("err %d\n", err); ++} ++ ++static void kick_reinit_br_wh(struct super_block *sb, struct au_branch *br) ++{ ++ int do_dec, wkq_err; ++ struct reinit_br_wh *arg; ++ ++ do_dec = 1; ++ if (atomic_inc_return(&br->br_wbr->wbr_wh_running) != 1) ++ goto out; ++ ++ /* ignore ENOMEM */ ++ arg = kmalloc(sizeof(*arg), GFP_NOFS); ++ if (arg) { ++ /* ++ * dec(wh_running), kfree(arg) and dec(br_count) ++ * in reinit function ++ */ ++ arg->sb = sb; ++ arg->br = br; ++ atomic_inc(&br->br_count); ++ wkq_err = au_wkq_nowait(reinit_br_wh, arg, sb, /*flags*/0); ++ if (unlikely(wkq_err)) { ++ atomic_dec(&br->br_wbr->wbr_wh_running); ++ atomic_dec(&br->br_count); ++ kfree(arg); ++ } ++ do_dec = 0; ++ } ++ ++out: ++ if (do_dec) ++ atomic_dec(&br->br_wbr->wbr_wh_running); ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* ++ * create the whiteout @wh. ++ */ ++static int link_or_create_wh(struct super_block *sb, aufs_bindex_t bindex, ++ struct dentry *wh) ++{ ++ int err; ++ struct path h_path = { ++ .dentry = wh ++ }; ++ struct au_branch *br; ++ struct au_wbr *wbr; ++ struct dentry *h_parent; ++ struct inode *h_dir, *delegated; ++ ++ h_parent = wh->d_parent; /* dir inode is locked */ ++ h_dir = h_parent->d_inode; ++ IMustLock(h_dir); ++ ++ br = au_sbr(sb, bindex); ++ h_path.mnt = au_br_mnt(br); ++ wbr = br->br_wbr; ++ wbr_wh_read_lock(wbr); ++ if (wbr->wbr_whbase) { ++ delegated = NULL; ++ err = vfsub_link(wbr->wbr_whbase, h_dir, &h_path, &delegated); ++ if (unlikely(err == -EWOULDBLOCK)) { ++ pr_warn("cannot retry for NFSv4 delegation" ++ " for an internal link\n"); ++ iput(delegated); ++ } ++ if (!err || err != -EMLINK) ++ goto out; ++ ++ /* link count full. re-initialize br_whbase. */ ++ kick_reinit_br_wh(sb, br); ++ } ++ ++ /* return this error in this context */ ++ err = vfsub_create(h_dir, &h_path, WH_MASK, /*want_excl*/true); ++ if (!err) ++ au_fhsm_wrote(sb, bindex, /*force*/0); ++ ++out: ++ wbr_wh_read_unlock(wbr); ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* ++ * create or remove the diropq. ++ */ ++static struct dentry *do_diropq(struct dentry *dentry, aufs_bindex_t bindex, ++ unsigned int flags) ++{ ++ struct dentry *opq_dentry, *h_dentry; ++ struct super_block *sb; ++ struct au_branch *br; ++ int err; ++ ++ sb = dentry->d_sb; ++ br = au_sbr(sb, bindex); ++ h_dentry = au_h_dptr(dentry, bindex); ++ opq_dentry = vfsub_lkup_one(&diropq_name, h_dentry); ++ if (IS_ERR(opq_dentry)) ++ goto out; ++ ++ if (au_ftest_diropq(flags, CREATE)) { ++ err = link_or_create_wh(sb, bindex, opq_dentry); ++ if (!err) { ++ au_set_dbdiropq(dentry, bindex); ++ goto out; /* success */ ++ } ++ } else { ++ struct path tmp = { ++ .dentry = opq_dentry, ++ .mnt = au_br_mnt(br) ++ }; ++ err = do_unlink_wh(au_h_iptr(dentry->d_inode, bindex), &tmp); ++ if (!err) ++ au_set_dbdiropq(dentry, -1); ++ } ++ dput(opq_dentry); ++ opq_dentry = ERR_PTR(err); ++ ++out: ++ return opq_dentry; ++} ++ ++struct do_diropq_args { ++ struct dentry **errp; ++ struct dentry *dentry; ++ aufs_bindex_t bindex; ++ unsigned int flags; ++}; ++ ++static void call_do_diropq(void *args) ++{ ++ struct do_diropq_args *a = args; ++ *a->errp = do_diropq(a->dentry, a->bindex, a->flags); ++} ++ ++struct dentry *au_diropq_sio(struct dentry *dentry, aufs_bindex_t bindex, ++ unsigned int flags) ++{ ++ struct dentry *diropq, *h_dentry; ++ ++ h_dentry = au_h_dptr(dentry, bindex); ++ if (!au_test_h_perm_sio(h_dentry->d_inode, MAY_EXEC | MAY_WRITE)) ++ diropq = do_diropq(dentry, bindex, flags); ++ else { ++ int wkq_err; ++ struct do_diropq_args args = { ++ .errp = &diropq, ++ .dentry = dentry, ++ .bindex = bindex, ++ .flags = flags ++ }; ++ ++ wkq_err = au_wkq_wait(call_do_diropq, &args); ++ if (unlikely(wkq_err)) ++ diropq = ERR_PTR(wkq_err); ++ } ++ ++ return diropq; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* ++ * lookup whiteout dentry. ++ * @h_parent: lower parent dentry which must exist and be locked ++ * @base_name: name of dentry which will be whiteouted ++ * returns dentry for whiteout. ++ */ ++struct dentry *au_wh_lkup(struct dentry *h_parent, struct qstr *base_name, ++ struct au_branch *br) ++{ ++ int err; ++ struct qstr wh_name; ++ struct dentry *wh_dentry; ++ ++ err = au_wh_name_alloc(&wh_name, base_name); ++ wh_dentry = ERR_PTR(err); ++ if (!err) { ++ wh_dentry = vfsub_lkup_one(&wh_name, h_parent); ++ kfree(wh_name.name); ++ } ++ return wh_dentry; ++} ++ ++/* ++ * link/create a whiteout for @dentry on @bindex. ++ */ ++struct dentry *au_wh_create(struct dentry *dentry, aufs_bindex_t bindex, ++ struct dentry *h_parent) ++{ ++ struct dentry *wh_dentry; ++ struct super_block *sb; ++ int err; ++ ++ sb = dentry->d_sb; ++ wh_dentry = au_wh_lkup(h_parent, &dentry->d_name, au_sbr(sb, bindex)); ++ if (!IS_ERR(wh_dentry) && !wh_dentry->d_inode) { ++ err = link_or_create_wh(sb, bindex, wh_dentry); ++ if (!err) { ++ au_set_dbwh(dentry, bindex); ++ au_fhsm_wrote(sb, bindex, /*force*/0); ++ } else { ++ dput(wh_dentry); ++ wh_dentry = ERR_PTR(err); ++ } ++ } ++ ++ return wh_dentry; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* Delete all whiteouts in this directory on branch bindex. */ ++static int del_wh_children(struct dentry *h_dentry, struct au_nhash *whlist, ++ aufs_bindex_t bindex, struct au_branch *br) ++{ ++ int err; ++ unsigned long ul, n; ++ struct qstr wh_name; ++ char *p; ++ struct hlist_head *head; ++ struct au_vdir_wh *pos; ++ struct au_vdir_destr *str; ++ ++ err = -ENOMEM; ++ p = (void *)__get_free_page(GFP_NOFS); ++ wh_name.name = p; ++ if (unlikely(!wh_name.name)) ++ goto out; ++ ++ err = 0; ++ memcpy(p, AUFS_WH_PFX, AUFS_WH_PFX_LEN); ++ p += AUFS_WH_PFX_LEN; ++ n = whlist->nh_num; ++ head = whlist->nh_head; ++ for (ul = 0; !err && ul < n; ul++, head++) { ++ hlist_for_each_entry(pos, head, wh_hash) { ++ if (pos->wh_bindex != bindex) ++ continue; ++ ++ str = &pos->wh_str; ++ if (str->len + AUFS_WH_PFX_LEN <= PATH_MAX) { ++ memcpy(p, str->name, str->len); ++ wh_name.len = AUFS_WH_PFX_LEN + str->len; ++ err = unlink_wh_name(h_dentry, &wh_name, br); ++ if (!err) ++ continue; ++ break; ++ } ++ AuIOErr("whiteout name too long %.*s\n", ++ str->len, str->name); ++ err = -EIO; ++ break; ++ } ++ } ++ free_page((unsigned long)wh_name.name); ++ ++out: ++ return err; ++} ++ ++struct del_wh_children_args { ++ int *errp; ++ struct dentry *h_dentry; ++ struct au_nhash *whlist; ++ aufs_bindex_t bindex; ++ struct au_branch *br; ++}; ++ ++static void call_del_wh_children(void *args) ++{ ++ struct del_wh_children_args *a = args; ++ *a->errp = del_wh_children(a->h_dentry, a->whlist, a->bindex, a->br); ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++struct au_whtmp_rmdir *au_whtmp_rmdir_alloc(struct super_block *sb, gfp_t gfp) ++{ ++ struct au_whtmp_rmdir *whtmp; ++ int err; ++ unsigned int rdhash; ++ ++ SiMustAnyLock(sb); ++ ++ whtmp = kzalloc(sizeof(*whtmp), gfp); ++ if (unlikely(!whtmp)) { ++ whtmp = ERR_PTR(-ENOMEM); ++ goto out; ++ } ++ ++ /* no estimation for dir size */ ++ rdhash = au_sbi(sb)->si_rdhash; ++ if (!rdhash) ++ rdhash = AUFS_RDHASH_DEF; ++ err = au_nhash_alloc(&whtmp->whlist, rdhash, gfp); ++ if (unlikely(err)) { ++ kfree(whtmp); ++ whtmp = ERR_PTR(err); ++ } ++ ++out: ++ return whtmp; ++} ++ ++void au_whtmp_rmdir_free(struct au_whtmp_rmdir *whtmp) ++{ ++ if (whtmp->br) ++ atomic_dec(&whtmp->br->br_count); ++ dput(whtmp->wh_dentry); ++ iput(whtmp->dir); ++ au_nhash_wh_free(&whtmp->whlist); ++ kfree(whtmp); ++} ++ ++/* ++ * rmdir the whiteouted temporary named dir @h_dentry. ++ * @whlist: whiteouted children. ++ */ ++int au_whtmp_rmdir(struct inode *dir, aufs_bindex_t bindex, ++ struct dentry *wh_dentry, struct au_nhash *whlist) ++{ ++ int err; ++ unsigned int h_nlink; ++ struct path h_tmp; ++ struct inode *wh_inode, *h_dir; ++ struct au_branch *br; ++ ++ h_dir = wh_dentry->d_parent->d_inode; /* dir inode is locked */ ++ IMustLock(h_dir); ++ ++ br = au_sbr(dir->i_sb, bindex); ++ wh_inode = wh_dentry->d_inode; ++ mutex_lock_nested(&wh_inode->i_mutex, AuLsc_I_CHILD); ++ ++ /* ++ * someone else might change some whiteouts while we were sleeping. ++ * it means this whlist may have an obsoleted entry. ++ */ ++ if (!au_test_h_perm_sio(wh_inode, MAY_EXEC | MAY_WRITE)) ++ err = del_wh_children(wh_dentry, whlist, bindex, br); ++ else { ++ int wkq_err; ++ struct del_wh_children_args args = { ++ .errp = &err, ++ .h_dentry = wh_dentry, ++ .whlist = whlist, ++ .bindex = bindex, ++ .br = br ++ }; ++ ++ wkq_err = au_wkq_wait(call_del_wh_children, &args); ++ if (unlikely(wkq_err)) ++ err = wkq_err; ++ } ++ mutex_unlock(&wh_inode->i_mutex); ++ ++ if (!err) { ++ h_tmp.dentry = wh_dentry; ++ h_tmp.mnt = au_br_mnt(br); ++ h_nlink = h_dir->i_nlink; ++ err = vfsub_rmdir(h_dir, &h_tmp); ++ /* some fs doesn't change the parent nlink in some cases */ ++ h_nlink -= h_dir->i_nlink; ++ } ++ ++ if (!err) { ++ if (au_ibstart(dir) == bindex) { ++ /* todo: dir->i_mutex is necessary */ ++ au_cpup_attr_timesizes(dir); ++ if (h_nlink) ++ vfsub_drop_nlink(dir); ++ } ++ return 0; /* success */ ++ } ++ ++ pr_warn("failed removing %pd(%d), ignored\n", wh_dentry, err); ++ return err; ++} ++ ++static void call_rmdir_whtmp(void *args) ++{ ++ int err; ++ aufs_bindex_t bindex; ++ struct au_whtmp_rmdir *a = args; ++ struct super_block *sb; ++ struct dentry *h_parent; ++ struct inode *h_dir; ++ struct au_hinode *hdir; ++ ++ /* rmdir by nfsd may cause deadlock with this i_mutex */ ++ /* mutex_lock(&a->dir->i_mutex); */ ++ err = -EROFS; ++ sb = a->dir->i_sb; ++ si_read_lock(sb, !AuLock_FLUSH); ++ if (!au_br_writable(a->br->br_perm)) ++ goto out; ++ bindex = au_br_index(sb, a->br->br_id); ++ if (unlikely(bindex < 0)) ++ goto out; ++ ++ err = -EIO; ++ ii_write_lock_parent(a->dir); ++ h_parent = dget_parent(a->wh_dentry); ++ h_dir = h_parent->d_inode; ++ hdir = au_hi(a->dir, bindex); ++ err = vfsub_mnt_want_write(au_br_mnt(a->br)); ++ if (unlikely(err)) ++ goto out_mnt; ++ au_hn_imtx_lock_nested(hdir, AuLsc_I_PARENT); ++ err = au_h_verify(a->wh_dentry, au_opt_udba(sb), h_dir, h_parent, ++ a->br); ++ if (!err) ++ err = au_whtmp_rmdir(a->dir, bindex, a->wh_dentry, &a->whlist); ++ au_hn_imtx_unlock(hdir); ++ vfsub_mnt_drop_write(au_br_mnt(a->br)); ++ ++out_mnt: ++ dput(h_parent); ++ ii_write_unlock(a->dir); ++out: ++ /* mutex_unlock(&a->dir->i_mutex); */ ++ au_whtmp_rmdir_free(a); ++ si_read_unlock(sb); ++ au_nwt_done(&au_sbi(sb)->si_nowait); ++ if (unlikely(err)) ++ AuIOErr("err %d\n", err); ++} ++ ++void au_whtmp_kick_rmdir(struct inode *dir, aufs_bindex_t bindex, ++ struct dentry *wh_dentry, struct au_whtmp_rmdir *args) ++{ ++ int wkq_err; ++ struct super_block *sb; ++ ++ IMustLock(dir); ++ ++ /* all post-process will be done in do_rmdir_whtmp(). */ ++ sb = dir->i_sb; ++ args->dir = au_igrab(dir); ++ args->br = au_sbr(sb, bindex); ++ atomic_inc(&args->br->br_count); ++ args->wh_dentry = dget(wh_dentry); ++ wkq_err = au_wkq_nowait(call_rmdir_whtmp, args, sb, /*flags*/0); ++ if (unlikely(wkq_err)) { ++ pr_warn("rmdir error %pd (%d), ignored\n", wh_dentry, wkq_err); ++ au_whtmp_rmdir_free(args); ++ } ++} +diff --git a/fs/aufs/whout.h b/fs/aufs/whout.h +new file mode 100644 +index 0000000..5a5c378 +--- /dev/null ++++ b/fs/aufs/whout.h +@@ -0,0 +1,85 @@ ++/* ++ * Copyright (C) 2005-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * whiteout for logical deletion and opaque directory ++ */ ++ ++#ifndef __AUFS_WHOUT_H__ ++#define __AUFS_WHOUT_H__ ++ ++#ifdef __KERNEL__ ++ ++#include "dir.h" ++ ++/* whout.c */ ++int au_wh_name_alloc(struct qstr *wh, const struct qstr *name); ++int au_wh_test(struct dentry *h_parent, struct qstr *wh_name, int try_sio); ++int au_diropq_test(struct dentry *h_dentry); ++struct au_branch; ++struct dentry *au_whtmp_lkup(struct dentry *h_parent, struct au_branch *br, ++ struct qstr *prefix); ++int au_whtmp_ren(struct dentry *h_dentry, struct au_branch *br); ++int au_wh_unlink_dentry(struct inode *h_dir, struct path *h_path, ++ struct dentry *dentry); ++int au_wh_init(struct au_branch *br, struct super_block *sb); ++ ++/* diropq flags */ ++#define AuDiropq_CREATE 1 ++#define au_ftest_diropq(flags, name) ((flags) & AuDiropq_##name) ++#define au_fset_diropq(flags, name) \ ++ do { (flags) |= AuDiropq_##name; } while (0) ++#define au_fclr_diropq(flags, name) \ ++ do { (flags) &= ~AuDiropq_##name; } while (0) ++ ++struct dentry *au_diropq_sio(struct dentry *dentry, aufs_bindex_t bindex, ++ unsigned int flags); ++struct dentry *au_wh_lkup(struct dentry *h_parent, struct qstr *base_name, ++ struct au_branch *br); ++struct dentry *au_wh_create(struct dentry *dentry, aufs_bindex_t bindex, ++ struct dentry *h_parent); ++ ++/* real rmdir for the whiteout-ed dir */ ++struct au_whtmp_rmdir { ++ struct inode *dir; ++ struct au_branch *br; ++ struct dentry *wh_dentry; ++ struct au_nhash whlist; ++}; ++ ++struct au_whtmp_rmdir *au_whtmp_rmdir_alloc(struct super_block *sb, gfp_t gfp); ++void au_whtmp_rmdir_free(struct au_whtmp_rmdir *whtmp); ++int au_whtmp_rmdir(struct inode *dir, aufs_bindex_t bindex, ++ struct dentry *wh_dentry, struct au_nhash *whlist); ++void au_whtmp_kick_rmdir(struct inode *dir, aufs_bindex_t bindex, ++ struct dentry *wh_dentry, struct au_whtmp_rmdir *args); ++ ++/* ---------------------------------------------------------------------- */ ++ ++static inline struct dentry *au_diropq_create(struct dentry *dentry, ++ aufs_bindex_t bindex) ++{ ++ return au_diropq_sio(dentry, bindex, AuDiropq_CREATE); ++} ++ ++static inline int au_diropq_remove(struct dentry *dentry, aufs_bindex_t bindex) ++{ ++ return PTR_ERR(au_diropq_sio(dentry, bindex, !AuDiropq_CREATE)); ++} ++ ++#endif /* __KERNEL__ */ ++#endif /* __AUFS_WHOUT_H__ */ +diff --git a/fs/aufs/wkq.c b/fs/aufs/wkq.c +new file mode 100644 +index 0000000..a4e1b92 +--- /dev/null ++++ b/fs/aufs/wkq.c +@@ -0,0 +1,213 @@ ++/* ++ * Copyright (C) 2005-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * workqueue for asynchronous/super-io operations ++ * todo: try new dredential scheme ++ */ ++ ++#include ++#include "aufs.h" ++ ++/* internal workqueue named AUFS_WKQ_NAME */ ++ ++static struct workqueue_struct *au_wkq; ++ ++struct au_wkinfo { ++ struct work_struct wk; ++ struct kobject *kobj; ++ ++ unsigned int flags; /* see wkq.h */ ++ ++ au_wkq_func_t func; ++ void *args; ++ ++ struct completion *comp; ++}; ++ ++/* ---------------------------------------------------------------------- */ ++ ++static void wkq_func(struct work_struct *wk) ++{ ++ struct au_wkinfo *wkinfo = container_of(wk, struct au_wkinfo, wk); ++ ++ AuDebugOn(!uid_eq(current_fsuid(), GLOBAL_ROOT_UID)); ++ AuDebugOn(rlimit(RLIMIT_FSIZE) != RLIM_INFINITY); ++ ++ wkinfo->func(wkinfo->args); ++ if (au_ftest_wkq(wkinfo->flags, WAIT)) ++ complete(wkinfo->comp); ++ else { ++ kobject_put(wkinfo->kobj); ++ module_put(THIS_MODULE); /* todo: ?? */ ++ kfree(wkinfo); ++ } ++} ++ ++/* ++ * Since struct completion is large, try allocating it dynamically. ++ */ ++#if 1 /* defined(CONFIG_4KSTACKS) || defined(AuTest4KSTACKS) */ ++#define AuWkqCompDeclare(name) struct completion *comp = NULL ++ ++static int au_wkq_comp_alloc(struct au_wkinfo *wkinfo, struct completion **comp) ++{ ++ *comp = kmalloc(sizeof(**comp), GFP_NOFS); ++ if (*comp) { ++ init_completion(*comp); ++ wkinfo->comp = *comp; ++ return 0; ++ } ++ return -ENOMEM; ++} ++ ++static void au_wkq_comp_free(struct completion *comp) ++{ ++ kfree(comp); ++} ++ ++#else ++ ++/* no braces */ ++#define AuWkqCompDeclare(name) \ ++ DECLARE_COMPLETION_ONSTACK(_ ## name); \ ++ struct completion *comp = &_ ## name ++ ++static int au_wkq_comp_alloc(struct au_wkinfo *wkinfo, struct completion **comp) ++{ ++ wkinfo->comp = *comp; ++ return 0; ++} ++ ++static void au_wkq_comp_free(struct completion *comp __maybe_unused) ++{ ++ /* empty */ ++} ++#endif /* 4KSTACKS */ ++ ++static void au_wkq_run(struct au_wkinfo *wkinfo) ++{ ++ if (au_ftest_wkq(wkinfo->flags, NEST)) { ++ if (au_wkq_test()) { ++ AuWarn1("wkq from wkq, unless silly-rename on NFS," ++ " due to a dead dir by UDBA?\n"); ++ AuDebugOn(au_ftest_wkq(wkinfo->flags, WAIT)); ++ } ++ } else ++ au_dbg_verify_kthread(); ++ ++ if (au_ftest_wkq(wkinfo->flags, WAIT)) { ++ INIT_WORK_ONSTACK(&wkinfo->wk, wkq_func); ++ queue_work(au_wkq, &wkinfo->wk); ++ } else { ++ INIT_WORK(&wkinfo->wk, wkq_func); ++ schedule_work(&wkinfo->wk); ++ } ++} ++ ++/* ++ * Be careful. It is easy to make deadlock happen. ++ * processA: lock, wkq and wait ++ * processB: wkq and wait, lock in wkq ++ * --> deadlock ++ */ ++int au_wkq_do_wait(unsigned int flags, au_wkq_func_t func, void *args) ++{ ++ int err; ++ AuWkqCompDeclare(comp); ++ struct au_wkinfo wkinfo = { ++ .flags = flags, ++ .func = func, ++ .args = args ++ }; ++ ++ err = au_wkq_comp_alloc(&wkinfo, &comp); ++ if (!err) { ++ au_wkq_run(&wkinfo); ++ /* no timeout, no interrupt */ ++ wait_for_completion(wkinfo.comp); ++ au_wkq_comp_free(comp); ++ destroy_work_on_stack(&wkinfo.wk); ++ } ++ ++ return err; ++ ++} ++ ++/* ++ * Note: dget/dput() in func for aufs dentries are not supported. It will be a ++ * problem in a concurrent umounting. ++ */ ++int au_wkq_nowait(au_wkq_func_t func, void *args, struct super_block *sb, ++ unsigned int flags) ++{ ++ int err; ++ struct au_wkinfo *wkinfo; ++ ++ atomic_inc(&au_sbi(sb)->si_nowait.nw_len); ++ ++ /* ++ * wkq_func() must free this wkinfo. ++ * it highly depends upon the implementation of workqueue. ++ */ ++ err = 0; ++ wkinfo = kmalloc(sizeof(*wkinfo), GFP_NOFS); ++ if (wkinfo) { ++ wkinfo->kobj = &au_sbi(sb)->si_kobj; ++ wkinfo->flags = flags & ~AuWkq_WAIT; ++ wkinfo->func = func; ++ wkinfo->args = args; ++ wkinfo->comp = NULL; ++ kobject_get(wkinfo->kobj); ++ __module_get(THIS_MODULE); /* todo: ?? */ ++ ++ au_wkq_run(wkinfo); ++ } else { ++ err = -ENOMEM; ++ au_nwt_done(&au_sbi(sb)->si_nowait); ++ } ++ ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++void au_nwt_init(struct au_nowait_tasks *nwt) ++{ ++ atomic_set(&nwt->nw_len, 0); ++ /* smp_mb(); */ /* atomic_set */ ++ init_waitqueue_head(&nwt->nw_wq); ++} ++ ++void au_wkq_fin(void) ++{ ++ destroy_workqueue(au_wkq); ++} ++ ++int __init au_wkq_init(void) ++{ ++ int err; ++ ++ err = 0; ++ au_wkq = alloc_workqueue(AUFS_WKQ_NAME, 0, WQ_DFL_ACTIVE); ++ if (IS_ERR(au_wkq)) ++ err = PTR_ERR(au_wkq); ++ else if (!au_wkq) ++ err = -ENOMEM; ++ ++ return err; ++} +diff --git a/fs/aufs/wkq.h b/fs/aufs/wkq.h +new file mode 100644 +index 0000000..830123c +--- /dev/null ++++ b/fs/aufs/wkq.h +@@ -0,0 +1,91 @@ ++/* ++ * Copyright (C) 2005-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * workqueue for asynchronous/super-io operations ++ * todo: try new credentials management scheme ++ */ ++ ++#ifndef __AUFS_WKQ_H__ ++#define __AUFS_WKQ_H__ ++ ++#ifdef __KERNEL__ ++ ++struct super_block; ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* ++ * in the next operation, wait for the 'nowait' tasks in system-wide workqueue ++ */ ++struct au_nowait_tasks { ++ atomic_t nw_len; ++ wait_queue_head_t nw_wq; ++}; ++ ++/* ---------------------------------------------------------------------- */ ++ ++typedef void (*au_wkq_func_t)(void *args); ++ ++/* wkq flags */ ++#define AuWkq_WAIT 1 ++#define AuWkq_NEST (1 << 1) ++#define au_ftest_wkq(flags, name) ((flags) & AuWkq_##name) ++#define au_fset_wkq(flags, name) \ ++ do { (flags) |= AuWkq_##name; } while (0) ++#define au_fclr_wkq(flags, name) \ ++ do { (flags) &= ~AuWkq_##name; } while (0) ++ ++#ifndef CONFIG_AUFS_HNOTIFY ++#undef AuWkq_NEST ++#define AuWkq_NEST 0 ++#endif ++ ++/* wkq.c */ ++int au_wkq_do_wait(unsigned int flags, au_wkq_func_t func, void *args); ++int au_wkq_nowait(au_wkq_func_t func, void *args, struct super_block *sb, ++ unsigned int flags); ++void au_nwt_init(struct au_nowait_tasks *nwt); ++int __init au_wkq_init(void); ++void au_wkq_fin(void); ++ ++/* ---------------------------------------------------------------------- */ ++ ++static inline int au_wkq_test(void) ++{ ++ return current->flags & PF_WQ_WORKER; ++} ++ ++static inline int au_wkq_wait(au_wkq_func_t func, void *args) ++{ ++ return au_wkq_do_wait(AuWkq_WAIT, func, args); ++} ++ ++static inline void au_nwt_done(struct au_nowait_tasks *nwt) ++{ ++ if (atomic_dec_and_test(&nwt->nw_len)) ++ wake_up_all(&nwt->nw_wq); ++} ++ ++static inline int au_nwt_flush(struct au_nowait_tasks *nwt) ++{ ++ wait_event(nwt->nw_wq, !atomic_read(&nwt->nw_len)); ++ return 0; ++} ++ ++#endif /* __KERNEL__ */ ++#endif /* __AUFS_WKQ_H__ */ +diff --git a/fs/aufs/xattr.c b/fs/aufs/xattr.c +new file mode 100644 +index 0000000..e16beea +--- /dev/null ++++ b/fs/aufs/xattr.c +@@ -0,0 +1,344 @@ ++/* ++ * Copyright (C) 2014-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * handling xattr functions ++ */ ++ ++#include ++#include "aufs.h" ++ ++static int au_xattr_ignore(int err, char *name, unsigned int ignore_flags) ++{ ++ if (!ignore_flags) ++ goto out; ++ switch (err) { ++ case -ENOMEM: ++ case -EDQUOT: ++ goto out; ++ } ++ ++ if ((ignore_flags & AuBrAttr_ICEX) == AuBrAttr_ICEX) { ++ err = 0; ++ goto out; ++ } ++ ++#define cmp(brattr, prefix) do { \ ++ if (!strncmp(name, XATTR_##prefix##_PREFIX, \ ++ XATTR_##prefix##_PREFIX_LEN)) { \ ++ if (ignore_flags & AuBrAttr_ICEX_##brattr) \ ++ err = 0; \ ++ goto out; \ ++ } \ ++ } while (0) ++ ++ cmp(SEC, SECURITY); ++ cmp(SYS, SYSTEM); ++ cmp(TR, TRUSTED); ++ cmp(USR, USER); ++#undef cmp ++ ++ if (ignore_flags & AuBrAttr_ICEX_OTH) ++ err = 0; ++ ++out: ++ return err; ++} ++ ++static const int au_xattr_out_of_list = AuBrAttr_ICEX_OTH << 1; ++ ++static int au_do_cpup_xattr(struct dentry *h_dst, struct dentry *h_src, ++ char *name, char **buf, unsigned int ignore_flags, ++ unsigned int verbose) ++{ ++ int err; ++ ssize_t ssz; ++ struct inode *h_idst; ++ ++ ssz = vfs_getxattr_alloc(h_src, name, buf, 0, GFP_NOFS); ++ err = ssz; ++ if (unlikely(err <= 0)) { ++ if (err == -ENODATA ++ || (err == -EOPNOTSUPP ++ && ((ignore_flags & au_xattr_out_of_list) ++ || (au_test_nfs_noacl(h_src->d_inode) ++ && (!strcmp(name, XATTR_NAME_POSIX_ACL_ACCESS) ++ || !strcmp(name, ++ XATTR_NAME_POSIX_ACL_DEFAULT)))) ++ )) ++ err = 0; ++ if (err && (verbose || au_debug_test())) ++ pr_err("%s, err %d\n", name, err); ++ goto out; ++ } ++ ++ /* unlock it temporary */ ++ h_idst = h_dst->d_inode; ++ mutex_unlock(&h_idst->i_mutex); ++ err = vfsub_setxattr(h_dst, name, *buf, ssz, /*flags*/0); ++ mutex_lock_nested(&h_idst->i_mutex, AuLsc_I_CHILD2); ++ if (unlikely(err)) { ++ if (verbose || au_debug_test()) ++ pr_err("%s, err %d\n", name, err); ++ err = au_xattr_ignore(err, name, ignore_flags); ++ } ++ ++out: ++ return err; ++} ++ ++int au_cpup_xattr(struct dentry *h_dst, struct dentry *h_src, int ignore_flags, ++ unsigned int verbose) ++{ ++ int err, unlocked, acl_access, acl_default; ++ ssize_t ssz; ++ struct inode *h_isrc, *h_idst; ++ char *value, *p, *o, *e; ++ ++ /* try stopping to update the source inode while we are referencing */ ++ /* there should not be the parent-child relationship between them */ ++ h_isrc = h_src->d_inode; ++ h_idst = h_dst->d_inode; ++ mutex_unlock(&h_idst->i_mutex); ++ mutex_lock_nested(&h_isrc->i_mutex, AuLsc_I_CHILD); ++ mutex_lock_nested(&h_idst->i_mutex, AuLsc_I_CHILD2); ++ unlocked = 0; ++ ++ /* some filesystems don't list POSIX ACL, for example tmpfs */ ++ ssz = vfs_listxattr(h_src, NULL, 0); ++ err = ssz; ++ if (unlikely(err < 0)) { ++ AuTraceErr(err); ++ if (err == -ENODATA ++ || err == -EOPNOTSUPP) ++ err = 0; /* ignore */ ++ goto out; ++ } ++ ++ err = 0; ++ p = NULL; ++ o = NULL; ++ if (ssz) { ++ err = -ENOMEM; ++ p = kmalloc(ssz, GFP_NOFS); ++ o = p; ++ if (unlikely(!p)) ++ goto out; ++ err = vfs_listxattr(h_src, p, ssz); ++ } ++ mutex_unlock(&h_isrc->i_mutex); ++ unlocked = 1; ++ AuDbg("err %d, ssz %zd\n", err, ssz); ++ if (unlikely(err < 0)) ++ goto out_free; ++ ++ err = 0; ++ e = p + ssz; ++ value = NULL; ++ acl_access = 0; ++ acl_default = 0; ++ while (!err && p < e) { ++ acl_access |= !strncmp(p, XATTR_NAME_POSIX_ACL_ACCESS, ++ sizeof(XATTR_NAME_POSIX_ACL_ACCESS) - 1); ++ acl_default |= !strncmp(p, XATTR_NAME_POSIX_ACL_DEFAULT, ++ sizeof(XATTR_NAME_POSIX_ACL_DEFAULT) ++ - 1); ++ err = au_do_cpup_xattr(h_dst, h_src, p, &value, ignore_flags, ++ verbose); ++ p += strlen(p) + 1; ++ } ++ AuTraceErr(err); ++ ignore_flags |= au_xattr_out_of_list; ++ if (!err && !acl_access) { ++ err = au_do_cpup_xattr(h_dst, h_src, ++ XATTR_NAME_POSIX_ACL_ACCESS, &value, ++ ignore_flags, verbose); ++ AuTraceErr(err); ++ } ++ if (!err && !acl_default) { ++ err = au_do_cpup_xattr(h_dst, h_src, ++ XATTR_NAME_POSIX_ACL_DEFAULT, &value, ++ ignore_flags, verbose); ++ AuTraceErr(err); ++ } ++ ++ kfree(value); ++ ++out_free: ++ kfree(o); ++out: ++ if (!unlocked) ++ mutex_unlock(&h_isrc->i_mutex); ++ AuTraceErr(err); ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++enum { ++ AU_XATTR_LIST, ++ AU_XATTR_GET ++}; ++ ++struct au_lgxattr { ++ int type; ++ union { ++ struct { ++ char *list; ++ size_t size; ++ } list; ++ struct { ++ const char *name; ++ void *value; ++ size_t size; ++ } get; ++ } u; ++}; ++ ++static ssize_t au_lgxattr(struct dentry *dentry, struct au_lgxattr *arg) ++{ ++ ssize_t err; ++ struct path h_path; ++ struct super_block *sb; ++ ++ sb = dentry->d_sb; ++ err = si_read_lock(sb, AuLock_FLUSH | AuLock_NOPLM); ++ if (unlikely(err)) ++ goto out; ++ err = au_h_path_getattr(dentry, /*force*/1, &h_path); ++ if (unlikely(err)) ++ goto out_si; ++ if (unlikely(!h_path.dentry)) ++ /* illegally overlapped or something */ ++ goto out_di; /* pretending success */ ++ ++ /* always topmost entry only */ ++ switch (arg->type) { ++ case AU_XATTR_LIST: ++ err = vfs_listxattr(h_path.dentry, ++ arg->u.list.list, arg->u.list.size); ++ break; ++ case AU_XATTR_GET: ++ err = vfs_getxattr(h_path.dentry, ++ arg->u.get.name, arg->u.get.value, ++ arg->u.get.size); ++ break; ++ } ++ ++out_di: ++ di_read_unlock(dentry, AuLock_IR); ++out_si: ++ si_read_unlock(sb); ++out: ++ AuTraceErr(err); ++ return err; ++} ++ ++ssize_t aufs_listxattr(struct dentry *dentry, char *list, size_t size) ++{ ++ struct au_lgxattr arg = { ++ .type = AU_XATTR_LIST, ++ .u.list = { ++ .list = list, ++ .size = size ++ }, ++ }; ++ ++ return au_lgxattr(dentry, &arg); ++} ++ ++ssize_t aufs_getxattr(struct dentry *dentry, const char *name, void *value, ++ size_t size) ++{ ++ struct au_lgxattr arg = { ++ .type = AU_XATTR_GET, ++ .u.get = { ++ .name = name, ++ .value = value, ++ .size = size ++ }, ++ }; ++ ++ return au_lgxattr(dentry, &arg); ++} ++ ++int aufs_setxattr(struct dentry *dentry, const char *name, const void *value, ++ size_t size, int flags) ++{ ++ struct au_srxattr arg = { ++ .type = AU_XATTR_SET, ++ .u.set = { ++ .name = name, ++ .value = value, ++ .size = size, ++ .flags = flags ++ }, ++ }; ++ ++ return au_srxattr(dentry, &arg); ++} ++ ++int aufs_removexattr(struct dentry *dentry, const char *name) ++{ ++ struct au_srxattr arg = { ++ .type = AU_XATTR_REMOVE, ++ .u.remove = { ++ .name = name ++ }, ++ }; ++ ++ return au_srxattr(dentry, &arg); ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++#if 0 ++static size_t au_xattr_list(struct dentry *dentry, char *list, size_t list_size, ++ const char *name, size_t name_len, int type) ++{ ++ return aufs_listxattr(dentry, list, list_size); ++} ++ ++static int au_xattr_get(struct dentry *dentry, const char *name, void *buffer, ++ size_t size, int type) ++{ ++ return aufs_getxattr(dentry, name, buffer, size); ++} ++ ++static int au_xattr_set(struct dentry *dentry, const char *name, ++ const void *value, size_t size, int flags, int type) ++{ ++ return aufs_setxattr(dentry, name, value, size, flags); ++} ++ ++static const struct xattr_handler au_xattr_handler = { ++ /* no prefix, no flags */ ++ .list = au_xattr_list, ++ .get = au_xattr_get, ++ .set = au_xattr_set ++ /* why no remove? */ ++}; ++ ++static const struct xattr_handler *au_xattr_handlers[] = { ++ &au_xattr_handler ++}; ++ ++void au_xattr_init(struct super_block *sb) ++{ ++ /* sb->s_xattr = au_xattr_handlers; */ ++} ++#endif +diff --git a/fs/aufs/xino.c b/fs/aufs/xino.c +new file mode 100644 +index 0000000..50ab4ca +--- /dev/null ++++ b/fs/aufs/xino.c +@@ -0,0 +1,1343 @@ ++/* ++ * Copyright (C) 2005-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++/* ++ * external inode number translation table and bitmap ++ */ ++ ++#include ++#include ++#include "aufs.h" ++ ++/* todo: unnecessary to support mmap_sem since kernel-space? */ ++ssize_t xino_fread(au_readf_t func, struct file *file, void *kbuf, size_t size, ++ loff_t *pos) ++{ ++ ssize_t err; ++ mm_segment_t oldfs; ++ union { ++ void *k; ++ char __user *u; ++ } buf; ++ ++ buf.k = kbuf; ++ oldfs = get_fs(); ++ set_fs(KERNEL_DS); ++ do { ++ /* todo: signal_pending? */ ++ err = func(file, buf.u, size, pos); ++ } while (err == -EAGAIN || err == -EINTR); ++ set_fs(oldfs); ++ ++#if 0 /* reserved for future use */ ++ if (err > 0) ++ fsnotify_access(file->f_dentry); ++#endif ++ ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++static ssize_t xino_fwrite_wkq(au_writef_t func, struct file *file, void *buf, ++ size_t size, loff_t *pos); ++ ++static ssize_t do_xino_fwrite(au_writef_t func, struct file *file, void *kbuf, ++ size_t size, loff_t *pos) ++{ ++ ssize_t err; ++ mm_segment_t oldfs; ++ union { ++ void *k; ++ const char __user *u; ++ } buf; ++ int i; ++ const int prevent_endless = 10; ++ ++ i = 0; ++ buf.k = kbuf; ++ oldfs = get_fs(); ++ set_fs(KERNEL_DS); ++ do { ++ err = func(file, buf.u, size, pos); ++ if (err == -EINTR ++ && !au_wkq_test() ++ && fatal_signal_pending(current)) { ++ set_fs(oldfs); ++ err = xino_fwrite_wkq(func, file, kbuf, size, pos); ++ BUG_ON(err == -EINTR); ++ oldfs = get_fs(); ++ set_fs(KERNEL_DS); ++ } ++ } while (i++ < prevent_endless ++ && (err == -EAGAIN || err == -EINTR)); ++ set_fs(oldfs); ++ ++#if 0 /* reserved for future use */ ++ if (err > 0) ++ fsnotify_modify(file->f_dentry); ++#endif ++ ++ return err; ++} ++ ++struct do_xino_fwrite_args { ++ ssize_t *errp; ++ au_writef_t func; ++ struct file *file; ++ void *buf; ++ size_t size; ++ loff_t *pos; ++}; ++ ++static void call_do_xino_fwrite(void *args) ++{ ++ struct do_xino_fwrite_args *a = args; ++ *a->errp = do_xino_fwrite(a->func, a->file, a->buf, a->size, a->pos); ++} ++ ++static ssize_t xino_fwrite_wkq(au_writef_t func, struct file *file, void *buf, ++ size_t size, loff_t *pos) ++{ ++ ssize_t err; ++ int wkq_err; ++ struct do_xino_fwrite_args args = { ++ .errp = &err, ++ .func = func, ++ .file = file, ++ .buf = buf, ++ .size = size, ++ .pos = pos ++ }; ++ ++ /* ++ * it breaks RLIMIT_FSIZE and normal user's limit, ++ * users should care about quota and real 'filesystem full.' ++ */ ++ wkq_err = au_wkq_wait(call_do_xino_fwrite, &args); ++ if (unlikely(wkq_err)) ++ err = wkq_err; ++ ++ return err; ++} ++ ++ssize_t xino_fwrite(au_writef_t func, struct file *file, void *buf, size_t size, ++ loff_t *pos) ++{ ++ ssize_t err; ++ ++ if (rlimit(RLIMIT_FSIZE) == RLIM_INFINITY) { ++ lockdep_off(); ++ err = do_xino_fwrite(func, file, buf, size, pos); ++ lockdep_on(); ++ } else ++ err = xino_fwrite_wkq(func, file, buf, size, pos); ++ ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* ++ * create a new xinofile at the same place/path as @base_file. ++ */ ++struct file *au_xino_create2(struct file *base_file, struct file *copy_src) ++{ ++ struct file *file; ++ struct dentry *base, *parent; ++ struct inode *dir, *delegated; ++ struct qstr *name; ++ struct path path; ++ int err; ++ ++ base = base_file->f_dentry; ++ parent = base->d_parent; /* dir inode is locked */ ++ dir = parent->d_inode; ++ IMustLock(dir); ++ ++ file = ERR_PTR(-EINVAL); ++ name = &base->d_name; ++ path.dentry = vfsub_lookup_one_len(name->name, parent, name->len); ++ if (IS_ERR(path.dentry)) { ++ file = (void *)path.dentry; ++ pr_err("%pd lookup err %ld\n", ++ base, PTR_ERR(path.dentry)); ++ goto out; ++ } ++ ++ /* no need to mnt_want_write() since we call dentry_open() later */ ++ err = vfs_create(dir, path.dentry, S_IRUGO | S_IWUGO, NULL); ++ if (unlikely(err)) { ++ file = ERR_PTR(err); ++ pr_err("%pd create err %d\n", base, err); ++ goto out_dput; ++ } ++ ++ path.mnt = base_file->f_path.mnt; ++ file = vfsub_dentry_open(&path, ++ O_RDWR | O_CREAT | O_EXCL | O_LARGEFILE ++ /* | __FMODE_NONOTIFY */); ++ if (IS_ERR(file)) { ++ pr_err("%pd open err %ld\n", base, PTR_ERR(file)); ++ goto out_dput; ++ } ++ ++ delegated = NULL; ++ err = vfsub_unlink(dir, &file->f_path, &delegated, /*force*/0); ++ if (unlikely(err == -EWOULDBLOCK)) { ++ pr_warn("cannot retry for NFSv4 delegation" ++ " for an internal unlink\n"); ++ iput(delegated); ++ } ++ if (unlikely(err)) { ++ pr_err("%pd unlink err %d\n", base, err); ++ goto out_fput; ++ } ++ ++ if (copy_src) { ++ /* no one can touch copy_src xino */ ++ err = au_copy_file(file, copy_src, vfsub_f_size_read(copy_src)); ++ if (unlikely(err)) { ++ pr_err("%pd copy err %d\n", base, err); ++ goto out_fput; ++ } ++ } ++ goto out_dput; /* success */ ++ ++out_fput: ++ fput(file); ++ file = ERR_PTR(err); ++out_dput: ++ dput(path.dentry); ++out: ++ return file; ++} ++ ++struct au_xino_lock_dir { ++ struct au_hinode *hdir; ++ struct dentry *parent; ++ struct mutex *mtx; ++}; ++ ++static void au_xino_lock_dir(struct super_block *sb, struct file *xino, ++ struct au_xino_lock_dir *ldir) ++{ ++ aufs_bindex_t brid, bindex; ++ ++ ldir->hdir = NULL; ++ bindex = -1; ++ brid = au_xino_brid(sb); ++ if (brid >= 0) ++ bindex = au_br_index(sb, brid); ++ if (bindex >= 0) { ++ ldir->hdir = au_hi(sb->s_root->d_inode, bindex); ++ au_hn_imtx_lock_nested(ldir->hdir, AuLsc_I_PARENT); ++ } else { ++ ldir->parent = dget_parent(xino->f_dentry); ++ ldir->mtx = &ldir->parent->d_inode->i_mutex; ++ mutex_lock_nested(ldir->mtx, AuLsc_I_PARENT); ++ } ++} ++ ++static void au_xino_unlock_dir(struct au_xino_lock_dir *ldir) ++{ ++ if (ldir->hdir) ++ au_hn_imtx_unlock(ldir->hdir); ++ else { ++ mutex_unlock(ldir->mtx); ++ dput(ldir->parent); ++ } ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* trucate xino files asynchronously */ ++ ++int au_xino_trunc(struct super_block *sb, aufs_bindex_t bindex) ++{ ++ int err; ++ unsigned long jiffy; ++ blkcnt_t blocks; ++ aufs_bindex_t bi, bend; ++ struct kstatfs *st; ++ struct au_branch *br; ++ struct file *new_xino, *file; ++ struct super_block *h_sb; ++ struct au_xino_lock_dir ldir; ++ ++ err = -ENOMEM; ++ st = kmalloc(sizeof(*st), GFP_NOFS); ++ if (unlikely(!st)) ++ goto out; ++ ++ err = -EINVAL; ++ bend = au_sbend(sb); ++ if (unlikely(bindex < 0 || bend < bindex)) ++ goto out_st; ++ br = au_sbr(sb, bindex); ++ file = br->br_xino.xi_file; ++ if (!file) ++ goto out_st; ++ ++ err = vfs_statfs(&file->f_path, st); ++ if (unlikely(err)) ++ AuErr1("statfs err %d, ignored\n", err); ++ jiffy = jiffies; ++ blocks = file_inode(file)->i_blocks; ++ pr_info("begin truncating xino(b%d), ib%llu, %llu/%llu free blks\n", ++ bindex, (u64)blocks, st->f_bfree, st->f_blocks); ++ ++ au_xino_lock_dir(sb, file, &ldir); ++ /* mnt_want_write() is unnecessary here */ ++ new_xino = au_xino_create2(file, file); ++ au_xino_unlock_dir(&ldir); ++ err = PTR_ERR(new_xino); ++ if (IS_ERR(new_xino)) { ++ pr_err("err %d, ignored\n", err); ++ goto out_st; ++ } ++ err = 0; ++ fput(file); ++ br->br_xino.xi_file = new_xino; ++ ++ h_sb = au_br_sb(br); ++ for (bi = 0; bi <= bend; bi++) { ++ if (unlikely(bi == bindex)) ++ continue; ++ br = au_sbr(sb, bi); ++ if (au_br_sb(br) != h_sb) ++ continue; ++ ++ fput(br->br_xino.xi_file); ++ br->br_xino.xi_file = new_xino; ++ get_file(new_xino); ++ } ++ ++ err = vfs_statfs(&new_xino->f_path, st); ++ if (!err) { ++ pr_info("end truncating xino(b%d), ib%llu, %llu/%llu free blks\n", ++ bindex, (u64)file_inode(new_xino)->i_blocks, ++ st->f_bfree, st->f_blocks); ++ if (file_inode(new_xino)->i_blocks < blocks) ++ au_sbi(sb)->si_xino_jiffy = jiffy; ++ } else ++ AuErr1("statfs err %d, ignored\n", err); ++ ++out_st: ++ kfree(st); ++out: ++ return err; ++} ++ ++struct xino_do_trunc_args { ++ struct super_block *sb; ++ struct au_branch *br; ++}; ++ ++static void xino_do_trunc(void *_args) ++{ ++ struct xino_do_trunc_args *args = _args; ++ struct super_block *sb; ++ struct au_branch *br; ++ struct inode *dir; ++ int err; ++ aufs_bindex_t bindex; ++ ++ err = 0; ++ sb = args->sb; ++ dir = sb->s_root->d_inode; ++ br = args->br; ++ ++ si_noflush_write_lock(sb); ++ ii_read_lock_parent(dir); ++ bindex = au_br_index(sb, br->br_id); ++ err = au_xino_trunc(sb, bindex); ++ ii_read_unlock(dir); ++ if (unlikely(err)) ++ pr_warn("err b%d, (%d)\n", bindex, err); ++ atomic_dec(&br->br_xino_running); ++ atomic_dec(&br->br_count); ++ si_write_unlock(sb); ++ au_nwt_done(&au_sbi(sb)->si_nowait); ++ kfree(args); ++} ++ ++static int xino_trunc_test(struct super_block *sb, struct au_branch *br) ++{ ++ int err; ++ struct kstatfs st; ++ struct au_sbinfo *sbinfo; ++ ++ /* todo: si_xino_expire and the ratio should be customizable */ ++ sbinfo = au_sbi(sb); ++ if (time_before(jiffies, ++ sbinfo->si_xino_jiffy + sbinfo->si_xino_expire)) ++ return 0; ++ ++ /* truncation border */ ++ err = vfs_statfs(&br->br_xino.xi_file->f_path, &st); ++ if (unlikely(err)) { ++ AuErr1("statfs err %d, ignored\n", err); ++ return 0; ++ } ++ if (div64_u64(st.f_bfree * 100, st.f_blocks) >= AUFS_XINO_DEF_TRUNC) ++ return 0; ++ ++ return 1; ++} ++ ++static void xino_try_trunc(struct super_block *sb, struct au_branch *br) ++{ ++ struct xino_do_trunc_args *args; ++ int wkq_err; ++ ++ if (!xino_trunc_test(sb, br)) ++ return; ++ ++ if (atomic_inc_return(&br->br_xino_running) > 1) ++ goto out; ++ ++ /* lock and kfree() will be called in trunc_xino() */ ++ args = kmalloc(sizeof(*args), GFP_NOFS); ++ if (unlikely(!args)) { ++ AuErr1("no memory\n"); ++ goto out_args; ++ } ++ ++ atomic_inc(&br->br_count); ++ args->sb = sb; ++ args->br = br; ++ wkq_err = au_wkq_nowait(xino_do_trunc, args, sb, /*flags*/0); ++ if (!wkq_err) ++ return; /* success */ ++ ++ pr_err("wkq %d\n", wkq_err); ++ atomic_dec(&br->br_count); ++ ++out_args: ++ kfree(args); ++out: ++ atomic_dec(&br->br_xino_running); ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++static int au_xino_do_write(au_writef_t write, struct file *file, ++ ino_t h_ino, ino_t ino) ++{ ++ loff_t pos; ++ ssize_t sz; ++ ++ pos = h_ino; ++ if (unlikely(au_loff_max / sizeof(ino) - 1 < pos)) { ++ AuIOErr1("too large hi%lu\n", (unsigned long)h_ino); ++ return -EFBIG; ++ } ++ pos *= sizeof(ino); ++ sz = xino_fwrite(write, file, &ino, sizeof(ino), &pos); ++ if (sz == sizeof(ino)) ++ return 0; /* success */ ++ ++ AuIOErr("write failed (%zd)\n", sz); ++ return -EIO; ++} ++ ++/* ++ * write @ino to the xinofile for the specified branch{@sb, @bindex} ++ * at the position of @h_ino. ++ * even if @ino is zero, it is written to the xinofile and means no entry. ++ * if the size of the xino file on a specific filesystem exceeds the watermark, ++ * try truncating it. ++ */ ++int au_xino_write(struct super_block *sb, aufs_bindex_t bindex, ino_t h_ino, ++ ino_t ino) ++{ ++ int err; ++ unsigned int mnt_flags; ++ struct au_branch *br; ++ ++ BUILD_BUG_ON(sizeof(long long) != sizeof(au_loff_max) ++ || ((loff_t)-1) > 0); ++ SiMustAnyLock(sb); ++ ++ mnt_flags = au_mntflags(sb); ++ if (!au_opt_test(mnt_flags, XINO)) ++ return 0; ++ ++ br = au_sbr(sb, bindex); ++ err = au_xino_do_write(au_sbi(sb)->si_xwrite, br->br_xino.xi_file, ++ h_ino, ino); ++ if (!err) { ++ if (au_opt_test(mnt_flags, TRUNC_XINO) ++ && au_test_fs_trunc_xino(au_br_sb(br))) ++ xino_try_trunc(sb, br); ++ return 0; /* success */ ++ } ++ ++ AuIOErr("write failed (%d)\n", err); ++ return -EIO; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* aufs inode number bitmap */ ++ ++static const int page_bits = (int)PAGE_SIZE * BITS_PER_BYTE; ++static ino_t xib_calc_ino(unsigned long pindex, int bit) ++{ ++ ino_t ino; ++ ++ AuDebugOn(bit < 0 || page_bits <= bit); ++ ino = AUFS_FIRST_INO + pindex * page_bits + bit; ++ return ino; ++} ++ ++static void xib_calc_bit(ino_t ino, unsigned long *pindex, int *bit) ++{ ++ AuDebugOn(ino < AUFS_FIRST_INO); ++ ino -= AUFS_FIRST_INO; ++ *pindex = ino / page_bits; ++ *bit = ino % page_bits; ++} ++ ++static int xib_pindex(struct super_block *sb, unsigned long pindex) ++{ ++ int err; ++ loff_t pos; ++ ssize_t sz; ++ struct au_sbinfo *sbinfo; ++ struct file *xib; ++ unsigned long *p; ++ ++ sbinfo = au_sbi(sb); ++ MtxMustLock(&sbinfo->si_xib_mtx); ++ AuDebugOn(pindex > ULONG_MAX / PAGE_SIZE ++ || !au_opt_test(sbinfo->si_mntflags, XINO)); ++ ++ if (pindex == sbinfo->si_xib_last_pindex) ++ return 0; ++ ++ xib = sbinfo->si_xib; ++ p = sbinfo->si_xib_buf; ++ pos = sbinfo->si_xib_last_pindex; ++ pos *= PAGE_SIZE; ++ sz = xino_fwrite(sbinfo->si_xwrite, xib, p, PAGE_SIZE, &pos); ++ if (unlikely(sz != PAGE_SIZE)) ++ goto out; ++ ++ pos = pindex; ++ pos *= PAGE_SIZE; ++ if (vfsub_f_size_read(xib) >= pos + PAGE_SIZE) ++ sz = xino_fread(sbinfo->si_xread, xib, p, PAGE_SIZE, &pos); ++ else { ++ memset(p, 0, PAGE_SIZE); ++ sz = xino_fwrite(sbinfo->si_xwrite, xib, p, PAGE_SIZE, &pos); ++ } ++ if (sz == PAGE_SIZE) { ++ sbinfo->si_xib_last_pindex = pindex; ++ return 0; /* success */ ++ } ++ ++out: ++ AuIOErr1("write failed (%zd)\n", sz); ++ err = sz; ++ if (sz >= 0) ++ err = -EIO; ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++static void au_xib_clear_bit(struct inode *inode) ++{ ++ int err, bit; ++ unsigned long pindex; ++ struct super_block *sb; ++ struct au_sbinfo *sbinfo; ++ ++ AuDebugOn(inode->i_nlink); ++ ++ sb = inode->i_sb; ++ xib_calc_bit(inode->i_ino, &pindex, &bit); ++ AuDebugOn(page_bits <= bit); ++ sbinfo = au_sbi(sb); ++ mutex_lock(&sbinfo->si_xib_mtx); ++ err = xib_pindex(sb, pindex); ++ if (!err) { ++ clear_bit(bit, sbinfo->si_xib_buf); ++ sbinfo->si_xib_next_bit = bit; ++ } ++ mutex_unlock(&sbinfo->si_xib_mtx); ++} ++ ++/* for s_op->delete_inode() */ ++void au_xino_delete_inode(struct inode *inode, const int unlinked) ++{ ++ int err; ++ unsigned int mnt_flags; ++ aufs_bindex_t bindex, bend, bi; ++ unsigned char try_trunc; ++ struct au_iinfo *iinfo; ++ struct super_block *sb; ++ struct au_hinode *hi; ++ struct inode *h_inode; ++ struct au_branch *br; ++ au_writef_t xwrite; ++ ++ sb = inode->i_sb; ++ mnt_flags = au_mntflags(sb); ++ if (!au_opt_test(mnt_flags, XINO) ++ || inode->i_ino == AUFS_ROOT_INO) ++ return; ++ ++ if (unlinked) { ++ au_xigen_inc(inode); ++ au_xib_clear_bit(inode); ++ } ++ ++ iinfo = au_ii(inode); ++ if (!iinfo) ++ return; ++ ++ bindex = iinfo->ii_bstart; ++ if (bindex < 0) ++ return; ++ ++ xwrite = au_sbi(sb)->si_xwrite; ++ try_trunc = !!au_opt_test(mnt_flags, TRUNC_XINO); ++ hi = iinfo->ii_hinode + bindex; ++ bend = iinfo->ii_bend; ++ for (; bindex <= bend; bindex++, hi++) { ++ h_inode = hi->hi_inode; ++ if (!h_inode ++ || (!unlinked && h_inode->i_nlink)) ++ continue; ++ ++ /* inode may not be revalidated */ ++ bi = au_br_index(sb, hi->hi_id); ++ if (bi < 0) ++ continue; ++ ++ br = au_sbr(sb, bi); ++ err = au_xino_do_write(xwrite, br->br_xino.xi_file, ++ h_inode->i_ino, /*ino*/0); ++ if (!err && try_trunc ++ && au_test_fs_trunc_xino(au_br_sb(br))) ++ xino_try_trunc(sb, br); ++ } ++} ++ ++/* get an unused inode number from bitmap */ ++ino_t au_xino_new_ino(struct super_block *sb) ++{ ++ ino_t ino; ++ unsigned long *p, pindex, ul, pend; ++ struct au_sbinfo *sbinfo; ++ struct file *file; ++ int free_bit, err; ++ ++ if (!au_opt_test(au_mntflags(sb), XINO)) ++ return iunique(sb, AUFS_FIRST_INO); ++ ++ sbinfo = au_sbi(sb); ++ mutex_lock(&sbinfo->si_xib_mtx); ++ p = sbinfo->si_xib_buf; ++ free_bit = sbinfo->si_xib_next_bit; ++ if (free_bit < page_bits && !test_bit(free_bit, p)) ++ goto out; /* success */ ++ free_bit = find_first_zero_bit(p, page_bits); ++ if (free_bit < page_bits) ++ goto out; /* success */ ++ ++ pindex = sbinfo->si_xib_last_pindex; ++ for (ul = pindex - 1; ul < ULONG_MAX; ul--) { ++ err = xib_pindex(sb, ul); ++ if (unlikely(err)) ++ goto out_err; ++ free_bit = find_first_zero_bit(p, page_bits); ++ if (free_bit < page_bits) ++ goto out; /* success */ ++ } ++ ++ file = sbinfo->si_xib; ++ pend = vfsub_f_size_read(file) / PAGE_SIZE; ++ for (ul = pindex + 1; ul <= pend; ul++) { ++ err = xib_pindex(sb, ul); ++ if (unlikely(err)) ++ goto out_err; ++ free_bit = find_first_zero_bit(p, page_bits); ++ if (free_bit < page_bits) ++ goto out; /* success */ ++ } ++ BUG(); ++ ++out: ++ set_bit(free_bit, p); ++ sbinfo->si_xib_next_bit = free_bit + 1; ++ pindex = sbinfo->si_xib_last_pindex; ++ mutex_unlock(&sbinfo->si_xib_mtx); ++ ino = xib_calc_ino(pindex, free_bit); ++ AuDbg("i%lu\n", (unsigned long)ino); ++ return ino; ++out_err: ++ mutex_unlock(&sbinfo->si_xib_mtx); ++ AuDbg("i0\n"); ++ return 0; ++} ++ ++/* ++ * read @ino from xinofile for the specified branch{@sb, @bindex} ++ * at the position of @h_ino. ++ * if @ino does not exist and @do_new is true, get new one. ++ */ ++int au_xino_read(struct super_block *sb, aufs_bindex_t bindex, ino_t h_ino, ++ ino_t *ino) ++{ ++ int err; ++ ssize_t sz; ++ loff_t pos; ++ struct file *file; ++ struct au_sbinfo *sbinfo; ++ ++ *ino = 0; ++ if (!au_opt_test(au_mntflags(sb), XINO)) ++ return 0; /* no xino */ ++ ++ err = 0; ++ sbinfo = au_sbi(sb); ++ pos = h_ino; ++ if (unlikely(au_loff_max / sizeof(*ino) - 1 < pos)) { ++ AuIOErr1("too large hi%lu\n", (unsigned long)h_ino); ++ return -EFBIG; ++ } ++ pos *= sizeof(*ino); ++ ++ file = au_sbr(sb, bindex)->br_xino.xi_file; ++ if (vfsub_f_size_read(file) < pos + sizeof(*ino)) ++ return 0; /* no ino */ ++ ++ sz = xino_fread(sbinfo->si_xread, file, ino, sizeof(*ino), &pos); ++ if (sz == sizeof(*ino)) ++ return 0; /* success */ ++ ++ err = sz; ++ if (unlikely(sz >= 0)) { ++ err = -EIO; ++ AuIOErr("xino read error (%zd)\n", sz); ++ } ++ ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* create and set a new xino file */ ++ ++struct file *au_xino_create(struct super_block *sb, char *fname, int silent) ++{ ++ struct file *file; ++ struct dentry *h_parent, *d; ++ struct inode *h_dir, *inode; ++ int err; ++ ++ /* ++ * at mount-time, and the xino file is the default path, ++ * hnotify is disabled so we have no notify events to ignore. ++ * when a user specified the xino, we cannot get au_hdir to be ignored. ++ */ ++ file = vfsub_filp_open(fname, O_RDWR | O_CREAT | O_EXCL | O_LARGEFILE ++ /* | __FMODE_NONOTIFY */, ++ S_IRUGO | S_IWUGO); ++ if (IS_ERR(file)) { ++ if (!silent) ++ pr_err("open %s(%ld)\n", fname, PTR_ERR(file)); ++ return file; ++ } ++ ++ /* keep file count */ ++ err = 0; ++ inode = file_inode(file); ++ h_parent = dget_parent(file->f_dentry); ++ h_dir = h_parent->d_inode; ++ mutex_lock_nested(&h_dir->i_mutex, AuLsc_I_PARENT); ++ /* mnt_want_write() is unnecessary here */ ++ /* no delegation since it is just created */ ++ if (inode->i_nlink) ++ err = vfsub_unlink(h_dir, &file->f_path, /*delegated*/NULL, ++ /*force*/0); ++ mutex_unlock(&h_dir->i_mutex); ++ dput(h_parent); ++ if (unlikely(err)) { ++ if (!silent) ++ pr_err("unlink %s(%d)\n", fname, err); ++ goto out; ++ } ++ ++ err = -EINVAL; ++ d = file->f_dentry; ++ if (unlikely(sb == d->d_sb)) { ++ if (!silent) ++ pr_err("%s must be outside\n", fname); ++ goto out; ++ } ++ if (unlikely(au_test_fs_bad_xino(d->d_sb))) { ++ if (!silent) ++ pr_err("xino doesn't support %s(%s)\n", ++ fname, au_sbtype(d->d_sb)); ++ goto out; ++ } ++ return file; /* success */ ++ ++out: ++ fput(file); ++ file = ERR_PTR(err); ++ return file; ++} ++ ++/* ++ * find another branch who is on the same filesystem of the specified ++ * branch{@btgt}. search until @bend. ++ */ ++static int is_sb_shared(struct super_block *sb, aufs_bindex_t btgt, ++ aufs_bindex_t bend) ++{ ++ aufs_bindex_t bindex; ++ struct super_block *tgt_sb = au_sbr_sb(sb, btgt); ++ ++ for (bindex = 0; bindex < btgt; bindex++) ++ if (unlikely(tgt_sb == au_sbr_sb(sb, bindex))) ++ return bindex; ++ for (bindex++; bindex <= bend; bindex++) ++ if (unlikely(tgt_sb == au_sbr_sb(sb, bindex))) ++ return bindex; ++ return -1; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* ++ * initialize the xinofile for the specified branch @br ++ * at the place/path where @base_file indicates. ++ * test whether another branch is on the same filesystem or not, ++ * if @do_test is true. ++ */ ++int au_xino_br(struct super_block *sb, struct au_branch *br, ino_t h_ino, ++ struct file *base_file, int do_test) ++{ ++ int err; ++ ino_t ino; ++ aufs_bindex_t bend, bindex; ++ struct au_branch *shared_br, *b; ++ struct file *file; ++ struct super_block *tgt_sb; ++ ++ shared_br = NULL; ++ bend = au_sbend(sb); ++ if (do_test) { ++ tgt_sb = au_br_sb(br); ++ for (bindex = 0; bindex <= bend; bindex++) { ++ b = au_sbr(sb, bindex); ++ if (tgt_sb == au_br_sb(b)) { ++ shared_br = b; ++ break; ++ } ++ } ++ } ++ ++ if (!shared_br || !shared_br->br_xino.xi_file) { ++ struct au_xino_lock_dir ldir; ++ ++ au_xino_lock_dir(sb, base_file, &ldir); ++ /* mnt_want_write() is unnecessary here */ ++ file = au_xino_create2(base_file, NULL); ++ au_xino_unlock_dir(&ldir); ++ err = PTR_ERR(file); ++ if (IS_ERR(file)) ++ goto out; ++ br->br_xino.xi_file = file; ++ } else { ++ br->br_xino.xi_file = shared_br->br_xino.xi_file; ++ get_file(br->br_xino.xi_file); ++ } ++ ++ ino = AUFS_ROOT_INO; ++ err = au_xino_do_write(au_sbi(sb)->si_xwrite, br->br_xino.xi_file, ++ h_ino, ino); ++ if (unlikely(err)) { ++ fput(br->br_xino.xi_file); ++ br->br_xino.xi_file = NULL; ++ } ++ ++out: ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* trucate a xino bitmap file */ ++ ++/* todo: slow */ ++static int do_xib_restore(struct super_block *sb, struct file *file, void *page) ++{ ++ int err, bit; ++ ssize_t sz; ++ unsigned long pindex; ++ loff_t pos, pend; ++ struct au_sbinfo *sbinfo; ++ au_readf_t func; ++ ino_t *ino; ++ unsigned long *p; ++ ++ err = 0; ++ sbinfo = au_sbi(sb); ++ MtxMustLock(&sbinfo->si_xib_mtx); ++ p = sbinfo->si_xib_buf; ++ func = sbinfo->si_xread; ++ pend = vfsub_f_size_read(file); ++ pos = 0; ++ while (pos < pend) { ++ sz = xino_fread(func, file, page, PAGE_SIZE, &pos); ++ err = sz; ++ if (unlikely(sz <= 0)) ++ goto out; ++ ++ err = 0; ++ for (ino = page; sz > 0; ino++, sz -= sizeof(ino)) { ++ if (unlikely(*ino < AUFS_FIRST_INO)) ++ continue; ++ ++ xib_calc_bit(*ino, &pindex, &bit); ++ AuDebugOn(page_bits <= bit); ++ err = xib_pindex(sb, pindex); ++ if (!err) ++ set_bit(bit, p); ++ else ++ goto out; ++ } ++ } ++ ++out: ++ return err; ++} ++ ++static int xib_restore(struct super_block *sb) ++{ ++ int err; ++ aufs_bindex_t bindex, bend; ++ void *page; ++ ++ err = -ENOMEM; ++ page = (void *)__get_free_page(GFP_NOFS); ++ if (unlikely(!page)) ++ goto out; ++ ++ err = 0; ++ bend = au_sbend(sb); ++ for (bindex = 0; !err && bindex <= bend; bindex++) ++ if (!bindex || is_sb_shared(sb, bindex, bindex - 1) < 0) ++ err = do_xib_restore ++ (sb, au_sbr(sb, bindex)->br_xino.xi_file, page); ++ else ++ AuDbg("b%d\n", bindex); ++ free_page((unsigned long)page); ++ ++out: ++ return err; ++} ++ ++int au_xib_trunc(struct super_block *sb) ++{ ++ int err; ++ ssize_t sz; ++ loff_t pos; ++ struct au_xino_lock_dir ldir; ++ struct au_sbinfo *sbinfo; ++ unsigned long *p; ++ struct file *file; ++ ++ SiMustWriteLock(sb); ++ ++ err = 0; ++ sbinfo = au_sbi(sb); ++ if (!au_opt_test(sbinfo->si_mntflags, XINO)) ++ goto out; ++ ++ file = sbinfo->si_xib; ++ if (vfsub_f_size_read(file) <= PAGE_SIZE) ++ goto out; ++ ++ au_xino_lock_dir(sb, file, &ldir); ++ /* mnt_want_write() is unnecessary here */ ++ file = au_xino_create2(sbinfo->si_xib, NULL); ++ au_xino_unlock_dir(&ldir); ++ err = PTR_ERR(file); ++ if (IS_ERR(file)) ++ goto out; ++ fput(sbinfo->si_xib); ++ sbinfo->si_xib = file; ++ ++ p = sbinfo->si_xib_buf; ++ memset(p, 0, PAGE_SIZE); ++ pos = 0; ++ sz = xino_fwrite(sbinfo->si_xwrite, sbinfo->si_xib, p, PAGE_SIZE, &pos); ++ if (unlikely(sz != PAGE_SIZE)) { ++ err = sz; ++ AuIOErr("err %d\n", err); ++ if (sz >= 0) ++ err = -EIO; ++ goto out; ++ } ++ ++ mutex_lock(&sbinfo->si_xib_mtx); ++ /* mnt_want_write() is unnecessary here */ ++ err = xib_restore(sb); ++ mutex_unlock(&sbinfo->si_xib_mtx); ++ ++out: ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* ++ * xino mount option handlers ++ */ ++static au_readf_t find_readf(struct file *h_file) ++{ ++ const struct file_operations *fop = h_file->f_op; ++ ++ if (fop->read) ++ return fop->read; ++ if (fop->aio_read) ++ return do_sync_read; ++ if (fop->read_iter) ++ return new_sync_read; ++ return ERR_PTR(-ENOSYS); ++} ++ ++static au_writef_t find_writef(struct file *h_file) ++{ ++ const struct file_operations *fop = h_file->f_op; ++ ++ if (fop->write) ++ return fop->write; ++ if (fop->aio_write) ++ return do_sync_write; ++ if (fop->write_iter) ++ return new_sync_write; ++ return ERR_PTR(-ENOSYS); ++} ++ ++/* xino bitmap */ ++static void xino_clear_xib(struct super_block *sb) ++{ ++ struct au_sbinfo *sbinfo; ++ ++ SiMustWriteLock(sb); ++ ++ sbinfo = au_sbi(sb); ++ sbinfo->si_xread = NULL; ++ sbinfo->si_xwrite = NULL; ++ if (sbinfo->si_xib) ++ fput(sbinfo->si_xib); ++ sbinfo->si_xib = NULL; ++ free_page((unsigned long)sbinfo->si_xib_buf); ++ sbinfo->si_xib_buf = NULL; ++} ++ ++static int au_xino_set_xib(struct super_block *sb, struct file *base) ++{ ++ int err; ++ loff_t pos; ++ struct au_sbinfo *sbinfo; ++ struct file *file; ++ ++ SiMustWriteLock(sb); ++ ++ sbinfo = au_sbi(sb); ++ file = au_xino_create2(base, sbinfo->si_xib); ++ err = PTR_ERR(file); ++ if (IS_ERR(file)) ++ goto out; ++ if (sbinfo->si_xib) ++ fput(sbinfo->si_xib); ++ sbinfo->si_xib = file; ++ sbinfo->si_xread = find_readf(file); ++ sbinfo->si_xwrite = find_writef(file); ++ ++ err = -ENOMEM; ++ if (!sbinfo->si_xib_buf) ++ sbinfo->si_xib_buf = (void *)get_zeroed_page(GFP_NOFS); ++ if (unlikely(!sbinfo->si_xib_buf)) ++ goto out_unset; ++ ++ sbinfo->si_xib_last_pindex = 0; ++ sbinfo->si_xib_next_bit = 0; ++ if (vfsub_f_size_read(file) < PAGE_SIZE) { ++ pos = 0; ++ err = xino_fwrite(sbinfo->si_xwrite, file, sbinfo->si_xib_buf, ++ PAGE_SIZE, &pos); ++ if (unlikely(err != PAGE_SIZE)) ++ goto out_free; ++ } ++ err = 0; ++ goto out; /* success */ ++ ++out_free: ++ free_page((unsigned long)sbinfo->si_xib_buf); ++ sbinfo->si_xib_buf = NULL; ++ if (err >= 0) ++ err = -EIO; ++out_unset: ++ fput(sbinfo->si_xib); ++ sbinfo->si_xib = NULL; ++ sbinfo->si_xread = NULL; ++ sbinfo->si_xwrite = NULL; ++out: ++ return err; ++} ++ ++/* xino for each branch */ ++static void xino_clear_br(struct super_block *sb) ++{ ++ aufs_bindex_t bindex, bend; ++ struct au_branch *br; ++ ++ bend = au_sbend(sb); ++ for (bindex = 0; bindex <= bend; bindex++) { ++ br = au_sbr(sb, bindex); ++ if (!br || !br->br_xino.xi_file) ++ continue; ++ ++ fput(br->br_xino.xi_file); ++ br->br_xino.xi_file = NULL; ++ } ++} ++ ++static int au_xino_set_br(struct super_block *sb, struct file *base) ++{ ++ int err; ++ ino_t ino; ++ aufs_bindex_t bindex, bend, bshared; ++ struct { ++ struct file *old, *new; ++ } *fpair, *p; ++ struct au_branch *br; ++ struct inode *inode; ++ au_writef_t writef; ++ ++ SiMustWriteLock(sb); ++ ++ err = -ENOMEM; ++ bend = au_sbend(sb); ++ fpair = kcalloc(bend + 1, sizeof(*fpair), GFP_NOFS); ++ if (unlikely(!fpair)) ++ goto out; ++ ++ inode = sb->s_root->d_inode; ++ ino = AUFS_ROOT_INO; ++ writef = au_sbi(sb)->si_xwrite; ++ for (bindex = 0, p = fpair; bindex <= bend; bindex++, p++) { ++ br = au_sbr(sb, bindex); ++ bshared = is_sb_shared(sb, bindex, bindex - 1); ++ if (bshared >= 0) { ++ /* shared xino */ ++ *p = fpair[bshared]; ++ get_file(p->new); ++ } ++ ++ if (!p->new) { ++ /* new xino */ ++ p->old = br->br_xino.xi_file; ++ p->new = au_xino_create2(base, br->br_xino.xi_file); ++ err = PTR_ERR(p->new); ++ if (IS_ERR(p->new)) { ++ p->new = NULL; ++ goto out_pair; ++ } ++ } ++ ++ err = au_xino_do_write(writef, p->new, ++ au_h_iptr(inode, bindex)->i_ino, ino); ++ if (unlikely(err)) ++ goto out_pair; ++ } ++ ++ for (bindex = 0, p = fpair; bindex <= bend; bindex++, p++) { ++ br = au_sbr(sb, bindex); ++ if (br->br_xino.xi_file) ++ fput(br->br_xino.xi_file); ++ get_file(p->new); ++ br->br_xino.xi_file = p->new; ++ } ++ ++out_pair: ++ for (bindex = 0, p = fpair; bindex <= bend; bindex++, p++) ++ if (p->new) ++ fput(p->new); ++ else ++ break; ++ kfree(fpair); ++out: ++ return err; ++} ++ ++void au_xino_clr(struct super_block *sb) ++{ ++ struct au_sbinfo *sbinfo; ++ ++ au_xigen_clr(sb); ++ xino_clear_xib(sb); ++ xino_clear_br(sb); ++ sbinfo = au_sbi(sb); ++ /* lvalue, do not call au_mntflags() */ ++ au_opt_clr(sbinfo->si_mntflags, XINO); ++} ++ ++int au_xino_set(struct super_block *sb, struct au_opt_xino *xino, int remount) ++{ ++ int err, skip; ++ struct dentry *parent, *cur_parent; ++ struct qstr *dname, *cur_name; ++ struct file *cur_xino; ++ struct inode *dir; ++ struct au_sbinfo *sbinfo; ++ ++ SiMustWriteLock(sb); ++ ++ err = 0; ++ sbinfo = au_sbi(sb); ++ parent = dget_parent(xino->file->f_dentry); ++ if (remount) { ++ skip = 0; ++ dname = &xino->file->f_dentry->d_name; ++ cur_xino = sbinfo->si_xib; ++ if (cur_xino) { ++ cur_parent = dget_parent(cur_xino->f_dentry); ++ cur_name = &cur_xino->f_dentry->d_name; ++ skip = (cur_parent == parent ++ && au_qstreq(dname, cur_name)); ++ dput(cur_parent); ++ } ++ if (skip) ++ goto out; ++ } ++ ++ au_opt_set(sbinfo->si_mntflags, XINO); ++ dir = parent->d_inode; ++ mutex_lock_nested(&dir->i_mutex, AuLsc_I_PARENT); ++ /* mnt_want_write() is unnecessary here */ ++ err = au_xino_set_xib(sb, xino->file); ++ if (!err) ++ err = au_xigen_set(sb, xino->file); ++ if (!err) ++ err = au_xino_set_br(sb, xino->file); ++ mutex_unlock(&dir->i_mutex); ++ if (!err) ++ goto out; /* success */ ++ ++ /* reset all */ ++ AuIOErr("failed creating xino(%d).\n", err); ++ au_xigen_clr(sb); ++ xino_clear_xib(sb); ++ ++out: ++ dput(parent); ++ return err; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* ++ * create a xinofile at the default place/path. ++ */ ++struct file *au_xino_def(struct super_block *sb) ++{ ++ struct file *file; ++ char *page, *p; ++ struct au_branch *br; ++ struct super_block *h_sb; ++ struct path path; ++ aufs_bindex_t bend, bindex, bwr; ++ ++ br = NULL; ++ bend = au_sbend(sb); ++ bwr = -1; ++ for (bindex = 0; bindex <= bend; bindex++) { ++ br = au_sbr(sb, bindex); ++ if (au_br_writable(br->br_perm) ++ && !au_test_fs_bad_xino(au_br_sb(br))) { ++ bwr = bindex; ++ break; ++ } ++ } ++ ++ if (bwr >= 0) { ++ file = ERR_PTR(-ENOMEM); ++ page = (void *)__get_free_page(GFP_NOFS); ++ if (unlikely(!page)) ++ goto out; ++ path.mnt = au_br_mnt(br); ++ path.dentry = au_h_dptr(sb->s_root, bwr); ++ p = d_path(&path, page, PATH_MAX - sizeof(AUFS_XINO_FNAME)); ++ file = (void *)p; ++ if (!IS_ERR(p)) { ++ strcat(p, "/" AUFS_XINO_FNAME); ++ AuDbg("%s\n", p); ++ file = au_xino_create(sb, p, /*silent*/0); ++ if (!IS_ERR(file)) ++ au_xino_brid_set(sb, br->br_id); ++ } ++ free_page((unsigned long)page); ++ } else { ++ file = au_xino_create(sb, AUFS_XINO_DEFPATH, /*silent*/0); ++ if (IS_ERR(file)) ++ goto out; ++ h_sb = file->f_dentry->d_sb; ++ if (unlikely(au_test_fs_bad_xino(h_sb))) { ++ pr_err("xino doesn't support %s(%s)\n", ++ AUFS_XINO_DEFPATH, au_sbtype(h_sb)); ++ fput(file); ++ file = ERR_PTR(-EINVAL); ++ } ++ if (!IS_ERR(file)) ++ au_xino_brid_set(sb, -1); ++ } ++ ++out: ++ return file; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++int au_xino_path(struct seq_file *seq, struct file *file) ++{ ++ int err; ++ ++ err = au_seq_path(seq, &file->f_path); ++ if (unlikely(err)) ++ goto out; ++ ++#define Deleted "\\040(deleted)" ++ seq->count -= sizeof(Deleted) - 1; ++ AuDebugOn(memcmp(seq->buf + seq->count, Deleted, ++ sizeof(Deleted) - 1)); ++#undef Deleted ++ ++out: ++ return err; ++} +diff --git a/fs/buffer.c b/fs/buffer.c +index 20805db..363569f 100644 +--- a/fs/buffer.c ++++ b/fs/buffer.c +@@ -2450,7 +2450,7 @@ int block_page_mkwrite(struct vm_area_struct *vma, struct vm_fault *vmf, + * Update file times before taking page lock. We may end up failing the + * fault so this update may be superfluous but who really cares... + */ +- file_update_time(vma->vm_file); ++ vma_file_update_time(vma); + + ret = __block_page_mkwrite(vma, vmf, get_block); + sb_end_pagefault(sb); +diff --git a/fs/dcache.c b/fs/dcache.c +index d25f8fd..857990a 100644 +--- a/fs/dcache.c ++++ b/fs/dcache.c +@@ -1022,7 +1022,7 @@ enum d_walk_ret { + * + * The @enter() and @finish() callbacks are called with d_lock held. + */ +-static void d_walk(struct dentry *parent, void *data, ++void d_walk(struct dentry *parent, void *data, + enum d_walk_ret (*enter)(void *, struct dentry *), + void (*finish)(void *)) + { +diff --git a/fs/fcntl.c b/fs/fcntl.c +index 99d440a..de1a407 100644 +--- a/fs/fcntl.c ++++ b/fs/fcntl.c +@@ -29,7 +29,7 @@ + + #define SETFL_MASK (O_APPEND | O_NONBLOCK | O_NDELAY | O_DIRECT | O_NOATIME) + +-static int setfl(int fd, struct file * filp, unsigned long arg) ++int setfl(int fd, struct file * filp, unsigned long arg) + { + struct inode * inode = file_inode(filp); + int error = 0; +@@ -59,6 +59,8 @@ static int setfl(int fd, struct file * filp, unsigned long arg) + + if (filp->f_op->check_flags) + error = filp->f_op->check_flags(arg); ++ if (!error && filp->f_op->setfl) ++ error = filp->f_op->setfl(filp, arg); + if (error) + return error; + +diff --git a/fs/inode.c b/fs/inode.c +index 56d1d2b..2998e86 100644 +--- a/fs/inode.c ++++ b/fs/inode.c +@@ -1497,7 +1497,7 @@ static int relatime_need_update(struct vfsmount *mnt, struct inode *inode, + * This does the actual work of updating an inodes time or version. Must have + * had called mnt_want_write() before calling this. + */ +-static int update_time(struct inode *inode, struct timespec *time, int flags) ++int update_time(struct inode *inode, struct timespec *time, int flags) + { + if (inode->i_op->update_time) + return inode->i_op->update_time(inode, time, flags); +diff --git a/fs/proc/base.c b/fs/proc/base.c +index 7dc3ea8..b368ad5 100644 +--- a/fs/proc/base.c ++++ b/fs/proc/base.c +@@ -1735,7 +1735,7 @@ static int proc_map_files_get_link(struct dentry *dentry, struct path *path) + down_read(&mm->mmap_sem); + vma = find_exact_vma(mm, vm_start, vm_end); + if (vma && vma->vm_file) { +- *path = vma->vm_file->f_path; ++ *path = vma_pr_or_file(vma)->f_path; + path_get(path); + rc = 0; + } +diff --git a/fs/proc/nommu.c b/fs/proc/nommu.c +index d4a3574..1397181 100644 +--- a/fs/proc/nommu.c ++++ b/fs/proc/nommu.c +@@ -45,7 +45,10 @@ static int nommu_region_show(struct seq_file *m, struct vm_region *region) + file = region->vm_file; + + if (file) { +- struct inode *inode = file_inode(region->vm_file); ++ struct inode *inode; ++ ++ file = vmr_pr_or_file(region); ++ inode = file_inode(file); + dev = inode->i_sb->s_dev; + ino = inode->i_ino; + } +diff --git a/fs/proc/task_mmu.c b/fs/proc/task_mmu.c +index 69aa378..426b962 100644 +--- a/fs/proc/task_mmu.c ++++ b/fs/proc/task_mmu.c +@@ -276,7 +276,10 @@ show_map_vma(struct seq_file *m, struct vm_area_struct *vma, int is_pid) + const char *name = NULL; + + if (file) { +- struct inode *inode = file_inode(vma->vm_file); ++ struct inode *inode; ++ ++ file = vma_pr_or_file(vma); ++ inode = file_inode(file); + dev = inode->i_sb->s_dev; + ino = inode->i_ino; + pgoff = ((loff_t)vma->vm_pgoff) << PAGE_SHIFT; +@@ -1447,7 +1450,7 @@ static int show_numa_map(struct seq_file *m, void *v, int is_pid) + struct proc_maps_private *proc_priv = &numa_priv->proc_maps; + struct vm_area_struct *vma = v; + struct numa_maps *md = &numa_priv->md; +- struct file *file = vma->vm_file; ++ struct file *file = vma_pr_or_file(vma); + struct mm_struct *mm = vma->vm_mm; + struct mm_walk walk = {}; + struct mempolicy *pol; +diff --git a/fs/proc/task_nommu.c b/fs/proc/task_nommu.c +index 599ec2e..1740207 100644 +--- a/fs/proc/task_nommu.c ++++ b/fs/proc/task_nommu.c +@@ -160,7 +160,10 @@ static int nommu_vma_show(struct seq_file *m, struct vm_area_struct *vma, + file = vma->vm_file; + + if (file) { +- struct inode *inode = file_inode(vma->vm_file); ++ struct inode *inode; ++ ++ file = vma_pr_or_file(vma); ++ inode = file_inode(file); + dev = inode->i_sb->s_dev; + ino = inode->i_ino; + pgoff = (loff_t)vma->vm_pgoff << PAGE_SHIFT; +diff --git a/fs/splice.c b/fs/splice.c +index 75c6058..619359a 100644 +--- a/fs/splice.c ++++ b/fs/splice.c +@@ -1114,8 +1114,8 @@ EXPORT_SYMBOL(generic_splice_sendpage); + /* + * Attempt to initiate a splice from pipe to file. + */ +-static long do_splice_from(struct pipe_inode_info *pipe, struct file *out, +- loff_t *ppos, size_t len, unsigned int flags) ++long do_splice_from(struct pipe_inode_info *pipe, struct file *out, ++ loff_t *ppos, size_t len, unsigned int flags) + { + ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, + loff_t *, size_t, unsigned int); +@@ -1131,9 +1131,9 @@ static long do_splice_from(struct pipe_inode_info *pipe, struct file *out, + /* + * Attempt to initiate a splice from a file to a pipe. + */ +-static long do_splice_to(struct file *in, loff_t *ppos, +- struct pipe_inode_info *pipe, size_t len, +- unsigned int flags) ++long do_splice_to(struct file *in, loff_t *ppos, ++ struct pipe_inode_info *pipe, size_t len, ++ unsigned int flags) + { + ssize_t (*splice_read)(struct file *, loff_t *, + struct pipe_inode_info *, size_t, unsigned int); +diff --git a/include/asm-generic/msi.h b/include/asm-generic/msi.h +new file mode 100644 +index 0000000..61c58d8 +--- /dev/null ++++ b/include/asm-generic/msi.h +@@ -0,0 +1,32 @@ ++#ifndef __ASM_GENERIC_MSI_H ++#define __ASM_GENERIC_MSI_H ++ ++#include ++ ++#ifndef NUM_MSI_ALLOC_SCRATCHPAD_REGS ++# define NUM_MSI_ALLOC_SCRATCHPAD_REGS 2 ++#endif ++ ++struct msi_desc; ++ ++/** ++ * struct msi_alloc_info - Default structure for MSI interrupt allocation. ++ * @desc: Pointer to msi descriptor ++ * @hwirq: Associated hw interrupt number in the domain ++ * @scratchpad: Storage for implementation specific scratch data ++ * ++ * Architectures can provide their own implementation by not including ++ * asm-generic/msi.h into their arch specific header file. ++ */ ++typedef struct msi_alloc_info { ++ struct msi_desc *desc; ++ irq_hw_number_t hwirq; ++ union { ++ unsigned long ul; ++ void *ptr; ++ } scratchpad[NUM_MSI_ALLOC_SCRATCHPAD_REGS]; ++} msi_alloc_info_t; ++ ++#define GENERIC_MSI_DOMAIN_OPS 1 ++ ++#endif +diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h +index aa70cbd..bee5d68 100644 +--- a/include/asm-generic/vmlinux.lds.h ++++ b/include/asm-generic/vmlinux.lds.h +@@ -164,6 +164,7 @@ + #define CLKSRC_OF_TABLES() OF_TABLE(CONFIG_CLKSRC_OF, clksrc) + #define IRQCHIP_OF_MATCH_TABLE() OF_TABLE(CONFIG_IRQCHIP, irqchip) + #define CLK_OF_TABLES() OF_TABLE(CONFIG_COMMON_CLK, clk) ++#define IOMMU_OF_TABLES() OF_TABLE(CONFIG_OF_IOMMU, iommu) + #define RESERVEDMEM_OF_TABLES() OF_TABLE(CONFIG_OF_RESERVED_MEM, reservedmem) + #define CPU_METHOD_OF_TABLES() OF_TABLE(CONFIG_SMP, cpu_method) + #define EARLYCON_OF_TABLES() OF_TABLE(CONFIG_SERIAL_EARLYCON, earlycon) +@@ -497,6 +498,7 @@ + CLK_OF_TABLES() \ + RESERVEDMEM_OF_TABLES() \ + CLKSRC_OF_TABLES() \ ++ IOMMU_OF_TABLES() \ + CPU_METHOD_OF_TABLES() \ + KERNEL_DTB() \ + IRQCHIP_OF_MATCH_TABLE() \ +diff --git a/include/linux/acpi.h b/include/linux/acpi.h +index 1c7eaa7..d017dbf 100644 +--- a/include/linux/acpi.h ++++ b/include/linux/acpi.h +@@ -27,6 +27,7 @@ + + #include + #include /* for struct resource */ ++#include + #include + #include + +@@ -290,11 +291,6 @@ unsigned long acpi_dev_irq_flags(u8 triggering, u8 polarity, u8 shareable); + bool acpi_dev_resource_interrupt(struct acpi_resource *ares, int index, + struct resource *res); + +-struct resource_list_entry { +- struct list_head node; +- struct resource res; +-}; +- + void acpi_dev_free_resource_list(struct list_head *list); + int acpi_dev_get_resources(struct acpi_device *adev, struct list_head *list, + int (*preproc)(struct acpi_resource *, void *), +diff --git a/include/linux/device.h b/include/linux/device.h +index ce1f216..941d97b 100644 +--- a/include/linux/device.h ++++ b/include/linux/device.h +@@ -690,6 +690,8 @@ struct acpi_dev_node { + * along with subsystem-level and driver-level callbacks. + * @pins: For device pin management. + * See Documentation/pinctrl.txt for details. ++ * @msi_list: Hosts MSI descriptors ++ * @msi_domain: The generic MSI domain this device is using. + * @numa_node: NUMA node this device is close to. + * @dma_mask: Dma mask (if dma'ble device). + * @coherent_dma_mask: Like dma_mask, but for alloc_coherent mapping as not all +@@ -750,9 +752,15 @@ struct device { + struct dev_pm_info power; + struct dev_pm_domain *pm_domain; + ++#ifdef CONFIG_GENERIC_MSI_IRQ_DOMAIN ++ struct irq_domain *msi_domain; ++#endif + #ifdef CONFIG_PINCTRL + struct dev_pin_info *pins; + #endif ++#ifdef CONFIG_GENERIC_MSI_IRQ ++ struct list_head msi_list; ++#endif + + #ifdef CONFIG_NUMA + int numa_node; /* NUMA node this device is close to */ +@@ -837,6 +845,22 @@ static inline void set_dev_node(struct device *dev, int node) + } + #endif + ++static inline struct irq_domain *dev_get_msi_domain(const struct device *dev) ++{ ++#ifdef CONFIG_GENERIC_MSI_IRQ_DOMAIN ++ return dev->msi_domain; ++#else ++ return NULL; ++#endif ++} ++ ++static inline void dev_set_msi_domain(struct device *dev, struct irq_domain *d) ++{ ++#ifdef CONFIG_GENERIC_MSI_IRQ_DOMAIN ++ dev->msi_domain = d; ++#endif ++} ++ + static inline void *dev_get_drvdata(const struct device *dev) + { + return dev->driver_data; +diff --git a/include/linux/dma-mapping.h b/include/linux/dma-mapping.h +index d5d3881..c3007cb 100644 +--- a/include/linux/dma-mapping.h ++++ b/include/linux/dma-mapping.h +@@ -129,11 +129,14 @@ static inline int dma_coerce_mask_and_coherent(struct device *dev, u64 mask) + + extern u64 dma_get_required_mask(struct device *dev); + +-#ifndef set_arch_dma_coherent_ops +-static inline int set_arch_dma_coherent_ops(struct device *dev) +-{ +- return 0; +-} ++#ifndef arch_setup_dma_ops ++static inline void arch_setup_dma_ops(struct device *dev, u64 dma_base, ++ u64 size, struct iommu_ops *iommu, ++ bool coherent) { } ++#endif ++ ++#ifndef arch_teardown_dma_ops ++static inline void arch_teardown_dma_ops(struct device *dev) { } + #endif + + static inline unsigned int dma_get_max_seg_size(struct device *dev) +diff --git a/include/linux/file.h b/include/linux/file.h +index 4d69123..62cffc0 100644 +--- a/include/linux/file.h ++++ b/include/linux/file.h +@@ -19,6 +19,7 @@ struct dentry; + struct path; + extern struct file *alloc_file(struct path *, fmode_t mode, + const struct file_operations *fop); ++extern struct file *get_empty_filp(void); + + static inline void fput_light(struct file *file, int fput_needed) + { +diff --git a/include/linux/fs.h b/include/linux/fs.h +index 6fd017e..c44d25d 100644 +--- a/include/linux/fs.h ++++ b/include/linux/fs.h +@@ -1149,6 +1149,7 @@ extern void fasync_free(struct fasync_struct *); + /* can be called from interrupts */ + extern void kill_fasync(struct fasync_struct **, int, int); + ++extern int setfl(int fd, struct file * filp, unsigned long arg); + extern void __f_setown(struct file *filp, struct pid *, enum pid_type, int force); + extern void f_setown(struct file *filp, unsigned long arg, int force); + extern void f_delown(struct file *filp); +@@ -1507,6 +1508,7 @@ struct file_operations { + ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); + unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long); + int (*check_flags)(int); ++ int (*setfl)(struct file *, unsigned long); + int (*flock) (struct file *, int, struct file_lock *); + ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int); + ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int); +@@ -2662,6 +2664,7 @@ extern int inode_change_ok(const struct inode *, struct iattr *); + extern int inode_newsize_ok(const struct inode *, loff_t offset); + extern void setattr_copy(struct inode *inode, const struct iattr *attr); + ++extern int update_time(struct inode *, struct timespec *, int); + extern int file_update_time(struct file *file); + + extern int generic_show_options(struct seq_file *m, struct dentry *root); +diff --git a/include/linux/fsl/guts.h b/include/linux/fsl/guts.h +new file mode 100644 +index 0000000..f13b12e +--- /dev/null ++++ b/include/linux/fsl/guts.h +@@ -0,0 +1,195 @@ ++/** ++ * Freecale 85xx and 86xx Global Utilties register set ++ * ++ * Authors: Jeff Brown ++ * Timur Tabi ++ * ++ * Copyright 2004,2007,2012 Freescale Semiconductor, Inc ++ * ++ * This program is free software; you can redistribute it and/or modify it ++ * under the terms of the GNU General Public License as published by the ++ * Free Software Foundation; either version 2 of the License, or (at your ++ * option) any later version. ++ */ ++ ++#ifndef __FSL_GUTS_H__ ++#define __FSL_GUTS_H__ ++ ++#include ++ ++/** ++ * Global Utility Registers. ++ * ++ * Not all registers defined in this structure are available on all chips, so ++ * you are expected to know whether a given register actually exists on your ++ * chip before you access it. ++ * ++ * Also, some registers are similar on different chips but have slightly ++ * different names. In these cases, one name is chosen to avoid extraneous ++ * #ifdefs. ++ */ ++struct ccsr_guts { ++ u32 porpllsr; /* 0x.0000 - POR PLL Ratio Status Register */ ++ u32 porbmsr; /* 0x.0004 - POR Boot Mode Status Register */ ++ u32 porimpscr; /* 0x.0008 - POR I/O Impedance Status and Control Register */ ++ u32 pordevsr; /* 0x.000c - POR I/O Device Status Register */ ++ u32 pordbgmsr; /* 0x.0010 - POR Debug Mode Status Register */ ++ u32 pordevsr2; /* 0x.0014 - POR device status register 2 */ ++ u8 res018[0x20 - 0x18]; ++ u32 porcir; /* 0x.0020 - POR Configuration Information Register */ ++ u8 res024[0x30 - 0x24]; ++ u32 gpiocr; /* 0x.0030 - GPIO Control Register */ ++ u8 res034[0x40 - 0x34]; ++ u32 gpoutdr; /* 0x.0040 - General-Purpose Output Data Register */ ++ u8 res044[0x50 - 0x44]; ++ u32 gpindr; /* 0x.0050 - General-Purpose Input Data Register */ ++ u8 res054[0x60 - 0x54]; ++ u32 pmuxcr; /* 0x.0060 - Alternate Function Signal Multiplex Control */ ++ u32 pmuxcr2; /* 0x.0064 - Alternate function signal multiplex control 2 */ ++ u32 dmuxcr; /* 0x.0068 - DMA Mux Control Register */ ++ u8 res06c[0x70 - 0x6c]; ++ u32 devdisr; /* 0x.0070 - Device Disable Control */ ++#define CCSR_GUTS_DEVDISR_TB1 0x00001000 ++#define CCSR_GUTS_DEVDISR_TB0 0x00004000 ++ u32 devdisr2; /* 0x.0074 - Device Disable Control 2 */ ++ u8 res078[0x7c - 0x78]; ++ u32 pmjcr; /* 0x.007c - 4 Power Management Jog Control Register */ ++ u32 powmgtcsr; /* 0x.0080 - Power Management Status and Control Register */ ++ u32 pmrccr; /* 0x.0084 - Power Management Reset Counter Configuration Register */ ++ u32 pmpdccr; /* 0x.0088 - Power Management Power Down Counter Configuration Register */ ++ u32 pmcdr; /* 0x.008c - 4Power management clock disable register */ ++ u32 mcpsumr; /* 0x.0090 - Machine Check Summary Register */ ++ u32 rstrscr; /* 0x.0094 - Reset Request Status and Control Register */ ++ u32 ectrstcr; /* 0x.0098 - Exception reset control register */ ++ u32 autorstsr; /* 0x.009c - Automatic reset status register */ ++ u32 pvr; /* 0x.00a0 - Processor Version Register */ ++ u32 svr; /* 0x.00a4 - System Version Register */ ++ u8 res0a8[0xb0 - 0xa8]; ++ u32 rstcr; /* 0x.00b0 - Reset Control Register */ ++ u8 res0b4[0xc0 - 0xb4]; ++ u32 iovselsr; /* 0x.00c0 - I/O voltage select status register ++ Called 'elbcvselcr' on 86xx SOCs */ ++ u8 res0c4[0x100 - 0xc4]; ++ u32 rcwsr[16]; /* 0x.0100 - Reset Control Word Status registers ++ There are 16 registers */ ++ u8 res140[0x224 - 0x140]; ++ u32 iodelay1; /* 0x.0224 - IO delay control register 1 */ ++ u32 iodelay2; /* 0x.0228 - IO delay control register 2 */ ++ u8 res22c[0x604 - 0x22c]; ++ u32 pamubypenr; /* 0x.604 - PAMU bypass enable register */ ++ u8 res608[0x800 - 0x608]; ++ u32 clkdvdr; /* 0x.0800 - Clock Divide Register */ ++ u8 res804[0x900 - 0x804]; ++ u32 ircr; /* 0x.0900 - Infrared Control Register */ ++ u8 res904[0x908 - 0x904]; ++ u32 dmacr; /* 0x.0908 - DMA Control Register */ ++ u8 res90c[0x914 - 0x90c]; ++ u32 elbccr; /* 0x.0914 - eLBC Control Register */ ++ u8 res918[0xb20 - 0x918]; ++ u32 ddr1clkdr; /* 0x.0b20 - DDR1 Clock Disable Register */ ++ u32 ddr2clkdr; /* 0x.0b24 - DDR2 Clock Disable Register */ ++ u32 ddrclkdr; /* 0x.0b28 - DDR Clock Disable Register */ ++ u8 resb2c[0xe00 - 0xb2c]; ++ u32 clkocr; /* 0x.0e00 - Clock Out Select Register */ ++ u8 rese04[0xe10 - 0xe04]; ++ u32 ddrdllcr; /* 0x.0e10 - DDR DLL Control Register */ ++ u8 rese14[0xe20 - 0xe14]; ++ u32 lbcdllcr; /* 0x.0e20 - LBC DLL Control Register */ ++ u32 cpfor; /* 0x.0e24 - L2 charge pump fuse override register */ ++ u8 rese28[0xf04 - 0xe28]; ++ u32 srds1cr0; /* 0x.0f04 - SerDes1 Control Register 0 */ ++ u32 srds1cr1; /* 0x.0f08 - SerDes1 Control Register 0 */ ++ u8 resf0c[0xf2c - 0xf0c]; ++ u32 itcr; /* 0x.0f2c - Internal transaction control register */ ++ u8 resf30[0xf40 - 0xf30]; ++ u32 srds2cr0; /* 0x.0f40 - SerDes2 Control Register 0 */ ++ u32 srds2cr1; /* 0x.0f44 - SerDes2 Control Register 0 */ ++} __attribute__ ((packed)); ++ ++#ifdef CONFIG_FSL_GUTS ++extern u32 guts_get_svr(void); ++#endif ++ ++/* Alternate function signal multiplex control */ ++#define MPC85xx_PMUXCR_QE(x) (0x8000 >> (x)) ++ ++#ifdef CONFIG_PPC_86xx ++ ++#define CCSR_GUTS_DMACR_DEV_SSI 0 /* DMA controller/channel set to SSI */ ++#define CCSR_GUTS_DMACR_DEV_IR 1 /* DMA controller/channel set to IR */ ++ ++/* ++ * Set the DMACR register in the GUTS ++ * ++ * The DMACR register determines the source of initiated transfers for each ++ * channel on each DMA controller. Rather than have a bunch of repetitive ++ * macros for the bit patterns, we just have a function that calculates ++ * them. ++ * ++ * guts: Pointer to GUTS structure ++ * co: The DMA controller (0 or 1) ++ * ch: The channel on the DMA controller (0, 1, 2, or 3) ++ * device: The device to set as the source (CCSR_GUTS_DMACR_DEV_xx) ++ */ ++static inline void guts_set_dmacr(struct ccsr_guts __iomem *guts, ++ unsigned int co, unsigned int ch, unsigned int device) ++{ ++ unsigned int shift = 16 + (8 * (1 - co) + 2 * (3 - ch)); ++ ++ clrsetbits_be32(&guts->dmacr, 3 << shift, device << shift); ++} ++ ++#define CCSR_GUTS_PMUXCR_LDPSEL 0x00010000 ++#define CCSR_GUTS_PMUXCR_SSI1_MASK 0x0000C000 /* Bitmask for SSI1 */ ++#define CCSR_GUTS_PMUXCR_SSI1_LA 0x00000000 /* Latched address */ ++#define CCSR_GUTS_PMUXCR_SSI1_HI 0x00004000 /* High impedance */ ++#define CCSR_GUTS_PMUXCR_SSI1_SSI 0x00008000 /* Used for SSI1 */ ++#define CCSR_GUTS_PMUXCR_SSI2_MASK 0x00003000 /* Bitmask for SSI2 */ ++#define CCSR_GUTS_PMUXCR_SSI2_LA 0x00000000 /* Latched address */ ++#define CCSR_GUTS_PMUXCR_SSI2_HI 0x00001000 /* High impedance */ ++#define CCSR_GUTS_PMUXCR_SSI2_SSI 0x00002000 /* Used for SSI2 */ ++#define CCSR_GUTS_PMUXCR_LA_22_25_LA 0x00000000 /* Latched Address */ ++#define CCSR_GUTS_PMUXCR_LA_22_25_HI 0x00000400 /* High impedance */ ++#define CCSR_GUTS_PMUXCR_DBGDRV 0x00000200 /* Signals not driven */ ++#define CCSR_GUTS_PMUXCR_DMA2_0 0x00000008 ++#define CCSR_GUTS_PMUXCR_DMA2_3 0x00000004 ++#define CCSR_GUTS_PMUXCR_DMA1_0 0x00000002 ++#define CCSR_GUTS_PMUXCR_DMA1_3 0x00000001 ++ ++/* ++ * Set the DMA external control bits in the GUTS ++ * ++ * The DMA external control bits in the PMUXCR are only meaningful for ++ * channels 0 and 3. Any other channels are ignored. ++ * ++ * guts: Pointer to GUTS structure ++ * co: The DMA controller (0 or 1) ++ * ch: The channel on the DMA controller (0, 1, 2, or 3) ++ * value: the new value for the bit (0 or 1) ++ */ ++static inline void guts_set_pmuxcr_dma(struct ccsr_guts __iomem *guts, ++ unsigned int co, unsigned int ch, unsigned int value) ++{ ++ if ((ch == 0) || (ch == 3)) { ++ unsigned int shift = 2 * (co + 1) - (ch & 1) - 1; ++ ++ clrsetbits_be32(&guts->pmuxcr, 1 << shift, value << shift); ++ } ++} ++ ++#define CCSR_GUTS_CLKDVDR_PXCKEN 0x80000000 ++#define CCSR_GUTS_CLKDVDR_SSICKEN 0x20000000 ++#define CCSR_GUTS_CLKDVDR_PXCKINV 0x10000000 ++#define CCSR_GUTS_CLKDVDR_PXCKDLY_SHIFT 25 ++#define CCSR_GUTS_CLKDVDR_PXCKDLY_MASK 0x06000000 ++#define CCSR_GUTS_CLKDVDR_PXCKDLY(x) \ ++ (((x) & 3) << CCSR_GUTS_CLKDVDR_PXCKDLY_SHIFT) ++#define CCSR_GUTS_CLKDVDR_PXCLK_SHIFT 16 ++#define CCSR_GUTS_CLKDVDR_PXCLK_MASK 0x001F0000 ++#define CCSR_GUTS_CLKDVDR_PXCLK(x) (((x) & 31) << CCSR_GUTS_CLKDVDR_PXCLK_SHIFT) ++#define CCSR_GUTS_CLKDVDR_SSICLK_MASK 0x000000FF ++#define CCSR_GUTS_CLKDVDR_SSICLK(x) ((x) & CCSR_GUTS_CLKDVDR_SSICLK_MASK) ++ ++#endif ++ ++#endif +diff --git a/include/linux/fsl/svr.h b/include/linux/fsl/svr.h +new file mode 100644 +index 0000000..8d13836 +--- /dev/null ++++ b/include/linux/fsl/svr.h +@@ -0,0 +1,95 @@ ++/* ++ * MPC85xx cpu type detection ++ * ++ * Copyright 2011-2012 Freescale Semiconductor, Inc. ++ * ++ * This is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ */ ++ ++#ifndef FSL_SVR_H ++#define FSL_SVR_H ++ ++#define SVR_REV(svr) ((svr) & 0xFF) /* SOC design resision */ ++#define SVR_MAJ(svr) (((svr) >> 4) & 0xF) /* Major revision field*/ ++#define SVR_MIN(svr) (((svr) >> 0) & 0xF) /* Minor revision field*/ ++ ++/* Some parts define SVR[0:23] as the SOC version */ ++#define SVR_SOC_VER(svr) (((svr) >> 8) & 0xFFF7FF) /* SOC Version fields */ ++ ++#define SVR_8533 0x803400 ++#define SVR_8535 0x803701 ++#define SVR_8536 0x803700 ++#define SVR_8540 0x803000 ++#define SVR_8541 0x807200 ++#define SVR_8543 0x803200 ++#define SVR_8544 0x803401 ++#define SVR_8545 0x803102 ++#define SVR_8547 0x803101 ++#define SVR_8548 0x803100 ++#define SVR_8555 0x807100 ++#define SVR_8560 0x807000 ++#define SVR_8567 0x807501 ++#define SVR_8568 0x807500 ++#define SVR_8569 0x808000 ++#define SVR_8572 0x80E000 ++#define SVR_P1010 0x80F100 ++#define SVR_P1011 0x80E500 ++#define SVR_P1012 0x80E501 ++#define SVR_P1013 0x80E700 ++#define SVR_P1014 0x80F101 ++#define SVR_P1017 0x80F700 ++#define SVR_P1020 0x80E400 ++#define SVR_P1021 0x80E401 ++#define SVR_P1022 0x80E600 ++#define SVR_P1023 0x80F600 ++#define SVR_P1024 0x80E402 ++#define SVR_P1025 0x80E403 ++#define SVR_P2010 0x80E300 ++#define SVR_P2020 0x80E200 ++#define SVR_P2040 0x821000 ++#define SVR_P2041 0x821001 ++#define SVR_P3041 0x821103 ++#define SVR_P4040 0x820100 ++#define SVR_P4080 0x820000 ++#define SVR_P5010 0x822100 ++#define SVR_P5020 0x822000 ++#define SVR_P5021 0X820500 ++#define SVR_P5040 0x820400 ++#define SVR_T4240 0x824000 ++#define SVR_T4120 0x824001 ++#define SVR_T4160 0x824100 ++#define SVR_T4080 0x824102 ++#define SVR_C291 0x850000 ++#define SVR_C292 0x850020 ++#define SVR_C293 0x850030 ++#define SVR_B4860 0X868000 ++#define SVR_G4860 0x868001 ++#define SVR_G4060 0x868003 ++#define SVR_B4440 0x868100 ++#define SVR_G4440 0x868101 ++#define SVR_B4420 0x868102 ++#define SVR_B4220 0x868103 ++#define SVR_T1040 0x852000 ++#define SVR_T1041 0x852001 ++#define SVR_T1042 0x852002 ++#define SVR_T1020 0x852100 ++#define SVR_T1021 0x852101 ++#define SVR_T1022 0x852102 ++#define SVR_T2080 0x853000 ++#define SVR_T2081 0x853100 ++ ++#define SVR_8610 0x80A000 ++#define SVR_8641 0x809000 ++#define SVR_8641D 0x809001 ++ ++#define SVR_9130 0x860001 ++#define SVR_9131 0x860000 ++#define SVR_9132 0x861000 ++#define SVR_9232 0x861400 ++ ++#define SVR_Unknown 0xFFFFFF ++ ++#endif +diff --git a/include/linux/fsl_ifc.h b/include/linux/fsl_ifc.h +index 84d60cb..3f9778c 100644 +--- a/include/linux/fsl_ifc.h ++++ b/include/linux/fsl_ifc.h +@@ -29,7 +29,20 @@ + #include + #include + +-#define FSL_IFC_BANK_COUNT 4 ++/* ++ * The actual number of banks implemented depends on the IFC version ++ * - IFC version 1.0 implements 4 banks. ++ * - IFC version 1.1 onward implements 8 banks. ++ */ ++#define FSL_IFC_BANK_COUNT 8 ++ ++#define FSL_IFC_VERSION_MASK 0x0F0F0000 ++#define FSL_IFC_VERSION_1_0_0 0x01000000 ++#define FSL_IFC_VERSION_1_1_0 0x01010000 ++#define FSL_IFC_VERSION_2_0_0 0x02000000 ++ ++#define PGOFFSET_64K (64*1024) ++#define PGOFFSET_4K (4*1024) + + /* + * CSPR - Chip Select Property Register +@@ -714,20 +727,26 @@ struct fsl_ifc_nand { + __be32 nand_evter_en; + u32 res17[0x2]; + __be32 nand_evter_intr_en; +- u32 res18[0x2]; ++ __be32 nand_vol_addr_stat; ++ u32 res18; + __be32 nand_erattr0; + __be32 nand_erattr1; + u32 res19[0x10]; + __be32 nand_fsr; +- u32 res20; +- __be32 nand_eccstat[4]; +- u32 res21[0x20]; ++ u32 res20[0x3]; ++ __be32 nand_eccstat[6]; ++ u32 res21[0x1c]; + __be32 nanndcr; + u32 res22[0x2]; + __be32 nand_autoboot_trgr; + u32 res23; + __be32 nand_mdr; +- u32 res24[0x5C]; ++ u32 res24[0x1C]; ++ __be32 nand_dll_lowcfg0; ++ __be32 nand_dll_lowcfg1; ++ u32 res25; ++ __be32 nand_dll_lowstat; ++ u32 res26[0x3c]; + }; + + /* +@@ -762,13 +781,12 @@ struct fsl_ifc_gpcm { + __be32 gpcm_erattr1; + __be32 gpcm_erattr2; + __be32 gpcm_stat; +- u32 res4[0x1F3]; + }; + + /* + * IFC Controller Registers + */ +-struct fsl_ifc_regs { ++struct fsl_ifc_global { + __be32 ifc_rev; + u32 res1[0x2]; + struct { +@@ -776,39 +794,44 @@ struct fsl_ifc_regs { + __be32 cspr; + u32 res2; + } cspr_cs[FSL_IFC_BANK_COUNT]; +- u32 res3[0x19]; ++ u32 res3[0xd]; + struct { + __be32 amask; + u32 res4[0x2]; + } amask_cs[FSL_IFC_BANK_COUNT]; +- u32 res5[0x18]; ++ u32 res5[0xc]; + struct { + __be32 csor; + __be32 csor_ext; + u32 res6; + } csor_cs[FSL_IFC_BANK_COUNT]; +- u32 res7[0x18]; ++ u32 res7[0xc]; + struct { + __be32 ftim[4]; + u32 res8[0x8]; + } ftim_cs[FSL_IFC_BANK_COUNT]; +- u32 res9[0x60]; ++ u32 res9[0x30]; + __be32 rb_stat; +- u32 res10[0x2]; ++ __be32 rb_map; ++ __be32 wb_map; + __be32 ifc_gcr; +- u32 res11[0x2]; ++ u32 res10[0x2]; + __be32 cm_evter_stat; +- u32 res12[0x2]; ++ u32 res11[0x2]; + __be32 cm_evter_en; +- u32 res13[0x2]; ++ u32 res12[0x2]; + __be32 cm_evter_intr_en; +- u32 res14[0x2]; ++ u32 res13[0x2]; + __be32 cm_erattr0; + __be32 cm_erattr1; +- u32 res15[0x2]; ++ u32 res14[0x2]; + __be32 ifc_ccr; + __be32 ifc_csr; +- u32 res16[0x2EB]; ++ __be32 ddr_ccr_low; ++}; ++ ++ ++struct fsl_ifc_runtime { + struct fsl_ifc_nand ifc_nand; + struct fsl_ifc_nor ifc_nor; + struct fsl_ifc_gpcm ifc_gpcm; +@@ -822,17 +845,70 @@ extern int fsl_ifc_find(phys_addr_t addr_base); + struct fsl_ifc_ctrl { + /* device info */ + struct device *dev; +- struct fsl_ifc_regs __iomem *regs; ++ struct fsl_ifc_global __iomem *gregs; ++ struct fsl_ifc_runtime __iomem *rregs; + int irq; + int nand_irq; + spinlock_t lock; + void *nand; ++ int version; ++ int banks; + + u32 nand_stat; + wait_queue_head_t nand_wait; ++ bool little_endian; + }; + + extern struct fsl_ifc_ctrl *fsl_ifc_ctrl_dev; + ++static inline u32 ifc_in32(void __iomem *addr) ++{ ++ u32 val; ++ ++ if (fsl_ifc_ctrl_dev->little_endian) ++ val = ioread32(addr); ++ else ++ val = ioread32be(addr); ++ ++ return val; ++} ++ ++static inline u16 ifc_in16(void __iomem *addr) ++{ ++ u16 val; ++ ++ if (fsl_ifc_ctrl_dev->little_endian) ++ val = ioread16(addr); ++ else ++ val = ioread16be(addr); ++ ++ return val; ++} ++ ++static inline u8 ifc_in8(void __iomem *addr) ++{ ++ return ioread8(addr); ++} ++ ++static inline void ifc_out32(u32 val, void __iomem *addr) ++{ ++ if (fsl_ifc_ctrl_dev->little_endian) ++ iowrite32(val, addr); ++ else ++ iowrite32be(val, addr); ++} ++ ++static inline void ifc_out16(u16 val, void __iomem *addr) ++{ ++ if (fsl_ifc_ctrl_dev->little_endian) ++ iowrite16(val, addr); ++ else ++ iowrite16be(val, addr); ++} ++ ++static inline void ifc_out8(u8 val, void __iomem *addr) ++{ ++ iowrite8(val, addr); ++} + + #endif /* __ASM_FSL_IFC_H */ +diff --git a/include/linux/interrupt.h b/include/linux/interrupt.h +index 69517a2..cbbe6a2 100644 +--- a/include/linux/interrupt.h ++++ b/include/linux/interrupt.h +@@ -356,6 +356,20 @@ static inline int disable_irq_wake(unsigned int irq) + return irq_set_irq_wake(irq, 0); + } + ++/* ++ * irq_get_irqchip_state/irq_set_irqchip_state specific flags ++ */ ++enum irqchip_irq_state { ++ IRQCHIP_STATE_PENDING, /* Is interrupt pending? */ ++ IRQCHIP_STATE_ACTIVE, /* Is interrupt in progress? */ ++ IRQCHIP_STATE_MASKED, /* Is interrupt masked? */ ++ IRQCHIP_STATE_LINE_LEVEL, /* Is IRQ line high? */ ++}; ++ ++extern int irq_get_irqchip_state(unsigned int irq, enum irqchip_irq_state which, ++ bool *state); ++extern int irq_set_irqchip_state(unsigned int irq, enum irqchip_irq_state which, ++ bool state); + + #ifdef CONFIG_IRQ_FORCED_THREADING + extern bool force_irqthreads; +diff --git a/include/linux/iommu.h b/include/linux/iommu.h +index e6a7c9f..7421bdf 100644 +--- a/include/linux/iommu.h ++++ b/include/linux/iommu.h +@@ -21,13 +21,16 @@ + + #include + #include ++#include + #include ++#include + #include + + #define IOMMU_READ (1 << 0) + #define IOMMU_WRITE (1 << 1) + #define IOMMU_CACHE (1 << 2) /* DMA cache coherency */ +-#define IOMMU_EXEC (1 << 3) ++#define IOMMU_NOEXEC (1 << 3) ++#define IOMMU_MMIO (1 << 4) /* Device memory access */ + + struct iommu_ops; + struct iommu_group; +@@ -49,9 +52,33 @@ struct iommu_domain_geometry { + bool force_aperture; /* DMA only allowed in mappable range? */ + }; + ++/* Domain feature flags */ ++#define __IOMMU_DOMAIN_PAGING (1U << 0) /* Support for iommu_map/unmap */ ++#define __IOMMU_DOMAIN_DMA_API (1U << 1) /* Domain for use in DMA-API ++ implementation */ ++#define __IOMMU_DOMAIN_PT (1U << 2) /* Domain is identity mapped */ ++ ++/* ++ * This are the possible domain-types ++ * ++ * IOMMU_DOMAIN_BLOCKED - All DMA is blocked, can be used to isolate ++ * devices ++ * IOMMU_DOMAIN_IDENTITY - DMA addresses are system physical addresses ++ * IOMMU_DOMAIN_UNMANAGED - DMA mappings managed by IOMMU-API user, used ++ * for VMs ++ * IOMMU_DOMAIN_DMA - Internally used for DMA-API implementations. ++ * This flag allows IOMMU drivers to implement ++ * certain optimizations for these domains ++ */ ++#define IOMMU_DOMAIN_BLOCKED (0U) ++#define IOMMU_DOMAIN_IDENTITY (__IOMMU_DOMAIN_PT) ++#define IOMMU_DOMAIN_UNMANAGED (__IOMMU_DOMAIN_PAGING) ++#define IOMMU_DOMAIN_DMA (__IOMMU_DOMAIN_PAGING | \ ++ __IOMMU_DOMAIN_DMA_API) ++ + struct iommu_domain { ++ unsigned type; + const struct iommu_ops *ops; +- void *priv; + iommu_fault_handler_t handler; + void *handler_token; + struct iommu_domain_geometry geometry; +@@ -61,6 +88,7 @@ enum iommu_cap { + IOMMU_CAP_CACHE_COHERENCY, /* IOMMU can enforce cache coherent DMA + transactions */ + IOMMU_CAP_INTR_REMAP, /* IOMMU supports interrupt isolation */ ++ IOMMU_CAP_NOEXEC, /* IOMMU_NOEXEC flag */ + }; + + /* +@@ -97,23 +125,32 @@ enum iommu_attr { + * @detach_dev: detach device from an iommu domain + * @map: map a physically contiguous memory region to an iommu domain + * @unmap: unmap a physically contiguous memory region from an iommu domain ++ * @map_sg: map a scatter-gather list of physically contiguous memory chunks ++ * to an iommu domain + * @iova_to_phys: translate iova to physical address + * @add_device: add device to iommu grouping + * @remove_device: remove device from iommu grouping + * @domain_get_attr: Query domain attributes + * @domain_set_attr: Change domain attributes ++ * @of_xlate: add OF master IDs to iommu grouping + * @pgsize_bitmap: bitmap of supported page sizes ++ * @priv: per-instance data private to the iommu driver + */ + struct iommu_ops { + bool (*capable)(enum iommu_cap); +- int (*domain_init)(struct iommu_domain *domain); +- void (*domain_destroy)(struct iommu_domain *domain); ++ ++ /* Domain allocation and freeing by the iommu driver */ ++ struct iommu_domain *(*domain_alloc)(unsigned iommu_domain_type); ++ void (*domain_free)(struct iommu_domain *); ++ + int (*attach_dev)(struct iommu_domain *domain, struct device *dev); + void (*detach_dev)(struct iommu_domain *domain, struct device *dev); + int (*map)(struct iommu_domain *domain, unsigned long iova, + phys_addr_t paddr, size_t size, int prot); + size_t (*unmap)(struct iommu_domain *domain, unsigned long iova, + size_t size); ++ size_t (*map_sg)(struct iommu_domain *domain, unsigned long iova, ++ struct scatterlist *sg, unsigned int nents, int prot); + phys_addr_t (*iova_to_phys)(struct iommu_domain *domain, dma_addr_t iova); + int (*add_device)(struct device *dev); + void (*remove_device)(struct device *dev); +@@ -131,8 +168,14 @@ struct iommu_ops { + int (*domain_set_windows)(struct iommu_domain *domain, u32 w_count); + /* Get the numer of window per domain */ + u32 (*domain_get_windows)(struct iommu_domain *domain); ++ struct iommu_domain *(*get_dev_iommu_domain)(struct device *dev); ++ ++#ifdef CONFIG_OF_IOMMU ++ int (*of_xlate)(struct device *dev, struct of_phandle_args *args); ++#endif + + unsigned long pgsize_bitmap; ++ void *priv; + }; + + #define IOMMU_GROUP_NOTIFY_ADD_DEVICE 1 /* Device added */ +@@ -156,6 +199,9 @@ extern int iommu_map(struct iommu_domain *domain, unsigned long iova, + phys_addr_t paddr, size_t size, int prot); + extern size_t iommu_unmap(struct iommu_domain *domain, unsigned long iova, + size_t size); ++extern size_t default_iommu_map_sg(struct iommu_domain *domain, unsigned long iova, ++ struct scatterlist *sg,unsigned int nents, ++ int prot); + extern phys_addr_t iommu_iova_to_phys(struct iommu_domain *domain, dma_addr_t iova); + extern void iommu_set_fault_handler(struct iommu_domain *domain, + iommu_fault_handler_t handler, void *token); +@@ -200,6 +246,9 @@ extern int iommu_domain_window_enable(struct iommu_domain *domain, u32 wnd_nr, + phys_addr_t offset, u64 size, + int prot); + extern void iommu_domain_window_disable(struct iommu_domain *domain, u32 wnd_nr); ++ ++extern struct iommu_domain *iommu_get_dev_domain(struct device *dev); ++ + /** + * report_iommu_fault() - report about an IOMMU fault to the IOMMU framework + * @domain: the iommu domain where the fault has happened +@@ -241,6 +290,13 @@ static inline int report_iommu_fault(struct iommu_domain *domain, + return ret; + } + ++static inline size_t iommu_map_sg(struct iommu_domain *domain, ++ unsigned long iova, struct scatterlist *sg, ++ unsigned int nents, int prot) ++{ ++ return domain->ops->map_sg(domain, iova, sg, nents, prot); ++} ++ + #else /* CONFIG_IOMMU_API */ + + struct iommu_ops {}; +@@ -293,6 +349,13 @@ static inline int iommu_unmap(struct iommu_domain *domain, unsigned long iova, + return -ENODEV; + } + ++static inline size_t iommu_map_sg(struct iommu_domain *domain, ++ unsigned long iova, struct scatterlist *sg, ++ unsigned int nents, int prot) ++{ ++ return -ENODEV; ++} ++ + static inline int iommu_domain_window_enable(struct iommu_domain *domain, + u32 wnd_nr, phys_addr_t paddr, + u64 size, int prot) +@@ -424,6 +487,11 @@ static inline void iommu_device_unlink(struct device *dev, struct device *link) + { + } + ++static inline struct iommu_domain *iommu_get_dev_domain(struct device *dev) ++{ ++ return NULL; ++} ++ + #endif /* CONFIG_IOMMU_API */ + + #endif /* __LINUX_IOMMU_H */ +diff --git a/include/linux/iopoll.h b/include/linux/iopoll.h +new file mode 100644 +index 0000000..1c30014 +--- /dev/null ++++ b/include/linux/iopoll.h +@@ -0,0 +1,144 @@ ++/* ++ * Copyright (c) 2012-2014 The Linux Foundation. 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 and ++ * only version 2 as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ */ ++ ++#ifndef _LINUX_IOPOLL_H ++#define _LINUX_IOPOLL_H ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++/** ++ * readx_poll_timeout - Periodically poll an address until a condition is met or a timeout occurs ++ * @op: accessor function (takes @addr as its only argument) ++ * @addr: Address to poll ++ * @val: Variable to read the value into ++ * @cond: Break condition (usually involving @val) ++ * @sleep_us: Maximum time to sleep between reads in us (0 ++ * tight-loops). Should be less than ~20ms since usleep_range ++ * is used (see Documentation/timers/timers-howto.txt). ++ * @timeout_us: Timeout in us, 0 means never timeout ++ * ++ * Returns 0 on success and -ETIMEDOUT upon a timeout. In either ++ * case, the last read value at @addr is stored in @val. Must not ++ * be called from atomic context if sleep_us or timeout_us are used. ++ * ++ * When available, you'll probably want to use one of the specialized ++ * macros defined below rather than this macro directly. ++ */ ++#define readx_poll_timeout(op, addr, val, cond, sleep_us, timeout_us) \ ++({ \ ++ ktime_t timeout = ktime_add_us(ktime_get(), timeout_us); \ ++ might_sleep_if(sleep_us); \ ++ for (;;) { \ ++ (val) = op(addr); \ ++ if (cond) \ ++ break; \ ++ if (timeout_us && ktime_compare(ktime_get(), timeout) > 0) { \ ++ (val) = op(addr); \ ++ break; \ ++ } \ ++ if (sleep_us) \ ++ usleep_range((sleep_us >> 2) + 1, sleep_us); \ ++ } \ ++ (cond) ? 0 : -ETIMEDOUT; \ ++}) ++ ++/** ++ * readx_poll_timeout_atomic - Periodically poll an address until a condition is met or a timeout occurs ++ * @op: accessor function (takes @addr as its only argument) ++ * @addr: Address to poll ++ * @val: Variable to read the value into ++ * @cond: Break condition (usually involving @val) ++ * @delay_us: Time to udelay between reads in us (0 tight-loops). Should ++ * be less than ~10us since udelay is used (see ++ * Documentation/timers/timers-howto.txt). ++ * @timeout_us: Timeout in us, 0 means never timeout ++ * ++ * Returns 0 on success and -ETIMEDOUT upon a timeout. In either ++ * case, the last read value at @addr is stored in @val. ++ * ++ * When available, you'll probably want to use one of the specialized ++ * macros defined below rather than this macro directly. ++ */ ++#define readx_poll_timeout_atomic(op, addr, val, cond, delay_us, timeout_us) \ ++({ \ ++ ktime_t timeout = ktime_add_us(ktime_get(), timeout_us); \ ++ for (;;) { \ ++ (val) = op(addr); \ ++ if (cond) \ ++ break; \ ++ if (timeout_us && ktime_compare(ktime_get(), timeout) > 0) { \ ++ (val) = op(addr); \ ++ break; \ ++ } \ ++ if (delay_us) \ ++ udelay(delay_us); \ ++ } \ ++ (cond) ? 0 : -ETIMEDOUT; \ ++}) ++ ++ ++#define readb_poll_timeout(addr, val, cond, delay_us, timeout_us) \ ++ readx_poll_timeout(readb, addr, val, cond, delay_us, timeout_us) ++ ++#define readb_poll_timeout_atomic(addr, val, cond, delay_us, timeout_us) \ ++ readx_poll_timeout_atomic(readb, addr, val, cond, delay_us, timeout_us) ++ ++#define readw_poll_timeout(addr, val, cond, delay_us, timeout_us) \ ++ readx_poll_timeout(readw, addr, val, cond, delay_us, timeout_us) ++ ++#define readw_poll_timeout_atomic(addr, val, cond, delay_us, timeout_us) \ ++ readx_poll_timeout_atomic(readw, addr, val, cond, delay_us, timeout_us) ++ ++#define readl_poll_timeout(addr, val, cond, delay_us, timeout_us) \ ++ readx_poll_timeout(readl, addr, val, cond, delay_us, timeout_us) ++ ++#define readl_poll_timeout_atomic(addr, val, cond, delay_us, timeout_us) \ ++ readx_poll_timeout_atomic(readl, addr, val, cond, delay_us, timeout_us) ++ ++#define readq_poll_timeout(addr, val, cond, delay_us, timeout_us) \ ++ readx_poll_timeout(readq, addr, val, cond, delay_us, timeout_us) ++ ++#define readq_poll_timeout_atomic(addr, val, cond, delay_us, timeout_us) \ ++ readx_poll_timeout_atomic(readq, addr, val, cond, delay_us, timeout_us) ++ ++#define readb_relaxed_poll_timeout(addr, val, cond, delay_us, timeout_us) \ ++ readx_poll_timeout(readb_relaxed, addr, val, cond, delay_us, timeout_us) ++ ++#define readb_relaxed_poll_timeout_atomic(addr, val, cond, delay_us, timeout_us) \ ++ readx_poll_timeout_atomic(readb_relaxed, addr, val, cond, delay_us, timeout_us) ++ ++#define readw_relaxed_poll_timeout(addr, val, cond, delay_us, timeout_us) \ ++ readx_poll_timeout(readw_relaxed, addr, val, cond, delay_us, timeout_us) ++ ++#define readw_relaxed_poll_timeout_atomic(addr, val, cond, delay_us, timeout_us) \ ++ readx_poll_timeout_atomic(readw_relaxed, addr, val, cond, delay_us, timeout_us) ++ ++#define readl_relaxed_poll_timeout(addr, val, cond, delay_us, timeout_us) \ ++ readx_poll_timeout(readl_relaxed, addr, val, cond, delay_us, timeout_us) ++ ++#define readl_relaxed_poll_timeout_atomic(addr, val, cond, delay_us, timeout_us) \ ++ readx_poll_timeout_atomic(readl_relaxed, addr, val, cond, delay_us, timeout_us) ++ ++#define readq_relaxed_poll_timeout(addr, val, cond, delay_us, timeout_us) \ ++ readx_poll_timeout(readq_relaxed, addr, val, cond, delay_us, timeout_us) ++ ++#define readq_relaxed_poll_timeout_atomic(addr, val, cond, delay_us, timeout_us) \ ++ readx_poll_timeout_atomic(readq_relaxed, addr, val, cond, delay_us, timeout_us) ++ ++#endif /* _LINUX_IOPOLL_H */ +diff --git a/include/linux/irq.h b/include/linux/irq.h +index 03f48d9..4931a8b 100644 +--- a/include/linux/irq.h ++++ b/include/linux/irq.h +@@ -15,11 +15,13 @@ + #include + #include + #include ++#include + #include + #include + #include + #include + #include ++#include + + #include + #include +@@ -27,11 +29,8 @@ + + struct seq_file; + struct module; +-struct irq_desc; +-struct irq_data; +-typedef void (*irq_flow_handler_t)(unsigned int irq, +- struct irq_desc *desc); +-typedef void (*irq_preflow_handler_t)(struct irq_data *data); ++struct msi_msg; ++enum irqchip_irq_state; + + /* + * IRQ line status. +@@ -113,10 +112,14 @@ enum { + * + * IRQ_SET_MASK_OK - OK, core updates irq_data.affinity + * IRQ_SET_MASK_NOCPY - OK, chip did update irq_data.affinity ++ * IRQ_SET_MASK_OK_DONE - Same as IRQ_SET_MASK_OK for core. Special code to ++ * support stacked irqchips, which indicates skipping ++ * all descendent irqchips. + */ + enum { + IRQ_SET_MASK_OK = 0, + IRQ_SET_MASK_OK_NOCOPY, ++ IRQ_SET_MASK_OK_DONE, + }; + + struct msi_desc; +@@ -133,6 +136,8 @@ struct irq_domain; + * @chip: low level interrupt hardware access + * @domain: Interrupt translation domain; responsible for mapping + * between hwirq number and linux irq number. ++ * @parent_data: pointer to parent struct irq_data to support hierarchy ++ * irq_domain + * @handler_data: per-IRQ data for the irq_chip methods + * @chip_data: platform-specific per-chip private data for the chip + * methods, to allow shared chip implementations +@@ -151,6 +156,9 @@ struct irq_data { + unsigned int state_use_accessors; + struct irq_chip *chip; + struct irq_domain *domain; ++#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY ++ struct irq_data *parent_data; ++#endif + void *handler_data; + void *chip_data; + struct msi_desc *msi_desc; +@@ -315,6 +323,10 @@ static inline irq_hw_number_t irqd_to_hwirq(struct irq_data *d) + * any other callback related to this irq + * @irq_release_resources: optional to release resources acquired with + * irq_request_resources ++ * @irq_compose_msi_msg: optional to compose message content for MSI ++ * @irq_write_msi_msg: optional to write message content for MSI ++ * @irq_get_irqchip_state: return the internal state of an interrupt ++ * @irq_set_irqchip_state: set the internal state of a interrupt + * @flags: chip specific flags + */ + struct irq_chip { +@@ -351,6 +363,12 @@ struct irq_chip { + int (*irq_request_resources)(struct irq_data *data); + void (*irq_release_resources)(struct irq_data *data); + ++ void (*irq_compose_msi_msg)(struct irq_data *data, struct msi_msg *msg); ++ void (*irq_write_msi_msg)(struct irq_data *data, struct msi_msg *msg); ++ ++ int (*irq_get_irqchip_state)(struct irq_data *data, enum irqchip_irq_state which, bool *state); ++ int (*irq_set_irqchip_state)(struct irq_data *data, enum irqchip_irq_state which, bool state); ++ + unsigned long flags; + }; + +@@ -438,6 +456,20 @@ extern void handle_percpu_devid_irq(unsigned int irq, struct irq_desc *desc); + extern void handle_bad_irq(unsigned int irq, struct irq_desc *desc); + extern void handle_nested_irq(unsigned int irq); + ++extern int irq_chip_compose_msi_msg(struct irq_data *data, struct msi_msg *msg); ++#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY ++extern void irq_chip_ack_parent(struct irq_data *data); ++extern int irq_chip_retrigger_hierarchy(struct irq_data *data); ++extern void irq_chip_mask_parent(struct irq_data *data); ++extern void irq_chip_unmask_parent(struct irq_data *data); ++extern void irq_chip_eoi_parent(struct irq_data *data); ++extern int irq_chip_set_affinity_parent(struct irq_data *data, ++ const struct cpumask *dest, ++ bool force); ++extern int irq_chip_set_wake_parent(struct irq_data *data, unsigned int on); ++extern int irq_chip_set_type_parent(struct irq_data *data, unsigned int type); ++#endif ++ + /* Handling of unhandled and spurious interrupts: */ + extern void note_interrupt(unsigned int irq, struct irq_desc *desc, + irqreturn_t action_ret); +@@ -582,7 +614,7 @@ static inline struct msi_desc *irq_get_msi_desc(unsigned int irq) + return d ? d->msi_desc : NULL; + } + +-static inline struct msi_desc *irq_data_get_msi(struct irq_data *d) ++static inline struct msi_desc *irq_data_get_msi_desc(struct irq_data *d) + { + return d->msi_desc; + } +@@ -639,13 +671,6 @@ void arch_teardown_hwirq(unsigned int irq); + void irq_init_desc(unsigned int irq); + #endif + +-#ifndef irq_reg_writel +-# define irq_reg_writel(val, addr) writel(val, addr) +-#endif +-#ifndef irq_reg_readl +-# define irq_reg_readl(addr) readl(addr) +-#endif +- + /** + * struct irq_chip_regs - register offsets for struct irq_gci + * @enable: Enable register offset to reg_base +@@ -692,6 +717,8 @@ struct irq_chip_type { + * struct irq_chip_generic - Generic irq chip data structure + * @lock: Lock to protect register and cache data access + * @reg_base: Register base address (virtual) ++ * @reg_readl: Alternate I/O accessor (defaults to readl if NULL) ++ * @reg_writel: Alternate I/O accessor (defaults to writel if NULL) + * @irq_base: Interrupt base nr for this chip + * @irq_cnt: Number of interrupts handled by this chip + * @mask_cache: Cached mask register shared between all chip types +@@ -716,6 +743,8 @@ struct irq_chip_type { + struct irq_chip_generic { + raw_spinlock_t lock; + void __iomem *reg_base; ++ u32 (*reg_readl)(void __iomem *addr); ++ void (*reg_writel)(u32 val, void __iomem *addr); + unsigned int irq_base; + unsigned int irq_cnt; + u32 mask_cache; +@@ -740,12 +769,14 @@ struct irq_chip_generic { + * the parent irq. Usually GPIO implementations + * @IRQ_GC_MASK_CACHE_PER_TYPE: Mask cache is chip type private + * @IRQ_GC_NO_MASK: Do not calculate irq_data->mask ++ * @IRQ_GC_BE_IO: Use big-endian register accesses (default: LE) + */ + enum irq_gc_flags { + IRQ_GC_INIT_MASK_CACHE = 1 << 0, + IRQ_GC_INIT_NESTED_LOCK = 1 << 1, + IRQ_GC_MASK_CACHE_PER_TYPE = 1 << 2, + IRQ_GC_NO_MASK = 1 << 3, ++ IRQ_GC_BE_IO = 1 << 4, + }; + + /* +@@ -821,4 +852,22 @@ static inline void irq_gc_lock(struct irq_chip_generic *gc) { } + static inline void irq_gc_unlock(struct irq_chip_generic *gc) { } + #endif + ++static inline void irq_reg_writel(struct irq_chip_generic *gc, ++ u32 val, int reg_offset) ++{ ++ if (gc->reg_writel) ++ gc->reg_writel(val, gc->reg_base + reg_offset); ++ else ++ writel(val, gc->reg_base + reg_offset); ++} ++ ++static inline u32 irq_reg_readl(struct irq_chip_generic *gc, ++ int reg_offset) ++{ ++ if (gc->reg_readl) ++ return gc->reg_readl(gc->reg_base + reg_offset); ++ else ++ return readl(gc->reg_base + reg_offset); ++} ++ + #endif /* _LINUX_IRQ_H */ +diff --git a/include/linux/irqchip/arm-gic-v3.h b/include/linux/irqchip/arm-gic-v3.h +index 03a4ea3..36caf46 100644 +--- a/include/linux/irqchip/arm-gic-v3.h ++++ b/include/linux/irqchip/arm-gic-v3.h +@@ -49,6 +49,10 @@ + #define GICD_CTLR_ENABLE_G1A (1U << 1) + #define GICD_CTLR_ENABLE_G1 (1U << 0) + ++#define GICD_TYPER_ID_BITS(typer) ((((typer) >> 19) & 0x1f) + 1) ++#define GICD_TYPER_IRQS(typer) ((((typer) & 0x1f) + 1) * 32) ++#define GICD_TYPER_LPIS (1U << 17) ++ + #define GICD_IROUTER_SPI_MODE_ONE (0U << 31) + #define GICD_IROUTER_SPI_MODE_ANY (1U << 31) + +@@ -76,9 +80,42 @@ + #define GICR_MOVALLR 0x0110 + #define GICR_PIDR2 GICD_PIDR2 + ++#define GICR_CTLR_ENABLE_LPIS (1UL << 0) ++ ++#define GICR_TYPER_CPU_NUMBER(r) (((r) >> 8) & 0xffff) ++ + #define GICR_WAKER_ProcessorSleep (1U << 1) + #define GICR_WAKER_ChildrenAsleep (1U << 2) + ++#define GICR_PROPBASER_NonShareable (0U << 10) ++#define GICR_PROPBASER_InnerShareable (1U << 10) ++#define GICR_PROPBASER_OuterShareable (2U << 10) ++#define GICR_PROPBASER_SHAREABILITY_MASK (3UL << 10) ++#define GICR_PROPBASER_nCnB (0U << 7) ++#define GICR_PROPBASER_nC (1U << 7) ++#define GICR_PROPBASER_RaWt (2U << 7) ++#define GICR_PROPBASER_RaWb (3U << 7) ++#define GICR_PROPBASER_WaWt (4U << 7) ++#define GICR_PROPBASER_WaWb (5U << 7) ++#define GICR_PROPBASER_RaWaWt (6U << 7) ++#define GICR_PROPBASER_RaWaWb (7U << 7) ++#define GICR_PROPBASER_CACHEABILITY_MASK (7U << 7) ++#define GICR_PROPBASER_IDBITS_MASK (0x1f) ++ ++#define GICR_PENDBASER_NonShareable (0U << 10) ++#define GICR_PENDBASER_InnerShareable (1U << 10) ++#define GICR_PENDBASER_OuterShareable (2U << 10) ++#define GICR_PENDBASER_SHAREABILITY_MASK (3UL << 10) ++#define GICR_PENDBASER_nCnB (0U << 7) ++#define GICR_PENDBASER_nC (1U << 7) ++#define GICR_PENDBASER_RaWt (2U << 7) ++#define GICR_PENDBASER_RaWb (3U << 7) ++#define GICR_PENDBASER_WaWt (4U << 7) ++#define GICR_PENDBASER_WaWb (5U << 7) ++#define GICR_PENDBASER_RaWaWt (6U << 7) ++#define GICR_PENDBASER_RaWaWb (7U << 7) ++#define GICR_PENDBASER_CACHEABILITY_MASK (7U << 7) ++ + /* + * Re-Distributor registers, offsets from SGI_base + */ +@@ -91,9 +128,100 @@ + #define GICR_IPRIORITYR0 GICD_IPRIORITYR + #define GICR_ICFGR0 GICD_ICFGR + ++#define GICR_TYPER_PLPIS (1U << 0) + #define GICR_TYPER_VLPIS (1U << 1) + #define GICR_TYPER_LAST (1U << 4) + ++#define LPI_PROP_GROUP1 (1 << 1) ++#define LPI_PROP_ENABLED (1 << 0) ++ ++/* ++ * ITS registers, offsets from ITS_base ++ */ ++#define GITS_CTLR 0x0000 ++#define GITS_IIDR 0x0004 ++#define GITS_TYPER 0x0008 ++#define GITS_CBASER 0x0080 ++#define GITS_CWRITER 0x0088 ++#define GITS_CREADR 0x0090 ++#define GITS_BASER 0x0100 ++#define GITS_PIDR2 GICR_PIDR2 ++ ++#define GITS_TRANSLATER 0x10040 ++ ++#define GITS_CTLR_ENABLE (1U << 0) ++#define GITS_CTLR_QUIESCENT (1U << 31) ++ ++#define GITS_TYPER_DEVBITS_SHIFT 13 ++#define GITS_TYPER_DEVBITS(r) ((((r) >> GITS_TYPER_DEVBITS_SHIFT) & 0x1f) + 1) ++#define GITS_TYPER_PTA (1UL << 19) ++ ++#define GITS_CBASER_VALID (1UL << 63) ++#define GITS_CBASER_nCnB (0UL << 59) ++#define GITS_CBASER_nC (1UL << 59) ++#define GITS_CBASER_RaWt (2UL << 59) ++#define GITS_CBASER_RaWb (3UL << 59) ++#define GITS_CBASER_WaWt (4UL << 59) ++#define GITS_CBASER_WaWb (5UL << 59) ++#define GITS_CBASER_RaWaWt (6UL << 59) ++#define GITS_CBASER_RaWaWb (7UL << 59) ++#define GITS_CBASER_CACHEABILITY_MASK (7UL << 59) ++#define GITS_CBASER_NonShareable (0UL << 10) ++#define GITS_CBASER_InnerShareable (1UL << 10) ++#define GITS_CBASER_OuterShareable (2UL << 10) ++#define GITS_CBASER_SHAREABILITY_MASK (3UL << 10) ++ ++#define GITS_BASER_NR_REGS 8 ++ ++#define GITS_BASER_VALID (1UL << 63) ++#define GITS_BASER_nCnB (0UL << 59) ++#define GITS_BASER_nC (1UL << 59) ++#define GITS_BASER_RaWt (2UL << 59) ++#define GITS_BASER_RaWb (3UL << 59) ++#define GITS_BASER_WaWt (4UL << 59) ++#define GITS_BASER_WaWb (5UL << 59) ++#define GITS_BASER_RaWaWt (6UL << 59) ++#define GITS_BASER_RaWaWb (7UL << 59) ++#define GITS_BASER_CACHEABILITY_MASK (7UL << 59) ++#define GITS_BASER_TYPE_SHIFT (56) ++#define GITS_BASER_TYPE(r) (((r) >> GITS_BASER_TYPE_SHIFT) & 7) ++#define GITS_BASER_ENTRY_SIZE_SHIFT (48) ++#define GITS_BASER_ENTRY_SIZE(r) ((((r) >> GITS_BASER_ENTRY_SIZE_SHIFT) & 0xff) + 1) ++#define GITS_BASER_NonShareable (0UL << 10) ++#define GITS_BASER_InnerShareable (1UL << 10) ++#define GITS_BASER_OuterShareable (2UL << 10) ++#define GITS_BASER_SHAREABILITY_SHIFT (10) ++#define GITS_BASER_SHAREABILITY_MASK (3UL << GITS_BASER_SHAREABILITY_SHIFT) ++#define GITS_BASER_PAGE_SIZE_SHIFT (8) ++#define GITS_BASER_PAGE_SIZE_4K (0UL << GITS_BASER_PAGE_SIZE_SHIFT) ++#define GITS_BASER_PAGE_SIZE_16K (1UL << GITS_BASER_PAGE_SIZE_SHIFT) ++#define GITS_BASER_PAGE_SIZE_64K (2UL << GITS_BASER_PAGE_SIZE_SHIFT) ++#define GITS_BASER_PAGE_SIZE_MASK (3UL << GITS_BASER_PAGE_SIZE_SHIFT) ++ ++#define GITS_BASER_TYPE_NONE 0 ++#define GITS_BASER_TYPE_DEVICE 1 ++#define GITS_BASER_TYPE_VCPU 2 ++#define GITS_BASER_TYPE_CPU 3 ++#define GITS_BASER_TYPE_COLLECTION 4 ++#define GITS_BASER_TYPE_RESERVED5 5 ++#define GITS_BASER_TYPE_RESERVED6 6 ++#define GITS_BASER_TYPE_RESERVED7 7 ++ ++/* ++ * ITS commands ++ */ ++#define GITS_CMD_MAPD 0x08 ++#define GITS_CMD_MAPC 0x09 ++#define GITS_CMD_MAPVI 0x0a ++#define GITS_CMD_MOVI 0x01 ++#define GITS_CMD_DISCARD 0x0f ++#define GITS_CMD_INV 0x0c ++#define GITS_CMD_MOVALL 0x0e ++#define GITS_CMD_INVALL 0x0d ++#define GITS_CMD_INT 0x03 ++#define GITS_CMD_CLEAR 0x04 ++#define GITS_CMD_SYNC 0x05 ++ + /* + * CPU interface registers + */ +@@ -142,6 +270,18 @@ + #define ICC_SRE_EL2_SRE (1 << 0) + #define ICC_SRE_EL2_ENABLE (1 << 3) + ++#define ICC_SGI1R_TARGET_LIST_SHIFT 0 ++#define ICC_SGI1R_TARGET_LIST_MASK (0xffff << ICC_SGI1R_TARGET_LIST_SHIFT) ++#define ICC_SGI1R_AFFINITY_1_SHIFT 16 ++#define ICC_SGI1R_AFFINITY_1_MASK (0xff << ICC_SGI1R_AFFINITY_1_SHIFT) ++#define ICC_SGI1R_SGI_ID_SHIFT 24 ++#define ICC_SGI1R_SGI_ID_MASK (0xff << ICC_SGI1R_SGI_ID_SHIFT) ++#define ICC_SGI1R_AFFINITY_2_SHIFT 32 ++#define ICC_SGI1R_AFFINITY_2_MASK (0xffULL << ICC_SGI1R_AFFINITY_1_SHIFT) ++#define ICC_SGI1R_IRQ_ROUTING_MODE_BIT 40 ++#define ICC_SGI1R_AFFINITY_3_SHIFT 48 ++#define ICC_SGI1R_AFFINITY_3_MASK (0xffULL << ICC_SGI1R_AFFINITY_1_SHIFT) ++ + /* + * System register definitions + */ +@@ -188,6 +328,24 @@ + #ifndef __ASSEMBLY__ + + #include ++#include ++ ++/* ++ * We need a value to serve as a irq-type for LPIs. Choose one that will ++ * hopefully pique the interest of the reviewer. ++ */ ++#define GIC_IRQ_TYPE_LPI 0xa110c8ed ++ ++struct rdists { ++ struct { ++ void __iomem *rd_base; ++ struct page *pend_page; ++ phys_addr_t phys_base; ++ } __percpu *rdist; ++ struct page *prop_page; ++ int id_bits; ++ u64 flags; ++}; + + static inline void gic_write_eoir(u64 irq) + { +@@ -195,6 +353,13 @@ static inline void gic_write_eoir(u64 irq) + isb(); + } + ++struct irq_domain; ++int its_cpu_init(void); ++int its_init(struct device_node *node, struct rdists *rdists, ++ struct irq_domain *domain); ++int __its_msi_prepare(struct irq_domain *domain, u32 dev_id, ++ struct device *dev, int nvec, msi_alloc_info_t *info); ++ + #endif + + #endif +diff --git a/include/linux/irqchip/arm-gic.h b/include/linux/irqchip/arm-gic.h +index 13eed92..60b09ed 100644 +--- a/include/linux/irqchip/arm-gic.h ++++ b/include/linux/irqchip/arm-gic.h +@@ -106,6 +106,8 @@ static inline void gic_init(unsigned int nr, int start, + gic_init_bases(nr, start, dist, cpu, 0, NULL); + } + ++int gicv2m_of_init(struct device_node *node, struct irq_domain *parent); ++ + void gic_send_sgi(unsigned int cpu_id, unsigned int irq); + int gic_get_cpu_id(unsigned int cpu); + void gic_migrate_target(unsigned int new_cpu_id); +diff --git a/include/linux/irqdomain.h b/include/linux/irqdomain.h +index b0f9d16..3c5ca45 100644 +--- a/include/linux/irqdomain.h ++++ b/include/linux/irqdomain.h +@@ -33,15 +33,32 @@ + #define _LINUX_IRQDOMAIN_H + + #include ++#include + #include + + struct device_node; + struct irq_domain; + struct of_device_id; ++struct irq_chip; ++struct irq_data; + + /* Number of irqs reserved for a legacy isa controller */ + #define NUM_ISA_INTERRUPTS 16 + ++/* ++ * Should several domains have the same device node, but serve ++ * different purposes (for example one domain is for PCI/MSI, and the ++ * other for wired IRQs), they can be distinguished using a ++ * bus-specific token. Most domains are expected to only carry ++ * DOMAIN_BUS_ANY. ++ */ ++enum irq_domain_bus_token { ++ DOMAIN_BUS_ANY = 0, ++ DOMAIN_BUS_PCI_MSI, ++ DOMAIN_BUS_PLATFORM_MSI, ++ DOMAIN_BUS_NEXUS, ++}; ++ + /** + * struct irq_domain_ops - Methods for irq_domain objects + * @match: Match an interrupt controller device node to a host, returns +@@ -58,12 +75,23 @@ struct of_device_id; + * to setup the irq_desc when returning from map(). + */ + struct irq_domain_ops { +- int (*match)(struct irq_domain *d, struct device_node *node); ++ int (*match)(struct irq_domain *d, struct device_node *node, ++ enum irq_domain_bus_token bus_token); + int (*map)(struct irq_domain *d, unsigned int virq, irq_hw_number_t hw); + void (*unmap)(struct irq_domain *d, unsigned int virq); + int (*xlate)(struct irq_domain *d, struct device_node *node, + const u32 *intspec, unsigned int intsize, + unsigned long *out_hwirq, unsigned int *out_type); ++ ++#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY ++ /* extended V2 interfaces to support hierarchy irq_domains */ ++ int (*alloc)(struct irq_domain *d, unsigned int virq, ++ unsigned int nr_irqs, void *arg); ++ void (*free)(struct irq_domain *d, unsigned int virq, ++ unsigned int nr_irqs); ++ void (*activate)(struct irq_domain *d, struct irq_data *irq_data); ++ void (*deactivate)(struct irq_domain *d, struct irq_data *irq_data); ++#endif + }; + + extern struct irq_domain_ops irq_generic_chip_ops; +@@ -77,6 +105,7 @@ struct irq_domain_chip_generic; + * @ops: pointer to irq_domain methods + * @host_data: private data pointer for use by owner. Not touched by irq_domain + * core code. ++ * @flags: host per irq_domain flags + * + * Optional elements + * @of_node: Pointer to device tree nodes associated with the irq_domain. Used +@@ -84,6 +113,7 @@ struct irq_domain_chip_generic; + * @gc: Pointer to a list of generic chips. There is a helper function for + * setting up one or more generic chips for interrupt controllers + * drivers using the generic chip library which uses this pointer. ++ * @parent: Pointer to parent irq_domain to support hierarchy irq_domains + * + * Revmap data, used internally by irq_domain + * @revmap_direct_max_irq: The largest hwirq that can be set for controllers that +@@ -97,10 +127,15 @@ struct irq_domain { + const char *name; + const struct irq_domain_ops *ops; + void *host_data; ++ unsigned int flags; + + /* Optional data */ + struct device_node *of_node; ++ enum irq_domain_bus_token bus_token; + struct irq_domain_chip_generic *gc; ++#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY ++ struct irq_domain *parent; ++#endif + + /* reverse map data. The linear map gets appended to the irq_domain */ + irq_hw_number_t hwirq_max; +@@ -110,6 +145,22 @@ struct irq_domain { + unsigned int linear_revmap[]; + }; + ++/* Irq domain flags */ ++enum { ++ /* Irq domain is hierarchical */ ++ IRQ_DOMAIN_FLAG_HIERARCHY = (1 << 0), ++ ++ /* Core calls alloc/free recursive through the domain hierarchy. */ ++ IRQ_DOMAIN_FLAG_AUTO_RECURSIVE = (1 << 1), ++ ++ /* ++ * Flags starting from IRQ_DOMAIN_FLAG_NONCORE are reserved ++ * for implementation specific purposes and ignored by the ++ * core code. ++ */ ++ IRQ_DOMAIN_FLAG_NONCORE = (1 << 16), ++}; ++ + #ifdef CONFIG_IRQ_DOMAIN + struct irq_domain *__irq_domain_add(struct device_node *of_node, int size, + irq_hw_number_t hwirq_max, int direct_max, +@@ -126,9 +177,15 @@ struct irq_domain *irq_domain_add_legacy(struct device_node *of_node, + irq_hw_number_t first_hwirq, + const struct irq_domain_ops *ops, + void *host_data); +-extern struct irq_domain *irq_find_host(struct device_node *node); ++extern struct irq_domain *irq_find_matching_host(struct device_node *node, ++ enum irq_domain_bus_token bus_token); + extern void irq_set_default_host(struct irq_domain *host); + ++static inline struct irq_domain *irq_find_host(struct device_node *node) ++{ ++ return irq_find_matching_host(node, DOMAIN_BUS_ANY); ++} ++ + /** + * irq_domain_add_linear() - Allocate and register a linear revmap irq_domain. + * @of_node: pointer to interrupt controller's device tree node. +@@ -220,8 +277,74 @@ int irq_domain_xlate_onetwocell(struct irq_domain *d, struct device_node *ctrlr, + const u32 *intspec, unsigned int intsize, + irq_hw_number_t *out_hwirq, unsigned int *out_type); + ++/* V2 interfaces to support hierarchy IRQ domains. */ ++extern struct irq_data *irq_domain_get_irq_data(struct irq_domain *domain, ++ unsigned int virq); ++#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY ++extern struct irq_domain *irq_domain_add_hierarchy(struct irq_domain *parent, ++ unsigned int flags, unsigned int size, ++ struct device_node *node, ++ const struct irq_domain_ops *ops, void *host_data); ++extern int __irq_domain_alloc_irqs(struct irq_domain *domain, int irq_base, ++ unsigned int nr_irqs, int node, void *arg, ++ bool realloc); ++extern void irq_domain_free_irqs(unsigned int virq, unsigned int nr_irqs); ++extern void irq_domain_activate_irq(struct irq_data *irq_data); ++extern void irq_domain_deactivate_irq(struct irq_data *irq_data); ++ ++static inline int irq_domain_alloc_irqs(struct irq_domain *domain, ++ unsigned int nr_irqs, int node, void *arg) ++{ ++ return __irq_domain_alloc_irqs(domain, -1, nr_irqs, node, arg, false); ++} ++ ++extern int irq_domain_set_hwirq_and_chip(struct irq_domain *domain, ++ unsigned int virq, ++ irq_hw_number_t hwirq, ++ struct irq_chip *chip, ++ void *chip_data); ++extern void irq_domain_set_info(struct irq_domain *domain, unsigned int virq, ++ irq_hw_number_t hwirq, struct irq_chip *chip, ++ void *chip_data, irq_flow_handler_t handler, ++ void *handler_data, const char *handler_name); ++extern void irq_domain_reset_irq_data(struct irq_data *irq_data); ++extern void irq_domain_free_irqs_common(struct irq_domain *domain, ++ unsigned int virq, ++ unsigned int nr_irqs); ++extern void irq_domain_free_irqs_top(struct irq_domain *domain, ++ unsigned int virq, unsigned int nr_irqs); ++ ++extern int irq_domain_alloc_irqs_parent(struct irq_domain *domain, ++ unsigned int irq_base, ++ unsigned int nr_irqs, void *arg); ++ ++extern void irq_domain_free_irqs_parent(struct irq_domain *domain, ++ unsigned int irq_base, ++ unsigned int nr_irqs); ++ ++static inline bool irq_domain_is_hierarchy(struct irq_domain *domain) ++{ ++ return domain->flags & IRQ_DOMAIN_FLAG_HIERARCHY; ++} ++#else /* CONFIG_IRQ_DOMAIN_HIERARCHY */ ++static inline void irq_domain_activate_irq(struct irq_data *data) { } ++static inline void irq_domain_deactivate_irq(struct irq_data *data) { } ++static inline int irq_domain_alloc_irqs(struct irq_domain *domain, ++ unsigned int nr_irqs, int node, void *arg) ++{ ++ return -1; ++} ++ ++static inline bool irq_domain_is_hierarchy(struct irq_domain *domain) ++{ ++ return false; ++} ++#endif /* CONFIG_IRQ_DOMAIN_HIERARCHY */ ++ + #else /* CONFIG_IRQ_DOMAIN */ + static inline void irq_dispose_mapping(unsigned int virq) { } ++static inline void irq_domain_activate_irq(struct irq_data *data) { } ++static inline void irq_domain_deactivate_irq(struct irq_data *data) { } + #endif /* !CONFIG_IRQ_DOMAIN */ + + #endif /* _LINUX_IRQDOMAIN_H */ +diff --git a/include/linux/irqhandler.h b/include/linux/irqhandler.h +new file mode 100644 +index 0000000..62d5430 +--- /dev/null ++++ b/include/linux/irqhandler.h +@@ -0,0 +1,14 @@ ++#ifndef _LINUX_IRQHANDLER_H ++#define _LINUX_IRQHANDLER_H ++ ++/* ++ * Interrupt flow handler typedefs are defined here to avoid circular ++ * include dependencies. ++ */ ++ ++struct irq_desc; ++struct irq_data; ++typedef void (*irq_flow_handler_t)(unsigned int irq, struct irq_desc *desc); ++typedef void (*irq_preflow_handler_t)(struct irq_data *data); ++ ++#endif +diff --git a/include/linux/mm.h b/include/linux/mm.h +index 86a977b..a2d0dbb 100644 +--- a/include/linux/mm.h ++++ b/include/linux/mm.h +@@ -1208,6 +1208,28 @@ static inline int fixup_user_fault(struct task_struct *tsk, + } + #endif + ++extern void vma_do_file_update_time(struct vm_area_struct *, const char[], int); ++extern struct file *vma_do_pr_or_file(struct vm_area_struct *, const char[], ++ int); ++extern void vma_do_get_file(struct vm_area_struct *, const char[], int); ++extern void vma_do_fput(struct vm_area_struct *, const char[], int); ++ ++#define vma_file_update_time(vma) vma_do_file_update_time(vma, __func__, \ ++ __LINE__) ++#define vma_pr_or_file(vma) vma_do_pr_or_file(vma, __func__, \ ++ __LINE__) ++#define vma_get_file(vma) vma_do_get_file(vma, __func__, __LINE__) ++#define vma_fput(vma) vma_do_fput(vma, __func__, __LINE__) ++ ++#ifndef CONFIG_MMU ++extern struct file *vmr_do_pr_or_file(struct vm_region *, const char[], int); ++extern void vmr_do_fput(struct vm_region *, const char[], int); ++ ++#define vmr_pr_or_file(region) vmr_do_pr_or_file(region, __func__, \ ++ __LINE__) ++#define vmr_fput(region) vmr_do_fput(region, __func__, __LINE__) ++#endif /* !CONFIG_MMU */ ++ + extern int access_process_vm(struct task_struct *tsk, unsigned long addr, void *buf, int len, int write); + extern int access_remote_vm(struct mm_struct *mm, unsigned long addr, + void *buf, int len, int write); +diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h +index 6e0b286..8f374ed 100644 +--- a/include/linux/mm_types.h ++++ b/include/linux/mm_types.h +@@ -232,6 +232,7 @@ struct vm_region { + unsigned long vm_top; /* region allocated to here */ + unsigned long vm_pgoff; /* the offset in vm_file corresponding to vm_start */ + struct file *vm_file; /* the backing file or NULL */ ++ struct file *vm_prfile; /* the virtual backing file or NULL */ + + int vm_usage; /* region usage count (access under nommu_region_sem) */ + bool vm_icache_flushed : 1; /* true if the icache has been flushed for +@@ -300,6 +301,7 @@ struct vm_area_struct { + unsigned long vm_pgoff; /* Offset (within vm_file) in PAGE_SIZE + units, *not* PAGE_CACHE_SIZE */ + struct file * vm_file; /* File we map to (can be NULL). */ ++ struct file *vm_prfile; /* shadow of vm_file */ + void * vm_private_data; /* was vm_pte (shared mem) */ + + #ifndef CONFIG_MMU +diff --git a/include/linux/mmc/sdhci.h b/include/linux/mmc/sdhci.h +index dba793e..62d966a 100644 +--- a/include/linux/mmc/sdhci.h ++++ b/include/linux/mmc/sdhci.h +@@ -100,6 +100,10 @@ struct sdhci_host { + #define SDHCI_QUIRK2_BROKEN_DDR50 (1<<7) + /* Stop command (CMD12) can set Transfer Complete when not using MMC_RSP_BUSY */ + #define SDHCI_QUIRK2_STOP_WITH_TC (1<<8) ++/* Controller does not support 64-bit DMA */ ++#define SDHCI_QUIRK2_BROKEN_64_BIT_DMA (1<<9) ++/* Controller broken with using ACMD23 */ ++#define SDHCI_QUIRK2_ACMD23_BROKEN (1<<14) + + int irq; /* Device IRQ */ + void __iomem *ioaddr; /* Mapped address */ +@@ -130,6 +134,7 @@ struct sdhci_host { + #define SDHCI_SDIO_IRQ_ENABLED (1<<9) /* SDIO irq enabled */ + #define SDHCI_SDR104_NEEDS_TUNING (1<<10) /* SDR104/HS200 needs tuning */ + #define SDHCI_USING_RETUNING_TIMER (1<<11) /* Host is using a retuning timer for the card */ ++#define SDHCI_USE_64_BIT_DMA (1<<12) /* Use 64-bit DMA */ + + unsigned int version; /* SDHCI spec. version */ + +@@ -155,12 +160,19 @@ struct sdhci_host { + + int sg_count; /* Mapped sg entries */ + +- u8 *adma_desc; /* ADMA descriptor table */ +- u8 *align_buffer; /* Bounce buffer */ ++ void *adma_table; /* ADMA descriptor table */ ++ void *align_buffer; /* Bounce buffer */ ++ ++ size_t adma_table_sz; /* ADMA descriptor table size */ ++ size_t align_buffer_sz; /* Bounce buffer size */ + + dma_addr_t adma_addr; /* Mapped ADMA descr. table */ + dma_addr_t align_addr; /* Mapped bounce buffer */ + ++ unsigned int desc_sz; /* ADMA descriptor size */ ++ unsigned int align_sz; /* ADMA alignment */ ++ unsigned int align_mask; /* ADMA alignment mask */ ++ + struct tasklet_struct finish_tasklet; /* Tasklet structures */ + + struct timer_list timer; /* Timer for timeouts */ +diff --git a/include/linux/msi.h b/include/linux/msi.h +index 44f4746..788d65b 100644 +--- a/include/linux/msi.h ++++ b/include/linux/msi.h +@@ -10,17 +10,13 @@ struct msi_msg { + u32 data; /* 16 bits of msi message data */ + }; + ++extern int pci_msi_ignore_mask; + /* Helper functions */ + struct irq_data; + struct msi_desc; +-void mask_msi_irq(struct irq_data *data); +-void unmask_msi_irq(struct irq_data *data); +-void __read_msi_msg(struct msi_desc *entry, struct msi_msg *msg); ++struct pci_dev; + void __get_cached_msi_msg(struct msi_desc *entry, struct msi_msg *msg); +-void __write_msi_msg(struct msi_desc *entry, struct msi_msg *msg); +-void read_msi_msg(unsigned int irq, struct msi_msg *msg); + void get_cached_msi_msg(unsigned int irq, struct msi_msg *msg); +-void write_msi_msg(unsigned int irq, struct msi_msg *msg); + + struct msi_desc { + struct { +@@ -42,12 +38,63 @@ struct msi_desc { + void __iomem *mask_base; + u8 mask_pos; + }; +- struct pci_dev *dev; ++ struct device *dev; + + /* Last set MSI message */ + struct msi_msg msg; + }; + ++/* Helpers to hide struct msi_desc implementation details */ ++#define msi_desc_to_dev(desc) ((desc)->dev) ++#define dev_to_msi_list(dev) (&(dev)->msi_list) ++#define first_msi_entry(dev) \ ++ list_first_entry(dev_to_msi_list((dev)), struct msi_desc, list) ++#define for_each_msi_entry(desc, dev) \ ++ list_for_each_entry((desc), dev_to_msi_list((dev)), list) ++ ++#ifdef CONFIG_PCI_MSI ++#define first_pci_msi_entry(pdev) first_msi_entry(&(pdev)->dev) ++#define for_each_pci_msi_entry(desc, pdev) \ ++ for_each_msi_entry((desc), &(pdev)->dev) ++ ++struct pci_dev *msi_desc_to_pci_dev(struct msi_desc *desc); ++void *msi_desc_to_pci_sysdata(struct msi_desc *desc); ++#else /* CONFIG_PCI_MSI */ ++static inline void *msi_desc_to_pci_sysdata(struct msi_desc *desc) ++{ ++ return NULL; ++} ++#endif /* CONFIG_PCI_MSI */ ++ ++struct msi_desc *alloc_msi_entry(struct device *dev); ++void free_msi_entry(struct msi_desc *entry); ++void __pci_read_msi_msg(struct msi_desc *entry, struct msi_msg *msg); ++void __pci_write_msi_msg(struct msi_desc *entry, struct msi_msg *msg); ++void pci_write_msi_msg(unsigned int irq, struct msi_msg *msg); ++ ++u32 __pci_msix_desc_mask_irq(struct msi_desc *desc, u32 flag); ++u32 __pci_msi_desc_mask_irq(struct msi_desc *desc, u32 mask, u32 flag); ++void pci_msi_mask_irq(struct irq_data *data); ++void pci_msi_unmask_irq(struct irq_data *data); ++ ++/* Conversion helpers. Should be removed after merging */ ++static inline void __write_msi_msg(struct msi_desc *entry, struct msi_msg *msg) ++{ ++ __pci_write_msi_msg(entry, msg); ++} ++static inline void write_msi_msg(int irq, struct msi_msg *msg) ++{ ++ pci_write_msi_msg(irq, msg); ++} ++static inline void mask_msi_irq(struct irq_data *data) ++{ ++ pci_msi_mask_irq(data); ++} ++static inline void unmask_msi_irq(struct irq_data *data) ++{ ++ pci_msi_unmask_irq(data); ++} ++ + /* + * The arch hooks to setup up msi irqs. Those functions are + * implemented as weak symbols so that they /can/ be overriden by +@@ -61,18 +108,146 @@ void arch_restore_msi_irqs(struct pci_dev *dev); + + void default_teardown_msi_irqs(struct pci_dev *dev); + void default_restore_msi_irqs(struct pci_dev *dev); +-u32 default_msi_mask_irq(struct msi_desc *desc, u32 mask, u32 flag); +-u32 default_msix_mask_irq(struct msi_desc *desc, u32 flag); ++#define default_msi_mask_irq __msi_mask_irq ++#define default_msix_mask_irq __msix_mask_irq + +-struct msi_chip { ++struct msi_controller { + struct module *owner; + struct device *dev; + struct device_node *of_node; + struct list_head list; ++#ifdef CONFIG_GENERIC_MSI_IRQ_DOMAIN ++ struct irq_domain *domain; ++#endif + +- int (*setup_irq)(struct msi_chip *chip, struct pci_dev *dev, ++ int (*setup_irq)(struct msi_controller *chip, struct pci_dev *dev, + struct msi_desc *desc); +- void (*teardown_irq)(struct msi_chip *chip, unsigned int irq); ++ int (*setup_irqs)(struct msi_controller *chip, struct pci_dev *dev, ++ int nvec, int type); ++ void (*teardown_irq)(struct msi_controller *chip, unsigned int irq); + }; + ++#ifdef CONFIG_GENERIC_MSI_IRQ_DOMAIN ++ ++#include ++#include ++ ++struct irq_domain; ++struct irq_chip; ++struct device_node; ++struct msi_domain_info; ++ ++/** ++ * struct msi_domain_ops - MSI interrupt domain callbacks ++ * @get_hwirq: Retrieve the resulting hw irq number ++ * @msi_init: Domain specific init function for MSI interrupts ++ * @msi_free: Domain specific function to free a MSI interrupts ++ * @msi_check: Callback for verification of the domain/info/dev data ++ * @msi_prepare: Prepare the allocation of the interrupts in the domain ++ * @msi_finish: Optional callbacl to finalize the allocation ++ * @set_desc: Set the msi descriptor for an interrupt ++ * @handle_error: Optional error handler if the allocation fails ++ * ++ * @get_hwirq, @msi_init and @msi_free are callbacks used by ++ * msi_create_irq_domain() and related interfaces ++ * ++ * @msi_check, @msi_prepare, @msi_finish, @set_desc and @handle_error ++ * are callbacks used by msi_irq_domain_alloc_irqs() and related ++ * interfaces which are based on msi_desc. ++ */ ++struct msi_domain_ops { ++ irq_hw_number_t (*get_hwirq)(struct msi_domain_info *info, ++ msi_alloc_info_t *arg); ++ int (*msi_init)(struct irq_domain *domain, ++ struct msi_domain_info *info, ++ unsigned int virq, irq_hw_number_t hwirq, ++ msi_alloc_info_t *arg); ++ void (*msi_free)(struct irq_domain *domain, ++ struct msi_domain_info *info, ++ unsigned int virq); ++ int (*msi_check)(struct irq_domain *domain, ++ struct msi_domain_info *info, ++ struct device *dev); ++ int (*msi_prepare)(struct irq_domain *domain, ++ struct device *dev, int nvec, ++ msi_alloc_info_t *arg); ++ void (*msi_finish)(msi_alloc_info_t *arg, int retval); ++ void (*set_desc)(msi_alloc_info_t *arg, ++ struct msi_desc *desc); ++ int (*handle_error)(struct irq_domain *domain, ++ struct msi_desc *desc, int error); ++}; ++ ++/** ++ * struct msi_domain_info - MSI interrupt domain data ++ * @flags: Flags to decribe features and capabilities ++ * @ops: The callback data structure ++ * @chip: Optional: associated interrupt chip ++ * @chip_data: Optional: associated interrupt chip data ++ * @handler: Optional: associated interrupt flow handler ++ * @handler_data: Optional: associated interrupt flow handler data ++ * @handler_name: Optional: associated interrupt flow handler name ++ * @data: Optional: domain specific data ++ */ ++struct msi_domain_info { ++ u32 flags; ++ struct msi_domain_ops *ops; ++ struct irq_chip *chip; ++ void *chip_data; ++ irq_flow_handler_t handler; ++ void *handler_data; ++ const char *handler_name; ++ void *data; ++}; ++ ++/* Flags for msi_domain_info */ ++enum { ++ /* ++ * Init non implemented ops callbacks with default MSI domain ++ * callbacks. ++ */ ++ MSI_FLAG_USE_DEF_DOM_OPS = (1 << 0), ++ /* ++ * Init non implemented chip callbacks with default MSI chip ++ * callbacks. ++ */ ++ MSI_FLAG_USE_DEF_CHIP_OPS = (1 << 1), ++ /* Build identity map between hwirq and irq */ ++ MSI_FLAG_IDENTITY_MAP = (1 << 2), ++ /* Support multiple PCI MSI interrupts */ ++ MSI_FLAG_MULTI_PCI_MSI = (1 << 3), ++ /* Support PCI MSIX interrupts */ ++ MSI_FLAG_PCI_MSIX = (1 << 4), ++}; ++ ++int msi_domain_set_affinity(struct irq_data *data, const struct cpumask *mask, ++ bool force); ++ ++struct irq_domain *msi_create_irq_domain(struct device_node *of_node, ++ struct msi_domain_info *info, ++ struct irq_domain *parent); ++int msi_domain_alloc_irqs(struct irq_domain *domain, struct device *dev, ++ int nvec); ++void msi_domain_free_irqs(struct irq_domain *domain, struct device *dev); ++struct msi_domain_info *msi_get_domain_info(struct irq_domain *domain); ++ ++#endif /* CONFIG_GENERIC_MSI_IRQ_DOMAIN */ ++ ++#ifdef CONFIG_PCI_MSI_IRQ_DOMAIN ++void pci_msi_domain_write_msg(struct irq_data *irq_data, struct msi_msg *msg); ++struct irq_domain *pci_msi_create_irq_domain(struct device_node *node, ++ struct msi_domain_info *info, ++ struct irq_domain *parent); ++int pci_msi_domain_alloc_irqs(struct irq_domain *domain, struct pci_dev *dev, ++ int nvec, int type); ++void pci_msi_domain_free_irqs(struct irq_domain *domain, struct pci_dev *dev); ++struct irq_domain *pci_msi_create_default_irq_domain(struct device_node *node, ++ struct msi_domain_info *info, struct irq_domain *parent); ++ ++irq_hw_number_t pci_msi_domain_calc_hwirq(struct pci_dev *dev, ++ struct msi_desc *desc); ++int pci_msi_domain_check_cap(struct irq_domain *domain, ++ struct msi_domain_info *info, struct device *dev); ++#endif /* CONFIG_PCI_MSI_IRQ_DOMAIN */ ++ + #endif /* LINUX_MSI_H */ +diff --git a/include/linux/of.h b/include/linux/of.h +index 4a6a489..25111fb 100644 +--- a/include/linux/of.h ++++ b/include/linux/of.h +@@ -57,7 +57,6 @@ struct device_node { + struct device_node *child; + struct device_node *sibling; + struct device_node *next; /* next device of same type */ +- struct device_node *allnext; /* next in list of all nodes */ + struct kobject kobj; + unsigned long _flags; + void *data; +@@ -109,7 +108,7 @@ static inline void of_node_put(struct device_node *node) { } + #ifdef CONFIG_OF + + /* Pointer for first entry in chain of all nodes. */ +-extern struct device_node *of_allnodes; ++extern struct device_node *of_root; + extern struct device_node *of_chosen; + extern struct device_node *of_aliases; + extern struct device_node *of_stdout; +@@ -117,7 +116,7 @@ extern raw_spinlock_t devtree_lock; + + static inline bool of_have_populated_dt(void) + { +- return of_allnodes != NULL; ++ return of_root != NULL; + } + + static inline bool of_node_is_root(const struct device_node *node) +@@ -161,6 +160,7 @@ static inline void of_property_clear_flag(struct property *p, unsigned long flag + clear_bit(flag, &p->_flags); + } + ++extern struct device_node *__of_find_all_nodes(struct device_node *prev); + extern struct device_node *of_find_all_nodes(struct device_node *prev); + + /* +@@ -216,8 +216,9 @@ static inline const char *of_node_full_name(const struct device_node *np) + return np ? np->full_name : ""; + } + +-#define for_each_of_allnodes(dn) \ +- for (dn = of_allnodes; dn; dn = dn->allnext) ++#define for_each_of_allnodes_from(from, dn) \ ++ for (dn = __of_find_all_nodes(from); dn; dn = __of_find_all_nodes(dn)) ++#define for_each_of_allnodes(dn) for_each_of_allnodes_from(NULL, dn) + extern struct device_node *of_find_node_by_name(struct device_node *from, + const char *name); + extern struct device_node *of_find_node_by_type(struct device_node *from, +diff --git a/include/linux/of_device.h b/include/linux/of_device.h +index ef37021..22801b1 100644 +--- a/include/linux/of_device.h ++++ b/include/linux/of_device.h +@@ -53,6 +53,7 @@ static inline struct device_node *of_cpu_device_node_get(int cpu) + return of_node_get(cpu_dev->of_node); + } + ++void of_dma_configure(struct device *dev, struct device_node *np); + #else /* CONFIG_OF */ + + static inline int of_driver_match_device(struct device *dev, +@@ -90,6 +91,8 @@ static inline struct device_node *of_cpu_device_node_get(int cpu) + { + return NULL; + } ++static inline void of_dma_configure(struct device *dev, struct device_node *np) ++{} + #endif /* CONFIG_OF */ + + #endif /* _LINUX_OF_DEVICE_H */ +diff --git a/include/linux/of_iommu.h b/include/linux/of_iommu.h +index 51a560f..ffbe470 100644 +--- a/include/linux/of_iommu.h ++++ b/include/linux/of_iommu.h +@@ -1,12 +1,20 @@ + #ifndef __OF_IOMMU_H + #define __OF_IOMMU_H + ++#include ++#include ++#include ++ + #ifdef CONFIG_OF_IOMMU + + extern int of_get_dma_window(struct device_node *dn, const char *prefix, + int index, unsigned long *busno, dma_addr_t *addr, + size_t *size); + ++extern void of_iommu_init(void); ++extern struct iommu_ops *of_iommu_configure(struct device *dev, ++ struct device_node *master_np); ++ + #else + + static inline int of_get_dma_window(struct device_node *dn, const char *prefix, +@@ -16,6 +24,23 @@ static inline int of_get_dma_window(struct device_node *dn, const char *prefix, + return -EINVAL; + } + ++static inline void of_iommu_init(void) { } ++static inline struct iommu_ops *of_iommu_configure(struct device *dev, ++ struct device_node *master_np) ++{ ++ return NULL; ++} ++ + #endif /* CONFIG_OF_IOMMU */ + ++void of_iommu_set_ops(struct device_node *np, struct iommu_ops *ops); ++struct iommu_ops *of_iommu_get_ops(struct device_node *np); ++ ++extern struct of_device_id __iommu_of_table; ++ ++typedef int (*of_iommu_init_fn)(struct device_node *); ++ ++#define IOMMU_OF_DECLARE(name, compat, fn) \ ++ _OF_DECLARE(iommu, name, compat, fn, of_iommu_init_fn) ++ + #endif /* __OF_IOMMU_H */ +diff --git a/include/linux/of_irq.h b/include/linux/of_irq.h +index bfec136..563ad28 100644 +--- a/include/linux/of_irq.h ++++ b/include/linux/of_irq.h +@@ -69,6 +69,7 @@ static inline int of_irq_get_byname(struct device_node *dev, const char *name) + */ + extern unsigned int irq_of_parse_and_map(struct device_node *node, int index); + extern struct device_node *of_irq_find_parent(struct device_node *child); ++extern void of_msi_configure(struct device *dev, struct device_node *np); + + #else /* !CONFIG_OF */ + static inline unsigned int irq_of_parse_and_map(struct device_node *dev, +diff --git a/include/linux/of_pci.h b/include/linux/of_pci.h +index 1fd207e..29fd3fe 100644 +--- a/include/linux/of_pci.h ++++ b/include/linux/of_pci.h +@@ -16,6 +16,7 @@ int of_pci_get_devfn(struct device_node *np); + int of_irq_parse_and_map_pci(const struct pci_dev *dev, u8 slot, u8 pin); + int of_pci_parse_bus_range(struct device_node *node, struct resource *res); + int of_get_pci_domain_nr(struct device_node *node); ++void of_pci_dma_configure(struct pci_dev *pci_dev); + #else + static inline int of_irq_parse_pci(const struct pci_dev *pdev, struct of_phandle_args *out_irq) + { +@@ -50,6 +51,8 @@ of_get_pci_domain_nr(struct device_node *node) + { + return -1; + } ++ ++static inline void of_pci_dma_configure(struct pci_dev *pci_dev) { } + #endif + + #if defined(CONFIG_OF_ADDRESS) +@@ -59,13 +62,13 @@ int of_pci_get_host_bridge_resources(struct device_node *dev, + #endif + + #if defined(CONFIG_OF) && defined(CONFIG_PCI_MSI) +-int of_pci_msi_chip_add(struct msi_chip *chip); +-void of_pci_msi_chip_remove(struct msi_chip *chip); +-struct msi_chip *of_pci_find_msi_chip_by_node(struct device_node *of_node); ++int of_pci_msi_chip_add(struct msi_controller *chip); ++void of_pci_msi_chip_remove(struct msi_controller *chip); ++struct msi_controller *of_pci_find_msi_chip_by_node(struct device_node *of_node); + #else +-static inline int of_pci_msi_chip_add(struct msi_chip *chip) { return -EINVAL; } +-static inline void of_pci_msi_chip_remove(struct msi_chip *chip) { } +-static inline struct msi_chip * ++static inline int of_pci_msi_chip_add(struct msi_controller *chip) { return -EINVAL; } ++static inline void of_pci_msi_chip_remove(struct msi_controller *chip) { } ++static inline struct msi_controller * + of_pci_find_msi_chip_by_node(struct device_node *of_node) { return NULL; } + #endif + +diff --git a/include/linux/of_pdt.h b/include/linux/of_pdt.h +index c65a18a..7e09244 100644 +--- a/include/linux/of_pdt.h ++++ b/include/linux/of_pdt.h +@@ -39,7 +39,6 @@ extern void *prom_early_alloc(unsigned long size); + /* for building the device tree */ + extern void of_pdt_build_devicetree(phandle root_node, struct of_pdt_ops *ops); + +-extern void (*of_pdt_build_more)(struct device_node *dp, +- struct device_node ***nextp); ++extern void (*of_pdt_build_more)(struct device_node *dp); + + #endif /* _LINUX_OF_PDT_H */ +diff --git a/include/linux/of_platform.h b/include/linux/of_platform.h +index c2b0627..8a860f0 100644 +--- a/include/linux/of_platform.h ++++ b/include/linux/of_platform.h +@@ -84,4 +84,10 @@ static inline int of_platform_populate(struct device_node *root, + static inline void of_platform_depopulate(struct device *parent) { } + #endif + ++#ifdef CONFIG_OF_DYNAMIC ++extern void of_platform_register_reconfig_notifier(void); ++#else ++static inline void of_platform_register_reconfig_notifier(void) { } ++#endif ++ + #endif /* _LINUX_OF_PLATFORM_H */ +diff --git a/include/linux/pci.h b/include/linux/pci.h +index 7a34844..f28c88b 100644 +--- a/include/linux/pci.h ++++ b/include/linux/pci.h +@@ -29,6 +29,7 @@ + #include + #include + #include ++#include + #include + + #include +@@ -171,8 +172,8 @@ enum pci_dev_flags { + PCI_DEV_FLAGS_ASSIGNED = (__force pci_dev_flags_t) (1 << 2), + /* Flag for quirk use to store if quirk-specific ACS is enabled */ + PCI_DEV_FLAGS_ACS_ENABLED_QUIRK = (__force pci_dev_flags_t) (1 << 3), +- /* Flag to indicate the device uses dma_alias_devfn */ +- PCI_DEV_FLAGS_DMA_ALIAS_DEVFN = (__force pci_dev_flags_t) (1 << 4), ++ /* Flag to indicate the device uses dma_alias_devid */ ++ PCI_DEV_FLAGS_DMA_ALIAS_DEVID = (__force pci_dev_flags_t) (1 << 4), + /* Use a PCIe-to-PCI bridge alias even if !pci_is_pcie */ + PCI_DEV_FLAG_PCIE_BRIDGE_ALIAS = (__force pci_dev_flags_t) (1 << 5), + /* Do not use bus resets for device */ +@@ -278,7 +279,7 @@ struct pci_dev { + u8 rom_base_reg; /* which config register controls the ROM */ + u8 pin; /* which interrupt pin this device uses */ + u16 pcie_flags_reg; /* cached PCIe Capabilities Register */ +- u8 dma_alias_devfn;/* devfn of DMA alias, if any */ ++ u32 dma_alias_devid;/* devid of DMA alias */ + + struct pci_driver *driver; /* which driver has allocated this device */ + u64 dma_mask; /* Mask of the bits of bus address this +@@ -365,7 +366,6 @@ struct pci_dev { + struct bin_attribute *res_attr[DEVICE_COUNT_RESOURCE]; /* sysfs file for resources */ + struct bin_attribute *res_attr_wc[DEVICE_COUNT_RESOURCE]; /* sysfs file for WC mapping of resources */ + #ifdef CONFIG_PCI_MSI +- struct list_head msi_list; + const struct attribute_group **msi_irq_groups; + #endif + struct pci_vpd *vpd; +@@ -400,16 +400,10 @@ static inline int pci_channel_offline(struct pci_dev *pdev) + return (pdev->error_state != pci_channel_io_normal); + } + +-struct pci_host_bridge_window { +- struct list_head list; +- struct resource *res; /* host bridge aperture (CPU address) */ +- resource_size_t offset; /* bus address + offset = CPU address */ +-}; +- + struct pci_host_bridge { + struct device dev; + struct pci_bus *bus; /* root bus */ +- struct list_head windows; /* pci_host_bridge_windows */ ++ struct list_head windows; /* resource_entry */ + void (*release_fn)(struct pci_host_bridge *); + void *release_data; + }; +@@ -456,7 +450,7 @@ struct pci_bus { + struct resource busn_res; /* bus numbers routed to this bus */ + + struct pci_ops *ops; /* configuration access functions */ +- struct msi_chip *msi; /* MSI controller */ ++ struct msi_controller *msi; /* MSI controller */ + void *sysdata; /* hook for sys-specific extension */ + struct proc_dir_entry *procdir; /* directory entry in /proc/bus/pci */ + +@@ -516,6 +510,9 @@ static inline struct pci_dev *pci_upstream_bridge(struct pci_dev *dev) + return dev->bus->self; + } + ++struct device *pci_get_host_bridge_device(struct pci_dev *dev); ++void pci_put_host_bridge_device(struct device *dev); ++ + #ifdef CONFIG_PCI_MSI + static inline bool pci_dev_msi_enabled(struct pci_dev *pci_dev) + { +@@ -565,6 +562,7 @@ static inline int pcibios_err_to_errno(int err) + /* Low-level architecture-dependent routines */ + + struct pci_ops { ++ void __iomem *(*map_bus)(struct pci_bus *bus, unsigned int devfn, int where); + int (*read)(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 *val); + int (*write)(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 val); + }; +@@ -862,6 +860,16 @@ int pci_bus_write_config_word(struct pci_bus *bus, unsigned int devfn, + int where, u16 val); + int pci_bus_write_config_dword(struct pci_bus *bus, unsigned int devfn, + int where, u32 val); ++ ++int pci_generic_config_read(struct pci_bus *bus, unsigned int devfn, ++ int where, int size, u32 *val); ++int pci_generic_config_write(struct pci_bus *bus, unsigned int devfn, ++ int where, int size, u32 val); ++int pci_generic_config_read32(struct pci_bus *bus, unsigned int devfn, ++ int where, int size, u32 *val); ++int pci_generic_config_write32(struct pci_bus *bus, unsigned int devfn, ++ int where, int size, u32 val); ++ + struct pci_ops *pci_bus_set_ops(struct pci_bus *bus, struct pci_ops *ops); + + static inline int pci_read_config_byte(const struct pci_dev *dev, int where, u8 *val) +diff --git a/include/linux/phy.h b/include/linux/phy.h +index d090cfc..eda18a8 100644 +--- a/include/linux/phy.h ++++ b/include/linux/phy.h +@@ -700,6 +700,7 @@ struct phy_device *phy_device_create(struct mii_bus *bus, int addr, int phy_id, + struct phy_c45_device_ids *c45_ids); + struct phy_device *get_phy_device(struct mii_bus *bus, int addr, bool is_c45); + int phy_device_register(struct phy_device *phy); ++void phy_device_remove(struct phy_device *phydev); + int phy_init_hw(struct phy_device *phydev); + int phy_suspend(struct phy_device *phydev); + int phy_resume(struct phy_device *phydev); +diff --git a/include/linux/phy_fixed.h b/include/linux/phy_fixed.h +index f2ca1b4..fe5732d 100644 +--- a/include/linux/phy_fixed.h ++++ b/include/linux/phy_fixed.h +@@ -11,7 +11,7 @@ struct fixed_phy_status { + + struct device_node; + +-#ifdef CONFIG_FIXED_PHY ++#if IS_ENABLED(CONFIG_FIXED_PHY) + extern int fixed_phy_add(unsigned int irq, int phy_id, + struct fixed_phy_status *status); + extern struct phy_device *fixed_phy_register(unsigned int irq, +@@ -21,6 +21,9 @@ extern void fixed_phy_del(int phy_addr); + extern int fixed_phy_set_link_update(struct phy_device *phydev, + int (*link_update)(struct net_device *, + struct fixed_phy_status *)); ++extern int fixed_phy_update_state(struct phy_device *phydev, ++ const struct fixed_phy_status *status, ++ const struct fixed_phy_status *changed); + #else + static inline int fixed_phy_add(unsigned int irq, int phy_id, + struct fixed_phy_status *status) +@@ -43,6 +46,12 @@ static inline int fixed_phy_set_link_update(struct phy_device *phydev, + { + return -ENODEV; + } ++static inline int fixed_phy_update_state(struct phy_device *phydev, ++ const struct fixed_phy_status *status, ++ const struct fixed_phy_status *changed) ++{ ++ return -ENODEV; ++} + #endif /* CONFIG_FIXED_PHY */ + + #endif /* __PHY_FIXED_H */ +diff --git a/include/linux/resource_ext.h b/include/linux/resource_ext.h +new file mode 100644 +index 0000000..e2bf63d +--- /dev/null ++++ b/include/linux/resource_ext.h +@@ -0,0 +1,77 @@ ++/* ++ * Copyright (C) 2015, Intel Corporation ++ * Author: Jiang Liu ++ * ++ * This program is free software; you can redistribute it and/or modify it ++ * under the terms and conditions of the GNU General Public License, ++ * version 2, as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope it will be useful, but WITHOUT ++ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ++ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for ++ * more details. ++ */ ++#ifndef _LINUX_RESOURCE_EXT_H ++#define _LINUX_RESOURCE_EXT_H ++#include ++#include ++#include ++#include ++ ++/* Represent resource window for bridge devices */ ++struct resource_win { ++ struct resource res; /* In master (CPU) address space */ ++ resource_size_t offset; /* Translation offset for bridge */ ++}; ++ ++/* ++ * Common resource list management data structure and interfaces to support ++ * ACPI, PNP and PCI host bridge etc. ++ */ ++struct resource_entry { ++ struct list_head node; ++ struct resource *res; /* In master (CPU) address space */ ++ resource_size_t offset; /* Translation offset for bridge */ ++ struct resource __res; /* Default storage for res */ ++}; ++ ++extern struct resource_entry * ++resource_list_create_entry(struct resource *res, size_t extra_size); ++extern void resource_list_free(struct list_head *head); ++ ++static inline void resource_list_add(struct resource_entry *entry, ++ struct list_head *head) ++{ ++ list_add(&entry->node, head); ++} ++ ++static inline void resource_list_add_tail(struct resource_entry *entry, ++ struct list_head *head) ++{ ++ list_add_tail(&entry->node, head); ++} ++ ++static inline void resource_list_del(struct resource_entry *entry) ++{ ++ list_del(&entry->node); ++} ++ ++static inline void resource_list_free_entry(struct resource_entry *entry) ++{ ++ kfree(entry); ++} ++ ++static inline void ++resource_list_destroy_entry(struct resource_entry *entry) ++{ ++ resource_list_del(entry); ++ resource_list_free_entry(entry); ++} ++ ++#define resource_list_for_each_entry(entry, list) \ ++ list_for_each_entry((entry), (list), node) ++ ++#define resource_list_for_each_entry_safe(entry, tmp, list) \ ++ list_for_each_entry_safe((entry), (tmp), (list), node) ++ ++#endif /* _LINUX_RESOURCE_EXT_H */ +diff --git a/include/linux/splice.h b/include/linux/splice.h +index da2751d..2e0fca6 100644 +--- a/include/linux/splice.h ++++ b/include/linux/splice.h +@@ -83,4 +83,10 @@ extern void splice_shrink_spd(struct splice_pipe_desc *); + extern void spd_release_page(struct splice_pipe_desc *, unsigned int); + + extern const struct pipe_buf_operations page_cache_pipe_buf_ops; ++ ++extern long do_splice_from(struct pipe_inode_info *pipe, struct file *out, ++ loff_t *ppos, size_t len, unsigned int flags); ++extern long do_splice_to(struct file *in, loff_t *ppos, ++ struct pipe_inode_info *pipe, size_t len, ++ unsigned int flags); + #endif +diff --git a/include/linux/usb/quirks.h b/include/linux/usb/quirks.h +index 9948c87..1d0043d 100644 +--- a/include/linux/usb/quirks.h ++++ b/include/linux/usb/quirks.h +@@ -47,4 +47,7 @@ + /* device generates spurious wakeup, ignore remote wakeup capability */ + #define USB_QUIRK_IGNORE_REMOTE_WAKEUP BIT(9) + ++/* device can't handle Link Power Management */ ++#define USB_QUIRK_NO_LPM BIT(10) ++ + #endif /* __LINUX_USB_QUIRKS_H */ +diff --git a/include/trace/events/iommu.h b/include/trace/events/iommu.h +index a8f5c32..2c7befb 100644 +--- a/include/trace/events/iommu.h ++++ b/include/trace/events/iommu.h +@@ -83,7 +83,7 @@ DEFINE_EVENT(iommu_device_event, detach_device_from_domain, + TP_ARGS(dev) + ); + +-DECLARE_EVENT_CLASS(iommu_map_unmap, ++TRACE_EVENT(map, + + TP_PROTO(unsigned long iova, phys_addr_t paddr, size_t size), + +@@ -92,7 +92,7 @@ DECLARE_EVENT_CLASS(iommu_map_unmap, + TP_STRUCT__entry( + __field(u64, iova) + __field(u64, paddr) +- __field(int, size) ++ __field(size_t, size) + ), + + TP_fast_assign( +@@ -101,26 +101,31 @@ DECLARE_EVENT_CLASS(iommu_map_unmap, + __entry->size = size; + ), + +- TP_printk("IOMMU: iova=0x%016llx paddr=0x%016llx size=0x%x", ++ TP_printk("IOMMU: iova=0x%016llx paddr=0x%016llx size=%zu", + __entry->iova, __entry->paddr, __entry->size + ) + ); + +-DEFINE_EVENT(iommu_map_unmap, map, ++TRACE_EVENT(unmap, + +- TP_PROTO(unsigned long iova, phys_addr_t paddr, size_t size), +- +- TP_ARGS(iova, paddr, size) +-); ++ TP_PROTO(unsigned long iova, size_t size, size_t unmapped_size), + +-DEFINE_EVENT_PRINT(iommu_map_unmap, unmap, ++ TP_ARGS(iova, size, unmapped_size), + +- TP_PROTO(unsigned long iova, phys_addr_t paddr, size_t size), ++ TP_STRUCT__entry( ++ __field(u64, iova) ++ __field(size_t, size) ++ __field(size_t, unmapped_size) ++ ), + +- TP_ARGS(iova, paddr, size), ++ TP_fast_assign( ++ __entry->iova = iova; ++ __entry->size = size; ++ __entry->unmapped_size = unmapped_size; ++ ), + +- TP_printk("IOMMU: iova=0x%016llx size=0x%x", +- __entry->iova, __entry->size ++ TP_printk("IOMMU: iova=0x%016llx size=%zu unmapped_size=%zu", ++ __entry->iova, __entry->size, __entry->unmapped_size + ) + ); + +diff --git a/include/uapi/linux/Kbuild b/include/uapi/linux/Kbuild +index 8523f9b..11f8f74 100644 +--- a/include/uapi/linux/Kbuild ++++ b/include/uapi/linux/Kbuild +@@ -56,6 +56,7 @@ header-y += atmppp.h + header-y += atmsap.h + header-y += atmsvc.h + header-y += audit.h ++header-y += aufs_type.h + header-y += auto_fs.h + header-y += auto_fs4.h + header-y += auxvec.h +diff --git a/include/uapi/linux/aufs_type.h b/include/uapi/linux/aufs_type.h +new file mode 100644 +index 0000000..75915f8 +--- /dev/null ++++ b/include/uapi/linux/aufs_type.h +@@ -0,0 +1,419 @@ ++/* ++ * Copyright (C) 2005-2016 Junjiro R. Okajima ++ * ++ * This program, aufs is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++#ifndef __AUFS_TYPE_H__ ++#define __AUFS_TYPE_H__ ++ ++#define AUFS_NAME "aufs" ++ ++#ifdef __KERNEL__ ++/* ++ * define it before including all other headers. ++ * sched.h may use pr_* macros before defining "current", so define the ++ * no-current version first, and re-define later. ++ */ ++#define pr_fmt(fmt) AUFS_NAME " %s:%d: " fmt, __func__, __LINE__ ++#include ++#undef pr_fmt ++#define pr_fmt(fmt) \ ++ AUFS_NAME " %s:%d:%.*s[%d]: " fmt, __func__, __LINE__, \ ++ (int)sizeof(current->comm), current->comm, current->pid ++#else ++#include ++#include ++#endif /* __KERNEL__ */ ++ ++#include ++ ++#define AUFS_VERSION "3.18.25+-20160509" ++ ++/* todo? move this to linux-2.6.19/include/magic.h */ ++#define AUFS_SUPER_MAGIC ('a' << 24 | 'u' << 16 | 'f' << 8 | 's') ++ ++/* ---------------------------------------------------------------------- */ ++ ++#ifdef CONFIG_AUFS_BRANCH_MAX_127 ++typedef int8_t aufs_bindex_t; ++#define AUFS_BRANCH_MAX 127 ++#else ++typedef int16_t aufs_bindex_t; ++#ifdef CONFIG_AUFS_BRANCH_MAX_511 ++#define AUFS_BRANCH_MAX 511 ++#elif defined(CONFIG_AUFS_BRANCH_MAX_1023) ++#define AUFS_BRANCH_MAX 1023 ++#elif defined(CONFIG_AUFS_BRANCH_MAX_32767) ++#define AUFS_BRANCH_MAX 32767 ++#endif ++#endif ++ ++#ifdef __KERNEL__ ++#ifndef AUFS_BRANCH_MAX ++#error unknown CONFIG_AUFS_BRANCH_MAX value ++#endif ++#endif /* __KERNEL__ */ ++ ++/* ---------------------------------------------------------------------- */ ++ ++#define AUFS_FSTYPE AUFS_NAME ++ ++#define AUFS_ROOT_INO 2 ++#define AUFS_FIRST_INO 11 ++ ++#define AUFS_WH_PFX ".wh." ++#define AUFS_WH_PFX_LEN ((int)sizeof(AUFS_WH_PFX) - 1) ++#define AUFS_WH_TMP_LEN 4 ++/* a limit for rmdir/rename a dir and copyup */ ++#define AUFS_MAX_NAMELEN (NAME_MAX \ ++ - AUFS_WH_PFX_LEN * 2 /* doubly whiteouted */\ ++ - 1 /* dot */\ ++ - AUFS_WH_TMP_LEN) /* hex */ ++#define AUFS_XINO_FNAME "." AUFS_NAME ".xino" ++#define AUFS_XINO_DEFPATH "/tmp/" AUFS_XINO_FNAME ++#define AUFS_XINO_DEF_SEC 30 /* seconds */ ++#define AUFS_XINO_DEF_TRUNC 45 /* percentage */ ++#define AUFS_DIRWH_DEF 3 ++#define AUFS_RDCACHE_DEF 10 /* seconds */ ++#define AUFS_RDCACHE_MAX 3600 /* seconds */ ++#define AUFS_RDBLK_DEF 512 /* bytes */ ++#define AUFS_RDHASH_DEF 32 ++#define AUFS_WKQ_NAME AUFS_NAME "d" ++#define AUFS_MFS_DEF_SEC 30 /* seconds */ ++#define AUFS_MFS_MAX_SEC 3600 /* seconds */ ++#define AUFS_FHSM_CACHE_DEF_SEC 30 /* seconds */ ++#define AUFS_PLINK_WARN 50 /* number of plinks in a single bucket */ ++ ++/* pseudo-link maintenace under /proc */ ++#define AUFS_PLINK_MAINT_NAME "plink_maint" ++#define AUFS_PLINK_MAINT_DIR "fs/" AUFS_NAME ++#define AUFS_PLINK_MAINT_PATH AUFS_PLINK_MAINT_DIR "/" AUFS_PLINK_MAINT_NAME ++ ++#define AUFS_DIROPQ_NAME AUFS_WH_PFX ".opq" /* whiteouted doubly */ ++#define AUFS_WH_DIROPQ AUFS_WH_PFX AUFS_DIROPQ_NAME ++ ++#define AUFS_BASE_NAME AUFS_WH_PFX AUFS_NAME ++#define AUFS_PLINKDIR_NAME AUFS_WH_PFX "plnk" ++#define AUFS_ORPHDIR_NAME AUFS_WH_PFX "orph" ++ ++/* doubly whiteouted */ ++#define AUFS_WH_BASE AUFS_WH_PFX AUFS_BASE_NAME ++#define AUFS_WH_PLINKDIR AUFS_WH_PFX AUFS_PLINKDIR_NAME ++#define AUFS_WH_ORPHDIR AUFS_WH_PFX AUFS_ORPHDIR_NAME ++ ++/* branch permissions and attributes */ ++#define AUFS_BRPERM_RW "rw" ++#define AUFS_BRPERM_RO "ro" ++#define AUFS_BRPERM_RR "rr" ++#define AUFS_BRATTR_COO_REG "coo_reg" ++#define AUFS_BRATTR_COO_ALL "coo_all" ++#define AUFS_BRATTR_FHSM "fhsm" ++#define AUFS_BRATTR_UNPIN "unpin" ++#define AUFS_BRATTR_ICEX "icex" ++#define AUFS_BRATTR_ICEX_SEC "icexsec" ++#define AUFS_BRATTR_ICEX_SYS "icexsys" ++#define AUFS_BRATTR_ICEX_TR "icextr" ++#define AUFS_BRATTR_ICEX_USR "icexusr" ++#define AUFS_BRATTR_ICEX_OTH "icexoth" ++#define AUFS_BRRATTR_WH "wh" ++#define AUFS_BRWATTR_NLWH "nolwh" ++#define AUFS_BRWATTR_MOO "moo" ++ ++#define AuBrPerm_RW 1 /* writable, hardlinkable wh */ ++#define AuBrPerm_RO (1 << 1) /* readonly */ ++#define AuBrPerm_RR (1 << 2) /* natively readonly */ ++#define AuBrPerm_Mask (AuBrPerm_RW | AuBrPerm_RO | AuBrPerm_RR) ++ ++#define AuBrAttr_COO_REG (1 << 3) /* copy-up on open */ ++#define AuBrAttr_COO_ALL (1 << 4) ++#define AuBrAttr_COO_Mask (AuBrAttr_COO_REG | AuBrAttr_COO_ALL) ++ ++#define AuBrAttr_FHSM (1 << 5) /* file-based hsm */ ++#define AuBrAttr_UNPIN (1 << 6) /* rename-able top dir of ++ branch. meaningless since ++ linux-3.18-rc1 */ ++ ++/* ignore error in copying XATTR */ ++#define AuBrAttr_ICEX_SEC (1 << 7) ++#define AuBrAttr_ICEX_SYS (1 << 8) ++#define AuBrAttr_ICEX_TR (1 << 9) ++#define AuBrAttr_ICEX_USR (1 << 10) ++#define AuBrAttr_ICEX_OTH (1 << 11) ++#define AuBrAttr_ICEX (AuBrAttr_ICEX_SEC \ ++ | AuBrAttr_ICEX_SYS \ ++ | AuBrAttr_ICEX_TR \ ++ | AuBrAttr_ICEX_USR \ ++ | AuBrAttr_ICEX_OTH) ++ ++#define AuBrRAttr_WH (1 << 12) /* whiteout-able */ ++#define AuBrRAttr_Mask AuBrRAttr_WH ++ ++#define AuBrWAttr_NoLinkWH (1 << 13) /* un-hardlinkable whiteouts */ ++#define AuBrWAttr_MOO (1 << 14) /* move-up on open */ ++#define AuBrWAttr_Mask (AuBrWAttr_NoLinkWH | AuBrWAttr_MOO) ++ ++#define AuBrAttr_CMOO_Mask (AuBrAttr_COO_Mask | AuBrWAttr_MOO) ++ ++/* #warning test userspace */ ++#ifdef __KERNEL__ ++#ifndef CONFIG_AUFS_FHSM ++#undef AuBrAttr_FHSM ++#define AuBrAttr_FHSM 0 ++#endif ++#ifndef CONFIG_AUFS_XATTR ++#undef AuBrAttr_ICEX ++#define AuBrAttr_ICEX 0 ++#undef AuBrAttr_ICEX_SEC ++#define AuBrAttr_ICEX_SEC 0 ++#undef AuBrAttr_ICEX_SYS ++#define AuBrAttr_ICEX_SYS 0 ++#undef AuBrAttr_ICEX_TR ++#define AuBrAttr_ICEX_TR 0 ++#undef AuBrAttr_ICEX_USR ++#define AuBrAttr_ICEX_USR 0 ++#undef AuBrAttr_ICEX_OTH ++#define AuBrAttr_ICEX_OTH 0 ++#endif ++#endif ++ ++/* the longest combination */ ++/* AUFS_BRATTR_ICEX and AUFS_BRATTR_ICEX_TR don't affect here */ ++#define AuBrPermStrSz sizeof(AUFS_BRPERM_RW \ ++ "+" AUFS_BRATTR_COO_REG \ ++ "+" AUFS_BRATTR_FHSM \ ++ "+" AUFS_BRATTR_UNPIN \ ++ "+" AUFS_BRATTR_ICEX_SEC \ ++ "+" AUFS_BRATTR_ICEX_SYS \ ++ "+" AUFS_BRATTR_ICEX_USR \ ++ "+" AUFS_BRATTR_ICEX_OTH \ ++ "+" AUFS_BRWATTR_NLWH) ++ ++typedef struct { ++ char a[AuBrPermStrSz]; ++} au_br_perm_str_t; ++ ++static inline int au_br_writable(int brperm) ++{ ++ return brperm & AuBrPerm_RW; ++} ++ ++static inline int au_br_whable(int brperm) ++{ ++ return brperm & (AuBrPerm_RW | AuBrRAttr_WH); ++} ++ ++static inline int au_br_wh_linkable(int brperm) ++{ ++ return !(brperm & AuBrWAttr_NoLinkWH); ++} ++ ++static inline int au_br_cmoo(int brperm) ++{ ++ return brperm & AuBrAttr_CMOO_Mask; ++} ++ ++static inline int au_br_fhsm(int brperm) ++{ ++ return brperm & AuBrAttr_FHSM; ++} ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* ioctl */ ++enum { ++ /* readdir in userspace */ ++ AuCtl_RDU, ++ AuCtl_RDU_INO, ++ ++ AuCtl_WBR_FD, /* pathconf wrapper */ ++ AuCtl_IBUSY, /* busy inode */ ++ AuCtl_MVDOWN, /* move-down */ ++ AuCtl_BR, /* info about branches */ ++ AuCtl_FHSM_FD /* connection for fhsm */ ++}; ++ ++/* borrowed from linux/include/linux/kernel.h */ ++#ifndef ALIGN ++#define ALIGN(x, a) __ALIGN_MASK(x, (typeof(x))(a)-1) ++#define __ALIGN_MASK(x, mask) (((x)+(mask))&~(mask)) ++#endif ++ ++/* borrowed from linux/include/linux/compiler-gcc3.h */ ++#ifndef __aligned ++#define __aligned(x) __attribute__((aligned(x))) ++#endif ++ ++#ifdef __KERNEL__ ++#ifndef __packed ++#define __packed __attribute__((packed)) ++#endif ++#endif ++ ++struct au_rdu_cookie { ++ uint64_t h_pos; ++ int16_t bindex; ++ uint8_t flags; ++ uint8_t pad; ++ uint32_t generation; ++} __aligned(8); ++ ++struct au_rdu_ent { ++ uint64_t ino; ++ int16_t bindex; ++ uint8_t type; ++ uint8_t nlen; ++ uint8_t wh; ++ char name[0]; ++} __aligned(8); ++ ++static inline int au_rdu_len(int nlen) ++{ ++ /* include the terminating NULL */ ++ return ALIGN(sizeof(struct au_rdu_ent) + nlen + 1, ++ sizeof(uint64_t)); ++} ++ ++union au_rdu_ent_ul { ++ struct au_rdu_ent __user *e; ++ uint64_t ul; ++}; ++ ++enum { ++ AufsCtlRduV_SZ, ++ AufsCtlRduV_End ++}; ++ ++struct aufs_rdu { ++ /* input */ ++ union { ++ uint64_t sz; /* AuCtl_RDU */ ++ uint64_t nent; /* AuCtl_RDU_INO */ ++ }; ++ union au_rdu_ent_ul ent; ++ uint16_t verify[AufsCtlRduV_End]; ++ ++ /* input/output */ ++ uint32_t blk; ++ ++ /* output */ ++ union au_rdu_ent_ul tail; ++ /* number of entries which were added in a single call */ ++ uint64_t rent; ++ uint8_t full; ++ uint8_t shwh; ++ ++ struct au_rdu_cookie cookie; ++} __aligned(8); ++ ++/* ---------------------------------------------------------------------- */ ++ ++struct aufs_wbr_fd { ++ uint32_t oflags; ++ int16_t brid; ++} __aligned(8); ++ ++/* ---------------------------------------------------------------------- */ ++ ++struct aufs_ibusy { ++ uint64_t ino, h_ino; ++ int16_t bindex; ++} __aligned(8); ++ ++/* ---------------------------------------------------------------------- */ ++ ++/* error code for move-down */ ++/* the actual message strings are implemented in aufs-util.git */ ++enum { ++ EAU_MVDOWN_OPAQUE = 1, ++ EAU_MVDOWN_WHITEOUT, ++ EAU_MVDOWN_UPPER, ++ EAU_MVDOWN_BOTTOM, ++ EAU_MVDOWN_NOUPPER, ++ EAU_MVDOWN_NOLOWERBR, ++ EAU_Last ++}; ++ ++/* flags for move-down */ ++#define AUFS_MVDOWN_DMSG 1 ++#define AUFS_MVDOWN_OWLOWER (1 << 1) /* overwrite lower */ ++#define AUFS_MVDOWN_KUPPER (1 << 2) /* keep upper */ ++#define AUFS_MVDOWN_ROLOWER (1 << 3) /* do even if lower is RO */ ++#define AUFS_MVDOWN_ROLOWER_R (1 << 4) /* did on lower RO */ ++#define AUFS_MVDOWN_ROUPPER (1 << 5) /* do even if upper is RO */ ++#define AUFS_MVDOWN_ROUPPER_R (1 << 6) /* did on upper RO */ ++#define AUFS_MVDOWN_BRID_UPPER (1 << 7) /* upper brid */ ++#define AUFS_MVDOWN_BRID_LOWER (1 << 8) /* lower brid */ ++#define AUFS_MVDOWN_FHSM_LOWER (1 << 9) /* find fhsm attr for lower */ ++#define AUFS_MVDOWN_STFS (1 << 10) /* req. stfs */ ++#define AUFS_MVDOWN_STFS_FAILED (1 << 11) /* output: stfs is unusable */ ++#define AUFS_MVDOWN_BOTTOM (1 << 12) /* output: no more lowers */ ++ ++/* index for move-down */ ++enum { ++ AUFS_MVDOWN_UPPER, ++ AUFS_MVDOWN_LOWER, ++ AUFS_MVDOWN_NARRAY ++}; ++ ++/* ++ * additional info of move-down ++ * number of free blocks and inodes. ++ * subset of struct kstatfs, but smaller and always 64bit. ++ */ ++struct aufs_stfs { ++ uint64_t f_blocks; ++ uint64_t f_bavail; ++ uint64_t f_files; ++ uint64_t f_ffree; ++}; ++ ++struct aufs_stbr { ++ int16_t brid; /* optional input */ ++ int16_t bindex; /* output */ ++ struct aufs_stfs stfs; /* output when AUFS_MVDOWN_STFS set */ ++} __aligned(8); ++ ++struct aufs_mvdown { ++ uint32_t flags; /* input/output */ ++ struct aufs_stbr stbr[AUFS_MVDOWN_NARRAY]; /* input/output */ ++ int8_t au_errno; /* output */ ++} __aligned(8); ++ ++/* ---------------------------------------------------------------------- */ ++ ++union aufs_brinfo { ++ /* PATH_MAX may differ between kernel-space and user-space */ ++ char _spacer[4096]; ++ struct { ++ int16_t id; ++ int perm; ++ char path[0]; ++ }; ++} __aligned(8); ++ ++/* ---------------------------------------------------------------------- */ ++ ++#define AuCtlType 'A' ++#define AUFS_CTL_RDU _IOWR(AuCtlType, AuCtl_RDU, struct aufs_rdu) ++#define AUFS_CTL_RDU_INO _IOWR(AuCtlType, AuCtl_RDU_INO, struct aufs_rdu) ++#define AUFS_CTL_WBR_FD _IOW(AuCtlType, AuCtl_WBR_FD, \ ++ struct aufs_wbr_fd) ++#define AUFS_CTL_IBUSY _IOWR(AuCtlType, AuCtl_IBUSY, struct aufs_ibusy) ++#define AUFS_CTL_MVDOWN _IOWR(AuCtlType, AuCtl_MVDOWN, \ ++ struct aufs_mvdown) ++#define AUFS_CTL_BRINFO _IOW(AuCtlType, AuCtl_BR, union aufs_brinfo) ++#define AUFS_CTL_FHSM_FD _IOW(AuCtlType, AuCtl_FHSM_FD, int) ++ ++#endif /* __AUFS_TYPE_H__ */ +diff --git a/include/uapi/linux/vfio.h b/include/uapi/linux/vfio.h +index 29715d2..6eca3ee 100644 +--- a/include/uapi/linux/vfio.h ++++ b/include/uapi/linux/vfio.h +@@ -160,6 +160,10 @@ struct vfio_device_info { + __u32 flags; + #define VFIO_DEVICE_FLAGS_RESET (1 << 0) /* Device supports reset */ + #define VFIO_DEVICE_FLAGS_PCI (1 << 1) /* vfio-pci device */ ++#define VFIO_DEVICE_FLAGS_PLATFORM (1 << 2) /* vfio-platform device */ ++#define VFIO_DEVICE_FLAGS_AMBA (1 << 3) /* vfio-amba device */ ++#define VFIO_DEVICE_FLAGS_FSL_MC (1 << 4) /* vfio Freescale MC device */ ++ + __u32 num_regions; /* Max region index + 1 */ + __u32 num_irqs; /* Max IRQ index + 1 */ + }; +@@ -404,6 +408,7 @@ struct vfio_iommu_type1_dma_map { + __u32 flags; + #define VFIO_DMA_MAP_FLAG_READ (1 << 0) /* readable from device */ + #define VFIO_DMA_MAP_FLAG_WRITE (1 << 1) /* writable from device */ ++#define VFIO_DMA_MAP_FLAG_MMIO (1 << 2) /* non-cachable device region */ + __u64 vaddr; /* Process virtual address */ + __u64 iova; /* IO virtual address */ + __u64 size; /* Size of mapping (bytes) */ +diff --git a/kernel/fork.c b/kernel/fork.c +index 0a4f601..67ecb91 100644 +--- a/kernel/fork.c ++++ b/kernel/fork.c +@@ -430,7 +430,7 @@ static int dup_mmap(struct mm_struct *mm, struct mm_struct *oldmm) + struct inode *inode = file_inode(file); + struct address_space *mapping = file->f_mapping; + +- get_file(file); ++ vma_get_file(tmp); + if (tmp->vm_flags & VM_DENYWRITE) + atomic_dec(&inode->i_writecount); + mutex_lock(&mapping->i_mmap_mutex); +diff --git a/kernel/irq/Kconfig b/kernel/irq/Kconfig +index 225086b..9a76e3b 100644 +--- a/kernel/irq/Kconfig ++++ b/kernel/irq/Kconfig +@@ -55,6 +55,21 @@ config GENERIC_IRQ_CHIP + config IRQ_DOMAIN + bool + ++# Support for hierarchical irq domains ++config IRQ_DOMAIN_HIERARCHY ++ bool ++ select IRQ_DOMAIN ++ ++# Generic MSI interrupt support ++config GENERIC_MSI_IRQ ++ bool ++ ++# Generic MSI hierarchical interrupt domain support ++config GENERIC_MSI_IRQ_DOMAIN ++ bool ++ select IRQ_DOMAIN_HIERARCHY ++ select GENERIC_MSI_IRQ ++ + config HANDLE_DOMAIN_IRQ + bool + +diff --git a/kernel/irq/Makefile b/kernel/irq/Makefile +index fff1738..d121235 100644 +--- a/kernel/irq/Makefile ++++ b/kernel/irq/Makefile +@@ -6,3 +6,4 @@ obj-$(CONFIG_IRQ_DOMAIN) += irqdomain.o + obj-$(CONFIG_PROC_FS) += proc.o + obj-$(CONFIG_GENERIC_PENDING_IRQ) += migration.o + obj-$(CONFIG_PM_SLEEP) += pm.o ++obj-$(CONFIG_GENERIC_MSI_IRQ) += msi.o +diff --git a/kernel/irq/chip.c b/kernel/irq/chip.c +index e5202f0..55dd2fb 100644 +--- a/kernel/irq/chip.c ++++ b/kernel/irq/chip.c +@@ -15,6 +15,7 @@ + #include + #include + #include ++#include + + #include + +@@ -178,6 +179,7 @@ int irq_startup(struct irq_desc *desc, bool resend) + irq_state_clr_disabled(desc); + desc->depth = 0; + ++ irq_domain_activate_irq(&desc->irq_data); + if (desc->irq_data.chip->irq_startup) { + ret = desc->irq_data.chip->irq_startup(&desc->irq_data); + irq_state_clr_masked(desc); +@@ -199,6 +201,7 @@ void irq_shutdown(struct irq_desc *desc) + desc->irq_data.chip->irq_disable(&desc->irq_data); + else + desc->irq_data.chip->irq_mask(&desc->irq_data); ++ irq_domain_deactivate_irq(&desc->irq_data); + irq_state_set_masked(desc); + } + +@@ -728,7 +731,30 @@ __irq_set_handler(unsigned int irq, irq_flow_handler_t handle, int is_chained, + if (!handle) { + handle = handle_bad_irq; + } else { +- if (WARN_ON(desc->irq_data.chip == &no_irq_chip)) ++ struct irq_data *irq_data = &desc->irq_data; ++#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY ++ /* ++ * With hierarchical domains we might run into a ++ * situation where the outermost chip is not yet set ++ * up, but the inner chips are there. Instead of ++ * bailing we install the handler, but obviously we ++ * cannot enable/startup the interrupt at this point. ++ */ ++ while (irq_data) { ++ if (irq_data->chip != &no_irq_chip) ++ break; ++ /* ++ * Bail out if the outer chip is not set up ++ * and the interrrupt supposed to be started ++ * right away. ++ */ ++ if (WARN_ON(is_chained)) ++ goto out; ++ /* Try the parent */ ++ irq_data = irq_data->parent_data; ++ } ++#endif ++ if (WARN_ON(!irq_data || irq_data->chip == &no_irq_chip)) + goto out; + } + +@@ -847,3 +873,138 @@ void irq_cpu_offline(void) + raw_spin_unlock_irqrestore(&desc->lock, flags); + } + } ++ ++#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY ++/** ++ * irq_chip_ack_parent - Acknowledge the parent interrupt ++ * @data: Pointer to interrupt specific data ++ */ ++void irq_chip_ack_parent(struct irq_data *data) ++{ ++ data = data->parent_data; ++ data->chip->irq_ack(data); ++} ++ ++/** ++ * irq_chip_mask_parent - Mask the parent interrupt ++ * @data: Pointer to interrupt specific data ++ */ ++void irq_chip_mask_parent(struct irq_data *data) ++{ ++ data = data->parent_data; ++ data->chip->irq_mask(data); ++} ++ ++/** ++ * irq_chip_unmask_parent - Unmask the parent interrupt ++ * @data: Pointer to interrupt specific data ++ */ ++void irq_chip_unmask_parent(struct irq_data *data) ++{ ++ data = data->parent_data; ++ data->chip->irq_unmask(data); ++} ++ ++/** ++ * irq_chip_eoi_parent - Invoke EOI on the parent interrupt ++ * @data: Pointer to interrupt specific data ++ */ ++void irq_chip_eoi_parent(struct irq_data *data) ++{ ++ data = data->parent_data; ++ data->chip->irq_eoi(data); ++} ++ ++/** ++ * irq_chip_set_affinity_parent - Set affinity on the parent interrupt ++ * @data: Pointer to interrupt specific data ++ * @dest: The affinity mask to set ++ * @force: Flag to enforce setting (disable online checks) ++ * ++ * Conditinal, as the underlying parent chip might not implement it. ++ */ ++int irq_chip_set_affinity_parent(struct irq_data *data, ++ const struct cpumask *dest, bool force) ++{ ++ data = data->parent_data; ++ if (data->chip->irq_set_affinity) ++ return data->chip->irq_set_affinity(data, dest, force); ++ ++ return -ENOSYS; ++} ++ ++/** ++ * irq_chip_set_type_parent - Set IRQ type on the parent interrupt ++ * @data: Pointer to interrupt specific data ++ * @type: IRQ_TYPE_{LEVEL,EDGE}_* value - see include/linux/irq.h ++ * ++ * Conditional, as the underlying parent chip might not implement it. ++ */ ++int irq_chip_set_type_parent(struct irq_data *data, unsigned int type) ++{ ++ data = data->parent_data; ++ ++ if (data->chip->irq_set_type) ++ return data->chip->irq_set_type(data, type); ++ ++ return -ENOSYS; ++} ++ ++/** ++ * irq_chip_retrigger_hierarchy - Retrigger an interrupt in hardware ++ * @data: Pointer to interrupt specific data ++ * ++ * Iterate through the domain hierarchy of the interrupt and check ++ * whether a hw retrigger function exists. If yes, invoke it. ++ */ ++int irq_chip_retrigger_hierarchy(struct irq_data *data) ++{ ++ for (data = data->parent_data; data; data = data->parent_data) ++ if (data->chip && data->chip->irq_retrigger) ++ return data->chip->irq_retrigger(data); ++ ++ return -ENOSYS; ++} ++ ++/** ++ * irq_chip_set_wake_parent - Set/reset wake-up on the parent interrupt ++ * @data: Pointer to interrupt specific data ++ * @on: Whether to set or reset the wake-up capability of this irq ++ * ++ * Conditional, as the underlying parent chip might not implement it. ++ */ ++int irq_chip_set_wake_parent(struct irq_data *data, unsigned int on) ++{ ++ data = data->parent_data; ++ if (data->chip->irq_set_wake) ++ return data->chip->irq_set_wake(data, on); ++ ++ return -ENOSYS; ++} ++#endif ++ ++/** ++ * irq_chip_compose_msi_msg - Componse msi message for a irq chip ++ * @data: Pointer to interrupt specific data ++ * @msg: Pointer to the MSI message ++ * ++ * For hierarchical domains we find the first chip in the hierarchy ++ * which implements the irq_compose_msi_msg callback. For non ++ * hierarchical we use the top level chip. ++ */ ++int irq_chip_compose_msi_msg(struct irq_data *data, struct msi_msg *msg) ++{ ++ struct irq_data *pos = NULL; ++ ++#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY ++ for (; data; data = data->parent_data) ++#endif ++ if (data->chip && data->chip->irq_compose_msi_msg) ++ pos = data; ++ if (!pos) ++ return -ENOSYS; ++ ++ pos->chip->irq_compose_msi_msg(pos, msg); ++ ++ return 0; ++} +diff --git a/kernel/irq/generic-chip.c b/kernel/irq/generic-chip.c +index cf80e7b..61024e8 100644 +--- a/kernel/irq/generic-chip.c ++++ b/kernel/irq/generic-chip.c +@@ -39,7 +39,7 @@ void irq_gc_mask_disable_reg(struct irq_data *d) + u32 mask = d->mask; + + irq_gc_lock(gc); +- irq_reg_writel(mask, gc->reg_base + ct->regs.disable); ++ irq_reg_writel(gc, mask, ct->regs.disable); + *ct->mask_cache &= ~mask; + irq_gc_unlock(gc); + } +@@ -59,7 +59,7 @@ void irq_gc_mask_set_bit(struct irq_data *d) + + irq_gc_lock(gc); + *ct->mask_cache |= mask; +- irq_reg_writel(*ct->mask_cache, gc->reg_base + ct->regs.mask); ++ irq_reg_writel(gc, *ct->mask_cache, ct->regs.mask); + irq_gc_unlock(gc); + } + EXPORT_SYMBOL_GPL(irq_gc_mask_set_bit); +@@ -79,7 +79,7 @@ void irq_gc_mask_clr_bit(struct irq_data *d) + + irq_gc_lock(gc); + *ct->mask_cache &= ~mask; +- irq_reg_writel(*ct->mask_cache, gc->reg_base + ct->regs.mask); ++ irq_reg_writel(gc, *ct->mask_cache, ct->regs.mask); + irq_gc_unlock(gc); + } + EXPORT_SYMBOL_GPL(irq_gc_mask_clr_bit); +@@ -98,7 +98,7 @@ void irq_gc_unmask_enable_reg(struct irq_data *d) + u32 mask = d->mask; + + irq_gc_lock(gc); +- irq_reg_writel(mask, gc->reg_base + ct->regs.enable); ++ irq_reg_writel(gc, mask, ct->regs.enable); + *ct->mask_cache |= mask; + irq_gc_unlock(gc); + } +@@ -114,7 +114,7 @@ void irq_gc_ack_set_bit(struct irq_data *d) + u32 mask = d->mask; + + irq_gc_lock(gc); +- irq_reg_writel(mask, gc->reg_base + ct->regs.ack); ++ irq_reg_writel(gc, mask, ct->regs.ack); + irq_gc_unlock(gc); + } + EXPORT_SYMBOL_GPL(irq_gc_ack_set_bit); +@@ -130,7 +130,7 @@ void irq_gc_ack_clr_bit(struct irq_data *d) + u32 mask = ~d->mask; + + irq_gc_lock(gc); +- irq_reg_writel(mask, gc->reg_base + ct->regs.ack); ++ irq_reg_writel(gc, mask, ct->regs.ack); + irq_gc_unlock(gc); + } + +@@ -145,8 +145,8 @@ void irq_gc_mask_disable_reg_and_ack(struct irq_data *d) + u32 mask = d->mask; + + irq_gc_lock(gc); +- irq_reg_writel(mask, gc->reg_base + ct->regs.mask); +- irq_reg_writel(mask, gc->reg_base + ct->regs.ack); ++ irq_reg_writel(gc, mask, ct->regs.mask); ++ irq_reg_writel(gc, mask, ct->regs.ack); + irq_gc_unlock(gc); + } + +@@ -161,7 +161,7 @@ void irq_gc_eoi(struct irq_data *d) + u32 mask = d->mask; + + irq_gc_lock(gc); +- irq_reg_writel(mask, gc->reg_base + ct->regs.eoi); ++ irq_reg_writel(gc, mask, ct->regs.eoi); + irq_gc_unlock(gc); + } + +@@ -191,6 +191,16 @@ int irq_gc_set_wake(struct irq_data *d, unsigned int on) + return 0; + } + ++static u32 irq_readl_be(void __iomem *addr) ++{ ++ return ioread32be(addr); ++} ++ ++static void irq_writel_be(u32 val, void __iomem *addr) ++{ ++ iowrite32be(val, addr); ++} ++ + static void + irq_init_generic_chip(struct irq_chip_generic *gc, const char *name, + int num_ct, unsigned int irq_base, +@@ -245,7 +255,7 @@ irq_gc_init_mask_cache(struct irq_chip_generic *gc, enum irq_gc_flags flags) + } + ct[i].mask_cache = mskptr; + if (flags & IRQ_GC_INIT_MASK_CACHE) +- *mskptr = irq_reg_readl(gc->reg_base + mskreg); ++ *mskptr = irq_reg_readl(gc, mskreg); + } + } + +@@ -300,7 +310,13 @@ int irq_alloc_domain_generic_chips(struct irq_domain *d, int irqs_per_chip, + dgc->gc[i] = gc = tmp; + irq_init_generic_chip(gc, name, num_ct, i * irqs_per_chip, + NULL, handler); ++ + gc->domain = d; ++ if (gcflags & IRQ_GC_BE_IO) { ++ gc->reg_readl = &irq_readl_be; ++ gc->reg_writel = &irq_writel_be; ++ } ++ + raw_spin_lock_irqsave(&gc_lock, flags); + list_add_tail(&gc->list, &gc_list); + raw_spin_unlock_irqrestore(&gc_lock, flags); +diff --git a/kernel/irq/irqdomain.c b/kernel/irq/irqdomain.c +index 6534ff6..021f823 100644 +--- a/kernel/irq/irqdomain.c ++++ b/kernel/irq/irqdomain.c +@@ -23,6 +23,10 @@ static DEFINE_MUTEX(irq_domain_mutex); + static DEFINE_MUTEX(revmap_trees_mutex); + static struct irq_domain *irq_default_domain; + ++static int irq_domain_alloc_descs(int virq, unsigned int nr_irqs, ++ irq_hw_number_t hwirq, int node); ++static void irq_domain_check_hierarchy(struct irq_domain *domain); ++ + /** + * __irq_domain_add() - Allocate a new irq_domain data structure + * @of_node: optional device-tree node of the interrupt controller +@@ -30,7 +34,7 @@ static struct irq_domain *irq_default_domain; + * @hwirq_max: Maximum number of interrupts supported by controller + * @direct_max: Maximum value of direct maps; Use ~0 for no limit; 0 for no + * direct mapping +- * @ops: map/unmap domain callbacks ++ * @ops: domain callbacks + * @host_data: Controller private data pointer + * + * Allocates and initialize and irq_domain structure. +@@ -56,6 +60,7 @@ struct irq_domain *__irq_domain_add(struct device_node *of_node, int size, + domain->hwirq_max = hwirq_max; + domain->revmap_size = size; + domain->revmap_direct_max_irq = direct_max; ++ irq_domain_check_hierarchy(domain); + + mutex_lock(&irq_domain_mutex); + list_add(&domain->link, &irq_domain_list); +@@ -109,7 +114,7 @@ EXPORT_SYMBOL_GPL(irq_domain_remove); + * @first_irq: first number of irq block assigned to the domain, + * pass zero to assign irqs on-the-fly. If first_irq is non-zero, then + * pre-map all of the irqs in the domain to virqs starting at first_irq. +- * @ops: map/unmap domain callbacks ++ * @ops: domain callbacks + * @host_data: Controller private data pointer + * + * Allocates an irq_domain, and optionally if first_irq is positive then also +@@ -174,20 +179,20 @@ struct irq_domain *irq_domain_add_legacy(struct device_node *of_node, + + domain = __irq_domain_add(of_node, first_hwirq + size, + first_hwirq + size, 0, ops, host_data); +- if (!domain) +- return NULL; +- +- irq_domain_associate_many(domain, first_irq, first_hwirq, size); ++ if (domain) ++ irq_domain_associate_many(domain, first_irq, first_hwirq, size); + + return domain; + } + EXPORT_SYMBOL_GPL(irq_domain_add_legacy); + + /** +- * irq_find_host() - Locates a domain for a given device node ++ * irq_find_matching_host() - Locates a domain for a given device node + * @node: device-tree node of the interrupt controller ++ * @bus_token: domain-specific data + */ +-struct irq_domain *irq_find_host(struct device_node *node) ++struct irq_domain *irq_find_matching_host(struct device_node *node, ++ enum irq_domain_bus_token bus_token) + { + struct irq_domain *h, *found = NULL; + int rc; +@@ -196,13 +201,19 @@ struct irq_domain *irq_find_host(struct device_node *node) + * it might potentially be set to match all interrupts in + * the absence of a device node. This isn't a problem so far + * yet though... ++ * ++ * bus_token == DOMAIN_BUS_ANY matches any domain, any other ++ * values must generate an exact match for the domain to be ++ * selected. + */ + mutex_lock(&irq_domain_mutex); + list_for_each_entry(h, &irq_domain_list, link) { + if (h->ops->match) +- rc = h->ops->match(h, node); ++ rc = h->ops->match(h, node, bus_token); + else +- rc = (h->of_node != NULL) && (h->of_node == node); ++ rc = ((h->of_node != NULL) && (h->of_node == node) && ++ ((bus_token == DOMAIN_BUS_ANY) || ++ (h->bus_token == bus_token))); + + if (rc) { + found = h; +@@ -212,7 +223,7 @@ struct irq_domain *irq_find_host(struct device_node *node) + mutex_unlock(&irq_domain_mutex); + return found; + } +-EXPORT_SYMBOL_GPL(irq_find_host); ++EXPORT_SYMBOL_GPL(irq_find_matching_host); + + /** + * irq_set_default_host() - Set a "default" irq domain +@@ -388,7 +399,6 @@ EXPORT_SYMBOL_GPL(irq_create_direct_mapping); + unsigned int irq_create_mapping(struct irq_domain *domain, + irq_hw_number_t hwirq) + { +- unsigned int hint; + int virq; + + pr_debug("irq_create_mapping(0x%p, 0x%lx)\n", domain, hwirq); +@@ -410,12 +420,8 @@ unsigned int irq_create_mapping(struct irq_domain *domain, + } + + /* Allocate a virtual interrupt number */ +- hint = hwirq % nr_irqs; +- if (hint == 0) +- hint++; +- virq = irq_alloc_desc_from(hint, of_node_to_nid(domain->of_node)); +- if (virq <= 0) +- virq = irq_alloc_desc_from(1, of_node_to_nid(domain->of_node)); ++ virq = irq_domain_alloc_descs(-1, 1, hwirq, ++ of_node_to_nid(domain->of_node)); + if (virq <= 0) { + pr_debug("-> virq allocation failed\n"); + return 0; +@@ -471,7 +477,7 @@ unsigned int irq_create_of_mapping(struct of_phandle_args *irq_data) + struct irq_domain *domain; + irq_hw_number_t hwirq; + unsigned int type = IRQ_TYPE_NONE; +- unsigned int virq; ++ int virq; + + domain = irq_data->np ? irq_find_host(irq_data->np) : irq_default_domain; + if (!domain) { +@@ -489,10 +495,24 @@ unsigned int irq_create_of_mapping(struct of_phandle_args *irq_data) + return 0; + } + +- /* Create mapping */ +- virq = irq_create_mapping(domain, hwirq); +- if (!virq) +- return virq; ++ if (irq_domain_is_hierarchy(domain)) { ++ /* ++ * If we've already configured this interrupt, ++ * don't do it again, or hell will break loose. ++ */ ++ virq = irq_find_mapping(domain, hwirq); ++ if (virq) ++ return virq; ++ ++ virq = irq_domain_alloc_irqs(domain, 1, NUMA_NO_NODE, irq_data); ++ if (virq <= 0) ++ return 0; ++ } else { ++ /* Create mapping */ ++ virq = irq_create_mapping(domain, hwirq); ++ if (!virq) ++ return virq; ++ } + + /* Set type if specified and different than the current one */ + if (type != IRQ_TYPE_NONE && +@@ -540,8 +560,8 @@ unsigned int irq_find_mapping(struct irq_domain *domain, + return 0; + + if (hwirq < domain->revmap_direct_max_irq) { +- data = irq_get_irq_data(hwirq); +- if (data && (data->domain == domain) && (data->hwirq == hwirq)) ++ data = irq_domain_get_irq_data(domain, hwirq); ++ if (data && data->hwirq == hwirq) + return hwirq; + } + +@@ -709,3 +729,518 @@ const struct irq_domain_ops irq_domain_simple_ops = { + .xlate = irq_domain_xlate_onetwocell, + }; + EXPORT_SYMBOL_GPL(irq_domain_simple_ops); ++ ++static int irq_domain_alloc_descs(int virq, unsigned int cnt, ++ irq_hw_number_t hwirq, int node) ++{ ++ unsigned int hint; ++ ++ if (virq >= 0) { ++ virq = irq_alloc_descs(virq, virq, cnt, node); ++ } else { ++ hint = hwirq % nr_irqs; ++ if (hint == 0) ++ hint++; ++ virq = irq_alloc_descs_from(hint, cnt, node); ++ if (virq <= 0 && hint > 1) ++ virq = irq_alloc_descs_from(1, cnt, node); ++ } ++ ++ return virq; ++} ++ ++#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY ++/** ++ * irq_domain_add_hierarchy - Add a irqdomain into the hierarchy ++ * @parent: Parent irq domain to associate with the new domain ++ * @flags: Irq domain flags associated to the domain ++ * @size: Size of the domain. See below ++ * @node: Optional device-tree node of the interrupt controller ++ * @ops: Pointer to the interrupt domain callbacks ++ * @host_data: Controller private data pointer ++ * ++ * If @size is 0 a tree domain is created, otherwise a linear domain. ++ * ++ * If successful the parent is associated to the new domain and the ++ * domain flags are set. ++ * Returns pointer to IRQ domain, or NULL on failure. ++ */ ++struct irq_domain *irq_domain_add_hierarchy(struct irq_domain *parent, ++ unsigned int flags, ++ unsigned int size, ++ struct device_node *node, ++ const struct irq_domain_ops *ops, ++ void *host_data) ++{ ++ struct irq_domain *domain; ++ ++ if (size) ++ domain = irq_domain_add_linear(node, size, ops, host_data); ++ else ++ domain = irq_domain_add_tree(node, ops, host_data); ++ if (domain) { ++ domain->parent = parent; ++ domain->flags |= flags; ++ } ++ ++ return domain; ++} ++ ++static void irq_domain_insert_irq(int virq) ++{ ++ struct irq_data *data; ++ ++ for (data = irq_get_irq_data(virq); data; data = data->parent_data) { ++ struct irq_domain *domain = data->domain; ++ irq_hw_number_t hwirq = data->hwirq; ++ ++ if (hwirq < domain->revmap_size) { ++ domain->linear_revmap[hwirq] = virq; ++ } else { ++ mutex_lock(&revmap_trees_mutex); ++ radix_tree_insert(&domain->revmap_tree, hwirq, data); ++ mutex_unlock(&revmap_trees_mutex); ++ } ++ ++ /* If not already assigned, give the domain the chip's name */ ++ if (!domain->name && data->chip) ++ domain->name = data->chip->name; ++ } ++ ++ irq_clear_status_flags(virq, IRQ_NOREQUEST); ++} ++ ++static void irq_domain_remove_irq(int virq) ++{ ++ struct irq_data *data; ++ ++ irq_set_status_flags(virq, IRQ_NOREQUEST); ++ irq_set_chip_and_handler(virq, NULL, NULL); ++ synchronize_irq(virq); ++ smp_mb(); ++ ++ for (data = irq_get_irq_data(virq); data; data = data->parent_data) { ++ struct irq_domain *domain = data->domain; ++ irq_hw_number_t hwirq = data->hwirq; ++ ++ if (hwirq < domain->revmap_size) { ++ domain->linear_revmap[hwirq] = 0; ++ } else { ++ mutex_lock(&revmap_trees_mutex); ++ radix_tree_delete(&domain->revmap_tree, hwirq); ++ mutex_unlock(&revmap_trees_mutex); ++ } ++ } ++} ++ ++static struct irq_data *irq_domain_insert_irq_data(struct irq_domain *domain, ++ struct irq_data *child) ++{ ++ struct irq_data *irq_data; ++ ++ irq_data = kzalloc_node(sizeof(*irq_data), GFP_KERNEL, child->node); ++ if (irq_data) { ++ child->parent_data = irq_data; ++ irq_data->irq = child->irq; ++ irq_data->node = child->node; ++ irq_data->domain = domain; ++ } ++ ++ return irq_data; ++} ++ ++static void irq_domain_free_irq_data(unsigned int virq, unsigned int nr_irqs) ++{ ++ struct irq_data *irq_data, *tmp; ++ int i; ++ ++ for (i = 0; i < nr_irqs; i++) { ++ irq_data = irq_get_irq_data(virq + i); ++ tmp = irq_data->parent_data; ++ irq_data->parent_data = NULL; ++ irq_data->domain = NULL; ++ ++ while (tmp) { ++ irq_data = tmp; ++ tmp = tmp->parent_data; ++ kfree(irq_data); ++ } ++ } ++} ++ ++static int irq_domain_alloc_irq_data(struct irq_domain *domain, ++ unsigned int virq, unsigned int nr_irqs) ++{ ++ struct irq_data *irq_data; ++ struct irq_domain *parent; ++ int i; ++ ++ /* The outermost irq_data is embedded in struct irq_desc */ ++ for (i = 0; i < nr_irqs; i++) { ++ irq_data = irq_get_irq_data(virq + i); ++ irq_data->domain = domain; ++ ++ for (parent = domain->parent; parent; parent = parent->parent) { ++ irq_data = irq_domain_insert_irq_data(parent, irq_data); ++ if (!irq_data) { ++ irq_domain_free_irq_data(virq, i + 1); ++ return -ENOMEM; ++ } ++ } ++ } ++ ++ return 0; ++} ++ ++/** ++ * irq_domain_get_irq_data - Get irq_data associated with @virq and @domain ++ * @domain: domain to match ++ * @virq: IRQ number to get irq_data ++ */ ++struct irq_data *irq_domain_get_irq_data(struct irq_domain *domain, ++ unsigned int virq) ++{ ++ struct irq_data *irq_data; ++ ++ for (irq_data = irq_get_irq_data(virq); irq_data; ++ irq_data = irq_data->parent_data) ++ if (irq_data->domain == domain) ++ return irq_data; ++ ++ return NULL; ++} ++ ++/** ++ * irq_domain_set_hwirq_and_chip - Set hwirq and irqchip of @virq at @domain ++ * @domain: Interrupt domain to match ++ * @virq: IRQ number ++ * @hwirq: The hwirq number ++ * @chip: The associated interrupt chip ++ * @chip_data: The associated chip data ++ */ ++int irq_domain_set_hwirq_and_chip(struct irq_domain *domain, unsigned int virq, ++ irq_hw_number_t hwirq, struct irq_chip *chip, ++ void *chip_data) ++{ ++ struct irq_data *irq_data = irq_domain_get_irq_data(domain, virq); ++ ++ if (!irq_data) ++ return -ENOENT; ++ ++ irq_data->hwirq = hwirq; ++ irq_data->chip = chip ? chip : &no_irq_chip; ++ irq_data->chip_data = chip_data; ++ ++ return 0; ++} ++ ++/** ++ * irq_domain_set_info - Set the complete data for a @virq in @domain ++ * @domain: Interrupt domain to match ++ * @virq: IRQ number ++ * @hwirq: The hardware interrupt number ++ * @chip: The associated interrupt chip ++ * @chip_data: The associated interrupt chip data ++ * @handler: The interrupt flow handler ++ * @handler_data: The interrupt flow handler data ++ * @handler_name: The interrupt handler name ++ */ ++void irq_domain_set_info(struct irq_domain *domain, unsigned int virq, ++ irq_hw_number_t hwirq, struct irq_chip *chip, ++ void *chip_data, irq_flow_handler_t handler, ++ void *handler_data, const char *handler_name) ++{ ++ irq_domain_set_hwirq_and_chip(domain, virq, hwirq, chip, chip_data); ++ __irq_set_handler(virq, handler, 0, handler_name); ++ irq_set_handler_data(virq, handler_data); ++} ++ ++/** ++ * irq_domain_reset_irq_data - Clear hwirq, chip and chip_data in @irq_data ++ * @irq_data: The pointer to irq_data ++ */ ++void irq_domain_reset_irq_data(struct irq_data *irq_data) ++{ ++ irq_data->hwirq = 0; ++ irq_data->chip = &no_irq_chip; ++ irq_data->chip_data = NULL; ++} ++ ++/** ++ * irq_domain_free_irqs_common - Clear irq_data and free the parent ++ * @domain: Interrupt domain to match ++ * @virq: IRQ number to start with ++ * @nr_irqs: The number of irqs to free ++ */ ++void irq_domain_free_irqs_common(struct irq_domain *domain, unsigned int virq, ++ unsigned int nr_irqs) ++{ ++ struct irq_data *irq_data; ++ int i; ++ ++ for (i = 0; i < nr_irqs; i++) { ++ irq_data = irq_domain_get_irq_data(domain, virq + i); ++ if (irq_data) ++ irq_domain_reset_irq_data(irq_data); ++ } ++ irq_domain_free_irqs_parent(domain, virq, nr_irqs); ++} ++ ++/** ++ * irq_domain_free_irqs_top - Clear handler and handler data, clear irqdata and free parent ++ * @domain: Interrupt domain to match ++ * @virq: IRQ number to start with ++ * @nr_irqs: The number of irqs to free ++ */ ++void irq_domain_free_irqs_top(struct irq_domain *domain, unsigned int virq, ++ unsigned int nr_irqs) ++{ ++ int i; ++ ++ for (i = 0; i < nr_irqs; i++) { ++ irq_set_handler_data(virq + i, NULL); ++ irq_set_handler(virq + i, NULL); ++ } ++ irq_domain_free_irqs_common(domain, virq, nr_irqs); ++} ++ ++static bool irq_domain_is_auto_recursive(struct irq_domain *domain) ++{ ++ return domain->flags & IRQ_DOMAIN_FLAG_AUTO_RECURSIVE; ++} ++ ++static void irq_domain_free_irqs_recursive(struct irq_domain *domain, ++ unsigned int irq_base, ++ unsigned int nr_irqs) ++{ ++ domain->ops->free(domain, irq_base, nr_irqs); ++ if (irq_domain_is_auto_recursive(domain)) { ++ BUG_ON(!domain->parent); ++ irq_domain_free_irqs_recursive(domain->parent, irq_base, ++ nr_irqs); ++ } ++} ++ ++static int irq_domain_alloc_irqs_recursive(struct irq_domain *domain, ++ unsigned int irq_base, ++ unsigned int nr_irqs, void *arg) ++{ ++ int ret = 0; ++ struct irq_domain *parent = domain->parent; ++ bool recursive = irq_domain_is_auto_recursive(domain); ++ ++ BUG_ON(recursive && !parent); ++ if (recursive) ++ ret = irq_domain_alloc_irqs_recursive(parent, irq_base, ++ nr_irqs, arg); ++ if (ret >= 0) ++ ret = domain->ops->alloc(domain, irq_base, nr_irqs, arg); ++ if (ret < 0 && recursive) ++ irq_domain_free_irqs_recursive(parent, irq_base, nr_irqs); ++ ++ return ret; ++} ++ ++/** ++ * __irq_domain_alloc_irqs - Allocate IRQs from domain ++ * @domain: domain to allocate from ++ * @irq_base: allocate specified IRQ nubmer if irq_base >= 0 ++ * @nr_irqs: number of IRQs to allocate ++ * @node: NUMA node id for memory allocation ++ * @arg: domain specific argument ++ * @realloc: IRQ descriptors have already been allocated if true ++ * ++ * Allocate IRQ numbers and initialized all data structures to support ++ * hierarchy IRQ domains. ++ * Parameter @realloc is mainly to support legacy IRQs. ++ * Returns error code or allocated IRQ number ++ * ++ * The whole process to setup an IRQ has been split into two steps. ++ * The first step, __irq_domain_alloc_irqs(), is to allocate IRQ ++ * descriptor and required hardware resources. The second step, ++ * irq_domain_activate_irq(), is to program hardwares with preallocated ++ * resources. In this way, it's easier to rollback when failing to ++ * allocate resources. ++ */ ++int __irq_domain_alloc_irqs(struct irq_domain *domain, int irq_base, ++ unsigned int nr_irqs, int node, void *arg, ++ bool realloc) ++{ ++ int i, ret, virq; ++ ++ if (domain == NULL) { ++ domain = irq_default_domain; ++ if (WARN(!domain, "domain is NULL; cannot allocate IRQ\n")) ++ return -EINVAL; ++ } ++ ++ if (!domain->ops->alloc) { ++ pr_debug("domain->ops->alloc() is NULL\n"); ++ return -ENOSYS; ++ } ++ ++ if (realloc && irq_base >= 0) { ++ virq = irq_base; ++ } else { ++ virq = irq_domain_alloc_descs(irq_base, nr_irqs, 0, node); ++ if (virq < 0) { ++ pr_debug("cannot allocate IRQ(base %d, count %d)\n", ++ irq_base, nr_irqs); ++ return virq; ++ } ++ } ++ ++ if (irq_domain_alloc_irq_data(domain, virq, nr_irqs)) { ++ pr_debug("cannot allocate memory for IRQ%d\n", virq); ++ ret = -ENOMEM; ++ goto out_free_desc; ++ } ++ ++ mutex_lock(&irq_domain_mutex); ++ ret = irq_domain_alloc_irqs_recursive(domain, virq, nr_irqs, arg); ++ if (ret < 0) { ++ mutex_unlock(&irq_domain_mutex); ++ goto out_free_irq_data; ++ } ++ for (i = 0; i < nr_irqs; i++) ++ irq_domain_insert_irq(virq + i); ++ mutex_unlock(&irq_domain_mutex); ++ ++ return virq; ++ ++out_free_irq_data: ++ irq_domain_free_irq_data(virq, nr_irqs); ++out_free_desc: ++ irq_free_descs(virq, nr_irqs); ++ return ret; ++} ++ ++/** ++ * irq_domain_free_irqs - Free IRQ number and associated data structures ++ * @virq: base IRQ number ++ * @nr_irqs: number of IRQs to free ++ */ ++void irq_domain_free_irqs(unsigned int virq, unsigned int nr_irqs) ++{ ++ struct irq_data *data = irq_get_irq_data(virq); ++ int i; ++ ++ if (WARN(!data || !data->domain || !data->domain->ops->free, ++ "NULL pointer, cannot free irq\n")) ++ return; ++ ++ mutex_lock(&irq_domain_mutex); ++ for (i = 0; i < nr_irqs; i++) ++ irq_domain_remove_irq(virq + i); ++ irq_domain_free_irqs_recursive(data->domain, virq, nr_irqs); ++ mutex_unlock(&irq_domain_mutex); ++ ++ irq_domain_free_irq_data(virq, nr_irqs); ++ irq_free_descs(virq, nr_irqs); ++} ++ ++/** ++ * irq_domain_alloc_irqs_parent - Allocate interrupts from parent domain ++ * @irq_base: Base IRQ number ++ * @nr_irqs: Number of IRQs to allocate ++ * @arg: Allocation data (arch/domain specific) ++ * ++ * Check whether the domain has been setup recursive. If not allocate ++ * through the parent domain. ++ */ ++int irq_domain_alloc_irqs_parent(struct irq_domain *domain, ++ unsigned int irq_base, unsigned int nr_irqs, ++ void *arg) ++{ ++ /* irq_domain_alloc_irqs_recursive() has called parent's alloc() */ ++ if (irq_domain_is_auto_recursive(domain)) ++ return 0; ++ ++ domain = domain->parent; ++ if (domain) ++ return irq_domain_alloc_irqs_recursive(domain, irq_base, ++ nr_irqs, arg); ++ return -ENOSYS; ++} ++ ++/** ++ * irq_domain_free_irqs_parent - Free interrupts from parent domain ++ * @irq_base: Base IRQ number ++ * @nr_irqs: Number of IRQs to free ++ * ++ * Check whether the domain has been setup recursive. If not free ++ * through the parent domain. ++ */ ++void irq_domain_free_irqs_parent(struct irq_domain *domain, ++ unsigned int irq_base, unsigned int nr_irqs) ++{ ++ /* irq_domain_free_irqs_recursive() will call parent's free */ ++ if (!irq_domain_is_auto_recursive(domain) && domain->parent) ++ irq_domain_free_irqs_recursive(domain->parent, irq_base, ++ nr_irqs); ++} ++ ++/** ++ * irq_domain_activate_irq - Call domain_ops->activate recursively to activate ++ * interrupt ++ * @irq_data: outermost irq_data associated with interrupt ++ * ++ * This is the second step to call domain_ops->activate to program interrupt ++ * controllers, so the interrupt could actually get delivered. ++ */ ++void irq_domain_activate_irq(struct irq_data *irq_data) ++{ ++ if (irq_data && irq_data->domain) { ++ struct irq_domain *domain = irq_data->domain; ++ ++ if (irq_data->parent_data) ++ irq_domain_activate_irq(irq_data->parent_data); ++ if (domain->ops->activate) ++ domain->ops->activate(domain, irq_data); ++ } ++} ++ ++/** ++ * irq_domain_deactivate_irq - Call domain_ops->deactivate recursively to ++ * deactivate interrupt ++ * @irq_data: outermost irq_data associated with interrupt ++ * ++ * It calls domain_ops->deactivate to program interrupt controllers to disable ++ * interrupt delivery. ++ */ ++void irq_domain_deactivate_irq(struct irq_data *irq_data) ++{ ++ if (irq_data && irq_data->domain) { ++ struct irq_domain *domain = irq_data->domain; ++ ++ if (domain->ops->deactivate) ++ domain->ops->deactivate(domain, irq_data); ++ if (irq_data->parent_data) ++ irq_domain_deactivate_irq(irq_data->parent_data); ++ } ++} ++ ++static void irq_domain_check_hierarchy(struct irq_domain *domain) ++{ ++ /* Hierarchy irq_domains must implement callback alloc() */ ++ if (domain->ops->alloc) ++ domain->flags |= IRQ_DOMAIN_FLAG_HIERARCHY; ++} ++#else /* CONFIG_IRQ_DOMAIN_HIERARCHY */ ++/** ++ * irq_domain_get_irq_data - Get irq_data associated with @virq and @domain ++ * @domain: domain to match ++ * @virq: IRQ number to get irq_data ++ */ ++struct irq_data *irq_domain_get_irq_data(struct irq_domain *domain, ++ unsigned int virq) ++{ ++ struct irq_data *irq_data = irq_get_irq_data(virq); ++ ++ return (irq_data && irq_data->domain == domain) ? irq_data : NULL; ++} ++ ++static void irq_domain_check_hierarchy(struct irq_domain *domain) ++{ ++} ++#endif /* CONFIG_IRQ_DOMAIN_HIERARCHY */ +diff --git a/kernel/irq/manage.c b/kernel/irq/manage.c +index 0a9104b..acb401f 100644 +--- a/kernel/irq/manage.c ++++ b/kernel/irq/manage.c +@@ -183,6 +183,7 @@ int irq_do_set_affinity(struct irq_data *data, const struct cpumask *mask, + ret = chip->irq_set_affinity(data, mask, force); + switch (ret) { + case IRQ_SET_MASK_OK: ++ case IRQ_SET_MASK_OK_DONE: + cpumask_copy(data->affinity, mask); + case IRQ_SET_MASK_OK_NOCOPY: + irq_set_thread_affinity(desc); +@@ -600,6 +601,7 @@ int __irq_set_trigger(struct irq_desc *desc, unsigned int irq, + + switch (ret) { + case IRQ_SET_MASK_OK: ++ case IRQ_SET_MASK_OK_DONE: + irqd_clear(&desc->irq_data, IRQD_TRIGGER_MASK); + irqd_set(&desc->irq_data, flags); + +@@ -1756,3 +1758,94 @@ int request_percpu_irq(unsigned int irq, irq_handler_t handler, + + return retval; + } ++ ++/** ++ * irq_get_irqchip_state - returns the irqchip state of a interrupt. ++ * @irq: Interrupt line that is forwarded to a VM ++ * @which: One of IRQCHIP_STATE_* the caller wants to know about ++ * @state: a pointer to a boolean where the state is to be storeed ++ * ++ * This call snapshots the internal irqchip state of an ++ * interrupt, returning into @state the bit corresponding to ++ * stage @which ++ * ++ * This function should be called with preemption disabled if the ++ * interrupt controller has per-cpu registers. ++ */ ++int irq_get_irqchip_state(unsigned int irq, enum irqchip_irq_state which, ++ bool *state) ++{ ++ struct irq_desc *desc; ++ struct irq_data *data; ++ struct irq_chip *chip; ++ unsigned long flags; ++ int err = -EINVAL; ++ ++ desc = irq_get_desc_buslock(irq, &flags, 0); ++ if (!desc) ++ return err; ++ ++ data = irq_desc_get_irq_data(desc); ++ ++ do { ++ chip = irq_data_get_irq_chip(data); ++ if (chip->irq_get_irqchip_state) ++ break; ++#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY ++ data = data->parent_data; ++#else ++ data = NULL; ++#endif ++ } while (data); ++ ++ if (data) ++ err = chip->irq_get_irqchip_state(data, which, state); ++ ++ irq_put_desc_busunlock(desc, flags); ++ return err; ++} ++ ++/** ++ * irq_set_irqchip_state - set the state of a forwarded interrupt. ++ * @irq: Interrupt line that is forwarded to a VM ++ * @which: State to be restored (one of IRQCHIP_STATE_*) ++ * @val: Value corresponding to @which ++ * ++ * This call sets the internal irqchip state of an interrupt, ++ * depending on the value of @which. ++ * ++ * This function should be called with preemption disabled if the ++ * interrupt controller has per-cpu registers. ++ */ ++int irq_set_irqchip_state(unsigned int irq, enum irqchip_irq_state which, ++ bool val) ++{ ++ struct irq_desc *desc; ++ struct irq_data *data; ++ struct irq_chip *chip; ++ unsigned long flags; ++ int err = -EINVAL; ++ ++ desc = irq_get_desc_buslock(irq, &flags, 0); ++ if (!desc) ++ return err; ++ ++ data = irq_desc_get_irq_data(desc); ++ ++ do { ++ chip = irq_data_get_irq_chip(data); ++ if (chip->irq_set_irqchip_state) ++ break; ++#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY ++ data = data->parent_data; ++#else ++ data = NULL; ++#endif ++ } while (data); ++ ++ if (data) ++ err = chip->irq_set_irqchip_state(data, which, val); ++ ++ irq_put_desc_busunlock(desc, flags); ++ return err; ++} +diff --git a/kernel/irq/msi.c b/kernel/irq/msi.c +new file mode 100644 +index 0000000..54433c2 +--- /dev/null ++++ b/kernel/irq/msi.c +@@ -0,0 +1,356 @@ ++/* ++ * linux/kernel/irq/msi.c ++ * ++ * Copyright (C) 2014 Intel Corp. ++ * Author: Jiang Liu ++ * ++ * This file is licensed under GPLv2. ++ * ++ * This file contains common code to support Message Signalled Interrupt for ++ * PCI compatible and non PCI compatible devices. ++ */ ++#include ++#include ++#include ++#include ++#include ++ ++/* Temparory solution for building, will be removed later */ ++#include ++ ++struct msi_desc *alloc_msi_entry(struct device *dev) ++{ ++ struct msi_desc *desc = kzalloc(sizeof(*desc), GFP_KERNEL); ++ if (!desc) ++ return NULL; ++ ++ INIT_LIST_HEAD(&desc->list); ++ desc->dev = dev; ++ ++ return desc; ++} ++ ++void free_msi_entry(struct msi_desc *entry) ++{ ++ kfree(entry); ++} ++ ++void __get_cached_msi_msg(struct msi_desc *entry, struct msi_msg *msg) ++{ ++ *msg = entry->msg; ++} ++ ++void get_cached_msi_msg(unsigned int irq, struct msi_msg *msg) ++{ ++ struct msi_desc *entry = irq_get_msi_desc(irq); ++ ++ __get_cached_msi_msg(entry, msg); ++} ++EXPORT_SYMBOL_GPL(get_cached_msi_msg); ++ ++#ifdef CONFIG_GENERIC_MSI_IRQ_DOMAIN ++static inline void irq_chip_write_msi_msg(struct irq_data *data, ++ struct msi_msg *msg) ++{ ++ data->chip->irq_write_msi_msg(data, msg); ++} ++ ++/** ++ * msi_domain_set_affinity - Generic affinity setter function for MSI domains ++ * @irq_data: The irq data associated to the interrupt ++ * @mask: The affinity mask to set ++ * @force: Flag to enforce setting (disable online checks) ++ * ++ * Intended to be used by MSI interrupt controllers which are ++ * implemented with hierarchical domains. ++ */ ++int msi_domain_set_affinity(struct irq_data *irq_data, ++ const struct cpumask *mask, bool force) ++{ ++ struct irq_data *parent = irq_data->parent_data; ++ struct msi_msg msg; ++ int ret; ++ ++ ret = parent->chip->irq_set_affinity(parent, mask, force); ++ if (ret >= 0 && ret != IRQ_SET_MASK_OK_DONE) { ++ BUG_ON(irq_chip_compose_msi_msg(irq_data, &msg)); ++ irq_chip_write_msi_msg(irq_data, &msg); ++ } ++ ++ return ret; ++} ++ ++static void msi_domain_activate(struct irq_domain *domain, ++ struct irq_data *irq_data) ++{ ++ struct msi_msg msg; ++ ++ BUG_ON(irq_chip_compose_msi_msg(irq_data, &msg)); ++ irq_chip_write_msi_msg(irq_data, &msg); ++} ++ ++static void msi_domain_deactivate(struct irq_domain *domain, ++ struct irq_data *irq_data) ++{ ++ struct msi_msg msg; ++ ++ memset(&msg, 0, sizeof(msg)); ++ irq_chip_write_msi_msg(irq_data, &msg); ++} ++ ++static int msi_domain_alloc(struct irq_domain *domain, unsigned int virq, ++ unsigned int nr_irqs, void *arg) ++{ ++ struct msi_domain_info *info = domain->host_data; ++ struct msi_domain_ops *ops = info->ops; ++ irq_hw_number_t hwirq = ops->get_hwirq(info, arg); ++ int i, ret; ++ ++#if 0 ++ if (irq_find_mapping(domain, hwirq) > 0) ++ return -EEXIST; ++#endif ++ ++ ret = irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, arg); ++ if (ret < 0) ++ return ret; ++ ++ for (i = 0; i < nr_irqs; i++) { ++ ret = ops->msi_init(domain, info, virq + i, hwirq + i, arg); ++ if (ret < 0) { ++ if (ops->msi_free) { ++ for (i--; i > 0; i--) ++ ops->msi_free(domain, info, virq + i); ++ } ++ irq_domain_free_irqs_top(domain, virq, nr_irqs); ++ return ret; ++ } ++ } ++ ++ return 0; ++} ++ ++static void msi_domain_free(struct irq_domain *domain, unsigned int virq, ++ unsigned int nr_irqs) ++{ ++ struct msi_domain_info *info = domain->host_data; ++ int i; ++ ++ if (info->ops->msi_free) { ++ for (i = 0; i < nr_irqs; i++) ++ info->ops->msi_free(domain, info, virq + i); ++ } ++ irq_domain_free_irqs_top(domain, virq, nr_irqs); ++} ++ ++static struct irq_domain_ops msi_domain_ops = { ++ .alloc = msi_domain_alloc, ++ .free = msi_domain_free, ++ .activate = msi_domain_activate, ++ .deactivate = msi_domain_deactivate, ++}; ++ ++#ifdef GENERIC_MSI_DOMAIN_OPS ++static irq_hw_number_t msi_domain_ops_get_hwirq(struct msi_domain_info *info, ++ msi_alloc_info_t *arg) ++{ ++ return arg->hwirq; ++} ++ ++static int msi_domain_ops_prepare(struct irq_domain *domain, struct device *dev, ++ int nvec, msi_alloc_info_t *arg) ++{ ++ memset(arg, 0, sizeof(*arg)); ++ return 0; ++} ++ ++static void msi_domain_ops_set_desc(msi_alloc_info_t *arg, ++ struct msi_desc *desc) ++{ ++ arg->desc = desc; ++} ++#else ++#define msi_domain_ops_get_hwirq NULL ++#define msi_domain_ops_prepare NULL ++#define msi_domain_ops_set_desc NULL ++#endif /* !GENERIC_MSI_DOMAIN_OPS */ ++ ++static int msi_domain_ops_init(struct irq_domain *domain, ++ struct msi_domain_info *info, ++ unsigned int virq, irq_hw_number_t hwirq, ++ msi_alloc_info_t *arg) ++{ ++ irq_domain_set_hwirq_and_chip(domain, virq, hwirq, info->chip, ++ info->chip_data); ++ if (info->handler && info->handler_name) { ++ __irq_set_handler(virq, info->handler, 0, info->handler_name); ++ if (info->handler_data) ++ irq_set_handler_data(virq, info->handler_data); ++ } ++ return 0; ++} ++ ++static int msi_domain_ops_check(struct irq_domain *domain, ++ struct msi_domain_info *info, ++ struct device *dev) ++{ ++ return 0; ++} ++ ++static struct msi_domain_ops msi_domain_ops_default = { ++ .get_hwirq = msi_domain_ops_get_hwirq, ++ .msi_init = msi_domain_ops_init, ++ .msi_check = msi_domain_ops_check, ++ .msi_prepare = msi_domain_ops_prepare, ++ .set_desc = msi_domain_ops_set_desc, ++}; ++ ++static void msi_domain_update_dom_ops(struct msi_domain_info *info) ++{ ++ struct msi_domain_ops *ops = info->ops; ++ ++ if (ops == NULL) { ++ info->ops = &msi_domain_ops_default; ++ return; ++ } ++ ++ if (ops->get_hwirq == NULL) ++ ops->get_hwirq = msi_domain_ops_default.get_hwirq; ++ if (ops->msi_init == NULL) ++ ops->msi_init = msi_domain_ops_default.msi_init; ++ if (ops->msi_check == NULL) ++ ops->msi_check = msi_domain_ops_default.msi_check; ++ if (ops->msi_prepare == NULL) ++ ops->msi_prepare = msi_domain_ops_default.msi_prepare; ++ if (ops->set_desc == NULL) ++ ops->set_desc = msi_domain_ops_default.set_desc; ++} ++ ++static void msi_domain_update_chip_ops(struct msi_domain_info *info) ++{ ++ struct irq_chip *chip = info->chip; ++ ++ BUG_ON(!chip); ++ if (!chip->irq_mask) ++ chip->irq_mask = pci_msi_mask_irq; ++ if (!chip->irq_unmask) ++ chip->irq_unmask = pci_msi_unmask_irq; ++ if (!chip->irq_set_affinity) ++ chip->irq_set_affinity = msi_domain_set_affinity; ++} ++ ++/** ++ * msi_create_irq_domain - Create a MSI interrupt domain ++ * @of_node: Optional device-tree node of the interrupt controller ++ * @info: MSI domain info ++ * @parent: Parent irq domain ++ */ ++struct irq_domain *msi_create_irq_domain(struct device_node *node, ++ struct msi_domain_info *info, ++ struct irq_domain *parent) ++{ ++ if (info->flags & MSI_FLAG_USE_DEF_DOM_OPS) ++ msi_domain_update_dom_ops(info); ++ if (info->flags & MSI_FLAG_USE_DEF_CHIP_OPS) ++ msi_domain_update_chip_ops(info); ++ ++ return irq_domain_add_hierarchy(parent, 0, 0, node, &msi_domain_ops, ++ info); ++} ++ ++/** ++ * msi_domain_alloc_irqs - Allocate interrupts from a MSI interrupt domain ++ * @domain: The domain to allocate from ++ * @dev: Pointer to device struct of the device for which the interrupts ++ * are allocated ++ * @nvec: The number of interrupts to allocate ++ * ++ * Returns 0 on success or an error code. ++ */ ++int msi_domain_alloc_irqs(struct irq_domain *domain, struct device *dev, ++ int nvec) ++{ ++ struct msi_domain_info *info = domain->host_data; ++ struct msi_domain_ops *ops = info->ops; ++ msi_alloc_info_t arg; ++ struct msi_desc *desc; ++ int i, ret, virq = -1; ++ ++ ret = ops->msi_check(domain, info, dev); ++ if (ret == 0) ++ ret = ops->msi_prepare(domain, dev, nvec, &arg); ++ if (ret) ++ return ret; ++ ++ for_each_msi_entry(desc, dev) { ++ ops->set_desc(&arg, desc); ++ if (info->flags & MSI_FLAG_IDENTITY_MAP) ++ virq = (int)ops->get_hwirq(info, &arg); ++ else ++ virq = -1; ++ ++ virq = __irq_domain_alloc_irqs(domain, virq, desc->nvec_used, ++ dev_to_node(dev), &arg, false); ++ if (virq < 0) { ++ ret = -ENOSPC; ++ if (ops->handle_error) ++ ret = ops->handle_error(domain, desc, ret); ++ if (ops->msi_finish) ++ ops->msi_finish(&arg, ret); ++ return ret; ++ } ++ ++ for (i = 0; i < desc->nvec_used; i++) ++ irq_set_msi_desc_off(virq, i, desc); ++ } ++ ++ if (ops->msi_finish) ++ ops->msi_finish(&arg, 0); ++ ++ for_each_msi_entry(desc, dev) { ++ if (desc->nvec_used == 1) ++ dev_dbg(dev, "irq %d for MSI\n", virq); ++ else ++ dev_dbg(dev, "irq [%d-%d] for MSI\n", ++ virq, virq + desc->nvec_used - 1); ++ } ++ ++ return 0; ++} ++ ++/** ++ * msi_domain_free_irqs - Free interrupts from a MSI interrupt @domain associated tp @dev ++ * @domain: The domain to managing the interrupts ++ * @dev: Pointer to device struct of the device for which the interrupts ++ * are free ++ */ ++void msi_domain_free_irqs(struct irq_domain *domain, struct device *dev) ++{ ++ struct msi_desc *desc; ++ ++ for_each_msi_entry(desc, dev) { ++ /* ++ * We might have failed to allocate an MSI early ++ * enough that there is no IRQ associated to this ++ * entry. If that's the case, don't do anything. ++ */ ++ if (desc->irq) { ++ irq_domain_free_irqs(desc->irq, desc->nvec_used); ++ desc->irq = 0; ++ } ++ } ++} ++ ++/** ++ * msi_get_domain_info - Get the MSI interrupt domain info for @domain ++ * @domain: The interrupt domain to retrieve data from ++ * ++ * Returns the pointer to the msi_domain_info stored in ++ * @domain->host_data. ++ */ ++struct msi_domain_info *msi_get_domain_info(struct irq_domain *domain) ++{ ++ return (struct msi_domain_info *)domain->host_data; ++} ++ ++#endif /* CONFIG_GENERIC_MSI_IRQ_DOMAIN */ +diff --git a/kernel/resource.c b/kernel/resource.c +index 0bcebff..19f2357 100644 +--- a/kernel/resource.c ++++ b/kernel/resource.c +@@ -22,6 +22,7 @@ + #include + #include + #include ++#include + #include + + +@@ -1529,6 +1530,30 @@ int iomem_is_exclusive(u64 addr) + return err; + } + ++struct resource_entry *resource_list_create_entry(struct resource *res, ++ size_t extra_size) ++{ ++ struct resource_entry *entry; ++ ++ entry = kzalloc(sizeof(*entry) + extra_size, GFP_KERNEL); ++ if (entry) { ++ INIT_LIST_HEAD(&entry->node); ++ entry->res = res ? res : &entry->__res; ++ } ++ ++ return entry; ++} ++EXPORT_SYMBOL(resource_list_create_entry); ++ ++void resource_list_free(struct list_head *head) ++{ ++ struct resource_entry *entry, *tmp; ++ ++ list_for_each_entry_safe(entry, tmp, head, node) ++ resource_list_destroy_entry(entry); ++} ++EXPORT_SYMBOL(resource_list_free); ++ + static int __init strict_iomem(char *str) + { + if (strstr(str, "relaxed")) +diff --git a/mm/Makefile b/mm/Makefile +index 8405eb0..e0bda2d 100644 +--- a/mm/Makefile ++++ b/mm/Makefile +@@ -18,7 +18,7 @@ obj-y := filemap.o mempool.o oom_kill.o \ + mm_init.o mmu_context.o percpu.o slab_common.o \ + compaction.o vmacache.o \ + interval_tree.o list_lru.o workingset.o \ +- iov_iter.o debug.o $(mmu-y) ++ iov_iter.o prfile.o debug.o $(mmu-y) + + obj-y += init-mm.o + +diff --git a/mm/filemap.c b/mm/filemap.c +index 7e6ab98..2fe1e57 100644 +--- a/mm/filemap.c ++++ b/mm/filemap.c +@@ -2063,7 +2063,7 @@ int filemap_page_mkwrite(struct vm_area_struct *vma, struct vm_fault *vmf) + int ret = VM_FAULT_LOCKED; + + sb_start_pagefault(inode->i_sb); +- file_update_time(vma->vm_file); ++ vma_file_update_time(vma); + lock_page(page); + if (page->mapping != inode->i_mapping) { + unlock_page(page); +diff --git a/mm/fremap.c b/mm/fremap.c +index 72b8fa3..a00bbf0 100644 +--- a/mm/fremap.c ++++ b/mm/fremap.c +@@ -224,16 +224,28 @@ get_write_lock: + */ + if (mapping_cap_account_dirty(mapping)) { + unsigned long addr; +- struct file *file = get_file(vma->vm_file); ++ struct file *file = vma->vm_file, ++ *prfile = vma->vm_prfile; ++ + /* mmap_region may free vma; grab the info now */ + vm_flags = vma->vm_flags; + ++ vma_get_file(vma); + addr = mmap_region(file, start, size, vm_flags, pgoff); +- fput(file); ++ vma_fput(vma); + if (IS_ERR_VALUE(addr)) { + err = addr; + } else { + BUG_ON(addr != start); ++ if (prfile) { ++ struct vm_area_struct *new_vma; ++ ++ new_vma = find_vma(mm, addr); ++ if (!new_vma->vm_prfile) ++ new_vma->vm_prfile = prfile; ++ if (new_vma != vma) ++ get_file(prfile); ++ } + err = 0; + } + goto out_freed; +diff --git a/mm/memory.c b/mm/memory.c +index 90fb265..844df2e 100644 +--- a/mm/memory.c ++++ b/mm/memory.c +@@ -2156,7 +2156,7 @@ reuse: + + /* file_update_time outside page_lock */ + if (vma->vm_file) +- file_update_time(vma->vm_file); ++ vma_file_update_time(vma); + } + put_page(dirty_page); + if (page_mkwrite) { +diff --git a/mm/mmap.c b/mm/mmap.c +index f88b4f9..9994987 100644 +--- a/mm/mmap.c ++++ b/mm/mmap.c +@@ -277,7 +277,7 @@ static struct vm_area_struct *remove_vma(struct vm_area_struct *vma) + if (vma->vm_ops && vma->vm_ops->close) + vma->vm_ops->close(vma); + if (vma->vm_file) +- fput(vma->vm_file); ++ vma_fput(vma); + mpol_put(vma_policy(vma)); + kmem_cache_free(vm_area_cachep, vma); + return next; +@@ -895,7 +895,7 @@ again: remove_next = 1 + (end > next->vm_end); + if (remove_next) { + if (file) { + uprobe_munmap(next, next->vm_start, next->vm_end); +- fput(file); ++ vma_fput(vma); + } + if (next->anon_vma) + anon_vma_merge(vma, next); +@@ -1680,8 +1680,8 @@ out: + return addr; + + unmap_and_free_vma: ++ vma_fput(vma); + vma->vm_file = NULL; +- fput(file); + + /* Undo any partial mapping done by a device driver. */ + unmap_region(mm, vma, prev, vma->vm_start, vma->vm_end); +@@ -2480,7 +2480,7 @@ static int __split_vma(struct mm_struct *mm, struct vm_area_struct *vma, + goto out_free_mpol; + + if (new->vm_file) +- get_file(new->vm_file); ++ vma_get_file(new); + + if (new->vm_ops && new->vm_ops->open) + new->vm_ops->open(new); +@@ -2499,7 +2499,7 @@ static int __split_vma(struct mm_struct *mm, struct vm_area_struct *vma, + if (new->vm_ops && new->vm_ops->close) + new->vm_ops->close(new); + if (new->vm_file) +- fput(new->vm_file); ++ vma_fput(new); + unlink_anon_vmas(new); + out_free_mpol: + mpol_put(vma_policy(new)); +@@ -2889,7 +2889,7 @@ struct vm_area_struct *copy_vma(struct vm_area_struct **vmap, + if (anon_vma_clone(new_vma, vma)) + goto out_free_mempol; + if (new_vma->vm_file) +- get_file(new_vma->vm_file); ++ vma_get_file(new_vma); + if (new_vma->vm_ops && new_vma->vm_ops->open) + new_vma->vm_ops->open(new_vma); + vma_link(mm, new_vma, prev, rb_link, rb_parent); +diff --git a/mm/nommu.c b/mm/nommu.c +index b5ba5bc..a7662fc 100644 +--- a/mm/nommu.c ++++ b/mm/nommu.c +@@ -658,7 +658,7 @@ static void __put_nommu_region(struct vm_region *region) + up_write(&nommu_region_sem); + + if (region->vm_file) +- fput(region->vm_file); ++ vmr_fput(region); + + /* IO memory and memory shared directly out of the pagecache + * from ramfs/tmpfs mustn't be released here */ +@@ -823,7 +823,7 @@ static void delete_vma(struct mm_struct *mm, struct vm_area_struct *vma) + if (vma->vm_ops && vma->vm_ops->close) + vma->vm_ops->close(vma); + if (vma->vm_file) +- fput(vma->vm_file); ++ vma_fput(vma); + put_nommu_region(vma->vm_region); + kmem_cache_free(vm_area_cachep, vma); + } +@@ -1385,7 +1385,7 @@ unsigned long do_mmap_pgoff(struct file *file, + goto error_just_free; + } + } +- fput(region->vm_file); ++ vmr_fput(region); + kmem_cache_free(vm_region_jar, region); + region = pregion; + result = start; +@@ -1461,10 +1461,10 @@ error_just_free: + up_write(&nommu_region_sem); + error: + if (region->vm_file) +- fput(region->vm_file); ++ vmr_fput(region); + kmem_cache_free(vm_region_jar, region); + if (vma->vm_file) +- fput(vma->vm_file); ++ vma_fput(vma); + kmem_cache_free(vm_area_cachep, vma); + kleave(" = %d", ret); + return ret; +diff --git a/mm/prfile.c b/mm/prfile.c +new file mode 100644 +index 0000000..532e518 +--- /dev/null ++++ b/mm/prfile.c +@@ -0,0 +1,86 @@ ++/* ++ * Mainly for aufs which mmap(2) diffrent file and wants to print different path ++ * in /proc/PID/maps. ++ * Call these functions via macros defined in linux/mm.h. ++ * ++ * See Documentation/filesystems/aufs/design/06mmap.txt ++ * ++ * Copyright (c) 2014 Junjro R. Okajima ++ * Copyright (c) 2014 Ian Campbell ++ */ ++ ++#include ++#include ++#include ++ ++/* #define PRFILE_TRACE */ ++static inline void prfile_trace(struct file *f, struct file *pr, ++ const char func[], int line, const char func2[]) ++{ ++#ifdef PRFILE_TRACE ++ if (pr) ++ pr_info("%s:%d: %s, %s\n", func, line, func2, ++ f ? (char *)f->f_dentry->d_name.name : "(null)"); ++#endif ++} ++ ++void vma_do_file_update_time(struct vm_area_struct *vma, const char func[], ++ int line) ++{ ++ struct file *f = vma->vm_file, *pr = vma->vm_prfile; ++ ++ prfile_trace(f, pr, func, line, __func__); ++ file_update_time(f); ++ if (f && pr) ++ file_update_time(pr); ++} ++ ++struct file *vma_do_pr_or_file(struct vm_area_struct *vma, const char func[], ++ int line) ++{ ++ struct file *f = vma->vm_file, *pr = vma->vm_prfile; ++ ++ prfile_trace(f, pr, func, line, __func__); ++ return (f && pr) ? pr : f; ++} ++ ++void vma_do_get_file(struct vm_area_struct *vma, const char func[], int line) ++{ ++ struct file *f = vma->vm_file, *pr = vma->vm_prfile; ++ ++ prfile_trace(f, pr, func, line, __func__); ++ get_file(f); ++ if (f && pr) ++ get_file(pr); ++} ++ ++void vma_do_fput(struct vm_area_struct *vma, const char func[], int line) ++{ ++ struct file *f = vma->vm_file, *pr = vma->vm_prfile; ++ ++ prfile_trace(f, pr, func, line, __func__); ++ fput(f); ++ if (f && pr) ++ fput(pr); ++} ++ ++#ifndef CONFIG_MMU ++struct file *vmr_do_pr_or_file(struct vm_region *region, const char func[], ++ int line) ++{ ++ struct file *f = region->vm_file, *pr = region->vm_prfile; ++ ++ prfile_trace(f, pr, func, line, __func__); ++ return (f && pr) ? pr : f; ++} ++ ++void vmr_do_fput(struct vm_region *region, const char func[], int line) ++{ ++ struct file *f = region->vm_file, *pr = region->vm_prfile; ++ ++ prfile_trace(f, pr, func, line, __func__); ++ fput(f); ++ if (f && pr) ++ fput(pr); ++} ++#endif /* !CONFIG_MMU */ +diff --git a/scripts/Kbuild.include b/scripts/Kbuild.include +index 65e7b08..5374b1b 100644 +--- a/scripts/Kbuild.include ++++ b/scripts/Kbuild.include +@@ -179,6 +179,12 @@ build := -f $(srctree)/scripts/Makefile.build obj + # $(Q)$(MAKE) $(modbuiltin)=dir + modbuiltin := -f $(srctree)/scripts/Makefile.modbuiltin obj + ++### ++# Shorthand for $(Q)$(MAKE) -f scripts/Makefile.dtbinst obj= ++# Usage: ++# $(Q)$(MAKE) $(dtbinst)=dir ++dtbinst := -f $(if $(KBUILD_SRC),$(srctree)/)scripts/Makefile.dtbinst obj ++ + # Prefix -I with $(srctree) if it is not an absolute path. + # skip if -I has no parameter + addtree = $(if $(patsubst -I%,%,$(1)), \ +diff --git a/scripts/Makefile.dtbinst b/scripts/Makefile.dtbinst +new file mode 100644 +index 0000000..909ed7a +--- /dev/null ++++ b/scripts/Makefile.dtbinst +@@ -0,0 +1,51 @@ ++# ========================================================================== ++# Installing dtb files ++# ++# Installs all dtb files listed in $(dtb-y) either in the ++# INSTALL_DTBS_PATH directory or the default location: ++# ++# $INSTALL_PATH/dtbs/$KERNELRELEASE ++# ++# Traverse through subdirectories listed in $(dts-dirs). ++# ========================================================================== ++ ++src := $(obj) ++ ++PHONY := __dtbs_install ++__dtbs_install: ++ ++export dtbinst-root ?= $(obj) ++ ++include include/config/auto.conf ++include scripts/Kbuild.include ++include $(srctree)/$(obj)/Makefile ++ ++PHONY += __dtbs_install_prep ++__dtbs_install_prep: ++ifeq ("$(dtbinst-root)", "$(obj)") ++ $(Q)if [ -d $(INSTALL_DTBS_PATH).old ]; then rm -rf $(INSTALL_DTBS_PATH).old; fi ++ $(Q)if [ -d $(INSTALL_DTBS_PATH) ]; then mv $(INSTALL_DTBS_PATH) $(INSTALL_DTBS_PATH).old; fi ++ $(Q)mkdir -p $(INSTALL_DTBS_PATH) ++endif ++ ++dtbinst-files := $(dtb-y) ++dtbinst-dirs := $(dts-dirs) ++ ++# Helper targets for Installing DTBs into the boot directory ++quiet_cmd_dtb_install = INSTALL $< ++ cmd_dtb_install = mkdir -p $(2); cp $< $(2) ++ ++install-dir = $(patsubst $(dtbinst-root)%,$(INSTALL_DTBS_PATH)%,$(obj)) ++ ++$(dtbinst-files) $(dtbinst-dirs): | __dtbs_install_prep ++ ++$(dtbinst-files): %.dtb: $(obj)/%.dtb ++ $(call cmd,dtb_install,$(install-dir)) ++ ++$(dtbinst-dirs): ++ $(Q)$(MAKE) $(dtbinst)=$(obj)/$@ ++ ++PHONY += $(dtbinst-files) $(dtbinst-dirs) ++__dtbs_install: $(dtbinst-files) $(dtbinst-dirs) ++ ++.PHONY: $(PHONY) +diff --git a/scripts/Makefile.lib b/scripts/Makefile.lib +index 54be19a..5117552 100644 +--- a/scripts/Makefile.lib ++++ b/scripts/Makefile.lib +@@ -283,18 +283,6 @@ $(obj)/%.dtb: $(src)/%.dts FORCE + + dtc-tmp = $(subst $(comma),_,$(dot-target).dts.tmp) + +-# Helper targets for Installing DTBs into the boot directory +-quiet_cmd_dtb_install = INSTALL $< +- cmd_dtb_install = cp $< $(2) +- +-_dtbinst_pre_: +- $(Q)if [ -d $(INSTALL_DTBS_PATH).old ]; then rm -rf $(INSTALL_DTBS_PATH).old; fi +- $(Q)if [ -d $(INSTALL_DTBS_PATH) ]; then mv $(INSTALL_DTBS_PATH) $(INSTALL_DTBS_PATH).old; fi +- $(Q)mkdir -p $(INSTALL_DTBS_PATH) +- +-%.dtb_dtbinst_: $(obj)/%.dtb _dtbinst_pre_ +- $(call cmd,dtb_install,$(INSTALL_DTBS_PATH)) +- + # Bzip2 + # --------------------------------------------------------------------------- + +diff --git a/sound/soc/fsl/mpc8610_hpcd.c b/sound/soc/fsl/mpc8610_hpcd.c +index fa756d0..ad57f0c 100644 +--- a/sound/soc/fsl/mpc8610_hpcd.c ++++ b/sound/soc/fsl/mpc8610_hpcd.c +@@ -12,11 +12,11 @@ + + #include + #include ++#include + #include + #include + #include + #include +-#include + + #include "fsl_dma.h" + #include "fsl_ssi.h" +diff --git a/sound/soc/fsl/p1022_ds.c b/sound/soc/fsl/p1022_ds.c +index f75c3cf..64a0bb6 100644 +--- a/sound/soc/fsl/p1022_ds.c ++++ b/sound/soc/fsl/p1022_ds.c +@@ -11,12 +11,12 @@ + */ + + #include ++#include + #include + #include + #include + #include + #include +-#include + + #include "fsl_dma.h" + #include "fsl_ssi.h" +diff --git a/sound/soc/fsl/p1022_rdk.c b/sound/soc/fsl/p1022_rdk.c +index 9d89bb0..4ce4aff 100644 +--- a/sound/soc/fsl/p1022_rdk.c ++++ b/sound/soc/fsl/p1022_rdk.c +@@ -18,12 +18,12 @@ + */ + + #include ++#include + #include + #include + #include + #include + #include +-#include + + #include "fsl_dma.h" + #include "fsl_ssi.h" +-- +2.7.4 + diff --git a/packages/base/any/kernels/3.18.25/patches/ls2_mc_console.patch b/packages/base/any/kernels/3.18.25/patches/ls2_mc_console.patch index cabeda6f..f5eb969e 100644 --- a/packages/base/any/kernels/3.18.25/patches/ls2_mc_console.patch +++ b/packages/base/any/kernels/3.18.25/patches/ls2_mc_console.patch @@ -1,20 +1,17 @@ diff -uNr a/drivers/soc/fsl/Kconfig.arm b/drivers/soc/fsl/Kconfig.arm ---- a/drivers/soc/fsl/Kconfig.arm 2017-04-14 17:32:33.398146919 +0530 -+++ b/drivers/soc/fsl/Kconfig.arm 2017-04-17 12:35:12.618118118 +0530 -@@ -20,6 +20,10 @@ - The Soc Drivers provides the device driver that is a specific block - or feature on Layerscape platform. - -+if LS_SOC_DRIVERS -+ source "drivers/soc/fsl/ls2-console/Kconfig" -+endif -+ +--- a/drivers/soc/fsl/Kconfig.arm 2017-06-05 17:37:14.530348991 +0530 ++++ b/drivers/soc/fsl/Kconfig.arm 2017-06-05 17:32:18.630348990 +0530 +@@ -23,3 +23,7 @@ if LS1_SOC_DRIVERS source "drivers/soc/fsl/ls1/Kconfig" endif ++ ++if LS_SOC_DRIVERS ++ source "drivers/soc/fsl/ls2-console/Kconfig" ++endif diff -uNr a/drivers/soc/fsl/ls2-console/Kconfig b/drivers/soc/fsl/ls2-console/Kconfig --- a/drivers/soc/fsl/ls2-console/Kconfig 1970-01-01 05:30:00.000000000 +0530 -+++ b/drivers/soc/fsl/ls2-console/Kconfig 2017-04-17 12:28:35.718118166 +0530 ++++ b/drivers/soc/fsl/ls2-console/Kconfig 2017-06-05 17:32:52.582348990 +0530 @@ -0,0 +1,4 @@ +config FSL_LS2_CONSOLE + tristate "Layerscape MC and AIOP console support" @@ -22,9 +19,9 @@ diff -uNr a/drivers/soc/fsl/ls2-console/Kconfig b/drivers/soc/fsl/ls2-console/Kc + default y diff -uNr a/drivers/soc/fsl/ls2-console/ls2-console.c b/drivers/soc/fsl/ls2-console/ls2-console.c --- a/drivers/soc/fsl/ls2-console/ls2-console.c 1970-01-01 05:30:00.000000000 +0530 -+++ b/drivers/soc/fsl/ls2-console/ls2-console.c 2017-04-17 12:28:35.682118166 +0530 -@@ -0,0 +1,272 @@ -+/* Copyright 2015 Freescale Semiconductor Inc. ++++ b/drivers/soc/fsl/ls2-console/ls2-console.c 2017-06-05 17:50:42.494348990 +0530 +@@ -0,0 +1,291 @@ ++/* Copyright 2015-2016 Freescale Semiconductor Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: @@ -64,9 +61,17 @@ diff -uNr a/drivers/soc/fsl/ls2-console/ls2-console.c b/drivers/soc/fsl/ls2-cons +#include +#include + -+/* MC and IOP character device to read from RAM */ ++/* SoC address for the MC firmware base low/high registers */ ++#define SOC_CCSR_MC_FW_BASE_ADDR_REGS 0x8340020 ++#define SOC_CCSR_MC_FW_BASE_ADDR_REGS_SIZE 2 ++/* MC firmware base low/high registers indexes */ ++#define MCFBALR_OFFSET 0 ++#define MCFBAHR_OFFSET 1 + -+#define MC_BASE_ADDR 0x83c0000000 ++/* Bit mask used to obtain the most significant part of the MC base address */ ++#define MC_FW_HIGH_ADDR_MASK 0x1FFFF ++/* Bit mask used to obtain the least significant part of the MC base address */ ++#define MC_FW_LOW_ADDR_MASK 0xE0000000 + +#define MC_BUFFER_OFFSET 0x01000000 +#define MC_BUFFER_SIZE (1024*1024*16) @@ -117,6 +122,17 @@ diff -uNr a/drivers/soc/fsl/ls2-console/ls2-console.c b/drivers/soc/fsl/ls2-cons + __adjust_end(cd); +} + ++static inline uint64_t get_mc_fw_base_address(void) { ++ uint32_t* mcfbaregs = (uint32_t*) ioremap(SOC_CCSR_MC_FW_BASE_ADDR_REGS, ++ SOC_CCSR_MC_FW_BASE_ADDR_REGS_SIZE); ++ uint64_t mcfwbase = 0ULL; ++ mcfwbase = readl(mcfbaregs + MCFBAHR_OFFSET) & MC_FW_HIGH_ADDR_MASK; ++ mcfwbase <<= 32; ++ mcfwbase |= readl(mcfbaregs + MCFBALR_OFFSET) & MC_FW_LOW_ADDR_MASK; ++ iounmap(mcfbaregs); ++ pr_info("fsl-ls2-console: MC base address at 0x%016llx\n", mcfwbase); ++ return mcfwbase; ++} + +static int fsl_ls2_generic_console_open(struct inode *node, struct file *fp, + u64 offset, u64 size, @@ -131,7 +147,7 @@ diff -uNr a/drivers/soc/fsl/ls2-console/ls2-console.c b/drivers/soc/fsl/ls2-cons + if (cd == NULL) + return -ENOMEM; + fp->private_data = cd; -+ cd->map_addr = ioremap(MC_BASE_ADDR + offset, size); ++ cd->map_addr = ioremap(get_mc_fw_base_address() + offset, size); + + cd->hdr = (struct log_header *) cd->map_addr; + invalidate(cd->hdr); @@ -298,12 +314,12 @@ diff -uNr a/drivers/soc/fsl/ls2-console/ls2-console.c b/drivers/soc/fsl/ls2-cons +MODULE_DESCRIPTION("Freescale LS2 console driver"); diff -uNr a/drivers/soc/fsl/ls2-console/Makefile b/drivers/soc/fsl/ls2-console/Makefile --- a/drivers/soc/fsl/ls2-console/Makefile 1970-01-01 05:30:00.000000000 +0530 -+++ b/drivers/soc/fsl/ls2-console/Makefile 2017-04-17 12:28:35.706118166 +0530 ++++ b/drivers/soc/fsl/ls2-console/Makefile 2017-06-05 17:32:52.582348990 +0530 @@ -0,0 +1 @@ +obj-$(CONFIG_FSL_LS2_CONSOLE) += ls2-console.o diff -uNr a/drivers/soc/fsl/Makefile b/drivers/soc/fsl/Makefile ---- a/drivers/soc/fsl/Makefile 2017-04-14 17:32:33.398146919 +0530 -+++ b/drivers/soc/fsl/Makefile 2017-04-17 12:30:18.578118153 +0530 +--- a/drivers/soc/fsl/Makefile 2017-06-05 17:37:14.530348991 +0530 ++++ b/drivers/soc/fsl/Makefile 2017-06-05 17:33:54.022348991 +0530 @@ -4,3 +4,4 @@ obj-$(CONFIG_LS1_SOC_DRIVERS) += ls1/ diff --git a/packages/base/any/kernels/3.18.25/patches/series.arm64 b/packages/base/any/kernels/3.18.25/patches/series.arm64 index e5de270b..f9ac0ce2 100644 --- a/packages/base/any/kernels/3.18.25/patches/series.arm64 +++ b/packages/base/any/kernels/3.18.25/patches/series.arm64 @@ -1,7 +1,2 @@ -aufs.patch -driver-support-intel-igb-bcm54616-phy.patch -add-kernel-patches-for-nxp-arm64-ls2080ardb-based-on.patch -add-nxp-arm64-ls2088ardb-device-tree.patch -add-fsl-dpaa2-and-fsl-mc-support-based-on-3.18.25.patch -backport-some-kernel-patches-based-on-3.18.25.patch +0001-Patch-set-for-booting-ls2088rdb-with-vfio.patch ls2_mc_console.patch