blob: 11a1e0f52bb2afd605b6fbda1092f839e81dc9f2 [file] [log] [blame]
/*
* 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 <linux/fs.h>
#include <linux/slab.h>
#include <soc/google/pt.h>
#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;
}
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;
/*
* 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);
}
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_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;
}