/* SPDX-License-Identifier: GPL-2.0-only */ /* * Copyright (c) 2022-2024 Qualcomm Innovation Center, Inc. All rights reserved. */ #ifndef _LINUX_GUNYAH_H #define _LINUX_GUNYAH_H #include #include #include #include #include #include #include #include struct gunyah_vm; int __must_check gunyah_vm_get(struct gunyah_vm *ghvm); void gunyah_vm_put(struct gunyah_vm *ghvm); struct gunyah_vm_function_instance; /** * struct gunyah_vm_function - Represents a function type * @type: value from &enum gunyah_fn_type * @name: friendly name for debug purposes * @mod: owner of the function type * @bind: Called when a new function of this type has been allocated. * @unbind: Called when the function instance is being destroyed. * @compare: Compare function instance @f's argument to the provided arg. * Return true if they are equivalent. Used on GUNYAH_VM_REMOVE_FUNCTION. */ struct gunyah_vm_function { u32 type; const char *name; struct module *mod; long (*bind)(struct gunyah_vm_function_instance *f); void (*unbind)(struct gunyah_vm_function_instance *f); bool (*compare)(const struct gunyah_vm_function_instance *f, const void *arg, size_t size); }; /** * struct gunyah_vm_function_instance - Represents one function instance * @arg_size: size of user argument * @argp: pointer to user argument * @ghvm: Pointer to VM instance * @rm: Pointer to resource manager for the VM instance * @fn: The ops for the function * @data: Private data for function * @vm_list: for gunyah_vm's functions list */ struct gunyah_vm_function_instance { size_t arg_size; void *argp; struct gunyah_vm *ghvm; struct gunyah_rm *rm; struct gunyah_vm_function *fn; void *data; struct list_head vm_list; }; int gunyah_vm_function_register(struct gunyah_vm_function *f); void gunyah_vm_function_unregister(struct gunyah_vm_function *f); /* Since the function identifiers were setup in a uapi header as an * enum and we do no want to change that, the user must supply the expanded * constant as well and the compiler checks they are the same. * See also MODULE_ALIAS_RDMA_NETLINK. */ #define MODULE_ALIAS_GUNYAH_VM_FUNCTION(_type, _idx) \ static inline void __maybe_unused __chk##_idx(void) \ { \ BUILD_BUG_ON(_type != _idx); \ } \ MODULE_ALIAS("ghfunc:" __stringify(_idx)) #define DECLARE_GUNYAH_VM_FUNCTION(_name, _type, _bind, _unbind, _compare) \ static struct gunyah_vm_function _name = { \ .type = _type, \ .name = __stringify(_name), \ .mod = THIS_MODULE, \ .bind = _bind, \ .unbind = _unbind, \ .compare = _compare, \ } #define module_gunyah_vm_function(__gf) \ module_driver(__gf, gunyah_vm_function_register, \ gunyah_vm_function_unregister) #define DECLARE_GUNYAH_VM_FUNCTION_INIT(_name, _type, _idx, _bind, _unbind, \ _compare) \ DECLARE_GUNYAH_VM_FUNCTION(_name, _type, _bind, _unbind, _compare); \ module_gunyah_vm_function(_name); \ MODULE_ALIAS_GUNYAH_VM_FUNCTION(_type, _idx) /* Matches resource manager's resource types for VM_GET_HYP_RESOURCES RPC */ enum gunyah_resource_type { /* clang-format off */ GUNYAH_RESOURCE_TYPE_BELL_TX = 0, GUNYAH_RESOURCE_TYPE_BELL_RX = 1, GUNYAH_RESOURCE_TYPE_MSGQ_TX = 2, GUNYAH_RESOURCE_TYPE_MSGQ_RX = 3, GUNYAH_RESOURCE_TYPE_VCPU = 4, GUNYAH_RESOURCE_TYPE_MEM_EXTENT = 9, GUNYAH_RESOURCE_TYPE_ADDR_SPACE = 10, /* clang-format on */ }; struct gunyah_resource { enum gunyah_resource_type type; u64 capid; unsigned int irq; struct list_head list; u32 rm_label; }; /** * struct gunyah_vm_resource_ticket - Represents a ticket to reserve access to VM resource(s) * @vm_list: for @gunyah_vm->resource_tickets * @resources: List of resource(s) associated with this ticket * (members are from @gunyah_resource->list) * @resource_type: Type of resource this ticket reserves * @label: Label of the resource from resource manager this ticket reserves. * @owner: owner of the ticket * @populate: callback provided by the ticket owner and called when a resource is found that * matches @resource_type and @label. Note that this callback could be called * multiple times if userspace created mutliple resources with the same type/label. * This callback may also have significant delay after gunyah_vm_add_resource_ticket() * since gunyah_vm_add_resource_ticket() could be called before the VM starts. * @unpopulate: callback provided by the ticket owner and called when the ticket owner should no * longer use the resource provided in the argument. When unpopulate() returns, * the ticket owner should not be able to use the resource any more as the resource * might being freed. */ struct gunyah_vm_resource_ticket { struct list_head vm_list; struct list_head resources; enum gunyah_resource_type resource_type; u32 label; struct module *owner; bool (*populate)(struct gunyah_vm_resource_ticket *ticket, struct gunyah_resource *ghrsc); void (*unpopulate)(struct gunyah_vm_resource_ticket *ticket, struct gunyah_resource *ghrsc); }; int gunyah_vm_add_resource_ticket(struct gunyah_vm *ghvm, struct gunyah_vm_resource_ticket *ticket); void gunyah_vm_remove_resource_ticket(struct gunyah_vm *ghvm, struct gunyah_vm_resource_ticket *ticket); /* * gunyah_vm_io_handler contains the info about an io device and its associated * addr and the ops associated with the io device. */ struct gunyah_vm_io_handler { struct rb_node node; u64 addr; bool datamatch; u8 len; u64 data; struct gunyah_vm_io_handler_ops *ops; }; /* * gunyah_vm_io_handler_ops contains function pointers associated with an iodevice. */ struct gunyah_vm_io_handler_ops { int (*read)(struct gunyah_vm_io_handler *io_dev, u64 addr, u32 len, u64 data); int (*write)(struct gunyah_vm_io_handler *io_dev, u64 addr, u32 len, u64 data); }; int gunyah_vm_add_io_handler(struct gunyah_vm *ghvm, struct gunyah_vm_io_handler *io_dev); void gunyah_vm_remove_io_handler(struct gunyah_vm *ghvm, struct gunyah_vm_io_handler *io_dev); #define GUNYAH_RM_ACL_X BIT(0) #define GUNYAH_RM_ACL_W BIT(1) #define GUNYAH_RM_ACL_R BIT(2) struct gunyah_rm_mem_acl_entry { __le16 vmid; u8 perms; u8 reserved; } __packed; struct gunyah_rm_mem_entry { __le64 phys_addr; __le64 size; } __packed; enum gunyah_rm_mem_type { GUNYAH_RM_MEM_TYPE_NORMAL = 0, GUNYAH_RM_MEM_TYPE_IO = 1, }; /* * struct gunyah_rm_mem_parcel - Info about memory to be lent/shared/donated/reclaimed * @mem_type: The type of memory: normal (DDR) or IO * @label: An client-specified identifier which can be used by the other VMs to identify the purpose * of the memory parcel. * @n_acl_entries: Count of the number of entries in the @acl_entries array. * @acl_entries: An array of access control entries. Each entry specifies a VM and what access * is allowed for the memory parcel. * @n_mem_entries: Count of the number of entries in the @mem_entries array. * @mem_entries: An array of regions to be associated with the memory parcel. Addresses should be * (intermediate) physical addresses from Linux's perspective. * @mem_handle: On success, filled with memory handle that RM allocates for this memory parcel */ struct gunyah_rm_mem_parcel { enum gunyah_rm_mem_type mem_type; u32 label; size_t n_acl_entries; struct gunyah_rm_mem_acl_entry *acl_entries; size_t n_mem_entries; struct gunyah_rm_mem_entry *mem_entries; u32 mem_handle; }; enum gunyah_pagetable_access { /* clang-format off */ GUNYAH_PAGETABLE_ACCESS_NONE = 0, GUNYAH_PAGETABLE_ACCESS_X = 1, GUNYAH_PAGETABLE_ACCESS_W = 2, GUNYAH_PAGETABLE_ACCESS_R = 4, GUNYAH_PAGETABLE_ACCESS_RX = 5, GUNYAH_PAGETABLE_ACCESS_RW = 6, GUNYAH_PAGETABLE_ACCESS_RWX = 7, /* clang-format on */ }; struct gunyah_rm_platform_ops { int (*pre_mem_share)(struct gunyah_rm *rm, struct gunyah_rm_mem_parcel *mem_parcel); int (*post_mem_reclaim)(struct gunyah_rm *rm, struct gunyah_rm_mem_parcel *mem_parcel); int (*pre_demand_page)(struct gunyah_rm *rm, u16 vmid, enum gunyah_pagetable_access access, struct folio *folio); int (*release_demand_page)(struct gunyah_rm *rm, u16 vmid, enum gunyah_pagetable_access access, struct folio *folio); }; #if IS_ENABLED(CONFIG_GUNYAH_PLATFORM_HOOKS) int gunyah_rm_register_platform_ops( const struct gunyah_rm_platform_ops *platform_ops); void gunyah_rm_unregister_platform_ops( const struct gunyah_rm_platform_ops *platform_ops); int devm_gunyah_rm_register_platform_ops( struct device *dev, const struct gunyah_rm_platform_ops *ops); #else static inline int gunyah_rm_register_platform_ops( const struct gunyah_rm_platform_ops *platform_ops) { return 0; } static inline void gunyah_rm_unregister_platform_ops( const struct gunyah_rm_platform_ops *platform_ops) { } static inline int devm_gunyah_rm_register_platform_ops(struct device *dev, const struct gunyah_rm_platform_ops *ops) { return 0; } #endif /******************************************************************************/ /* Common arch-independent definitions for Gunyah hypercalls */ #define GUNYAH_CAPID_INVAL U64_MAX #define GUNYAH_VMID_ROOT_VM 0xff enum gunyah_error { /* clang-format off */ GUNYAH_ERROR_OK = 0, GUNYAH_ERROR_UNIMPLEMENTED = -1, GUNYAH_ERROR_RETRY = -2, GUNYAH_ERROR_ARG_INVAL = 1, GUNYAH_ERROR_ARG_SIZE = 2, GUNYAH_ERROR_ARG_ALIGN = 3, GUNYAH_ERROR_NOMEM = 10, GUNYAH_ERROR_ADDR_OVFL = 20, GUNYAH_ERROR_ADDR_UNFL = 21, GUNYAH_ERROR_ADDR_INVAL = 22, GUNYAH_ERROR_DENIED = 30, GUNYAH_ERROR_BUSY = 31, GUNYAH_ERROR_IDLE = 32, GUNYAH_ERROR_IRQ_BOUND = 40, GUNYAH_ERROR_IRQ_UNBOUND = 41, GUNYAH_ERROR_CSPACE_CAP_NULL = 50, GUNYAH_ERROR_CSPACE_CAP_REVOKED = 51, GUNYAH_ERROR_CSPACE_WRONG_OBJ_TYPE = 52, GUNYAH_ERROR_CSPACE_INSUF_RIGHTS = 53, GUNYAH_ERROR_CSPACE_FULL = 54, GUNYAH_ERROR_MSGQUEUE_EMPTY = 60, GUNYAH_ERROR_MSGQUEUE_FULL = 61, /* clang-format on */ }; /** * gunyah_error_remap() - Remap Gunyah hypervisor errors into a Linux error code * @gunyah_error: Gunyah hypercall return value */ static inline int gunyah_error_remap(enum gunyah_error gunyah_error) { switch (gunyah_error) { case GUNYAH_ERROR_OK: return 0; case GUNYAH_ERROR_NOMEM: return -ENOMEM; case GUNYAH_ERROR_DENIED: case GUNYAH_ERROR_CSPACE_CAP_NULL: case GUNYAH_ERROR_CSPACE_CAP_REVOKED: case GUNYAH_ERROR_CSPACE_WRONG_OBJ_TYPE: case GUNYAH_ERROR_CSPACE_INSUF_RIGHTS: return -EACCES; case GUNYAH_ERROR_CSPACE_FULL: case GUNYAH_ERROR_BUSY: case GUNYAH_ERROR_IDLE: return -EBUSY; case GUNYAH_ERROR_IRQ_BOUND: case GUNYAH_ERROR_IRQ_UNBOUND: case GUNYAH_ERROR_MSGQUEUE_FULL: case GUNYAH_ERROR_MSGQUEUE_EMPTY: return -EIO; case GUNYAH_ERROR_UNIMPLEMENTED: return -EOPNOTSUPP; case GUNYAH_ERROR_RETRY: return -EAGAIN; default: return -EINVAL; } } enum gunyah_api_feature { /* clang-format off */ GUNYAH_FEATURE_DOORBELL = 1, GUNYAH_FEATURE_MSGQUEUE = 2, GUNYAH_FEATURE_VCPU = 5, GUNYAH_FEATURE_MEMEXTENT = 6, /* clang-format on */ }; bool arch_is_gunyah_guest(void); #define GUNYAH_API_V1 1 /* Other bits reserved for future use and will be zero */ /* clang-format off */ #define GUNYAH_API_INFO_API_VERSION_MASK GENMASK_ULL(13, 0) #define GUNYAH_API_INFO_BIG_ENDIAN BIT_ULL(14) #define GUNYAH_API_INFO_IS_64BIT BIT_ULL(15) #define GUNYAH_API_INFO_VARIANT_MASK GENMASK_ULL(63, 56) /* clang-format on */ struct gunyah_hypercall_hyp_identify_resp { u64 api_info; u64 flags[3]; }; static inline u16 gunyah_api_version(const struct gunyah_hypercall_hyp_identify_resp *gunyah_api) { return FIELD_GET(GUNYAH_API_INFO_API_VERSION_MASK, gunyah_api->api_info); } void gunyah_hypercall_hyp_identify( struct gunyah_hypercall_hyp_identify_resp *hyp_identity); enum gunyah_error gunyah_hypercall_bell_send(u64 capid, u64 new_flags, u64 *old_flags); enum gunyah_error gunyah_hypercall_bell_set_mask(u64 capid, u64 enable_mask, u64 ack_mask); /* Immediately raise RX vIRQ on receiver VM */ #define GUNYAH_HYPERCALL_MSGQ_TX_FLAGS_PUSH BIT(0) enum gunyah_error gunyah_hypercall_msgq_send(u64 capid, size_t size, void *buff, u64 tx_flags, bool *ready); enum gunyah_error gunyah_hypercall_msgq_recv(u64 capid, void *buff, size_t size, size_t *recv_size, bool *ready); #define GUNYAH_ADDRSPACE_SELF_CAP 0 /* clang-format off */ #define GUNYAH_MEMEXTENT_MAPPING_USER_ACCESS GENMASK_ULL(2, 0) #define GUNYAH_MEMEXTENT_MAPPING_KERNEL_ACCESS GENMASK_ULL(6, 4) #define GUNYAH_MEMEXTENT_MAPPING_TYPE GENMASK_ULL(23, 16) /* clang-format on */ enum gunyah_memextent_donate_type { /* clang-format off */ GUNYAH_MEMEXTENT_DONATE_TO_CHILD = 0, GUNYAH_MEMEXTENT_DONATE_TO_PARENT = 1, GUNYAH_MEMEXTENT_DONATE_TO_SIBLING = 2, GUNYAH_MEMEXTENT_DONATE_TO_PROTECTED = 3, GUNYAH_MEMEXTENT_DONATE_FROM_PROTECTED = 4, /* clang-format on */ }; enum gunyah_addrspace_map_flag_bits { /* clang-format off */ GUNYAH_ADDRSPACE_MAP_FLAG_PARTIAL = 0, GUNYAH_ADDRSPACE_MAP_FLAG_PRIVATE = 1, GUNYAH_ADDRSPACE_MAP_FLAG_VMMIO = 2, GUNYAH_ADDRSPACE_MAP_FLAG_NOSYNC = 31, /* clang-format on */ }; enum gunyah_error gunyah_hypercall_addrspace_map(u64 capid, u64 extent_capid, u64 vbase, u32 extent_attrs, u32 flags, u64 offset, u64 size); enum gunyah_error gunyah_hypercall_addrspace_unmap(u64 capid, u64 extent_capid, u64 vbase, u32 flags, u64 offset, u64 size); /* clang-format off */ #define GUNYAH_MEMEXTENT_OPTION_TYPE_MASK GENMASK_ULL(7, 0) #define GUNYAH_MEMEXTENT_OPTION_NOSYNC BIT(31) /* clang-format on */ enum gunyah_error gunyah_hypercall_memextent_donate(u32 options, u64 from_capid, u64 to_capid, u64 offset, u64 size); struct gunyah_hypercall_vcpu_run_resp { union { enum { /* clang-format off */ /* VCPU is ready to run */ GUNYAH_VCPU_STATE_READY = 0, /* VCPU is sleeping until an interrupt arrives */ GUNYAH_VCPU_STATE_EXPECTS_WAKEUP = 1, /* VCPU is powered off */ GUNYAH_VCPU_STATE_POWERED_OFF = 2, /* VCPU is blocked in EL2 for unspecified reason */ GUNYAH_VCPU_STATE_BLOCKED = 3, /* VCPU has returned for MMIO READ */ GUNYAH_VCPU_ADDRSPACE_VMMIO_READ = 4, /* VCPU has returned for MMIO WRITE */ GUNYAH_VCPU_ADDRSPACE_VMMIO_WRITE = 5, /* VCPU blocked on fault where we can demand page */ GUNYAH_VCPU_ADDRSPACE_PAGE_FAULT = 7, /* clang-format on */ } state; u64 sized_state; }; u64 state_data[3]; }; enum { GUNYAH_ADDRSPACE_VMMIO_ACTION_EMULATE = 0, GUNYAH_ADDRSPACE_VMMIO_ACTION_RETRY = 1, GUNYAH_ADDRSPACE_VMMIO_ACTION_FAULT = 2, }; enum gunyah_error gunyah_hypercall_vcpu_run(u64 capid, unsigned long *resume_data, struct gunyah_hypercall_vcpu_run_resp *resp); #endif