[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] Re: [PATCH v4 2/2] xen/arm: add FF-A mediator
Hi Julien, On Fri, Jul 8, 2022 at 9:54 PM Julien Grall <julien@xxxxxxx> wrote: > > Hi Jens, > > This is the second part of the review. > > On 22/06/2022 14:42, Jens Wiklander wrote: > > +static int get_shm_pages(struct domain *d, struct ffa_shm_mem *shm, > > + struct ffa_address_range *range, uint32_t > > range_count, > > AFAICT, 'range' is not meant to be modified. So I would add 'const'. OK > > > + unsigned int start_page_idx, > > + unsigned int *last_page_idx) > > +{ > > + unsigned int pg_idx = start_page_idx; > > + unsigned long gfn; > > Below you are using gaddr_to_gfn() which will return gfn_t. This is > using the typesafe infrastructure: gfn_t will be a structure with > CONFIG_DEBUG=y to allow type checking and a plain 'unsigned long' when > CONFIG_DEBUG=n. > > Please make sure to test build with CONFIG_DEBUG=y. > > As a side note, I would suggest to try booting as CONFIG_DEBUG as it > enables extra check for the common pitfalls. Thanks, I'll do that. > > > + unsigned int n; > > + unsigned int m; > > + p2m_type_t t; > > + uint64_t addr; > > + > > + for ( n = 0; n < range_count; n++ ) > > + { > > + for ( m = 0; m < range[n].page_count; m++ ) > > + { > > + if ( pg_idx >= shm->page_count ) > > + return FFA_RET_INVALID_PARAMETERS; > > Shouldn't we call put_page() to drop the references taken by > get_page_from_gfn()? Yes, and that's done by put_shm_pages(). One would normally expect get_shm_pages() to do this on error, but that's not needed here since we're always calling put_shm_pages() just before freeing the shm. I can change to let get_shm_pages() do the cleanup on error instead if you prefer that. > > > + > > + addr = read_atomic(&range[n].address); > > IIUC, range is part of the shared page with the guest. Where do you > check that all the access will be within the shared page? It's checked by the callers. > > > + gfn = gaddr_to_gfn(addr + m * PAGE_SIZE); > > Is addr meant to be page-aligned? Also, you are using the hypervisor > page size here when AFAICT page_count is provided by the domain. You're right, this is confusing. I'll define a FFA_PAGE_SIZE to SZ_4K and use that instead. Note that with FF-A a page is always 4K even if the smallest unit/granule may be larger (16kB or 64kB). > > How do you guarantee that both Xen and the domain agree on the page size? For now, I'll add a BUILD_BUG_ON() to check that the hypervisor page size is 4K to simplify the initial implementation. We can update to support a larger minimal memory granule later on. > > > + shm->pages[pg_idx] = get_page_from_gfn(d, gfn, &t, P2M_ALLOC); > > + if ( !shm->pages[pg_idx] ) > > + return FFA_RET_DENIED; > > + pg_idx++; > > + /* Only normal RAM for now */ > > Similar to my earlier remark, the comment doesn't match the check below. I'll fix. > > > + if ( t != p2m_ram_rw ) > > + return FFA_RET_DENIED; > > + } > > + } > > + > > + *last_page_idx = pg_idx; > > + > > + return FFA_RET_OK; > > +} > > + > > +static void put_shm_pages(struct ffa_shm_mem *shm) > > +{ > > + unsigned int n; > > + > > + for ( n = 0; n < shm->page_count && shm->pages[n]; n++ ) > > + { > > + put_page(shm->pages[n]); > > + shm->pages[n] = NULL; > > + } > > +} > > + > > +static void init_range(struct ffa_address_range *addr_range, > > + paddr_t pa) > +{ > > + memset(addr_range, 0, sizeof(*addr_range)); > > + addr_range->address = pa; > > + addr_range->page_count = 1; > > +} > > + > > +static int share_shm(struct ffa_shm_mem *shm) > > AFAIU, share_shm() cannot be concurrently called. You seem to use > ffa_buffer_lock to guarantee that. So I would suggest to add: > 1) an ASSERT(spin_is_Locked(&ffa_buffer_lock)) > 2) a comment on top of share_shm() explaining that the function > should be called with ffa_buffer_lock taken. Yes, it's the ffa_tx buffer that must be protected against concurrent use. I'll update. > > > +{ > > + uint32_t max_frag_len = ffa_page_count * PAGE_SIZE; > > + struct ffa_mem_transaction_1_1 *descr = ffa_tx; > > + struct ffa_mem_access *mem_access_array; > > + struct ffa_mem_region *region_descr; > > + struct ffa_address_range *addr_range; > > + paddr_t pa; > > + paddr_t last_pa; > > + unsigned int n; > > + uint32_t frag_len; > > + uint32_t tot_len; > > + int ret; > > + unsigned int range_count; > > + unsigned int range_base; > > + bool first; > > + > > + memset(descr, 0, sizeof(*descr)); > > + descr->sender_id = shm->sender_id; > > + descr->global_handle = shm->handle; > > + descr->mem_reg_attr = FFA_NORMAL_MEM_REG_ATTR; > > + descr->mem_access_count = 1; > > + descr->mem_access_size = sizeof(*mem_access_array); > > + descr->mem_access_offs = sizeof(*descr); > > + mem_access_array = (void *)(descr + 1); > > + region_descr = (void *)(mem_access_array + 1); > > The (void *)(descr + 1) seems to be very common in your comment. Can you > consider to add a wrapper? This will make easier to read the code? OK, I'll try something different. > > > + > > + memset(mem_access_array, 0, sizeof(*mem_access_array)); > > + mem_access_array[0].access_perm.endpoint_id = shm->ep_id; > > + mem_access_array[0].access_perm.perm = FFA_MEM_ACC_RW; > > + mem_access_array[0].region_offs = (vaddr_t)region_descr - > > (vaddr_t)ffa_tx; > > Same for calculating the offset. OK > > > + > > + memset(region_descr, 0, sizeof(*region_descr)); > > + region_descr->total_page_count = shm->page_count; > > + > > + region_descr->address_range_count = 1; > > + last_pa = page_to_maddr(shm->pages[0]); > For hardening purpose, I would suggest to check if shm->page_count is at > least 1. If you think this could be a programming error then you could > write: > > if ( .... ) > { > ASSERT_UNREACHABLE() > return <error>; > } OK > > > + for ( n = 1; n < shm->page_count; last_pa = pa, n++ ) > > + { > > + pa = page_to_maddr(shm->pages[n]); > > + if ( last_pa + PAGE_SIZE == pa ) > > + { > > Coding style: We usually avoid {} for single line. OK > > > + continue; > > + } > > + region_descr->address_range_count++; > > + } > > + > > + tot_len = sizeof(*descr) + sizeof(*mem_access_array) + > > + sizeof(*region_descr) + > > + region_descr->address_range_count * sizeof(*addr_range); > > How do you make sure that you will not write past the end of ffa_tx? > > I think it would be worth to consider adding an helper that would allow > you to allocate space in ffa_tx and zero it. This would return an error > if there is not enough space. That's what I'm doing with frag_len. If the descriptor cannot fit it's divided into fragments that will fit. > > > + > > + addr_range = region_descr->address_range_array; > > + frag_len = (vaddr_t)(addr_range + 1) - (vaddr_t)ffa_tx; > > + last_pa = page_to_maddr(shm->pages[0]); > > + init_range(addr_range, last_pa); > > + first = true; > > + range_count = 1; > > + range_base = 0; > > + for ( n = 1; n < shm->page_count; last_pa = pa, n++ ) > > + { > > + pa = page_to_maddr(shm->pages[n]); > > + if ( last_pa + PAGE_SIZE == pa ) > > + { > > + addr_range->page_count++; > > + continue; > > + } > > + > > + if ( frag_len == max_frag_len ) > > + { > > + if ( first ) > > + { > > + ret = ffa_mem_share(tot_len, frag_len, 0, 0, &shm->handle); > > + first = false; > > + } > > + else > > + { > > + ret = ffa_mem_frag_tx(shm->handle, frag_len, > > shm->sender_id); > > + } > > + if ( ret <= 0) > > Coding style: Missing space before ). > > > + return ret; > > + range_base = range_count; > > You set range_base but don't seem to read it > > > + range_count = 0; > > Same here. I'll remove them. > > > + frag_len = sizeof(*addr_range); > > + addr_range = ffa_tx; > > + } > > + else > > + { > > + frag_len += sizeof(*addr_range); > > + addr_range++; > > + } > > + init_range(addr_range, pa); > > + range_count++; > > + } > > + > > + if ( first ) > > + return ffa_mem_share(tot_len, frag_len, 0, 0, &shm->handle); > > + else > > + return ffa_mem_frag_tx(shm->handle, frag_len, shm->sender_id); > > +} > > + > > +static int read_mem_transaction(uint32_t ffa_vers, void *buf, size_t blen, > > buf should be const if it is not meant to be modified by the function. OK > > > + struct ffa_mem_transaction_x *trans) > > +{ > > + uint16_t mem_reg_attr; > > + uint32_t flags; > > + uint32_t count; > > + uint32_t offs; > > + uint32_t size; > > + > > + if ( ffa_vers >= FFA_VERSION_1_1 ) > > + { > > + struct ffa_mem_transaction_1_1 *descr; > > + > > + if ( blen < sizeof(*descr) ) > > + return FFA_RET_INVALID_PARAMETERS; > > + > > + descr = buf; > > + trans->sender_id = read_atomic(&descr->sender_id); > AFAIU, descr point to guest data. If yes, then we can't trust input. In > which case, is this really necessary to use read_atomic() for every access? > > The reason I am asking is read_atomic() is quite a hammer when a > compiler barrier should be sufficient. I see, I'll use barrier() instead. > > > + mem_reg_attr = read_atomic(&descr->mem_reg_attr); > > + flags = read_atomic(&descr->flags); > > + trans->global_handle = read_atomic(&descr->global_handle); > > + trans->tag = read_atomic(&descr->tag); > > + > > + count = read_atomic(&descr->mem_access_count); > > + size = read_atomic(&descr->mem_access_size); > > + offs = read_atomic(&descr->mem_access_offs); > > + } > > + else > > + { > > + struct ffa_mem_transaction_1_0 *descr; > > + > > + if ( blen < sizeof(*descr) ) > > + return FFA_RET_INVALID_PARAMETERS; > > + > > + descr = buf; > > + trans->sender_id = read_atomic(&descr->sender_id); > > + mem_reg_attr = read_atomic(&descr->mem_reg_attr); > > + flags = read_atomic(&descr->flags); > > + trans->global_handle = read_atomic(&descr->global_handle); > > + trans->tag = read_atomic(&descr->tag); > > + > > + count = read_atomic(&descr->mem_access_count); > > + size = sizeof(struct ffa_mem_access); > > + offs = offsetof(struct ffa_mem_transaction_1_0, mem_access_array); > > + } > > + > > + if ( mem_reg_attr > UINT8_MAX || flags > UINT8_MAX || size > UINT8_MAX > > || > > AFAIU, these checks are to ensure that the fields fit in your structure > below. However, it is not clear to me why we are capping the values > provided by the domain. > > I think this would be good to explain it in a comment. OK, I'll add something. > > > + count > UINT8_MAX || offs > UINT16_MAX ) > > + return FFA_RET_INVALID_PARAMETERS; > > + > > + /* Check that the endpoint memory access descriptor array fits */ > > + if ( size * count + offs > blen ) > > + return FFA_RET_INVALID_PARAMETERS; > > + > > + trans->mem_reg_attr = mem_reg_attr; > > + trans->flags = flags; > > + trans->mem_access_size = size; > > + trans->mem_access_count = count; > > + trans->mem_access_offs = offs; > > + return 0; > > +} > > + > > +static int add_mem_share_frag(struct mem_frag_state *s, unsigned int offs, > > + unsigned int frag_len) > > +{ > > + struct domain *d = current->domain; > > + unsigned int o = offs; > > + unsigned int l; > > + int ret; > > + > > + if ( frag_len < o ) > > + return FFA_RET_INVALID_PARAMETERS; > > + > > + /* Fill up the first struct ffa_address_range */ > > + l = min_t(unsigned int, frag_len - o, sizeof(s->range) - > > s->range_offset); > > + memcpy((uint8_t *)&s->range + s->range_offset, s->buf + o, l); > > + s->range_offset += l; > > + o += l; > > + if ( s->range_offset != sizeof(s->range) ) > > + goto out; > > + s->range_offset = 0; > > + > > + while ( true ) > > + { > > + ret = get_shm_pages(d, s->shm, &s->range, 1, s->current_page_idx, > > + &s->current_page_idx); > > + if ( ret ) > > + return ret; > > + if ( s->range_count == 1 ) > > + return 0; > > + s->range_count--; > > + if ( frag_len - o < sizeof(s->range) ) > > + break; > > + memcpy(&s->range, s->buf + o, sizeof(s->range)); > > + o += sizeof(s->range); > > + } > > + > > + /* Collect any remaining bytes for the next struct ffa_address_range */ > > + s->range_offset = frag_len - o; > > + memcpy(&s->range, s->buf + o, frag_len - o); > > +out: > > + s->frag_offset += frag_len; > > + return s->frag_offset; > > +} > > + > > +static void handle_mem_share(struct cpu_user_regs *regs) > > +{ > > + uint32_t tot_len = get_user_reg(regs, 1); > > + uint32_t frag_len = get_user_reg(regs, 2); > > + uint64_t addr = get_user_reg(regs, 3); > > + uint32_t page_count = get_user_reg(regs, 4); > > + struct ffa_mem_transaction_x trans; > > + struct ffa_mem_access *mem_access; > > + struct ffa_mem_region *region_descr; > > + struct domain *d = current->domain; > > + struct ffa_ctx *ctx = d->arch.ffa; > > + struct ffa_shm_mem *shm = NULL; > > + unsigned int last_page_idx = 0; > > + uint32_t range_count; > > + uint32_t region_offs; > > + int ret = FFA_RET_DENIED; > > + uint32_t handle_hi = 0; > > + uint32_t handle_lo = 0; > > + > > + /* > > + * We're only accepting memory transaction descriptors via the rx/tx > > + * buffer. > > + */ > > + if ( addr ) > > + { > > + ret = FFA_RET_NOT_SUPPORTED; > > + goto out_unlock; > > + } > > + > > + /* Check that fragment legnth doesn't exceed total length */ > > Typo: s/legnth/length/ > > > + if ( frag_len > tot_len ) > > + { > > + ret = FFA_RET_INVALID_PARAMETERS; > > + goto out_unlock; > > + } > > + > > + spin_lock(&ctx->lock); > > + > > + if ( frag_len > ctx->page_count * PAGE_SIZE ) > > + goto out_unlock; > > + > > + if ( !ffa_page_count ) > > + { > > + ret = FFA_RET_NO_MEMORY; > > + goto out_unlock; > > + } > > + > > + ret = read_mem_transaction(ctx->guest_vers, ctx->tx, frag_len, &trans); > > + if ( ret ) > > + goto out_unlock; > > + > > + if ( trans.mem_reg_attr != FFA_NORMAL_MEM_REG_ATTR ) > > + { > > + ret = FFA_RET_NOT_SUPPORTED; > > + goto out; > > + } > > + > > + /* Only supports sharing it with one SP for now */ > > + if ( trans.mem_access_count != 1 ) > > + { > > + ret = FFA_RET_NOT_SUPPORTED; > > + goto out_unlock; > > + } > > + > > + if ( trans.sender_id != get_vm_id(d) ) > > + { > > + ret = FFA_RET_INVALID_PARAMETERS; > > + goto out_unlock; > > + } > > + > > + /* Check that it fits in the supplied data */ > > + if ( trans.mem_access_offs + trans.mem_access_size > frag_len ) > > + goto out_unlock; > > + > > + mem_access = (void *)((vaddr_t)ctx->tx + trans.mem_access_offs); > > There are a bit too much open-cast in this series. Please try to reduce > the numbers. OK > > In this case, the two casts are unnecessary because tx is already a void > pointer. Thanks, I didn't realize that void pointer arithmetics was allowed here. > > In addition to that, ctx->tx could be a "const void *" because it is not > meant to be written by Xen. The const would also needs to be propagated > to mem_access & co. OK > > > + if ( read_atomic(&mem_access->access_perm.perm) != FFA_MEM_ACC_RW ) > > + { > > + ret = FFA_RET_NOT_SUPPORTED; > > + goto out_unlock; > > + } > > + > > + region_offs = read_atomic(&mem_access->region_offs); > > + if ( sizeof(*region_descr) + region_offs > frag_len ) > > + { > > + ret = FFA_RET_NOT_SUPPORTED; > > + goto out_unlock; > > + } > > + > > + region_descr = (void *)((vaddr_t)ctx->tx + region_offs); > > + range_count = read_atomic(®ion_descr->address_range_count); > > + page_count = read_atomic(®ion_descr->total_page_count); > > + > > + shm = xzalloc_flex_struct(struct ffa_shm_mem, pages, page_count) > This will allow a guest to allocate an arbitrarily large array in Xen. > So please sanitize page_count before using it. This is tricky, what is a reasonable limit? If we do set a limit the guest can still share many separate memory ranges. > > > + if ( !shm ) > > + { > > + ret = FFA_RET_NO_MEMORY; > > + goto out; > > + } > > + shm->sender_id = trans.sender_id; > > + shm->ep_id = read_atomic(&mem_access->access_perm.endpoint_id); > > + shm->page_count = page_count; > > + > > + if ( frag_len != tot_len ) > > + { > > + struct mem_frag_state *s = xzalloc(struct mem_frag_state); > > + > > + if ( !s ) > > + { > > + ret = FFA_RET_NO_MEMORY; > > + goto out; > > + } > > + s->shm = shm; > > + s->range_count = range_count; > > + s->buf = ctx->tx; > > + s->buf_size = ffa_page_count * PAGE_SIZE; > > + ret = add_mem_share_frag(s, sizeof(*region_descr) + region_offs, > > + frag_len); > > + if ( ret <= 0 ) > > + { > > + xfree(s); > > + if ( ret < 0 ) > > + goto out; > > + } > > + else > > + { > > + shm->handle = next_handle++; > > + reg_pair_from_64(&handle_hi, &handle_lo, shm->handle); > > + list_add_tail(&s->list, &ctx->frag_list); > > + } > > + goto out_unlock; > > + } > > + > > + /* > > + * Check that the Composite memory region descriptor fits. > > + */ > > + if ( sizeof(*region_descr) + region_offs + > > + range_count * sizeof(struct ffa_address_range) > frag_len ) > > + { > > + ret = FFA_RET_INVALID_PARAMETERS; > > + goto out; > > + } > > + > > + ret = get_shm_pages(d, shm, region_descr->address_range_array, > > range_count, > > + 0, &last_page_idx); > > + if ( ret ) > > + goto out; > > + if ( last_page_idx != shm->page_count ) > > + { > > + ret = FFA_RET_INVALID_PARAMETERS; > > + goto out; > > + } > > + > > + /* Note that share_shm() uses our tx buffer */ > > + spin_lock(&ffa_buffer_lock); > > + ret = share_shm(shm); > > + spin_unlock(&ffa_buffer_lock); > > + if ( ret ) > > + goto out; > > + > > + spin_lock(&ffa_mem_list_lock); > > + list_add_tail(&shm->list, &ffa_mem_list); > > A couple of questions: > - What is the maximum size of the list? Currently, there is no limit. I'm not sure what is a reasonable limit more than five for sure, but depending on the use case more than 100 might be excessive. > - Why is the list is global rather than per domain? In fact, looking > at handle_mem_reclaim() it looks like a domain could potentially reclaim > in memory (we don't seem to sanitize the input other than checking the > handle is used). So it seems to me the list should be per-domain. OK, I'll move it into struct ffa_ctx. > > > + spin_unlock(&ffa_mem_list_lock); > + > > + reg_pair_from_64(&handle_hi, &handle_lo, shm->handle); > > + > > +out: > > + if ( ret && shm ) > > + { > > + put_shm_pages(shm); > > + xfree(shm); > > + } > > +out_unlock: > > + spin_unlock(&ctx->lock); > > + > > + if ( ret > 0 ) > > + set_regs_frag_rx(regs, handle_lo, handle_hi, ret, > > trans.sender_id); > > + else if ( ret == 0) > > Coding style: missing space before ). OK > > > + set_regs_success(regs, handle_lo, handle_hi); > > + else > > + set_regs_error(regs, ret); > > +} > > + > > I will continue the review later on. Thanks for the comments so far. Cheers, Jens
|
Lists.xenproject.org is hosted with RackSpace, monitoring our |