qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [Qemu-devel] [RFC PATCH] mmio-exec: Make device return MemoryRegion


From: Cédric Le Goater
Subject: Re: [Qemu-devel] [RFC PATCH] mmio-exec: Make device return MemoryRegion rather than host pointer
Date: Mon, 28 May 2018 17:25:44 +0200
User-agent: Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Thunderbird/52.5.2

On 05/28/2018 07:22 AM, Cédric Le Goater wrote:
> On 04/26/2018 05:09 PM, Peter Maydell wrote:
>> Our current interface for allowing a device to support execution from
>> MMIO regions has the device return a pointer into host memory
>> containing the contents to be used for execution.  Unfortunately the
>> obvious implementation this encourages (and which our only in-tree
>> user uses) is to have a single buffer which is reused for each new
>> request_ptr call.  This doesn't work, because RCU means that removal
>> of the old RAMBlock based on the host pointer may be deferred until a
>> point after a new RAMBlock with the same pointer has been added to
>> the list.  The RAMBlock APIs assume that the host-pointer to ram_addr
>> mapping is unique, and mmio-exec is breaking that assumption.
> 
> yes. I have run into the same problem while implementing mmio-exec 
> for the aspeed fmc controllers. 
> 
> The FW (U-boot) in some configuration does a CRC calculation which 
> jumps from one code section to another. This trashes the MMIO cache 
> a very large number of times and the RAM block list can contain up 
> to 40 occurrences of the same cache ... it is nearly impossible not 
> to hit the bug you describe.

For the records, there are more than 31000 request/invalidate calls.
 
> This patch fixes the issue 

There is still an issue (or a limitation) with the patch. It is not
possible to reload the cache when a load is performed on the MMIO 
region from which QEMU is exec'ing. This breaks the TCG flow it seems. 

> but maybe an another approach could be to introduce a cache allocator > which 
> would make sure that host-pointers are unique ?

but freeing these cache lines is a problem. Same as of today ... 
Hmm, really a complex problem. 

Thanks,

C.

