/* * Google LWIS DMA Buffer Utilities * * Copyright (c) 2018 Google, LLC * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #define pr_fmt(fmt) KBUILD_MODNAME "-buffer: " fmt #include #include #include #include "lwis_buffer.h" #include "lwis_device.h" #include "lwis_device_slc.h" #include "lwis_platform_dma.h" static struct lwis_buffer_enrollment_list *enrollment_list_find(struct lwis_client *client, dma_addr_t dma_vaddr) { struct lwis_buffer_enrollment_list *list; hash_for_each_possible (client->enrolled_buffers, list, node, dma_vaddr) { if (list->vaddr == dma_vaddr) { return list; } } return NULL; } static struct lwis_buffer_enrollment_list *enrollment_list_create(struct lwis_client *client, dma_addr_t dma_vaddr) { struct lwis_buffer_enrollment_list *enrollment_list = kmalloc(sizeof(struct lwis_buffer_enrollment_list), GFP_KERNEL); if (!enrollment_list) { dev_err(client->lwis_dev->dev, "Cannot allocate new entrollment list\n"); return NULL; } enrollment_list->vaddr = dma_vaddr; INIT_LIST_HEAD(&enrollment_list->list); hash_add(client->enrolled_buffers, &enrollment_list->node, dma_vaddr); return enrollment_list; } static struct lwis_buffer_enrollment_list * enrollment_list_find_or_create(struct lwis_client *client, dma_addr_t dma_vaddr) { struct lwis_buffer_enrollment_list *list = enrollment_list_find(client, dma_vaddr); return (list == NULL) ? enrollment_list_create(client, dma_vaddr) : list; } static void dump_total_enrolled_buffer_size(struct lwis_device *lwis_dev) { struct lwis_client *client; unsigned long flags; int i; struct lwis_buffer_enrollment_list *enrollment_list; struct lwis_enrolled_buffer *buffer; size_t total_enrolled_size = 0; int num_enrolled_buffers = 0; spin_lock_irqsave(&lwis_dev->lock, flags); list_for_each_entry (client, &lwis_dev->clients, node) { if (hash_empty(client->enrolled_buffers)) { continue; } hash_for_each (client->enrolled_buffers, i, enrollment_list, node) { buffer = list_first_entry(&enrollment_list->list, struct lwis_enrolled_buffer, list_node); total_enrolled_size += buffer->dma_buf->size; num_enrolled_buffers++; } } spin_unlock_irqrestore(&lwis_dev->lock, flags); if (total_enrolled_size > 0) { pr_info("%-16s: %16d %16lu kB\n", lwis_dev->name, num_enrolled_buffers, total_enrolled_size / 1024); } } int lwis_buffer_alloc(struct lwis_client *lwis_client, struct lwis_alloc_buffer_info *alloc_info, struct lwis_allocated_buffer *buffer) { struct dma_buf *dma_buf; int ret = 0; if (!lwis_client) { pr_err("Alloc: LWIS client is NULL\n"); return -ENODEV; } if (!alloc_info || !buffer) { pr_err("Alloc: alloc_info and/or buffer is NULL\n"); return -EINVAL; } if (alloc_info->flags & LWIS_DMA_SYSTEM_CACHE_RESERVATION) { if (lwis_client->lwis_dev->type == DEVICE_TYPE_SLC) { ret = lwis_slc_buffer_alloc(lwis_client->lwis_dev, alloc_info); if (ret) { return ret; } dma_buf = NULL; } else { pr_err("Can't allocate system cache buffer on non-slc device\n"); return -EINVAL; } } else { alloc_info->size = PAGE_ALIGN(alloc_info->size); dma_buf = lwis_platform_dma_buffer_alloc(alloc_info->size, alloc_info->flags); if (IS_ERR_OR_NULL(dma_buf)) { pr_err("lwis_platform_dma_buffer_alloc failed (%ld)\n", PTR_ERR(dma_buf)); return -ENOMEM; } /* * Increment refcount of the fd to 1 first before dma_buf_fd() * which is increment refcount of the fd to 2. * Both userspace's close(fd) and kernel's lwis_buffer_free() * will decrement the refcount by 1. Whoever reaches 0 refcount * frees the buffer. */ get_dma_buf(dma_buf); alloc_info->dma_fd = dma_buf_fd(dma_buf, O_CLOEXEC); if (alloc_info->dma_fd < 0) { pr_err("dma_buf_fd failed (%d)\n", alloc_info->dma_fd); dma_buf_put(dma_buf); return alloc_info->dma_fd; } alloc_info->partition_id = PT_PTID_INVALID; } buffer->fd = alloc_info->dma_fd; buffer->size = alloc_info->size; buffer->dma_buf = dma_buf; hash_add(lwis_client->allocated_buffers, &buffer->node, buffer->fd); return 0; } int lwis_buffer_free(struct lwis_client *lwis_client, struct lwis_allocated_buffer *buffer) { if (!lwis_client) { pr_err("Free: LWIS client is NULL\n"); return -ENODEV; } if (!buffer) { pr_err("Free: buffer is NULL\n"); return -EINVAL; } if (buffer->dma_buf == NULL) { if (lwis_client->lwis_dev->type == DEVICE_TYPE_SLC) { lwis_slc_buffer_free(lwis_client->lwis_dev, buffer->fd); } else { pr_err("Unexpected NULL dma_buf\n"); return -EINVAL; } } else { dma_buf_put(buffer->dma_buf); } hash_del(&buffer->node); return 0; } int lwis_buffer_enroll(struct lwis_client *lwis_client, struct lwis_enrolled_buffer *buffer) { struct lwis_buffer_enrollment_list *enrollment_list; struct list_head *it_enrollment; struct lwis_enrolled_buffer *old_buffer; if (!lwis_client) { pr_err("Enroll: LWIS client is NULL\n"); return -ENODEV; } if (!buffer) { pr_err("Enroll: buffer is NULL\n"); return -EINVAL; } if (buffer->info.dma_read && buffer->info.dma_write) { buffer->dma_direction = DMA_BIDIRECTIONAL; } else if (buffer->info.dma_read) { buffer->dma_direction = DMA_TO_DEVICE; } else if (buffer->info.dma_write) { buffer->dma_direction = DMA_FROM_DEVICE; } else { buffer->dma_direction = DMA_NONE; } if (!valid_dma_direction(buffer->dma_direction)) { dev_err(lwis_client->lwis_dev->dev, "Enroll: buffer->dma_direction is invalid\n"); return -EINVAL; } buffer->dma_buf = dma_buf_get(buffer->info.fd); if (IS_ERR_OR_NULL(buffer->dma_buf)) { dev_err(lwis_client->lwis_dev->dev, "Could not get dma buffer for fd: %d (errno: %ld)", buffer->info.fd, PTR_ERR(buffer->dma_buf)); return PTR_ERR(buffer->dma_buf); } buffer->dma_buf_attachment = dma_buf_attach(buffer->dma_buf, &lwis_client->lwis_dev->plat_dev->dev); if (IS_ERR_OR_NULL(buffer->dma_buf_attachment)) { dev_err(lwis_client->lwis_dev->dev, "Could not attach dma buffer for fd: %d (errno: %ld)", buffer->info.fd, PTR_ERR(buffer->dma_buf_attachment)); dma_buf_put(buffer->dma_buf); return PTR_ERR(buffer->dma_buf_attachment); } buffer->sg_table = dma_buf_map_attachment(buffer->dma_buf_attachment, buffer->dma_direction); if (IS_ERR_OR_NULL(buffer->sg_table)) { dev_err(lwis_client->lwis_dev->dev, "Could not map dma attachment for fd: %d (errno: %ld)", buffer->info.fd, PTR_ERR(buffer->sg_table)); if (PTR_ERR(buffer->sg_table) == -ENOMEM) { lwis_device_info_dump("Enroll buffer sizes", dump_total_enrolled_buffer_size); } dma_buf_detach(buffer->dma_buf, buffer->dma_buf_attachment); dma_buf_put(buffer->dma_buf); return PTR_ERR(buffer->sg_table); } buffer->info.dma_vaddr = sg_dma_address(buffer->sg_table->sgl); if (IS_ERR_OR_NULL((void *)buffer->info.dma_vaddr)) { dev_err(lwis_client->lwis_dev->dev, "Could not map dma vaddr for fd: %d", buffer->info.fd); goto err; } // Insert the new enrollment to the enrolled_buffers hashtable. enrollment_list = enrollment_list_find_or_create(lwis_client, buffer->info.dma_vaddr); if (!enrollment_list) { dev_err(lwis_client->lwis_dev->dev, "Cannot create enrollment list\n"); goto err; } // Check if there was duplicated identical enrollment. list_for_each (it_enrollment, &enrollment_list->list) { old_buffer = list_entry(it_enrollment, struct lwis_enrolled_buffer, list_node); if (old_buffer->info.fd == buffer->info.fd && old_buffer->info.dma_vaddr == buffer->info.dma_vaddr) { dev_err(lwis_client->lwis_dev->dev, "Duplicate vaddr %pad for fd %d", &buffer->info.dma_vaddr, buffer->info.fd); goto err; } } list_add_tail(&buffer->list_node, &enrollment_list->list); buffer->enrollment_list = enrollment_list; return 0; err: dma_buf_unmap_attachment(buffer->dma_buf_attachment, buffer->sg_table, buffer->dma_direction); dma_buf_detach(buffer->dma_buf, buffer->dma_buf_attachment); dma_buf_put(buffer->dma_buf); return -EINVAL; } int lwis_buffer_disenroll(struct lwis_client *lwis_client, struct lwis_enrolled_buffer *buffer) { if (!lwis_client) { pr_err("Disenroll: LWIS client is NULL\n"); return -ENODEV; } if (!buffer) { pr_err("Disenroll: buffer is NULL\n"); return -EINVAL; } lwis_platform_dma_buffer_unmap(lwis_client->lwis_dev, buffer->dma_buf_attachment, buffer->info.dma_vaddr); dma_buf_unmap_attachment(buffer->dma_buf_attachment, buffer->sg_table, buffer->dma_direction); dma_buf_detach(buffer->dma_buf, buffer->dma_buf_attachment); dma_buf_put(buffer->dma_buf); /* Delete the node from the hash table */ list_del(&buffer->list_node); if (list_empty(&buffer->enrollment_list->list)) { hash_del(&buffer->enrollment_list->node); kfree(buffer->enrollment_list); } return 0; } struct lwis_enrolled_buffer *lwis_client_enrolled_buffer_find(struct lwis_client *lwis_client, int fd, dma_addr_t dma_vaddr) { struct lwis_buffer_enrollment_list *enrollment_list; struct lwis_enrolled_buffer *buffer; struct list_head *it_enrollment; if (!lwis_client) { pr_err("lwis_client_enrolled_buffer_find: LWIS client is NULL\n"); return NULL; } enrollment_list = enrollment_list_find(lwis_client, dma_vaddr); if (!enrollment_list || list_empty(&enrollment_list->list)) { return NULL; } list_for_each (it_enrollment, &enrollment_list->list) { buffer = list_entry(it_enrollment, struct lwis_enrolled_buffer, list_node); if (buffer->info.fd == fd && buffer->info.dma_vaddr == dma_vaddr) { return buffer; } } return NULL; } int lwis_buffer_cpu_access(struct lwis_client *lwis_client, struct lwis_buffer_cpu_access_op *op) { struct dma_buf *dma_buf; enum dma_data_direction dma_direction; int ret = 0; if (!lwis_client) { pr_err("BufferCpuAccess: LWIS client is NULL\n"); return -ENODEV; } if (op->read && op->write) { dma_direction = DMA_BIDIRECTIONAL; } else if (op->read) { dma_direction = DMA_FROM_DEVICE; } else if (op->write) { dma_direction = DMA_TO_DEVICE; } else { dma_direction = DMA_NONE; } if (!valid_dma_direction(dma_direction)) { dev_err(lwis_client->lwis_dev->dev, "BufferCpuAccess: dma_direction is invalid\n"); return -EINVAL; } dma_buf = dma_buf_get(op->fd); if (IS_ERR_OR_NULL(dma_buf)) { pr_err("Could not get dma buffer for fd: %d", op->fd); return -EINVAL; } if (op->start) { ret = dma_buf_begin_cpu_access_partial(dma_buf, dma_direction, op->offset, op->len); } else { ret = dma_buf_end_cpu_access_partial(dma_buf, dma_direction, op->offset, op->len); } dma_buf_put(dma_buf); return ret; } int lwis_client_enrolled_buffers_clear(struct lwis_client *lwis_client) { /* Our hash table iterator */ struct lwis_buffer_enrollment_list *enrollment_list; /* Temporary vars for hash table traversal */ struct hlist_node *n; int i; /* Enrollment list iterator */ struct list_head *it_enrollment, *it_enrollment_tmp; struct lwis_enrolled_buffer *buffer; if (!lwis_client) { pr_err("lwis_client_enrolled_buffers_clear: LWIS client is NULL\n"); return -ENODEV; } /* Iterate over the entire hash table */ hash_for_each_safe (lwis_client->enrolled_buffers, i, n, enrollment_list, node) { list_for_each_safe (it_enrollment, it_enrollment_tmp, &enrollment_list->list) { buffer = list_entry(it_enrollment, struct lwis_enrolled_buffer, list_node); /* Disenroll the buffer */ lwis_buffer_disenroll(lwis_client, buffer); /* Free the object */ kfree(buffer); } } return 0; } struct lwis_allocated_buffer *lwis_client_allocated_buffer_find(struct lwis_client *lwis_client, int fd) { struct lwis_allocated_buffer *p; if (!lwis_client) { pr_err("lwis_client_allocated_buffer_find: LWIS client is NULL\n"); return NULL; } hash_for_each_possible (lwis_client->allocated_buffers, p, node, fd) { if (p->fd == fd) { return p; } } return NULL; } int lwis_client_allocated_buffers_clear(struct lwis_client *lwis_client) { struct lwis_allocated_buffer *buffer; struct hlist_node *n; int i; if (!lwis_client) { pr_err("lwis_client_allocated_buffers_clear: LWIS client is NULL\n"); return -ENODEV; } hash_for_each_safe (lwis_client->allocated_buffers, i, n, buffer, node) { if (lwis_client->lwis_dev->type != DEVICE_TYPE_SLC) { lwis_buffer_free(lwis_client, buffer); } else { hash_del(&buffer->node); } kfree(buffer); } return 0; }