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

Re: [PATCH v5 3/6] xen/arm: ffa: Introduce VM to VM support



Hi Bertrand,

On Wed, Apr 16, 2025 at 9:40 AM Bertrand Marquis
<bertrand.marquis@xxxxxxx> wrote:
>
> Create a CONFIG_FFA_VM_TO_VM parameter to activate FFA communication
> between VMs.
> When activated list VMs in the system with FF-A support in part_info_get.
>
> When VM to VM is activated, Xen will be tainted as Insecure and a
> message is displayed to the user during the boot as there is no
> filtering of VMs in FF-A so any VM can communicate or see any other VM
> in the system.
>
> WARNING: There is no filtering for now and all VMs are listed !!
>
> This patch is reorganizing the ffa_ctx structure to make clear which
> lock is protecting what parts.
>
> This patch is introducing a chain list of the ffa_ctx with a FFA Version
> negociated allowing to create the partinfo results for VMs without
> taking a lock on the global domain list in Xen.
>
> Signed-off-by: Bertrand Marquis <bertrand.marquis@xxxxxxx>
> ---
> Changes in v5:
> - remove invalid comment about 1.1 firmware support
> - rename variables from d and dom to curr_d and dest_d (Julien)
> - add a TODO in the code for potential holding for long of the CPU
>   (Julien)
> - use an atomic global variable to store the number of VMs instead of
>   recomputing the value each time (Julien)
> - add partinfo information in ffa_ctx (id, cpus and 64bit) and create a
>   chain list of ctx. Use this chain list to create the partinfo result
>   without holding a global lock to prevent concurrency issues.
> - Move some changes in a preparation patch modifying partinfo for sps to
>   reduce this patch size and make the review easier
> Changes in v4:
> - properly handle SPMC version 1.0 header size case in partinfo_get
> - switch to local counting variables instead of *pointer += 1 form
> - coding style issue with missing spaces in if ()
> Changes in v3:
> - break partinfo_get in several sub functions to make the implementation
>   easier to understand and lock handling easier
> - rework implementation to check size along the way and prevent previous
>   implementation limits which had to check that the number of VMs or SPs
>   did not change
> - taint Xen as INSECURE when VM to VM is enabled
> Changes in v2:
> - Switch ifdef to IS_ENABLED
> - dom was not switched to d as requested by Jan because there is already
>   a variable d pointing to the current domain and it must not be
>   shadowed.
> ---
>  xen/arch/arm/tee/Kconfig        |  11 ++++
>  xen/arch/arm/tee/ffa.c          |  47 +++++++++++++-
>  xen/arch/arm/tee/ffa_partinfo.c |  95 ++++++++++++++++++++++++---
>  xen/arch/arm/tee/ffa_private.h  | 112 ++++++++++++++++++++++++++------
>  4 files changed, 233 insertions(+), 32 deletions(-)
>
> diff --git a/xen/arch/arm/tee/Kconfig b/xen/arch/arm/tee/Kconfig
> index c5b0f88d7522..88a4c4c99154 100644
> --- a/xen/arch/arm/tee/Kconfig
> +++ b/xen/arch/arm/tee/Kconfig
> @@ -28,5 +28,16 @@ config FFA
>
>           [1] https://developer.arm.com/documentation/den0077/latest
>
> +config FFA_VM_TO_VM
> +    bool "Enable FF-A between VMs (UNSUPPORTED)" if UNSUPPORTED
> +    default n
> +    depends on FFA
> +    help
> +      This option enables to use FF-A between VMs.
> +      This is experimental and there is no access control so any
> +      guest can communicate with any other guest.
> +
> +      If unsure, say N.
> +
>  endmenu
>
> diff --git a/xen/arch/arm/tee/ffa.c b/xen/arch/arm/tee/ffa.c
> index 3bbdd7168a6b..c1c4c0957091 100644
> --- a/xen/arch/arm/tee/ffa.c
> +++ b/xen/arch/arm/tee/ffa.c
> @@ -118,6 +118,13 @@ void *ffa_tx __read_mostly;
>  DEFINE_SPINLOCK(ffa_rx_buffer_lock);
>  DEFINE_SPINLOCK(ffa_tx_buffer_lock);
>
> +struct list_head ffa_ctx_head;
> +/* Lock to protect addition/removal in ffa_ctx_head */
> +DEFINE_SPINLOCK(ffa_ctx_list_lock);
> +
> +#ifdef CONFIG_FFA_VM_TO_VM
> +atomic_t ffa_vm_count;
> +#endif
>
>  /* Used to track domains that could not be torn down immediately. */
>  static struct timer ffa_teardown_timer;
> @@ -160,10 +167,21 @@ static void handle_version(struct cpu_user_regs *regs)
>       */
>      if ( FFA_VERSION_MAJOR(vers) == FFA_MY_VERSION_MAJOR )
>      {
> +        uint32_t old_vers = ACCESS_ONCE(ctx->guest_vers);
> +
>          if ( FFA_VERSION_MINOR(vers) > FFA_MY_VERSION_MINOR )
> -            ctx->guest_vers = FFA_MY_VERSION;
> +            ACCESS_ONCE(ctx->guest_vers) = FFA_MY_VERSION;
>          else
> -            ctx->guest_vers = vers;
> +            ACCESS_ONCE(ctx->guest_vers) = vers;

What is the ACCESS_ONCE() scheme intended for?

> +
> +        if ( IS_ENABLED(CONFIG_FFA_VM_TO_VM) && !old_vers )

If handle_version() is called concurrently on two CPUs, it might be
possible for a context to be added twice.
How about a separate flag to indicate whether a context has been added
to the list?

Cheers,
Jens

> +        {
> +            /* One more VM with FF-A support available */
> +            inc_ffa_vm_count();
> +            spin_lock(&ffa_ctx_list_lock);
> +            list_add_tail(&ctx->ctx_list, &ffa_ctx_head);
> +            spin_unlock(&ffa_ctx_list_lock);
> +        }
>      }
>      ffa_set_regs(regs, FFA_MY_VERSION, 0, 0, 0, 0, 0, 0, 0);
>  }
> @@ -345,6 +363,10 @@ static int ffa_domain_init(struct domain *d)
>      ctx->teardown_d = d;
>      INIT_LIST_HEAD(&ctx->shm_list);
>
> +    ctx->ffa_id = ffa_get_vm_id(d);
> +    ctx->num_vcpus = d->max_vcpus;
> +    ctx->is_64bit = is_64bit_domain(d);
> +
>      /*
>       * ffa_domain_teardown() will be called if ffa_domain_init() returns an
>       * error, so no need for cleanup in this function.
> @@ -421,6 +443,14 @@ static int ffa_domain_teardown(struct domain *d)
>      if ( !ctx )
>          return 0;
>
> +    if ( IS_ENABLED(CONFIG_FFA_VM_TO_VM) && ACCESS_ONCE(ctx->guest_vers) )
> +    {
> +        dec_ffa_vm_count();
> +        spin_lock(&ffa_ctx_list_lock);
> +        list_del(&ctx->ctx_list);
> +        spin_unlock(&ffa_ctx_list_lock);
> +    }
> +
>      ffa_rxtx_domain_destroy(d);
>      ffa_notif_domain_destroy(d);
>
> @@ -464,6 +494,18 @@ static bool ffa_probe(void)
>      printk(XENLOG_INFO "ARM FF-A Mediator version %u.%u\n",
>             FFA_MY_VERSION_MAJOR, FFA_MY_VERSION_MINOR);
>
> +    if ( IS_ENABLED(CONFIG_FFA_VM_TO_VM) )
> +    {
> +        /*
> +         * When FFA VM to VM is enabled, the current implementation does not
> +         * offer any way to limit which VM can communicate with which VM 
> using
> +         * FF-A.
> +         * Signal this in the xen console and taint the system as insecure.
> +         * TODO: Introduce a solution to limit what a VM can do through FFA.
> +         */
> +        printk(XENLOG_ERR "ffa: VM to VM is enabled, system is insecure 
> !!\n");
> +        add_taint(TAINT_MACHINE_INSECURE);
> +    }
>      /*
>       * psci_init_smccc() updates this value with what's reported by EL-3
>       * or secure world.
> @@ -538,6 +580,7 @@ static bool ffa_probe(void)
>
>      ffa_notif_init();
>      INIT_LIST_HEAD(&ffa_teardown_head);
> +    INIT_LIST_HEAD(&ffa_ctx_head);
>      init_timer(&ffa_teardown_timer, ffa_teardown_timer_callback, NULL, 0);
>
>      return true;
> diff --git a/xen/arch/arm/tee/ffa_partinfo.c b/xen/arch/arm/tee/ffa_partinfo.c
> index e524445c6fb8..66ea1860e97a 100644
> --- a/xen/arch/arm/tee/ffa_partinfo.c
> +++ b/xen/arch/arm/tee/ffa_partinfo.c
> @@ -149,6 +149,68 @@ out:
>      spin_unlock(&ffa_rx_buffer_lock);
>      return ret;
>  }
> +
> +static int32_t ffa_get_vm_partinfo(uint32_t *vm_count, void *dst_buf,
> +                                   void *end_buf, uint32_t dst_size)
> +{
> +    struct ffa_ctx *curr_ctx = current->domain->arch.tee;
> +    struct ffa_ctx *dest_ctx, *tmp;
> +    uint32_t count = 0;
> +
> +    /*
> +     * There could potentially be a lot of VMs in the system and we could
> +     * hold the CPU for long here.
> +     * Right now there is no solution in FF-A specification to split
> +     * the work in this case.
> +     * TODO: Check how we could delay the work or have preemption checks.
> +     */
> +    list_for_each_entry_safe(dest_ctx, tmp, &ffa_ctx_head, ctx_list)
> +    {
> +        /*
> +         * Do not include an entry for the caller VM as the spec is not
> +         * clearly mandating it and it is not supported by Linux.
> +         */
> +        if ( dest_ctx != curr_ctx )
> +        {
> +            /*
> +             * We do not have UUID info for VMs so use
> +             * the 1.0 structure so that we set UUIDs to
> +             * zero using memset
> +             */
> +            struct ffa_partition_info_1_0 info;
> +
> +            if  ( dst_buf > (end_buf - dst_size) )
> +            {
> +                return FFA_RET_NO_MEMORY;
> +            }
> +
> +            /*
> +             * Context might has been removed since we go it or being removed
> +             * right now so we might return information for a VM not existing
> +             * anymore. This is acceptable as we return a view of the system
> +             * which could change at any time.
> +             */
> +            info.id = dest_ctx->ffa_id;
> +            info.execution_context = dest_ctx->num_vcpus;
> +            info.partition_properties = FFA_PART_VM_PROP;
> +            if ( dest_ctx->is_64bit )
> +                info.partition_properties |= FFA_PART_PROP_AARCH64_STATE;
> +
> +            memcpy(dst_buf, &info, MIN(sizeof(info), dst_size));
> +
> +            if ( dst_size > sizeof(info) )
> +                memset(dst_buf + sizeof(info), 0,
> +                       dst_size - sizeof(info));
> +
> +            dst_buf += dst_size;
> +            count++;
> +        }
> +    }
> +    *vm_count = count;
> +
> +    return FFA_RET_OK;
> +}
> +
>  void ffa_handle_partition_info_get(struct cpu_user_regs *regs)
>  {
>      int32_t ret = FFA_RET_OK;
> @@ -163,7 +225,7 @@ void ffa_handle_partition_info_get(struct cpu_user_regs 
> *regs)
>      };
>      uint32_t dst_size = 0;
>      void *dst_buf, *end_buf;
> -    uint32_t ffa_sp_count = 0;
> +    uint32_t ffa_vm_count = 0, ffa_sp_count = 0;
>
>      /*
>       * If the guest is v1.0, he does not get back the entry size so we must
> @@ -190,15 +252,18 @@ void ffa_handle_partition_info_get(struct cpu_user_regs 
> *regs)
>          }
>
>          if ( ffa_fw_supports_fid(FFA_PARTITION_INFO_GET) )
> +        {
>              ret = ffa_get_sp_count(uuid, &ffa_sp_count);
> +            if ( ret )
> +                goto out;
> +        }
>
> -        goto out;
> -    }
> +        /*
> +         * Do not count the caller VM as the spec is not clearly mandating it
> +         * and it is not supported by Linux.
> +         */
> +        ffa_vm_count = get_ffa_vm_count() - 1;
>
> -    if ( !ffa_fw_supports_fid(FFA_PARTITION_INFO_GET) )
> -    {
> -        /* Just give an empty partition list to the caller */
> -        ret = FFA_RET_OK;
>          goto out;
>      }
>
> @@ -223,9 +288,19 @@ void ffa_handle_partition_info_get(struct cpu_user_regs 
> *regs)
>          goto out_rx_release;
>      }
>
> -    ret = ffa_get_sp_partinfo(uuid, &ffa_sp_count, dst_buf, end_buf,
> -                              dst_size);
> +    if ( ffa_fw_supports_fid(FFA_PARTITION_INFO_GET) )
> +    {
> +        ret = ffa_get_sp_partinfo(uuid, &ffa_sp_count, dst_buf, end_buf,
> +                                  dst_size);
> +
> +        if ( ret )
> +            goto out_rx_release;
> +
> +        dst_buf += ffa_sp_count * dst_size;
> +    }
>
> +    if ( IS_ENABLED(CONFIG_FFA_VM_TO_VM) )
> +        ret = ffa_get_vm_partinfo(&ffa_vm_count, dst_buf, end_buf, dst_size);
>
>  out_rx_release:
>      if ( ret )
> @@ -234,7 +309,7 @@ out:
>      if ( ret )
>          ffa_set_regs_error(regs, ret);
>      else
> -        ffa_set_regs_success(regs, ffa_sp_count, dst_size);
> +        ffa_set_regs_success(regs, ffa_sp_count + ffa_vm_count, dst_size);
>  }
>
>  static int32_t ffa_direct_req_send_vm(uint16_t sp_id, uint16_t vm_id,
> diff --git a/xen/arch/arm/tee/ffa_private.h b/xen/arch/arm/tee/ffa_private.h
> index 0a9c1082db28..52c1078b06f4 100644
> --- a/xen/arch/arm/tee/ffa_private.h
> +++ b/xen/arch/arm/tee/ffa_private.h
> @@ -195,6 +195,18 @@
>   */
>  #define FFA_PARTITION_INFO_GET_COUNT_FLAG BIT(0, U)
>
> +/*
> + * Partition properties we give for a normal world VM:
> + * - can send direct message but not receive them
> + * - can handle indirect messages
> + * - can receive notifications
> + * 32/64 bit flag is set depending on the VM
> + */
> +#define FFA_PART_VM_PROP    (FFA_PART_PROP_DIRECT_REQ_SEND | \
> +                             FFA_PART_PROP_INDIRECT_MSGS | \
> +                             FFA_PART_PROP_RECV_NOTIF | \
> +                             FFA_PART_PROP_IS_PE_ID)
> +
>  /* Flags used in calls to FFA_NOTIFICATION_GET interface  */
>  #define FFA_NOTIF_FLAG_BITMAP_SP        BIT(0, U)
>  #define FFA_NOTIF_FLAG_BITMAP_VM        BIT(1, U)
> @@ -297,36 +309,66 @@ struct ffa_ctx_notif {
>  };
>
>  struct ffa_ctx {
> -    void *rx;
> -    const void *tx;
> -    struct page_info *rx_pg;
> -    struct page_info *tx_pg;
> -    /* Number of 4kB pages in each of rx/rx_pg and tx/tx_pg */
> -    unsigned int page_count;
> +    /*
> +     * Chain list of all FF-A contexts, to prevent locking access to this 
> list,
> +     * all "unlocked" data from the structure must be set before adding an
> +     * entry in the list and an entry must be removed from the list before
> +     * freeing a context.
> +     */
> +    struct list_head ctx_list; /* chain list of all FF-A contexts */
> +
> +    /*
> +     * Data access unlocked (mainly for part_info_get in VM to VM).
> +     * Those should be set before the ctx is added in the list.
> +     */
> +    /* FF-A Endpoint ID */
> +    uint16_t ffa_id;
> +    uint16_t num_vcpus;
> +    bool is_64bit;
> +
> +    /*
> +     * Global data accessed atomically or using ACCES_ONCE.
> +     */
>      /* FF-A version used by the guest */
>      uint32_t guest_vers;
> -    bool rx_is_free;
> -    /* Used shared memory objects, struct ffa_shm_mem */
> -    struct list_head shm_list;
> +    struct ffa_ctx_notif notif;
> +
> +    /*
> +     * Global data accessed with lock locked.
> +     */
> +    spinlock_t lock;
> +    /* Number of 4kB pages in each of rx/rx_pg and tx/tx_pg */
> +    unsigned int page_count;
>      /* Number of allocated shared memory object */
>      unsigned int shm_count;
> -    struct ffa_ctx_notif notif;
> +    /* Used shared memory objects, struct ffa_shm_mem */
> +    struct list_head shm_list;
> +
>      /*
> -     * tx_lock is used to serialize access to tx
> -     * rx_lock is used to serialize access to rx_is_free
> -     * lock is used for the rest in this struct
> +     * Rx buffer, accessed with rx_lock locked.
> +     * rx_is_free is used to serialize access.
>       */
> -    spinlock_t tx_lock;
>      spinlock_t rx_lock;
> -    spinlock_t lock;
> -    /* Used if domain can't be torn down immediately */
> +    bool rx_is_free;
> +    void *rx;
> +    struct page_info *rx_pg;
> +
> +    /*
> +     * Tx buffer, access with tx_lock locked.
> +     */
> +    spinlock_t tx_lock;
> +    const void *tx;
> +    struct page_info *tx_pg;
> +
> +
> +    /*
> +     * Domain teardown handling if data shared or used by other domains
> +     * do not allow to teardown the domain immediately.
> +     */
>      struct domain *teardown_d;
>      struct list_head teardown_list;
>      s_time_t teardown_expire;
> -    /*
> -     * Used for ffa_domain_teardown() to keep track of which SPs should be
> -     * notified that this guest is being destroyed.
> -     */
> +    /* Keep track of SPs that should be notified of VM destruction */
>      unsigned long *vm_destroy_bitmap;
>  };
>
> @@ -334,8 +376,15 @@ extern void *ffa_rx;
>  extern void *ffa_tx;
>  extern spinlock_t ffa_rx_buffer_lock;
>  extern spinlock_t ffa_tx_buffer_lock;
> +extern spinlock_t ffa_ctx_list_lock;
>  extern DECLARE_BITMAP(ffa_fw_abi_supported, FFA_ABI_BITMAP_SIZE);
>
> +extern struct list_head ffa_ctx_head;
> +
> +#ifdef CONFIG_FFA_VM_TO_VM
> +extern atomic_t ffa_vm_count;
> +#endif
> +
>  bool ffa_shm_domain_destroy(struct domain *d);
>  void ffa_handle_mem_share(struct cpu_user_regs *regs);
>  int ffa_handle_mem_reclaim(uint64_t handle, uint32_t flags);
> @@ -368,6 +417,29 @@ int ffa_handle_notification_set(struct cpu_user_regs 
> *regs);
>  void ffa_handle_msg_send_direct_req(struct cpu_user_regs *regs, uint32_t 
> fid);
>  int32_t ffa_handle_msg_send2(struct cpu_user_regs *regs);
>
> +#ifdef CONFIG_FFA_VM_TO_VM
> +static inline uint16_t get_ffa_vm_count(void)
> +{
> +    return atomic_read(&ffa_vm_count);
> +}
> +
> +static inline void inc_ffa_vm_count(void)
> +{
> +    atomic_inc(&ffa_vm_count);
> +}
> +
> +static inline void dec_ffa_vm_count(void)
> +{
> +    ASSERT(atomic_read(&ffa_vm_count) > 0);
> +    atomic_dec(&ffa_vm_count);
> +}
> +#else
> +/* Only count the caller VM */
> +#define get_ffa_vm_count()  ((uint16_t)1UL)
> +#define inc_ffa_vm_count()  do {} while(0)
> +#define dec_ffa_vm_count()  do {} while(0)
> +#endif
> +
>  static inline uint16_t ffa_get_vm_id(const struct domain *d)
>  {
>      /* +1 since 0 is reserved for the hypervisor in FF-A */
> --
> 2.47.1
>



 


Rackspace

Lists.xenproject.org is hosted with RackSpace, monitoring our
servers 24x7x365 and backed by RackSpace's Fanatical Support®.