* lsk-44/linux-linaro-lsk-v4.4: Linux 4.4.3 modules: fix modparam async_probe request module: wrapper for symbol name. itimers: Handle relative timers with CONFIG_TIME_LOW_RES proper posix-timers: Handle relative timers with CONFIG_TIME_LOW_RES proper timerfd: Handle relative timers with CONFIG_TIME_LOW_RES proper prctl: take mmap sem for writing to protect against others xfs: log mount failures don't wait for buffers to be released Revert "xfs: clear PF_NOFREEZE for xfsaild kthread" xfs: inode recovery readahead can race with inode buffer creation libxfs: pack the agfl header structure so XFS_AGFL_SIZE is correct ovl: setattr: check permissions before copy-up ovl: root: copy attr ovl: check dentry positiveness in ovl_cleanup_whiteouts() ovl: use a minimal buffer in ovl_copy_xattr ovl: allow zero size xattr futex: Drop refcount if requeue_pi() acquired the rtmutex devm_memremap_release(): fix memremap'd addr handling ipc/shm: handle removed segments gracefully in shm_mmap() intel_scu_ipcutil: underflow in scu_reg_access() mm,thp: khugepaged: call pte flush at the time of collapse dump_stack: avoid potential deadlocks radix-tree: fix oops after radix_tree_iter_retry drivers/hwspinlock: fix race between radix tree insertion and lookup radix-tree: fix race in gang lookup MAINTAINERS: return arch/sh to maintained state, with new maintainers memcg: only free spare array when readers are done numa: fix /proc/<pid>/numa_maps for hugetlbfs on s390 fs/hugetlbfs/inode.c: fix bugs in hugetlb_vmtruncate_list() scripts/bloat-o-meter: fix python3 syntax error dma-debug: switch check from _text to _stext m32r: fix m32104ut_defconfig build fail xhci: Fix list corruption in urb dequeue at host removal Revert "xhci: don't finish a TD if we get a short-transfer event mid TD" iommu/vt-d: Clear PPR bit to ensure we get more page request interrupts iommu/vt-d: Fix 64-bit accesses to 32-bit DMAR_GSTS_REG iommu/vt-d: Fix mm refcounting to hold mm_count not mm_users iommu/amd: Correct the wrong setting of alias DTE in do_attach iommu/vt-d: Don't skip PCI devices when disabling IOTLB Input: vmmouse - fix absolute device registration string_helpers: fix precision loss for some inputs Input: i8042 - add Fujitsu Lifebook U745 to the nomux list Input: elantech - mark protocols v2 and v3 as semi-mt mm: fix regression in remap_file_pages() emulation mm: replace vma_lock_anon_vma with anon_vma_lock_read/write mm: fix mlock accouting libnvdimm: fix namespace object confusion in is_uuid_busy() mm: soft-offline: check return value in second __get_any_page() call perf kvm record/report: 'unprocessable sample' error while recording/reporting guest data KVM: PPC: Fix ONE_REG AltiVec support KVM: PPC: Fix emulation of H_SET_DABR/X on POWER8 KVM: arm/arm64: Fix reference to uninitialised VGIC arm64: dma-mapping: fix handling of devices registered before arch_initcall ARM: OMAP2+: Fix ppa_zero_params and ppa_por_params for rodata ARM: OMAP2+: Fix save_secure_ram_context for rodata ARM: OMAP2+: Fix l2dis_3630 for rodata ARM: OMAP2+: Fix l2_inv_api_params for rodata ARM: OMAP2+: Fix wait_dll_lock_timed for rodata ARM: dts: at91: sama5d4ek: add phy address and IRQ for macb0 ARM: dts: at91: sama5d4 xplained: fix phy0 IRQ type ARM: dts: at91: sama5d4: fix instance id of DBGU ARM: dts: at91: sama5d4 xplained: properly mux phy interrupt ARM: dts: omap5-board-common: enable rtc and charging of backup battery ARM: dts: Fix omap5 PMIC control lines for RTC writes ARM: dts: Fix wl12xx missing clocks that cause hangs ARM: nomadik: fix up SD/MMC DT settings ARM: 8517/1: ICST: avoid arithmetic overflow in icst_hz() ARM: 8519/1: ICST: try other dividends than 1 arm64: mm: avoid calling apply_to_page_range on empty range ARM: mvebu: remove duplicated regulator definition in Armada 388 GP powerpc/ioda: Set "read" permission when "write" is set powerpc/powernv: Fix stale PE primary bus powerpc/eeh: Fix stale cached primary bus powerpc/eeh: Fix PE location code SUNRPC: Fixup socket wait for memory udf: Check output buffer length when converting name to CS0 udf: Prevent buffer overrun with multi-byte characters udf: limit the maximum number of indirect extents in a row pNFS/flexfiles: Fix an XDR encoding bug in layoutreturn nfs: Fix race in __update_open_stateid() pNFS/flexfiles: Fix an Oopsable typo in ff_mirror_match_fh() NFS: Fix attribute cache revalidation cifs: fix erroneous return value cifs_dbg() outputs an uninitialized buffer in cifs_readdir() cifs: fix race between call_async() and reconnect() cifs: Ratelimit kernel log messages iio: inkern: fix a NULL dereference on error iio: pressure: mpl115: fix temperature offset sign iio: light: acpi-als: Report data as processed iio: dac: mcp4725: set iio name property in sysfs iio: add IIO_TRIGGER dependency to STK8BA50 iio: add HAS_IOMEM dependency to VF610_ADC iio-light: Use a signed return type for ltr501_match_samp_freq() iio:adc:ti_am335x_adc Fix buffered mode by identifying as software buffer. iio: adis_buffer: Fix out-of-bounds memory access scsi: fix soft lockup in scsi_remove_target() on module removal SCSI: Add Marvell Console to VPD blacklist scsi_dh_rdac: always retry MODE SELECT on command lock violation drivers/scsi/sg.c: mark VMA as VM_IO to prevent migration SCSI: fix crashes in sd and sr runtime PM iscsi-target: Fix potential dead-lock during node acl delete scsi: add Synology to 1024 sector blacklist klist: fix starting point removed bug in klist iterators tracepoints: Do not trace when cpu is offline tracing: Fix freak link error caused by branch tracer perf tools: tracepoint_error() can receive e=NULL, robustify it tools lib traceevent: Fix output of %llu for 64 bit values read on 32 bit machines ptrace: use fsuid, fsgid, effective creds for fs access checks Btrfs: fix direct IO requests not reporting IO error to user space Btrfs: fix hang on extent buffer lock caused by the inode_paths ioctl Btrfs: fix page reading in extent_same ioctl leading to csum errors Btrfs: fix invalid page accesses in extent_same (dedup) ioctl btrfs: properly set the termination value of ctx->pos in readdir Revert "btrfs: clear PF_NOFREEZE in cleaner_kthread()" Btrfs: fix fitrim discarding device area reserved for boot loader's use btrfs: handle invalid num_stripes in sys_array ext4: don't read blocks from disk after extents being swapped ext4: fix potential integer overflow ext4: fix scheduling in atomic on group checksum failure serial: omap: Prevent DoS using unprivileged ioctl(TIOCSRS485) serial: 8250_pci: Add Intel Broadwell ports tty: Add support for PCIe WCH382 2S multi-IO card pty: make sure super_block is still valid in final /dev/tty close pty: fix possible use after free of tty->driver_data staging/speakup: Use tty_ldisc_ref() for paste kworker phy: twl4030-usb: Fix unbalanced pm_runtime_enable on module reload phy: twl4030-usb: Relase usb phy on unload ALSA: seq: Fix double port list deletion ALSA: seq: Fix leak of pool buffer at concurrent writes ALSA: pcm: Fix rwsem deadlock for non-atomic PCM stream ALSA: hda - Cancel probe work instead of flush at remove x86/mm: Fix vmalloc_fault() to handle large pages properly x86/uaccess/64: Handle the caching of 4-byte nocache copies properly in __copy_user_nocache() x86/uaccess/64: Make the __copy_user_nocache() assembly code more readable x86/mm/pat: Avoid truncation when converting cpa->numpages to address x86/mm: Fix types used in pgprot cacheability flags translations Linux 4.4.2 HID: multitouch: fix input mode switching on some Elan panels mm, vmstat: fix wrong WQ sleep when memory reclaim doesn't make any progress zsmalloc: fix migrate_zspage-zs_free race condition zram: don't call idr_remove() from zram_remove() zram: try vmalloc() after kmalloc() zram/zcomp: use GFP_NOIO to allocate streams rtlwifi: rtl8821ae: Fix 5G failure when EEPROM is incorrectly encoded rtlwifi: rtl8821ae: Fix errors in parameter initialization crypto: marvell/cesa - fix test in mv_cesa_dev_dma_init() crypto: atmel-sha - remove calls of clk_prepare() from atomic contexts crypto: atmel-sha - fix atmel_sha_remove() crypto: algif_skcipher - Do not set MAY_BACKLOG on the async path crypto: algif_skcipher - Do not dereference ctx without socket lock crypto: algif_skcipher - Do not assume that req is unchanged crypto: user - lock crypto_alg_list on alg dump EVM: Use crypto_memneq() for digest comparisons crypto: algif_hash - wait for crypto_ahash_init() to complete crypto: shash - Fix has_key setting crypto: chacha20-ssse3 - Align stack pointer to 64 bytes crypto: caam - make write transactions bufferable on PPC platforms crypto: algif_skcipher - sendmsg SG marking is off by one crypto: algif_skcipher - Load TX SG list after waiting crypto: crc32c - Fix crc32c soft dependency crypto: algif_skcipher - Fix race condition in skcipher_check_key crypto: algif_hash - Fix race condition in hash_check_key crypto: af_alg - Forbid bind(2) when nokey child sockets are present crypto: algif_skcipher - Remove custom release parent function crypto: algif_hash - Remove custom release parent function crypto: af_alg - Allow af_af_alg_release_parent to be called on nokey path ahci: Intel DNV device IDs SATA libata: disable forced PORTS_IMPL for >= AHCI 1.3 crypto: algif_skcipher - Add key check exception for cipher_null crypto: skcipher - Add crypto_skcipher_has_setkey crypto: algif_hash - Require setkey before accept(2) crypto: hash - Add crypto_ahash_has_setkey crypto: algif_skcipher - Add nokey compatibility path crypto: af_alg - Add nokey compatibility path crypto: af_alg - Fix socket double-free when accept fails crypto: af_alg - Disallow bind/setkey/... after accept(2) crypto: algif_skcipher - Require setkey before accept(2) sched: Fix crash in sched_init_numa() ext4 crypto: add missing locking for keyring_key access iommu/io-pgtable-arm: Ensure we free the final level on teardown tty: Fix unsafe ldisc reference via ioctl(TIOCGETD) tty: Retry failed reopen if tty teardown in-progress tty: Wait interruptibly for tty lock on reopen n_tty: Fix unsafe reference to "other" ldisc usb: xhci: apply XHCI_PME_STUCK_QUIRK to Intel Broxton-M platforms usb: xhci: handle both SSIC ports in PME stuck quirk usb: phy: msm: fix error handling in probe. usb: cdc-acm: send zero packet for intel 7260 modem usb: cdc-acm: handle unlinked urb in acm read callback USB: option: fix Cinterion AHxx enumeration USB: serial: option: Adding support for Telit LE922 USB: cp210x: add ID for IAI USB to RS485 adaptor USB: serial: ftdi_sio: add support for Yaesu SCU-18 cable usb: hub: do not clear BOS field during reset device USB: visor: fix null-deref at probe USB: serial: visor: fix crash on detecting device without write_urbs ASoC: rt5645: fix the shift bit of IN1 boost saa7134-alsa: Only frees registered sound cards ALSA: dummy: Implement timer backend switching more safely ALSA: hda - Fix bad dereference of jack object ALSA: hda - Fix speaker output from VAIO AiO machines Revert "ALSA: hda - Fix noise on Gigabyte Z170X mobo" ALSA: hda - Fix static checker warning in patch_hdmi.c ALSA: hda - Add fixup for Mac Mini 7,1 model ALSA: timer: Fix race between stop and interrupt ALSA: timer: Fix wrong instance passed to slave callbacks ALSA: timer: Fix race at concurrent reads ALSA: timer: Fix link corruption due to double start or stop ALSA: timer: Fix leftover link at closing ALSA: timer: Code cleanup ALSA: seq: Fix lockdep warnings due to double mutex locks ALSA: seq: Fix race at closing in virmidi driver ALSA: seq: Fix yet another races among ALSA timer accesses ASoC: dpcm: fix the BE state on hw_free ALSA: pcm: Fix potential deadlock in OSS emulation ALSA: hda/realtek - Support Dell headset mode for ALC225 ALSA: hda/realtek - Support headset mode for ALC225 ALSA: hda/realtek - New codec support of ALC225 ALSA: rawmidi: Fix race at copying & updating the position ALSA: rawmidi: Remove kernel WARNING for NULL user-space buffer check ALSA: rawmidi: Make snd_rawmidi_transmit() race-free ALSA: seq: Degrade the error message for too many opens ALSA: seq: Fix incorrect sanity check at snd_seq_oss_synth_cleanup() ALSA: dummy: Disable switching timer backend via sysfs ALSA: compress: Disable GET_CODEC_CAPS ioctl for some architectures ALSA: hda - disable dynamic clock gating on Broxton before reset ALSA: Add missing dependency on CONFIG_SND_TIMER ALSA: bebob: Use a signed return type for get_formation_index ALSA: usb-audio: avoid freeing umidi object twice ALSA: usb-audio: Add native DSD support for PS Audio NuWave DAC ALSA: usb-audio: Fix OPPO HA-1 vendor ID ALSA: usb-audio: Add quirk for Microsoft LifeCam HD-6000 ALSA: usb-audio: Fix TEAC UD-501/UD-503/NT-503 usb delay hrtimer: Handle remaining time proper for TIME_LOW_RES md/raid: only permit hot-add of compatible integrity profiles media: i2c: Don't export ir-kbd-i2c module alias parisc: Fix __ARCH_SI_PREAMBLE_SIZE parisc: Protect huge page pte changes with spinlocks printk: do cond_resched() between lines while outputting to consoles tracing/stacktrace: Show entire trace if passed in function not found tracing: Fix stacktrace skip depth in trace_buffer_unlock_commit_regs() PCI: Fix minimum allocation address overwrite PCI: host: Mark PCIe/PCI (MSI) IRQ cascade handlers as IRQF_NO_THREAD mtd: nand: assign reasonable default name for NAND drivers wlcore/wl12xx: spi: fix NULL pointer dereference (Oops) wlcore/wl12xx: spi: fix oops on firmware load ocfs2/dlm: clear refmap bit of recovery lock while doing local recovery cleanup ocfs2/dlm: ignore cleaning the migration mle that is inuse ALSA: hda - Implement loopback control switch for Realtek and other codecs block: fix bio splitting on max sectors base/platform: Fix platform drivers with no probe callback HID: usbhid: fix recursive deadlock ocfs2: NFS hangs in __ocfs2_cluster_lock due to race with ocfs2_unblock_lock block: split bios to max possible length NFSv4.1/pnfs: Fixup an lo->plh_block_lgets imbalance in layoutreturn crypto: sun4i-ss - add missing statesize Linux 4.4.1 arm64: kernel: fix architected PMU registers unconditional access arm64: kernel: enforce pmuserenr_el0 initialization and restore arm64: mm: ensure that the zero page is visible to the page table walker arm64: Clear out any singlestep state on a ptrace detach operation powerpc/module: Handle R_PPC64_ENTRY relocations scripts/recordmcount.pl: support data in text section on powerpc powerpc: Make {cmp}xchg* and their atomic_ versions fully ordered powerpc: Make value-returning atomics fully ordered powerpc/tm: Check for already reclaimed tasks batman-adv: Drop immediate orig_node free function batman-adv: Drop immediate batadv_hard_iface free function batman-adv: Drop immediate neigh_ifinfo free function batman-adv: Drop immediate batadv_neigh_node free function batman-adv: Drop immediate batadv_orig_ifinfo free function batman-adv: Avoid recursive call_rcu for batadv_nc_node batman-adv: Avoid recursive call_rcu for batadv_bla_claim team: Replace rcu_read_lock with a mutex in team_vlan_rx_kill_vid net/mlx5_core: Fix trimming down IRQ number bridge: fix lockdep addr_list_lock false positive splat ipv6: update skb->csum when CE mark is propagated net: bpf: reject invalid shifts phonet: properly unshare skbs in phonet_rcv() dwc_eth_qos: Fix dma address for multi-fragment skbs bonding: Prevent IPv6 link local address on enslaved devices net: preserve IP control block during GSO segmentation udp: disallow UFO for sockets with SO_NO_CHECK option net: pktgen: fix null ptr deref in skb allocation sched,cls_flower: set key address type when present tcp_yeah: don't set ssthresh below 2 ipv6: tcp: add rcu locking in tcp_v6_send_synack() net: sctp: prevent writes to cookie_hmac_alg from accessing invalid memory vxlan: fix test which detect duplicate vxlan iface unix: properly account for FDs passed over unix sockets xhci: refuse loading if nousb is used usb: core: lpm: fix usb3_hardware_lpm sysfs node USB: cp210x: add ID for ELV Marble Sound Board 1 rtlwifi: fix memory leak for USB device ASoC: compress: Fix compress device direction check ASoC: wm5110: Fix PGA clear when disabling DRE ALSA: timer: Handle disconnection more safely ALSA: hda - Flush the pending probe work at remove ALSA: hda - Fix missing module loading with model=generic option ALSA: hda - Fix bass pin fixup for ASUS N550JX ALSA: control: Avoid kernel warnings from tlv ioctl with numid 0 ALSA: hrtimer: Fix stall by hrtimer_cancel() ALSA: pcm: Fix snd_pcm_hw_params struct copy in compat mode ALSA: seq: Fix snd_seq_call_port_info_ioctl in compat mode ALSA: hda - Add fixup for Dell Latitidue E6540 ALSA: timer: Fix double unlink of active_list ALSA: timer: Fix race among timer ioctls ALSA: hda - fix the headset mic detection problem for a Dell laptop ALSA: timer: Harden slave timer list handling ALSA: usb-audio: Fix mixer ctl regression of Native Instrument devices ALSA: hda - Fix white noise on Dell Latitude E5550 ALSA: seq: Fix race at timer setup and close ALSA: usb-audio: Avoid calling usb_autopm_put_interface() at disconnect ALSA: seq: Fix missing NULL check at remove_events ioctl ALSA: hda - Fixup inverted internal mic for Lenovo E50-80 ALSA: usb: Add native DSD support for Oppo HA-1 x86/mm: Improve switch_mm() barrier comments x86/mm: Add barriers and document switch_mm()-vs-flush synchronization x86/boot: Double BOOT_HEAP_SIZE to 64KB x86/reboot/quirks: Add iMac10,1 to pci_reboot_dmi_table[] kvm: x86: Fix vmwrite to SECONDARY_VM_EXEC_CONTROL KVM: x86: correctly print #AC in traces KVM: x86: expose MSR_TSC_AUX to userspace x86/xen: don't reset vcpu_info on a cancelled suspend KEYS: Fix keyring ref leak in join_session_keyring() Conflicts: arch/arm64/kernel/perf_event.c drivers/scsi/sd.c sound/core/compress_offload.c Change-Id: I9f77fe42aaae249c24cd6e170202110ab1426878 Signed-off-by: Trilok Soni <tsoni@codeaurora.org>
2152 lines
56 KiB
C
2152 lines
56 KiB
C
/*
|
|
* SWIOTLB-based DMA API implementation
|
|
*
|
|
* Copyright (C) 2012 ARM Ltd.
|
|
* Author: Catalin Marinas <catalin.marinas@arm.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <linux/gfp.h>
|
|
#include <linux/acpi.h>
|
|
#include <linux/export.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/genalloc.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/dma-contiguous.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/iommu.h>
|
|
#include <linux/vmalloc.h>
|
|
#include <linux/swiotlb.h>
|
|
|
|
#include <asm/cacheflush.h>
|
|
#include <asm/tlbflush.h>
|
|
#include <linux/io.h>
|
|
#include <asm/dma-iommu.h>
|
|
|
|
#include "mm.h"
|
|
|
|
|
|
static pgprot_t __get_dma_pgprot(struct dma_attrs *attrs, pgprot_t prot,
|
|
bool coherent)
|
|
{
|
|
if (dma_get_attr(DMA_ATTR_STRONGLY_ORDERED, attrs))
|
|
return pgprot_noncached(prot);
|
|
else if (!coherent || dma_get_attr(DMA_ATTR_WRITE_COMBINE, attrs))
|
|
return pgprot_writecombine(prot);
|
|
return prot;
|
|
}
|
|
|
|
static struct gen_pool *atomic_pool;
|
|
#define NO_KERNEL_MAPPING_DUMMY 0x2222
|
|
#define DEFAULT_DMA_COHERENT_POOL_SIZE SZ_256K
|
|
static size_t atomic_pool_size = DEFAULT_DMA_COHERENT_POOL_SIZE;
|
|
|
|
static int __init early_coherent_pool(char *p)
|
|
{
|
|
atomic_pool_size = memparse(p, &p);
|
|
return 0;
|
|
}
|
|
early_param("coherent_pool", early_coherent_pool);
|
|
|
|
static void *__alloc_from_pool(size_t size, struct page **ret_page, gfp_t flags)
|
|
{
|
|
unsigned long val;
|
|
void *ptr = NULL;
|
|
|
|
if (!atomic_pool) {
|
|
WARN(1, "coherent pool not initialised!\n");
|
|
return NULL;
|
|
}
|
|
|
|
val = gen_pool_alloc(atomic_pool, size);
|
|
if (val) {
|
|
phys_addr_t phys = gen_pool_virt_to_phys(atomic_pool, val);
|
|
|
|
*ret_page = phys_to_page(phys);
|
|
ptr = (void *)val;
|
|
memset(ptr, 0, size);
|
|
}
|
|
|
|
return ptr;
|
|
}
|
|
|
|
static bool __in_atomic_pool(void *start, size_t size)
|
|
{
|
|
return addr_in_gen_pool(atomic_pool, (unsigned long)start, size);
|
|
}
|
|
|
|
static int __free_from_pool(void *start, size_t size)
|
|
{
|
|
if (!__in_atomic_pool(start, size))
|
|
return 0;
|
|
|
|
gen_pool_free(atomic_pool, (unsigned long)start, size);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int __dma_update_pte(pte_t *pte, pgtable_t token, unsigned long addr,
|
|
void *data)
|
|
{
|
|
struct page *page = virt_to_page(addr);
|
|
pgprot_t prot = *(pgprot_t *)data;
|
|
|
|
set_pte(pte, mk_pte(page, prot));
|
|
return 0;
|
|
}
|
|
|
|
static int __dma_clear_pte(pte_t *pte, pgtable_t token, unsigned long addr,
|
|
void *data)
|
|
{
|
|
pte_clear(&init_mm, addr, pte);
|
|
return 0;
|
|
}
|
|
|
|
static void __dma_remap(struct page *page, size_t size, pgprot_t prot,
|
|
bool no_kernel_map)
|
|
{
|
|
unsigned long start = (unsigned long) page_address(page);
|
|
unsigned end = start + size;
|
|
int (*func)(pte_t *pte, pgtable_t token, unsigned long addr,
|
|
void *data);
|
|
|
|
if (no_kernel_map)
|
|
func = __dma_clear_pte;
|
|
else
|
|
func = __dma_update_pte;
|
|
|
|
apply_to_page_range(&init_mm, start, size, func, &prot);
|
|
mb();
|
|
flush_tlb_kernel_range(start, end);
|
|
}
|
|
|
|
static void *__dma_alloc_coherent(struct device *dev, size_t size,
|
|
dma_addr_t *dma_handle, gfp_t flags,
|
|
struct dma_attrs *attrs)
|
|
{
|
|
if (dev == NULL) {
|
|
WARN_ONCE(1, "Use an actual device structure for DMA allocation\n");
|
|
return NULL;
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_ZONE_DMA) &&
|
|
dev->coherent_dma_mask <= DMA_BIT_MASK(32))
|
|
flags |= GFP_DMA;
|
|
if (dev_get_cma_area(dev) && gfpflags_allow_blocking(flags)) {
|
|
struct page *page;
|
|
void *addr;
|
|
|
|
page = dma_alloc_from_contiguous(dev, size >> PAGE_SHIFT,
|
|
get_order(size));
|
|
if (!page)
|
|
return NULL;
|
|
|
|
*dma_handle = phys_to_dma(dev, page_to_phys(page));
|
|
addr = page_address(page);
|
|
memset(addr, 0, size);
|
|
|
|
if (dma_get_attr(DMA_ATTR_NO_KERNEL_MAPPING, attrs) ||
|
|
dma_get_attr(DMA_ATTR_STRONGLY_ORDERED, attrs)) {
|
|
/*
|
|
* flush the caches here because we can't later
|
|
*/
|
|
__dma_flush_range(addr, addr + size);
|
|
__dma_remap(page, size, 0, true);
|
|
}
|
|
|
|
return addr;
|
|
} else {
|
|
return swiotlb_alloc_coherent(dev, size, dma_handle, flags);
|
|
}
|
|
}
|
|
|
|
static void __dma_free_coherent(struct device *dev, size_t size,
|
|
void *vaddr, dma_addr_t dma_handle,
|
|
struct dma_attrs *attrs)
|
|
{
|
|
bool freed;
|
|
phys_addr_t paddr = dma_to_phys(dev, dma_handle);
|
|
|
|
size = PAGE_ALIGN(size);
|
|
if (dev == NULL) {
|
|
WARN_ONCE(1, "Use an actual device structure for DMA allocation\n");
|
|
return;
|
|
}
|
|
|
|
if (dma_get_attr(DMA_ATTR_NO_KERNEL_MAPPING, attrs) ||
|
|
dma_get_attr(DMA_ATTR_STRONGLY_ORDERED, attrs))
|
|
__dma_remap(phys_to_page(paddr), size, PAGE_KERNEL, false);
|
|
|
|
freed = dma_release_from_contiguous(dev,
|
|
phys_to_page(paddr),
|
|
size >> PAGE_SHIFT);
|
|
if (!freed)
|
|
swiotlb_free_coherent(dev, size, vaddr, dma_handle);
|
|
}
|
|
|
|
static void *__dma_alloc(struct device *dev, size_t size,
|
|
dma_addr_t *dma_handle, gfp_t flags,
|
|
struct dma_attrs *attrs)
|
|
{
|
|
struct page *page;
|
|
void *ptr, *coherent_ptr;
|
|
bool coherent = is_device_dma_coherent(dev);
|
|
|
|
size = PAGE_ALIGN(size);
|
|
|
|
if (!coherent && !gfpflags_allow_blocking(flags)) {
|
|
struct page *page = NULL;
|
|
void *addr = __alloc_from_pool(size, &page, flags);
|
|
|
|
if (addr)
|
|
*dma_handle = phys_to_dma(dev, page_to_phys(page));
|
|
|
|
return addr;
|
|
}
|
|
|
|
ptr = __dma_alloc_coherent(dev, size, dma_handle, flags, attrs);
|
|
if (!ptr)
|
|
goto no_mem;
|
|
|
|
/* no need for non-cacheable mapping if coherent */
|
|
if (coherent)
|
|
return ptr;
|
|
|
|
if (dma_get_attr(DMA_ATTR_NO_KERNEL_MAPPING, attrs)) {
|
|
coherent_ptr = (void *)NO_KERNEL_MAPPING_DUMMY;
|
|
} else {
|
|
if (!dma_get_attr(DMA_ATTR_STRONGLY_ORDERED, attrs))
|
|
/* remove any dirty cache lines on the kernel alias */
|
|
__dma_flush_range(ptr, ptr + size);
|
|
|
|
/* create a coherent mapping */
|
|
page = virt_to_page(ptr);
|
|
coherent_ptr = dma_common_contiguous_remap(page, size, VM_USERMAP,
|
|
__get_dma_pgprot(attrs,
|
|
__pgprot(PROT_NORMAL_NC), false),
|
|
NULL);
|
|
if (!coherent_ptr)
|
|
goto no_map;
|
|
}
|
|
return coherent_ptr;
|
|
|
|
no_map:
|
|
__dma_free_coherent(dev, size, ptr, *dma_handle, attrs);
|
|
no_mem:
|
|
*dma_handle = DMA_ERROR_CODE;
|
|
return NULL;
|
|
}
|
|
|
|
static void __dma_free(struct device *dev, size_t size,
|
|
void *vaddr, dma_addr_t dma_handle,
|
|
struct dma_attrs *attrs)
|
|
{
|
|
void *swiotlb_addr = phys_to_virt(dma_to_phys(dev, dma_handle));
|
|
|
|
size = PAGE_ALIGN(size);
|
|
|
|
if (!is_device_dma_coherent(dev)) {
|
|
if (__free_from_pool(vaddr, size))
|
|
return;
|
|
if (!dma_get_attr(DMA_ATTR_NO_KERNEL_MAPPING, attrs))
|
|
vunmap(vaddr);
|
|
}
|
|
__dma_free_coherent(dev, size, swiotlb_addr, dma_handle, attrs);
|
|
}
|
|
|
|
static dma_addr_t __swiotlb_map_page(struct device *dev, struct page *page,
|
|
unsigned long offset, size_t size,
|
|
enum dma_data_direction dir,
|
|
struct dma_attrs *attrs)
|
|
{
|
|
dma_addr_t dev_addr;
|
|
|
|
dev_addr = swiotlb_map_page(dev, page, offset, size, dir, attrs);
|
|
if (!is_device_dma_coherent(dev))
|
|
__dma_map_area(phys_to_virt(dma_to_phys(dev, dev_addr)), size, dir);
|
|
|
|
return dev_addr;
|
|
}
|
|
|
|
|
|
static void __swiotlb_unmap_page(struct device *dev, dma_addr_t dev_addr,
|
|
size_t size, enum dma_data_direction dir,
|
|
struct dma_attrs *attrs)
|
|
{
|
|
if (!is_device_dma_coherent(dev))
|
|
__dma_unmap_area(phys_to_virt(dma_to_phys(dev, dev_addr)), size, dir);
|
|
swiotlb_unmap_page(dev, dev_addr, size, dir, attrs);
|
|
}
|
|
|
|
static int __swiotlb_map_sg_attrs(struct device *dev, struct scatterlist *sgl,
|
|
int nelems, enum dma_data_direction dir,
|
|
struct dma_attrs *attrs)
|
|
{
|
|
struct scatterlist *sg;
|
|
int i, ret;
|
|
|
|
ret = swiotlb_map_sg_attrs(dev, sgl, nelems, dir, attrs);
|
|
if (!is_device_dma_coherent(dev))
|
|
for_each_sg(sgl, sg, ret, i)
|
|
__dma_map_area(phys_to_virt(dma_to_phys(dev, sg->dma_address)),
|
|
sg->length, dir);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void __swiotlb_unmap_sg_attrs(struct device *dev,
|
|
struct scatterlist *sgl, int nelems,
|
|
enum dma_data_direction dir,
|
|
struct dma_attrs *attrs)
|
|
{
|
|
struct scatterlist *sg;
|
|
int i;
|
|
|
|
if (!is_device_dma_coherent(dev))
|
|
for_each_sg(sgl, sg, nelems, i)
|
|
__dma_unmap_area(phys_to_virt(dma_to_phys(dev, sg->dma_address)),
|
|
sg->length, dir);
|
|
swiotlb_unmap_sg_attrs(dev, sgl, nelems, dir, attrs);
|
|
}
|
|
|
|
static void __swiotlb_sync_single_for_cpu(struct device *dev,
|
|
dma_addr_t dev_addr, size_t size,
|
|
enum dma_data_direction dir)
|
|
{
|
|
if (!is_device_dma_coherent(dev))
|
|
__dma_unmap_area(phys_to_virt(dma_to_phys(dev, dev_addr)), size, dir);
|
|
swiotlb_sync_single_for_cpu(dev, dev_addr, size, dir);
|
|
}
|
|
|
|
static void __swiotlb_sync_single_for_device(struct device *dev,
|
|
dma_addr_t dev_addr, size_t size,
|
|
enum dma_data_direction dir)
|
|
{
|
|
swiotlb_sync_single_for_device(dev, dev_addr, size, dir);
|
|
if (!is_device_dma_coherent(dev))
|
|
__dma_map_area(phys_to_virt(dma_to_phys(dev, dev_addr)), size, dir);
|
|
}
|
|
|
|
static void __swiotlb_sync_sg_for_cpu(struct device *dev,
|
|
struct scatterlist *sgl, int nelems,
|
|
enum dma_data_direction dir)
|
|
{
|
|
struct scatterlist *sg;
|
|
int i;
|
|
|
|
if (!is_device_dma_coherent(dev))
|
|
for_each_sg(sgl, sg, nelems, i)
|
|
__dma_unmap_area(phys_to_virt(dma_to_phys(dev, sg->dma_address)),
|
|
sg->length, dir);
|
|
swiotlb_sync_sg_for_cpu(dev, sgl, nelems, dir);
|
|
}
|
|
|
|
static void __swiotlb_sync_sg_for_device(struct device *dev,
|
|
struct scatterlist *sgl, int nelems,
|
|
enum dma_data_direction dir)
|
|
{
|
|
struct scatterlist *sg;
|
|
int i;
|
|
|
|
swiotlb_sync_sg_for_device(dev, sgl, nelems, dir);
|
|
if (!is_device_dma_coherent(dev))
|
|
for_each_sg(sgl, sg, nelems, i)
|
|
__dma_map_area(phys_to_virt(dma_to_phys(dev, sg->dma_address)),
|
|
sg->length, dir);
|
|
}
|
|
|
|
static int __swiotlb_mmap(struct device *dev,
|
|
struct vm_area_struct *vma,
|
|
void *cpu_addr, dma_addr_t dma_addr, size_t size,
|
|
struct dma_attrs *attrs)
|
|
{
|
|
int ret = -ENXIO;
|
|
unsigned long nr_vma_pages = (vma->vm_end - vma->vm_start) >>
|
|
PAGE_SHIFT;
|
|
unsigned long nr_pages = PAGE_ALIGN(size) >> PAGE_SHIFT;
|
|
unsigned long pfn = dma_to_phys(dev, dma_addr) >> PAGE_SHIFT;
|
|
unsigned long off = vma->vm_pgoff;
|
|
|
|
vma->vm_page_prot = __get_dma_pgprot(attrs, vma->vm_page_prot,
|
|
is_device_dma_coherent(dev));
|
|
|
|
if (dma_mmap_from_coherent(dev, vma, cpu_addr, size, &ret))
|
|
return ret;
|
|
|
|
if (off < nr_pages && nr_vma_pages <= (nr_pages - off)) {
|
|
ret = remap_pfn_range(vma, vma->vm_start,
|
|
pfn + off,
|
|
vma->vm_end - vma->vm_start,
|
|
vma->vm_page_prot);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int __swiotlb_get_sgtable(struct device *dev, struct sg_table *sgt,
|
|
void *cpu_addr, dma_addr_t handle, size_t size,
|
|
struct dma_attrs *attrs)
|
|
{
|
|
int ret = sg_alloc_table(sgt, 1, GFP_KERNEL);
|
|
|
|
if (!ret)
|
|
sg_set_page(sgt->sgl, phys_to_page(dma_to_phys(dev, handle)),
|
|
PAGE_ALIGN(size), 0);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void *arm64_dma_remap(struct device *dev, void *cpu_addr,
|
|
dma_addr_t handle, size_t size,
|
|
struct dma_attrs *attrs)
|
|
{
|
|
struct page *page = phys_to_page(dma_to_phys(dev, handle));
|
|
pgprot_t prot = __get_dma_pgprot(attrs, PAGE_KERNEL, false);
|
|
unsigned long offset = handle & ~PAGE_MASK;
|
|
struct vm_struct *area;
|
|
unsigned long addr;
|
|
|
|
size = PAGE_ALIGN(size + offset);
|
|
|
|
/*
|
|
* DMA allocation can be mapped to user space, so lets
|
|
* set VM_USERMAP flags too.
|
|
*/
|
|
area = get_vm_area(size, VM_USERMAP);
|
|
if (!area)
|
|
return NULL;
|
|
|
|
addr = (unsigned long)area->addr;
|
|
area->phys_addr = __pfn_to_phys(page_to_pfn(page));
|
|
|
|
if (ioremap_page_range(addr, addr + size, area->phys_addr, prot)) {
|
|
vunmap((void *)addr);
|
|
return NULL;
|
|
}
|
|
return (void *)addr + offset;
|
|
}
|
|
|
|
static void arm64_dma_unremap(struct device *dev, void *remapped_addr,
|
|
size_t size)
|
|
{
|
|
struct vm_struct *area;
|
|
|
|
remapped_addr = (void *)((unsigned long)remapped_addr & PAGE_MASK);
|
|
|
|
area = find_vm_area(remapped_addr);
|
|
if (!area) {
|
|
WARN(1, "trying to free invalid coherent area: %p\n",
|
|
remapped_addr);
|
|
return;
|
|
}
|
|
vunmap(remapped_addr);
|
|
}
|
|
|
|
static struct dma_map_ops swiotlb_dma_ops = {
|
|
.alloc = __dma_alloc,
|
|
.free = __dma_free,
|
|
.mmap = __swiotlb_mmap,
|
|
.get_sgtable = __swiotlb_get_sgtable,
|
|
.map_page = __swiotlb_map_page,
|
|
.unmap_page = __swiotlb_unmap_page,
|
|
.map_sg = __swiotlb_map_sg_attrs,
|
|
.unmap_sg = __swiotlb_unmap_sg_attrs,
|
|
.sync_single_for_cpu = __swiotlb_sync_single_for_cpu,
|
|
.sync_single_for_device = __swiotlb_sync_single_for_device,
|
|
.sync_sg_for_cpu = __swiotlb_sync_sg_for_cpu,
|
|
.sync_sg_for_device = __swiotlb_sync_sg_for_device,
|
|
.dma_supported = swiotlb_dma_supported,
|
|
.mapping_error = swiotlb_dma_mapping_error,
|
|
.remap = arm64_dma_remap,
|
|
.unremap = arm64_dma_unremap,
|
|
};
|
|
|
|
static int __init atomic_pool_init(void)
|
|
{
|
|
pgprot_t prot = __pgprot(PROT_NORMAL_NC);
|
|
unsigned long nr_pages = atomic_pool_size >> PAGE_SHIFT;
|
|
struct page *page;
|
|
void *addr;
|
|
unsigned int pool_size_order = get_order(atomic_pool_size);
|
|
|
|
if (dev_get_cma_area(NULL))
|
|
page = dma_alloc_from_contiguous(NULL, nr_pages,
|
|
pool_size_order);
|
|
else
|
|
page = alloc_pages(GFP_DMA, pool_size_order);
|
|
|
|
if (page) {
|
|
int ret;
|
|
void *page_addr = page_address(page);
|
|
|
|
memset(page_addr, 0, atomic_pool_size);
|
|
__dma_flush_range(page_addr, page_addr + atomic_pool_size);
|
|
|
|
atomic_pool = gen_pool_create(PAGE_SHIFT, -1);
|
|
if (!atomic_pool)
|
|
goto free_page;
|
|
|
|
addr = dma_common_contiguous_remap(page, atomic_pool_size,
|
|
VM_USERMAP, prot, atomic_pool_init);
|
|
|
|
if (!addr)
|
|
goto destroy_genpool;
|
|
|
|
ret = gen_pool_add_virt(atomic_pool, (unsigned long)addr,
|
|
page_to_phys(page),
|
|
atomic_pool_size, -1);
|
|
if (ret)
|
|
goto remove_mapping;
|
|
|
|
gen_pool_set_algo(atomic_pool,
|
|
gen_pool_first_fit_order_align,
|
|
(void *)PAGE_SHIFT);
|
|
|
|
pr_info("DMA: preallocated %zu KiB pool for atomic allocations\n",
|
|
atomic_pool_size / 1024);
|
|
return 0;
|
|
}
|
|
goto out;
|
|
|
|
remove_mapping:
|
|
dma_common_free_remap(addr, atomic_pool_size, VM_USERMAP, true);
|
|
destroy_genpool:
|
|
gen_pool_destroy(atomic_pool);
|
|
atomic_pool = NULL;
|
|
free_page:
|
|
if (!dma_release_from_contiguous(NULL, page, nr_pages))
|
|
__free_pages(page, pool_size_order);
|
|
out:
|
|
pr_err("DMA: failed to allocate %zu KiB pool for atomic coherent allocation\n",
|
|
atomic_pool_size / 1024);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/********************************************
|
|
* The following APIs are for dummy DMA ops *
|
|
********************************************/
|
|
|
|
static void *__dummy_alloc(struct device *dev, size_t size,
|
|
dma_addr_t *dma_handle, gfp_t flags,
|
|
struct dma_attrs *attrs)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
static void __dummy_free(struct device *dev, size_t size,
|
|
void *vaddr, dma_addr_t dma_handle,
|
|
struct dma_attrs *attrs)
|
|
{
|
|
}
|
|
|
|
static int __dummy_mmap(struct device *dev,
|
|
struct vm_area_struct *vma,
|
|
void *cpu_addr, dma_addr_t dma_addr, size_t size,
|
|
struct dma_attrs *attrs)
|
|
{
|
|
return -ENXIO;
|
|
}
|
|
|
|
static dma_addr_t __dummy_map_page(struct device *dev, struct page *page,
|
|
unsigned long offset, size_t size,
|
|
enum dma_data_direction dir,
|
|
struct dma_attrs *attrs)
|
|
{
|
|
return DMA_ERROR_CODE;
|
|
}
|
|
|
|
static void __dummy_unmap_page(struct device *dev, dma_addr_t dev_addr,
|
|
size_t size, enum dma_data_direction dir,
|
|
struct dma_attrs *attrs)
|
|
{
|
|
}
|
|
|
|
static int __dummy_map_sg(struct device *dev, struct scatterlist *sgl,
|
|
int nelems, enum dma_data_direction dir,
|
|
struct dma_attrs *attrs)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static void __dummy_unmap_sg(struct device *dev,
|
|
struct scatterlist *sgl, int nelems,
|
|
enum dma_data_direction dir,
|
|
struct dma_attrs *attrs)
|
|
{
|
|
}
|
|
|
|
static void __dummy_sync_single(struct device *dev,
|
|
dma_addr_t dev_addr, size_t size,
|
|
enum dma_data_direction dir)
|
|
{
|
|
}
|
|
|
|
static void __dummy_sync_sg(struct device *dev,
|
|
struct scatterlist *sgl, int nelems,
|
|
enum dma_data_direction dir)
|
|
{
|
|
}
|
|
|
|
static int __dummy_mapping_error(struct device *hwdev, dma_addr_t dma_addr)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
static int __dummy_dma_supported(struct device *hwdev, u64 mask)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
struct dma_map_ops dummy_dma_ops = {
|
|
.alloc = __dummy_alloc,
|
|
.free = __dummy_free,
|
|
.mmap = __dummy_mmap,
|
|
.map_page = __dummy_map_page,
|
|
.unmap_page = __dummy_unmap_page,
|
|
.map_sg = __dummy_map_sg,
|
|
.unmap_sg = __dummy_unmap_sg,
|
|
.sync_single_for_cpu = __dummy_sync_single,
|
|
.sync_single_for_device = __dummy_sync_single,
|
|
.sync_sg_for_cpu = __dummy_sync_sg,
|
|
.sync_sg_for_device = __dummy_sync_sg,
|
|
.mapping_error = __dummy_mapping_error,
|
|
.dma_supported = __dummy_dma_supported,
|
|
};
|
|
EXPORT_SYMBOL(dummy_dma_ops);
|
|
|
|
static int __init arm64_dma_init(void)
|
|
{
|
|
return atomic_pool_init();
|
|
}
|
|
arch_initcall(arm64_dma_init);
|
|
|
|
#define PREALLOC_DMA_DEBUG_ENTRIES 4096
|
|
|
|
static int __init dma_debug_do_init(void)
|
|
{
|
|
dma_debug_init(PREALLOC_DMA_DEBUG_ENTRIES);
|
|
return 0;
|
|
}
|
|
fs_initcall(dma_debug_do_init);
|
|
|
|
|
|
#ifdef CONFIG_IOMMU_DMA
|
|
#include <linux/dma-iommu.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/amba/bus.h>
|
|
|
|
/* Thankfully, all cache ops are by VA so we can ignore phys here */
|
|
static void flush_page(struct device *dev, const void *virt, phys_addr_t phys)
|
|
{
|
|
__dma_flush_range(virt, virt + PAGE_SIZE);
|
|
}
|
|
|
|
static void *__iommu_alloc_attrs(struct device *dev, size_t size,
|
|
dma_addr_t *handle, gfp_t gfp,
|
|
struct dma_attrs *attrs)
|
|
{
|
|
bool coherent = is_device_dma_coherent(dev);
|
|
int ioprot = dma_direction_to_prot(DMA_BIDIRECTIONAL, coherent);
|
|
size_t iosize = size;
|
|
void *addr;
|
|
|
|
if (WARN(!dev, "cannot create IOMMU mapping for unknown device\n"))
|
|
return NULL;
|
|
|
|
size = PAGE_ALIGN(size);
|
|
|
|
/*
|
|
* Some drivers rely on this, and we probably don't want the
|
|
* possibility of stale kernel data being read by devices anyway.
|
|
*/
|
|
gfp |= __GFP_ZERO;
|
|
|
|
if (gfpflags_allow_blocking(gfp)) {
|
|
struct page **pages;
|
|
pgprot_t prot = __get_dma_pgprot(attrs, PAGE_KERNEL, coherent);
|
|
|
|
pages = iommu_dma_alloc(dev, iosize, gfp, ioprot, handle,
|
|
flush_page);
|
|
if (!pages)
|
|
return NULL;
|
|
|
|
addr = dma_common_pages_remap(pages, size, VM_USERMAP, prot,
|
|
__builtin_return_address(0));
|
|
if (!addr)
|
|
iommu_dma_free(dev, pages, iosize, handle);
|
|
} else {
|
|
struct page *page;
|
|
/*
|
|
* In atomic context we can't remap anything, so we'll only
|
|
* get the virtually contiguous buffer we need by way of a
|
|
* physically contiguous allocation.
|
|
*/
|
|
if (coherent) {
|
|
page = alloc_pages(gfp, get_order(size));
|
|
addr = page ? page_address(page) : NULL;
|
|
} else {
|
|
addr = __alloc_from_pool(size, &page, gfp);
|
|
}
|
|
if (!addr)
|
|
return NULL;
|
|
|
|
*handle = iommu_dma_map_page(dev, page, 0, iosize, ioprot);
|
|
if (iommu_dma_mapping_error(dev, *handle)) {
|
|
if (coherent)
|
|
__free_pages(page, get_order(size));
|
|
else
|
|
__free_from_pool(addr, size);
|
|
addr = NULL;
|
|
}
|
|
}
|
|
return addr;
|
|
}
|
|
|
|
static void __iommu_free_attrs(struct device *dev, size_t size, void *cpu_addr,
|
|
dma_addr_t handle, struct dma_attrs *attrs)
|
|
{
|
|
size_t iosize = size;
|
|
|
|
size = PAGE_ALIGN(size);
|
|
/*
|
|
* @cpu_addr will be one of 3 things depending on how it was allocated:
|
|
* - A remapped array of pages from iommu_dma_alloc(), for all
|
|
* non-atomic allocations.
|
|
* - A non-cacheable alias from the atomic pool, for atomic
|
|
* allocations by non-coherent devices.
|
|
* - A normal lowmem address, for atomic allocations by
|
|
* coherent devices.
|
|
* Hence how dodgy the below logic looks...
|
|
*/
|
|
if (__in_atomic_pool(cpu_addr, size)) {
|
|
iommu_dma_unmap_page(dev, handle, iosize, 0, NULL);
|
|
__free_from_pool(cpu_addr, size);
|
|
} else if (is_vmalloc_addr(cpu_addr)){
|
|
struct vm_struct *area = find_vm_area(cpu_addr);
|
|
|
|
if (WARN_ON(!area || !area->pages))
|
|
return;
|
|
iommu_dma_free(dev, area->pages, iosize, &handle);
|
|
dma_common_free_remap(cpu_addr, size, VM_USERMAP, true);
|
|
} else {
|
|
iommu_dma_unmap_page(dev, handle, iosize, 0, NULL);
|
|
__free_pages(virt_to_page(cpu_addr), get_order(size));
|
|
}
|
|
}
|
|
|
|
static int __iommu_mmap_attrs(struct device *dev, struct vm_area_struct *vma,
|
|
void *cpu_addr, dma_addr_t dma_addr, size_t size,
|
|
struct dma_attrs *attrs)
|
|
{
|
|
struct vm_struct *area;
|
|
int ret;
|
|
|
|
vma->vm_page_prot = __get_dma_pgprot(attrs, vma->vm_page_prot,
|
|
is_device_dma_coherent(dev));
|
|
|
|
if (dma_mmap_from_coherent(dev, vma, cpu_addr, size, &ret))
|
|
return ret;
|
|
|
|
area = find_vm_area(cpu_addr);
|
|
if (WARN_ON(!area || !area->pages))
|
|
return -ENXIO;
|
|
|
|
return iommu_dma_mmap(area->pages, size, vma);
|
|
}
|
|
|
|
static int __iommu_get_sgtable(struct device *dev, struct sg_table *sgt,
|
|
void *cpu_addr, dma_addr_t dma_addr,
|
|
size_t size, struct dma_attrs *attrs)
|
|
{
|
|
unsigned int count = PAGE_ALIGN(size) >> PAGE_SHIFT;
|
|
struct vm_struct *area = find_vm_area(cpu_addr);
|
|
|
|
if (WARN_ON(!area || !area->pages))
|
|
return -ENXIO;
|
|
|
|
return sg_alloc_table_from_pages(sgt, area->pages, count, 0, size,
|
|
GFP_KERNEL);
|
|
}
|
|
|
|
static void __iommu_sync_single_for_cpu(struct device *dev,
|
|
dma_addr_t dev_addr, size_t size,
|
|
enum dma_data_direction dir)
|
|
{
|
|
phys_addr_t phys;
|
|
|
|
if (is_device_dma_coherent(dev))
|
|
return;
|
|
|
|
phys = iommu_iova_to_phys(iommu_get_domain_for_dev(dev), dev_addr);
|
|
__dma_unmap_area(phys_to_virt(phys), size, dir);
|
|
}
|
|
|
|
static void __iommu_sync_single_for_device(struct device *dev,
|
|
dma_addr_t dev_addr, size_t size,
|
|
enum dma_data_direction dir)
|
|
{
|
|
phys_addr_t phys;
|
|
|
|
if (is_device_dma_coherent(dev))
|
|
return;
|
|
|
|
phys = iommu_iova_to_phys(iommu_get_domain_for_dev(dev), dev_addr);
|
|
__dma_map_area(phys_to_virt(phys), size, dir);
|
|
}
|
|
|
|
static dma_addr_t __iommu_map_page(struct device *dev, struct page *page,
|
|
unsigned long offset, size_t size,
|
|
enum dma_data_direction dir,
|
|
struct dma_attrs *attrs)
|
|
{
|
|
bool coherent = is_device_dma_coherent(dev);
|
|
int prot = dma_direction_to_prot(dir, coherent);
|
|
dma_addr_t dev_addr = iommu_dma_map_page(dev, page, offset, size, prot);
|
|
|
|
if (!iommu_dma_mapping_error(dev, dev_addr) &&
|
|
!dma_get_attr(DMA_ATTR_SKIP_CPU_SYNC, attrs))
|
|
__iommu_sync_single_for_device(dev, dev_addr, size, dir);
|
|
|
|
return dev_addr;
|
|
}
|
|
|
|
static void __iommu_unmap_page(struct device *dev, dma_addr_t dev_addr,
|
|
size_t size, enum dma_data_direction dir,
|
|
struct dma_attrs *attrs)
|
|
{
|
|
if (!dma_get_attr(DMA_ATTR_SKIP_CPU_SYNC, attrs))
|
|
__iommu_sync_single_for_cpu(dev, dev_addr, size, dir);
|
|
|
|
iommu_dma_unmap_page(dev, dev_addr, size, dir, attrs);
|
|
}
|
|
|
|
static void __iommu_sync_sg_for_cpu(struct device *dev,
|
|
struct scatterlist *sgl, int nelems,
|
|
enum dma_data_direction dir)
|
|
{
|
|
struct scatterlist *sg;
|
|
int i;
|
|
|
|
if (is_device_dma_coherent(dev))
|
|
return;
|
|
|
|
for_each_sg(sgl, sg, nelems, i)
|
|
__dma_unmap_area(sg_virt(sg), sg->length, dir);
|
|
}
|
|
|
|
static void __iommu_sync_sg_for_device(struct device *dev,
|
|
struct scatterlist *sgl, int nelems,
|
|
enum dma_data_direction dir)
|
|
{
|
|
struct scatterlist *sg;
|
|
int i;
|
|
|
|
if (is_device_dma_coherent(dev))
|
|
return;
|
|
|
|
for_each_sg(sgl, sg, nelems, i)
|
|
__dma_map_area(sg_virt(sg), sg->length, dir);
|
|
}
|
|
|
|
static int __iommu_map_sg_attrs(struct device *dev, struct scatterlist *sgl,
|
|
int nelems, enum dma_data_direction dir,
|
|
struct dma_attrs *attrs)
|
|
{
|
|
bool coherent = is_device_dma_coherent(dev);
|
|
|
|
if (!dma_get_attr(DMA_ATTR_SKIP_CPU_SYNC, attrs))
|
|
__iommu_sync_sg_for_device(dev, sgl, nelems, dir);
|
|
|
|
return iommu_dma_map_sg(dev, sgl, nelems,
|
|
dma_direction_to_prot(dir, coherent));
|
|
}
|
|
|
|
static void __iommu_unmap_sg_attrs(struct device *dev,
|
|
struct scatterlist *sgl, int nelems,
|
|
enum dma_data_direction dir,
|
|
struct dma_attrs *attrs)
|
|
{
|
|
if (!dma_get_attr(DMA_ATTR_SKIP_CPU_SYNC, attrs))
|
|
__iommu_sync_sg_for_cpu(dev, sgl, nelems, dir);
|
|
|
|
iommu_dma_unmap_sg(dev, sgl, nelems, dir, attrs);
|
|
}
|
|
|
|
static struct dma_map_ops iommu_dma_ops = {
|
|
.alloc = __iommu_alloc_attrs,
|
|
.free = __iommu_free_attrs,
|
|
.mmap = __iommu_mmap_attrs,
|
|
.get_sgtable = __iommu_get_sgtable,
|
|
.map_page = __iommu_map_page,
|
|
.unmap_page = __iommu_unmap_page,
|
|
.map_sg = __iommu_map_sg_attrs,
|
|
.unmap_sg = __iommu_unmap_sg_attrs,
|
|
.sync_single_for_cpu = __iommu_sync_single_for_cpu,
|
|
.sync_single_for_device = __iommu_sync_single_for_device,
|
|
.sync_sg_for_cpu = __iommu_sync_sg_for_cpu,
|
|
.sync_sg_for_device = __iommu_sync_sg_for_device,
|
|
.dma_supported = iommu_dma_supported,
|
|
.mapping_error = iommu_dma_mapping_error,
|
|
};
|
|
|
|
/*
|
|
* TODO: Right now __iommu_setup_dma_ops() gets called too early to do
|
|
* everything it needs to - the device is only partially created and the
|
|
* IOMMU driver hasn't seen it yet, so it can't have a group. Thus we
|
|
* need this delayed attachment dance. Once IOMMU probe ordering is sorted
|
|
* to move the arch_setup_dma_ops() call later, all the notifier bits below
|
|
* become unnecessary, and will go away.
|
|
*/
|
|
struct iommu_dma_notifier_data {
|
|
struct list_head list;
|
|
struct device *dev;
|
|
const struct iommu_ops *ops;
|
|
u64 dma_base;
|
|
u64 size;
|
|
};
|
|
static LIST_HEAD(iommu_dma_masters);
|
|
static DEFINE_MUTEX(iommu_dma_notifier_lock);
|
|
|
|
/*
|
|
* Temporarily "borrow" a domain feature flag to to tell if we had to resort
|
|
* to creating our own domain here, in case we need to clean it up again.
|
|
*/
|
|
#define __IOMMU_DOMAIN_FAKE_DEFAULT (1U << 31)
|
|
|
|
static bool do_iommu_attach(struct device *dev, const struct iommu_ops *ops,
|
|
u64 dma_base, u64 size)
|
|
{
|
|
struct iommu_domain *domain = iommu_get_domain_for_dev(dev);
|
|
|
|
/*
|
|
* Best case: The device is either part of a group which was
|
|
* already attached to a domain in a previous call, or it's
|
|
* been put in a default DMA domain by the IOMMU core.
|
|
*/
|
|
if (!domain) {
|
|
/*
|
|
* Urgh. The IOMMU core isn't going to do default domains
|
|
* for non-PCI devices anyway, until it has some means of
|
|
* abstracting the entirely implementation-specific
|
|
* sideband data/SoC topology/unicorn dust that may or
|
|
* may not differentiate upstream masters.
|
|
* So until then, HORRIBLE HACKS!
|
|
*/
|
|
domain = ops->domain_alloc(IOMMU_DOMAIN_DMA);
|
|
if (!domain)
|
|
goto out_no_domain;
|
|
|
|
domain->ops = ops;
|
|
domain->type = IOMMU_DOMAIN_DMA | __IOMMU_DOMAIN_FAKE_DEFAULT;
|
|
|
|
if (iommu_attach_device(domain, dev))
|
|
goto out_put_domain;
|
|
}
|
|
|
|
if (iommu_dma_init_domain(domain, dma_base, size))
|
|
goto out_detach;
|
|
|
|
dev->archdata.dma_ops = &iommu_dma_ops;
|
|
return true;
|
|
|
|
out_detach:
|
|
iommu_detach_device(domain, dev);
|
|
out_put_domain:
|
|
if (domain->type & __IOMMU_DOMAIN_FAKE_DEFAULT)
|
|
iommu_domain_free(domain);
|
|
out_no_domain:
|
|
pr_warn("Failed to set up IOMMU for device %s; retaining platform DMA ops\n",
|
|
dev_name(dev));
|
|
return false;
|
|
}
|
|
|
|
static void queue_iommu_attach(struct device *dev, const struct iommu_ops *ops,
|
|
u64 dma_base, u64 size)
|
|
{
|
|
struct iommu_dma_notifier_data *iommudata;
|
|
|
|
iommudata = kzalloc(sizeof(*iommudata), GFP_KERNEL);
|
|
if (!iommudata)
|
|
return;
|
|
|
|
iommudata->dev = dev;
|
|
iommudata->ops = ops;
|
|
iommudata->dma_base = dma_base;
|
|
iommudata->size = size;
|
|
|
|
mutex_lock(&iommu_dma_notifier_lock);
|
|
list_add(&iommudata->list, &iommu_dma_masters);
|
|
mutex_unlock(&iommu_dma_notifier_lock);
|
|
}
|
|
|
|
static int __iommu_attach_notifier(struct notifier_block *nb,
|
|
unsigned long action, void *data)
|
|
{
|
|
struct iommu_dma_notifier_data *master, *tmp;
|
|
|
|
if (action != BUS_NOTIFY_ADD_DEVICE)
|
|
return 0;
|
|
|
|
mutex_lock(&iommu_dma_notifier_lock);
|
|
list_for_each_entry_safe(master, tmp, &iommu_dma_masters, list) {
|
|
if (do_iommu_attach(master->dev, master->ops,
|
|
master->dma_base, master->size)) {
|
|
list_del(&master->list);
|
|
kfree(master);
|
|
}
|
|
}
|
|
mutex_unlock(&iommu_dma_notifier_lock);
|
|
return 0;
|
|
}
|
|
|
|
static int register_iommu_dma_ops_notifier(struct bus_type *bus)
|
|
{
|
|
struct notifier_block *nb = kzalloc(sizeof(*nb), GFP_KERNEL);
|
|
int ret;
|
|
|
|
if (!nb)
|
|
return -ENOMEM;
|
|
/*
|
|
* The device must be attached to a domain before the driver probe
|
|
* routine gets a chance to start allocating DMA buffers. However,
|
|
* the IOMMU driver also needs a chance to configure the iommu_group
|
|
* via its add_device callback first, so we need to make the attach
|
|
* happen between those two points. Since the IOMMU core uses a bus
|
|
* notifier with default priority for add_device, do the same but
|
|
* with a lower priority to ensure the appropriate ordering.
|
|
*/
|
|
nb->notifier_call = __iommu_attach_notifier;
|
|
nb->priority = -100;
|
|
|
|
ret = bus_register_notifier(bus, nb);
|
|
if (ret) {
|
|
pr_warn("Failed to register DMA domain notifier; IOMMU DMA ops unavailable on bus '%s'\n",
|
|
bus->name);
|
|
kfree(nb);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int __init __iommu_dma_init(void)
|
|
{
|
|
int ret;
|
|
|
|
ret = iommu_dma_init();
|
|
if (!ret)
|
|
ret = register_iommu_dma_ops_notifier(&platform_bus_type);
|
|
if (!ret)
|
|
ret = register_iommu_dma_ops_notifier(&amba_bustype);
|
|
|
|
/* handle devices queued before this arch_initcall */
|
|
if (!ret)
|
|
__iommu_attach_notifier(NULL, BUS_NOTIFY_ADD_DEVICE, NULL);
|
|
return ret;
|
|
}
|
|
arch_initcall(__iommu_dma_init);
|
|
|
|
static void __iommu_setup_dma_ops(struct device *dev, u64 dma_base, u64 size,
|
|
const struct iommu_ops *ops)
|
|
{
|
|
struct iommu_group *group;
|
|
|
|
if (!ops)
|
|
return;
|
|
/*
|
|
* TODO: As a concession to the future, we're ready to handle being
|
|
* called both early and late (i.e. after bus_add_device). Once all
|
|
* the platform bus code is reworked to call us late and the notifier
|
|
* junk above goes away, move the body of do_iommu_attach here.
|
|
*/
|
|
group = iommu_group_get(dev);
|
|
if (group) {
|
|
do_iommu_attach(dev, ops, dma_base, size);
|
|
iommu_group_put(group);
|
|
} else {
|
|
queue_iommu_attach(dev, ops, dma_base, size);
|
|
}
|
|
}
|
|
|
|
void arch_teardown_dma_ops(struct device *dev)
|
|
{
|
|
struct iommu_domain *domain = iommu_get_domain_for_dev(dev);
|
|
|
|
if (domain) {
|
|
iommu_detach_device(domain, dev);
|
|
if (domain->type & __IOMMU_DOMAIN_FAKE_DEFAULT)
|
|
iommu_domain_free(domain);
|
|
}
|
|
|
|
dev->archdata.dma_ops = NULL;
|
|
}
|
|
|
|
#else
|
|
|
|
static void __iommu_setup_dma_ops(struct device *dev, u64 dma_base, u64 size,
|
|
struct iommu_ops *iommu)
|
|
{ }
|
|
|
|
#endif /* CONFIG_IOMMU_DMA */
|
|
|
|
void arch_setup_dma_ops(struct device *dev, u64 dma_base, u64 size,
|
|
struct iommu_ops *iommu, bool coherent)
|
|
{
|
|
if (!dev->archdata.dma_ops)
|
|
dev->archdata.dma_ops = &swiotlb_dma_ops;
|
|
|
|
dev->archdata.dma_coherent = coherent;
|
|
__iommu_setup_dma_ops(dev, dma_base, size, iommu);
|
|
}
|
|
|
|
#ifdef CONFIG_ARM64_DMA_USE_IOMMU
|
|
|
|
/*
|
|
* Make an area consistent for devices.
|
|
* Note: Drivers should NOT use this function directly, as it will break
|
|
* platforms with CONFIG_DMABOUNCE.
|
|
* Use the driver DMA support - see dma-mapping.h (dma_sync_*)
|
|
*/
|
|
static void __dma_page_cpu_to_dev(struct page *page, unsigned long off,
|
|
size_t size, enum dma_data_direction dir)
|
|
{
|
|
__dma_map_area(page_address(page) + off, size, dir);
|
|
}
|
|
|
|
static void __dma_page_dev_to_cpu(struct page *page, unsigned long off,
|
|
size_t size, enum dma_data_direction dir)
|
|
{
|
|
__dma_unmap_area(page_address(page) + off, size, dir);
|
|
|
|
/*
|
|
* Mark the D-cache clean for this page to avoid extra flushing.
|
|
*/
|
|
if (dir != DMA_TO_DEVICE && off == 0 && size >= PAGE_SIZE)
|
|
set_bit(PG_dcache_clean, &page->flags);
|
|
}
|
|
|
|
static int arm_dma_set_mask(struct device *dev, u64 dma_mask)
|
|
{
|
|
if (!dev->dma_mask || !dma_supported(dev, dma_mask))
|
|
return -EIO;
|
|
|
|
*dev->dma_mask = dma_mask;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* IOMMU */
|
|
|
|
static void __dma_clear_buffer(struct page *page, size_t size,
|
|
struct dma_attrs *attrs)
|
|
{
|
|
/*
|
|
* Ensure that the allocated pages are zeroed, and that any data
|
|
* lurking in the kernel direct-mapped region is invalidated.
|
|
*/
|
|
void *ptr = page_address(page);
|
|
if (!dma_get_attr(DMA_ATTR_SKIP_ZEROING, attrs))
|
|
memset(ptr, 0, size);
|
|
dmac_flush_range(ptr, ptr + size);
|
|
}
|
|
|
|
static inline dma_addr_t __alloc_iova(struct dma_iommu_mapping *mapping,
|
|
size_t size)
|
|
{
|
|
unsigned int order = get_order(size);
|
|
unsigned int align = 0;
|
|
unsigned int count, start;
|
|
unsigned long flags;
|
|
|
|
if (order > CONFIG_ARM64_DMA_IOMMU_ALIGNMENT)
|
|
order = CONFIG_ARM64_DMA_IOMMU_ALIGNMENT;
|
|
|
|
count = PAGE_ALIGN(size) >> PAGE_SHIFT;
|
|
align = (1 << order) - 1;
|
|
|
|
spin_lock_irqsave(&mapping->lock, flags);
|
|
start = bitmap_find_next_zero_area(mapping->bitmap, mapping->bits, 0,
|
|
count, align);
|
|
if (start > mapping->bits) {
|
|
spin_unlock_irqrestore(&mapping->lock, flags);
|
|
return DMA_ERROR_CODE;
|
|
}
|
|
|
|
bitmap_set(mapping->bitmap, start, count);
|
|
spin_unlock_irqrestore(&mapping->lock, flags);
|
|
|
|
return mapping->base + (start << PAGE_SHIFT);
|
|
}
|
|
|
|
static inline void __free_iova(struct dma_iommu_mapping *mapping,
|
|
dma_addr_t addr, size_t size)
|
|
{
|
|
unsigned int start = (addr - mapping->base) >> PAGE_SHIFT;
|
|
unsigned int count = size >> PAGE_SHIFT;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&mapping->lock, flags);
|
|
bitmap_clear(mapping->bitmap, start, count);
|
|
spin_unlock_irqrestore(&mapping->lock, flags);
|
|
}
|
|
|
|
static struct page **__iommu_alloc_buffer(struct device *dev, size_t size,
|
|
gfp_t gfp, struct dma_attrs *attrs)
|
|
{
|
|
struct page **pages;
|
|
int count = size >> PAGE_SHIFT;
|
|
int array_size = count * sizeof(struct page *);
|
|
int i = 0;
|
|
|
|
if (array_size <= PAGE_SIZE)
|
|
pages = kzalloc(array_size, gfp);
|
|
else
|
|
pages = vzalloc(array_size);
|
|
if (!pages)
|
|
return NULL;
|
|
|
|
if (dma_get_attr(DMA_ATTR_FORCE_CONTIGUOUS, attrs)) {
|
|
unsigned long order = get_order(size);
|
|
struct page *page;
|
|
|
|
page = dma_alloc_from_contiguous(dev, count, order);
|
|
if (!page)
|
|
goto error;
|
|
|
|
__dma_clear_buffer(page, size, attrs);
|
|
|
|
for (i = 0; i < count; i++)
|
|
pages[i] = page + i;
|
|
|
|
return pages;
|
|
}
|
|
|
|
/*
|
|
* IOMMU can map any pages, so himem can also be used here
|
|
*/
|
|
gfp |= __GFP_NOWARN | __GFP_HIGHMEM;
|
|
|
|
while (count) {
|
|
int j, order = __fls(count);
|
|
|
|
pages[i] = alloc_pages(gfp, order);
|
|
while (!pages[i] && order)
|
|
pages[i] = alloc_pages(gfp, --order);
|
|
if (!pages[i])
|
|
goto error;
|
|
|
|
if (order) {
|
|
split_page(pages[i], order);
|
|
j = 1 << order;
|
|
while (--j)
|
|
pages[i + j] = pages[i] + j;
|
|
}
|
|
|
|
__dma_clear_buffer(pages[i], PAGE_SIZE << order, attrs);
|
|
i += 1 << order;
|
|
count -= 1 << order;
|
|
}
|
|
|
|
return pages;
|
|
error:
|
|
while (i--)
|
|
if (pages[i])
|
|
__free_pages(pages[i], 0);
|
|
if (array_size <= PAGE_SIZE)
|
|
kfree(pages);
|
|
else
|
|
vfree(pages);
|
|
return NULL;
|
|
}
|
|
|
|
static int __iommu_free_buffer(struct device *dev, struct page **pages,
|
|
size_t size, struct dma_attrs *attrs)
|
|
{
|
|
int count = size >> PAGE_SHIFT;
|
|
int array_size = count * sizeof(struct page *);
|
|
int i;
|
|
|
|
if (dma_get_attr(DMA_ATTR_FORCE_CONTIGUOUS, attrs)) {
|
|
dma_release_from_contiguous(dev, pages[0], count);
|
|
} else {
|
|
for (i = 0; i < count; i++)
|
|
if (pages[i])
|
|
__free_pages(pages[i], 0);
|
|
}
|
|
|
|
if (array_size <= PAGE_SIZE)
|
|
kfree(pages);
|
|
else
|
|
vfree(pages);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Create a CPU mapping for a specified pages
|
|
*/
|
|
static void *
|
|
__iommu_alloc_remap(struct page **pages, size_t size, gfp_t gfp, pgprot_t prot,
|
|
const void *caller)
|
|
{
|
|
return dma_common_pages_remap(pages, size, VM_USERMAP, prot, caller);
|
|
}
|
|
|
|
/*
|
|
* Create a mapping in device IO address space for specified pages
|
|
*/
|
|
static dma_addr_t __iommu_create_mapping(struct device *dev,
|
|
struct page **pages, size_t size,
|
|
struct dma_attrs *attrs)
|
|
{
|
|
struct dma_iommu_mapping *mapping = dev->archdata.mapping;
|
|
unsigned int count = PAGE_ALIGN(size) >> PAGE_SHIFT;
|
|
dma_addr_t dma_addr, iova;
|
|
int i, ret;
|
|
int prot = IOMMU_READ | IOMMU_WRITE;
|
|
|
|
dma_addr = __alloc_iova(mapping, size);
|
|
if (dma_addr == DMA_ERROR_CODE)
|
|
return dma_addr;
|
|
|
|
if (!dma_get_attr(DMA_ATTR_EXEC_MAPPING, attrs))
|
|
prot |= IOMMU_NOEXEC;
|
|
|
|
iova = dma_addr;
|
|
for (i = 0; i < count; ) {
|
|
unsigned int next_pfn = page_to_pfn(pages[i]) + 1;
|
|
phys_addr_t phys = page_to_phys(pages[i]);
|
|
unsigned int len, j;
|
|
|
|
for (j = i + 1; j < count; j++, next_pfn++)
|
|
if (page_to_pfn(pages[j]) != next_pfn)
|
|
break;
|
|
|
|
len = (j - i) << PAGE_SHIFT;
|
|
ret = iommu_map(mapping->domain, iova, phys, len, prot);
|
|
if (ret < 0)
|
|
goto fail;
|
|
iova += len;
|
|
i = j;
|
|
}
|
|
return dma_addr;
|
|
fail:
|
|
iommu_unmap(mapping->domain, dma_addr, iova-dma_addr);
|
|
__free_iova(mapping, dma_addr, size);
|
|
return DMA_ERROR_CODE;
|
|
}
|
|
|
|
static int __iommu_remove_mapping(struct device *dev, dma_addr_t iova,
|
|
size_t size)
|
|
{
|
|
struct dma_iommu_mapping *mapping = dev->archdata.mapping;
|
|
|
|
/*
|
|
* add optional in-page offset from iova to size and align
|
|
* result to page size
|
|
*/
|
|
size = PAGE_ALIGN((iova & ~PAGE_MASK) + size);
|
|
iova &= PAGE_MASK;
|
|
|
|
iommu_unmap(mapping->domain, iova, size);
|
|
__free_iova(mapping, iova, size);
|
|
return 0;
|
|
}
|
|
|
|
static struct page **__atomic_get_pages(void *addr)
|
|
{
|
|
struct page *page;
|
|
phys_addr_t phys;
|
|
|
|
phys = gen_pool_virt_to_phys(atomic_pool, (unsigned long)addr);
|
|
page = phys_to_page(phys);
|
|
|
|
return (struct page **)page;
|
|
}
|
|
|
|
static struct page **__iommu_get_pages(void *cpu_addr, struct dma_attrs *attrs)
|
|
{
|
|
struct vm_struct *area;
|
|
|
|
if (__in_atomic_pool(cpu_addr, PAGE_SIZE))
|
|
return __atomic_get_pages(cpu_addr);
|
|
|
|
if (dma_get_attr(DMA_ATTR_NO_KERNEL_MAPPING, attrs))
|
|
return cpu_addr;
|
|
|
|
area = find_vm_area(cpu_addr);
|
|
if (area)
|
|
return area->pages;
|
|
return NULL;
|
|
}
|
|
|
|
static void *__iommu_alloc_atomic(struct device *dev, size_t size,
|
|
dma_addr_t *handle, gfp_t gfp,
|
|
struct dma_attrs *attrs)
|
|
{
|
|
struct page *page;
|
|
struct page **pages;
|
|
int count = size >> PAGE_SHIFT;
|
|
int array_size = count * sizeof(struct page *);
|
|
int i;
|
|
void *addr;
|
|
|
|
if (array_size <= PAGE_SIZE)
|
|
pages = kzalloc(array_size, gfp);
|
|
else
|
|
pages = vzalloc(array_size);
|
|
|
|
if (!pages)
|
|
return NULL;
|
|
|
|
addr = __alloc_from_pool(size, &page, gfp);
|
|
if (!addr)
|
|
goto err_free;
|
|
|
|
for (i = 0; i < count ; i++)
|
|
pages[i] = page + i;
|
|
|
|
*handle = __iommu_create_mapping(dev, pages, size, attrs);
|
|
if (*handle == DMA_ERROR_CODE)
|
|
goto err_mapping;
|
|
|
|
kvfree(pages);
|
|
return addr;
|
|
|
|
err_mapping:
|
|
__free_from_pool(addr, size);
|
|
err_free:
|
|
kvfree(pages);
|
|
return NULL;
|
|
}
|
|
|
|
static void __iommu_free_atomic(struct device *dev, void *cpu_addr,
|
|
dma_addr_t handle, size_t size)
|
|
{
|
|
__iommu_remove_mapping(dev, handle, size);
|
|
__free_from_pool(cpu_addr, size);
|
|
}
|
|
|
|
static void *arm_iommu_alloc_attrs(struct device *dev, size_t size,
|
|
dma_addr_t *handle, gfp_t gfp, struct dma_attrs *attrs)
|
|
{
|
|
pgprot_t prot = __get_dma_pgprot(attrs, PAGE_KERNEL, false);
|
|
struct page **pages;
|
|
void *addr = NULL;
|
|
|
|
*handle = DMA_ERROR_CODE;
|
|
size = PAGE_ALIGN(size);
|
|
|
|
if (!gfpflags_allow_blocking(gfp))
|
|
return __iommu_alloc_atomic(dev, size, handle, gfp, attrs);
|
|
|
|
/*
|
|
* Following is a work-around (a.k.a. hack) to prevent pages
|
|
* with __GFP_COMP being passed to split_page() which cannot
|
|
* handle them. The real problem is that this flag probably
|
|
* should be 0 on ARM as it is not supported on this
|
|
* platform; see CONFIG_HUGETLBFS.
|
|
*/
|
|
gfp &= ~(__GFP_COMP);
|
|
|
|
pages = __iommu_alloc_buffer(dev, size, gfp, attrs);
|
|
if (!pages)
|
|
return NULL;
|
|
|
|
*handle = __iommu_create_mapping(dev, pages, size, attrs);
|
|
if (*handle == DMA_ERROR_CODE)
|
|
goto err_buffer;
|
|
|
|
if (dma_get_attr(DMA_ATTR_NO_KERNEL_MAPPING, attrs))
|
|
return pages;
|
|
|
|
addr = __iommu_alloc_remap(pages, size, gfp, prot,
|
|
__builtin_return_address(0));
|
|
if (!addr)
|
|
goto err_mapping;
|
|
|
|
return addr;
|
|
|
|
err_mapping:
|
|
__iommu_remove_mapping(dev, *handle, size);
|
|
err_buffer:
|
|
__iommu_free_buffer(dev, pages, size, attrs);
|
|
return NULL;
|
|
}
|
|
|
|
static int arm_iommu_mmap_attrs(struct device *dev, struct vm_area_struct *vma,
|
|
void *cpu_addr, dma_addr_t dma_addr, size_t size,
|
|
struct dma_attrs *attrs)
|
|
{
|
|
unsigned long uaddr = vma->vm_start;
|
|
unsigned long usize = vma->vm_end - vma->vm_start;
|
|
struct page **pages = __iommu_get_pages(cpu_addr, attrs);
|
|
|
|
vma->vm_page_prot = __get_dma_pgprot(attrs, vma->vm_page_prot, false);
|
|
|
|
if (!pages)
|
|
return -ENXIO;
|
|
|
|
do {
|
|
int ret = vm_insert_page(vma, uaddr, *pages++);
|
|
if (ret) {
|
|
pr_err("Remapping memory failed: %d\n", ret);
|
|
return ret;
|
|
}
|
|
uaddr += PAGE_SIZE;
|
|
usize -= PAGE_SIZE;
|
|
} while (usize > 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* free a page as defined by the above mapping.
|
|
* Must not be called with IRQs disabled.
|
|
*/
|
|
void arm_iommu_free_attrs(struct device *dev, size_t size, void *cpu_addr,
|
|
dma_addr_t handle, struct dma_attrs *attrs)
|
|
{
|
|
struct page **pages;
|
|
size = PAGE_ALIGN(size);
|
|
|
|
if (__in_atomic_pool(cpu_addr, size)) {
|
|
__iommu_free_atomic(dev, cpu_addr, handle, size);
|
|
return;
|
|
}
|
|
|
|
pages = __iommu_get_pages(cpu_addr, attrs);
|
|
if (!pages) {
|
|
WARN(1, "trying to free invalid coherent area: %p\n", cpu_addr);
|
|
return;
|
|
}
|
|
|
|
if (!dma_get_attr(DMA_ATTR_NO_KERNEL_MAPPING, attrs))
|
|
dma_common_free_remap(cpu_addr, size, VM_USERMAP, true);
|
|
|
|
__iommu_remove_mapping(dev, handle, size);
|
|
__iommu_free_buffer(dev, pages, size, attrs);
|
|
}
|
|
|
|
int arm_iommu_get_sgtable(struct device *dev, struct sg_table *sgt,
|
|
void *cpu_addr, dma_addr_t dma_addr,
|
|
size_t size, struct dma_attrs *attrs)
|
|
{
|
|
unsigned int count = PAGE_ALIGN(size) >> PAGE_SHIFT;
|
|
struct page **pages = __iommu_get_pages(cpu_addr, attrs);
|
|
|
|
if (!pages)
|
|
return -ENXIO;
|
|
|
|
return sg_alloc_table_from_pages(sgt, pages, count, 0, size,
|
|
GFP_KERNEL);
|
|
}
|
|
|
|
static int __dma_direction_to_prot(enum dma_data_direction dir)
|
|
{
|
|
int prot;
|
|
|
|
switch (dir) {
|
|
case DMA_BIDIRECTIONAL:
|
|
prot = IOMMU_READ | IOMMU_WRITE;
|
|
break;
|
|
case DMA_TO_DEVICE:
|
|
prot = IOMMU_READ;
|
|
break;
|
|
case DMA_FROM_DEVICE:
|
|
prot = IOMMU_WRITE;
|
|
break;
|
|
default:
|
|
prot = 0;
|
|
}
|
|
|
|
return prot;
|
|
}
|
|
|
|
/*
|
|
* Map a part of the scatter-gather list into contiguous io address space
|
|
*/
|
|
static int __map_sg_chunk(struct device *dev, struct scatterlist *sg,
|
|
size_t size, dma_addr_t *handle,
|
|
enum dma_data_direction dir, struct dma_attrs *attrs,
|
|
bool is_coherent)
|
|
{
|
|
struct dma_iommu_mapping *mapping = dev->archdata.mapping;
|
|
dma_addr_t iova, iova_base;
|
|
int ret = 0;
|
|
unsigned int count;
|
|
struct scatterlist *s;
|
|
int prot;
|
|
|
|
size = PAGE_ALIGN(size);
|
|
*handle = DMA_ERROR_CODE;
|
|
|
|
iova_base = iova = __alloc_iova(mapping, size);
|
|
if (iova == DMA_ERROR_CODE)
|
|
return -ENOMEM;
|
|
|
|
for (count = 0, s = sg; count < (size >> PAGE_SHIFT); s = sg_next(s)) {
|
|
phys_addr_t phys = page_to_phys(sg_page(s));
|
|
unsigned int len = PAGE_ALIGN(s->offset + s->length);
|
|
|
|
if (!is_coherent &&
|
|
!dma_get_attr(DMA_ATTR_SKIP_CPU_SYNC, attrs))
|
|
__dma_page_cpu_to_dev(sg_page(s), s->offset, s->length,
|
|
dir);
|
|
|
|
prot = __dma_direction_to_prot(dir);
|
|
if (!dma_get_attr(DMA_ATTR_EXEC_MAPPING, attrs))
|
|
prot |= IOMMU_NOEXEC;
|
|
|
|
ret = iommu_map(mapping->domain, iova, phys, len, prot);
|
|
if (ret < 0)
|
|
goto fail;
|
|
count += len >> PAGE_SHIFT;
|
|
iova += len;
|
|
}
|
|
*handle = iova_base;
|
|
|
|
return 0;
|
|
fail:
|
|
iommu_unmap(mapping->domain, iova_base, count * PAGE_SIZE);
|
|
__free_iova(mapping, iova_base, size);
|
|
return ret;
|
|
}
|
|
|
|
static int __iommu_map_sg(struct device *dev, struct scatterlist *sg, int nents,
|
|
enum dma_data_direction dir, struct dma_attrs *attrs,
|
|
bool is_coherent)
|
|
{
|
|
struct scatterlist *s = sg, *dma = sg, *start = sg;
|
|
int i, count = 0;
|
|
unsigned int offset = s->offset;
|
|
unsigned int size = s->offset + s->length;
|
|
unsigned int max = dma_get_max_seg_size(dev);
|
|
|
|
for (i = 1; i < nents; i++) {
|
|
s = sg_next(s);
|
|
|
|
s->dma_address = DMA_ERROR_CODE;
|
|
s->dma_length = 0;
|
|
|
|
if (s->offset || (size & ~PAGE_MASK)
|
|
|| size + s->length > max) {
|
|
if (__map_sg_chunk(dev, start, size, &dma->dma_address,
|
|
dir, attrs, is_coherent) < 0)
|
|
goto bad_mapping;
|
|
|
|
dma->dma_address += offset;
|
|
dma->dma_length = size - offset;
|
|
|
|
size = offset = s->offset;
|
|
start = s;
|
|
dma = sg_next(dma);
|
|
count += 1;
|
|
}
|
|
size += s->length;
|
|
}
|
|
if (__map_sg_chunk(dev, start, size, &dma->dma_address, dir, attrs,
|
|
is_coherent) < 0)
|
|
goto bad_mapping;
|
|
|
|
dma->dma_address += offset;
|
|
dma->dma_length = size - offset;
|
|
|
|
return count+1;
|
|
|
|
bad_mapping:
|
|
for_each_sg(sg, s, count, i)
|
|
__iommu_remove_mapping(dev, sg_dma_address(s), sg_dma_len(s));
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* arm_coherent_iommu_map_sg - map a set of SG buffers for streaming mode DMA
|
|
* @dev: valid struct device pointer
|
|
* @sg: list of buffers
|
|
* @nents: number of buffers to map
|
|
* @dir: DMA transfer direction
|
|
*
|
|
* Map a set of i/o coherent buffers described by scatterlist in streaming
|
|
* mode for DMA. The scatter gather list elements are merged together (if
|
|
* possible) and tagged with the appropriate dma address and length. They are
|
|
* obtained via sg_dma_{address,length}.
|
|
*/
|
|
int arm_coherent_iommu_map_sg(struct device *dev, struct scatterlist *sg,
|
|
int nents, enum dma_data_direction dir, struct dma_attrs *attrs)
|
|
{
|
|
return __iommu_map_sg(dev, sg, nents, dir, attrs, true);
|
|
}
|
|
|
|
/**
|
|
* arm_iommu_map_sg - map a set of SG buffers for streaming mode DMA
|
|
* @dev: valid struct device pointer
|
|
* @sg: list of buffers
|
|
* @nents: number of buffers to map
|
|
* @dir: DMA transfer direction
|
|
*
|
|
* Map a set of buffers described by scatterlist in streaming mode for DMA.
|
|
* The scatter gather list elements are merged together (if possible) and
|
|
* tagged with the appropriate dma address and length. They are obtained via
|
|
* sg_dma_{address,length}.
|
|
*/
|
|
int arm_iommu_map_sg(struct device *dev, struct scatterlist *sg,
|
|
int nents, enum dma_data_direction dir, struct dma_attrs *attrs)
|
|
{
|
|
struct scatterlist *s;
|
|
int ret, i;
|
|
struct dma_iommu_mapping *mapping = dev->archdata.mapping;
|
|
unsigned int total_length = 0, current_offset = 0;
|
|
dma_addr_t iova;
|
|
int prot = __dma_direction_to_prot(dir);
|
|
|
|
for_each_sg(sg, s, nents, i)
|
|
total_length += s->length;
|
|
|
|
iova = __alloc_iova(mapping, total_length);
|
|
if (iova == DMA_ERROR_CODE) {
|
|
dev_err(dev, "Couldn't allocate iova for sg %p\n", sg);
|
|
return 0;
|
|
}
|
|
|
|
if (!dma_get_attr(DMA_ATTR_EXEC_MAPPING, attrs))
|
|
prot |= IOMMU_NOEXEC;
|
|
|
|
ret = iommu_map_sg(mapping->domain, iova, sg, nents, prot);
|
|
if (ret != total_length) {
|
|
__free_iova(mapping, iova, total_length);
|
|
return 0;
|
|
}
|
|
|
|
for_each_sg(sg, s, nents, i) {
|
|
s->dma_address = iova + current_offset;
|
|
s->dma_length = total_length - current_offset;
|
|
current_offset += s->length;
|
|
}
|
|
|
|
return nents;
|
|
}
|
|
|
|
static void __iommu_unmap_sg(struct device *dev, struct scatterlist *sg,
|
|
int nents, enum dma_data_direction dir, struct dma_attrs *attrs,
|
|
bool is_coherent)
|
|
{
|
|
struct scatterlist *s;
|
|
int i;
|
|
|
|
for_each_sg(sg, s, nents, i) {
|
|
if (sg_dma_len(s))
|
|
__iommu_remove_mapping(dev, sg_dma_address(s),
|
|
sg_dma_len(s));
|
|
if (!is_coherent &&
|
|
!dma_get_attr(DMA_ATTR_SKIP_CPU_SYNC, attrs))
|
|
__dma_page_dev_to_cpu(sg_page(s), s->offset,
|
|
s->length, dir);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* arm_coherent_iommu_unmap_sg - unmap a set of SG buffers mapped by dma_map_sg
|
|
* @dev: valid struct device pointer
|
|
* @sg: list of buffers
|
|
* @nents: number of buffers to unmap (same as was passed to dma_map_sg)
|
|
* @dir: DMA transfer direction (same as was passed to dma_map_sg)
|
|
*
|
|
* Unmap a set of streaming mode DMA translations. Again, CPU access
|
|
* rules concerning calls here are the same as for dma_unmap_single().
|
|
*/
|
|
void arm_coherent_iommu_unmap_sg(struct device *dev, struct scatterlist *sg,
|
|
int nents, enum dma_data_direction dir, struct dma_attrs *attrs)
|
|
{
|
|
__iommu_unmap_sg(dev, sg, nents, dir, attrs, true);
|
|
}
|
|
|
|
/**
|
|
* arm_iommu_unmap_sg - unmap a set of SG buffers mapped by dma_map_sg
|
|
* @dev: valid struct device pointer
|
|
* @sg: list of buffers
|
|
* @nents: number of buffers to unmap (same as was passed to dma_map_sg)
|
|
* @dir: DMA transfer direction (same as was passed to dma_map_sg)
|
|
*
|
|
* Unmap a set of streaming mode DMA translations. Again, CPU access
|
|
* rules concerning calls here are the same as for dma_unmap_single().
|
|
*/
|
|
void arm_iommu_unmap_sg(struct device *dev, struct scatterlist *sg, int nents,
|
|
enum dma_data_direction dir, struct dma_attrs *attrs)
|
|
{
|
|
struct dma_iommu_mapping *mapping = dev->archdata.mapping;
|
|
unsigned int total_length = sg_dma_len(sg);
|
|
dma_addr_t iova = sg_dma_address(sg);
|
|
|
|
total_length = PAGE_ALIGN((iova & ~PAGE_MASK) + total_length);
|
|
iova &= PAGE_MASK;
|
|
|
|
iommu_unmap(mapping->domain, iova, total_length);
|
|
__free_iova(mapping, iova, total_length);
|
|
}
|
|
|
|
/**
|
|
* arm_iommu_sync_sg_for_cpu
|
|
* @dev: valid struct device pointer
|
|
* @sg: list of buffers
|
|
* @nents: number of buffers to map (returned from dma_map_sg)
|
|
* @dir: DMA transfer direction (same as was passed to dma_map_sg)
|
|
*/
|
|
void arm_iommu_sync_sg_for_cpu(struct device *dev, struct scatterlist *sg,
|
|
int nents, enum dma_data_direction dir)
|
|
{
|
|
struct scatterlist *s;
|
|
int i;
|
|
|
|
for_each_sg(sg, s, nents, i)
|
|
__dma_page_dev_to_cpu(sg_page(s), s->offset, s->length, dir);
|
|
|
|
}
|
|
|
|
/**
|
|
* arm_iommu_sync_sg_for_device
|
|
* @dev: valid struct device pointer
|
|
* @sg: list of buffers
|
|
* @nents: number of buffers to map (returned from dma_map_sg)
|
|
* @dir: DMA transfer direction (same as was passed to dma_map_sg)
|
|
*/
|
|
void arm_iommu_sync_sg_for_device(struct device *dev, struct scatterlist *sg,
|
|
int nents, enum dma_data_direction dir)
|
|
{
|
|
struct scatterlist *s;
|
|
int i;
|
|
|
|
for_each_sg(sg, s, nents, i)
|
|
__dma_page_cpu_to_dev(sg_page(s), s->offset, s->length, dir);
|
|
}
|
|
|
|
|
|
/**
|
|
* arm_coherent_iommu_map_page
|
|
* @dev: valid struct device pointer
|
|
* @page: page that buffer resides in
|
|
* @offset: offset into page for start of buffer
|
|
* @size: size of buffer to map
|
|
* @dir: DMA transfer direction
|
|
*
|
|
* Coherent IOMMU aware version of arm_dma_map_page()
|
|
*/
|
|
static dma_addr_t arm_coherent_iommu_map_page(struct device *dev,
|
|
struct page *page, unsigned long offset, size_t size,
|
|
enum dma_data_direction dir, struct dma_attrs *attrs)
|
|
{
|
|
struct dma_iommu_mapping *mapping = dev->archdata.mapping;
|
|
dma_addr_t dma_addr;
|
|
int ret, prot, len = PAGE_ALIGN(size + offset);
|
|
|
|
dma_addr = __alloc_iova(mapping, len);
|
|
if (dma_addr == DMA_ERROR_CODE)
|
|
return dma_addr;
|
|
|
|
prot = __dma_direction_to_prot(dir);
|
|
if (!dma_get_attr(DMA_ATTR_EXEC_MAPPING, attrs))
|
|
prot |= IOMMU_NOEXEC;
|
|
|
|
ret = iommu_map(mapping->domain, dma_addr, page_to_phys(page), len,
|
|
prot);
|
|
if (ret < 0)
|
|
goto fail;
|
|
|
|
return dma_addr + offset;
|
|
fail:
|
|
__free_iova(mapping, dma_addr, len);
|
|
return DMA_ERROR_CODE;
|
|
}
|
|
|
|
/**
|
|
* arm_iommu_map_page
|
|
* @dev: valid struct device pointer
|
|
* @page: page that buffer resides in
|
|
* @offset: offset into page for start of buffer
|
|
* @size: size of buffer to map
|
|
* @dir: DMA transfer direction
|
|
*
|
|
* IOMMU aware version of arm_dma_map_page()
|
|
*/
|
|
static dma_addr_t arm_iommu_map_page(struct device *dev, struct page *page,
|
|
unsigned long offset, size_t size, enum dma_data_direction dir,
|
|
struct dma_attrs *attrs)
|
|
{
|
|
if (!dma_get_attr(DMA_ATTR_SKIP_CPU_SYNC, attrs))
|
|
__dma_page_cpu_to_dev(page, offset, size, dir);
|
|
|
|
return arm_coherent_iommu_map_page(dev, page, offset, size, dir, attrs);
|
|
}
|
|
|
|
/**
|
|
* arm_coherent_iommu_unmap_page
|
|
* @dev: valid struct device pointer
|
|
* @handle: DMA address of buffer
|
|
* @size: size of buffer (same as passed to dma_map_page)
|
|
* @dir: DMA transfer direction (same as passed to dma_map_page)
|
|
*
|
|
* Coherent IOMMU aware version of arm_dma_unmap_page()
|
|
*/
|
|
static void arm_coherent_iommu_unmap_page(struct device *dev, dma_addr_t handle,
|
|
size_t size, enum dma_data_direction dir,
|
|
struct dma_attrs *attrs)
|
|
{
|
|
struct dma_iommu_mapping *mapping = dev->archdata.mapping;
|
|
dma_addr_t iova = handle & PAGE_MASK;
|
|
int offset = handle & ~PAGE_MASK;
|
|
int len = PAGE_ALIGN(size + offset);
|
|
|
|
if (!iova)
|
|
return;
|
|
|
|
iommu_unmap(mapping->domain, iova, len);
|
|
__free_iova(mapping, iova, len);
|
|
}
|
|
|
|
/**
|
|
* arm_iommu_unmap_page
|
|
* @dev: valid struct device pointer
|
|
* @handle: DMA address of buffer
|
|
* @size: size of buffer (same as passed to dma_map_page)
|
|
* @dir: DMA transfer direction (same as passed to dma_map_page)
|
|
*
|
|
* IOMMU aware version of arm_dma_unmap_page()
|
|
*/
|
|
static void arm_iommu_unmap_page(struct device *dev, dma_addr_t handle,
|
|
size_t size, enum dma_data_direction dir,
|
|
struct dma_attrs *attrs)
|
|
{
|
|
struct dma_iommu_mapping *mapping = dev->archdata.mapping;
|
|
dma_addr_t iova = handle & PAGE_MASK;
|
|
struct page *page = phys_to_page(iommu_iova_to_phys(
|
|
mapping->domain, iova));
|
|
int offset = handle & ~PAGE_MASK;
|
|
int len = PAGE_ALIGN(size + offset);
|
|
|
|
if (!iova)
|
|
return;
|
|
|
|
if (!dma_get_attr(DMA_ATTR_SKIP_CPU_SYNC, attrs))
|
|
__dma_page_dev_to_cpu(page, offset, size, dir);
|
|
|
|
iommu_unmap(mapping->domain, iova, len);
|
|
__free_iova(mapping, iova, len);
|
|
}
|
|
|
|
static void arm_iommu_sync_single_for_cpu(struct device *dev,
|
|
dma_addr_t handle, size_t size, enum dma_data_direction dir)
|
|
{
|
|
struct dma_iommu_mapping *mapping = dev->archdata.mapping;
|
|
dma_addr_t iova = handle & PAGE_MASK;
|
|
struct page *page = phys_to_page(iommu_iova_to_phys(
|
|
mapping->domain, iova));
|
|
unsigned int offset = handle & ~PAGE_MASK;
|
|
|
|
if (!iova)
|
|
return;
|
|
|
|
__dma_page_dev_to_cpu(page, offset, size, dir);
|
|
}
|
|
|
|
static void arm_iommu_sync_single_for_device(struct device *dev,
|
|
dma_addr_t handle, size_t size, enum dma_data_direction dir)
|
|
{
|
|
struct dma_iommu_mapping *mapping = dev->archdata.mapping;
|
|
dma_addr_t iova = handle & PAGE_MASK;
|
|
struct page *page = phys_to_page(iommu_iova_to_phys(
|
|
mapping->domain, iova));
|
|
unsigned int offset = handle & ~PAGE_MASK;
|
|
|
|
if (!iova)
|
|
return;
|
|
|
|
__dma_page_cpu_to_dev(page, offset, size, dir);
|
|
}
|
|
|
|
static int arm_iommu_dma_supported(struct device *dev, u64 mask)
|
|
{
|
|
struct dma_iommu_mapping *mapping = to_dma_iommu_mapping(dev);
|
|
|
|
if (!mapping) {
|
|
dev_warn(dev, "No IOMMU mapping for device\n");
|
|
return 0;
|
|
}
|
|
|
|
return iommu_dma_supported(mapping->domain, dev, mask);
|
|
}
|
|
|
|
static int arm_iommu_mapping_error(struct device *dev,
|
|
dma_addr_t dma_addr)
|
|
{
|
|
return dma_addr == DMA_ERROR_CODE;
|
|
}
|
|
|
|
const struct dma_map_ops iommu_ops = {
|
|
.alloc = arm_iommu_alloc_attrs,
|
|
.free = arm_iommu_free_attrs,
|
|
.mmap = arm_iommu_mmap_attrs,
|
|
.get_sgtable = arm_iommu_get_sgtable,
|
|
|
|
.map_page = arm_iommu_map_page,
|
|
.unmap_page = arm_iommu_unmap_page,
|
|
.sync_single_for_cpu = arm_iommu_sync_single_for_cpu,
|
|
.sync_single_for_device = arm_iommu_sync_single_for_device,
|
|
|
|
.map_sg = arm_iommu_map_sg,
|
|
.unmap_sg = arm_iommu_unmap_sg,
|
|
.sync_sg_for_cpu = arm_iommu_sync_sg_for_cpu,
|
|
.sync_sg_for_device = arm_iommu_sync_sg_for_device,
|
|
|
|
.set_dma_mask = arm_dma_set_mask,
|
|
.dma_supported = arm_iommu_dma_supported,
|
|
.mapping_error = arm_iommu_mapping_error,
|
|
};
|
|
|
|
const struct dma_map_ops iommu_coherent_ops = {
|
|
.alloc = arm_iommu_alloc_attrs,
|
|
.free = arm_iommu_free_attrs,
|
|
.mmap = arm_iommu_mmap_attrs,
|
|
.get_sgtable = arm_iommu_get_sgtable,
|
|
|
|
.map_page = arm_coherent_iommu_map_page,
|
|
.unmap_page = arm_coherent_iommu_unmap_page,
|
|
|
|
.map_sg = arm_coherent_iommu_map_sg,
|
|
.unmap_sg = arm_coherent_iommu_unmap_sg,
|
|
|
|
.set_dma_mask = arm_dma_set_mask,
|
|
.dma_supported = arm_iommu_dma_supported,
|
|
};
|
|
|
|
/**
|
|
* arm_iommu_create_mapping
|
|
* @bus: pointer to the bus holding the client device (for IOMMU calls)
|
|
* @base: start address of the valid IO address space
|
|
* @size: maximum size of the valid IO address space
|
|
*
|
|
* Creates a mapping structure which holds information about used/unused
|
|
* IO address ranges, which is required to perform memory allocation and
|
|
* mapping with IOMMU aware functions.
|
|
*
|
|
* The client device need to be attached to the mapping with
|
|
* arm_iommu_attach_device function.
|
|
*/
|
|
struct dma_iommu_mapping *
|
|
arm_iommu_create_mapping(struct bus_type *bus, dma_addr_t base, size_t size)
|
|
{
|
|
unsigned int bits = size >> PAGE_SHIFT;
|
|
unsigned int bitmap_size = BITS_TO_LONGS(bits) * sizeof(long);
|
|
struct dma_iommu_mapping *mapping;
|
|
int err = -ENOMEM;
|
|
|
|
if (!bitmap_size)
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
mapping = kzalloc(sizeof(struct dma_iommu_mapping), GFP_KERNEL);
|
|
if (!mapping)
|
|
goto err;
|
|
|
|
mapping->bitmap = kzalloc(bitmap_size, GFP_KERNEL);
|
|
if (!mapping->bitmap)
|
|
goto err2;
|
|
|
|
mapping->base = base;
|
|
mapping->bits = BITS_PER_BYTE * bitmap_size;
|
|
spin_lock_init(&mapping->lock);
|
|
|
|
mapping->domain = iommu_domain_alloc(bus);
|
|
if (!mapping->domain)
|
|
goto err3;
|
|
|
|
kref_init(&mapping->kref);
|
|
return mapping;
|
|
err3:
|
|
kfree(mapping->bitmap);
|
|
err2:
|
|
kfree(mapping);
|
|
err:
|
|
return ERR_PTR(err);
|
|
}
|
|
EXPORT_SYMBOL(arm_iommu_create_mapping);
|
|
|
|
static void release_iommu_mapping(struct kref *kref)
|
|
{
|
|
struct dma_iommu_mapping *mapping =
|
|
container_of(kref, struct dma_iommu_mapping, kref);
|
|
|
|
iommu_domain_free(mapping->domain);
|
|
kfree(mapping->bitmap);
|
|
kfree(mapping);
|
|
}
|
|
|
|
void arm_iommu_release_mapping(struct dma_iommu_mapping *mapping)
|
|
{
|
|
if (mapping)
|
|
kref_put(&mapping->kref, release_iommu_mapping);
|
|
}
|
|
EXPORT_SYMBOL(arm_iommu_release_mapping);
|
|
|
|
/**
|
|
* arm_iommu_attach_device
|
|
* @dev: valid struct device pointer
|
|
* @mapping: io address space mapping structure (returned from
|
|
* arm_iommu_create_mapping)
|
|
*
|
|
* Attaches specified io address space mapping to the provided device,
|
|
* this replaces the dma operations (dma_map_ops pointer) with the
|
|
* IOMMU aware version. More than one client might be attached to
|
|
* the same io address space mapping.
|
|
*/
|
|
int arm_iommu_attach_device(struct device *dev,
|
|
struct dma_iommu_mapping *mapping)
|
|
{
|
|
int err;
|
|
|
|
err = iommu_attach_device(mapping->domain, dev);
|
|
if (err)
|
|
return err;
|
|
|
|
kref_get(&mapping->kref);
|
|
dev->archdata.mapping = mapping;
|
|
set_dma_ops(dev, &iommu_ops);
|
|
|
|
pr_debug("Attached IOMMU controller to %s device.\n", dev_name(dev));
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(arm_iommu_attach_device);
|
|
|
|
/**
|
|
* arm_iommu_detach_device
|
|
* @dev: valid struct device pointer
|
|
*
|
|
* Detaches the provided device from a previously attached map.
|
|
* This voids the dma operations (dma_map_ops pointer)
|
|
*/
|
|
void arm_iommu_detach_device(struct device *dev)
|
|
{
|
|
struct dma_iommu_mapping *mapping;
|
|
|
|
mapping = to_dma_iommu_mapping(dev);
|
|
if (!mapping) {
|
|
dev_warn(dev, "Not attached\n");
|
|
return;
|
|
}
|
|
|
|
iommu_detach_device(mapping->domain, dev);
|
|
kref_put(&mapping->kref, release_iommu_mapping);
|
|
dev->archdata.mapping = NULL;
|
|
set_dma_ops(dev, NULL);
|
|
|
|
pr_debug("Detached IOMMU controller from %s device.\n", dev_name(dev));
|
|
}
|
|
EXPORT_SYMBOL(arm_iommu_detach_device);
|
|
|
|
#endif
|