Received: by 10.223.164.202 with SMTP id h10csp1194032wrb; Tue, 7 Nov 2017 23:52:22 -0800 (PST) X-Google-Smtp-Source: ABhQp+TqI8tYuBfhjaWK8aqhxYL3Qy6GDBejKjbDaq/ukn/dd1D6mvgoGEDKkU7XRM7hBK+BSbky X-Received: by 10.98.247.26 with SMTP id h26mr1583052pfi.233.1510127542451; Tue, 07 Nov 2017 23:52:22 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1510127542; cv=none; d=google.com; s=arc-20160816; b=Gid4ANDiJUnc8MgctYcvBtaMoj0FvOdcKn7elk1rTS9ZRmxvHiNLIw27g0cCNxsaKQ NQcLLYVh8E3trlNy/mfrqHaTBca5XKH9oQuoXOf8DYNtO0qOB6YULWgEIf7SQ7pRVQ7v 1blb2ZMTgWhS7TD61R77O4Y+zsCSE/AIeD0XhHrGWgsH7568LTV1Oj+Kuov74+7SMCxE jploiw9WzhIGIFCcXb0//UxWjo+MddhMzzmMBU7iX7BVc3haV+5yeBgtP/PUGQO4vZ7G xEbz71BsYxXCasHJNvG5tqyOHClagpoYeQzp2WC+jFoGYW0Sf5nObcUBhKSyFGXXRe+r cI0g== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:references:in-reply-to:references :in-reply-to:message-id:date:subject:cc:to:from:dkim-signature :arc-authentication-results; bh=eoM9XptiViYne5XeHQbbi5n90v6YpYZ1R++LiO3mi/g=; b=GkBoFquBCk9oEvIIMt+Uxbi2qZeESZWOdGeCXxg6KujEooHHgnbDxwcWOpSObAPxY9 TvYMqTeOnRzSHy03ZLcCvV1zxj+fd0Ep4sR8bEHvB8ExNTSnNry00UbH4wXabFxplrfu kniV0DGhYiTJpz2aSmx2vQq9RI9H6ttQc3C160ftTF6pTqlCOspJxs9Tz5wAjMahMfP5 cltyf3quz+qCZqGY86Nudfug0C6nDUjwIivJZuwrd4/8qaxzqn5LqiLKWpNQTnK88R9u 76AnDdZRnzWJllDbGzkn8KkMtyyihm4JtPqQ+lgudTvdgDehPF1YUttjN3TbnPQIw2tM Af7w== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@gmail.com header.s=20161025 header.b=p/zRvFk9; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=gmail.com Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id v81si3400674pfk.236.2017.11.07.23.52.10; Tue, 07 Nov 2017 23:52:22 -0800 (PST) Received-SPF: pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; dkim=pass header.i=@gmail.com header.s=20161025 header.b=p/zRvFk9; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=gmail.com Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1755021AbdKHG0l (ORCPT + 91 others); Wed, 8 Nov 2017 01:26:41 -0500 Received: from mail-pl0-f68.google.com ([209.85.160.68]:44526 "EHLO mail-pl0-f68.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1754069AbdKHGUa (ORCPT ); Wed, 8 Nov 2017 01:20:30 -0500 Received: by mail-pl0-f68.google.com with SMTP id g16so668710plj.1; Tue, 07 Nov 2017 22:20:30 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references :in-reply-to:references; bh=eoM9XptiViYne5XeHQbbi5n90v6YpYZ1R++LiO3mi/g=; b=p/zRvFk9iyOV5EiJHyIrpyAgCkZKTLSErByz1ksKV/+pQvv0d4A7gjdleoc84WNkVX teUHlC0fox4rvMTafsjtpHcuw7vmmbWl6GslOikLDKObPbA8ED5r0gfglwGGoLggi/a4 sIg4w+Vjyq9a/MiVmJxs8Vwg1TwEPPPw0d3PRN1kmCdlddXbMcn+aaAMXxmPCSiKbz3+ 0XVZHcXRWUBAHE+n60YpCivk1d8mIhKJMAld7sKzW3qBudgpNaICDQKKCREfnLNdPMgM bcwYN88mlpVPVGnbulxlauHm9RkvQU7fU223BobZrw4now4leh29gIG4qo8plftIZSfO 2lxw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:in-reply-to:references; bh=eoM9XptiViYne5XeHQbbi5n90v6YpYZ1R++LiO3mi/g=; b=G9UzwE0P3nnDqYjq+XEqoF/sYwFEkHGw6/haWJMoPG4Gplgbk26JMu2hUc8Wpte4Ui QoJEt7c54YLT3KWklIB6VQvBMDILrZH9/5KJGiD3WICfxfaQiuJXiNcQjE4i3r4fDdZX 5D2Rpvsw1VNtc6QbPbO6ppjKS+CQQR0IdG4LFvh20y3DEjWAPkw5cDAm7J18SlFA7yMJ IkIUE42byEWZYkSANk3MC1E22CkTAJiqfSLJ/oySPUbjpr1AFmln7zrWFj4DFlO858EG FT8iUWJIXO+NfPHkKgvz0M2tU/PLnfYvnZR0YMV4DtWkgIGONjTgpIBnR5baBW673cOE kVSQ== X-Gm-Message-State: AJaThX7KtSaJZ3/Fk/mcfBw81DqSCDWuI3jDMBy763O9i5GLyxCArY6Q 5peBpEK7OKeACduj1Ogwfj4= X-Received: by 10.84.246.197 with SMTP id j5mr1227397plt.7.1510122029361; Tue, 07 Nov 2017 22:20:29 -0800 (PST) Received: from app09.andestech.com ([118.163.51.199]) by smtp.gmail.com with ESMTPSA id a4sm6581339pfj.72.2017.11.07.22.20.26 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Tue, 07 Nov 2017 22:20:28 -0800 (PST) From: Greentime Hu To: greentime@andestech.com, linux-kernel@vger.kernel.org, arnd@arndb.de, linux-arch@vger.kernel.org, tglx@linutronix.de, jason@lakedaemon.net, marc.zyngier@arm.com, robh+dt@kernel.org, netdev@vger.kernel.org Cc: green.hu@gmail.com, Vincent Chen Subject: [PATCH 13/31] nds32: DMA mapping API Date: Wed, 8 Nov 2017 13:55:01 +0800 Message-Id: <763e6a252de1ad8ccd28344fd0676ae2f92f796d.1510118606.git.green.hu@gmail.com> X-Mailer: git-send-email 1.7.9.5 In-Reply-To: References: In-Reply-To: References: Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org From: Greentime Hu Signed-off-by: Vincent Chen Signed-off-by: Greentime Hu --- arch/nds32/include/asm/dma-mapping.h | 27 ++ arch/nds32/kernel/dma.c | 478 ++++++++++++++++++++++++++++++++++ 2 files changed, 505 insertions(+) create mode 100644 arch/nds32/include/asm/dma-mapping.h create mode 100644 arch/nds32/kernel/dma.c diff --git a/arch/nds32/include/asm/dma-mapping.h b/arch/nds32/include/asm/dma-mapping.h new file mode 100644 index 0000000..63dbeb7 --- /dev/null +++ b/arch/nds32/include/asm/dma-mapping.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2005-2017 Andes Technology Corporation + * + * 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 . + */ + +#ifndef ASMNDS32_DMA_MAPPING_H +#define ASMNDS32_DMA_MAPPING_H + +extern struct dma_map_ops nds32_dma_ops; + +static inline struct dma_map_ops *get_arch_dma_ops(struct bus_type *bus) +{ + return &nds32_dma_ops; +} + +#endif diff --git a/arch/nds32/kernel/dma.c b/arch/nds32/kernel/dma.c new file mode 100644 index 0000000..939702e --- /dev/null +++ b/arch/nds32/kernel/dma.c @@ -0,0 +1,478 @@ +/* + * Copyright (C) 2005-2017 Andes Technology Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * This is the page table (2MB) covering uncached, DMA consistent allocations + */ +static pte_t *consistent_pte; +static DEFINE_RAW_SPINLOCK(consistent_lock); + +/* + * VM region handling support. + * + * This should become something generic, handling VM region allocations for + * vmalloc and similar (ioremap, module space, etc). + * + * I envisage vmalloc()'s supporting vm_struct becoming: + * + * struct vm_struct { + * struct vm_region region; + * unsigned long flags; + * struct page **pages; + * unsigned int nr_pages; + * unsigned long phys_addr; + * }; + * + * get_vm_area() would then call vm_region_alloc with an appropriate + * struct vm_region head (eg): + * + * struct vm_region vmalloc_head = { + * .vm_list = LIST_HEAD_INIT(vmalloc_head.vm_list), + * .vm_start = VMALLOC_START, + * .vm_end = VMALLOC_END, + * }; + * + * However, vmalloc_head.vm_start is variable (typically, it is dependent on + * the amount of RAM found at boot time.) I would imagine that get_vm_area() + * would have to initialise this each time prior to calling vm_region_alloc(). + */ +struct arch_vm_region { + struct list_head vm_list; + unsigned long vm_start; + unsigned long vm_end; + struct page *vm_pages; +}; + +static struct arch_vm_region consistent_head = { + .vm_list = LIST_HEAD_INIT(consistent_head.vm_list), + .vm_start = CONSISTENT_BASE, + .vm_end = CONSISTENT_END, +}; + +static struct arch_vm_region *vm_region_alloc(struct arch_vm_region *head, + size_t size, int gfp) +{ + unsigned long addr = head->vm_start, end = head->vm_end - size; + unsigned long flags; + struct arch_vm_region *c, *new; + + new = kmalloc(sizeof(struct arch_vm_region), gfp); + if (!new) + goto out; + + raw_spin_lock_irqsave(&consistent_lock, flags); + + list_for_each_entry(c, &head->vm_list, vm_list) { + if ((addr + size) < addr) + goto nospc; + if ((addr + size) <= c->vm_start) + goto found; + addr = c->vm_end; + if (addr > end) + goto nospc; + } + +found: + /* + * Insert this entry _before_ the one we found. + */ + list_add_tail(&new->vm_list, &c->vm_list); + new->vm_start = addr; + new->vm_end = addr + size; + + raw_spin_unlock_irqrestore(&consistent_lock, flags); + return new; + +nospc: + raw_spin_unlock_irqrestore(&consistent_lock, flags); + kfree(new); +out: + return NULL; +} + +static struct arch_vm_region *vm_region_find(struct arch_vm_region *head, + unsigned long addr) +{ + struct arch_vm_region *c; + + list_for_each_entry(c, &head->vm_list, vm_list) { + if (c->vm_start == addr) + goto out; + } + c = NULL; +out: + return c; +} + +/* FIXME: attrs is not used. */ +static void *nds32_dma_alloc_coherent(struct device *dev, size_t size, + dma_addr_t * handle, gfp_t gfp, + unsigned long attrs) +{ + struct page *page; + struct arch_vm_region *c; + unsigned long order; + u64 mask = ISA_DMA_THRESHOLD, limit; + pgprot_t prot = pgprot_noncached(PAGE_KERNEL); + + if (!consistent_pte) { + pr_err("%s: not initialized\n", __func__); + dump_stack(); + return NULL; + } + + if (dev) { + mask = dev->coherent_dma_mask; + + /* + * Sanity check the DMA mask - it must be non-zero, and + * must be able to be satisfied by a DMA allocation. + */ + if (mask == 0) { + dev_warn(dev, "coherent DMA mask is unset\n"); + goto no_page; + } + + if ((~mask) & ISA_DMA_THRESHOLD) { + dev_warn(dev, "coherent DMA mask %#llx is smaller " + "than system GFP_DMA mask %#llx\n", + mask, (unsigned long long)ISA_DMA_THRESHOLD); + goto no_page; + } + } + + /* + * Sanity check the allocation size. + */ + size = PAGE_ALIGN(size); + limit = (mask + 1) & ~mask; + if ((limit && size >= limit) || + size >= (CONSISTENT_END - CONSISTENT_BASE)) { + pr_warn("coherent allocation too big " + "(requested %#x mask %#llx)\n", size, mask); + goto no_page; + } + + order = get_order(size); + + if (mask != 0xffffffff) + gfp |= GFP_DMA; + + page = alloc_pages(gfp, order); + if (!page) + goto no_page; + + /* + * Invalidate any data that might be lurking in the + * kernel direct-mapped region for device DMA. + */ + { + unsigned long kaddr = (unsigned long)page_address(page); + memset(page_address(page), 0, size); + cpu_dma_wbinval_range(kaddr, kaddr + size); + } + + /* + * Allocate a virtual address in the consistent mapping region. + */ + c = vm_region_alloc(&consistent_head, size, + gfp & ~(__GFP_DMA | __GFP_HIGHMEM)); + if (c) { + pte_t *pte = consistent_pte + CONSISTENT_OFFSET(c->vm_start); + struct page *end = page + (1 << order); + + c->vm_pages = page; + + /* + * Set the "dma handle" + */ + *handle = page_to_dma(dev, page); + + do { + BUG_ON(!pte_none(*pte)); + + /* + * x86 does not mark the pages reserved... + */ + SetPageReserved(page); + set_pte(pte, mk_pte(page, prot)); + page++; + pte++; + } while (size -= PAGE_SIZE); + + /* + * Free the otherwise unused pages. + */ + while (page < end) { + __free_page(page); + page++; + } + + return (void *)c->vm_start; + } + + if (page) + __free_pages(page, order); +no_page: + *handle = ~0; + return NULL; +} + +static void nds32_dma_free(struct device *dev, size_t size, void *cpu_addr, + dma_addr_t handle, unsigned long attrs) +{ + struct arch_vm_region *c; + unsigned long flags, addr; + pte_t *ptep; + + size = PAGE_ALIGN(size); + + raw_spin_lock_irqsave(&consistent_lock, flags); + + c = vm_region_find(&consistent_head, (unsigned long)cpu_addr); + if (!c) + goto no_area; + + if ((c->vm_end - c->vm_start) != size) { + pr_err("%s: freeing wrong coherent size (%ld != %d)\n", + __func__, c->vm_end - c->vm_start, size); + dump_stack(); + size = c->vm_end - c->vm_start; + } + + ptep = consistent_pte + CONSISTENT_OFFSET(c->vm_start); + addr = c->vm_start; + do { + pte_t pte = ptep_get_and_clear(&init_mm, addr, ptep); + unsigned long pfn; + + ptep++; + addr += PAGE_SIZE; + + if (!pte_none(pte) && pte_present(pte)) { + pfn = pte_pfn(pte); + + if (pfn_valid(pfn)) { + struct page *page = pfn_to_page(pfn); + + /* + * x86 does not mark the pages reserved... + */ + ClearPageReserved(page); + + __free_page(page); + continue; + } + } + + pr_crit("%s: bad page in kernel page table\n", __func__); + } while (size -= PAGE_SIZE); + + flush_tlb_kernel_range(c->vm_start, c->vm_end); + + list_del(&c->vm_list); + + raw_spin_unlock_irqrestore(&consistent_lock, flags); + + kfree(c); + return; + +no_area: + raw_spin_unlock_irqrestore(&consistent_lock, flags); + pr_err("%s: trying to free invalid coherent area: %p\n", + __func__, cpu_addr); + dump_stack(); +} + +/* + * Initialise the consistent memory allocation. + */ +static int __init consistent_init(void) +{ + pgd_t *pgd; + pmd_t *pmd; + pte_t *pte; + int ret = 0; + + do { + pgd = pgd_offset(&init_mm, CONSISTENT_BASE); + pmd = pmd_alloc(&init_mm, pgd, CONSISTENT_BASE); + if (!pmd) { + pr_err("%s: no pmd tables\n", __func__); + ret = -ENOMEM; + break; + } + /* The first level mapping may be created in somewhere. + * It's not necessary to warn here. */ + /* WARN_ON(!pmd_none(*pmd)); */ + + pte = pte_alloc_kernel(pmd, CONSISTENT_BASE); + if (!pte) { + ret = -ENOMEM; + break; + } + + consistent_pte = pte; + } while (0); + + return ret; +} + +core_initcall(consistent_init); +static void consistent_sync(void *vaddr, size_t size, int direction); +static dma_addr_t nds32_dma_map_page(struct device *dev, struct page *page, + unsigned long offset, size_t size, + enum dma_data_direction dir, + unsigned long attrs) +{ + consistent_sync((void *)(page_address(page) + offset), size, dir); + return page_to_phys(page) + offset; +} + +static void nds32_dma_unmap_page(struct device *dev, dma_addr_t handle, + size_t size, enum dma_data_direction dir, + unsigned long attrs) +{ + consistent_sync(phys_to_virt(handle), size, dir); +} + +/* + * Make an area consistent for devices. + */ +static void consistent_sync(void *vaddr, size_t size, int direction) +{ + unsigned long start = (unsigned long)vaddr; + unsigned long end = start + size; + + switch (direction) { + case DMA_FROM_DEVICE: /* invalidate only */ + cpu_dma_inval_range(start, end); + break; + case DMA_TO_DEVICE: /* writeback only */ + cpu_dma_wb_range(start, end); + break; + case DMA_BIDIRECTIONAL: /* writeback and invalidate */ + cpu_dma_wbinval_range(start, end); + break; + default: + BUG(); + } +} + +static int nds32_dma_map_sg(struct device *dev, struct scatterlist *sg, + int nents, enum dma_data_direction dir, + unsigned long attrs) +{ + int i; + + for (i = 0; i < nents; i++, sg++) { + void *virt; + unsigned long pfn; + struct page *page = sg_page(sg); + + sg->dma_address = page_to_dma(dev, page) + sg->offset; + pfn = page_to_pfn(page) + sg->offset / PAGE_SIZE; + page = pfn_to_page(pfn); + if (PageHighMem(page)) { + virt = kmap_atomic(page); + consistent_sync(virt, sg->length, dir); + kunmap_atomic(virt); + } else { + if (sg->offset > PAGE_SIZE) + panic("sg->offset:%08x > PAGE_SIZE\n", + sg->offset); + virt = page_address(page) + sg->offset; + consistent_sync(virt, sg->length, dir); + } + } + return nents; +} + +static void nds32_dma_unmap_sg(struct device *dev, struct scatterlist *sg, + int nhwentries, enum dma_data_direction dir, + unsigned long attrs) +{ +} + +static void +nds32_dma_sync_single_for_cpu(struct device *dev, dma_addr_t handle, + size_t size, enum dma_data_direction dir) +{ + consistent_sync((void *)dma_to_virt(dev, handle), size, dir); +} + +static void +nds32_dma_sync_single_for_device(struct device *dev, dma_addr_t handle, + size_t size, enum dma_data_direction dir) +{ + consistent_sync((void *)dma_to_virt(dev, handle), size, dir); +} + +static void +nds32_dma_sync_sg_for_cpu(struct device *dev, struct scatterlist *sg, int nents, + enum dma_data_direction dir) +{ + int i; + + for (i = 0; i < nents; i++, sg++) { + char *virt = + page_address((struct page *)sg->page_link) + sg->offset; + consistent_sync(virt, sg->length, dir); + } +} + +static void +nds32_dma_sync_sg_for_device(struct device *dev, struct scatterlist *sg, + int nents, enum dma_data_direction dir) +{ + int i; + + for (i = 0; i < nents; i++, sg++) { + char *virt = + page_address((struct page *)sg->page_link) + sg->offset; + consistent_sync(virt, sg->length, dir); + } +} + +struct dma_map_ops nds32_dma_ops = { + .alloc = nds32_dma_alloc_coherent, + .free = nds32_dma_free, + .map_page = nds32_dma_map_page, + .unmap_page = nds32_dma_unmap_page, + .map_sg = nds32_dma_map_sg, + .unmap_sg = nds32_dma_unmap_sg, + .sync_single_for_device = nds32_dma_sync_single_for_device, + .sync_single_for_cpu = nds32_dma_sync_single_for_cpu, + .sync_sg_for_cpu = nds32_dma_sync_sg_for_cpu, + .sync_sg_for_device = nds32_dma_sync_sg_for_device, +}; + +EXPORT_SYMBOL(nds32_dma_ops); -- 1.7.9.5 From 1586949712194803073@xxx Sat Dec 16 14:06:21 +0000 2017 X-GM-THRID: 1586049229904585981 X-Gmail-Labels: Inbox,Category Forums