Hello Everyone,
A lot of things has happened in the area of improving Exynos IOMMU driver and discussion about generic IOMMU bindings, which finally motivated me to get back to IOMMU related tasks. Just to remind, here are those 2 important threads:
1. [PATCH v13 00/19] iommu/exynos: Fixes and Enhancements of System MMU driver with DT: https://lkml.org/lkml/2014/5/12/34
2. [PATCH v4] devicetree: Add generic IOMMU device tree bindings: https://lkml.org/lkml/2014/7/4/349
As a follow up of those discussions I've decided to finish our internal code, which adapts Exynos SYSMMU driver to meet generic IOMMU bindings requirements and implement all needed glue code to finally demonstare seemless integration IOMMU controller with DMA-mapping subsystem for the drivers available on Exynos SoCs.
1. Introduction - a few words for those who are not fully aware of the Exynos SoC hardware
Exynos SoC consists of various devices integrated directly into SoCs. Most of them are multimedia devices, which usually process large buffers. Some of them (like i.e. MFC - a multimedia codec or FIMD - a multi-window framebuffer device & lcd panel controller) are equipped with more than one memory interface for higher processing performance. There are also really complex subsystems (like ISP, the camera sensor interface & processor), which consist of many sub-blocks, each having its own memory interface/channel/bus (different names are used for the same thing).
Each such memory controller might be equipped with SYSMMU device, which acts as IOMMU controller for the parent device (called master device, a device which that memory interface belongs to). Each SYSMMU controller has its own register set and clock, belongs to the same power domain as master device. There is also some non-direct relation from master's device gate clock - SYSMMU registers can be accessed only when master's gate clock is enabled.
Basically we have following dependencies between hardware and drivers: - each multimedia device might have 1 or more SYSMMU controller - each SYSMMU controller belongs only to 1 master device - all SYSMMU controllers are independent of each other, there is no global hardware ID that must be assigned to enable given SYSMMU controller - multimedia devices are modeled usually by a separate node in device tree with it's own compatible string and separate driver for them - sub-blocks of complex devices right now are not modeled by a separate device tree nodes, but this might be changed in the future - some multimedia devices have limited address space per each memory controller/channel (i.e. codec might access buffers only in a 256MiB window for each of it's memory channels) - some drivers for independent device are used together to provide a more complex subsystem, i.e. FIMD, HDMI-mixer and others form together Exynos DRM subsystem; it is highly welcome to let them to operate in the same, shared DMA address space to simplify buffer sharing
2. Introduction part 2 - a few word of summary of the discussions about generic IOMMU DT bindings
There have been a lot of discussions on the method of modeling IOMMU controllers in device tree. The approach which has been selected as the generic IOMMU binding candidate has been described in the '[PATCH v4] devicetree: Add generic IOMMU device tree bindings' thread.
Those bindings describe how to link an IOMMU controller with its master device. Basically an 'iommus' property placed in the master's device node has been introduced. This property contains phandle to IOMMU controller node. Optional properties of the particular binding can also be specified after the phandle, assuming that IOMMU controller node contains '#iommu-cells' property, which defines number of cells used for those parameters. Those parameters are then interpreted by particular IOMMU controller driver. Those parameters might be some hw channel id required for correct hardware setup, base address and size pair for limited IO address space window or others hardware dependant properties.
3. IOMMU integration to DMA-mapping subsystem
By default we assume that each master device, which has been equipped with IOMMU controller gets its own DMA (IO) address space. This is created automatically and transparently without any changes in the device driver code. All DMA-mapping functions are replaced with the IOMMU aware versions. This has to be done somewhere by the architecture or SoC startup code, so when master's driver probe() function is called, everything is in place.
However some device drivers might need (for various reasons) to manually manage DMA (IO) address space. For this case a driver need to notify kernel about that and do the management of DMA address space on its own. This has been achieved by introducing DRIVER_HAS_OWN_IOMMU_MANAGER flag, which can be set in struct device_driver. This way the startup code can easily determine if creating the default per-device separate DMA address space is required for a given driver or not without any unneeded alloc/free call sequences.
4. Linux DMA-mapping subsystem and more than one DMA address space
DMA-mapping subsystem assumes that there is only one DMA (IO) address space associated with the given struct device entity. Usually struct device is mapped in one-to-one relation to a node describing given device in device tree. To let driver to access other DMA (IO) address spaces a sub-device has been introduced. This approach has been already used by s5p-mfc driver (drivers/media/platform/s5p-mfc/s5p_mfc.c). The only question is how and when sub-devices are created.
In the proposed approach, such additional address spaces are named with the names of the respective IOMMU controllers (iommu-names property in master's DT node). To let driver to access an address space, a sub-device named 'parent_device_name:address_space_name' need to be created and added as a child to master's struct device. A good example is codec device, which on Exynos4412 SoC is instantiated as '13400000.codec' device. It has 2 memory interfaces ('left' and 'right'), so the sub-devices called '13400000.codec:left' and '13400000.codec:right' must be created by a driver and added as children of '13400000.codec' device. Once then the driver is allowed to allocate 2 separate dma-mapping address spaces by calling arm_iommu_create_mapping() and arm_iommu_detach_device() functions or newly introduced helper arm_iommu_create_default_mapping(). For more details, please refer to the last patch in this series.
Exactly the same approach is planned to be done for memory regions and DMA-mapping implemented on top of CMA or DMA-coherent memory allocators.
When driver doesn't specify that it wants to manage its DMA (IO) address spaces, a default DMA (IO) address space will be created and all SYSMMU controllers will be bound to it, so this space will be shared across master's device IO channels / memory interfaces. This way IOMMU support might be added only to drivers which really benefit from having separate IO address space per memory interface without a need to alter the other drivers.
Why driver might need to manage the IO address space on its own? Once again the codec device on Exynos4 series is a good example. Memory interfaces found in the mentioned codec device are limited and can address only 256MiB window. If we bind both interfaces to common address space, driver is able only to access memory buffers, which fits into 256MiB window. If we use separate spaces for each memory interface, codec device will be able to access buffers of total 2*256MiB=512MiB, which is a significant advantage over the default case of shared address space.
5. Power management (runtime)
Runtime power management is the most tricky part of the proposed solution. I assumed that it is a sane requirement that from the master's device driver the operation without IOMMU and with IOMMU (with default, per-device mapping) should be exactly the same. The runtime power management, which is now mainly limited to enabling and disabling hardware power domains is done by the master's device driver. However from the hardware perspective, there is also a need to save SYSMMU context before switch pm domain off and restore it after switching pm domain on.
To achieve this way of SYSMMU operation, a notifiers for power domains have been introduces. With such an approach no changes are needed in master's device driver and SYSMMU driver seamlessly integrates with master's device runtime pm operations.
6. Proposed patches and changes
Patch 0001 "pm: Add PM domain notifications" adds support for power domain notifiers (see chapter 5 above).
Patch 0002 "ARM: Exynos: bind power domains earlier, on device creation" changes the time, when Exynos power domains are bound to the device. Now this happens on DEVICE_ADD event instead of DRIVER_BIND, so when SYSMMU driver is being initialized, the power domains are already bound and notifiers can be added.
Patch 0003 "clk: exynos: add missing smmu_g2d clock and update comments" simply simply adds missing sysmmu related entities to Exynos clock driver.
Patch 0004 "drivers: base: add notifier for failed driver bind" add event for failed driver bind, so things prepared in DRIVER_BIND event can be cleaned up, similar to DRIVER_UNBOUND.
Patch 0005 "drivers: convert suppress_bind_attrs parameter into flags" is preparation for adding new flags to struct device_driver.
Patch 0006 "drivers: iommu: add notify about failed bind" adds support for recently introduced failed driver bind event to IOMMU subsystem.
Patch 0007 "ARM: dma-mapping: arm_iommu_attach_device: automatically set max_seg_size" moves common operation of setting dma max_seg_size directly to arm_iommu_attach_device function.
Patch 0008 "ARM: dma-mapping: add helpers for managing default per-device dma mappings" adds convenient helpers for the most common case of setting up per-device, separate DMA (IO) address space.
Patch 0009 "ARM: dma-mapping: provide stubs if no ARM_DMA_USE_IOMMU has been selected" fixes usage of IOMMU related ARM DMA-mapping functions in common code.
Patch 0010 "drivers: add DRIVER_HAS_OWN_IOMMU_MANAGER flag" adds a flag described in chapter 3.
Patch 0011 "DRM: exynos: add DRIVER_HAS_OWN_IOMMU_MANAGER flag to all sub-drivers" marks all Exynos DRM sub-drivers with a flag notifying that they perform own management of DMA (IO) address space. All the code to setup dma-mapping and attach all devices is realy there.
Patch 0012 "DRM: Exynos: fix window clear code" is a simple bugfix of broken init code, which triggers issues when used with IOMMU (page fault happens on systems, where bootloader has left framebuffer enabled).
Patch 0013 "temporary: media: s5p-mfc: remove DT hacks & initialization custom memory init code" removes all custom memory region handling, to let later demonstrate how to use separate DMA (IO) address spaces from master's device driver.
Patch 0014 "devicetree: Update Exynos SYSMMU device tree bindings" adds a few words about proposed solution to SYSMMU device tree bindings documentation.
Patch 0015 "ARM: DTS: Exynos4: add System MMU nodes" adds device tree nodes for all SYSMMU controllers found in Exynos 4210 and 4x12 SoC and respective properties to their master devices.
Patch 0016-0021 are simple bugfixes and code refactoring to simplify the driver: "iommu: exynos: make driver multiarch friendly", "iommu: exynos: don't read version register on every tlb", "iommu: exynos: remove unused functions", "iommu: exynos: remove useless spinlock", "iommu: exynos: refactor function parameters to simplify code", "iommu: exynos: remove unused functions, part 2".
Patch 0022 "iommu: exynos: add support for binding more than one sysmmu to master device" adds support for storing a list of SYSMMU controllers in the master's iommu arch data structure.
Patch 0023 "iommu: exynos: init iommu controllers from device tree" finally implements bindings described in patch 0015 and access to particular DMA address space managed by SYSMMU controller via sub-device of predefined name (see chapter 4).
Patch 0024 "iommu: exynos: create default iommu-based dma-mapping for master devices" does what patch title says.
Patch 0025 "iommu: exynos: add support for runtime_pm" implements power management scheme described in chapter 5.
Patch 0026-0028 are cleanup and refactoring to make the code easier to understand: "iommu: exynos: rename variables to reflect their purpose", "iommu: exynos: document internal structures", "iommu: exynos: remove excessive includes and sort others alphabetically".
Patch 0029 "temporary: media: s5p-mfc: add support for IOMMU" demonstrates how to use sub-devices to get access to separate DMA (IO) address spaces. The driver is able to work both with and without this patch. Without this patch a common shared address space is created for both SYSMMU controllers (so only 256MiB of total address space is available, see end of chapter 4).
7. Summary
All the development of those patches have been done on Exynos4412-based OdroidU3 board and Exynos4210-based UniversalC210, on top of v3.16 kernel with some additional patches to enable HDMI support on Odroid board. This version is available in the following GIT repository: http://git.linaro.org/git/people/marek.szyprowski/linux-dma-mapping.git on branch v3.16-odroid-iommu.
However, the version posted here has been rebased on top of linux-next kernel (next-20140804 tag), to make marging the easier once v3.17-rc1 is out.
8. Diffstat
.../devicetree/bindings/iommu/samsung,sysmmu.txt | 93 ++- Documentation/power/notifiers.txt | 14 + arch/arm/boot/dts/exynos4.dtsi | 118 ++++ arch/arm/boot/dts/exynos4210.dtsi | 23 + arch/arm/boot/dts/exynos4x12.dtsi | 82 +++ arch/arm/include/asm/dma-iommu.h | 36 ++ arch/arm/mach-exynos/pm_domains.c | 12 +- arch/arm/mach-integrator/impd1.c | 2 +- arch/arm/mm/dma-mapping.c | 47 ++ drivers/base/bus.c | 4 +- drivers/base/dd.c | 10 +- drivers/base/platform.c | 2 +- drivers/base/power/domain.c | 70 ++- drivers/clk/samsung/clk-exynos4.c | 1 + drivers/gpu/drm/exynos/exynos_drm_fimc.c | 1 + drivers/gpu/drm/exynos/exynos_drm_fimd.c | 26 +- drivers/gpu/drm/exynos/exynos_drm_g2d.c | 1 + drivers/gpu/drm/exynos/exynos_drm_gsc.c | 1 + drivers/gpu/drm/exynos/exynos_drm_rotator.c | 1 + drivers/gpu/drm/exynos/exynos_mixer.c | 1 + drivers/iommu/exynos-iommu.c | 663 +++++++++++++-------- drivers/iommu/iommu.c | 3 + drivers/media/platform/s5p-mfc/s5p_mfc.c | 107 ++-- drivers/pci/host/pci-mvebu.c | 2 +- drivers/pci/host/pci-rcar-gen2.c | 2 +- drivers/pci/host/pci-tegra.c | 2 +- drivers/pci/host/pcie-rcar.c | 2 +- drivers/soc/tegra/pmc.c | 2 +- include/dt-bindings/clock/exynos4.h | 10 +- include/linux/device.h | 12 +- include/linux/iommu.h | 1 + include/linux/pm.h | 2 + include/linux/pm_domain.h | 19 + 33 files changed, 1016 insertions(+), 356 deletions(-)
Best regards Marek Szyprowski Samsung R&D Institute Poland
From: Sylwester Nawrocki s.nawrocki@samsung.com
This patch adds notifiers to the runtime PM/genpd subsystem. It is now possible to register a notifier, which will be called before and after the generic power domain subsystem calls the power domain's power_on and power_off callbacks.
Signed-off-by: Sylwester Nawrocki s.nawrocki@samsung.com --- Documentation/power/notifiers.txt | 14 ++++++++ drivers/base/power/domain.c | 70 ++++++++++++++++++++++++++++++++++++--- include/linux/pm.h | 2 ++ include/linux/pm_domain.h | 19 +++++++++++ 4 files changed, 101 insertions(+), 4 deletions(-)
diff --git a/Documentation/power/notifiers.txt b/Documentation/power/notifiers.txt index a81fa25..62303f6 100644 --- a/Documentation/power/notifiers.txt +++ b/Documentation/power/notifiers.txt @@ -53,3 +53,17 @@ NULL). To register and/or unregister a suspend notifier use the functions register_pm_notifier() and unregister_pm_notifier(), respectively, defined in include/linux/suspend.h . If you don't need to unregister the notifier, you can also use the pm_notifier() macro defined in include/linux/suspend.h . + +Power Domain notifiers +---------------------- + +The power domain notifiers allow subsystems or drivers to register for power +domain on/off notifications, should they need to perform any actions right +before or right after the power domain on/off. The device must be already +added to a power domain before its subsystem or driver registers the notifier. +Following events are supported: + +PM_GENPD_POWER_ON_PREPARE The power domain is about to turn on. +PM_GENPD_POST_POWER_ON The power domain has just turned on. +PM_GENPD_POWER_OFF_PREPARE The power domain is about to turn off. +PM_GENPD_POST_POWER_OFF The power domain has just turned off. diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index eee55c1..5fe0966 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -70,6 +70,45 @@ static struct generic_pm_domain *pm_genpd_lookup_name(const char *domain_name) return genpd; }
+int pm_genpd_register_notifier(struct device *dev, struct notifier_block *nb) +{ + struct pm_domain_data *pdd; + int ret = -EINVAL; + + spin_lock_irq(&dev->power.lock); + if (dev->power.subsys_data) { + pdd = dev->power.subsys_data->domain_data; + ret = blocking_notifier_chain_register(&pdd->notify_chain_head, + nb); + } + spin_unlock_irq(&dev->power.lock); + return ret; +} +EXPORT_SYMBOL_GPL(pm_genpd_register_notifier); + +void pm_genpd_unregister_notifier(struct device *dev, struct notifier_block *nb) +{ + struct pm_domain_data *pdd; + + spin_lock_irq(&dev->power.lock); + if (dev->power.subsys_data) { + pdd = dev->power.subsys_data->domain_data; + blocking_notifier_chain_unregister(&pdd->notify_chain_head, nb); + } + spin_unlock_irq(&dev->power.lock); +} +EXPORT_SYMBOL_GPL(pm_genpd_unregister_notifier); + +static void pm_genpd_notifier_call(unsigned long event, + struct generic_pm_domain *genpd) +{ + struct pm_domain_data *pdd; + + list_for_each_entry(pdd, &genpd->dev_list, list_node) + blocking_notifier_call_chain(&pdd->notify_chain_head, + event, pdd->dev); +} + #ifdef CONFIG_PM
struct generic_pm_domain *dev_to_genpd(struct device *dev) @@ -231,10 +270,14 @@ static int __pm_genpd_poweron(struct generic_pm_domain *genpd) ktime_t time_start = ktime_get(); s64 elapsed_ns;
+ pm_genpd_notifier_call(PM_GENPD_POWER_ON_PREPARE, genpd); + ret = genpd->power_on(genpd); if (ret) goto err;
+ pm_genpd_notifier_call(PM_GENPD_POST_POWER_ON, genpd); + elapsed_ns = ktime_to_ns(ktime_sub(ktime_get(), time_start)); if (elapsed_ns > genpd->power_on_latency_ns) { genpd->power_on_latency_ns = elapsed_ns; @@ -554,13 +597,17 @@ static int pm_genpd_poweroff(struct generic_pm_domain *genpd) * the pm_genpd_poweron() restore power for us (this shouldn't * happen very often). */ + pm_genpd_notifier_call(PM_GENPD_POWER_OFF_PREPARE, genpd); + ret = genpd->power_off(genpd); if (ret == -EBUSY) { genpd_set_active(genpd); goto out; } - elapsed_ns = ktime_to_ns(ktime_sub(ktime_get(), time_start)); + + pm_genpd_notifier_call(PM_GENPD_POST_POWER_OFF, genpd); + if (elapsed_ns > genpd->power_off_latency_ns) { genpd->power_off_latency_ns = elapsed_ns; genpd->max_off_time_changed = true; @@ -837,9 +884,13 @@ static void pm_genpd_sync_poweroff(struct generic_pm_domain *genpd) || atomic_read(&genpd->sd_count) > 0) return;
- if (genpd->power_off) + if (genpd->power_off) { + pm_genpd_notifier_call(PM_GENPD_POWER_OFF_PREPARE, genpd); genpd->power_off(genpd);
+ pm_genpd_notifier_call(PM_GENPD_POST_POWER_OFF, genpd); + } + genpd->status = GPD_STATE_POWER_OFF;
list_for_each_entry(link, &genpd->slave_links, slave_node) { @@ -869,8 +920,11 @@ static void pm_genpd_sync_poweron(struct generic_pm_domain *genpd) genpd_sd_counter_inc(link->master); }
- if (genpd->power_on) + if (genpd->power_on) { + pm_genpd_notifier_call(PM_GENPD_POWER_ON_PREPARE, genpd); genpd->power_on(genpd); + pm_genpd_notifier_call(PM_GENPD_POST_POWER_ON, genpd); + }
genpd->status = GPD_STATE_ACTIVE; } @@ -1292,9 +1346,16 @@ static int pm_genpd_restore_noirq(struct device *dev) * If the domain was off before the hibernation, make * sure it will be off going forward. */ - if (genpd->power_off) + if (genpd->power_off) { + pm_genpd_notifier_call(PM_GENPD_POWER_OFF_PREPARE, + genpd); + genpd->power_off(genpd);
+ pm_genpd_notifier_call(PM_GENPD_POST_POWER_OFF, + genpd); + } + return 0; } } @@ -1467,6 +1528,7 @@ int __pm_genpd_add_device(struct generic_pm_domain *genpd, struct device *dev, spin_unlock_irq(&dev->power.lock);
mutex_lock(&gpd_data->lock); + BLOCKING_INIT_NOTIFIER_HEAD(&gpd_data->base.notify_chain_head); gpd_data->base.dev = dev; list_add_tail(&gpd_data->base.list_node, &genpd->dev_list); gpd_data->need_restore = genpd->status == GPD_STATE_POWER_OFF; diff --git a/include/linux/pm.h b/include/linux/pm.h index 72c0fe0..bfc55d4 100644 --- a/include/linux/pm.h +++ b/include/linux/pm.h @@ -22,6 +22,7 @@ #define _LINUX_PM_H
#include <linux/list.h> +#include <linux/notifier.h> #include <linux/workqueue.h> #include <linux/spinlock.h> #include <linux/wait.h> @@ -542,6 +543,7 @@ struct wakeup_source; struct pm_domain_data { struct list_head list_node; struct device *dev; + struct blocking_notifier_head notify_chain_head; };
struct pm_subsys_data { diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h index 7c1d252..569ab16 100644 --- a/include/linux/pm_domain.h +++ b/include/linux/pm_domain.h @@ -17,6 +17,12 @@ #include <linux/notifier.h> #include <linux/cpuidle.h>
+/* PM domain state transition notifications */ +#define PM_GENPD_POWER_ON_PREPARE 0x01 +#define PM_GENPD_POST_POWER_ON 0x02 +#define PM_GENPD_POWER_OFF_PREPARE 0x03 +#define PM_GENPD_POST_POWER_OFF 0x04 + enum gpd_status { GPD_STATE_ACTIVE = 0, /* PM domain is active */ GPD_STATE_WAIT_MASTER, /* PM domain's master is being waited for */ @@ -167,6 +173,11 @@ extern int pm_genpd_name_poweron(const char *domain_name);
extern bool default_stop_ok(struct device *dev);
+extern int pm_genpd_register_notifier(struct device *dev, + struct notifier_block *nb); +extern void pm_genpd_unregister_notifier(struct device *dev, + struct notifier_block *nb); + extern struct dev_power_governor pm_domain_always_on_gov; #else
@@ -259,6 +270,14 @@ static inline bool default_stop_ok(struct device *dev) { return false; } +static inline int pm_genpd_register_notifier(struct device *dev, + struct notifier_block *nb) +{ + return -ENOSYS; +} +static inline void pm_genpd_unregister_notifier(struct device *dev, + struct notifier_block *nb) {} + #define simple_qos_governor NULL #define pm_domain_always_on_gov NULL #endif
This patches change initialization time of power domain driver from client device driver bind to device creation. This lets other core drivers to register power domain notifiers before client driver is bound.
Signed-off-by: Marek Szyprowski m.szyprowski@samsung.com --- arch/arm/mach-exynos/pm_domains.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/arch/arm/mach-exynos/pm_domains.c b/arch/arm/mach-exynos/pm_domains.c index fd76e1b..1d368a2 100644 --- a/arch/arm/mach-exynos/pm_domains.c +++ b/arch/arm/mach-exynos/pm_domains.c @@ -159,13 +159,13 @@ static int exynos_pm_notifier_call(struct notifier_block *nb, struct device *dev = data;
switch (event) { - case BUS_NOTIFY_BIND_DRIVER: + case BUS_NOTIFY_ADD_DEVICE: if (dev->of_node) exynos_read_domain_from_dt(dev);
break;
- case BUS_NOTIFY_UNBOUND_DRIVER: + case BUS_NOTIFY_DEL_DEVICE: exynos_remove_device_from_domain(dev);
break; @@ -177,6 +177,13 @@ static struct notifier_block platform_nb = { .notifier_call = exynos_pm_notifier_call, };
+static int exynos_pm_domain_add(struct device *dev, void *priv) +{ + if (dev->of_node) + exynos_read_domain_from_dt(dev); + return 0; +} + static __init int exynos4_pm_init_power_domain(void) { struct platform_device *pdev; @@ -236,6 +243,7 @@ no_clk: }
bus_register_notifier(&platform_bus_type, &platform_nb); + bus_for_each_dev(&platform_bus_type, NULL, NULL, exynos_pm_domain_add);
return 0; }
This patch adds missing smmu_g2d clock implementation and updates comment about Exynos4 clocks from 278-282 range. Those clocks are available on all Exynos4 SoC series, so the misleading comment has been removed.
Signed-off-by: Marek Szyprowski m.szyprowski@samsung.com --- drivers/clk/samsung/clk-exynos4.c | 1 + include/dt-bindings/clock/exynos4.h | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-)
diff --git a/drivers/clk/samsung/clk-exynos4.c b/drivers/clk/samsung/clk-exynos4.c index ac163d7..12a7cc3 100644 --- a/drivers/clk/samsung/clk-exynos4.c +++ b/drivers/clk/samsung/clk-exynos4.c @@ -1183,6 +1183,7 @@ static struct samsung_gate_clock exynos4x12_gate_clks[] __initdata = { GATE(CLK_SPI1_ISP, "spi1_isp", "aclk200", E4X12_GATE_ISP1, 13, CLK_IGNORE_UNUSED | CLK_GET_RATE_NOCACHE, 0), GATE(CLK_G2D, "g2d", "aclk200", GATE_IP_DMC, 23, 0, 0), + GATE(CLK_SMMU_G2D, "smmu_g2d", "aclk200", GATE_IP_DMC, 24, 0, 0), GATE(CLK_TMU_APBIF, "tmu_apbif", "aclk100", E4X12_GATE_IP_PERIR, 17, 0, 0), }; diff --git a/include/dt-bindings/clock/exynos4.h b/include/dt-bindings/clock/exynos4.h index 459bd2b..fb981635 100644 --- a/include/dt-bindings/clock/exynos4.h +++ b/include/dt-bindings/clock/exynos4.h @@ -115,11 +115,11 @@ #define CLK_SMMU_MFCR 275 #define CLK_G3D 276 #define CLK_G2D 277 -#define CLK_ROTATOR 278 /* Exynos4210 only */ -#define CLK_MDMA 279 /* Exynos4210 only */ -#define CLK_SMMU_G2D 280 /* Exynos4210 only */ -#define CLK_SMMU_ROTATOR 281 /* Exynos4210 only */ -#define CLK_SMMU_MDMA 282 /* Exynos4210 only */ +#define CLK_ROTATOR 278 +#define CLK_MDMA 279 +#define CLK_SMMU_G2D 280 +#define CLK_SMMU_ROTATOR 281 +#define CLK_SMMU_MDMA 282 #define CLK_FIMD0 283 #define CLK_MIE0 284 #define CLK_MDNIE0 285 /* Exynos4412 only */
On 05.08.2014 12:47, Marek Szyprowski wrote:
This patch adds missing smmu_g2d clock implementation and updates comment about Exynos4 clocks from 278-282 range. Those clocks are available on all Exynos4 SoC series, so the misleading comment has been removed.
Signed-off-by: Marek Szyprowski m.szyprowski@samsung.com
drivers/clk/samsung/clk-exynos4.c | 1 + include/dt-bindings/clock/exynos4.h | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-)
Acked-by: Tomasz Figa t.figa@samsung.com
Best regards, Tomasz
This patch adds support for getting a notify for failed device driver bind, so all the items done in BUS_NOTIFY_BIND_DRIVER event can be cleaned if the driver fails to bind.
Signed-off-by: Marek Szyprowski m.szyprowski@samsung.com --- drivers/base/dd.c | 10 +++++++--- include/linux/device.h | 4 +++- 2 files changed, 10 insertions(+), 4 deletions(-)
diff --git a/drivers/base/dd.c b/drivers/base/dd.c index e4ffbcf..541a41f 100644 --- a/drivers/base/dd.c +++ b/drivers/base/dd.c @@ -237,10 +237,14 @@ static int driver_sysfs_add(struct device *dev) return ret; }
-static void driver_sysfs_remove(struct device *dev) +static void driver_sysfs_remove(struct device *dev, int failed) { struct device_driver *drv = dev->driver;
+ if (failed && dev->bus) + blocking_notifier_call_chain(&dev->bus->p->bus_notifier, + BUS_NOTIFY_DRVBIND_FAILED, dev); + if (drv) { sysfs_remove_link(&drv->p->kobj, kobject_name(&dev->kobj)); sysfs_remove_link(&dev->kobj, "driver"); @@ -316,7 +320,7 @@ static int really_probe(struct device *dev, struct device_driver *drv)
probe_failed: devres_release_all(dev); - driver_sysfs_remove(dev); + driver_sysfs_remove(dev, true); dev->driver = NULL; dev_set_drvdata(dev, NULL);
@@ -509,7 +513,7 @@ static void __device_release_driver(struct device *dev) if (drv) { pm_runtime_get_sync(dev);
- driver_sysfs_remove(dev); + driver_sysfs_remove(dev, false);
if (dev->bus) blocking_notifier_call_chain(&dev->bus->p->bus_notifier, diff --git a/include/linux/device.h b/include/linux/device.h index b387710..92daded 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -176,7 +176,7 @@ extern int bus_register_notifier(struct bus_type *bus, extern int bus_unregister_notifier(struct bus_type *bus, struct notifier_block *nb);
-/* All 4 notifers below get called with the target struct device * +/* All 7 notifers below get called with the target struct device * * as an argument. Note that those functions are likely to be called * with the device lock held in the core, so be careful. */ @@ -189,6 +189,8 @@ extern int bus_unregister_notifier(struct bus_type *bus, unbound */ #define BUS_NOTIFY_UNBOUND_DRIVER 0x00000006 /* driver is unbound from the device */ +#define BUS_NOTIFY_DRVBIND_FAILED 0x00000007 /* driver failed to bind + to device */
extern struct kset *bus_get_kset(struct bus_type *bus); extern struct klist *bus_get_device_klist(struct bus_type *bus);
Hi Marek,
Thank you for the patch.
On Tuesday 05 August 2014 12:47:32 Marek Szyprowski wrote:
This patch adds support for getting a notify for failed device driver bind, so all the items done in BUS_NOTIFY_BIND_DRIVER event can be cleaned if the driver fails to bind.
Signed-off-by: Marek Szyprowski m.szyprowski@samsung.com
drivers/base/dd.c | 10 +++++++--- include/linux/device.h | 4 +++- 2 files changed, 10 insertions(+), 4 deletions(-)
diff --git a/drivers/base/dd.c b/drivers/base/dd.c index e4ffbcf..541a41f 100644 --- a/drivers/base/dd.c +++ b/drivers/base/dd.c @@ -237,10 +237,14 @@ static int driver_sysfs_add(struct device *dev) return ret; }
-static void driver_sysfs_remove(struct device *dev) +static void driver_sysfs_remove(struct device *dev, int failed) { struct device_driver *drv = dev->driver;
- if (failed && dev->bus)
blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
BUS_NOTIFY_DRVBIND_FAILED, dev);
This might be a stupid question, but as you only call driver_sysfs_remove with failed set to true in a single location (in the failure path of really_probe), how about moving the blocking_notifier_call_chain to that location ? The code seems to be a bit out of place here.
- if (drv) { sysfs_remove_link(&drv->p->kobj, kobject_name(&dev->kobj)); sysfs_remove_link(&dev->kobj, "driver");
@@ -316,7 +320,7 @@ static int really_probe(struct device *dev, struct device_driver *drv)
probe_failed: devres_release_all(dev);
- driver_sysfs_remove(dev);
- driver_sysfs_remove(dev, true); dev->driver = NULL; dev_set_drvdata(dev, NULL);
@@ -509,7 +513,7 @@ static void __device_release_driver(struct device *dev) if (drv) { pm_runtime_get_sync(dev);
driver_sysfs_remove(dev);
driver_sysfs_remove(dev, false);
if (dev->bus) blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
diff --git a/include/linux/device.h b/include/linux/device.h index b387710..92daded 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -176,7 +176,7 @@ extern int bus_register_notifier(struct bus_type *bus, extern int bus_unregister_notifier(struct bus_type *bus, struct notifier_block *nb);
-/* All 4 notifers below get called with the target struct device * +/* All 7 notifers below get called with the target struct device *
- as an argument. Note that those functions are likely to be called
- with the device lock held in the core, so be careful.
*/ @@ -189,6 +189,8 @@ extern int bus_unregister_notifier(struct bus_type *bus, unbound */ #define BUS_NOTIFY_UNBOUND_DRIVER 0x00000006 /* driver is unbound from the device */ +#define BUS_NOTIFY_DRVBIND_FAILED 0x00000007 /* driver failed to bind
to device */
extern struct kset *bus_get_kset(struct bus_type *bus); extern struct klist *bus_get_device_klist(struct bus_type *bus);
On Tue, Aug 05, 2014 at 12:47:32PM +0200, Marek Szyprowski wrote:
This patch adds support for getting a notify for failed device driver bind, so all the items done in BUS_NOTIFY_BIND_DRIVER event can be cleaned if the driver fails to bind.
But doesn't the bus know if the driver fails to bind, so why would a notifier be needed here? Can't it unwind any problems then?
Signed-off-by: Marek Szyprowski m.szyprowski@samsung.com
drivers/base/dd.c | 10 +++++++--- include/linux/device.h | 4 +++- 2 files changed, 10 insertions(+), 4 deletions(-)
diff --git a/drivers/base/dd.c b/drivers/base/dd.c index e4ffbcf..541a41f 100644 --- a/drivers/base/dd.c +++ b/drivers/base/dd.c @@ -237,10 +237,14 @@ static int driver_sysfs_add(struct device *dev) return ret; } -static void driver_sysfs_remove(struct device *dev) +static void driver_sysfs_remove(struct device *dev, int failed)
I _hate_ having functions with a flag that does something different depending on it. If you _really_ need this, just make the notifier call before calling this function, which should work just fine, right?
thanks,
greg k-h
Hello,
On 2014-08-25 22:05, Greg Kroah-Hartman wrote:
On Tue, Aug 05, 2014 at 12:47:32PM +0200, Marek Szyprowski wrote:
This patch adds support for getting a notify for failed device driver bind, so all the items done in BUS_NOTIFY_BIND_DRIVER event can be cleaned if the driver fails to bind.
But doesn't the bus know if the driver fails to bind, so why would a notifier be needed here? Can't it unwind any problems then?
Some other subsystems (like IOMMU) might register its own notifiers on the given bus. Such notifier is called before driver probe (BUS_NOTIFY_BIND_DRIVER event), so one can allocate some resources there. However there is no way to do the cleanup if the driver fails to bind, because no notifier is called in such case.
Signed-off-by: Marek Szyprowski m.szyprowski@samsung.com
drivers/base/dd.c | 10 +++++++--- include/linux/device.h | 4 +++- 2 files changed, 10 insertions(+), 4 deletions(-)
diff --git a/drivers/base/dd.c b/drivers/base/dd.c index e4ffbcf..541a41f 100644 --- a/drivers/base/dd.c +++ b/drivers/base/dd.c @@ -237,10 +237,14 @@ static int driver_sysfs_add(struct device *dev) return ret; } -static void driver_sysfs_remove(struct device *dev) +static void driver_sysfs_remove(struct device *dev, int failed)
I _hate_ having functions with a flag that does something different depending on it. If you _really_ need this, just make the notifier call before calling this function, which should work just fine, right?
Ok, I will fix this. If I remember correctly I followed the driver_sysfs_add() function pattern, which (surprisingly, especially when one considers only the function name) also calls the bus notifiers.
Best regards
On Tue, Aug 05, 2014 at 12:47:32PM +0200, Marek Szyprowski wrote:
- if (failed && dev->bus)
blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
BUS_NOTIFY_DRVBIND_FAILED, dev);
Why can't you just use the notifier for BUS_NOTIFY_UNBIND_DRIVER or BUS_NOTIFY_UNBOUND_DRIVER when something goes wrong in driver initialization?
Joerg
Hello,
On 2014-08-25 23:18, Joerg Roedel wrote:
On Tue, Aug 05, 2014 at 12:47:32PM +0200, Marek Szyprowski wrote:
- if (failed && dev->bus)
blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
BUS_NOTIFY_DRVBIND_FAILED, dev);
Why can't you just use the notifier for BUS_NOTIFY_UNBIND_DRIVER or BUS_NOTIFY_UNBOUND_DRIVER when something goes wrong in driver initialization?
Hmmm, you might be right. BUS_NOTIFY_UNBIND_DRIVER event happens before unbinding the driver, but BUS_NOTIFY_UNBOUND_DRIVER is called when driver remove has been finished, so it can be considered as a symmetrical pair for BUS_NOTIFY_BIND_DRIVER. Driver which registered bus notifiers is mainly interested in doing right cleanup, so the code executed for IOMMU_GROUP_NOTIFY_UNBOUND_DRIVER and IOMMU_GROUP_NOTIFY_DRVBIND_FAILED is same.
I will remove this additional event and simply add a call to BUS_NOTIFY_UNBOUND_DRIVER event when driver probe fails.
Best regards
This patch extends struct device_driver with a flags member and converts existing suppress_bind_attrs bool field to a flag. This way new flags can be easily added in the future without changing the structure itself.
Signed-off-by: Marek Szyprowski m.szyprowski@samsung.com --- arch/arm/mach-integrator/impd1.c | 2 +- drivers/base/bus.c | 4 ++-- drivers/base/platform.c | 2 +- drivers/pci/host/pci-mvebu.c | 2 +- drivers/pci/host/pci-rcar-gen2.c | 2 +- drivers/pci/host/pci-tegra.c | 2 +- drivers/pci/host/pcie-rcar.c | 2 +- drivers/soc/tegra/pmc.c | 2 +- include/linux/device.h | 6 ++++-- 9 files changed, 13 insertions(+), 11 deletions(-)
diff --git a/arch/arm/mach-integrator/impd1.c b/arch/arm/mach-integrator/impd1.c index 3ce8807..a7e7330 100644 --- a/arch/arm/mach-integrator/impd1.c +++ b/arch/arm/mach-integrator/impd1.c @@ -406,7 +406,7 @@ static struct lm_driver impd1_driver = { * As we're dropping the probe() function, suppress driver * binding from sysfs. */ - .suppress_bind_attrs = true, + .flags = DRIVER_SUPPRESS_BIND_ATTRS, }, .probe = impd1_probe, .remove = impd1_remove, diff --git a/drivers/base/bus.c b/drivers/base/bus.c index 83e910a..f223f26 100644 --- a/drivers/base/bus.c +++ b/drivers/base/bus.c @@ -707,7 +707,7 @@ int bus_add_driver(struct device_driver *drv) __func__, drv->name); }
- if (!drv->suppress_bind_attrs) { + if (!(drv->flags & DRIVER_SUPPRESS_BIND_ATTRS)) { error = add_bind_files(drv); if (error) { /* Ditto */ @@ -740,7 +740,7 @@ void bus_remove_driver(struct device_driver *drv) if (!drv->bus) return;
- if (!drv->suppress_bind_attrs) + if (!(drv->flags & DRIVER_SUPPRESS_BIND_ATTRS)) remove_bind_files(drv); driver_remove_groups(drv, drv->bus->drv_groups); driver_remove_file(drv, &driver_attr_uevent); diff --git a/drivers/base/platform.c b/drivers/base/platform.c index 68a8b77..c696058 100644 --- a/drivers/base/platform.c +++ b/drivers/base/platform.c @@ -608,7 +608,7 @@ int __init_or_module platform_driver_probe(struct platform_driver *drv, drv->prevent_deferred_probe = true;
/* make sure driver won't have bind/unbind attributes */ - drv->driver.suppress_bind_attrs = true; + drv->driver.flags = DRIVER_SUPPRESS_BIND_ATTRS;
/* temporary section violation during probe() */ drv->probe = probe; diff --git a/drivers/pci/host/pci-mvebu.c b/drivers/pci/host/pci-mvebu.c index a8c6f1a..6815c50 100644 --- a/drivers/pci/host/pci-mvebu.c +++ b/drivers/pci/host/pci-mvebu.c @@ -1086,7 +1086,7 @@ static struct platform_driver mvebu_pcie_driver = { .name = "mvebu-pcie", .of_match_table = mvebu_pcie_of_match_table, /* driver unloading/unbinding currently not supported */ - .suppress_bind_attrs = true, + .flags = DRIVER_SUPPRESS_BIND_ATTRS, }, .probe = mvebu_pcie_probe, }; diff --git a/drivers/pci/host/pci-rcar-gen2.c b/drivers/pci/host/pci-rcar-gen2.c index 3ef854f..6f1b890 100644 --- a/drivers/pci/host/pci-rcar-gen2.c +++ b/drivers/pci/host/pci-rcar-gen2.c @@ -413,7 +413,7 @@ static struct platform_driver rcar_pci_driver = { .driver = { .name = "pci-rcar-gen2", .owner = THIS_MODULE, - .suppress_bind_attrs = true, + .flags = DRIVER_SUPPRESS_BIND_ATTRS, .of_match_table = rcar_pci_of_match, }, .probe = rcar_pci_probe, diff --git a/drivers/pci/host/pci-tegra.c b/drivers/pci/host/pci-tegra.c index 0fb0fdb..2e1698d 100644 --- a/drivers/pci/host/pci-tegra.c +++ b/drivers/pci/host/pci-tegra.c @@ -1927,7 +1927,7 @@ static struct platform_driver tegra_pcie_driver = { .name = "tegra-pcie", .owner = THIS_MODULE, .of_match_table = tegra_pcie_of_match, - .suppress_bind_attrs = true, + .flags = DRIVER_SUPPRESS_BIND_ATTRS, }, .probe = tegra_pcie_probe, }; diff --git a/drivers/pci/host/pcie-rcar.c b/drivers/pci/host/pcie-rcar.c index 4884ee5..9a1936e 100644 --- a/drivers/pci/host/pcie-rcar.c +++ b/drivers/pci/host/pcie-rcar.c @@ -981,7 +981,7 @@ static struct platform_driver rcar_pcie_driver = { .name = DRV_NAME, .owner = THIS_MODULE, .of_match_table = rcar_pcie_of_match, - .suppress_bind_attrs = true, + .flags = DRIVER_SUPPRESS_BIND_ATTRS, }, .probe = rcar_pcie_probe, }; diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c index a2c0ceb..77c3eb3 100644 --- a/drivers/soc/tegra/pmc.c +++ b/drivers/soc/tegra/pmc.c @@ -892,7 +892,7 @@ static const struct of_device_id tegra_pmc_match[] = { static struct platform_driver tegra_pmc_driver = { .driver = { .name = "tegra-pmc", - .suppress_bind_attrs = true, + .flags = DRIVER_SUPPRESS_BIND_ATTRS, .of_match_table = tegra_pmc_match, .pm = &tegra_pmc_pm_ops, }, diff --git a/include/linux/device.h b/include/linux/device.h index 92daded..5f4ff02 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -201,7 +201,7 @@ extern struct klist *bus_get_device_klist(struct bus_type *bus); * @bus: The bus which the device of this driver belongs to. * @owner: The module owner. * @mod_name: Used for built-in modules. - * @suppress_bind_attrs: Disables bind/unbind via sysfs. + * @flags: Flags defining driver behaviour, see below. * @of_match_table: The open firmware table. * @acpi_match_table: The ACPI match table. * @probe: Called to query the existence of a specific device, @@ -234,7 +234,7 @@ struct device_driver { struct module *owner; const char *mod_name; /* used for built-in modules */
- bool suppress_bind_attrs; /* disables bind/unbind via sysfs */ + unsigned long flags;
const struct of_device_id *of_match_table; const struct acpi_device_id *acpi_match_table; @@ -251,6 +251,8 @@ struct device_driver { struct driver_private *p; };
+/* disables bind/unbind via sysfs */ +#define DRIVER_SUPPRESS_BIND_ATTRS (1 << 0)
extern int __must_check driver_register(struct device_driver *drv); extern void driver_unregister(struct device_driver *drv);
This patch adds support for forwarding recently introduced BUS_NOTIFY_DRVBIND_FAILED event to iommu groups. This lets us getting a notify for failed device driver bind, so all the items done in IOMMU_GROUP_NOTIFY_BIND_DRIVER event can be cleaned if the driver fails to bind.
Signed-off-by: Marek Szyprowski m.szyprowski@samsung.com --- drivers/iommu/iommu.c | 3 +++ include/linux/iommu.h | 1 + 2 files changed, 4 insertions(+)
diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c index 1698360..516e93a 100644 --- a/drivers/iommu/iommu.c +++ b/drivers/iommu/iommu.c @@ -758,6 +758,9 @@ static int iommu_bus_notifier(struct notifier_block *nb, case BUS_NOTIFY_UNBOUND_DRIVER: group_action = IOMMU_GROUP_NOTIFY_UNBOUND_DRIVER; break; + case BUS_NOTIFY_DRVBIND_FAILED: + group_action = IOMMU_GROUP_NOTIFY_DRVBIND_FAILED; + break; }
if (group_action) diff --git a/include/linux/iommu.h b/include/linux/iommu.h index 20f9a52..f9fdae5 100644 --- a/include/linux/iommu.h +++ b/include/linux/iommu.h @@ -139,6 +139,7 @@ struct iommu_ops { #define IOMMU_GROUP_NOTIFY_BOUND_DRIVER 4 /* Post Driver bind */ #define IOMMU_GROUP_NOTIFY_UNBIND_DRIVER 5 /* Pre Driver unbind */ #define IOMMU_GROUP_NOTIFY_UNBOUND_DRIVER 6 /* Post Driver unbind */ +#define IOMMU_GROUP_NOTIFY_DRVBIND_FAILED 7 /* Driver failed to bind */
extern int bus_set_iommu(struct bus_type *bus, const struct iommu_ops *ops); extern bool iommu_present(struct bus_type *bus);
If device has no max_seg_size set, we assume that there is no limit and force it to DMA_BIT_MASK(32) to always use contiguous mappings in DMA address space.
Signed-off-by: Marek Szyprowski m.szyprowski@samsung.com --- arch/arm/mm/dma-mapping.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+)
diff --git a/arch/arm/mm/dma-mapping.c b/arch/arm/mm/dma-mapping.c index 7a996aa..8161102 100644 --- a/arch/arm/mm/dma-mapping.c +++ b/arch/arm/mm/dma-mapping.c @@ -2051,6 +2051,22 @@ int arm_iommu_attach_device(struct device *dev, { int err;
+ /* + * if device has no max_seg_size set, we assume that there is no limit + * and force it to DMA_BIT_MASK(32) to always use contiguous mappings + * in DMA address space + */ + if (!dev->dma_parms) { + dev->dma_parms = kzalloc(sizeof(*dev->dma_parms), GFP_KERNEL); + if (!dev->dma_parms) + return -ENOMEM; + } + if (!dev->dma_parms->max_segment_size) { + err = dma_set_max_seg_size(dev, DMA_BIT_MASK(32)); + if (err) + return err; + } + err = iommu_attach_device(mapping->domain, dev); if (err) return err;
This patch adds 2 helpers: arm_iommu_create_default_mapping and arm_iommu_release_default_mapping for managing default iommu-based dma-mapping address space, created for exlusive use only by the given device. Those helpers are convenient for setting up iommu-based dma-mapping for most typical devices in the system.
Signed-off-by: Marek Szyprowski m.szyprowski@samsung.com --- arch/arm/include/asm/dma-iommu.h | 5 +++++ arch/arm/mm/dma-mapping.c | 31 +++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+)
diff --git a/arch/arm/include/asm/dma-iommu.h b/arch/arm/include/asm/dma-iommu.h index 8e3fcb9..ae3dac0 100644 --- a/arch/arm/include/asm/dma-iommu.h +++ b/arch/arm/include/asm/dma-iommu.h @@ -33,5 +33,10 @@ int arm_iommu_attach_device(struct device *dev, struct dma_iommu_mapping *mapping); void arm_iommu_detach_device(struct device *dev);
+int arm_iommu_create_default_mapping(struct device *dev, dma_addr_t base, + size_t size); + +void arm_iommu_release_default_mapping(struct device *dev); + #endif /* __KERNEL__ */ #endif diff --git a/arch/arm/mm/dma-mapping.c b/arch/arm/mm/dma-mapping.c index 8161102..233a8cf 100644 --- a/arch/arm/mm/dma-mapping.c +++ b/arch/arm/mm/dma-mapping.c @@ -2106,4 +2106,35 @@ void arm_iommu_detach_device(struct device *dev) } EXPORT_SYMBOL_GPL(arm_iommu_detach_device);
+int arm_iommu_create_default_mapping(struct device *dev, dma_addr_t base, + size_t size) +{ + struct dma_iommu_mapping *mapping; + int ret; + + mapping = arm_iommu_create_mapping(dev->bus, base, size); + if (IS_ERR(mapping)) + return PTR_ERR(mapping); + + ret = arm_iommu_attach_device(dev, mapping); + if (ret) { + arm_iommu_release_mapping(mapping); + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(arm_iommu_create_default_mapping); + +void arm_iommu_release_default_mapping(struct device *dev) +{ + struct dma_iommu_mapping *mapping = to_dma_iommu_mapping(dev); + if (!mapping) + return; + + arm_iommu_detach_device(dev); + arm_iommu_release_mapping(mapping); +} +EXPORT_SYMBOL_GPL(arm_iommu_release_default_mapping); + #endif
This patch provides stubs returing errors for all iommu related arm dma-mapping functions, which are used when CONFIG_ARM_DMA_USE_IOMMU is not set. This let drivers to use common code for iommu and non-iommu cases without additional ifdefs.
Signed-off-by: Marek Szyprowski m.szyprowski@samsung.com --- arch/arm/include/asm/dma-iommu.h | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+)
diff --git a/arch/arm/include/asm/dma-iommu.h b/arch/arm/include/asm/dma-iommu.h index ae3dac0..1e57569 100644 --- a/arch/arm/include/asm/dma-iommu.h +++ b/arch/arm/include/asm/dma-iommu.h @@ -9,6 +9,8 @@ #include <linux/kmemcheck.h> #include <linux/kref.h>
+#ifdef CONFIG_ARM_DMA_USE_IOMMU + struct dma_iommu_mapping { /* iommu specific data */ struct iommu_domain *domain; @@ -38,5 +40,34 @@ int arm_iommu_create_default_mapping(struct device *dev, dma_addr_t base,
void arm_iommu_release_default_mapping(struct device *dev);
+#else + +static inline struct dma_iommu_mapping * +arm_iommu_create_mapping(struct bus_type *bus, dma_addr_t base, size_t size) +{ + return ERR_PTR(-ENOSYS); +} + +static inline void +arm_iommu_release_mapping(struct dma_iommu_mapping *mapping) { } + +static inline int arm_iommu_attach_device(struct device *dev, + struct dma_iommu_mapping *mapping) +{ + return -ENOSYS; +} + +static inline void arm_iommu_detach_device(struct device *dev) { } + +static inline int arm_iommu_create_default_mapping(struct device *dev, + dma_addr_t base, size_t size) +{ + return -ENOSYS; +} + +static inline void arm_iommu_release_default_mapping(struct device *dev) { } + +#endif + #endif /* __KERNEL__ */ #endif
This patch adds a new flags for device drivers. This flag instructs kernel that the device driver does it own management of IOMMU assisted IO address space translations, so no default dma-mapping structures should be initialized.
Signed-off-by: Marek Szyprowski m.szyprowski@samsung.com --- include/linux/device.h | 2 ++ 1 file changed, 2 insertions(+)
diff --git a/include/linux/device.h b/include/linux/device.h index 5f4ff02..2e62371 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -253,6 +253,8 @@ struct device_driver {
/* disables bind/unbind via sysfs */ #define DRIVER_SUPPRESS_BIND_ATTRS (1 << 0) +/* driver uses own methods to manage IO address space */ +#define DRIVER_HAS_OWN_IOMMU_MANAGER (1 << 1)
extern int __must_check driver_register(struct device_driver *drv); extern void driver_unregister(struct device_driver *drv);
Hi Greg,
On 2014-08-05 12:47, Marek Szyprowski wrote:
This patch adds a new flags for device drivers. This flag instructs kernel that the device driver does it own management of IOMMU assisted IO address space translations, so no default dma-mapping structures should be initialized.
Signed-off-by: Marek Szyprowski m.szyprowski@samsung.com
include/linux/device.h | 2 ++ 1 file changed, 2 insertions(+)
diff --git a/include/linux/device.h b/include/linux/device.h index 5f4ff02..2e62371 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -253,6 +253,8 @@ struct device_driver { /* disables bind/unbind via sysfs */ #define DRIVER_SUPPRESS_BIND_ATTRS (1 << 0) +/* driver uses own methods to manage IO address space */ +#define DRIVER_HAS_OWN_IOMMU_MANAGER (1 << 1) extern int __must_check driver_register(struct device_driver *drv); extern void driver_unregister(struct device_driver *drv);
Could you comment if the approach of using flags in the struct driver could be accepted? I've converted suppress_bind_attrs entry to flags to avoid extending the structure, please see patch "[PATCH 05/29] drivers: convert suppress_bind_attrs parameter into flags".
Best regards
On Mon, Sep 01, 2014 at 07:22:32AM +0200, Marek Szyprowski wrote:
Hi Greg,
On 2014-08-05 12:47, Marek Szyprowski wrote:
This patch adds a new flags for device drivers. This flag instructs kernel that the device driver does it own management of IOMMU assisted IO address space translations, so no default dma-mapping structures should be initialized.
Signed-off-by: Marek Szyprowski m.szyprowski@samsung.com
include/linux/device.h | 2 ++ 1 file changed, 2 insertions(+)
diff --git a/include/linux/device.h b/include/linux/device.h index 5f4ff02..2e62371 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -253,6 +253,8 @@ struct device_driver { /* disables bind/unbind via sysfs */ #define DRIVER_SUPPRESS_BIND_ATTRS (1 << 0) +/* driver uses own methods to manage IO address space */ +#define DRIVER_HAS_OWN_IOMMU_MANAGER (1 << 1) extern int __must_check driver_register(struct device_driver *drv); extern void driver_unregister(struct device_driver *drv);
Could you comment if the approach of using flags in the struct driver could be accepted? I've converted suppress_bind_attrs entry to flags to avoid extending the structure, please see patch "[PATCH 05/29] drivers: convert suppress_bind_attrs parameter into flags".
Is this really necessary? What I did as part of an RFC series for Tegra IOMMU support is keep this knowledge within the IOMMU driver rather than export it to the driver core.
The idea being that the IOMMU driver wouldn't create an ARM DMA/IOMMU mapping by default but rather allow individual drivers to be marked as "kernel-internal" and use the DMA/IOMMU glue in that case. Drivers such as DRM would use the IOMMU API directly.
Thierry
Hello,
On 2014-09-01 09:07, Thierry Reding wrote:
On Mon, Sep 01, 2014 at 07:22:32AM +0200, Marek Szyprowski wrote:
Hi Greg,
On 2014-08-05 12:47, Marek Szyprowski wrote:
This patch adds a new flags for device drivers. This flag instructs kernel that the device driver does it own management of IOMMU assisted IO address space translations, so no default dma-mapping structures should be initialized.
Signed-off-by: Marek Szyprowski m.szyprowski@samsung.com
include/linux/device.h | 2 ++ 1 file changed, 2 insertions(+)
diff --git a/include/linux/device.h b/include/linux/device.h index 5f4ff02..2e62371 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -253,6 +253,8 @@ struct device_driver { /* disables bind/unbind via sysfs */ #define DRIVER_SUPPRESS_BIND_ATTRS (1 << 0) +/* driver uses own methods to manage IO address space */ +#define DRIVER_HAS_OWN_IOMMU_MANAGER (1 << 1) extern int __must_check driver_register(struct device_driver *drv); extern void driver_unregister(struct device_driver *drv);
Could you comment if the approach of using flags in the struct driver could be accepted? I've converted suppress_bind_attrs entry to flags to avoid extending the structure, please see patch "[PATCH 05/29] drivers: convert suppress_bind_attrs parameter into flags".
Is this really necessary? What I did as part of an RFC series for Tegra IOMMU support is keep this knowledge within the IOMMU driver rather than export it to the driver core.i
The problem with embedding the list of drivers that you would need to update it everytime when you modify or extend iommu support in the other drivers. I've tried also other approach, like adding respective notifiers to individual drivers to initialize custom iommu support (or disable default iommu mapping) before iommu driver gets initialized, but such solution is in my opinion too complex and hard to understand if one is not familiar will all this stuff.
All in all it turned out that the simplest and most generic way is to simply add the flag to the driver core. Flags might be also used in the future to model other kinds of dependencies between device drivers and/or driver core.
The idea being that the IOMMU driver wouldn't create an ARM DMA/IOMMU mapping by default but rather allow individual drivers to be marked as "kernel-internal" and use the DMA/IOMMU glue in that case. Drivers such as DRM would use the IOMMU API directly.
Best regards
On Monday 01 September 2014 09:53:29 Marek Szyprowski wrote:
On 2014-09-01 09:07, Thierry Reding wrote:
On Mon, Sep 01, 2014 at 07:22:32AM +0200, Marek Szyprowski wrote:
Hi Greg,
On 2014-08-05 12:47, Marek Szyprowski wrote:
This patch adds a new flags for device drivers. This flag instructs kernel that the device driver does it own management of IOMMU assisted IO address space translations, so no default dma-mapping structures should be initialized.
Signed-off-by: Marek Szyprowski m.szyprowski@samsung.com
include/linux/device.h | 2 ++ 1 file changed, 2 insertions(+)
diff --git a/include/linux/device.h b/include/linux/device.h index 5f4ff02..2e62371 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -253,6 +253,8 @@ struct device_driver { /* disables bind/unbind via sysfs */ #define DRIVER_SUPPRESS_BIND_ATTRS (1 << 0) +/* driver uses own methods to manage IO address space */ +#define DRIVER_HAS_OWN_IOMMU_MANAGER (1 << 1) extern int __must_check driver_register(struct device_driver *drv); extern void driver_unregister(struct device_driver *drv);
Could you comment if the approach of using flags in the struct driver could be accepted? I've converted suppress_bind_attrs entry to flags to avoid extending the structure, please see patch "[PATCH 05/29] drivers: convert suppress_bind_attrs parameter into flags".
Is this really necessary? What I did as part of an RFC series for Tegra IOMMU support is keep this knowledge within the IOMMU driver rather than export it to the driver core.i
The problem with embedding the list of drivers that you would need to update it everytime when you modify or extend iommu support in the other drivers. I've tried also other approach, like adding respective notifiers to individual drivers to initialize custom iommu support (or disable default iommu mapping) before iommu driver gets initialized, but such solution is in my opinion too complex and hard to understand if one is not familiar will all this stuff.
All in all it turned out that the simplest and most generic way is to simply add the flag to the driver core. Flags might be also used in the future to model other kinds of dependencies between device drivers and/or driver core.
I don't get it yet. I would expect that a driver doing its own management of the iommu gets to use the linux/iommu.h interfaces, while a driver using the default iommu setup uses linux/dma-mapping.h. Who do you think needs to set this flag, and who needs to read it?
Arnd
Hello,
On 2014-09-01 11:38, Arnd Bergmann wrote:
On Monday 01 September 2014 09:53:29 Marek Szyprowski wrote:
On 2014-09-01 09:07, Thierry Reding wrote:
On Mon, Sep 01, 2014 at 07:22:32AM +0200, Marek Szyprowski wrote:
Hi Greg,
On 2014-08-05 12:47, Marek Szyprowski wrote:
This patch adds a new flags for device drivers. This flag instructs kernel that the device driver does it own management of IOMMU assisted IO address space translations, so no default dma-mapping structures should be initialized.
Signed-off-by: Marek Szyprowski m.szyprowski@samsung.com
include/linux/device.h | 2 ++ 1 file changed, 2 insertions(+)
diff --git a/include/linux/device.h b/include/linux/device.h index 5f4ff02..2e62371 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -253,6 +253,8 @@ struct device_driver { /* disables bind/unbind via sysfs */ #define DRIVER_SUPPRESS_BIND_ATTRS (1 << 0) +/* driver uses own methods to manage IO address space */ +#define DRIVER_HAS_OWN_IOMMU_MANAGER (1 << 1) extern int __must_check driver_register(struct device_driver *drv); extern void driver_unregister(struct device_driver *drv);
Could you comment if the approach of using flags in the struct driver could be accepted? I've converted suppress_bind_attrs entry to flags to avoid extending the structure, please see patch "[PATCH 05/29] drivers: convert suppress_bind_attrs parameter into flags".
Is this really necessary? What I did as part of an RFC series for Tegra IOMMU support is keep this knowledge within the IOMMU driver rather than export it to the driver core.i
The problem with embedding the list of drivers that you would need to update it everytime when you modify or extend iommu support in the other drivers. I've tried also other approach, like adding respective notifiers to individual drivers to initialize custom iommu support (or disable default iommu mapping) before iommu driver gets initialized, but such solution is in my opinion too complex and hard to understand if one is not familiar will all this stuff.
All in all it turned out that the simplest and most generic way is to simply add the flag to the driver core. Flags might be also used in the future to model other kinds of dependencies between device drivers and/or driver core.
I don't get it yet. I would expect that a driver doing its own management of the iommu gets to use the linux/iommu.h interfaces, while a driver using the default iommu setup uses linux/dma-mapping.h.
You are right.
Who do you think needs to set this flag, and who needs to read it?
In the proposed solution Exynos IOMMU driver creates a separate IO address space for every client device in a system and binds it to the default dma-mapping space for the given device. When drivers are doing its own management of IO address space, instead of relying on what is available by default with dma-mapping interface, this will require releasing of the previously created default structures and resources. So this flag is set by the driver doing its own management of io address space. The flags is then checked by Exynos IOMMU driver to avoid creating the default dma-mapping address space for devices which driver does its own management.
Best regards
On Monday 01 September 2014 12:47:08 Marek Szyprowski wrote:
Who do you think needs to set this flag, and who needs to read it?
In the proposed solution Exynos IOMMU driver creates a separate IO address space for every client device in a system and binds it to the default dma-mapping space for the given device. When drivers are doing its own management of IO address space, instead of relying on what is available by default with dma-mapping interface, this will require releasing of the previously created default structures and resources. So this flag is set by the driver doing its own management of io address space. The flags is then checked by Exynos IOMMU driver to avoid creating the default dma-mapping address space for devices which driver does its own management.
I don't completely understand it yet. I would assume the device to be added to the default domain at device creation time (of_platform_populate), way before we know which device driver is going to be used. How can this prevent the iommu driver from doing the association with the domain?
Arnd
Hello,
On 2014-09-01 13:56, Arnd Bergmann wrote:
On Monday 01 September 2014 12:47:08 Marek Szyprowski wrote:
Who do you think needs to set this flag, and who needs to read it?
In the proposed solution Exynos IOMMU driver creates a separate IO address space for every client device in a system and binds it to the default dma-mapping space for the given device. When drivers are doing its own management of IO address space, instead of relying on what is available by default with dma-mapping interface, this will require releasing of the previously created default structures and resources. So this flag is set by the driver doing its own management of io address space. The flags is then checked by Exynos IOMMU driver to avoid creating the default dma-mapping address space for devices which driver does its own management.
I don't completely understand it yet. I would assume the device to be added to the default domain at device creation time (of_platform_populate), way before we know which device driver is going to be used. How can this prevent the iommu driver from doing the association with the domain?
of_platform_populate() is too early to do the association, because that time the exynos iommu driver is even not yet probed.
The association with default dma-mapping domain is done in IOMMU_GROUP_NOTIFY_BIND_DRIVER notifier, just before binding the driver to the given device. This way iommu driver can check dev->driver->flags and skip creating default dma-mapping domain if driver announces that it wants to handle it by itself.
Best regards
On Monday 01 September 2014 14:07:34 Marek Szyprowski wrote:
On 2014-09-01 13:56, Arnd Bergmann wrote:
On Monday 01 September 2014 12:47:08 Marek Szyprowski wrote:
Who do you think needs to set this flag, and who needs to read it?
In the proposed solution Exynos IOMMU driver creates a separate IO address space for every client device in a system and binds it to the default dma-mapping space for the given device. When drivers are doing its own management of IO address space, instead of relying on what is available by default with dma-mapping interface, this will require releasing of the previously created default structures and resources. So this flag is set by the driver doing its own management of io address space. The flags is then checked by Exynos IOMMU driver to avoid creating the default dma-mapping address space for devices which driver does its own management.
I don't completely understand it yet. I would assume the device to be added to the default domain at device creation time (of_platform_populate), way before we know which device driver is going to be used. How can this prevent the iommu driver from doing the association with the domain?
of_platform_populate() is too early to do the association, because that time the exynos iommu driver is even not yet probed.
Please have a look at the "Introduce automatic DMA configuration for IOMMU masters" series that Will Deacon sent out the other day. The idea is that we are changing the probe order so that the iommu gets initialized early enough for all IOMMU association to be done there.
The association with default dma-mapping domain is done in IOMMU_GROUP_NOTIFY_BIND_DRIVER notifier, just before binding the driver to the given device. This way iommu driver can check dev->driver->flags and skip creating default dma-mapping domain if driver announces that it wants to handle it by itself.
I really want to kill off those notifiers.
Arnd
Exynos DRM drivers do their own management of IO address space of all controlled devices, so set DRIVER_HAS_OWN_IOMMU_MANAGER flag to instruct IOMMU subsystem not to create any defaults for them.
Signed-off-by: Marek Szyprowski m.szyprowski@samsung.com --- drivers/gpu/drm/exynos/exynos_drm_fimc.c | 1 + drivers/gpu/drm/exynos/exynos_drm_fimd.c | 1 + drivers/gpu/drm/exynos/exynos_drm_g2d.c | 1 + drivers/gpu/drm/exynos/exynos_drm_gsc.c | 1 + drivers/gpu/drm/exynos/exynos_drm_rotator.c | 1 + drivers/gpu/drm/exynos/exynos_mixer.c | 1 + 6 files changed, 6 insertions(+)
diff --git a/drivers/gpu/drm/exynos/exynos_drm_fimc.c b/drivers/gpu/drm/exynos/exynos_drm_fimc.c index 831dde9..fe84215 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_fimc.c +++ b/drivers/gpu/drm/exynos/exynos_drm_fimc.c @@ -1896,6 +1896,7 @@ struct platform_driver fimc_driver = { .name = "exynos-drm-fimc", .owner = THIS_MODULE, .pm = &fimc_pm_ops, + .flags = DRIVER_HAS_OWN_IOMMU_MANAGER, }, };
diff --git a/drivers/gpu/drm/exynos/exynos_drm_fimd.c b/drivers/gpu/drm/exynos/exynos_drm_fimd.c index 33161ad..41904df 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_fimd.c +++ b/drivers/gpu/drm/exynos/exynos_drm_fimd.c @@ -1031,5 +1031,6 @@ struct platform_driver fimd_driver = { .name = "exynos4-fb", .owner = THIS_MODULE, .of_match_table = fimd_driver_dt_match, + .flags = DRIVER_HAS_OWN_IOMMU_MANAGER, }, }; diff --git a/drivers/gpu/drm/exynos/exynos_drm_g2d.c b/drivers/gpu/drm/exynos/exynos_drm_g2d.c index 8001587..dddeae3 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_g2d.c +++ b/drivers/gpu/drm/exynos/exynos_drm_g2d.c @@ -1555,5 +1555,6 @@ struct platform_driver g2d_driver = { .owner = THIS_MODULE, .pm = &g2d_pm_ops, .of_match_table = exynos_g2d_match, + .flags = DRIVER_HAS_OWN_IOMMU_MANAGER, }, }; diff --git a/drivers/gpu/drm/exynos/exynos_drm_gsc.c b/drivers/gpu/drm/exynos/exynos_drm_gsc.c index 9e3ff16..76e8b1e 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_gsc.c +++ b/drivers/gpu/drm/exynos/exynos_drm_gsc.c @@ -1797,6 +1797,7 @@ struct platform_driver gsc_driver = { .name = "exynos-drm-gsc", .owner = THIS_MODULE, .pm = &gsc_pm_ops, + .flags = DRIVER_HAS_OWN_IOMMU_MANAGER, }, };
diff --git a/drivers/gpu/drm/exynos/exynos_drm_rotator.c b/drivers/gpu/drm/exynos/exynos_drm_rotator.c index f01fbb6..2da91c4 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_rotator.c +++ b/drivers/gpu/drm/exynos/exynos_drm_rotator.c @@ -852,5 +852,6 @@ struct platform_driver rotator_driver = { .owner = THIS_MODULE, .pm = &rotator_pm_ops, .of_match_table = exynos_rotator_match, + .flags = DRIVER_HAS_OWN_IOMMU_MANAGER, }, }; diff --git a/drivers/gpu/drm/exynos/exynos_mixer.c b/drivers/gpu/drm/exynos/exynos_mixer.c index 7529946..79b1309a 100644 --- a/drivers/gpu/drm/exynos/exynos_mixer.c +++ b/drivers/gpu/drm/exynos/exynos_mixer.c @@ -1320,6 +1320,7 @@ struct platform_driver mixer_driver = { .name = "exynos-mixer", .owner = THIS_MODULE, .of_match_table = mixer_match_types, + .flags = DRIVER_HAS_OWN_IOMMU_MANAGER, }, .probe = mixer_probe, .remove = mixer_remove,
To correctly disable hardware window during driver init, both enable bits (WINCONx_ENWIN in WINCON and SHADOWCON_CHx_ENABLE in SHADOWCON) must be cleared, otherwise hardware fails to re-enable such window later.
While touching this function, also temporarily disable ctx->suspended flag to let fimd_wait_for_vblank function really to do its job.
Signed-off-by: Marek Szyprowski m.szyprowski@samsung.com --- drivers/gpu/drm/exynos/exynos_drm_fimd.c | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-)
diff --git a/drivers/gpu/drm/exynos/exynos_drm_fimd.c b/drivers/gpu/drm/exynos/exynos_drm_fimd.c index 41904df..7a363d2 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_fimd.c +++ b/drivers/gpu/drm/exynos/exynos_drm_fimd.c @@ -165,27 +165,38 @@ static void fimd_wait_for_vblank(struct exynos_drm_manager *mgr) DRM_DEBUG_KMS("vblank wait timed out.\n"); }
- static void fimd_clear_channel(struct exynos_drm_manager *mgr) { struct fimd_context *ctx = mgr->ctx; - int win, ch_enabled = 0; + int state, win, ch_enabled = 0;
DRM_DEBUG_KMS("%s\n", __FILE__);
/* Check if any channel is enabled. */ for (win = 0; win < WINDOWS_NR; win++) { - u32 val = readl(ctx->regs + SHADOWCON); - if (val & SHADOWCON_CHx_ENABLE(win)) { - val &= ~SHADOWCON_CHx_ENABLE(win); - writel(val, ctx->regs + SHADOWCON); + u32 val = readl(ctx->regs + WINCON(win)); + if (val & WINCONx_ENWIN) { + /* wincon */ + val &= ~WINCONx_ENWIN; + writel(val, ctx->regs + WINCON(win)); + + /* unprotect windows */ + if (ctx->driver_data->has_shadowcon) { + val = readl(ctx->regs + SHADOWCON); + val &= ~SHADOWCON_CHx_ENABLE(win); + writel(val, ctx->regs + SHADOWCON); + } ch_enabled = 1; } }
/* Wait for vsync, as disable channel takes effect at next vsync */ - if (ch_enabled) + if (ch_enabled) { + state = ctx->suspended; + ctx->suspended = 0; fimd_wait_for_vblank(mgr); + ctx->suspended = state; + } }
static int fimd_mgr_initialize(struct exynos_drm_manager *mgr,
Hi Marek,
On 2014년 08월 05일 19:47, Marek Szyprowski wrote:
To correctly disable hardware window during driver init, both enable bits (WINCONx_ENWIN in WINCON and SHADOWCON_CHx_ENABLE in SHADOWCON) must be cleared, otherwise hardware fails to re-enable such window later.
While touching this function, also temporarily disable ctx->suspended flag to let fimd_wait_for_vblank function really to do its job.
Signed-off-by: Marek Szyprowski m.szyprowski@samsung.com
drivers/gpu/drm/exynos/exynos_drm_fimd.c | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-)
diff --git a/drivers/gpu/drm/exynos/exynos_drm_fimd.c b/drivers/gpu/drm/exynos/exynos_drm_fimd.c index 41904df..7a363d2 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_fimd.c +++ b/drivers/gpu/drm/exynos/exynos_drm_fimd.c @@ -165,27 +165,38 @@ static void fimd_wait_for_vblank(struct exynos_drm_manager *mgr) DRM_DEBUG_KMS("vblank wait timed out.\n"); }
static void fimd_clear_channel(struct exynos_drm_manager *mgr) { struct fimd_context *ctx = mgr->ctx;
- int win, ch_enabled = 0;
- int state, win, ch_enabled = 0;
It doesn't need to declare state variable here because this variable is used only when ch_enabled is 1.
DRM_DEBUG_KMS("%s\n", __FILE__); /* Check if any channel is enabled. */ for (win = 0; win < WINDOWS_NR; win++) {
u32 val = readl(ctx->regs + SHADOWCON);
if (val & SHADOWCON_CHx_ENABLE(win)) {
val &= ~SHADOWCON_CHx_ENABLE(win);
writel(val, ctx->regs + SHADOWCON);
u32 val = readl(ctx->regs + WINCON(win));
WARNING: Missing a blank line after declarations
if (val & WINCONx_ENWIN) {
/* wincon */
val &= ~WINCONx_ENWIN;
writel(val, ctx->regs + WINCON(win));
/* unprotect windows */
if (ctx->driver_data->has_shadowcon) {
val = readl(ctx->regs + SHADOWCON);
val &= ~SHADOWCON_CHx_ENABLE(win);
writel(val, ctx->regs + SHADOWCON);
} }} ch_enabled = 1;
/* Wait for vsync, as disable channel takes effect at next vsync */
- if (ch_enabled)
- if (ch_enabled) {
state = ctx->suspended;
int state = ctx->suspended;
fimd_wait_for_vblank(mgr);ctx->suspended = 0;
ctx->suspended = state;
- }
} static int fimd_mgr_initialize(struct exynos_drm_manager *mgr,
Above is trivial so I fixed them. Picked it up.
Thanks, Inki Dae
This patch removes custom initialization of reserved memory regions from s5p-mfc driver. Driver will use main device pointer for all memory allocations.
This patch is temporary, do not merge it yet.
Signed-off-by: Marek Szyprowski m.szyprowski@samsung.com --- drivers/media/platform/s5p-mfc/s5p_mfc.c | 73 +------------------------------- 1 file changed, 2 insertions(+), 71 deletions(-)
diff --git a/drivers/media/platform/s5p-mfc/s5p_mfc.c b/drivers/media/platform/s5p-mfc/s5p_mfc.c index d35b041..77b99ae 100644 --- a/drivers/media/platform/s5p-mfc/s5p_mfc.c +++ b/drivers/media/platform/s5p-mfc/s5p_mfc.c @@ -996,55 +996,8 @@ static const struct v4l2_file_operations s5p_mfc_fops = { .mmap = s5p_mfc_mmap, };
-static int match_child(struct device *dev, void *data) -{ - if (!dev_name(dev)) - return 0; - return !strcmp(dev_name(dev), (char *)data); -} - static void *mfc_get_drv_data(struct platform_device *pdev);
-static int s5p_mfc_alloc_memdevs(struct s5p_mfc_dev *dev) -{ - unsigned int mem_info[2] = { }; - - dev->mem_dev_l = devm_kzalloc(&dev->plat_dev->dev, - sizeof(struct device), GFP_KERNEL); - if (!dev->mem_dev_l) { - mfc_err("Not enough memory\n"); - return -ENOMEM; - } - device_initialize(dev->mem_dev_l); - of_property_read_u32_array(dev->plat_dev->dev.of_node, - "samsung,mfc-l", mem_info, 2); - if (dma_declare_coherent_memory(dev->mem_dev_l, mem_info[0], - mem_info[0], mem_info[1], - DMA_MEMORY_MAP | DMA_MEMORY_EXCLUSIVE) == 0) { - mfc_err("Failed to declare coherent memory for\n" - "MFC device\n"); - return -ENOMEM; - } - - dev->mem_dev_r = devm_kzalloc(&dev->plat_dev->dev, - sizeof(struct device), GFP_KERNEL); - if (!dev->mem_dev_r) { - mfc_err("Not enough memory\n"); - return -ENOMEM; - } - device_initialize(dev->mem_dev_r); - of_property_read_u32_array(dev->plat_dev->dev.of_node, - "samsung,mfc-r", mem_info, 2); - if (dma_declare_coherent_memory(dev->mem_dev_r, mem_info[0], - mem_info[0], mem_info[1], - DMA_MEMORY_MAP | DMA_MEMORY_EXCLUSIVE) == 0) { - pr_err("Failed to declare coherent memory for\n" - "MFC device\n"); - return -ENOMEM; - } - return 0; -} - /* MFC probe function */ static int s5p_mfc_probe(struct platform_device *pdev) { @@ -1096,26 +1049,8 @@ static int s5p_mfc_probe(struct platform_device *pdev) goto err_res; }
- if (pdev->dev.of_node) { - ret = s5p_mfc_alloc_memdevs(dev); - if (ret < 0) - goto err_res; - } else { - dev->mem_dev_l = device_find_child(&dev->plat_dev->dev, - "s5p-mfc-l", match_child); - if (!dev->mem_dev_l) { - mfc_err("Mem child (L) device get failed\n"); - ret = -ENODEV; - goto err_res; - } - dev->mem_dev_r = device_find_child(&dev->plat_dev->dev, - "s5p-mfc-r", match_child); - if (!dev->mem_dev_r) { - mfc_err("Mem child (R) device get failed\n"); - ret = -ENODEV; - goto err_res; - } - } + dev->mem_dev_l = &dev->plat_dev->dev; + dev->mem_dev_r = &dev->plat_dev->dev;
dev->alloc_ctx[0] = vb2_dma_contig_init_ctx(dev->mem_dev_l); if (IS_ERR(dev->alloc_ctx[0])) { @@ -1246,10 +1181,6 @@ static int s5p_mfc_remove(struct platform_device *pdev) s5p_mfc_release_firmware(dev); vb2_dma_contig_cleanup_ctx(dev->alloc_ctx[0]); vb2_dma_contig_cleanup_ctx(dev->alloc_ctx[1]); - if (pdev->dev.of_node) { - put_device(dev->mem_dev_l); - put_device(dev->mem_dev_r); - }
s5p_mfc_final_pm(dev); return 0;
This patch describes how generic iommu bindings are implemented by Exynos SYSMMU driver.
Signed-off-by: Marek Szyprowski m.szyprowski@samsung.com --- .../devicetree/bindings/iommu/samsung,sysmmu.txt | 93 +++++++++++++++++++--- 1 file changed, 84 insertions(+), 9 deletions(-)
diff --git a/Documentation/devicetree/bindings/iommu/samsung,sysmmu.txt b/Documentation/devicetree/bindings/iommu/samsung,sysmmu.txt index 6fa4c73..999ba6d 100644 --- a/Documentation/devicetree/bindings/iommu/samsung,sysmmu.txt +++ b/Documentation/devicetree/bindings/iommu/samsung,sysmmu.txt @@ -23,16 +23,33 @@ MMUs. for window 1, 2 and 3. * M2M Scalers and G2D in Exynos5420 has one System MMU on the read channel and the other System MMU on the write channel. -The drivers must consider how to handle those System MMUs. One of the idea is -to implement child devices or sub-devices which are the client devices of the -System MMU.
-Note: -The current DT binding for the Exynos System MMU is incomplete. -The following properties can be removed or changed, if found incompatible with -the "Generic IOMMU Binding" support for attaching devices to the IOMMU. +The drivers must consider how to handle those System MMUs. When device +have more than one SYSMMU controller it is neccessary to add +"iommu-names" property, which specifies which SYSMMU controller operates +on which bus or memory channel.
-Required properties: +It is up to the master device driver to decide how such case will be +handled. It is possible to create separate IO address spaces for each +SYSMMU or to bind them together to one common IO address space. It is +also possible to bind more than one device to one IO address space. All +this has to be handled by master device driver in its initialization +procedure or flags and no changes to device tree nodes are needed. + +In Linux kernel, the general idea is that presence of the SYSMMU +controllers is transparent to master drivers if they use standard DMA +API. When driver wants to use IO separate address spaces for each bus or +memory channel (each SYSMMU) or to bind more than one device to one IO +address space, it has to specify this to SYSMMU driver by +DRIVER_HAS_OWN_IOMMU_MANAGER flag. To get access to each SYSMMU bound to +the given device, additional child devices with special names (matching +"parent:bus" scheme) have to be registered. Once then, all standard +IOMMU operations can be performed on such child devices, what will +result in respective operations done on IO address space managed by +SYSMMU of the given name. Other operating systems might implement those +features differently. + +Required properties for SYSMMU controller node: - compatible: Should be "samsung,exynos-sysmmu" - reg: A tuple of base address and size of System MMU registers. - interrupt-parent: The phandle of the interrupt controller of System MMU @@ -45,11 +62,27 @@ Required properties: Exynos4 SoCs, there needs no "master" clock. Exynos5 SoCs, some System MMUs must have "master" clocks. - clocks: Required if the System MMU is needed to gate its clock. +- #iommu-cells: Specify number of cells describing IO address space parameters, + can be: 0 (zero), meaning all 32bit address space is available, + or 2, if address space is limited, first cell then stores + base IO address, second cell contains IO window size in bytes. - samsung,power-domain: Required if the System MMU is needed to gate its power. Please refer to the following document: Documentation/devicetree/bindings/arm/exynos/power_domain.txt
-Examples: +Required properties for master device: +- iommus: one or more phandles to the SYSMMU controller node, with optionally + specified IO address space (see #iommu-cells property above) +- iommu-names: if more than one SYSMMU controller is specified, this property + must contain names for each of them. Those names are defined by + the bindings for a particular master device. + +For more information, please refer to generic iommu bindings defined in +iommu.txt file. + +Example 1: +GScaller device with one SYSMMU controller + gsc_0: gsc@13e00000 { compatible = "samsung,exynos5-gsc"; reg = <0x13e00000 0x1000>; @@ -57,6 +90,7 @@ Examples: samsung,power-domain = <&pd_gsc>; clocks = <&clock CLK_GSCL0>; clock-names = "gscl"; + iommus = <&sysmmu_gsc0>; };
sysmmu_gsc0: sysmmu@13E80000 { @@ -67,4 +101,45 @@ Examples: clock-names = "sysmmu", "master"; clocks = <&clock CLK_SMMU_GSCL0>, <&clock CLK_GSCL0>; samsung,power-domain = <&pd_gsc>; + #iommu-cells = <0>; + }; + +Example 2: +MFC Codec with two SYSMMU controllers (on "left" and "right" bus), with address +space limited to 256MiB each, left bus starts IO address space at 0x20000000, +while right bus at 0x30000000 + + mfc: codec@13400000 { + compatible = "samsung,mfc-v5"; + reg = <0x13400000 0x10000>; + interrupts = <0 94 0>; + samsung,power-domain = <&pd_mfc>; + clocks = <&clock CLK_MFC>; + clock-names = "mfc"; + status = "disabled"; + iommus = <&sysmmu_mfc_l 0x20000000 0x10000000>, + <&sysmmu_mfc_r 0x30000000 0x10000000>; + iommu-names = "left", "right"; + }; + + sysmmu_mfc_l: sysmmu@13620000 { + compatible = "samsung,exynos-sysmmu"; + reg = <0x13620000 0x1000>; + interrupt-parent = <&combiner>; + interrupts = <5 5>; + clock-names = "sysmmu", "master"; + clocks = <&clock CLK_SMMU_MFCL>, <&clock CLK_MFC>; + samsung,power-domain = <&pd_mfc>; + #iommu-cells = <2>; + }; + + sysmmu_mfc_r: sysmmu@13630000 { + compatible = "samsung,exynos-sysmmu"; + reg = <0x13630000 0x1000>; + interrupt-parent = <&combiner>; + interrupts = <5 6>; + clock-names = "sysmmu", "master"; + clocks = <&clock CLK_SMMU_MFCR>, <&clock CLK_MFC>; + samsung,power-domain = <&pd_mfc>; + #iommu-cells = <2>; };
This patch adds System MMU nodes that are specific to Exynos4210/4x12 series.
Signed-off-by: Marek Szyprowski m.szyprowski@samsung.com --- arch/arm/boot/dts/exynos4.dtsi | 118 ++++++++++++++++++++++++++++++++++++++ arch/arm/boot/dts/exynos4210.dtsi | 23 ++++++++ arch/arm/boot/dts/exynos4x12.dtsi | 82 ++++++++++++++++++++++++++ 3 files changed, 223 insertions(+)
diff --git a/arch/arm/boot/dts/exynos4.dtsi b/arch/arm/boot/dts/exynos4.dtsi index 3385b17..a76b4e5 100644 --- a/arch/arm/boot/dts/exynos4.dtsi +++ b/arch/arm/boot/dts/exynos4.dtsi @@ -174,6 +174,7 @@ clock-names = "fimc", "sclk_fimc"; samsung,power-domain = <&pd_cam>; samsung,sysreg = <&sys_reg>; + iommus = <&sysmmu_fimc0>; status = "disabled"; };
@@ -185,6 +186,7 @@ clock-names = "fimc", "sclk_fimc"; samsung,power-domain = <&pd_cam>; samsung,sysreg = <&sys_reg>; + iommus = <&sysmmu_fimc1>; status = "disabled"; };
@@ -196,6 +198,7 @@ clock-names = "fimc", "sclk_fimc"; samsung,power-domain = <&pd_cam>; samsung,sysreg = <&sys_reg>; + iommus = <&sysmmu_fimc2>; status = "disabled"; };
@@ -207,6 +210,7 @@ clock-names = "fimc", "sclk_fimc"; samsung,power-domain = <&pd_cam>; samsung,sysreg = <&sys_reg>; + iommus = <&sysmmu_fimc3>; status = "disabled"; };
@@ -395,6 +399,9 @@ clocks = <&clock CLK_MFC>; clock-names = "mfc"; status = "disabled"; + iommus = <&sysmmu_mfc_l 0x20000000 0x10000000>, + <&sysmmu_mfc_r 0x30000000 0x10000000>; + iommu-names = "left", "right"; };
serial_0: serial@13800000 { @@ -642,6 +649,117 @@ clocks = <&clock CLK_SCLK_FIMD0>, <&clock CLK_FIMD0>; clock-names = "sclk_fimd", "fimd"; samsung,power-domain = <&pd_lcd0>; + iommus = <&sysmmu_fimd0>; status = "disabled"; }; + + sysmmu_mfc_l: sysmmu@13620000 { + compatible = "samsung,exynos-sysmmu"; + reg = <0x13620000 0x1000>; + interrupt-parent = <&combiner>; + interrupts = <5 5>; + clock-names = "sysmmu", "master"; + clocks = <&clock CLK_SMMU_MFCL>, <&clock CLK_MFC>; + samsung,power-domain = <&pd_mfc>; + #iommu-cells = <2>; + }; + + sysmmu_mfc_r: sysmmu@13630000 { + compatible = "samsung,exynos-sysmmu"; + reg = <0x13630000 0x1000>; + interrupt-parent = <&combiner>; + interrupts = <5 6>; + clock-names = "sysmmu", "master"; + clocks = <&clock CLK_SMMU_MFCR>, <&clock CLK_MFC>; + samsung,power-domain = <&pd_mfc>; + #iommu-cells = <2>; + }; + + sysmmu_tv: sysmmu@12E20000 { + compatible = "samsung,exynos-sysmmu"; + reg = <0x12E20000 0x1000>; + interrupt-parent = <&combiner>; + interrupts = <5 4>; + clock-names = "sysmmu", "master"; + clocks = <&clock CLK_SMMU_TV>, <&clock CLK_MIXER>; + samsung,power-domain = <&pd_tv>; + #iommu-cells = <0>; + }; + + sysmmu_fimc0: sysmmu@11A20000 { + compatible = "samsung,exynos-sysmmu"; + reg = <0x11A20000 0x1000>; + interrupt-parent = <&combiner>; + interrupts = <4 2>; + clock-names = "sysmmu", "master"; + clocks = <&clock CLK_SMMU_FIMC0>, <&clock CLK_FIMC0>; + samsung,power-domain = <&pd_cam>; + #iommu-cells = <0>; + }; + + sysmmu_fimc1: sysmmu@11A30000 { + compatible = "samsung,exynos-sysmmu"; + reg = <0x11A30000 0x1000>; + interrupt-parent = <&combiner>; + interrupts = <4 3>; + clock-names = "sysmmu", "master"; + clocks = <&clock CLK_SMMU_FIMC1>, <&clock CLK_FIMC1>; + samsung,power-domain = <&pd_cam>; + #iommu-cells = <0>; + }; + + sysmmu_fimc2: sysmmu@11A40000 { + compatible = "samsung,exynos-sysmmu"; + reg = <0x11A40000 0x1000>; + interrupt-parent = <&combiner>; + interrupts = <4 4>; + clock-names = "sysmmu", "master"; + clocks = <&clock CLK_SMMU_FIMC2>, <&clock CLK_FIMC2>; + samsung,power-domain = <&pd_cam>; + #iommu-cells = <0>; + }; + + sysmmu_fimc3: sysmmu@11A50000 { + compatible = "samsung,exynos-sysmmu"; + reg = <0x11A50000 0x1000>; + interrupt-parent = <&combiner>; + interrupts = <4 5>; + clock-names = "sysmmu", "master"; + clocks = <&clock CLK_SMMU_FIMC3>, <&clock CLK_FIMC3>; + samsung,power-domain = <&pd_cam>; + #iommu-cells = <0>; + }; + + sysmmu_jpeg: sysmmu@11A60000 { + compatible = "samsung,exynos-sysmmu"; + reg = <0x11A60000 0x1000>; + interrupt-parent = <&combiner>; + interrupts = <4 6>; + clock-names = "sysmmu", "master"; + clocks = <&clock CLK_SMMU_JPEG>, <&clock CLK_JPEG>; + samsung,power-domain = <&pd_cam>; + #iommu-cells = <0>; + }; + + sysmmu_rotator: sysmmu@12A30000 { + compatible = "samsung,exynos-sysmmu"; + reg = <0x12A30000 0x1000>; + interrupt-parent = <&combiner>; + interrupts = <5 0>; + clock-names = "sysmmu", "master"; + clocks = <&clock CLK_SMMU_ROTATOR>, <&clock CLK_ROTATOR>; + samsung,power-domain = <&pd_lcd0>; + #iommu-cells = <0>; + }; + + sysmmu_fimd0: sysmmu@11E20000 { + compatible = "samsung,exynos-sysmmu"; + reg = <0x11E20000 0x1000>; + interrupt-parent = <&combiner>; + interrupts = <5 2>; + clock-names = "sysmmu", "master"; + clocks = <&clock CLK_SMMU_FIMD0>, <&clock CLK_FIMD0>; + samsung,power-domain = <&pd_lcd0>; + #iommu-cells = <0>; + }; }; diff --git a/arch/arm/boot/dts/exynos4210.dtsi b/arch/arm/boot/dts/exynos4210.dtsi index 807bb5b..9ae48c2 100644 --- a/arch/arm/boot/dts/exynos4210.dtsi +++ b/arch/arm/boot/dts/exynos4210.dtsi @@ -142,6 +142,7 @@ interrupts = <0 89 0>; clocks = <&clock CLK_SCLK_FIMG2D>, <&clock CLK_G2D>; clock-names = "sclk_fimg2d", "fimg2d"; + iommus = <&sysmmu_g2d>; status = "disabled"; };
@@ -175,4 +176,26 @@ samsung,lcd-wb; }; }; + + sysmmu_g2d: sysmmu@12A20000 { + compatible = "samsung,exynos-sysmmu"; + reg = <0x12A20000 0x1000>; + interrupt-parent = <&combiner>; + interrupts = <4 7>; + clock-names = "sysmmu", "master"; + clocks = <&clock CLK_SMMU_G2D>, <&clock CLK_G2D>; + samsung,power-domain = <&pd_lcd0>; + #iommu-cells = <0>; + }; + + sysmmu_fimd1: sysmmu@12220000 { + compatible = "samsung,exynos-sysmmu"; + interrupt-parent = <&combiner>; + reg = <0x12220000 0x1000>; + interrupts = <5 3>; + clock-names = "sysmmu", "master"; + clocks = <&clock CLK_SMMU_FIMD1>, <&clock CLK_FIMD1>; + samsung,power-domain = <&pd_lcd1>; + #iommu-cells = <0>; + }; }; diff --git a/arch/arm/boot/dts/exynos4x12.dtsi b/arch/arm/boot/dts/exynos4x12.dtsi index 861bb91..cc97e18 100644 --- a/arch/arm/boot/dts/exynos4x12.dtsi +++ b/arch/arm/boot/dts/exynos4x12.dtsi @@ -148,6 +148,7 @@ interrupts = <0 89 0>; clocks = <&clock CLK_SCLK_FIMG2D>, <&clock CLK_G2D>; clock-names = "sclk_fimg2d", "fimg2d"; + iommus = <&sysmmu_g2d>; status = "disabled"; };
@@ -197,6 +198,7 @@ samsung,power-domain = <&pd_isp>; clocks = <&clock CLK_FIMC_LITE0>; clock-names = "flite"; + iommus = <&sysmmu_fimc_lite0>; status = "disabled"; };
@@ -207,6 +209,7 @@ samsung,power-domain = <&pd_isp>; clocks = <&clock CLK_FIMC_LITE1>; clock-names = "flite"; + iommus = <&sysmmu_fimc_lite1>; status = "disabled"; };
@@ -235,6 +238,9 @@ "mcuispdiv1", "uart", "aclk200", "div_aclk200", "aclk400mcuisp", "div_aclk400mcuisp"; + iommus = <&sysmmu_fimc_isp>, <&sysmmu_fimc_drc>, + <&sysmmu_fimc_fd>, <&sysmmu_fimc_mcuctl>; + iommu-names = "isp", "drc", "fd", "mcuctl"; #address-cells = <1>; #size-cells = <1>; ranges; @@ -271,4 +277,80 @@ compatible = "samsung,exynos4x12-usb2-phy"; samsung,sysreg-phandle = <&sys_reg>; }; + + sysmmu_g2d: sysmmu@10A40000{ + compatible = "samsung,exynos-sysmmu"; + reg = <0x10A40000 0x1000>; + interrupt-parent = <&combiner>; + interrupts = <4 7>; + clock-names = "sysmmu", "master"; + clocks = <&clock CLK_SMMU_G2D>, <&clock CLK_G2D>; + #iommu-cells = <0>; + }; + + sysmmu_fimc_isp: sysmmu@12260000 { + compatible = "samsung,exynos-sysmmu"; + reg = <0x12260000 0x1000>; + interrupt-parent = <&combiner>; + interrupts = <16 2>; + samsung,power-domain = <&pd_isp>; + clock-names = "sysmmu"; + clocks = <&clock CLK_SMMU_ISP>; + #iommu-cells = <0>; + }; + + sysmmu_fimc_drc: sysmmu@12270000 { + compatible = "samsung,exynos-sysmmu"; + reg = <0x12270000 0x1000>; + interrupt-parent = <&combiner>; + interrupts = <16 3>; + samsung,power-domain = <&pd_isp>; + clock-names = "sysmmu"; + clocks = <&clock CLK_SMMU_DRC>; + #iommu-cells = <0>; + }; + + sysmmu_fimc_fd: sysmmu@122A0000 { + compatible = "samsung,exynos-sysmmu"; + reg = <0x122A0000 0x1000>; + interrupt-parent = <&combiner>; + interrupts = <16 4>; + samsung,power-domain = <&pd_isp>; + clock-names = "sysmmu"; + clocks = <&clock CLK_SMMU_FD>; + #iommu-cells = <0>; + }; + + sysmmu_fimc_mcuctl: sysmmu@122B0000 { + compatible = "samsung,exynos-sysmmu"; + reg = <0x122B0000 0x1000>; + interrupt-parent = <&combiner>; + interrupts = <16 5>; + samsung,power-domain = <&pd_isp>; + clock-names = "sysmmu"; + clocks = <&clock CLK_SMMU_ISPCX>; + #iommu-cells = <0>; + }; + + sysmmu_fimc_lite0: sysmmu@123B0000 { + compatible = "samsung,exynos-sysmmu"; + reg = <0x123B0000 0x1000>; + interrupt-parent = <&combiner>; + interrupts = <16 0>; + samsung,power-domain = <&pd_isp>; + clock-names = "sysmmu", "master"; + clocks = <&clock CLK_SMMU_LITE0>, <&clock CLK_FIMC_LITE0>; + #iommu-cells = <0>; + }; + + sysmmu_fimc_lite1: sysmmu@123C0000 { + compatible = "samsung,exynos-sysmmu"; + reg = <0x123C0000 0x1000>; + interrupt-parent = <&combiner>; + interrupts = <16 1>; + samsung,power-domain = <&pd_isp>; + clock-names = "sysmmu", "master"; + clocks = <&clock CLK_SMMU_LITE1>, <&clock CLK_FIMC_LITE1>; + #iommu-cells = <0>; + }; };
Initialize all structures and register to iommu subsystem only on Exynos compatible platforms.
Signed-off-by: Marek Szyprowski m.szyprowski@samsung.com --- drivers/iommu/exynos-iommu.c | 6 ++++++ 1 file changed, 6 insertions(+)
diff --git a/drivers/iommu/exynos-iommu.c b/drivers/iommu/exynos-iommu.c index d037e87..64b3bc8 100644 --- a/drivers/iommu/exynos-iommu.c +++ b/drivers/iommu/exynos-iommu.c @@ -20,6 +20,7 @@ #include <linux/clk.h> #include <linux/err.h> #include <linux/mm.h> +#include <linux/of.h> #include <linux/iommu.h> #include <linux/errno.h> #include <linux/list.h> @@ -1187,6 +1188,11 @@ static int __init exynos_iommu_init(void) { int ret;
+ if (!of_machine_is_compatible("samsung,exynos3") && + !of_machine_is_compatible("samsung,exynos4") && + !of_machine_is_compatible("samsung,exynos5")) + return -ENODEV; + lv2table_kmem_cache = kmem_cache_create("exynos-iommu-lv2table", LV2TABLE_SIZE, LV2TABLE_SIZE, 0, NULL); if (!lv2table_kmem_cache) {
This patch removes reading of REG_MMU_VERSION register on every tlb operation and caches SYSMMU version in driver's internal data.
Signed-off-by: Marek Szyprowski m.szyprowski@samsung.com --- drivers/iommu/exynos-iommu.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-)
diff --git a/drivers/iommu/exynos-iommu.c b/drivers/iommu/exynos-iommu.c index 64b3bc8..8927923 100644 --- a/drivers/iommu/exynos-iommu.c +++ b/drivers/iommu/exynos-iommu.c @@ -213,6 +213,7 @@ struct sysmmu_drvdata { spinlock_t lock; struct iommu_domain *domain; phys_addr_t pgtable; + int version; };
static bool set_sysmmu_active(struct sysmmu_drvdata *data) @@ -239,11 +240,6 @@ static void sysmmu_unblock(void __iomem *sfrbase) __raw_writel(CTRL_ENABLE, sfrbase + REG_MMU_CTRL); }
-static unsigned int __raw_sysmmu_version(struct sysmmu_drvdata *data) -{ - return MMU_RAW_VER(__raw_readl(data->sfrbase + REG_MMU_VERSION)); -} - static bool sysmmu_block(void __iomem *sfrbase) { int i = 120; @@ -403,7 +399,7 @@ static void __sysmmu_init_config(struct sysmmu_drvdata *data) unsigned int cfg = CFG_LRU | CFG_QOS(15); unsigned int ver;
- ver = __raw_sysmmu_version(data); + ver = MMU_RAW_VER(__raw_readl(data->sfrbase + REG_MMU_VERSION)); if (MMU_MAJ_VER(ver) == 3) { if (MMU_MIN_VER(ver) >= 2) { cfg |= CFG_FLPDCACHE; @@ -417,6 +413,7 @@ static void __sysmmu_init_config(struct sysmmu_drvdata *data) }
__raw_writel(cfg, data->sfrbase + REG_MMU_CFG); + data->version = ver; }
static void __sysmmu_enable_nocount(struct sysmmu_drvdata *data) @@ -526,7 +523,7 @@ static bool exynos_sysmmu_disable(struct device *dev) static void __sysmmu_tlb_invalidate_flpdcache(struct sysmmu_drvdata *data, sysmmu_iova_t iova) { - if (__raw_sysmmu_version(data) == MAKE_MMU_VER(3, 3)) + if (data->version == MAKE_MMU_VER(3, 3)) __raw_writel(iova | 0x1, data->sfrbase + REG_MMU_FLUSH_ENTRY); }
@@ -575,7 +572,7 @@ static void sysmmu_tlb_invalidate_entry(struct device *dev, sysmmu_iova_t iova, * 1MB page can be cached in one of all sets. * 64KB page can be one of 16 consecutive sets. */ - if (MMU_MAJ_VER(__raw_sysmmu_version(data)) == 2) + if (MMU_MAJ_VER(data->version) == 2) num_inv = min_t(unsigned int, size / PAGE_SIZE, 64);
if (sysmmu_block(data->sfrbase)) {
This patch removes two unneeded functions, which are not a part of generic IOMMU API and were never used by any other driver.
Signed-off-by: Marek Szyprowski m.szyprowski@samsung.com --- drivers/iommu/exynos-iommu.c | 31 ------------------------------- 1 file changed, 31 deletions(-)
diff --git a/drivers/iommu/exynos-iommu.c b/drivers/iommu/exynos-iommu.c index 8927923..ec3c882 100644 --- a/drivers/iommu/exynos-iommu.c +++ b/drivers/iommu/exynos-iommu.c @@ -491,13 +491,6 @@ static int __exynos_sysmmu_enable(struct device *dev, phys_addr_t pgtable, return ret; }
-int exynos_sysmmu_enable(struct device *dev, phys_addr_t pgtable) -{ - BUG_ON(!memblock_is_memory(pgtable)); - - return __exynos_sysmmu_enable(dev, pgtable, NULL); -} - static bool exynos_sysmmu_disable(struct device *dev) { unsigned long flags; @@ -589,30 +582,6 @@ static void sysmmu_tlb_invalidate_entry(struct device *dev, sysmmu_iova_t iova, spin_unlock_irqrestore(&data->lock, flags); }
-void exynos_sysmmu_tlb_invalidate(struct device *dev) -{ - struct exynos_iommu_owner *owner = dev->archdata.iommu; - unsigned long flags; - struct sysmmu_drvdata *data; - - data = dev_get_drvdata(owner->sysmmu); - - spin_lock_irqsave(&data->lock, flags); - if (is_sysmmu_active(data)) { - if (!IS_ERR(data->clk_master)) - clk_enable(data->clk_master); - if (sysmmu_block(data->sfrbase)) { - __sysmmu_tlb_invalidate(data->sfrbase); - sysmmu_unblock(data->sfrbase); - } - if (!IS_ERR(data->clk_master)) - clk_disable(data->clk_master); - } else { - dev_dbg(dev, "disabled. Skipping TLB invalidation\n"); - } - spin_unlock_irqrestore(&data->lock, flags); -} - static int __init exynos_sysmmu_probe(struct platform_device *pdev) { int irq, ret;
This patch removes useless spinlocks and other unused members from struct exynos_iommu_owner. There is no point is protecting this structure by spinlock because content of this structure doesn't change and other structures have their own spinlocks.
Signed-off-by: Marek Szyprowski m.szyprowski@samsung.com --- drivers/iommu/exynos-iommu.c | 11 ----------- 1 file changed, 11 deletions(-)
diff --git a/drivers/iommu/exynos-iommu.c b/drivers/iommu/exynos-iommu.c index ec3c882..ed8c518 100644 --- a/drivers/iommu/exynos-iommu.c +++ b/drivers/iommu/exynos-iommu.c @@ -190,9 +190,6 @@ struct exynos_iommu_owner { struct list_head client; /* entry of exynos_iommu_domain.clients */ struct device *dev; struct device *sysmmu; - struct iommu_domain *domain; - void *vmm_data; /* IO virtual memory manager's data */ - spinlock_t lock; /* Lock to preserve consistency of System MMU */ };
struct exynos_iommu_domain { @@ -478,16 +475,12 @@ static int __exynos_sysmmu_enable(struct device *dev, phys_addr_t pgtable,
BUG_ON(!has_sysmmu(dev));
- spin_lock_irqsave(&owner->lock, flags); - data = dev_get_drvdata(owner->sysmmu);
ret = __sysmmu_enable(data, pgtable, domain); if (ret >= 0) data->master = dev;
- spin_unlock_irqrestore(&owner->lock, flags); - return ret; }
@@ -500,16 +493,12 @@ static bool exynos_sysmmu_disable(struct device *dev)
BUG_ON(!has_sysmmu(dev));
- spin_lock_irqsave(&owner->lock, flags); - data = dev_get_drvdata(owner->sysmmu);
disabled = __sysmmu_disable(data); if (disabled) data->master = NULL;
- spin_unlock_irqrestore(&owner->lock, flags); - return disabled; }
This patch simplifies the code by: - refactoring function parameters from struct device pointer to direct pointer to struct sysmmu drvdata - moving list_head enteries from struct exynos_iommu_owner directly to struct sysmmu_drvdata
Signed-off-by: Marek Szyprowski m.szyprowski@samsung.com --- drivers/iommu/exynos-iommu.c | 93 ++++++++++++++++++++++---------------------- 1 file changed, 46 insertions(+), 47 deletions(-)
diff --git a/drivers/iommu/exynos-iommu.c b/drivers/iommu/exynos-iommu.c index ed8c518..018a615 100644 --- a/drivers/iommu/exynos-iommu.c +++ b/drivers/iommu/exynos-iommu.c @@ -187,8 +187,6 @@ static char *sysmmu_fault_name[SYSMMU_FAULTS_NUM] = {
/* attached to dev.archdata.iommu of the master device */ struct exynos_iommu_owner { - struct list_head client; /* entry of exynos_iommu_domain.clients */ - struct device *dev; struct device *sysmmu; };
@@ -209,6 +207,7 @@ struct sysmmu_drvdata { int activations; spinlock_t lock; struct iommu_domain *domain; + struct list_head domain_node; phys_addr_t pgtable; int version; }; @@ -509,12 +508,10 @@ static void __sysmmu_tlb_invalidate_flpdcache(struct sysmmu_drvdata *data, __raw_writel(iova | 0x1, data->sfrbase + REG_MMU_FLUSH_ENTRY); }
-static void sysmmu_tlb_invalidate_flpdcache(struct device *dev, +static void sysmmu_tlb_invalidate_flpdcache(struct sysmmu_drvdata *data, sysmmu_iova_t iova) { unsigned long flags; - struct exynos_iommu_owner *owner = dev->archdata.iommu; - struct sysmmu_drvdata *data = dev_get_drvdata(owner->sysmmu);
if (!IS_ERR(data->clk_master)) clk_enable(data->clk_master); @@ -528,14 +525,10 @@ static void sysmmu_tlb_invalidate_flpdcache(struct device *dev, clk_disable(data->clk_master); }
-static void sysmmu_tlb_invalidate_entry(struct device *dev, sysmmu_iova_t iova, - size_t size) +static void sysmmu_tlb_invalidate_entry(struct sysmmu_drvdata *data, + sysmmu_iova_t iova, size_t size) { - struct exynos_iommu_owner *owner = dev->archdata.iommu; unsigned long flags; - struct sysmmu_drvdata *data; - - data = dev_get_drvdata(owner->sysmmu);
spin_lock_irqsave(&data->lock, flags); if (is_sysmmu_active(data)) { @@ -565,8 +558,8 @@ static void sysmmu_tlb_invalidate_entry(struct device *dev, sysmmu_iova_t iova, if (!IS_ERR(data->clk_master)) clk_disable(data->clk_master); } else { - dev_dbg(dev, "disabled. Skipping TLB invalidation @ %#x\n", - iova); + dev_dbg(data->master, + "disabled. Skipping TLB invalidation @ %#x\n", iova); } spin_unlock_irqrestore(&data->lock, flags); } @@ -705,7 +698,7 @@ err_pgtable: static void exynos_iommu_domain_destroy(struct iommu_domain *domain) { struct exynos_iommu_domain *priv = domain->priv; - struct exynos_iommu_owner *owner; + struct sysmmu_drvdata *data; unsigned long flags; int i;
@@ -713,14 +706,12 @@ static void exynos_iommu_domain_destroy(struct iommu_domain *domain)
spin_lock_irqsave(&priv->lock, flags);
- list_for_each_entry(owner, &priv->clients, client) { - while (!exynos_sysmmu_disable(owner->dev)) - ; /* until System MMU is actually disabled */ + list_for_each_entry(data, &priv->clients, domain_node) { + if (__sysmmu_disable(data)) + data->master = NULL; + list_del_init(&data->domain_node); }
- while (!list_empty(&priv->clients)) - list_del_init(priv->clients.next); - spin_unlock_irqrestore(&priv->lock, flags);
for (i = 0; i < NUM_LV1ENTRIES; i++) @@ -739,20 +730,26 @@ static int exynos_iommu_attach_device(struct iommu_domain *domain, { struct exynos_iommu_owner *owner = dev->archdata.iommu; struct exynos_iommu_domain *priv = domain->priv; + struct sysmmu_drvdata *data; phys_addr_t pagetable = virt_to_phys(priv->pgtable); unsigned long flags; - int ret; + int ret = -ENODEV;
- spin_lock_irqsave(&priv->lock, flags); + if (!has_sysmmu(dev)) + return -ENODEV;
- ret = __exynos_sysmmu_enable(dev, pagetable, domain); - if (ret == 0) { - list_add_tail(&owner->client, &priv->clients); - owner->domain = domain; + data = dev_get_drvdata(owner->sysmmu); + if (data) { + ret = __sysmmu_enable(data, pagetable, domain); + if (ret >= 0) { + data->master = dev; + + spin_lock_irqsave(&priv->lock, flags); + list_add_tail(&data->domain_node, &priv->clients); + spin_unlock_irqrestore(&priv->lock, flags); + } }
- spin_unlock_irqrestore(&priv->lock, flags); - if (ret < 0) { dev_err(dev, "%s: Failed to attach IOMMU with pgtable %pa\n", __func__, &pagetable); @@ -768,26 +765,29 @@ static int exynos_iommu_attach_device(struct iommu_domain *domain, static void exynos_iommu_detach_device(struct iommu_domain *domain, struct device *dev) { - struct exynos_iommu_owner *owner; struct exynos_iommu_domain *priv = domain->priv; phys_addr_t pagetable = virt_to_phys(priv->pgtable); + struct sysmmu_drvdata *data; unsigned long flags; + int found = 0;
- spin_lock_irqsave(&priv->lock, flags); + if (!has_sysmmu(dev)) + return;
- list_for_each_entry(owner, &priv->clients, client) { - if (owner == dev->archdata.iommu) { - if (exynos_sysmmu_disable(dev)) { - list_del_init(&owner->client); - owner->domain = NULL; + spin_lock_irqsave(&priv->lock, flags); + list_for_each_entry(data, &priv->clients, domain_node) { + if (data->master == dev) { + if (__sysmmu_disable(data)) { + data->master = NULL; + list_del_init(&data->domain_node); } + found = true; break; } } - spin_unlock_irqrestore(&priv->lock, flags);
- if (owner == dev->archdata.iommu) + if (found) dev_dbg(dev, "%s: Detached IOMMU with pgtable %pa\n", __func__, &pagetable); else @@ -834,12 +834,11 @@ static sysmmu_pte_t *alloc_lv2entry(struct exynos_iommu_domain *priv, * not currently mapped. */ if (need_flush_flpd_cache) { - struct exynos_iommu_owner *owner; + struct sysmmu_drvdata *data;
spin_lock(&priv->lock); - list_for_each_entry(owner, &priv->clients, client) - sysmmu_tlb_invalidate_flpdcache( - owner->dev, iova); + list_for_each_entry(data, &priv->clients, domain_node) + sysmmu_tlb_invalidate_flpdcache(data, iova); spin_unlock(&priv->lock); } } @@ -874,13 +873,13 @@ static int lv1set_section(struct exynos_iommu_domain *priv,
spin_lock(&priv->lock); if (lv1ent_page_zero(sent)) { - struct exynos_iommu_owner *owner; + struct sysmmu_drvdata *data; /* * Flushing FLPD cache in System MMU v3.3 that may cache a FLPD * entry by speculative prefetch of SLPD which has no mapping. */ - list_for_each_entry(owner, &priv->clients, client) - sysmmu_tlb_invalidate_flpdcache(owner->dev, iova); + list_for_each_entry(data, &priv->clients, domain_node) + sysmmu_tlb_invalidate_flpdcache(data, iova); } spin_unlock(&priv->lock);
@@ -985,13 +984,13 @@ static int exynos_iommu_map(struct iommu_domain *domain, unsigned long l_iova, static void exynos_iommu_tlb_invalidate_entry(struct exynos_iommu_domain *priv, sysmmu_iova_t iova, size_t size) { - struct exynos_iommu_owner *owner; + struct sysmmu_drvdata *data; unsigned long flags;
spin_lock_irqsave(&priv->lock, flags);
- list_for_each_entry(owner, &priv->clients, client) - sysmmu_tlb_invalidate_entry(owner->dev, iova, size); + list_for_each_entry(data, &priv->clients, domain_node) + sysmmu_tlb_invalidate_entry(data, iova, size);
spin_unlock_irqrestore(&priv->lock, flags); }
After refactoring functions to use pointer to struct sysmmu_drvdata directly, some functions became useless and thus never used, so remove them completely.
Signed-off-by: Marek Szyprowski m.szyprowski@samsung.com --- drivers/iommu/exynos-iommu.c | 43 ------------------------------------------- 1 file changed, 43 deletions(-)
diff --git a/drivers/iommu/exynos-iommu.c b/drivers/iommu/exynos-iommu.c index 018a615..674d1fb 100644 --- a/drivers/iommu/exynos-iommu.c +++ b/drivers/iommu/exynos-iommu.c @@ -458,49 +458,6 @@ static int __sysmmu_enable(struct sysmmu_drvdata *data, return ret; }
-/* __exynos_sysmmu_enable: Enables System MMU - * - * returns -error if an error occurred and System MMU is not enabled, - * 0 if the System MMU has been just enabled and 1 if System MMU was already - * enabled before. - */ -static int __exynos_sysmmu_enable(struct device *dev, phys_addr_t pgtable, - struct iommu_domain *domain) -{ - int ret = 0; - unsigned long flags; - struct exynos_iommu_owner *owner = dev->archdata.iommu; - struct sysmmu_drvdata *data; - - BUG_ON(!has_sysmmu(dev)); - - data = dev_get_drvdata(owner->sysmmu); - - ret = __sysmmu_enable(data, pgtable, domain); - if (ret >= 0) - data->master = dev; - - return ret; -} - -static bool exynos_sysmmu_disable(struct device *dev) -{ - unsigned long flags; - bool disabled = true; - struct exynos_iommu_owner *owner = dev->archdata.iommu; - struct sysmmu_drvdata *data; - - BUG_ON(!has_sysmmu(dev)); - - data = dev_get_drvdata(owner->sysmmu); - - disabled = __sysmmu_disable(data); - if (disabled) - data->master = NULL; - - return disabled; -} - static void __sysmmu_tlb_invalidate_flpdcache(struct sysmmu_drvdata *data, sysmmu_iova_t iova) {
This patch adds support for assigning more than one SYSMMU controller to the master device. This has been achieved simply by chaning the struct device pointer in struct exynos_iommu_owner into the list of struct sysmmu_drvdata of all controllers assigned to the given master device.
Signed-off-by: Marek Szyprowski m.szyprowski@samsung.com --- drivers/iommu/exynos-iommu.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-)
diff --git a/drivers/iommu/exynos-iommu.c b/drivers/iommu/exynos-iommu.c index 674d1fb..46e0edc 100644 --- a/drivers/iommu/exynos-iommu.c +++ b/drivers/iommu/exynos-iommu.c @@ -187,7 +187,7 @@ static char *sysmmu_fault_name[SYSMMU_FAULTS_NUM] = {
/* attached to dev.archdata.iommu of the master device */ struct exynos_iommu_owner { - struct device *sysmmu; + struct list_head clients; };
struct exynos_iommu_domain { @@ -208,6 +208,7 @@ struct sysmmu_drvdata { spinlock_t lock; struct iommu_domain *domain; struct list_head domain_node; + struct list_head owner_node; phys_addr_t pgtable; int version; }; @@ -695,8 +696,7 @@ static int exynos_iommu_attach_device(struct iommu_domain *domain, if (!has_sysmmu(dev)) return -ENODEV;
- data = dev_get_drvdata(owner->sysmmu); - if (data) { + list_for_each_entry(data, &owner->clients, owner_node) { ret = __sysmmu_enable(data, pagetable, domain); if (ret >= 0) { data->master = dev; @@ -724,7 +724,7 @@ static void exynos_iommu_detach_device(struct iommu_domain *domain, { struct exynos_iommu_domain *priv = domain->priv; phys_addr_t pagetable = virt_to_phys(priv->pgtable); - struct sysmmu_drvdata *data; + struct sysmmu_drvdata *data, *next; unsigned long flags; int found = 0;
@@ -732,14 +732,13 @@ static void exynos_iommu_detach_device(struct iommu_domain *domain, return;
spin_lock_irqsave(&priv->lock, flags); - list_for_each_entry(data, &priv->clients, domain_node) { + list_for_each_entry_safe(data, next, &priv->clients, domain_node) { if (data->master == dev) { if (__sysmmu_disable(data)) { data->master = NULL; list_del_init(&data->domain_node); } found = true; - break; } } spin_unlock_irqrestore(&priv->lock, flags);
This patch adds code to initialize and assign SYSMMU controllers to their master devices defined in device tree.
Signed-off-by: Marek Szyprowski m.szyprowski@samsung.com --- drivers/iommu/exynos-iommu.c | 152 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 145 insertions(+), 7 deletions(-)
diff --git a/drivers/iommu/exynos-iommu.c b/drivers/iommu/exynos-iommu.c index 46e0edc..845f547 100644 --- a/drivers/iommu/exynos-iommu.c +++ b/drivers/iommu/exynos-iommu.c @@ -21,6 +21,7 @@ #include <linux/err.h> #include <linux/mm.h> #include <linux/of.h> +#include <linux/of_platform.h> #include <linux/iommu.h> #include <linux/errno.h> #include <linux/list.h> @@ -211,6 +212,9 @@ struct sysmmu_drvdata { struct list_head owner_node; phys_addr_t pgtable; int version; + const char *name; + dma_addr_t base; + size_t size; };
static bool set_sysmmu_active(struct sysmmu_drvdata *data) @@ -574,6 +578,11 @@ static int __init exynos_sysmmu_probe(struct platform_device *pdev) }
data->sysmmu = dev; + + /* default io address space parameters */ + data->base = SZ_1G; + data->size = SZ_2G; + spin_lock_init(&data->lock);
platform_set_drvdata(pdev, data); @@ -1055,30 +1064,159 @@ static phys_addr_t exynos_iommu_iova_to_phys(struct iommu_domain *domain, return phys; }
+static void __free_owner_struct(struct exynos_iommu_owner *owner, + struct device *dev) +{ + while (!list_empty(&owner->clients)) + list_del_init(owner->clients.next); + + kfree(owner); + dev->archdata.iommu = NULL; +} + +static int __init_master_sysmmu(struct device *dev) +{ + struct of_phandle_args sysmmu_spec; + struct exynos_iommu_owner *owner; + int i = 0; + int ret; + + owner = kzalloc(sizeof(*owner), GFP_KERNEL); + if (!owner) + return -ENOMEM; + INIT_LIST_HEAD(&owner->clients); + + while (!of_parse_phandle_with_args(dev->of_node, "iommus", + "#iommu-cells", i, + &sysmmu_spec)) { + struct platform_device *sysmmu; + struct sysmmu_drvdata *data; + + sysmmu = of_find_device_by_node(sysmmu_spec.np); + if (!sysmmu) { + dev_err(dev, "sysmmu node not found\n"); + ret = -ENODEV; + goto err; + } + data = platform_get_drvdata(sysmmu); + if (!data) { + ret = -ENODEV; + goto err; + } + + of_property_read_string_index(dev->of_node, "iommu-names", i, + &data->name); + + if (sysmmu_spec.args_count == 2) { + data->base = sysmmu_spec.args[0]; + data->size = sysmmu_spec.args[1]; + } else if (sysmmu_spec.args_count != 0) { + dev_err(dev, "incorrect iommu property specified\n"); + ret = -EINVAL; + goto err; + } + + list_add_tail(&data->owner_node, &owner->clients); + + i++; + } + + if (i == 0) { + ret = -ENODEV; + goto err; + } + + dev->archdata.iommu = owner; + dev_dbg(dev, "registered %d sysmmu controllers\n", i); + + return 0; +err: + __free_owner_struct(owner, dev); + return ret; +} + +static int __init_subdevice_sysmmu(struct device *dev) +{ + struct device *parent = dev->parent; + struct exynos_iommu_owner *owner; + struct sysmmu_drvdata *data; + char *name; + + name = strrchr(dev_name(dev), ':'); + if (!name) + return -ENODEV; + name++; + + owner = parent->archdata.iommu; + if (!owner) + return -ENODEV; + + list_for_each_entry(data, &owner->clients, owner_node) + if (strcmp(name, data->name) == 0) + break; + if (!data) + return -ENODEV; + + owner = kzalloc(sizeof(*owner), GFP_KERNEL); + if (!owner) + return -ENOMEM; + INIT_LIST_HEAD(&owner->clients); + + /* move sysmmu from parent to child device */ + list_del(&data->owner_node); + list_add_tail(&data->owner_node, &owner->clients); + + dev->archdata.iommu = owner; + dev_dbg(dev->parent, + "registered sysmmu controller for %s subdevice\n", data->name); + + return 0; +} + static int exynos_iommu_add_device(struct device *dev) { struct iommu_group *group; int ret;
- group = iommu_group_get(dev); + BUG_ON(dev->archdata.iommu != NULL);
- if (!group) { - group = iommu_group_alloc(); - if (IS_ERR(group)) { - dev_err(dev, "Failed to allocate IOMMU group\n"); - return PTR_ERR(group); - } + if (of_get_property(dev->of_node, "iommus", NULL)) + ret = __init_master_sysmmu(dev); + else if (dev->parent && + of_get_property(dev->parent->of_node, "iommus", NULL)) + ret = __init_subdevice_sysmmu(dev); + else + return -ENODEV; + + if (ret) + return ret; + + group = iommu_group_alloc(); + if (IS_ERR(group)) { + dev_err(dev, "Failed to allocate IOMMU group\n"); + ret = PTR_ERR(group); + goto err; }
ret = iommu_group_add_device(group, dev); + if (ret != 0) + goto err; + iommu_group_put(group);
+ return 0; +err: + __free_owner_struct(dev->archdata.iommu, dev); return ret; }
static void exynos_iommu_remove_device(struct device *dev) { + struct exynos_iommu_owner *owner = dev->archdata.iommu; + iommu_group_remove_device(dev); + if (owner) + __free_owner_struct(owner, dev); }
static const struct iommu_ops exynos_iommu_ops = {
This patch adds code to create default per-device iommu-based dma-mapping instance for all master devices, whose driver didn't set DRIVER_HAS_OWN_IOMMU_MANAGER flag.
Signed-off-by: Marek Szyprowski m.szyprowski@samsung.com --- drivers/iommu/exynos-iommu.c | 49 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-)
diff --git a/drivers/iommu/exynos-iommu.c b/drivers/iommu/exynos-iommu.c index 845f547..336b2f8 100644 --- a/drivers/iommu/exynos-iommu.c +++ b/drivers/iommu/exynos-iommu.c @@ -12,6 +12,7 @@ #define DEBUG #endif
+#include <linux/dma-mapping.h> #include <linux/io.h> #include <linux/interrupt.h> #include <linux/platform_device.h> @@ -29,6 +30,7 @@ #include <linux/export.h>
#include <asm/cacheflush.h> +#include <asm/dma-iommu.h> #include <asm/pgtable.h>
typedef u32 sysmmu_iova_t; @@ -1064,6 +1066,43 @@ static phys_addr_t exynos_iommu_iova_to_phys(struct iommu_domain *domain, return phys; }
+static int sysmmu_master_device_event(struct notifier_block *nb, + unsigned long val, void *p) +{ + struct device *dev = p; + struct exynos_iommu_owner *owner = dev->archdata.iommu; + struct sysmmu_drvdata *data; + + if (!owner) + return 0; + + data = list_first_entry(&owner->clients, struct sysmmu_drvdata, + owner_node); + if (!data) + return 0; + + switch (val) { + + case IOMMU_GROUP_NOTIFY_BIND_DRIVER: + if (!(dev->driver->flags & DRIVER_HAS_OWN_IOMMU_MANAGER)) + arm_iommu_create_default_mapping(dev, data->base, + data->size); + break; + + case IOMMU_GROUP_NOTIFY_UNBOUND_DRIVER: + case IOMMU_GROUP_NOTIFY_DRVBIND_FAILED: + if (!(dev->driver->flags & DRIVER_HAS_OWN_IOMMU_MANAGER)) + arm_iommu_release_default_mapping(dev); + break; + } + + return 0; +} + +static struct notifier_block sysmmu_master_device_notifier = { + .notifier_call = sysmmu_master_device_event, +}; + static void __free_owner_struct(struct exynos_iommu_owner *owner, struct device *dev) { @@ -1202,6 +1241,7 @@ static int exynos_iommu_add_device(struct device *dev) if (ret != 0) goto err;
+ iommu_group_register_notifier(group, &sysmmu_master_device_notifier); iommu_group_put(group);
return 0; @@ -1213,8 +1253,15 @@ err: static void exynos_iommu_remove_device(struct device *dev) { struct exynos_iommu_owner *owner = dev->archdata.iommu; + struct iommu_group *group = iommu_group_get(dev); + + if (group) { + iommu_group_unregister_notifier(group, + &sysmmu_master_device_notifier); + iommu_group_remove_device(dev); + iommu_group_put(group); + }
- iommu_group_remove_device(dev); if (owner) __free_owner_struct(owner, dev); }
This patch enables support for runtime pm for SYSMMU controllers. State of each controller is saved before master's device power domain is turned off and restored after it has been turned on.
exynos_iommu_attach_device() function might be called anytime, even when power domain for master's device has been turned off, so to let SYSMMU controllers to access its registers, a call to pm_runtime_get_sync() has to be done, which turns on the power domain, which SYSMMU belongs to. Later, once SYSMMU has been enabled, a call to pm_runtime_put() lets runtime pm to turn off the power domain if there are no other devices enabled. This way, the SYSMMU drivers get a genpd pm event and save its state with sysmmu_save_state() function.
Signed-off-by: Marek Szyprowski m.szyprowski@samsung.com --- drivers/iommu/exynos-iommu.c | 54 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-)
diff --git a/drivers/iommu/exynos-iommu.c b/drivers/iommu/exynos-iommu.c index 336b2f8..5cd91b11 100644 --- a/drivers/iommu/exynos-iommu.c +++ b/drivers/iommu/exynos-iommu.c @@ -28,6 +28,7 @@ #include <linux/list.h> #include <linux/memblock.h> #include <linux/export.h> +#include <linux/pm_domain.h>
#include <asm/cacheflush.h> #include <asm/dma-iommu.h> @@ -208,6 +209,7 @@ struct sysmmu_drvdata { struct clk *clk; struct clk *clk_master; int activations; + int suspended; spinlock_t lock; struct iommu_domain *domain; struct list_head domain_node; @@ -217,6 +219,7 @@ struct sysmmu_drvdata { const char *name; dma_addr_t base; size_t size; + struct notifier_block pm_notifier; };
static bool set_sysmmu_active(struct sysmmu_drvdata *data) @@ -235,7 +238,7 @@ static bool set_sysmmu_inactive(struct sysmmu_drvdata *data)
static bool is_sysmmu_active(struct sysmmu_drvdata *data) { - return data->activations > 0; + return (!data->suspended && data->activations > 0); }
static void sysmmu_unblock(void __iomem *sfrbase) @@ -528,6 +531,51 @@ static void sysmmu_tlb_invalidate_entry(struct sysmmu_drvdata *data, spin_unlock_irqrestore(&data->lock, flags); }
+static void sysmmu_restore_state(struct sysmmu_drvdata *data) +{ + unsigned long flags; + + spin_lock_irqsave(&data->lock, flags); + if (data->activations > 0) { + data->suspended = false; + __sysmmu_enable_nocount(data); + dev_dbg(data->sysmmu, "restored state\n"); + } + spin_unlock_irqrestore(&data->lock, flags); +} + +static void sysmmu_save_state(struct sysmmu_drvdata *data) +{ + unsigned long flags; + + spin_lock_irqsave(&data->lock, flags); + if (data->activations > 0) { + __sysmmu_disable_nocount(data); + data->suspended = true; + dev_dbg(data->sysmmu, "saved state\n"); + } + spin_unlock_irqrestore(&data->lock, flags); +} + +static int sysmmu_runtime_genpd_event(struct notifier_block *this, + unsigned long event, void *ptr) +{ + struct sysmmu_drvdata *data; + + data = container_of(this, struct sysmmu_drvdata, pm_notifier); + + switch (event) { + case PM_GENPD_POST_POWER_ON: + sysmmu_restore_state(data); + break; + case PM_GENPD_POWER_OFF_PREPARE: + sysmmu_save_state(data); + break; + } + + return NOTIFY_DONE; +} + static int __init exynos_sysmmu_probe(struct platform_device *pdev) { int irq, ret; @@ -580,6 +628,7 @@ static int __init exynos_sysmmu_probe(struct platform_device *pdev) }
data->sysmmu = dev; + data->pm_notifier.notifier_call = sysmmu_runtime_genpd_event;
/* default io address space parameters */ data->base = SZ_1G; @@ -708,6 +757,7 @@ static int exynos_iommu_attach_device(struct iommu_domain *domain, return -ENODEV;
list_for_each_entry(data, &owner->clients, owner_node) { + pm_runtime_get_sync(data->sysmmu); ret = __sysmmu_enable(data, pagetable, domain); if (ret >= 0) { data->master = dev; @@ -716,6 +766,7 @@ static int exynos_iommu_attach_device(struct iommu_domain *domain, list_add_tail(&data->domain_node, &priv->clients); spin_unlock_irqrestore(&priv->lock, flags); } + pm_runtime_put(data->sysmmu); }
if (ret < 0) { @@ -1156,6 +1207,7 @@ static int __init_master_sysmmu(struct device *dev) }
list_add_tail(&data->owner_node, &owner->clients); + pm_genpd_register_notifier(dev, &data->pm_notifier);
i++; }
This patch renames some variables to make the code easier to understand. 'domain' is replaced by 'iommu_domain' (more generic entity) and really meaning less 'priv' by 'domain' to reflect its purpose.
Signed-off-by: Marek Szyprowski m.szyprowski@samsung.com --- drivers/iommu/exynos-iommu.c | 191 ++++++++++++++++++++++--------------------- 1 file changed, 97 insertions(+), 94 deletions(-)
diff --git a/drivers/iommu/exynos-iommu.c b/drivers/iommu/exynos-iommu.c index 5cd91b11..7600861 100644 --- a/drivers/iommu/exynos-iommu.c +++ b/drivers/iommu/exynos-iommu.c @@ -440,8 +440,8 @@ static void __sysmmu_enable_nocount(struct sysmmu_drvdata *data) clk_disable(data->clk_master); }
-static int __sysmmu_enable(struct sysmmu_drvdata *data, - phys_addr_t pgtable, struct iommu_domain *domain) +static int __sysmmu_enable(struct sysmmu_drvdata *data, phys_addr_t pgtable, + struct iommu_domain *iommu_domain) { int ret = 0; unsigned long flags; @@ -449,7 +449,7 @@ static int __sysmmu_enable(struct sysmmu_drvdata *data, spin_lock_irqsave(&data->lock, flags); if (set_sysmmu_active(data)) { data->pgtable = pgtable; - data->domain = domain; + data->domain = iommu_domain;
__sysmmu_enable_nocount(data);
@@ -664,92 +664,93 @@ static inline void pgtable_flush(void *vastart, void *vaend) virt_to_phys(vaend)); }
-static int exynos_iommu_domain_init(struct iommu_domain *domain) +static int exynos_iommu_domain_init(struct iommu_domain *iommu_domain) { - struct exynos_iommu_domain *priv; + struct exynos_iommu_domain *domain; int i;
- priv = kzalloc(sizeof(*priv), GFP_KERNEL); - if (!priv) + domain = kzalloc(sizeof(*domain), GFP_KERNEL); + if (!domain) return -ENOMEM;
- priv->pgtable = (sysmmu_pte_t *)__get_free_pages(GFP_KERNEL, 2); - if (!priv->pgtable) + domain->pgtable = (sysmmu_pte_t *)__get_free_pages(GFP_KERNEL, 2); + if (!domain->pgtable) goto err_pgtable;
- priv->lv2entcnt = (short *)__get_free_pages(GFP_KERNEL | __GFP_ZERO, 1); - if (!priv->lv2entcnt) + domain->lv2entcnt = (short *) + __get_free_pages(GFP_KERNEL | __GFP_ZERO, 1); + if (!domain->lv2entcnt) goto err_counter;
/* w/a of System MMU v3.3 to prevent caching 1MiB mapping */ for (i = 0; i < NUM_LV1ENTRIES; i += 8) { - priv->pgtable[i + 0] = ZERO_LV2LINK; - priv->pgtable[i + 1] = ZERO_LV2LINK; - priv->pgtable[i + 2] = ZERO_LV2LINK; - priv->pgtable[i + 3] = ZERO_LV2LINK; - priv->pgtable[i + 4] = ZERO_LV2LINK; - priv->pgtable[i + 5] = ZERO_LV2LINK; - priv->pgtable[i + 6] = ZERO_LV2LINK; - priv->pgtable[i + 7] = ZERO_LV2LINK; + domain->pgtable[i + 0] = ZERO_LV2LINK; + domain->pgtable[i + 1] = ZERO_LV2LINK; + domain->pgtable[i + 2] = ZERO_LV2LINK; + domain->pgtable[i + 3] = ZERO_LV2LINK; + domain->pgtable[i + 4] = ZERO_LV2LINK; + domain->pgtable[i + 5] = ZERO_LV2LINK; + domain->pgtable[i + 6] = ZERO_LV2LINK; + domain->pgtable[i + 7] = ZERO_LV2LINK; }
- pgtable_flush(priv->pgtable, priv->pgtable + NUM_LV1ENTRIES); + pgtable_flush(domain->pgtable, domain->pgtable + NUM_LV1ENTRIES);
- spin_lock_init(&priv->lock); - spin_lock_init(&priv->pgtablelock); - INIT_LIST_HEAD(&priv->clients); + spin_lock_init(&domain->lock); + spin_lock_init(&domain->pgtablelock); + INIT_LIST_HEAD(&domain->clients);
- domain->geometry.aperture_start = 0; - domain->geometry.aperture_end = ~0UL; - domain->geometry.force_aperture = true; + iommu_domain->geometry.aperture_start = 0; + iommu_domain->geometry.aperture_end = ~0UL; + iommu_domain->geometry.force_aperture = true;
- domain->priv = priv; + iommu_domain->priv = domain; return 0;
err_counter: - free_pages((unsigned long)priv->pgtable, 2); + free_pages((unsigned long)domain->pgtable, 2); err_pgtable: - kfree(priv); + kfree(domain); return -ENOMEM; }
-static void exynos_iommu_domain_destroy(struct iommu_domain *domain) +static void exynos_iommu_domain_destroy(struct iommu_domain *iommu_domain) { - struct exynos_iommu_domain *priv = domain->priv; + struct exynos_iommu_domain *domain = iommu_domain->priv; struct sysmmu_drvdata *data; unsigned long flags; int i;
- WARN_ON(!list_empty(&priv->clients)); + WARN_ON(!list_empty(&domain->clients));
- spin_lock_irqsave(&priv->lock, flags); + spin_lock_irqsave(&domain->lock, flags);
- list_for_each_entry(data, &priv->clients, domain_node) { + list_for_each_entry(data, &domain->clients, domain_node) { if (__sysmmu_disable(data)) data->master = NULL; list_del_init(&data->domain_node); }
- spin_unlock_irqrestore(&priv->lock, flags); + spin_unlock_irqrestore(&domain->lock, flags);
for (i = 0; i < NUM_LV1ENTRIES; i++) - if (lv1ent_page(priv->pgtable + i)) + if (lv1ent_page(domain->pgtable + i)) kmem_cache_free(lv2table_kmem_cache, - phys_to_virt(lv2table_base(priv->pgtable + i))); + phys_to_virt(lv2table_base(domain->pgtable + i)));
- free_pages((unsigned long)priv->pgtable, 2); - free_pages((unsigned long)priv->lv2entcnt, 1); - kfree(domain->priv); - domain->priv = NULL; + free_pages((unsigned long)domain->pgtable, 2); + free_pages((unsigned long)domain->lv2entcnt, 1); + kfree(iommu_domain->priv); + iommu_domain->priv = NULL; }
-static int exynos_iommu_attach_device(struct iommu_domain *domain, +static int exynos_iommu_attach_device(struct iommu_domain *iommu_domain, struct device *dev) { struct exynos_iommu_owner *owner = dev->archdata.iommu; - struct exynos_iommu_domain *priv = domain->priv; + struct exynos_iommu_domain *domain = iommu_domain->priv; struct sysmmu_drvdata *data; - phys_addr_t pagetable = virt_to_phys(priv->pgtable); + phys_addr_t pagetable = virt_to_phys(domain->pgtable); unsigned long flags; int ret = -ENODEV;
@@ -758,13 +759,13 @@ static int exynos_iommu_attach_device(struct iommu_domain *domain,
list_for_each_entry(data, &owner->clients, owner_node) { pm_runtime_get_sync(data->sysmmu); - ret = __sysmmu_enable(data, pagetable, domain); + ret = __sysmmu_enable(data, pagetable, iommu_domain); if (ret >= 0) { data->master = dev;
- spin_lock_irqsave(&priv->lock, flags); - list_add_tail(&data->domain_node, &priv->clients); - spin_unlock_irqrestore(&priv->lock, flags); + spin_lock_irqsave(&domain->lock, flags); + list_add_tail(&data->domain_node, &domain->clients); + spin_unlock_irqrestore(&domain->lock, flags); } pm_runtime_put(data->sysmmu); } @@ -781,11 +782,11 @@ static int exynos_iommu_attach_device(struct iommu_domain *domain, return ret; }
-static void exynos_iommu_detach_device(struct iommu_domain *domain, +static void exynos_iommu_detach_device(struct iommu_domain *iommu_domain, struct device *dev) { - struct exynos_iommu_domain *priv = domain->priv; - phys_addr_t pagetable = virt_to_phys(priv->pgtable); + struct exynos_iommu_domain *domain = iommu_domain->priv; + phys_addr_t pagetable = virt_to_phys(domain->pgtable); struct sysmmu_drvdata *data, *next; unsigned long flags; int found = 0; @@ -793,8 +794,8 @@ static void exynos_iommu_detach_device(struct iommu_domain *domain, if (!has_sysmmu(dev)) return;
- spin_lock_irqsave(&priv->lock, flags); - list_for_each_entry_safe(data, next, &priv->clients, domain_node) { + spin_lock_irqsave(&domain->lock, flags); + list_for_each_entry_safe(data, next, &domain->clients, domain_node) { if (data->master == dev) { if (__sysmmu_disable(data)) { data->master = NULL; @@ -803,7 +804,7 @@ static void exynos_iommu_detach_device(struct iommu_domain *domain, found = true; } } - spin_unlock_irqrestore(&priv->lock, flags); + spin_unlock_irqrestore(&domain->lock, flags);
if (found) dev_dbg(dev, "%s: Detached IOMMU with pgtable %pa\n", @@ -812,7 +813,7 @@ static void exynos_iommu_detach_device(struct iommu_domain *domain, dev_err(dev, "%s: No IOMMU is attached\n", __func__); }
-static sysmmu_pte_t *alloc_lv2entry(struct exynos_iommu_domain *priv, +static sysmmu_pte_t *alloc_lv2entry(struct exynos_iommu_domain *domain, sysmmu_pte_t *sent, sysmmu_iova_t iova, short *pgcounter) { if (lv1ent_section(sent)) { @@ -854,17 +855,17 @@ static sysmmu_pte_t *alloc_lv2entry(struct exynos_iommu_domain *priv, if (need_flush_flpd_cache) { struct sysmmu_drvdata *data;
- spin_lock(&priv->lock); - list_for_each_entry(data, &priv->clients, domain_node) + spin_lock(&domain->lock); + list_for_each_entry(data, &domain->clients, domain_node) sysmmu_tlb_invalidate_flpdcache(data, iova); - spin_unlock(&priv->lock); + spin_unlock(&domain->lock); } }
return page_entry(sent, iova); }
-static int lv1set_section(struct exynos_iommu_domain *priv, +static int lv1set_section(struct exynos_iommu_domain *domain, sysmmu_pte_t *sent, sysmmu_iova_t iova, phys_addr_t paddr, short *pgcnt) { @@ -889,17 +890,17 @@ static int lv1set_section(struct exynos_iommu_domain *priv,
pgtable_flush(sent, sent + 1);
- spin_lock(&priv->lock); + spin_lock(&domain->lock); if (lv1ent_page_zero(sent)) { struct sysmmu_drvdata *data; /* * Flushing FLPD cache in System MMU v3.3 that may cache a FLPD * entry by speculative prefetch of SLPD which has no mapping. */ - list_for_each_entry(data, &priv->clients, domain_node) + list_for_each_entry(data, &domain->clients, domain_node) sysmmu_tlb_invalidate_flpdcache(data, iova); } - spin_unlock(&priv->lock); + spin_unlock(&domain->lock);
return 0; } @@ -959,74 +960,76 @@ static int lv2set_page(sysmmu_pte_t *pent, phys_addr_t paddr, size_t size, * than or equal size to 128KiB. * - Start address of an I/O virtual region must be aligned by 128KiB. */ -static int exynos_iommu_map(struct iommu_domain *domain, unsigned long l_iova, - phys_addr_t paddr, size_t size, int prot) +static int exynos_iommu_map(struct iommu_domain *iommu_domain, + unsigned long l_iova, phys_addr_t paddr, size_t size, + int prot) { - struct exynos_iommu_domain *priv = domain->priv; + struct exynos_iommu_domain *domain = iommu_domain->priv; sysmmu_pte_t *entry; sysmmu_iova_t iova = (sysmmu_iova_t)l_iova; unsigned long flags; int ret = -ENOMEM;
- BUG_ON(priv->pgtable == NULL); + BUG_ON(domain->pgtable == NULL);
- spin_lock_irqsave(&priv->pgtablelock, flags); + spin_lock_irqsave(&domain->pgtablelock, flags);
- entry = section_entry(priv->pgtable, iova); + entry = section_entry(domain->pgtable, iova);
if (size == SECT_SIZE) { - ret = lv1set_section(priv, entry, iova, paddr, - &priv->lv2entcnt[lv1ent_offset(iova)]); + ret = lv1set_section(domain, entry, iova, paddr, + &domain->lv2entcnt[lv1ent_offset(iova)]); } else { sysmmu_pte_t *pent;
- pent = alloc_lv2entry(priv, entry, iova, - &priv->lv2entcnt[lv1ent_offset(iova)]); + pent = alloc_lv2entry(domain, entry, iova, + &domain->lv2entcnt[lv1ent_offset(iova)]);
if (IS_ERR(pent)) ret = PTR_ERR(pent); else ret = lv2set_page(pent, paddr, size, - &priv->lv2entcnt[lv1ent_offset(iova)]); + &domain->lv2entcnt[lv1ent_offset(iova)]); }
if (ret) pr_err("%s: Failed(%d) to map %#zx bytes @ %#x\n", __func__, ret, size, iova);
- spin_unlock_irqrestore(&priv->pgtablelock, flags); + spin_unlock_irqrestore(&domain->pgtablelock, flags);
return ret; }
-static void exynos_iommu_tlb_invalidate_entry(struct exynos_iommu_domain *priv, - sysmmu_iova_t iova, size_t size) +static +void exynos_iommu_tlb_invalidate_entry(struct exynos_iommu_domain *domain, + sysmmu_iova_t iova, size_t size) { struct sysmmu_drvdata *data; unsigned long flags;
- spin_lock_irqsave(&priv->lock, flags); + spin_lock_irqsave(&domain->lock, flags);
- list_for_each_entry(data, &priv->clients, domain_node) + list_for_each_entry(data, &domain->clients, domain_node) sysmmu_tlb_invalidate_entry(data, iova, size);
- spin_unlock_irqrestore(&priv->lock, flags); + spin_unlock_irqrestore(&domain->lock, flags); }
-static size_t exynos_iommu_unmap(struct iommu_domain *domain, +static size_t exynos_iommu_unmap(struct iommu_domain *iommu_domain, unsigned long l_iova, size_t size) { - struct exynos_iommu_domain *priv = domain->priv; + struct exynos_iommu_domain *domain = iommu_domain->priv; sysmmu_iova_t iova = (sysmmu_iova_t)l_iova; sysmmu_pte_t *ent; size_t err_pgsize; unsigned long flags;
- BUG_ON(priv->pgtable == NULL); + BUG_ON(domain->pgtable == NULL);
- spin_lock_irqsave(&priv->pgtablelock, flags); + spin_lock_irqsave(&domain->pgtablelock, flags);
- ent = section_entry(priv->pgtable, iova); + ent = section_entry(domain->pgtable, iova);
if (lv1ent_section(ent)) { if (WARN_ON(size < SECT_SIZE)) { @@ -1059,7 +1062,7 @@ static size_t exynos_iommu_unmap(struct iommu_domain *domain, *ent = 0; size = SPAGE_SIZE; pgtable_flush(ent, ent + 1); - priv->lv2entcnt[lv1ent_offset(iova)] += 1; + domain->lv2entcnt[lv1ent_offset(iova)] += 1; goto done; }
@@ -1073,15 +1076,15 @@ static size_t exynos_iommu_unmap(struct iommu_domain *domain, pgtable_flush(ent, ent + SPAGES_PER_LPAGE);
size = LPAGE_SIZE; - priv->lv2entcnt[lv1ent_offset(iova)] += SPAGES_PER_LPAGE; + domain->lv2entcnt[lv1ent_offset(iova)] += SPAGES_PER_LPAGE; done: - spin_unlock_irqrestore(&priv->pgtablelock, flags); + spin_unlock_irqrestore(&domain->pgtablelock, flags);
- exynos_iommu_tlb_invalidate_entry(priv, iova, size); + exynos_iommu_tlb_invalidate_entry(domain, iova, size);
return size; err: - spin_unlock_irqrestore(&priv->pgtablelock, flags); + spin_unlock_irqrestore(&domain->pgtablelock, flags);
pr_err("%s: Failed: size(%#zx) @ %#x is smaller than page size %#zx\n", __func__, size, iova, err_pgsize); @@ -1089,17 +1092,17 @@ err: return 0; }
-static phys_addr_t exynos_iommu_iova_to_phys(struct iommu_domain *domain, +static phys_addr_t exynos_iommu_iova_to_phys(struct iommu_domain *iommu_domain, dma_addr_t iova) { - struct exynos_iommu_domain *priv = domain->priv; + struct exynos_iommu_domain *domain = iommu_domain->priv; sysmmu_pte_t *entry; unsigned long flags; phys_addr_t phys = 0;
- spin_lock_irqsave(&priv->pgtablelock, flags); + spin_lock_irqsave(&domain->pgtablelock, flags);
- entry = section_entry(priv->pgtable, iova); + entry = section_entry(domain->pgtable, iova);
if (lv1ent_section(entry)) { phys = section_phys(entry) + section_offs(iova); @@ -1112,7 +1115,7 @@ static phys_addr_t exynos_iommu_iova_to_phys(struct iommu_domain *domain, phys = spage_phys(entry) + spage_offs(iova); }
- spin_unlock_irqrestore(&priv->pgtablelock, flags); + spin_unlock_irqrestore(&domain->pgtablelock, flags);
return phys; }
Add a few words of comment to all internal structures used by the driver.
Signed-off-by: Marek Szyprowski m.szyprowski@samsung.com --- drivers/iommu/exynos-iommu.c | 59 ++++++++++++++++++++++++++++---------------- 1 file changed, 38 insertions(+), 21 deletions(-)
diff --git a/drivers/iommu/exynos-iommu.c b/drivers/iommu/exynos-iommu.c index 7600861..78ce618 100644 --- a/drivers/iommu/exynos-iommu.c +++ b/drivers/iommu/exynos-iommu.c @@ -189,37 +189,54 @@ static char *sysmmu_fault_name[SYSMMU_FAULTS_NUM] = { "UNKNOWN FAULT" };
-/* attached to dev.archdata.iommu of the master device */ +/* + * This structure is attached to dev.archdata.iommu of the master device + * on device add, contains a list of SYSMMU controllers defined by device tree, + * which are bound to given master device. It is usually referenced by 'owner' + * pointer. + */ struct exynos_iommu_owner { - struct list_head clients; + struct list_head clients; /* list of sysmmu_drvdata.owner_node */ };
+/* + * This structure is stored in ->priv field of generic struct iommu_domain, + * contains list of SYSMMU controllers from all master devices, which has been + * attached to this domain and page tables of IO address space defined by this + * domain. It is usually referenced by 'domain' pointer. + */ struct exynos_iommu_domain { - struct list_head clients; /* list of sysmmu_drvdata.node */ + struct list_head clients; /* list of sysmmu_drvdata.domain_node */ sysmmu_pte_t *pgtable; /* lv1 page table, 16KB */ short *lv2entcnt; /* free lv2 entry counter for each section */ - spinlock_t lock; /* lock for this structure */ + spinlock_t lock; /* lock for modyfying list of clients */ spinlock_t pgtablelock; /* lock for modifying page table @ pgtable */ };
+/* + * This structure hold all data of a single SYSMMU controller, this includes + * hw resources like registers and clocks, pointers and list nodes to connect + * it to all other structures, internal state and parameters read from device + * tree. It is usually referenced by 'data' pointer. + */ struct sysmmu_drvdata { - struct device *sysmmu; /* System MMU's device descriptor */ - struct device *master; /* Owner of system MMU */ - void __iomem *sfrbase; - struct clk *clk; - struct clk *clk_master; - int activations; - int suspended; - spinlock_t lock; - struct iommu_domain *domain; - struct list_head domain_node; - struct list_head owner_node; - phys_addr_t pgtable; - int version; - const char *name; - dma_addr_t base; - size_t size; - struct notifier_block pm_notifier; + struct device *sysmmu; /* SYSMMU controller device */ + struct device *master; /* master device (owner of given SYSMMU) */ + void __iomem *sfrbase; /* our registers */ + struct clk *clk; /* SYSMMU's clock */ + struct clk *clk_master; /* master's device clock */ + int activations; /* number of calls to sysmmu_enable */ + int suspended; /* status of the controller (managed by runtime pm) */ + spinlock_t lock; /* lock for modyfying enable/disable state */ + struct iommu_domain *domain; /* domain we belong to */ + struct list_head domain_node; /* node for domain clients list */ + struct list_head owner_node; /* node for owner clients list */ + phys_addr_t pgtable; /* assigned page table structure */ + int version; /* our version */ + const char *name; /* our name from device tree */ + dma_addr_t base; /* base addres of IO address space define in DT */ + size_t size; /* size of IO address space define in DT */ + struct notifier_block pm_notifier; /* notifier for pm domain on/off */ };
static bool set_sysmmu_active(struct sysmmu_drvdata *data)
Removed following unused includes: <linux/mm.h>, <linux/errno.h>, <linux/memblock.h> and <linux/export.h>.
Signed-off-by: Marek Szyprowski m.szyprowski@samsung.com --- drivers/iommu/exynos-iommu.c | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-)
diff --git a/drivers/iommu/exynos-iommu.c b/drivers/iommu/exynos-iommu.c index 78ce618..1bbb100 100644 --- a/drivers/iommu/exynos-iommu.c +++ b/drivers/iommu/exynos-iommu.c @@ -12,23 +12,19 @@ #define DEBUG #endif
+#include <linux/clk.h> #include <linux/dma-mapping.h> +#include <linux/err.h> #include <linux/io.h> +#include <linux/iommu.h> #include <linux/interrupt.h> -#include <linux/platform_device.h> -#include <linux/slab.h> -#include <linux/pm_runtime.h> -#include <linux/clk.h> -#include <linux/err.h> -#include <linux/mm.h> +#include <linux/list.h> #include <linux/of.h> #include <linux/of_platform.h> -#include <linux/iommu.h> -#include <linux/errno.h> -#include <linux/list.h> -#include <linux/memblock.h> -#include <linux/export.h> +#include <linux/platform_device.h> #include <linux/pm_domain.h> +#include <linux/pm_runtime.h> +#include <linux/slab.h>
#include <asm/cacheflush.h> #include <asm/dma-iommu.h>
This patch is an example how to use more than one IOMMU controller in a device driver for hardware blocks, which have more then one dma master (memory interface with iommu controller).
This patch is temporary, do not merge it yet.
Signed-off-by: Marek Szyprowski m.szyprowski@samsung.com --- drivers/media/platform/s5p-mfc/s5p_mfc.c | 46 ++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-)
diff --git a/drivers/media/platform/s5p-mfc/s5p_mfc.c b/drivers/media/platform/s5p-mfc/s5p_mfc.c index 77b99ae..c2e95ab 100644 --- a/drivers/media/platform/s5p-mfc/s5p_mfc.c +++ b/drivers/media/platform/s5p-mfc/s5p_mfc.c @@ -22,7 +22,11 @@ #include <media/v4l2-event.h> #include <linux/workqueue.h> #include <linux/of.h> +#include <linux/of_platform.h> #include <media/videobuf2-core.h> + +#include <asm/dma-iommu.h> + #include "s5p_mfc_common.h" #include "s5p_mfc_ctrl.h" #include "s5p_mfc_debug.h" @@ -996,6 +1000,39 @@ static const struct v4l2_file_operations s5p_mfc_fops = { .mmap = s5p_mfc_mmap, };
+static struct device *s5p_mfc_alloc_memdev(struct device *dev, const char *name) +{ + struct device *child; + int ret; + + child = devm_kzalloc(dev, sizeof(struct device), GFP_KERNEL); + if (!child) + return NULL; + + device_initialize(child); + dev_set_name(child, "%s:%s", dev_name(dev), name); + child->parent = dev; + child->bus = dev->bus; + child->coherent_dma_mask = dev->coherent_dma_mask; + child->dma_mask = dev->dma_mask; + + if (device_add(child) == 0) { + ret = arm_iommu_create_default_mapping(child, 0x20000000, + SZ_256M); + if (ret == 0) + return child; + } + + put_device(child); + return NULL; +} + +void s5p_mfc_free_memdev(struct device *dev) +{ + arm_iommu_release_default_mapping(dev); + put_device(dev); +} + static void *mfc_get_drv_data(struct platform_device *pdev);
/* MFC probe function */ @@ -1049,8 +1086,8 @@ static int s5p_mfc_probe(struct platform_device *pdev) goto err_res; }
- dev->mem_dev_l = &dev->plat_dev->dev; - dev->mem_dev_r = &dev->plat_dev->dev; + dev->mem_dev_l = s5p_mfc_alloc_memdev(&dev->plat_dev->dev, "left"); + dev->mem_dev_r = s5p_mfc_alloc_memdev(&dev->plat_dev->dev, "right");
dev->alloc_ctx[0] = vb2_dma_contig_init_ctx(dev->mem_dev_l); if (IS_ERR(dev->alloc_ctx[0])) { @@ -1181,6 +1218,10 @@ static int s5p_mfc_remove(struct platform_device *pdev) s5p_mfc_release_firmware(dev); vb2_dma_contig_cleanup_ctx(dev->alloc_ctx[0]); vb2_dma_contig_cleanup_ctx(dev->alloc_ctx[1]); + if (dev->mem_dev_l) + s5p_mfc_free_memdev(dev->mem_dev_l); + if (dev->mem_dev_r) + s5p_mfc_free_memdev(dev->mem_dev_r);
s5p_mfc_final_pm(dev); return 0; @@ -1436,6 +1477,7 @@ static struct platform_driver s5p_mfc_driver = { .owner = THIS_MODULE, .pm = &s5p_mfc_pm_ops, .of_match_table = exynos_mfc_match, + .flags = DRIVER_HAS_OWN_IOMMU_MANAGER, }, };
On Tue, Aug 05, 2014 at 12:47:28PM +0200, Marek Szyprowski wrote:
.../devicetree/bindings/iommu/samsung,sysmmu.txt | 93 ++- Documentation/power/notifiers.txt | 14 + arch/arm/boot/dts/exynos4.dtsi | 118 ++++ arch/arm/boot/dts/exynos4210.dtsi | 23 + arch/arm/boot/dts/exynos4x12.dtsi | 82 +++ arch/arm/include/asm/dma-iommu.h | 36 ++ arch/arm/mach-exynos/pm_domains.c | 12 +- arch/arm/mach-integrator/impd1.c | 2 +- arch/arm/mm/dma-mapping.c | 47 ++ drivers/base/bus.c | 4 +- drivers/base/dd.c | 10 +- drivers/base/platform.c | 2 +- drivers/base/power/domain.c | 70 ++- drivers/clk/samsung/clk-exynos4.c | 1 + drivers/gpu/drm/exynos/exynos_drm_fimc.c | 1 + drivers/gpu/drm/exynos/exynos_drm_fimd.c | 26 +- drivers/gpu/drm/exynos/exynos_drm_g2d.c | 1 + drivers/gpu/drm/exynos/exynos_drm_gsc.c | 1 + drivers/gpu/drm/exynos/exynos_drm_rotator.c | 1 + drivers/gpu/drm/exynos/exynos_mixer.c | 1 + drivers/iommu/exynos-iommu.c | 663 +++++++++++++-------- drivers/iommu/iommu.c | 3 + drivers/media/platform/s5p-mfc/s5p_mfc.c | 107 ++-- drivers/pci/host/pci-mvebu.c | 2 +- drivers/pci/host/pci-rcar-gen2.c | 2 +- drivers/pci/host/pci-tegra.c | 2 +- drivers/pci/host/pcie-rcar.c | 2 +- drivers/soc/tegra/pmc.c | 2 +- include/dt-bindings/clock/exynos4.h | 10 +- include/linux/device.h | 12 +- include/linux/iommu.h | 1 + include/linux/pm.h | 2 + include/linux/pm_domain.h | 19 + 33 files changed, 1016 insertions(+), 356 deletions(-)
This touches a lot of non-iommu stuff. What is your strategy on getting this in, do you plan to get the non-iommu changes merged first or do you want to collect the respective Acks and merge this all through one tree?
Joerg
Hello,
On 2014-08-19 01:32, Joerg Roedel wrote:
On Tue, Aug 05, 2014 at 12:47:28PM +0200, Marek Szyprowski wrote:
.../devicetree/bindings/iommu/samsung,sysmmu.txt | 93 ++- Documentation/power/notifiers.txt | 14 + arch/arm/boot/dts/exynos4.dtsi | 118 ++++ arch/arm/boot/dts/exynos4210.dtsi | 23 + arch/arm/boot/dts/exynos4x12.dtsi | 82 +++ arch/arm/include/asm/dma-iommu.h | 36 ++ arch/arm/mach-exynos/pm_domains.c | 12 +- arch/arm/mach-integrator/impd1.c | 2 +- arch/arm/mm/dma-mapping.c | 47 ++ drivers/base/bus.c | 4 +- drivers/base/dd.c | 10 +- drivers/base/platform.c | 2 +- drivers/base/power/domain.c | 70 ++- drivers/clk/samsung/clk-exynos4.c | 1 + drivers/gpu/drm/exynos/exynos_drm_fimc.c | 1 + drivers/gpu/drm/exynos/exynos_drm_fimd.c | 26 +- drivers/gpu/drm/exynos/exynos_drm_g2d.c | 1 + drivers/gpu/drm/exynos/exynos_drm_gsc.c | 1 + drivers/gpu/drm/exynos/exynos_drm_rotator.c | 1 + drivers/gpu/drm/exynos/exynos_mixer.c | 1 + drivers/iommu/exynos-iommu.c | 663 +++++++++++++-------- drivers/iommu/iommu.c | 3 + drivers/media/platform/s5p-mfc/s5p_mfc.c | 107 ++-- drivers/pci/host/pci-mvebu.c | 2 +- drivers/pci/host/pci-rcar-gen2.c | 2 +- drivers/pci/host/pci-tegra.c | 2 +- drivers/pci/host/pcie-rcar.c | 2 +- drivers/soc/tegra/pmc.c | 2 +- include/dt-bindings/clock/exynos4.h | 10 +- include/linux/device.h | 12 +- include/linux/iommu.h | 1 + include/linux/pm.h | 2 + include/linux/pm_domain.h | 19 + 33 files changed, 1016 insertions(+), 356 deletions(-)
This touches a lot of non-iommu stuff. What is your strategy on getting this in, do you plan to get the non-iommu changes merged first or do you want to collect the respective Acks and merge this all through one tree?
Those patches are posted as one patchset mainly to demonstrate how to get everything to work together. I also posted this as a single patch series to get some feedback from other iommu developers, especially all those involved in the generic iommu dt bindings.
For merging, I will split them into smaller series and try to get respective acks.
Best regards
Hi Marek and Inki,
Am 19.08.2014 08:07, schrieb Marek Szyprowski:
On 2014-08-19 01:32, Joerg Roedel wrote:
On Tue, Aug 05, 2014 at 12:47:28PM +0200, Marek Szyprowski wrote:
[...]
33 files changed, 1016 insertions(+), 356 deletions(-)
This touches a lot of non-iommu stuff. What is your strategy on getting this in, do you plan to get the non-iommu changes merged first or do you want to collect the respective Acks and merge this all through one tree?
Those patches are posted as one patchset mainly to demonstrate how to get everything to work together. I also posted this as a single patch series to get some feedback from other iommu developers, especially all those involved in the generic iommu dt bindings.
For merging, I will split them into smaller series and try to get respective acks.
I'm working on 5250 based Spring Chromebook and noticed that v3.17-rc1 got some more iommu support. With the new CONFIG_DRM_EXYNOS_IOMMU=y my machine stops booting. So I'm wondering, is any of this a fix for 3.17, or is all of this "unrelated" -next material? Also, are you or someone working on the respective DT changes for Exynos5?
Regards, Andreas
Hello,
On 2014-08-19 13:39, Andreas Färber wrote:
Hi Marek and Inki,
Am 19.08.2014 08:07, schrieb Marek Szyprowski:
On 2014-08-19 01:32, Joerg Roedel wrote:
On Tue, Aug 05, 2014 at 12:47:28PM +0200, Marek Szyprowski wrote:
[...]
33 files changed, 1016 insertions(+), 356 deletions(-)
This touches a lot of non-iommu stuff. What is your strategy on getting this in, do you plan to get the non-iommu changes merged first or do you want to collect the respective Acks and merge this all through one tree?
Those patches are posted as one patchset mainly to demonstrate how to get everything to work together. I also posted this as a single patch series to get some feedback from other iommu developers, especially all those involved in the generic iommu dt bindings.
For merging, I will split them into smaller series and try to get respective acks.
I'm working on 5250 based Spring Chromebook and noticed that v3.17-rc1 got some more iommu support. With the new CONFIG_DRM_EXYNOS_IOMMU=y my machine stops booting. So I'm wondering, is any of this a fix for 3.17, or is all of this "unrelated" -next material?
This is probably a side effect of patch 3170447c1f264d51b8d1f3898bf2588588a64fdc ("iommu/exynos: Select ARM_DMA_USE_IOMMU"). It added selection of ARM_DMA_USE_IOMMU symbol, on which IOMMU support in Exynos DRM subsystem depends. However selecting this symbol is all that this patch does, without providing any code code which implements real support for ARM DMA IOMMU integration, which is needed by Exynos DRM driver. Please disable CONFIG_DRM_EXYNOS_IOMMU in kernel .config and your system should be bootable again.
Also, are you or someone working on the respective DT changes for Exynos5?
I can prepare DT changes for Exynos5 as well, but first I wanted to clarify if everyone involved in generic IOMMU bindings and Exynos IOMMU driver agrees on my proposal.
Best regards
Hi,
Am 19.08.2014 14:01, schrieb Marek Szyprowski:
On 2014-08-19 13:39, Andreas Färber wrote:
I'm working on 5250 based Spring Chromebook and noticed that v3.17-rc1 got some more iommu support. With the new CONFIG_DRM_EXYNOS_IOMMU=y my machine stops booting. So I'm wondering, is any of this a fix for 3.17, or is all of this "unrelated" -next material?
This is probably a side effect of patch 3170447c1f264d51b8d1f3898bf2588588a64fdc ("iommu/exynos: Select ARM_DMA_USE_IOMMU"). It added selection of ARM_DMA_USE_IOMMU symbol, on which IOMMU support in Exynos DRM subsystem depends. However selecting this symbol is all that this patch does, without providing any code code which implements real support for ARM DMA IOMMU integration, which is needed by Exynos DRM driver. Please disable CONFIG_DRM_EXYNOS_IOMMU in kernel .config and your system should be bootable again.
Yes, that's what my report implied. :)
I'm bringing this up for -rc2 though: It sounds as if that option should remain unavailable until the required code is in? Thanks.
Also, are you or someone working on the respective DT changes for Exynos5?
I can prepare DT changes for Exynos5 as well, but first I wanted to clarify if everyone involved in generic IOMMU bindings and Exynos IOMMU driver agrees on my proposal.
Sure. I'm updating my old spring-bridge.v6 branch [1] to 3.17-rc1 [2], which involves three drm bridge patches rebased onto the new drm panel prepare/unprepare infrastructure plus two LVDS DT patches in addition to the IOMMU patches. That old branch included DT changes for 5250 in "ARM: dts: add System MMU nodes of Exynos SoCs", but that'll probably need updating for the new bindings.
Regards, Andreas
[1] https://github.com/afaerber/linux/commits/spring-bridge.v6 [2] https://github.com/afaerber/linux/commits/spring-next
linaro-mm-sig@lists.linaro.org