diff --git a/util/wsialloc/wsialloc.h b/util/wsialloc/wsialloc.h index 4a15572..d07e0b9 100644 --- a/util/wsialloc/wsialloc.h +++ b/util/wsialloc/wsialloc.h @@ -48,11 +48,6 @@ extern "C" { * @note The bare minimum work is done to provide the buffer. For example, it is up to the client to initialize the data * if that is required for the desired buffer format. * - * The client may have already obtained a file descriptor (fd) for a Direct Rendering Manager (DRM) driver so it may - * perform other operations (e.g. presenting a buffer). Hence, such an fd is passed in on Allocator Construction so that - * the underlying allocator may allocate from the existing fd. However, the underlying implementation is also free to - * ignore that fd and use some other allocation mechanism, even in a production system. - * * The underlying allocator implementation is chosen at compile time. * * All API Public entry points are implemented in a way that is thread safe, and the client does not need to be @@ -61,28 +56,31 @@ extern "C" { */ /** - * @brief Struct for allocator type. + * @brief Allocator type. * - * Represents a private allocator structures. + * Represents a private allocator structure. */ -struct wsialloc_allocator; typedef struct wsialloc_allocator wsialloc_allocator; +typedef enum wsialloc_error +{ + WSIALLOC_ERROR_NONE = 0, + WSIALLOC_ERROR_INVALID = -1, + WSIALLOC_ERROR_NOT_SUPPORTED = -2, + WSIALLOC_ERROR_NO_RESOURCE = -3, +} wsialloc_error; + /** * @brief Allocate and initialize a new WSI Allocator from an existing file descriptor. * * The allocator from the function is used in subsequent calls to wsialloc_alloc() to allocate new buffers. * - * This function will be implemented as thread-safe. The implementer must ensure they use thread-safe functions, or use - * appropriate locking for non-threadsafe functions/writable global data. + * @param[out] allocator a valid allocator for use in wsialloc functions. * - * @note The underlying implementation may choose to use @p external_fd or its own platform-specific allocation method. - * - * @param external_fd file descriptor that the WSI Allocator could use for allocating new buffers - * - * @return Pointer to a wsialloc_allocator struct. A NULL return value indicates an error. + * @retval WSIALLOC_ERROR_NONE on successful allocator creation. + * @retval WSIALLOC_ERROR_FAILED on failed allocator creation. */ -wsialloc_allocator *wsialloc_new(int external_fd); +wsialloc_error wsialloc_new(wsialloc_allocator **allocator); /** * @brief Close down and free resources associated with a WSI Allocator @@ -94,74 +92,103 @@ wsialloc_allocator *wsialloc_new(int external_fd); * @note For the implementer: there is usually no need to track this explicitly, since kernel side allocators * automatically defer their closing until all their allocations are also freed. * - * This function will be implemented as thread-safe. The implementer must ensure they use thread-safe functions, or use - * appropriate locking for non-threadsafe functions/writable global data. - * * @post no more new allocations should be made on @p allocator even if previous allocations still exist. * - * @param allocator The allocator to close down and free - * @retval 0 on success - * @retval non-zero on failure with errno + * @param allocator The allocator to close down and free. */ -int wsialloc_delete(wsialloc_allocator *allocator); +void wsialloc_delete(wsialloc_allocator *allocator); + +enum wsialloc_format_flag +{ + /** The format requires a memory allocation with the same file descriptor for all planes. */ + WSIALLOC_FORMAT_NON_DISJOINT = 0x1, +}; + +enum wsialloc_allocate_flag +{ + /** Allocates the buffer in protected memory. */ + WSIALLOC_ALLOCATE_PROTECTED = 0x1, + /** Performs allocation calculations and format selection without allocating any memory. */ + WSIALLOC_ALLOCATE_NO_MEMORY = 0x2, +}; + +typedef struct wsialloc_format +{ + uint32_t fourcc; /**< The DRM_FORMAT_<...> code */ + uint64_t modifier; /**< DRM modifier applied to all planes. */ + uint64_t flags; /**< Set of @r wsialloc_format_flag format flags. */ +} wsialloc_format; + +typedef struct wsialloc_allocate_info +{ + wsialloc_format *formats; /** List of formats to select from for the allocation */ + unsigned format_count; /** Number of elements in formats array */ + uint32_t width; /** The number of pixel columns required in the buffer. */ + uint32_t height; /** The number of pixel rows required in the buffer. */ + uint64_t flags; /** Set of @r wsialloc_allocate_flag allocation flags. */ +} wsialloc_allocate_info; /** * @brief Allocate a buffer from the WSI Allocator * - * Allocate a buffer of size @p width x @p height of format @p fourcc_fmt in a way that is suitable for the underlying + * Allocate a buffer of size @p info::width x @p info::height in a way that is suitable for the underlying * window system and GPU. * - * Each plane is returned as a file descriptor. All other information returned about the buffer is also per-plane. It is - * assumed the caller already knows how many planes are implied by @p fourcc_fmt and @p modifiers. + * The allocation is made using a format from the list specified in @p info::formats . On success, @p format is set with + * the selected format that was used for the allocation. * - * Each row in the buffer may be larger than @p width to account for buffer alignment requirements in the underlying - * window system. @p strides must be examined to determine the number of bytes between subsequent rows in each of the - * buffer's planes. Only positive strides are allowed. + * Each plane is returned as a file descriptor. All other information returned about the buffer is also per-plane. It is + * assumed the caller already knows how many planes are implied by the @p format that was selected. + * + * Each row in the buffer may be larger than @p info::width to account for buffer alignment requirements in the + * underlying window system. @p strides must be examined to determine the number of bytes between subsequent rows + * in each of the buffer's planes. Only positive strides are allowed. * * The client may free the buffer's planes by invoking close() on some or all of the elements of @p buffer_fds * * The same file descriptor ('fd') may be written to different elements of @p buffer_fds more than once, for some or all * of the planes. In this case: * - @p offsets @b must be used to determine where each plane starts in the file descriptor - * - When the client frees the buffer, each unique fd in @p buffer_fds must only be closed once + * - When the client frees the buffer, each unique fd in @p buffer_fds must only be closed once. * - * Even if @p buffer_fds are all different or @p fourcc_fmt is for a single plane, then the client must inspect @p - * offsets in case it contains non-zero values. + * Even if @p buffer_fds are all different or @p format is for a single plane, then the client must inspect @p offsets + * in case it contains non-zero values. * * @note The implementation might not export the file descriptors in @p buffer_fds in such a way that allows the client * to directly map them on the CPU as writable (PROT_WRITE). * - * @p modifiers allows for a fourcc_mod_code() (as defined in drm_fourcc.h) to be passed in - * per-plane to request that allocation account for a reordering or other modification of the data in the buffer's - * planes (e.g. compression, change in number of planes, etc). + * The selected @p format modifier allows for a fourcc_mod_code() (as defined in drm_fourcc.h) to define + * a reordering or other modification of the data in the buffer's planes (e.g. compression, + * change in number of planes, etc). * - * @p strides and @p offsets may have modifier-specific meaning when @p modifiers are in use. + * @p strides is the per plane row byte stride of the @p format. For linear formats this is the number of bytes from the + * start of a row to the start of the next row. For block based formats it is the number of bytes from the start of one + * row of blocks to the start of the next. It may also have format specific meaning for formats not in those categories. * - * This function will be implemented as thread-safe. The implementer must ensure they use thread-safe functions, or use - * appropriate locking for non-threadsafe functions/writable global data. + * @p strides and @p offsets may have modifier-specific meaning when a @p format with modifier is selected. * * @pre @p strides, @p buffer_fds, @p offsets are pointers to storage large enough to hold per-plane information - * @pre @p width >=1 && @p height >= 1 + * @pre @p info::width >=1 && @p info::height >= 1 * @pre @p allocator is a currently valid WSI Allocator from wsialloc_new() * @post The allocated buffer will be zeroed. * - * @param allocator The WSI Allocator to allocate from - * @param fourcc_fmt The DRM_FORMAT_<...> code - * @param width The number of pixel columns required in the buffer - * @param height The number of pixel rows required in the buffer - * @param[out] strides Per-plane number of bytes between successive rows in the buffer. - * @param[out] buffer_fds Per-plane file descriptors for the buffer - * @param[out] offsets Per-plane offset into the file descriptor for the start of the plane - * @param[in] modifiers Per-plane modifiers or NULL if no modifiers required - * @retval 0 on successful buffer allocation - * @retval Non-zero indicates some error listed below and/or a window-system/allocator specific (usually negative) - * error - * @retval -EINVAL is also returned for invalid parameters - * @retval -ENOTSUP is also returned for unsupported parameters, such as a modifier or recognized format - * not supported by the underlying window-system/allocator + * @param allocator The WSI Allocator to allocate from. + * @param[in] info The requested allocation information. + * @param[out] format The selected format for allocation. + * @param[out] strides Per-plane row byte stride of the buffer. + * @param[out] buffer_fds Per-plane file descriptors for the buffer. + * @param[out] offsets Per-plane offset into the file descriptor for the start of the plane. + + * @retval WSIALLOC_ERROR_NONE on successful buffer allocation. + * @retval WSIALLOC_ERROR_INVALID is returned for invalid parameters. + * @retval WSIALLOC_ERROR_NOT_SUPPORTED is returned for unsupported parameters, such as a modifier or recognized + * format not supported by the underlying window-system/allocator. + * @retval WSIALLOC_ERROR_NO_RESOURCE is returned on failed buffer allocation due to lack of available memory or other + * system resources. */ -int wsialloc_alloc(wsialloc_allocator *allocator, uint32_t fourcc_fmt, uint32_t width, uint32_t height, int *strides, int *buffer_fds, - uint32_t *offsets, const uint64_t *modifiers); + +wsialloc_error wsialloc_alloc(wsialloc_allocator *allocator, const wsialloc_allocate_info *info, + wsialloc_format *format, int *strides, int *buffer_fds, uint32_t *offsets); #ifdef __cplusplus } diff --git a/util/wsialloc/wsialloc_ion.c b/util/wsialloc/wsialloc_ion.c index 0a31f4d..badfab4 100644 --- a/util/wsialloc/wsialloc_ion.c +++ b/util/wsialloc/wsialloc_ion.c @@ -34,13 +34,15 @@ #include #include #include +#include #include #include - -#define UNUSED(x) ((void)(x)) +#include /** Default alignment */ #define WSIALLOCP_MIN_ALIGN_SZ (64u) +/** Maximum image size allowed for each dimension */ +#define MAX_IMAGE_SIZE 128000 struct wsialloc_allocator { @@ -48,16 +50,24 @@ struct wsialloc_allocator int fd; /* Allocator heap id. */ uint32_t alloc_heap_id; + /* Protected allocator heap id */ + uint32_t protected_alloc_heap_id; + bool protected_heap_exists; }; +typedef struct wsialloc_format_descriptor +{ + wsialloc_format format; + fmt_spec format_spec; +} wsialloc_format_descriptor; + static int find_alloc_heap_id(int fd) { assert(fd != -1); struct ion_heap_data heaps[ION_NUM_HEAP_IDS]; struct ion_heap_query query = { - .cnt = ION_NUM_HEAP_IDS, - .heaps = (uint64_t)(uintptr_t)heaps, + .cnt = ION_NUM_HEAP_IDS, .heaps = (uint64_t)(uintptr_t)heaps, }; int ret = ioctl(fd, ION_IOC_HEAP_QUERY, &query); @@ -85,9 +95,7 @@ static int allocate(int fd, uint64_t size, uint32_t heap_id) assert(fd != -1); struct ion_allocation_data alloc = { - .len = size, - .heap_id_mask = 1u << heap_id, - .flags = 0, + .len = size, .heap_id_mask = 1u << heap_id, .flags = 0, }; int ret = ioctl(fd, ION_IOC_ALLOC, &alloc); if (ret < 0) @@ -103,150 +111,238 @@ static uint64_t round_size_up_to_align(uint64_t size) return (size + WSIALLOCP_MIN_ALIGN_SZ - 1) & ~(WSIALLOCP_MIN_ALIGN_SZ - 1); } -wsialloc_allocator *wsialloc_new(int external_fd) +wsialloc_error wsialloc_new(wsialloc_allocator **allocator) { - UNUSED(external_fd); + assert(allocator != NULL); + wsialloc_error ret = WSIALLOC_ERROR_NONE; - wsialloc_allocator *ion = NULL; - ion = malloc(sizeof(*ion)); + wsialloc_allocator *ion = malloc(sizeof(wsialloc_allocator)); if (NULL == ion) { + ret = WSIALLOC_ERROR_NO_RESOURCE; goto fail; } ion->fd = open("/dev/ion", O_RDONLY); if (ion->fd < 0) { + ret = WSIALLOC_ERROR_NO_RESOURCE; goto fail; } ion->alloc_heap_id = find_alloc_heap_id(ion->fd); if (ion->alloc_heap_id < 0) { + ret = WSIALLOC_ERROR_NO_RESOURCE; goto fail; } - return ion; + ion->protected_heap_exists = false; + *allocator = ion; + return ret; fail: wsialloc_delete(ion); - return NULL; -} - -int wsialloc_delete(wsialloc_allocator *ion) -{ - int ret = 0; - - if (NULL == ion) - { - return 0; - } - - if (ion->fd != -1) - { - if (close(ion->fd) != 0) - { - ret = -errno; - } - } - - free(ion); return ret; } -static int wsiallocp_get_fmt_info(uint32_t fourcc_fmt, uint32_t *nr_planes, uint32_t *plane_bpp) +void wsialloc_delete(wsialloc_allocator *allocator) { - unsigned int fmt_idx; - const fmt_spec *found_fmt; - unsigned int plane_idx; - - assert(nr_planes != NULL && plane_bpp != NULL); - - /* Mask off any bits not necessary for allocation size */ - fourcc_fmt = fourcc_fmt & (~(uint32_t)DRM_FORMAT_BIG_ENDIAN); - - /* Search table for the format*/ - for (fmt_idx = 0; fmt_idx < fourcc_format_table_len; ++fmt_idx) + if (NULL == allocator) { - if (fourcc_fmt == fourcc_format_table[fmt_idx].drm_format) - { - break; - } + return; } - if (fmt_idx >= fourcc_format_table_len) + if (allocator->fd >= 0) { - return -ENOTSUP; + close(allocator->fd); } - /* fmt_idx is now a correct index into the table */ - found_fmt = &fourcc_format_table[fmt_idx]; - - assert(found_fmt->nr_planes <= WSIALLOCP_MAX_PLANES); - *nr_planes = found_fmt->nr_planes; - - /* Only write out as many bpp as there are planes */ - for (plane_idx = 0; plane_idx < found_fmt->nr_planes; ++plane_idx) - { - plane_bpp[plane_idx] = (uint32_t)found_fmt->bpp[plane_idx]; - } - - return 0; + free(allocator); } -int wsialloc_alloc( - wsialloc_allocator *ion, - uint32_t fourcc, - uint32_t width, - uint32_t height, - int *stride, - int *new_fd, - uint32_t *offset, - const uint64_t *modifier) +static wsialloc_error calculate_format_properties(const wsialloc_format_descriptor *descriptor, + const wsialloc_allocate_info *info, int *strides, uint32_t *offsets) { - assert(ion != NULL); - assert(fourcc != 0); - assert(width > 0); - assert(height > 0); - assert(stride != NULL); - assert(new_fd != NULL); - assert(offset != NULL); + assert(descriptor != NULL); + assert(info != NULL); + assert(strides != NULL); + assert(offsets != NULL); - if (modifier != NULL && *modifier != 0) + const uint8_t *bits_per_pixel = descriptor->format_spec.bpp; + const uint64_t flags = descriptor->format.flags; + const uint64_t modifier = descriptor->format.modifier; + const uint32_t num_planes = descriptor->format_spec.nr_planes; + + /* We currently don't support any kind of custom modifiers */ + if (modifier != DRM_FORMAT_MOD_LINEAR) { - return -ENOTSUP; + return WSIALLOC_ERROR_NOT_SUPPORTED; } - - /* Validate format and determine per-plane bits per pixel. */ - uint32_t nr_planes, bits_per_pixel[WSIALLOCP_MAX_PLANES]; - int ret = wsiallocp_get_fmt_info(fourcc, &nr_planes, bits_per_pixel); - if (ret != 0) + /* No multi-plane format support */ + if (num_planes > 1) { - return ret; + return WSIALLOC_ERROR_NOT_SUPPORTED; } size_t size = 0; - for (uint32_t plane = 0; plane < nr_planes; plane++) + for (size_t plane = 0; plane < num_planes; plane++) { - offset[plane] = size; - /* Assumes multiple of 8--rework otherwise. */ const uint32_t plane_bytes_per_pixel = bits_per_pixel[plane] / 8; assert(plane_bytes_per_pixel * 8 == bits_per_pixel[plane]); - stride[plane] = round_size_up_to_align(width * plane_bytes_per_pixel); - size += stride[plane] * height; + /* With large enough width, this can overflow as strides are signed. In practice, this shouldn't happen */ + strides[plane] = round_size_up_to_align(info->width * plane_bytes_per_pixel); + + offsets[plane] = size; + + size += strides[plane] * info->height; } - new_fd[0] = allocate(ion->fd, size, ion->alloc_heap_id); - if (new_fd[0] < 0) - { - return -errno; - } - - for (uint32_t plane = 1; plane < nr_planes; plane++) - { - new_fd[plane] = new_fd[0]; - } - - return 0; + return WSIALLOC_ERROR_NONE; } + +static wsialloc_error allocate_format(const wsialloc_allocator *allocator, const wsialloc_format_descriptor *descriptor, + const wsialloc_allocate_info *info, const int *strides, const uint32_t *offsets, + int *buffer_fds) +{ + assert(allocator != NULL); + assert(descriptor != NULL); + assert(info != NULL); + assert(offsets != NULL); + assert(strides != NULL); + assert(buffer_fds != NULL); + + const uint64_t flags = descriptor->format.flags; + const uint32_t num_planes = descriptor->format_spec.nr_planes; + + /* The only error that can be encountered on allocations is lack of resources. Other parameter validation and + * support checks are done on format selection. */ + assert(num_planes == 1); + uint32_t alloc_heap_id = allocator->alloc_heap_id; + if (info->flags & WSIALLOC_ALLOCATE_PROTECTED) + { + /* Exit if we don't support allocating protected memory */ + if (!allocator->protected_heap_exists) + { + return WSIALLOC_ERROR_NO_RESOURCE; + } + alloc_heap_id = allocator->protected_alloc_heap_id; + } + + size_t total_size = offsets[0] + (strides[0] * info->height); + + buffer_fds[0] = allocate(allocator->fd, total_size, alloc_heap_id); + if (buffer_fds[0] < 0) + { + return WSIALLOC_ERROR_NO_RESOURCE; + } + + return WSIALLOC_ERROR_NONE; +} + +static const fmt_spec *find_format(uint32_t fourcc) +{ + /* Mask off any bits not necessary for allocation size */ + fourcc = fourcc & (~(uint32_t)DRM_FORMAT_BIG_ENDIAN); + + /* Search table for the format*/ + for (size_t i = 0; i < fourcc_format_table_len; i++) + { + if (fourcc == fourcc_format_table[i].drm_format) + { + const fmt_spec *found_fmt = &fourcc_format_table[i]; + assert(found_fmt->nr_planes <= WSIALLOCP_MAX_PLANES); + + return found_fmt; + } + } + + return NULL; +} + +static bool validate_parameters(const wsialloc_allocator *allocator, const wsialloc_allocate_info *info, + const wsialloc_format *format, const int *strides, const uint32_t *offsets) +{ + if (allocator == NULL) + { + return false; + } + else if (!strides || !offsets) + { + return false; + } + else if (info->format_count == 0 || info->formats == NULL) + { + return false; + } + else if (info->width < 1 || info->height < 1 || info->width > MAX_IMAGE_SIZE || info->height > MAX_IMAGE_SIZE) + { + return false; + } + + return true; +} + +wsialloc_error wsialloc_alloc(wsialloc_allocator *allocator, const wsialloc_allocate_info *info, + wsialloc_format *format, int *strides, int *buffer_fds, uint32_t *offsets) +{ + assert(allocator != NULL); + assert(info != NULL); + assert(format != NULL); + assert(strides != NULL); + assert(offsets != NULL); + + if (!validate_parameters(allocator, info, format, strides, offsets)) + { + return WSIALLOC_ERROR_INVALID; + } + + int local_strides[WSIALLOCP_MAX_PLANES]; + int local_fds[WSIALLOCP_MAX_PLANES] = { -1 }; + int local_offsets[WSIALLOCP_MAX_PLANES]; + wsialloc_error err = WSIALLOC_ERROR_NONE; + wsialloc_format_descriptor selected_format_desc = {}; + + for (size_t i = 0; i < info->format_count; i++) + { + const wsialloc_format *current_format = &info->formats[i]; + const fmt_spec *format_spec = find_format(current_format->fourcc); + if (!format_spec) + { + err = WSIALLOC_ERROR_NOT_SUPPORTED; + continue; + } + + wsialloc_format_descriptor current_format_desc = { *current_format, *format_spec }; + err = calculate_format_properties(¤t_format_desc, info, local_strides, local_offsets); + if (err != WSIALLOC_ERROR_NONE) + { + continue; + } + + /* A compatible format was found */ + selected_format_desc = current_format_desc; + break; + } + + if (err == WSIALLOC_ERROR_NONE) + { + if (!(info->flags & WSIALLOC_ALLOCATE_NO_MEMORY)) + { + err = allocate_format(allocator, &selected_format_desc, info, local_strides, local_offsets, local_fds); + } + } + + if (err == WSIALLOC_ERROR_NONE) + { + *format = selected_format_desc.format; + *strides = local_strides[0]; + *offsets = local_offsets[0]; + if (!(info->flags & WSIALLOC_ALLOCATE_NO_MEMORY)) + { + *buffer_fds = local_fds[0]; + } + } + return err; +} \ No newline at end of file diff --git a/wsi/wayland/swapchain.cpp b/wsi/wayland/swapchain.cpp index d058ab7..3584626 100644 --- a/wsi/wayland/swapchain.cpp +++ b/wsi/wayland/swapchain.cpp @@ -98,14 +98,9 @@ swapchain::swapchain(layer::device_private_data &dev_data, const VkAllocationCal swapchain::~swapchain() { - int res; teardown(); - res = wsialloc_delete(m_wsi_allocator); - if (res != 0) - { - WSI_LOG_ERROR("error deleting the allocator: %d", res); - } + wsialloc_delete(m_wsi_allocator); m_wsi_allocator = nullptr; if (m_surface_queue != nullptr) { @@ -179,8 +174,7 @@ VkResult swapchain::init_platform(VkDevice device, const VkSwapchainCreateInfoKH return VK_ERROR_INITIALIZATION_FAILED; } - m_wsi_allocator = wsialloc_new(-1); - if (nullptr == m_wsi_allocator) + if (wsialloc_new(&m_wsi_allocator) != WSIALLOC_ERROR_NONE) { WSI_LOG_ERROR("Failed to create wsi allocator."); return VK_ERROR_INITIALIZATION_FAILED; @@ -432,13 +426,30 @@ VkResult swapchain::allocate_image(VkImageCreateInfo &image_create_info, wayland { /* TODO: Handle Dedicated allocation bit. */ const auto fourcc = util::drm::vk_to_drm_format(image_create_info.format); + const auto is_protected_memory = (image_create_info.flags & VK_IMAGE_CREATE_PROTECTED_BIT) != 0; - const auto res = - wsialloc_alloc(m_wsi_allocator, fourcc, image_create_info.extent.width, image_create_info.extent.height, - image_data->stride, image_data->buffer_fd, image_data->offset, nullptr); - if (res != 0) + const uint64_t format_flags = is_disjoint ? 0 : WSIALLOC_FORMAT_NON_DISJOINT; + wsialloc_format format = {fourcc, modifier, format_flags}; + + const uint64_t allocation_flags = is_protected_memory ? WSIALLOC_ALLOCATE_PROTECTED : 0; + wsialloc_allocate_info alloc_info = { + &format, + 1, + image_create_info.extent.width, + image_create_info.extent.height, + allocation_flags + }; + + wsialloc_format allocated_format = {0}; + const auto res = wsialloc_alloc(m_wsi_allocator, &alloc_info, &allocated_format, image_data->stride, + image_data->buffer_fd, image_data->offset); + if (res != WSIALLOC_ERROR_NONE) { - WSI_LOG_ERROR("Failed allocation of DMA Buffer."); + WSI_LOG_ERROR("Failed allocation of DMA Buffer. WSI error: %d", static_cast(res)); + if(res == WSIALLOC_ERROR_NOT_SUPPORTED) + { + return VK_ERROR_FORMAT_NOT_SUPPORTED; + } return VK_ERROR_OUT_OF_HOST_MEMORY; }