From patchwork Thu Oct 11 13:26:59 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Avi Kivity X-Patchwork-Id: 190916 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [208.118.235.17]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (Client did not present a certificate) by ozlabs.org (Postfix) with ESMTPS id 240372C007B for ; Fri, 12 Oct 2012 01:13:35 +1100 (EST) Received: from localhost ([::1]:53348 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1TMIoT-00016b-2m for incoming@patchwork.ozlabs.org; Thu, 11 Oct 2012 09:28:33 -0400 Received: from eggs.gnu.org ([208.118.235.92]:34868) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1TMInU-0006xp-NE for qemu-devel@nongnu.org; Thu, 11 Oct 2012 09:27:42 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1TMInL-0008S4-47 for qemu-devel@nongnu.org; Thu, 11 Oct 2012 09:27:32 -0400 Received: from mx1.redhat.com ([209.132.183.28]:6716) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1TMInK-0008Rk-Pl for qemu-devel@nongnu.org; Thu, 11 Oct 2012 09:27:23 -0400 Received: from int-mx11.intmail.prod.int.phx2.redhat.com (int-mx11.intmail.prod.int.phx2.redhat.com [10.5.11.24]) by mx1.redhat.com (8.14.4/8.14.4) with ESMTP id q9BDRJjd024048 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=OK); Thu, 11 Oct 2012 09:27:19 -0400 Received: from s01.tlv.redhat.com (s01.tlv.redhat.com [10.35.255.8]) by int-mx11.intmail.prod.int.phx2.redhat.com (8.14.4/8.14.4) with ESMTP id q9BDR72o003602; Thu, 11 Oct 2012 09:27:17 -0400 From: Avi Kivity To: qemu-devel@nongnu.org, Blue Swirl , Anthony Liguori , "Michael S. Tsirkin" , Alex Williamson , liu ping fan , Paolo Bonzini Date: Thu, 11 Oct 2012 15:26:59 +0200 Message-Id: <1349962023-560-4-git-send-email-avi@redhat.com> In-Reply-To: <1349962023-560-1-git-send-email-avi@redhat.com> References: <1349962023-560-1-git-send-email-avi@redhat.com> X-Scanned-By: MIMEDefang 2.68 on 10.5.11.24 X-detected-operating-system: by eggs.gnu.org: Genre and OS details not recognized. X-Received-From: 209.132.183.28 Subject: [Qemu-devel] [RFC v1 3/7] memory: iommu support X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Add a new memory region type that translates addresses it is given, then forwards them to a target address space. This is similar to an alias, except that the mapping is more flexible than a linear translation and trucation, and also less efficient since the translation happens at runtime. The implementation uses an AddressSpace mapping the target region to avoid hierarchical dispatch all the way to the resolved region; only iommu regions are looked up dynamically. Signed-off-by: Avi Kivity --- exec.c | 28 ++++++++++++++--- memory.c | 107 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ memory.h | 46 +++++++++++++++++++++++++++ 3 files changed, 177 insertions(+), 4 deletions(-) diff --git a/exec.c b/exec.c index 328753d..13df16c 100644 --- a/exec.c +++ b/exec.c @@ -3498,6 +3498,7 @@ void cpu_physical_memory_write_rom(target_phys_addr_t addr, typedef struct { void *buffer; + AddressSpace *as; target_phys_addr_t addr; target_phys_addr_t len; } BounceBuffer; @@ -3563,23 +3564,42 @@ void *address_space_map(AddressSpace *as, ram_addr_t raddr = RAM_ADDR_MAX; ram_addr_t rlen; void *ret; + IOMMUTLBEntry iotlb; + target_phys_addr_t xlat; + AddressSpace *as_xlat; while (len > 0) { + xlat = addr; + as_xlat = as; page = addr & TARGET_PAGE_MASK; l = (page + TARGET_PAGE_SIZE) - addr; if (l > len) l = len; section = phys_page_find(d, page >> TARGET_PAGE_BITS); + while (section->mr->iommu_ops) { + iotlb = section->mr->iommu_ops->translate(section->mr, addr, is_write); + if (iotlb.valid) { + xlat = ((iotlb.translated_addr & ~iotlb.addr_mask) + | (addr & iotlb.addr_mask)); + as_xlat = section->mr->iommu_target_as; + l = (MIN(xlat + l - 1, xlat | iotlb.addr_mask) - xlat) + 1; + section = phys_page_find(as_xlat->dispatch, xlat >> TARGET_PAGE_BITS); + } else { + section = &phys_sections[phys_section_unassigned]; + } + } + if (!(memory_region_is_ram(section->mr) && !section->readonly)) { if (todo || bounce.buffer) { break; } bounce.buffer = qemu_memalign(TARGET_PAGE_SIZE, TARGET_PAGE_SIZE); - bounce.addr = addr; + bounce.addr = xlat; + bounce.as = as_xlat; bounce.len = l; if (!is_write) { - address_space_read(as, addr, bounce.buffer, l); + address_space_read(as_xlat, xlat, bounce.buffer, l); } *plen = l; @@ -3587,7 +3607,7 @@ void *address_space_map(AddressSpace *as, } if (!todo) { raddr = memory_region_get_ram_addr(section->mr) - + memory_region_section_addr(section, addr); + + memory_region_section_addr(section, xlat); } len -= l; @@ -3632,7 +3652,7 @@ void address_space_unmap(AddressSpace *as, void *buffer, target_phys_addr_t len, return; } if (is_write) { - address_space_write(as, bounce.addr, bounce.buffer, access_len); + address_space_write(bounce.as, bounce.addr, bounce.buffer, access_len); } qemu_vfree(bounce.buffer); bounce.buffer = NULL; diff --git a/memory.c b/memory.c index 5df6177..1d92bb8 100644 --- a/memory.c +++ b/memory.c @@ -775,6 +775,12 @@ static void memory_region_destructor_rom_device(MemoryRegion *mr) qemu_ram_free(mr->ram_addr & TARGET_PAGE_MASK); } +static void memory_region_destructor_iommu(MemoryRegion *mr) +{ + address_space_destroy(mr->iommu_target_as); + g_free(mr->iommu_target_as); +} + static bool memory_region_wrong_endianness(MemoryRegion *mr) { #ifdef TARGET_WORDS_BIGENDIAN @@ -789,6 +795,7 @@ void memory_region_init(MemoryRegion *mr, uint64_t size) { mr->ops = NULL; + mr->iommu_ops = NULL; mr->parent = NULL; mr->size = int128_make64(size); if (size == UINT64_MAX) { @@ -980,6 +987,101 @@ void memory_region_init_rom_device(MemoryRegion *mr, mr->ram_addr = qemu_ram_alloc(size, mr); } +static void memory_region_iommu_rw(MemoryRegion *iommu, target_phys_addr_t addr, + uint8_t *buf, unsigned len, bool is_write) +{ + IOMMUTLBEntry tlb; + unsigned clen; + target_phys_addr_t xlat; + + while (len) { + tlb = iommu->iommu_ops->translate(iommu, addr, is_write); + clen = (MIN(addr | tlb.addr_mask, addr + len - 1) - addr) + 1; + if (tlb.valid) { + xlat = (tlb.translated_addr & ~tlb.addr_mask) | (addr & tlb.addr_mask); + address_space_rw(iommu->iommu_target_as, xlat, buf, clen, is_write); + } else { + if (!is_write) { + memset(buf, 0xff, clen); + } + } + buf += clen; + addr += clen; + len -= clen; + } +} + +static uint64_t memory_region_iommu_read(void *opaque, target_phys_addr_t addr, + unsigned size) +{ + MemoryRegion *iommu = opaque; + union { + uint8_t buf[8]; + uint8_t u8; + uint16_t u16; + uint32_t u32; + uint64_t u64; + } ret; + + memory_region_iommu_rw(iommu, addr, ret.buf, size, false); + switch (size) { + case 1: return ret.u8; + case 2: return ret.u16; + case 4: return ret.u32; + case 8: return ret.u64; + default: abort(); + } +} + +static void memory_region_iommu_write(void *opaque, target_phys_addr_t addr, + uint64_t data, unsigned size) +{ + MemoryRegion *iommu = opaque; + union { + uint8_t buf[8]; + uint8_t u8; + uint16_t u16; + uint32_t u32; + uint64_t u64; + } in; + + switch (size) { + case 1: in.u8 = data; break; + case 2: in.u16 = data; break; + case 4: in.u32 = data; break; + case 8: in.u64 = data; break; + default: abort(); + } + memory_region_iommu_rw(iommu, addr, in.buf, size, true); +} + +static MemoryRegionOps memory_region_iommu_ops = { + .read = memory_region_iommu_read, + .write = memory_region_iommu_write, +#ifdef HOST_BIGENDIAN + .endianness = DEVICE_BIG_ENDIAN, +#else + .endianness = DEVICE_LITTLE_ENDIAN, +#endif +}; + +void memory_region_init_iommu(MemoryRegion *mr, + MemoryRegionIOMMUOps *ops, + MemoryRegion *target, + const char *name, + uint64_t size) +{ + memory_region_init(mr, name, size); + mr->ops = &memory_region_iommu_ops; + mr->iommu_ops = ops, + mr->opaque = mr; + mr->terminates = true; /* then re-forwards */ + mr->destructor = memory_region_destructor_iommu; + mr->iommu_target = target; + mr->iommu_target_as = g_new(AddressSpace, 1); + address_space_init(mr->iommu_target_as, target); +} + static uint64_t invalid_read(void *opaque, target_phys_addr_t addr, unsigned size) { @@ -1053,6 +1155,11 @@ bool memory_region_is_rom(MemoryRegion *mr) return mr->ram && mr->readonly; } +bool memory_region_is_iommu(MemoryRegion *mr) +{ + return mr->iommu_ops; +} + void memory_region_set_log(MemoryRegion *mr, bool log, unsigned client) { uint8_t mask = 1 << client; diff --git a/memory.h b/memory.h index 79393f1..299d584 100644 --- a/memory.h +++ b/memory.h @@ -113,12 +113,29 @@ struct MemoryRegionOps { const MemoryRegionMmio old_mmio; }; +typedef struct IOMMUTLBEntry IOMMUTLBEntry; +typedef struct MemoryRegionIOMMUOps MemoryRegionIOMMUOps; + +struct IOMMUTLBEntry { + target_phys_addr_t device_addr; + target_phys_addr_t translated_addr; + target_phys_addr_t addr_mask; /* 0xfff = 4k translation */ + bool valid; +}; + +struct MemoryRegionIOMMUOps { + /* Returns a TLB entry that contains a given address. */ + IOMMUTLBEntry (*translate)(MemoryRegion *iommu, target_phys_addr_t addr, + bool is_write); +}; + typedef struct CoalescedMemoryRange CoalescedMemoryRange; typedef struct MemoryRegionIoeventfd MemoryRegionIoeventfd; struct MemoryRegion { /* All fields are private - violators will be prosecuted */ const MemoryRegionOps *ops; + const MemoryRegionIOMMUOps *iommu_ops; void *opaque; MemoryRegion *parent; Int128 size; @@ -145,6 +162,8 @@ struct MemoryRegion { uint8_t dirty_log_mask; unsigned ioeventfd_nb; MemoryRegionIoeventfd *ioeventfds; + MemoryRegion *iommu_target; + struct AddressSpace *iommu_target_as; }; struct MemoryRegionPortio { @@ -334,6 +353,24 @@ void memory_region_init_rom_device(MemoryRegion *mr, void memory_region_init_reservation(MemoryRegion *mr, const char *name, uint64_t size); + +/** + * memory_region_init_iommu: Initialize a memory region that translates addresses + * + * An IOMMU region translates addresses and forwards accesses to a target memory region. + * + * @mr: the #MemoryRegion to be initialized + * @ops: a function that translates addresses into the @target region + * @target: a #MemoryRegion that will be used to satisfy accesses to translated addresses + * @name: used for debugging; not visible to the user or ABI + * @size: size of the region. + */ +void memory_region_init_iommu(MemoryRegion *mr, + MemoryRegionIOMMUOps *ops, + MemoryRegion *target, + const char *name, + uint64_t size); + /** * memory_region_destroy: Destroy a memory region and reclaim all resources. * @@ -373,6 +410,15 @@ static inline bool memory_region_is_romd(MemoryRegion *mr) } /** + * memory_region_is_ram: check whether a memory region is an iommu + * + * Returns %true is a memory region is an iommu. + * + * @mr: the memory region being queried + */ +bool memory_region_is_iommu(MemoryRegion *mr); + +/** * memory_region_name: get a memory region's name * * Returns the string that was used to initialize the memory region.