2010-08-31 19:41:25 +04:00
|
|
|
/*
|
|
|
|
* Copyright (C) 2011 Citrix Ltd.
|
|
|
|
*
|
|
|
|
* This work is licensed under the terms of the GNU GPL, version 2. See
|
|
|
|
* the COPYING file in the top-level directory.
|
|
|
|
*
|
2012-01-13 20:44:23 +04:00
|
|
|
* Contributions after 2012-01-13 are licensed under the terms of the
|
|
|
|
* GNU GPL, version 2 or (at your option) any later version.
|
2010-08-31 19:41:25 +04:00
|
|
|
*/
|
|
|
|
|
2016-01-26 21:17:06 +03:00
|
|
|
#include "qemu/osdep.h"
|
2018-06-25 15:42:03 +03:00
|
|
|
#include "qemu/units.h"
|
2017-09-11 22:52:53 +03:00
|
|
|
#include "qemu/error-report.h"
|
2010-08-31 19:41:25 +04:00
|
|
|
|
|
|
|
#include <sys/resource.h>
|
|
|
|
|
2019-01-08 17:48:46 +03:00
|
|
|
#include "hw/xen/xen-legacy-backend.h"
|
2012-12-17 21:20:00 +04:00
|
|
|
#include "qemu/bitmap.h"
|
2010-08-31 19:41:25 +04:00
|
|
|
|
2019-08-12 08:23:59 +03:00
|
|
|
#include "sysemu/runstate.h"
|
2012-12-17 21:20:04 +04:00
|
|
|
#include "sysemu/xen-mapcache.h"
|
2017-04-06 02:21:31 +03:00
|
|
|
#include "trace.h"
|
2010-08-31 19:41:25 +04:00
|
|
|
|
|
|
|
|
|
|
|
//#define MAPCACHE_DEBUG
|
|
|
|
|
|
|
|
#ifdef MAPCACHE_DEBUG
|
|
|
|
# define DPRINTF(fmt, ...) do { \
|
|
|
|
fprintf(stderr, "xen_mapcache: " fmt, ## __VA_ARGS__); \
|
|
|
|
} while (0)
|
|
|
|
#else
|
|
|
|
# define DPRINTF(fmt, ...) do { } while (0)
|
|
|
|
#endif
|
|
|
|
|
2013-12-18 23:17:32 +04:00
|
|
|
#if HOST_LONG_BITS == 32
|
2010-08-31 19:41:25 +04:00
|
|
|
# define MCACHE_BUCKET_SHIFT 16
|
2011-03-22 17:50:28 +03:00
|
|
|
# define MCACHE_MAX_SIZE (1UL<<31) /* 2GB Cap */
|
2013-12-18 23:17:32 +04:00
|
|
|
#else
|
2010-08-31 19:41:25 +04:00
|
|
|
# define MCACHE_BUCKET_SHIFT 20
|
2011-03-22 17:50:28 +03:00
|
|
|
# define MCACHE_MAX_SIZE (1UL<<35) /* 32GB Cap */
|
2010-08-31 19:41:25 +04:00
|
|
|
#endif
|
|
|
|
#define MCACHE_BUCKET_SIZE (1UL << MCACHE_BUCKET_SHIFT)
|
|
|
|
|
2011-09-09 16:50:18 +04:00
|
|
|
/* This is the size of the virtual address space reserve to QEMU that will not
|
|
|
|
* be use by MapCache.
|
|
|
|
* From empirical tests I observed that qemu use 75MB more than the
|
|
|
|
* max_mcache_size.
|
|
|
|
*/
|
2018-06-25 15:42:03 +03:00
|
|
|
#define NON_MCACHE_MEMORY_SIZE (80 * MiB)
|
2011-09-09 16:50:18 +04:00
|
|
|
|
2010-08-31 19:41:25 +04:00
|
|
|
typedef struct MapCacheEntry {
|
2012-10-23 14:30:10 +04:00
|
|
|
hwaddr paddr_index;
|
2010-08-31 19:41:25 +04:00
|
|
|
uint8_t *vaddr_base;
|
2011-05-19 21:35:42 +04:00
|
|
|
unsigned long *valid_mapping;
|
2022-01-24 13:44:50 +03:00
|
|
|
uint32_t lock;
|
2017-07-11 01:40:01 +03:00
|
|
|
#define XEN_MAPCACHE_ENTRY_DUMMY (1 << 0)
|
|
|
|
uint8_t flags;
|
2012-10-23 14:30:10 +04:00
|
|
|
hwaddr size;
|
2010-08-31 19:41:25 +04:00
|
|
|
struct MapCacheEntry *next;
|
|
|
|
} MapCacheEntry;
|
|
|
|
|
|
|
|
typedef struct MapCacheRev {
|
|
|
|
uint8_t *vaddr_req;
|
2012-10-23 14:30:10 +04:00
|
|
|
hwaddr paddr_index;
|
|
|
|
hwaddr size;
|
2010-08-31 19:41:25 +04:00
|
|
|
QTAILQ_ENTRY(MapCacheRev) next;
|
xen/mapcache: store dma information in revmapcache entries for debugging
The Xen mapcache is able to create long term mappings, they are called
"locked" mappings. The third parameter of the xen_map_cache call
specifies if a mapping is a "locked" mapping.
>From the QEMU point of view there are two kinds of long term mappings:
[a] device memory mappings, such as option roms and video memory
[b] dma mappings, created by dma_memory_map & friends
After certain operations, ballooning a VM in particular, Xen asks QEMU
kindly to destroy all mappings. However, certainly [a] mappings are
present and cannot be removed. That's not a problem as they are not
affected by balloonning. The *real* problem is that if there are any
mappings of type [b], any outstanding dma operations could fail. This is
a known shortcoming. In other words, when Xen asks QEMU to destroy all
mappings, it is an error if any [b] mappings exist.
However today we have no way of distinguishing [a] from [b]. Because of
that, we cannot even print a decent warning.
This patch introduces a new "dma" bool field to MapCacheRev entires, to
remember if a given mapping is for dma or is a long term device memory
mapping. When xen_invalidate_map_cache is called, we print a warning if
any [b] mappings exist. We ignore [a] mappings.
Mappings created by qemu_map_ram_ptr are assumed to be [a], while
mappings created by address_space_map->qemu_ram_ptr_length are assumed
to be [b].
The goal of the patch is to make debugging and system understanding
easier.
Signed-off-by: Stefano Stabellini <sstabellini@kernel.org>
Acked-by: Paolo Bonzini <pbonzini@redhat.com>
Acked-by: Anthony PERARD <anthony.perard@citrix.com>
2017-05-04 00:00:35 +03:00
|
|
|
bool dma;
|
2010-08-31 19:41:25 +04:00
|
|
|
} MapCacheRev;
|
|
|
|
|
|
|
|
typedef struct MapCache {
|
|
|
|
MapCacheEntry *entry;
|
|
|
|
unsigned long nr_buckets;
|
2018-12-06 13:58:10 +03:00
|
|
|
QTAILQ_HEAD(, MapCacheRev) locked_entries;
|
2010-08-31 19:41:25 +04:00
|
|
|
|
|
|
|
/* For most cases (>99.9%), the page address is the same. */
|
2013-04-02 17:23:40 +04:00
|
|
|
MapCacheEntry *last_entry;
|
2010-08-31 19:41:25 +04:00
|
|
|
unsigned long max_mcache_size;
|
|
|
|
unsigned int mcache_bucket_shift;
|
2012-01-18 16:21:38 +04:00
|
|
|
|
|
|
|
phys_offset_to_gaddr_t phys_offset_to_gaddr;
|
2015-01-14 13:20:56 +03:00
|
|
|
QemuMutex lock;
|
2012-01-18 16:21:38 +04:00
|
|
|
void *opaque;
|
2010-08-31 19:41:25 +04:00
|
|
|
} MapCache;
|
|
|
|
|
|
|
|
static MapCache *mapcache;
|
|
|
|
|
2015-01-14 13:20:56 +03:00
|
|
|
static inline void mapcache_lock(void)
|
|
|
|
{
|
|
|
|
qemu_mutex_lock(&mapcache->lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void mapcache_unlock(void)
|
|
|
|
{
|
|
|
|
qemu_mutex_unlock(&mapcache->lock);
|
|
|
|
}
|
|
|
|
|
2011-05-19 21:35:42 +04:00
|
|
|
static inline int test_bits(int nr, int size, const unsigned long *addr)
|
|
|
|
{
|
|
|
|
unsigned long res = find_next_zero_bit(addr, size + nr, nr);
|
|
|
|
if (res >= nr + size)
|
|
|
|
return 1;
|
|
|
|
else
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-01-18 16:21:38 +04:00
|
|
|
void xen_map_cache_init(phys_offset_to_gaddr_t f, void *opaque)
|
2010-08-31 19:41:25 +04:00
|
|
|
{
|
|
|
|
unsigned long size;
|
|
|
|
struct rlimit rlimit_as;
|
|
|
|
|
2022-03-15 17:41:56 +03:00
|
|
|
mapcache = g_new0(MapCache, 1);
|
2010-08-31 19:41:25 +04:00
|
|
|
|
2012-01-18 16:21:38 +04:00
|
|
|
mapcache->phys_offset_to_gaddr = f;
|
|
|
|
mapcache->opaque = opaque;
|
2015-01-14 13:20:56 +03:00
|
|
|
qemu_mutex_init(&mapcache->lock);
|
2012-01-18 16:21:38 +04:00
|
|
|
|
2010-08-31 19:41:25 +04:00
|
|
|
QTAILQ_INIT(&mapcache->locked_entries);
|
|
|
|
|
2011-09-09 16:50:18 +04:00
|
|
|
if (geteuid() == 0) {
|
|
|
|
rlimit_as.rlim_cur = RLIM_INFINITY;
|
|
|
|
rlimit_as.rlim_max = RLIM_INFINITY;
|
|
|
|
mapcache->max_mcache_size = MCACHE_MAX_SIZE;
|
2011-03-22 17:50:28 +03:00
|
|
|
} else {
|
2011-09-09 16:50:18 +04:00
|
|
|
getrlimit(RLIMIT_AS, &rlimit_as);
|
|
|
|
rlimit_as.rlim_cur = rlimit_as.rlim_max;
|
|
|
|
|
|
|
|
if (rlimit_as.rlim_max != RLIM_INFINITY) {
|
2017-09-11 22:52:53 +03:00
|
|
|
warn_report("QEMU's maximum size of virtual"
|
2017-09-11 22:52:56 +03:00
|
|
|
" memory is not infinity");
|
2011-09-09 16:50:18 +04:00
|
|
|
}
|
|
|
|
if (rlimit_as.rlim_max < MCACHE_MAX_SIZE + NON_MCACHE_MEMORY_SIZE) {
|
|
|
|
mapcache->max_mcache_size = rlimit_as.rlim_max -
|
|
|
|
NON_MCACHE_MEMORY_SIZE;
|
|
|
|
} else {
|
|
|
|
mapcache->max_mcache_size = MCACHE_MAX_SIZE;
|
|
|
|
}
|
2011-03-22 17:50:28 +03:00
|
|
|
}
|
|
|
|
|
2010-08-31 19:41:25 +04:00
|
|
|
setrlimit(RLIMIT_AS, &rlimit_as);
|
|
|
|
|
|
|
|
mapcache->nr_buckets =
|
|
|
|
(((mapcache->max_mcache_size >> XC_PAGE_SHIFT) +
|
|
|
|
(1UL << (MCACHE_BUCKET_SHIFT - XC_PAGE_SHIFT)) - 1) >>
|
|
|
|
(MCACHE_BUCKET_SHIFT - XC_PAGE_SHIFT));
|
|
|
|
|
|
|
|
size = mapcache->nr_buckets * sizeof (MapCacheEntry);
|
|
|
|
size = (size + XC_PAGE_SIZE - 1) & ~(XC_PAGE_SIZE - 1);
|
2011-06-22 00:59:08 +04:00
|
|
|
DPRINTF("%s, nr_buckets = %lx size %lu\n", __func__,
|
|
|
|
mapcache->nr_buckets, size);
|
2011-08-21 07:09:37 +04:00
|
|
|
mapcache->entry = g_malloc0(size);
|
2010-08-31 19:41:25 +04:00
|
|
|
}
|
|
|
|
|
2011-06-22 00:59:08 +04:00
|
|
|
static void xen_remap_bucket(MapCacheEntry *entry,
|
2017-07-11 01:40:02 +03:00
|
|
|
void *vaddr,
|
2012-10-23 14:30:10 +04:00
|
|
|
hwaddr size,
|
2017-07-11 01:40:01 +03:00
|
|
|
hwaddr address_index,
|
|
|
|
bool dummy)
|
2010-08-31 19:41:25 +04:00
|
|
|
{
|
|
|
|
uint8_t *vaddr_base;
|
|
|
|
xen_pfn_t *pfns;
|
|
|
|
int *err;
|
2011-03-22 17:50:28 +03:00
|
|
|
unsigned int i;
|
2012-10-23 14:30:10 +04:00
|
|
|
hwaddr nb_pfn = size >> XC_PAGE_SHIFT;
|
2010-08-31 19:41:25 +04:00
|
|
|
|
2011-06-22 00:59:08 +04:00
|
|
|
trace_xen_remap_bucket(address_index);
|
2010-08-31 19:41:25 +04:00
|
|
|
|
2022-03-15 17:41:56 +03:00
|
|
|
pfns = g_new0(xen_pfn_t, nb_pfn);
|
|
|
|
err = g_new0(int, nb_pfn);
|
2010-08-31 19:41:25 +04:00
|
|
|
|
|
|
|
if (entry->vaddr_base != NULL) {
|
2017-07-11 01:40:02 +03:00
|
|
|
if (!(entry->flags & XEN_MAPCACHE_ENTRY_DUMMY)) {
|
2021-04-29 14:27:00 +03:00
|
|
|
ram_block_notify_remove(entry->vaddr_base, entry->size,
|
|
|
|
entry->size);
|
2017-07-11 01:40:02 +03:00
|
|
|
}
|
2021-04-20 06:35:02 +03:00
|
|
|
|
|
|
|
/*
|
|
|
|
* If an entry is being replaced by another mapping and we're using
|
|
|
|
* MAP_FIXED flag for it - there is possibility of a race for vaddr
|
|
|
|
* address with another thread doing an mmap call itself
|
|
|
|
* (see man 2 mmap). To avoid that we skip explicit unmapping here
|
|
|
|
* and allow the kernel to destroy the previous mappings by replacing
|
|
|
|
* them in mmap call later.
|
|
|
|
*
|
|
|
|
* Non-identical replacements are not allowed therefore.
|
|
|
|
*/
|
|
|
|
assert(!vaddr || (entry->vaddr_base == vaddr && entry->size == size));
|
|
|
|
|
|
|
|
if (!vaddr && munmap(entry->vaddr_base, entry->size) != 0) {
|
2010-08-31 19:41:25 +04:00
|
|
|
perror("unmap fails");
|
|
|
|
exit(-1);
|
|
|
|
}
|
|
|
|
}
|
2015-08-26 15:02:53 +03:00
|
|
|
g_free(entry->valid_mapping);
|
|
|
|
entry->valid_mapping = NULL;
|
2010-08-31 19:41:25 +04:00
|
|
|
|
|
|
|
for (i = 0; i < nb_pfn; i++) {
|
|
|
|
pfns[i] = (address_index << (MCACHE_BUCKET_SHIFT-XC_PAGE_SHIFT)) + i;
|
|
|
|
}
|
|
|
|
|
2019-03-18 20:37:31 +03:00
|
|
|
/*
|
|
|
|
* If the caller has requested the mapping at a specific address use
|
|
|
|
* MAP_FIXED to make sure it's honored.
|
|
|
|
*/
|
2017-07-11 01:40:01 +03:00
|
|
|
if (!dummy) {
|
2017-07-11 01:40:02 +03:00
|
|
|
vaddr_base = xenforeignmemory_map2(xen_fmem, xen_domid, vaddr,
|
2019-03-18 20:37:31 +03:00
|
|
|
PROT_READ | PROT_WRITE,
|
|
|
|
vaddr ? MAP_FIXED : 0,
|
2017-07-11 01:40:01 +03:00
|
|
|
nb_pfn, pfns, err);
|
|
|
|
if (vaddr_base == NULL) {
|
2017-07-11 01:40:02 +03:00
|
|
|
perror("xenforeignmemory_map2");
|
2017-07-11 01:40:01 +03:00
|
|
|
exit(-1);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
/*
|
|
|
|
* We create dummy mappings where we are unable to create a foreign
|
|
|
|
* mapping immediately due to certain circumstances (i.e. on resume now)
|
|
|
|
*/
|
2017-07-11 01:40:02 +03:00
|
|
|
vaddr_base = mmap(vaddr, size, PROT_READ | PROT_WRITE,
|
2019-03-18 20:37:31 +03:00
|
|
|
MAP_ANON | MAP_SHARED | (vaddr ? MAP_FIXED : 0),
|
|
|
|
-1, 0);
|
2017-12-01 21:31:57 +03:00
|
|
|
if (vaddr_base == MAP_FAILED) {
|
2017-07-11 01:40:01 +03:00
|
|
|
perror("mmap");
|
|
|
|
exit(-1);
|
|
|
|
}
|
2010-08-31 19:41:25 +04:00
|
|
|
}
|
|
|
|
|
2017-07-11 01:40:02 +03:00
|
|
|
if (!(entry->flags & XEN_MAPCACHE_ENTRY_DUMMY)) {
|
2021-04-29 14:27:00 +03:00
|
|
|
ram_block_notify_add(vaddr_base, size, size);
|
2017-07-11 01:40:02 +03:00
|
|
|
}
|
|
|
|
|
2010-08-31 19:41:25 +04:00
|
|
|
entry->vaddr_base = vaddr_base;
|
|
|
|
entry->paddr_index = address_index;
|
2011-05-19 21:35:42 +04:00
|
|
|
entry->size = size;
|
2022-03-15 17:41:56 +03:00
|
|
|
entry->valid_mapping = g_new0(unsigned long,
|
|
|
|
BITS_TO_LONGS(size >> XC_PAGE_SHIFT));
|
2010-08-31 19:41:25 +04:00
|
|
|
|
2017-07-11 01:40:01 +03:00
|
|
|
if (dummy) {
|
|
|
|
entry->flags |= XEN_MAPCACHE_ENTRY_DUMMY;
|
|
|
|
} else {
|
|
|
|
entry->flags &= ~(XEN_MAPCACHE_ENTRY_DUMMY);
|
|
|
|
}
|
|
|
|
|
2011-03-22 17:50:28 +03:00
|
|
|
bitmap_zero(entry->valid_mapping, nb_pfn);
|
|
|
|
for (i = 0; i < nb_pfn; i++) {
|
|
|
|
if (!err[i]) {
|
|
|
|
bitmap_set(entry->valid_mapping, i, 1);
|
2010-08-31 19:41:25 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-08-21 07:09:37 +04:00
|
|
|
g_free(pfns);
|
|
|
|
g_free(err);
|
2010-08-31 19:41:25 +04:00
|
|
|
}
|
|
|
|
|
2015-01-14 13:20:56 +03:00
|
|
|
static uint8_t *xen_map_cache_unlocked(hwaddr phys_addr, hwaddr size,
|
xen/mapcache: store dma information in revmapcache entries for debugging
The Xen mapcache is able to create long term mappings, they are called
"locked" mappings. The third parameter of the xen_map_cache call
specifies if a mapping is a "locked" mapping.
>From the QEMU point of view there are two kinds of long term mappings:
[a] device memory mappings, such as option roms and video memory
[b] dma mappings, created by dma_memory_map & friends
After certain operations, ballooning a VM in particular, Xen asks QEMU
kindly to destroy all mappings. However, certainly [a] mappings are
present and cannot be removed. That's not a problem as they are not
affected by balloonning. The *real* problem is that if there are any
mappings of type [b], any outstanding dma operations could fail. This is
a known shortcoming. In other words, when Xen asks QEMU to destroy all
mappings, it is an error if any [b] mappings exist.
However today we have no way of distinguishing [a] from [b]. Because of
that, we cannot even print a decent warning.
This patch introduces a new "dma" bool field to MapCacheRev entires, to
remember if a given mapping is for dma or is a long term device memory
mapping. When xen_invalidate_map_cache is called, we print a warning if
any [b] mappings exist. We ignore [a] mappings.
Mappings created by qemu_map_ram_ptr are assumed to be [a], while
mappings created by address_space_map->qemu_ram_ptr_length are assumed
to be [b].
The goal of the patch is to make debugging and system understanding
easier.
Signed-off-by: Stefano Stabellini <sstabellini@kernel.org>
Acked-by: Paolo Bonzini <pbonzini@redhat.com>
Acked-by: Anthony PERARD <anthony.perard@citrix.com>
2017-05-04 00:00:35 +03:00
|
|
|
uint8_t lock, bool dma)
|
2010-08-31 19:41:25 +04:00
|
|
|
{
|
xen-mapcache: Fix the bug when overlapping emulated DMA operations may cause inconsistency in guest memory mappings
Under certain circumstances normal xen-mapcache functioning may be broken
by guest's actions. This may lead to either QEMU performing exit() due to
a caught bad pointer (and with QEMU process gone the guest domain simply
appears hung afterwards) or actual use of the incorrect pointer inside
QEMU address space -- a write to unmapped memory is possible. The bug is
hard to reproduce on a i440 machine as multiple DMA sources are required
(though it's possible in theory, using multiple emulated devices), but can
be reproduced somewhat easily on a Q35 machine using an emulated AHCI
controller -- each NCQ queue command slot may be used as an independent
DMA source ex. using READ FPDMA QUEUED command, so a single storage
device on the AHCI controller port will be enough to produce multiple DMAs
(up to 32). The detailed description of the issue follows.
Xen-mapcache provides an ability to map parts of a guest memory into
QEMU's own address space to work with.
There are two types of cache lookups:
- translating a guest physical address into a pointer in QEMU's address
space, mapping a part of guest domain memory if necessary (while trying
to reduce a number of such (re)mappings to a minimum)
- translating a QEMU's pointer back to its physical address in guest RAM
These lookups are managed via two linked-lists of structures.
MapCacheEntry is used for forward cache lookups, while MapCacheRev -- for
reverse lookups.
Every guest physical address is broken down into 2 parts:
address_index = phys_addr >> MCACHE_BUCKET_SHIFT;
address_offset = phys_addr & (MCACHE_BUCKET_SIZE - 1);
MCACHE_BUCKET_SHIFT depends on a system (32/64) and is equal to 20 for
a 64-bit system (which assumed for the further description). Basically,
this means that we deal with 1 MB chunks and offsets within those 1 MB
chunks. All mappings are created with 1MB-granularity, i.e. 1MB/2MB/3MB
etc. Most DMA transfers typically are less than 1MB, however, if the
transfer crosses any 1MB border(s) - than a nearest larger mapping size
will be used, so ex. a 512-byte DMA transfer with the start address
700FFF80h will actually require a 2MB range.
Current implementation assumes that MapCacheEntries are unique for a given
address_index and size pair and that a single MapCacheEntry may be reused
by multiple requests -- in this case the 'lock' field will be larger than
1. On other hand, each requested guest physical address (with 'lock' flag)
is described by each own MapCacheRev. So there may be multiple MapCacheRev
entries corresponding to a single MapCacheEntry. The xen-mapcache code
uses MapCacheRev entries to retrieve the address_index & size pair which
in turn used to find a related MapCacheEntry. The 'lock' field within
a MapCacheEntry structure is actually a reference counter which shows
a number of corresponding MapCacheRev entries.
The bug lies in ability for the guest to indirectly manipulate with the
xen-mapcache MapCacheEntries list via a special sequence of DMA
operations, typically for storage devices. In order to trigger the bug,
guest needs to issue DMA operations in specific order and timing.
Although xen-mapcache is protected by the mutex lock -- this doesn't help
in this case, as the bug is not due to a race condition.
Suppose we have 3 DMA transfers, namely A, B and C, where
- transfer A crosses 1MB border and thus uses a 2MB mapping
- transfers B and C are normal transfers within 1MB range
- and all 3 transfers belong to the same address_index
In this case, if all these transfers are to be executed one-by-one
(without overlaps), no special treatment necessary -- each transfer's
mapping lock will be set and then cleared on unmap before starting
the next transfer.
The situation changes when DMA transfers overlap in time, ex. like this:
|===== transfer A (2MB) =====|
|===== transfer B (1MB) =====|
|===== transfer C (1MB) =====|
time --->
In this situation the following sequence of actions happens:
1. transfer A creates a mapping to 2MB area (lock=1)
2. transfer B (1MB) tries to find available mapping but cannot find one
because transfer A is still in progress, and it has 2MB size + non-zero
lock. So transfer B creates another mapping -- same address_index,
but 1MB size.
3. transfer A completes, making 1st mapping entry available by setting its
lock to 0
4. transfer C starts and tries to find available mapping entry and sees
that 1st entry has lock=0, so it uses this entry but remaps the mapping
to a 1MB size
5. transfer B completes and by this time
- there are two locked entries in the MapCacheEntry list with the SAME
values for both address_index and size
- the entry for transfer B actually resides farther in list while
transfer C's entry is first
6. xen_ram_addr_from_mapcache() for transfer B gets correct address_index
and size pair from corresponding MapCacheRev entry, but then it starts
looking for MapCacheEntry with these values and finds the first entry
-- which belongs to transfer C.
At this point there may be following possible (bad) consequences:
1. xen_ram_addr_from_mapcache() will use a wrong entry->vaddr_base value
in this statement:
raddr = (reventry->paddr_index << MCACHE_BUCKET_SHIFT) +
((unsigned long) ptr - (unsigned long) entry->vaddr_base);
resulting in an incorrent raddr value returned from the function. The
(ptr - entry->vaddr_base) expression may produce both positive and negative
numbers and its actual value may differ greatly as there are many
map/unmap operations take place. If the value will be beyond guest RAM
limits then a "Bad RAM offset" error will be triggered and logged,
followed by exit() in QEMU.
2. If raddr value won't exceed guest RAM boundaries, the same sequence
of actions will be performed for xen_invalidate_map_cache_entry() on DMA
unmap, resulting in a wrong MapCacheEntry being unmapped while DMA
operation which uses it is still active. The above example must
be extended by one more DMA transfer in order to allow unmapping as the
first mapping in the list is sort of resident.
The patch modifies the behavior in which MapCacheEntry's are added to the
list, avoiding duplicates.
Signed-off-by: Alexey Gerasimenko <x1917x@gmail.com>
Signed-off-by: Stefano Stabellini <sstabellini@kernel.org>
2017-07-22 03:34:20 +03:00
|
|
|
MapCacheEntry *entry, *pentry = NULL,
|
|
|
|
*free_entry = NULL, *free_pentry = NULL;
|
2012-10-23 14:30:10 +04:00
|
|
|
hwaddr address_index;
|
|
|
|
hwaddr address_offset;
|
2015-01-14 13:20:55 +03:00
|
|
|
hwaddr cache_size = size;
|
|
|
|
hwaddr test_bit_size;
|
2017-07-11 01:40:03 +03:00
|
|
|
bool translated G_GNUC_UNUSED = false;
|
2017-07-11 01:40:01 +03:00
|
|
|
bool dummy = false;
|
2012-01-18 16:21:38 +04:00
|
|
|
|
|
|
|
tryagain:
|
|
|
|
address_index = phys_addr >> MCACHE_BUCKET_SHIFT;
|
|
|
|
address_offset = phys_addr & (MCACHE_BUCKET_SIZE - 1);
|
2010-08-31 19:41:25 +04:00
|
|
|
|
2011-06-22 00:59:08 +04:00
|
|
|
trace_xen_map_cache(phys_addr);
|
2010-08-31 19:41:25 +04:00
|
|
|
|
2015-01-14 13:20:55 +03:00
|
|
|
/* test_bit_size is always a multiple of XC_PAGE_SIZE */
|
2013-04-02 17:22:41 +04:00
|
|
|
if (size) {
|
2015-01-14 13:20:55 +03:00
|
|
|
test_bit_size = size + (phys_addr & (XC_PAGE_SIZE - 1));
|
2013-04-02 17:22:41 +04:00
|
|
|
|
2015-01-14 13:20:55 +03:00
|
|
|
if (test_bit_size % XC_PAGE_SIZE) {
|
|
|
|
test_bit_size += XC_PAGE_SIZE - (test_bit_size % XC_PAGE_SIZE);
|
2013-04-02 17:22:41 +04:00
|
|
|
}
|
|
|
|
} else {
|
2015-01-14 13:20:55 +03:00
|
|
|
test_bit_size = XC_PAGE_SIZE;
|
2013-04-02 17:22:41 +04:00
|
|
|
}
|
|
|
|
|
2013-04-02 17:23:40 +04:00
|
|
|
if (mapcache->last_entry != NULL &&
|
|
|
|
mapcache->last_entry->paddr_index == address_index &&
|
2015-01-14 13:20:55 +03:00
|
|
|
!lock && !size &&
|
2013-04-02 17:22:41 +04:00
|
|
|
test_bits(address_offset >> XC_PAGE_SHIFT,
|
2015-01-14 13:20:55 +03:00
|
|
|
test_bit_size >> XC_PAGE_SHIFT,
|
2013-04-02 17:22:41 +04:00
|
|
|
mapcache->last_entry->valid_mapping)) {
|
2013-04-02 17:23:40 +04:00
|
|
|
trace_xen_map_cache_return(mapcache->last_entry->vaddr_base + address_offset);
|
|
|
|
return mapcache->last_entry->vaddr_base + address_offset;
|
2010-08-31 19:41:25 +04:00
|
|
|
}
|
|
|
|
|
2011-05-19 21:35:42 +04:00
|
|
|
/* size is always a multiple of MCACHE_BUCKET_SIZE */
|
2012-04-13 21:18:56 +04:00
|
|
|
if (size) {
|
2015-01-14 13:20:55 +03:00
|
|
|
cache_size = size + address_offset;
|
|
|
|
if (cache_size % MCACHE_BUCKET_SIZE) {
|
|
|
|
cache_size += MCACHE_BUCKET_SIZE - (cache_size % MCACHE_BUCKET_SIZE);
|
2012-04-13 21:18:56 +04:00
|
|
|
}
|
|
|
|
} else {
|
2015-01-14 13:20:55 +03:00
|
|
|
cache_size = MCACHE_BUCKET_SIZE;
|
2012-04-13 21:18:56 +04:00
|
|
|
}
|
2011-05-19 21:35:42 +04:00
|
|
|
|
2010-08-31 19:41:25 +04:00
|
|
|
entry = &mapcache->entry[address_index % mapcache->nr_buckets];
|
|
|
|
|
xen-mapcache: Fix the bug when overlapping emulated DMA operations may cause inconsistency in guest memory mappings
Under certain circumstances normal xen-mapcache functioning may be broken
by guest's actions. This may lead to either QEMU performing exit() due to
a caught bad pointer (and with QEMU process gone the guest domain simply
appears hung afterwards) or actual use of the incorrect pointer inside
QEMU address space -- a write to unmapped memory is possible. The bug is
hard to reproduce on a i440 machine as multiple DMA sources are required
(though it's possible in theory, using multiple emulated devices), but can
be reproduced somewhat easily on a Q35 machine using an emulated AHCI
controller -- each NCQ queue command slot may be used as an independent
DMA source ex. using READ FPDMA QUEUED command, so a single storage
device on the AHCI controller port will be enough to produce multiple DMAs
(up to 32). The detailed description of the issue follows.
Xen-mapcache provides an ability to map parts of a guest memory into
QEMU's own address space to work with.
There are two types of cache lookups:
- translating a guest physical address into a pointer in QEMU's address
space, mapping a part of guest domain memory if necessary (while trying
to reduce a number of such (re)mappings to a minimum)
- translating a QEMU's pointer back to its physical address in guest RAM
These lookups are managed via two linked-lists of structures.
MapCacheEntry is used for forward cache lookups, while MapCacheRev -- for
reverse lookups.
Every guest physical address is broken down into 2 parts:
address_index = phys_addr >> MCACHE_BUCKET_SHIFT;
address_offset = phys_addr & (MCACHE_BUCKET_SIZE - 1);
MCACHE_BUCKET_SHIFT depends on a system (32/64) and is equal to 20 for
a 64-bit system (which assumed for the further description). Basically,
this means that we deal with 1 MB chunks and offsets within those 1 MB
chunks. All mappings are created with 1MB-granularity, i.e. 1MB/2MB/3MB
etc. Most DMA transfers typically are less than 1MB, however, if the
transfer crosses any 1MB border(s) - than a nearest larger mapping size
will be used, so ex. a 512-byte DMA transfer with the start address
700FFF80h will actually require a 2MB range.
Current implementation assumes that MapCacheEntries are unique for a given
address_index and size pair and that a single MapCacheEntry may be reused
by multiple requests -- in this case the 'lock' field will be larger than
1. On other hand, each requested guest physical address (with 'lock' flag)
is described by each own MapCacheRev. So there may be multiple MapCacheRev
entries corresponding to a single MapCacheEntry. The xen-mapcache code
uses MapCacheRev entries to retrieve the address_index & size pair which
in turn used to find a related MapCacheEntry. The 'lock' field within
a MapCacheEntry structure is actually a reference counter which shows
a number of corresponding MapCacheRev entries.
The bug lies in ability for the guest to indirectly manipulate with the
xen-mapcache MapCacheEntries list via a special sequence of DMA
operations, typically for storage devices. In order to trigger the bug,
guest needs to issue DMA operations in specific order and timing.
Although xen-mapcache is protected by the mutex lock -- this doesn't help
in this case, as the bug is not due to a race condition.
Suppose we have 3 DMA transfers, namely A, B and C, where
- transfer A crosses 1MB border and thus uses a 2MB mapping
- transfers B and C are normal transfers within 1MB range
- and all 3 transfers belong to the same address_index
In this case, if all these transfers are to be executed one-by-one
(without overlaps), no special treatment necessary -- each transfer's
mapping lock will be set and then cleared on unmap before starting
the next transfer.
The situation changes when DMA transfers overlap in time, ex. like this:
|===== transfer A (2MB) =====|
|===== transfer B (1MB) =====|
|===== transfer C (1MB) =====|
time --->
In this situation the following sequence of actions happens:
1. transfer A creates a mapping to 2MB area (lock=1)
2. transfer B (1MB) tries to find available mapping but cannot find one
because transfer A is still in progress, and it has 2MB size + non-zero
lock. So transfer B creates another mapping -- same address_index,
but 1MB size.
3. transfer A completes, making 1st mapping entry available by setting its
lock to 0
4. transfer C starts and tries to find available mapping entry and sees
that 1st entry has lock=0, so it uses this entry but remaps the mapping
to a 1MB size
5. transfer B completes and by this time
- there are two locked entries in the MapCacheEntry list with the SAME
values for both address_index and size
- the entry for transfer B actually resides farther in list while
transfer C's entry is first
6. xen_ram_addr_from_mapcache() for transfer B gets correct address_index
and size pair from corresponding MapCacheRev entry, but then it starts
looking for MapCacheEntry with these values and finds the first entry
-- which belongs to transfer C.
At this point there may be following possible (bad) consequences:
1. xen_ram_addr_from_mapcache() will use a wrong entry->vaddr_base value
in this statement:
raddr = (reventry->paddr_index << MCACHE_BUCKET_SHIFT) +
((unsigned long) ptr - (unsigned long) entry->vaddr_base);
resulting in an incorrent raddr value returned from the function. The
(ptr - entry->vaddr_base) expression may produce both positive and negative
numbers and its actual value may differ greatly as there are many
map/unmap operations take place. If the value will be beyond guest RAM
limits then a "Bad RAM offset" error will be triggered and logged,
followed by exit() in QEMU.
2. If raddr value won't exceed guest RAM boundaries, the same sequence
of actions will be performed for xen_invalidate_map_cache_entry() on DMA
unmap, resulting in a wrong MapCacheEntry being unmapped while DMA
operation which uses it is still active. The above example must
be extended by one more DMA transfer in order to allow unmapping as the
first mapping in the list is sort of resident.
The patch modifies the behavior in which MapCacheEntry's are added to the
list, avoiding duplicates.
Signed-off-by: Alexey Gerasimenko <x1917x@gmail.com>
Signed-off-by: Stefano Stabellini <sstabellini@kernel.org>
2017-07-22 03:34:20 +03:00
|
|
|
while (entry && (lock || entry->lock) && entry->vaddr_base &&
|
2015-01-14 13:20:55 +03:00
|
|
|
(entry->paddr_index != address_index || entry->size != cache_size ||
|
2013-04-02 17:22:41 +04:00
|
|
|
!test_bits(address_offset >> XC_PAGE_SHIFT,
|
2015-01-14 13:20:55 +03:00
|
|
|
test_bit_size >> XC_PAGE_SHIFT,
|
2011-05-19 21:35:42 +04:00
|
|
|
entry->valid_mapping))) {
|
xen-mapcache: Fix the bug when overlapping emulated DMA operations may cause inconsistency in guest memory mappings
Under certain circumstances normal xen-mapcache functioning may be broken
by guest's actions. This may lead to either QEMU performing exit() due to
a caught bad pointer (and with QEMU process gone the guest domain simply
appears hung afterwards) or actual use of the incorrect pointer inside
QEMU address space -- a write to unmapped memory is possible. The bug is
hard to reproduce on a i440 machine as multiple DMA sources are required
(though it's possible in theory, using multiple emulated devices), but can
be reproduced somewhat easily on a Q35 machine using an emulated AHCI
controller -- each NCQ queue command slot may be used as an independent
DMA source ex. using READ FPDMA QUEUED command, so a single storage
device on the AHCI controller port will be enough to produce multiple DMAs
(up to 32). The detailed description of the issue follows.
Xen-mapcache provides an ability to map parts of a guest memory into
QEMU's own address space to work with.
There are two types of cache lookups:
- translating a guest physical address into a pointer in QEMU's address
space, mapping a part of guest domain memory if necessary (while trying
to reduce a number of such (re)mappings to a minimum)
- translating a QEMU's pointer back to its physical address in guest RAM
These lookups are managed via two linked-lists of structures.
MapCacheEntry is used for forward cache lookups, while MapCacheRev -- for
reverse lookups.
Every guest physical address is broken down into 2 parts:
address_index = phys_addr >> MCACHE_BUCKET_SHIFT;
address_offset = phys_addr & (MCACHE_BUCKET_SIZE - 1);
MCACHE_BUCKET_SHIFT depends on a system (32/64) and is equal to 20 for
a 64-bit system (which assumed for the further description). Basically,
this means that we deal with 1 MB chunks and offsets within those 1 MB
chunks. All mappings are created with 1MB-granularity, i.e. 1MB/2MB/3MB
etc. Most DMA transfers typically are less than 1MB, however, if the
transfer crosses any 1MB border(s) - than a nearest larger mapping size
will be used, so ex. a 512-byte DMA transfer with the start address
700FFF80h will actually require a 2MB range.
Current implementation assumes that MapCacheEntries are unique for a given
address_index and size pair and that a single MapCacheEntry may be reused
by multiple requests -- in this case the 'lock' field will be larger than
1. On other hand, each requested guest physical address (with 'lock' flag)
is described by each own MapCacheRev. So there may be multiple MapCacheRev
entries corresponding to a single MapCacheEntry. The xen-mapcache code
uses MapCacheRev entries to retrieve the address_index & size pair which
in turn used to find a related MapCacheEntry. The 'lock' field within
a MapCacheEntry structure is actually a reference counter which shows
a number of corresponding MapCacheRev entries.
The bug lies in ability for the guest to indirectly manipulate with the
xen-mapcache MapCacheEntries list via a special sequence of DMA
operations, typically for storage devices. In order to trigger the bug,
guest needs to issue DMA operations in specific order and timing.
Although xen-mapcache is protected by the mutex lock -- this doesn't help
in this case, as the bug is not due to a race condition.
Suppose we have 3 DMA transfers, namely A, B and C, where
- transfer A crosses 1MB border and thus uses a 2MB mapping
- transfers B and C are normal transfers within 1MB range
- and all 3 transfers belong to the same address_index
In this case, if all these transfers are to be executed one-by-one
(without overlaps), no special treatment necessary -- each transfer's
mapping lock will be set and then cleared on unmap before starting
the next transfer.
The situation changes when DMA transfers overlap in time, ex. like this:
|===== transfer A (2MB) =====|
|===== transfer B (1MB) =====|
|===== transfer C (1MB) =====|
time --->
In this situation the following sequence of actions happens:
1. transfer A creates a mapping to 2MB area (lock=1)
2. transfer B (1MB) tries to find available mapping but cannot find one
because transfer A is still in progress, and it has 2MB size + non-zero
lock. So transfer B creates another mapping -- same address_index,
but 1MB size.
3. transfer A completes, making 1st mapping entry available by setting its
lock to 0
4. transfer C starts and tries to find available mapping entry and sees
that 1st entry has lock=0, so it uses this entry but remaps the mapping
to a 1MB size
5. transfer B completes and by this time
- there are two locked entries in the MapCacheEntry list with the SAME
values for both address_index and size
- the entry for transfer B actually resides farther in list while
transfer C's entry is first
6. xen_ram_addr_from_mapcache() for transfer B gets correct address_index
and size pair from corresponding MapCacheRev entry, but then it starts
looking for MapCacheEntry with these values and finds the first entry
-- which belongs to transfer C.
At this point there may be following possible (bad) consequences:
1. xen_ram_addr_from_mapcache() will use a wrong entry->vaddr_base value
in this statement:
raddr = (reventry->paddr_index << MCACHE_BUCKET_SHIFT) +
((unsigned long) ptr - (unsigned long) entry->vaddr_base);
resulting in an incorrent raddr value returned from the function. The
(ptr - entry->vaddr_base) expression may produce both positive and negative
numbers and its actual value may differ greatly as there are many
map/unmap operations take place. If the value will be beyond guest RAM
limits then a "Bad RAM offset" error will be triggered and logged,
followed by exit() in QEMU.
2. If raddr value won't exceed guest RAM boundaries, the same sequence
of actions will be performed for xen_invalidate_map_cache_entry() on DMA
unmap, resulting in a wrong MapCacheEntry being unmapped while DMA
operation which uses it is still active. The above example must
be extended by one more DMA transfer in order to allow unmapping as the
first mapping in the list is sort of resident.
The patch modifies the behavior in which MapCacheEntry's are added to the
list, avoiding duplicates.
Signed-off-by: Alexey Gerasimenko <x1917x@gmail.com>
Signed-off-by: Stefano Stabellini <sstabellini@kernel.org>
2017-07-22 03:34:20 +03:00
|
|
|
if (!free_entry && !entry->lock) {
|
|
|
|
free_entry = entry;
|
|
|
|
free_pentry = pentry;
|
|
|
|
}
|
2010-08-31 19:41:25 +04:00
|
|
|
pentry = entry;
|
|
|
|
entry = entry->next;
|
|
|
|
}
|
xen-mapcache: Fix the bug when overlapping emulated DMA operations may cause inconsistency in guest memory mappings
Under certain circumstances normal xen-mapcache functioning may be broken
by guest's actions. This may lead to either QEMU performing exit() due to
a caught bad pointer (and with QEMU process gone the guest domain simply
appears hung afterwards) or actual use of the incorrect pointer inside
QEMU address space -- a write to unmapped memory is possible. The bug is
hard to reproduce on a i440 machine as multiple DMA sources are required
(though it's possible in theory, using multiple emulated devices), but can
be reproduced somewhat easily on a Q35 machine using an emulated AHCI
controller -- each NCQ queue command slot may be used as an independent
DMA source ex. using READ FPDMA QUEUED command, so a single storage
device on the AHCI controller port will be enough to produce multiple DMAs
(up to 32). The detailed description of the issue follows.
Xen-mapcache provides an ability to map parts of a guest memory into
QEMU's own address space to work with.
There are two types of cache lookups:
- translating a guest physical address into a pointer in QEMU's address
space, mapping a part of guest domain memory if necessary (while trying
to reduce a number of such (re)mappings to a minimum)
- translating a QEMU's pointer back to its physical address in guest RAM
These lookups are managed via two linked-lists of structures.
MapCacheEntry is used for forward cache lookups, while MapCacheRev -- for
reverse lookups.
Every guest physical address is broken down into 2 parts:
address_index = phys_addr >> MCACHE_BUCKET_SHIFT;
address_offset = phys_addr & (MCACHE_BUCKET_SIZE - 1);
MCACHE_BUCKET_SHIFT depends on a system (32/64) and is equal to 20 for
a 64-bit system (which assumed for the further description). Basically,
this means that we deal with 1 MB chunks and offsets within those 1 MB
chunks. All mappings are created with 1MB-granularity, i.e. 1MB/2MB/3MB
etc. Most DMA transfers typically are less than 1MB, however, if the
transfer crosses any 1MB border(s) - than a nearest larger mapping size
will be used, so ex. a 512-byte DMA transfer with the start address
700FFF80h will actually require a 2MB range.
Current implementation assumes that MapCacheEntries are unique for a given
address_index and size pair and that a single MapCacheEntry may be reused
by multiple requests -- in this case the 'lock' field will be larger than
1. On other hand, each requested guest physical address (with 'lock' flag)
is described by each own MapCacheRev. So there may be multiple MapCacheRev
entries corresponding to a single MapCacheEntry. The xen-mapcache code
uses MapCacheRev entries to retrieve the address_index & size pair which
in turn used to find a related MapCacheEntry. The 'lock' field within
a MapCacheEntry structure is actually a reference counter which shows
a number of corresponding MapCacheRev entries.
The bug lies in ability for the guest to indirectly manipulate with the
xen-mapcache MapCacheEntries list via a special sequence of DMA
operations, typically for storage devices. In order to trigger the bug,
guest needs to issue DMA operations in specific order and timing.
Although xen-mapcache is protected by the mutex lock -- this doesn't help
in this case, as the bug is not due to a race condition.
Suppose we have 3 DMA transfers, namely A, B and C, where
- transfer A crosses 1MB border and thus uses a 2MB mapping
- transfers B and C are normal transfers within 1MB range
- and all 3 transfers belong to the same address_index
In this case, if all these transfers are to be executed one-by-one
(without overlaps), no special treatment necessary -- each transfer's
mapping lock will be set and then cleared on unmap before starting
the next transfer.
The situation changes when DMA transfers overlap in time, ex. like this:
|===== transfer A (2MB) =====|
|===== transfer B (1MB) =====|
|===== transfer C (1MB) =====|
time --->
In this situation the following sequence of actions happens:
1. transfer A creates a mapping to 2MB area (lock=1)
2. transfer B (1MB) tries to find available mapping but cannot find one
because transfer A is still in progress, and it has 2MB size + non-zero
lock. So transfer B creates another mapping -- same address_index,
but 1MB size.
3. transfer A completes, making 1st mapping entry available by setting its
lock to 0
4. transfer C starts and tries to find available mapping entry and sees
that 1st entry has lock=0, so it uses this entry but remaps the mapping
to a 1MB size
5. transfer B completes and by this time
- there are two locked entries in the MapCacheEntry list with the SAME
values for both address_index and size
- the entry for transfer B actually resides farther in list while
transfer C's entry is first
6. xen_ram_addr_from_mapcache() for transfer B gets correct address_index
and size pair from corresponding MapCacheRev entry, but then it starts
looking for MapCacheEntry with these values and finds the first entry
-- which belongs to transfer C.
At this point there may be following possible (bad) consequences:
1. xen_ram_addr_from_mapcache() will use a wrong entry->vaddr_base value
in this statement:
raddr = (reventry->paddr_index << MCACHE_BUCKET_SHIFT) +
((unsigned long) ptr - (unsigned long) entry->vaddr_base);
resulting in an incorrent raddr value returned from the function. The
(ptr - entry->vaddr_base) expression may produce both positive and negative
numbers and its actual value may differ greatly as there are many
map/unmap operations take place. If the value will be beyond guest RAM
limits then a "Bad RAM offset" error will be triggered and logged,
followed by exit() in QEMU.
2. If raddr value won't exceed guest RAM boundaries, the same sequence
of actions will be performed for xen_invalidate_map_cache_entry() on DMA
unmap, resulting in a wrong MapCacheEntry being unmapped while DMA
operation which uses it is still active. The above example must
be extended by one more DMA transfer in order to allow unmapping as the
first mapping in the list is sort of resident.
The patch modifies the behavior in which MapCacheEntry's are added to the
list, avoiding duplicates.
Signed-off-by: Alexey Gerasimenko <x1917x@gmail.com>
Signed-off-by: Stefano Stabellini <sstabellini@kernel.org>
2017-07-22 03:34:20 +03:00
|
|
|
if (!entry && free_entry) {
|
|
|
|
entry = free_entry;
|
|
|
|
pentry = free_pentry;
|
|
|
|
}
|
2010-08-31 19:41:25 +04:00
|
|
|
if (!entry) {
|
2022-03-15 17:41:56 +03:00
|
|
|
entry = g_new0(MapCacheEntry, 1);
|
2010-08-31 19:41:25 +04:00
|
|
|
pentry->next = entry;
|
2017-07-11 01:40:02 +03:00
|
|
|
xen_remap_bucket(entry, NULL, cache_size, address_index, dummy);
|
2010-08-31 19:41:25 +04:00
|
|
|
} else if (!entry->lock) {
|
|
|
|
if (!entry->vaddr_base || entry->paddr_index != address_index ||
|
2015-01-14 13:20:55 +03:00
|
|
|
entry->size != cache_size ||
|
2013-04-02 17:22:41 +04:00
|
|
|
!test_bits(address_offset >> XC_PAGE_SHIFT,
|
2015-01-14 13:20:55 +03:00
|
|
|
test_bit_size >> XC_PAGE_SHIFT,
|
2011-05-19 21:35:42 +04:00
|
|
|
entry->valid_mapping)) {
|
2017-07-11 01:40:02 +03:00
|
|
|
xen_remap_bucket(entry, NULL, cache_size, address_index, dummy);
|
2010-08-31 19:41:25 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-04-02 17:22:41 +04:00
|
|
|
if(!test_bits(address_offset >> XC_PAGE_SHIFT,
|
2015-01-14 13:20:55 +03:00
|
|
|
test_bit_size >> XC_PAGE_SHIFT,
|
2011-05-19 21:35:42 +04:00
|
|
|
entry->valid_mapping)) {
|
2013-04-02 17:23:40 +04:00
|
|
|
mapcache->last_entry = NULL;
|
2017-07-11 01:40:03 +03:00
|
|
|
#ifdef XEN_COMPAT_PHYSMAP
|
2012-01-18 16:21:38 +04:00
|
|
|
if (!translated && mapcache->phys_offset_to_gaddr) {
|
2018-04-25 16:46:47 +03:00
|
|
|
phys_addr = mapcache->phys_offset_to_gaddr(phys_addr, size);
|
2012-01-18 16:21:38 +04:00
|
|
|
translated = true;
|
|
|
|
goto tryagain;
|
2017-07-11 01:40:01 +03:00
|
|
|
}
|
2017-07-11 01:40:03 +03:00
|
|
|
#endif
|
2017-07-11 01:40:01 +03:00
|
|
|
if (!dummy && runstate_check(RUN_STATE_INMIGRATE)) {
|
|
|
|
dummy = true;
|
|
|
|
goto tryagain;
|
2012-01-18 16:21:38 +04:00
|
|
|
}
|
2011-06-22 00:59:08 +04:00
|
|
|
trace_xen_map_cache_return(NULL);
|
2010-08-31 19:41:25 +04:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2013-04-02 17:23:40 +04:00
|
|
|
mapcache->last_entry = entry;
|
2010-08-31 19:41:25 +04:00
|
|
|
if (lock) {
|
2022-03-15 17:41:56 +03:00
|
|
|
MapCacheRev *reventry = g_new0(MapCacheRev, 1);
|
2010-08-31 19:41:25 +04:00
|
|
|
entry->lock++;
|
2022-01-24 13:44:50 +03:00
|
|
|
if (entry->lock == 0) {
|
|
|
|
fprintf(stderr,
|
2023-01-11 00:29:47 +03:00
|
|
|
"mapcache entry lock overflow: "HWADDR_FMT_plx" -> %p\n",
|
2022-01-24 13:44:50 +03:00
|
|
|
entry->paddr_index, entry->vaddr_base);
|
|
|
|
abort();
|
|
|
|
}
|
xen/mapcache: store dma information in revmapcache entries for debugging
The Xen mapcache is able to create long term mappings, they are called
"locked" mappings. The third parameter of the xen_map_cache call
specifies if a mapping is a "locked" mapping.
>From the QEMU point of view there are two kinds of long term mappings:
[a] device memory mappings, such as option roms and video memory
[b] dma mappings, created by dma_memory_map & friends
After certain operations, ballooning a VM in particular, Xen asks QEMU
kindly to destroy all mappings. However, certainly [a] mappings are
present and cannot be removed. That's not a problem as they are not
affected by balloonning. The *real* problem is that if there are any
mappings of type [b], any outstanding dma operations could fail. This is
a known shortcoming. In other words, when Xen asks QEMU to destroy all
mappings, it is an error if any [b] mappings exist.
However today we have no way of distinguishing [a] from [b]. Because of
that, we cannot even print a decent warning.
This patch introduces a new "dma" bool field to MapCacheRev entires, to
remember if a given mapping is for dma or is a long term device memory
mapping. When xen_invalidate_map_cache is called, we print a warning if
any [b] mappings exist. We ignore [a] mappings.
Mappings created by qemu_map_ram_ptr are assumed to be [a], while
mappings created by address_space_map->qemu_ram_ptr_length are assumed
to be [b].
The goal of the patch is to make debugging and system understanding
easier.
Signed-off-by: Stefano Stabellini <sstabellini@kernel.org>
Acked-by: Paolo Bonzini <pbonzini@redhat.com>
Acked-by: Anthony PERARD <anthony.perard@citrix.com>
2017-05-04 00:00:35 +03:00
|
|
|
reventry->dma = dma;
|
2013-04-02 17:23:40 +04:00
|
|
|
reventry->vaddr_req = mapcache->last_entry->vaddr_base + address_offset;
|
|
|
|
reventry->paddr_index = mapcache->last_entry->paddr_index;
|
2011-05-19 21:35:42 +04:00
|
|
|
reventry->size = entry->size;
|
2010-08-31 19:41:25 +04:00
|
|
|
QTAILQ_INSERT_HEAD(&mapcache->locked_entries, reventry, next);
|
|
|
|
}
|
|
|
|
|
2013-04-02 17:23:40 +04:00
|
|
|
trace_xen_map_cache_return(mapcache->last_entry->vaddr_base + address_offset);
|
|
|
|
return mapcache->last_entry->vaddr_base + address_offset;
|
2010-08-31 19:41:25 +04:00
|
|
|
}
|
|
|
|
|
2015-01-14 13:20:56 +03:00
|
|
|
uint8_t *xen_map_cache(hwaddr phys_addr, hwaddr size,
|
xen/mapcache: store dma information in revmapcache entries for debugging
The Xen mapcache is able to create long term mappings, they are called
"locked" mappings. The third parameter of the xen_map_cache call
specifies if a mapping is a "locked" mapping.
>From the QEMU point of view there are two kinds of long term mappings:
[a] device memory mappings, such as option roms and video memory
[b] dma mappings, created by dma_memory_map & friends
After certain operations, ballooning a VM in particular, Xen asks QEMU
kindly to destroy all mappings. However, certainly [a] mappings are
present and cannot be removed. That's not a problem as they are not
affected by balloonning. The *real* problem is that if there are any
mappings of type [b], any outstanding dma operations could fail. This is
a known shortcoming. In other words, when Xen asks QEMU to destroy all
mappings, it is an error if any [b] mappings exist.
However today we have no way of distinguishing [a] from [b]. Because of
that, we cannot even print a decent warning.
This patch introduces a new "dma" bool field to MapCacheRev entires, to
remember if a given mapping is for dma or is a long term device memory
mapping. When xen_invalidate_map_cache is called, we print a warning if
any [b] mappings exist. We ignore [a] mappings.
Mappings created by qemu_map_ram_ptr are assumed to be [a], while
mappings created by address_space_map->qemu_ram_ptr_length are assumed
to be [b].
The goal of the patch is to make debugging and system understanding
easier.
Signed-off-by: Stefano Stabellini <sstabellini@kernel.org>
Acked-by: Paolo Bonzini <pbonzini@redhat.com>
Acked-by: Anthony PERARD <anthony.perard@citrix.com>
2017-05-04 00:00:35 +03:00
|
|
|
uint8_t lock, bool dma)
|
2015-01-14 13:20:56 +03:00
|
|
|
{
|
|
|
|
uint8_t *p;
|
|
|
|
|
|
|
|
mapcache_lock();
|
xen/mapcache: store dma information in revmapcache entries for debugging
The Xen mapcache is able to create long term mappings, they are called
"locked" mappings. The third parameter of the xen_map_cache call
specifies if a mapping is a "locked" mapping.
>From the QEMU point of view there are two kinds of long term mappings:
[a] device memory mappings, such as option roms and video memory
[b] dma mappings, created by dma_memory_map & friends
After certain operations, ballooning a VM in particular, Xen asks QEMU
kindly to destroy all mappings. However, certainly [a] mappings are
present and cannot be removed. That's not a problem as they are not
affected by balloonning. The *real* problem is that if there are any
mappings of type [b], any outstanding dma operations could fail. This is
a known shortcoming. In other words, when Xen asks QEMU to destroy all
mappings, it is an error if any [b] mappings exist.
However today we have no way of distinguishing [a] from [b]. Because of
that, we cannot even print a decent warning.
This patch introduces a new "dma" bool field to MapCacheRev entires, to
remember if a given mapping is for dma or is a long term device memory
mapping. When xen_invalidate_map_cache is called, we print a warning if
any [b] mappings exist. We ignore [a] mappings.
Mappings created by qemu_map_ram_ptr are assumed to be [a], while
mappings created by address_space_map->qemu_ram_ptr_length are assumed
to be [b].
The goal of the patch is to make debugging and system understanding
easier.
Signed-off-by: Stefano Stabellini <sstabellini@kernel.org>
Acked-by: Paolo Bonzini <pbonzini@redhat.com>
Acked-by: Anthony PERARD <anthony.perard@citrix.com>
2017-05-04 00:00:35 +03:00
|
|
|
p = xen_map_cache_unlocked(phys_addr, size, lock, dma);
|
2015-01-14 13:20:56 +03:00
|
|
|
mapcache_unlock();
|
|
|
|
return p;
|
|
|
|
}
|
|
|
|
|
2011-06-22 00:59:08 +04:00
|
|
|
ram_addr_t xen_ram_addr_from_mapcache(void *ptr)
|
2010-08-31 19:41:25 +04:00
|
|
|
{
|
2011-07-26 18:33:11 +04:00
|
|
|
MapCacheEntry *entry = NULL;
|
2010-08-31 19:41:25 +04:00
|
|
|
MapCacheRev *reventry;
|
2012-10-23 14:30:10 +04:00
|
|
|
hwaddr paddr_index;
|
|
|
|
hwaddr size;
|
2015-01-14 13:20:56 +03:00
|
|
|
ram_addr_t raddr;
|
2010-08-31 19:41:25 +04:00
|
|
|
int found = 0;
|
|
|
|
|
2015-01-14 13:20:56 +03:00
|
|
|
mapcache_lock();
|
2010-08-31 19:41:25 +04:00
|
|
|
QTAILQ_FOREACH(reventry, &mapcache->locked_entries, next) {
|
|
|
|
if (reventry->vaddr_req == ptr) {
|
|
|
|
paddr_index = reventry->paddr_index;
|
2011-05-19 21:35:42 +04:00
|
|
|
size = reventry->size;
|
2010-08-31 19:41:25 +04:00
|
|
|
found = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!found) {
|
2011-06-22 00:59:08 +04:00
|
|
|
fprintf(stderr, "%s, could not find %p\n", __func__, ptr);
|
2010-08-31 19:41:25 +04:00
|
|
|
QTAILQ_FOREACH(reventry, &mapcache->locked_entries, next) {
|
2023-01-11 00:29:47 +03:00
|
|
|
DPRINTF(" "HWADDR_FMT_plx" -> %p is present\n", reventry->paddr_index,
|
2010-08-31 19:41:25 +04:00
|
|
|
reventry->vaddr_req);
|
|
|
|
}
|
|
|
|
abort();
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-05-19 21:35:42 +04:00
|
|
|
entry = &mapcache->entry[paddr_index % mapcache->nr_buckets];
|
|
|
|
while (entry && (entry->paddr_index != paddr_index || entry->size != size)) {
|
|
|
|
entry = entry->next;
|
|
|
|
}
|
|
|
|
if (!entry) {
|
|
|
|
DPRINTF("Trying to find address %p that is not in the mapcache!\n", ptr);
|
2015-01-14 13:20:56 +03:00
|
|
|
raddr = 0;
|
|
|
|
} else {
|
|
|
|
raddr = (reventry->paddr_index << MCACHE_BUCKET_SHIFT) +
|
|
|
|
((unsigned long) ptr - (unsigned long) entry->vaddr_base);
|
2011-05-19 21:35:42 +04:00
|
|
|
}
|
2015-01-14 13:20:56 +03:00
|
|
|
mapcache_unlock();
|
|
|
|
return raddr;
|
2010-08-31 19:41:25 +04:00
|
|
|
}
|
|
|
|
|
2015-01-14 13:20:56 +03:00
|
|
|
static void xen_invalidate_map_cache_entry_unlocked(uint8_t *buffer)
|
2010-08-31 19:41:25 +04:00
|
|
|
{
|
|
|
|
MapCacheEntry *entry = NULL, *pentry = NULL;
|
|
|
|
MapCacheRev *reventry;
|
2012-10-23 14:30:10 +04:00
|
|
|
hwaddr paddr_index;
|
|
|
|
hwaddr size;
|
2010-08-31 19:41:25 +04:00
|
|
|
int found = 0;
|
|
|
|
|
|
|
|
QTAILQ_FOREACH(reventry, &mapcache->locked_entries, next) {
|
|
|
|
if (reventry->vaddr_req == buffer) {
|
|
|
|
paddr_index = reventry->paddr_index;
|
2011-05-19 21:35:42 +04:00
|
|
|
size = reventry->size;
|
2010-08-31 19:41:25 +04:00
|
|
|
found = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!found) {
|
2011-06-22 00:59:08 +04:00
|
|
|
DPRINTF("%s, could not find %p\n", __func__, buffer);
|
2010-08-31 19:41:25 +04:00
|
|
|
QTAILQ_FOREACH(reventry, &mapcache->locked_entries, next) {
|
2023-01-11 00:29:47 +03:00
|
|
|
DPRINTF(" "HWADDR_FMT_plx" -> %p is present\n", reventry->paddr_index, reventry->vaddr_req);
|
2010-08-31 19:41:25 +04:00
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
QTAILQ_REMOVE(&mapcache->locked_entries, reventry, next);
|
2011-08-21 07:09:37 +04:00
|
|
|
g_free(reventry);
|
2010-08-31 19:41:25 +04:00
|
|
|
|
2013-04-02 17:23:40 +04:00
|
|
|
if (mapcache->last_entry != NULL &&
|
|
|
|
mapcache->last_entry->paddr_index == paddr_index) {
|
|
|
|
mapcache->last_entry = NULL;
|
2012-08-22 14:17:04 +04:00
|
|
|
}
|
|
|
|
|
2010-08-31 19:41:25 +04:00
|
|
|
entry = &mapcache->entry[paddr_index % mapcache->nr_buckets];
|
2011-05-19 21:35:42 +04:00
|
|
|
while (entry && (entry->paddr_index != paddr_index || entry->size != size)) {
|
2010-08-31 19:41:25 +04:00
|
|
|
pentry = entry;
|
|
|
|
entry = entry->next;
|
|
|
|
}
|
|
|
|
if (!entry) {
|
|
|
|
DPRINTF("Trying to unmap address %p that is not in the mapcache!\n", buffer);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
entry->lock--;
|
|
|
|
if (entry->lock > 0 || pentry == NULL) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
pentry->next = entry->next;
|
2021-04-29 14:27:00 +03:00
|
|
|
ram_block_notify_remove(entry->vaddr_base, entry->size, entry->size);
|
2011-05-19 21:35:42 +04:00
|
|
|
if (munmap(entry->vaddr_base, entry->size) != 0) {
|
2010-08-31 19:41:25 +04:00
|
|
|
perror("unmap fails");
|
|
|
|
exit(-1);
|
|
|
|
}
|
2011-08-21 07:09:37 +04:00
|
|
|
g_free(entry->valid_mapping);
|
|
|
|
g_free(entry);
|
2010-08-31 19:41:25 +04:00
|
|
|
}
|
|
|
|
|
2015-01-14 13:20:56 +03:00
|
|
|
void xen_invalidate_map_cache_entry(uint8_t *buffer)
|
|
|
|
{
|
|
|
|
mapcache_lock();
|
|
|
|
xen_invalidate_map_cache_entry_unlocked(buffer);
|
|
|
|
mapcache_unlock();
|
|
|
|
}
|
|
|
|
|
2011-06-22 00:59:08 +04:00
|
|
|
void xen_invalidate_map_cache(void)
|
2010-08-31 19:41:25 +04:00
|
|
|
{
|
|
|
|
unsigned long i;
|
|
|
|
MapCacheRev *reventry;
|
|
|
|
|
|
|
|
/* Flush pending AIO before destroying the mapcache */
|
2011-11-30 16:23:43 +04:00
|
|
|
bdrv_drain_all();
|
2010-08-31 19:41:25 +04:00
|
|
|
|
2015-01-14 13:20:56 +03:00
|
|
|
mapcache_lock();
|
|
|
|
|
2010-08-31 19:41:25 +04:00
|
|
|
QTAILQ_FOREACH(reventry, &mapcache->locked_entries, next) {
|
xen/mapcache: store dma information in revmapcache entries for debugging
The Xen mapcache is able to create long term mappings, they are called
"locked" mappings. The third parameter of the xen_map_cache call
specifies if a mapping is a "locked" mapping.
>From the QEMU point of view there are two kinds of long term mappings:
[a] device memory mappings, such as option roms and video memory
[b] dma mappings, created by dma_memory_map & friends
After certain operations, ballooning a VM in particular, Xen asks QEMU
kindly to destroy all mappings. However, certainly [a] mappings are
present and cannot be removed. That's not a problem as they are not
affected by balloonning. The *real* problem is that if there are any
mappings of type [b], any outstanding dma operations could fail. This is
a known shortcoming. In other words, when Xen asks QEMU to destroy all
mappings, it is an error if any [b] mappings exist.
However today we have no way of distinguishing [a] from [b]. Because of
that, we cannot even print a decent warning.
This patch introduces a new "dma" bool field to MapCacheRev entires, to
remember if a given mapping is for dma or is a long term device memory
mapping. When xen_invalidate_map_cache is called, we print a warning if
any [b] mappings exist. We ignore [a] mappings.
Mappings created by qemu_map_ram_ptr are assumed to be [a], while
mappings created by address_space_map->qemu_ram_ptr_length are assumed
to be [b].
The goal of the patch is to make debugging and system understanding
easier.
Signed-off-by: Stefano Stabellini <sstabellini@kernel.org>
Acked-by: Paolo Bonzini <pbonzini@redhat.com>
Acked-by: Anthony PERARD <anthony.perard@citrix.com>
2017-05-04 00:00:35 +03:00
|
|
|
if (!reventry->dma) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
fprintf(stderr, "Locked DMA mapping while invalidating mapcache!"
|
2023-01-11 00:29:47 +03:00
|
|
|
" "HWADDR_FMT_plx" -> %p is present\n",
|
2010-08-31 19:41:25 +04:00
|
|
|
reventry->paddr_index, reventry->vaddr_req);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < mapcache->nr_buckets; i++) {
|
|
|
|
MapCacheEntry *entry = &mapcache->entry[i];
|
|
|
|
|
|
|
|
if (entry->vaddr_base == NULL) {
|
|
|
|
continue;
|
|
|
|
}
|
2012-04-13 21:33:02 +04:00
|
|
|
if (entry->lock > 0) {
|
|
|
|
continue;
|
|
|
|
}
|
2010-08-31 19:41:25 +04:00
|
|
|
|
2011-05-19 21:35:42 +04:00
|
|
|
if (munmap(entry->vaddr_base, entry->size) != 0) {
|
2010-08-31 19:41:25 +04:00
|
|
|
perror("unmap fails");
|
|
|
|
exit(-1);
|
|
|
|
}
|
|
|
|
|
|
|
|
entry->paddr_index = 0;
|
|
|
|
entry->vaddr_base = NULL;
|
2011-05-19 21:35:42 +04:00
|
|
|
entry->size = 0;
|
2011-08-21 07:09:37 +04:00
|
|
|
g_free(entry->valid_mapping);
|
2011-05-19 21:35:42 +04:00
|
|
|
entry->valid_mapping = NULL;
|
2010-08-31 19:41:25 +04:00
|
|
|
}
|
|
|
|
|
2013-04-02 17:23:40 +04:00
|
|
|
mapcache->last_entry = NULL;
|
2010-08-31 19:41:25 +04:00
|
|
|
|
|
|
|
mapcache_unlock();
|
|
|
|
}
|
2017-07-11 01:40:02 +03:00
|
|
|
|
|
|
|
static uint8_t *xen_replace_cache_entry_unlocked(hwaddr old_phys_addr,
|
|
|
|
hwaddr new_phys_addr,
|
|
|
|
hwaddr size)
|
|
|
|
{
|
|
|
|
MapCacheEntry *entry;
|
|
|
|
hwaddr address_index, address_offset;
|
|
|
|
hwaddr test_bit_size, cache_size = size;
|
|
|
|
|
|
|
|
address_index = old_phys_addr >> MCACHE_BUCKET_SHIFT;
|
|
|
|
address_offset = old_phys_addr & (MCACHE_BUCKET_SIZE - 1);
|
|
|
|
|
|
|
|
assert(size);
|
|
|
|
/* test_bit_size is always a multiple of XC_PAGE_SIZE */
|
|
|
|
test_bit_size = size + (old_phys_addr & (XC_PAGE_SIZE - 1));
|
|
|
|
if (test_bit_size % XC_PAGE_SIZE) {
|
|
|
|
test_bit_size += XC_PAGE_SIZE - (test_bit_size % XC_PAGE_SIZE);
|
|
|
|
}
|
|
|
|
cache_size = size + address_offset;
|
|
|
|
if (cache_size % MCACHE_BUCKET_SIZE) {
|
|
|
|
cache_size += MCACHE_BUCKET_SIZE - (cache_size % MCACHE_BUCKET_SIZE);
|
|
|
|
}
|
|
|
|
|
|
|
|
entry = &mapcache->entry[address_index % mapcache->nr_buckets];
|
|
|
|
while (entry && !(entry->paddr_index == address_index &&
|
|
|
|
entry->size == cache_size)) {
|
|
|
|
entry = entry->next;
|
|
|
|
}
|
|
|
|
if (!entry) {
|
2023-01-11 00:29:47 +03:00
|
|
|
DPRINTF("Trying to update an entry for "HWADDR_FMT_plx \
|
2017-07-11 01:40:02 +03:00
|
|
|
"that is not in the mapcache!\n", old_phys_addr);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
address_index = new_phys_addr >> MCACHE_BUCKET_SHIFT;
|
|
|
|
address_offset = new_phys_addr & (MCACHE_BUCKET_SIZE - 1);
|
|
|
|
|
2023-01-11 00:29:47 +03:00
|
|
|
fprintf(stderr, "Replacing a dummy mapcache entry for "HWADDR_FMT_plx \
|
|
|
|
" with "HWADDR_FMT_plx"\n", old_phys_addr, new_phys_addr);
|
2017-07-11 01:40:02 +03:00
|
|
|
|
|
|
|
xen_remap_bucket(entry, entry->vaddr_base,
|
|
|
|
cache_size, address_index, false);
|
|
|
|
if (!test_bits(address_offset >> XC_PAGE_SHIFT,
|
|
|
|
test_bit_size >> XC_PAGE_SHIFT,
|
|
|
|
entry->valid_mapping)) {
|
2023-01-11 00:29:47 +03:00
|
|
|
DPRINTF("Unable to update a mapcache entry for "HWADDR_FMT_plx"!\n",
|
2017-07-22 03:32:56 +03:00
|
|
|
old_phys_addr);
|
2017-07-11 01:40:02 +03:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return entry->vaddr_base + address_offset;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t *xen_replace_cache_entry(hwaddr old_phys_addr,
|
|
|
|
hwaddr new_phys_addr,
|
|
|
|
hwaddr size)
|
|
|
|
{
|
|
|
|
uint8_t *p;
|
|
|
|
|
|
|
|
mapcache_lock();
|
|
|
|
p = xen_replace_cache_entry_unlocked(old_phys_addr, new_phys_addr, size);
|
|
|
|
mapcache_unlock();
|
|
|
|
return p;
|
|
|
|
}
|