>> Instead, change the API so that the device is responsible for
>> allocating a new RAM MemoryRegion and populating it.  The
>> MemoryRegion will be g_free()d when the region is eventually
>> invalidated or otherwise unneeded.
>>
>> HACKS:
>>  * Note that in order for the refcounting to work correctly without
>>    having to manufacture a device purely to be the owner of the
>>    MemoryRegion, we must pass OBJECT(newmr) as the owner (so the MR is
>>    its own owner!) and NULL as the name (or the property created on the
>>    owner causes the refcount to go negative during finalization)...
>>
>> QUESTIONS:
>>  * do we really need to postpone things from
>>    memory_region_invalidate_mmio_ptr() to
>>    memory_region_do_invalidate_mmio_ptr(), given that the deletion of
>>    the memory region and ramblock is RCU-deferred anyway?
>>  * should we add the subregion at elevated prio so it definitely hits
>>    first?  I've left it the way the existing code does for the
>>    moment...
>>  * is there any way to avoid the weird self-owning MemoryRegion
>>    and corresponding need to pass a NULL name pointer?
>>
>> NOTES:
>>  * This change means that hw/misc/mmio_interface.c is no longer
>>    used; if this gets beyond RFC stage then another patch to
>>    delete it can follow.
>>  * The "request_mmio_exec mustn't fail after it has called
>>    memory_region_mmio_ptr_alloc()" requirement is a bit ugly, but
>>    could probably be removed.
>>
>> Signed-off-by: Peter Maydell <address@hidden>
>> ---
>> This is decidedly RFC, but it is sufficient to get the xilinx-spips
>> mmio-execution test case to run without crashing. Mostly looking for
>> feedback on whether this is the right general direction or a load
>> of rubbish :-)
>>
>>  include/exec/memory.h | 44 ++++++++++++++++++++----
>>  hw/ssi/xilinx_spips.c | 22 ++++++++----
>>  memory.c              | 78 ++++++++++++++++++++++++++++---------------
>>  3 files changed, 105 insertions(+), 39 deletions(-)
>>
>> diff --git a/include/exec/memory.h b/include/exec/memory.h
>> index 31eae0a640..e55e06a166 100644
>> --- a/include/exec/memory.h
>> +++ b/include/exec/memory.h
>> @@ -139,14 +139,32 @@ struct MemoryRegionOps {
>>                                      unsigned size,
>>                                      MemTxAttrs attrs);
>>      /* Instruction execution pre-callback:
>> -     * @addr is the address of the access relative to the @mr.
>> -     * @size is the size of the area returned by the callback.
>> -     * @offset is the location of the pointer inside @mr.
>> +     * @opaque is the opaque pointer for the MemoryRegion.
>> +     * @alloc_token is a token to pass to memory_region_mmio_ptr_alloc()
>> +     * @offset contains the address of the access relative to the @mr.
>>       *
>> -     * Returns a pointer to a location which contains guest code.
>> +     * The request_mmio_exec callback must:
>> +     *  - adjust the offset downwards as desired and determine the size
>> +     *    of the region it wants to provide cached guest code for.
>> +     *    This must start on a guest page boundary and be a multiple
>> +     *    of a guest page in size.
>> +     *  - call memory_region_mmio_ptr_alloc(@alloc_token, size)
>> +     *  - fill in the contents of the RAM
>> +     * Ownership of @newmr remains with the caller; it will be
>> +     * automatically freed when no longer in use.
>> +     *
>> +     * If the callback cannot satisfy the request then it should
>> +     * return false. Otherwise it must
>> +     *  - update @offset to the offset of the memory within the MemoryRegion
>> +     *    this is a callback for (ie the possibly rounded down value)
>> +     *  - return true
>> +     * Note that if you return false then execution of the guest is going
>> +     * to go wrong, either by QEMU delivering a bus abort exception to the
>> +     * guest, or by simply exiting as unable to handle the situation, in
>> +     * the same way that it would if you did not provide a request_mmio_exec
>> +     * callback at all.
>>       */
>> -    void *(*request_ptr)(void *opaque, hwaddr addr, unsigned *size,
>> -                         unsigned *offset);
>> +    bool (*request_mmio_exec)(void *opaque, void *alloc_token, hwaddr 
>> *offset);
>>  
>>      enum device_endian endianness;
>>      /* Guest-visible constraints: */
>> @@ -1569,6 +1587,20 @@ bool memory_region_request_mmio_ptr(MemoryRegion *mr, 
>> hwaddr addr);
>>  void memory_region_invalidate_mmio_ptr(MemoryRegion *mr, hwaddr offset,
>>                                         unsigned size);
>>  
>> +/**
>> + * memory_region_mmio_ptr_alloc: allocate memory from request_mmio_exec 
>> callback
>> + *
>> + * This function should be called only from a MemoryRegion's 
>> request_mmio_exec
>> + * callback function, in order to allocate the memory that should be filled
>> + * with the cached contents to be executed.
>> + * Note that once the request_mmio_exec callback calls this function it is
>> + * committed to handling the request, and may not return false.
>> + *
>> + * @alloc_token: the argument passed into the request_mmio_exec callback
>> + * @size: size in bytes to allocate
>> + */
>> +void *memory_region_mmio_ptr_alloc(void *alloc_token, uint64_t size);
>> +
>>  /**
>>   * memory_region_dispatch_read: perform a read directly to the specified
>>   * MemoryRegion.
>> diff --git a/hw/ssi/xilinx_spips.c b/hw/ssi/xilinx_spips.c
>> index 03f5faee4b..8bb049f797 100644
>> --- a/hw/ssi/xilinx_spips.c
>> +++ b/hw/ssi/xilinx_spips.c
>> @@ -1202,21 +1202,29 @@ static void lqspi_load_cache(void *opaque, hwaddr 
>> addr)
>>      }
>>  }
>>  
>> -static void *lqspi_request_mmio_ptr(void *opaque, hwaddr addr, unsigned 
>> *size,
>> -                                    unsigned *offset)
>> +static bool lqspi_request_mmio_exec(void *opaque, void *alloc_token,
>> +                                    hwaddr *offset)
>>  {
>>      XilinxQSPIPS *q = opaque;
>>      hwaddr offset_within_the_region;
>> +    void *hostptr;
>>  
>>      if (!q->mmio_execution_enabled) {
>> -        return NULL;
>> +        return false;
>>      }
>>  
>> -    offset_within_the_region = addr & ~(LQSPI_CACHE_SIZE - 1);
>> +    /* This could perhaps be cleverer and avoid the memcpy by
>> +     * caching directly into the allocated area.
>> +     */
>> +    offset_within_the_region = *offset & ~(LQSPI_CACHE_SIZE - 1);
>>      lqspi_load_cache(opaque, offset_within_the_region);
>> -    *size = LQSPI_CACHE_SIZE;
>> +
>> +    hostptr = memory_region_mmio_ptr_alloc(alloc_token, LQSPI_CACHE_SIZE);
>> +
>> +    memcpy(hostptr, q->lqspi_buf, LQSPI_CACHE_SIZE);
>> +
>>      *offset = offset_within_the_region;
>> -    return q->lqspi_buf;
>> +    return true;
>>  }
>>  
>>  static uint64_t
>> @@ -1240,7 +1248,7 @@ lqspi_read(void *opaque, hwaddr addr, unsigned int 
>> size)
>>  
>>  static const MemoryRegionOps lqspi_ops = {
>>      .read = lqspi_read,
>> -    .request_ptr = lqspi_request_mmio_ptr,
>> +    .request_mmio_exec = lqspi_request_mmio_exec,
>>      .endianness = DEVICE_NATIVE_ENDIAN,
>>      .valid = {
>>          .min_access_size = 1,
>> diff --git a/memory.c b/memory.c
>> index e70b64b8b9..71dec85156 100644
>> --- a/memory.c
>> +++ b/memory.c
>> @@ -2653,41 +2653,74 @@ void memory_listener_unregister(MemoryListener 
>> *listener)
>>  
>>  bool memory_region_request_mmio_ptr(MemoryRegion *mr, hwaddr addr)
>>  {
>> -    void *host;
>> -    unsigned size = 0;
>> -    unsigned offset = 0;
>> -    Object *new_interface;
>> +    MemoryRegion *newmr;
>> +    hwaddr offset;
>>  
>> -    if (!mr || !mr->ops->request_ptr) {
>> +    if (!mr || !mr->ops->request_mmio_exec) {
>>          return false;
>>      }
>>  
>>      /*
>> -     * Avoid an update if the request_ptr call
>> -     * memory_region_invalidate_mmio_ptr which seems to be likely when we 
>> use
>> -     * a cache.
>> +     * Avoid an update if the request_mmio_exec hook calls
>> +     * memory_region_invalidate_mmio_ptr, which it will do if the
>> +     * device can only handle one outstanding MMIO exec region at once.
>>       */
>>      memory_region_transaction_begin();
>>  
>> -    host = mr->ops->request_ptr(mr->opaque, addr - mr->addr, &size, 
>> &offset);
>> -
>> -    if (!host || !size) {
>> +    newmr = g_new0(MemoryRegion, 1);
>> +    offset = addr - mr->addr;
>> +    if (!mr->ops->request_mmio_exec(mr->opaque, newmr, &offset)) {
>>          memory_region_transaction_commit();
>> +        g_free(newmr);
>>          return false;
>>      }
>>  
>> -    new_interface = object_new("mmio_interface");
>> -    qdev_prop_set_uint64(DEVICE(new_interface), "start", offset);
>> -    qdev_prop_set_uint64(DEVICE(new_interface), "end", offset + size - 1);
>> -    qdev_prop_set_bit(DEVICE(new_interface), "ro", true);
>> -    qdev_prop_set_ptr(DEVICE(new_interface), "host_ptr", host);
>> -    qdev_prop_set_ptr(DEVICE(new_interface), "subregion", mr);
>> -    object_property_set_bool(OBJECT(new_interface), true, "realized", NULL);
>> +    /* These requirements are because currently TCG needs a page sized
>> +     * area to execute from.
>> +     */
>> +    assert((offset & ~TARGET_PAGE_MASK) == 0);
>> +    assert((memory_region_size(newmr) & ~TARGET_PAGE_MASK) == 0);
>>  
>> +
>> +    memory_region_add_subregion(mr, offset, newmr);
>>      memory_region_transaction_commit();
>> +
>> +    /* Arrange that we automatically free this MemoryRegion when
>> +     * its refcount goes to zero, which (once we've unrefed it here)
>> +     * will happen when it is removed from the subregion.
>> +     */
>> +    OBJECT(newmr)->free = g_free;
>> +    object_unref(OBJECT(newmr));
>>      return true;
>>  }
>>  
>> +void *memory_region_mmio_ptr_alloc(void *alloc_token, uint64_t size)
>> +{
>> +    /* Allocate and return memory for an mmio_ptr request. This
>> +     * is a function intended to be called from a memory region's
>> +     * request_mmio_exec function, and the opaque pointer is the
>> +     * newmr MemoryRegion allocated in memory_region_request_mmio_ptr().
>> +     */
>> +    MemoryRegion *newmr = alloc_token;
>> +    Error *err = NULL;
>> +
>> +    /* Note that in order for the refcounting to work correctly
>> +     * without having to manufacture a device purely to be the
>> +     * owner of the MemoryRegion, we must pass OBJECT(newmr) as
>> +     * the owner (so the MR is its own owner!) and NULL as the name
>> +     * (or the property created on the owner causes the refcount
>> +     * to go negative during finalization)...
>> +     */
>> +    memory_region_init_ram_nomigrate(newmr, OBJECT(newmr), NULL,
>> +                                     size, &err);
>> +    if (err) {
>> +        error_free(err);
>> +        return NULL;
>> +    }
>> +
>> +    return memory_region_get_ram_ptr(newmr);
>> +}
>> +
>>  typedef struct MMIOPtrInvalidate {
>>      MemoryRegion *mr;
>>      hwaddr offset;
>> @@ -2714,15 +2747,8 @@ static void 
>> memory_region_do_invalidate_mmio_ptr(CPUState *cpu,
>>      cpu_physical_memory_test_and_clear_dirty(offset, size, 1);
>>  
>>      if (section.mr != mr) {
>> -        /* memory_region_find add a ref on section.mr */
>> +        memory_region_del_subregion(mr, section.mr);
>>          memory_region_unref(section.mr);
>> -        if (MMIO_INTERFACE(section.mr->owner)) {
>> -            /* We found the interface just drop it. */
>> -            object_property_set_bool(section.mr->owner, false, "realized",
>> -                                     NULL);
>> -            object_unref(section.mr->owner);
>> -            object_unparent(section.mr->owner);
>> -        }
>>      }
>>  
>>      qemu_mutex_unlock_iothread();
>>
> 




reply via email to

[Prev in Thread] Current Thread [Next in Thread]