1767 lines
47 KiB
C
1767 lines
47 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* drivers/rpbuf/rpbuf_core.c
|
|
*
|
|
* (C) Copyright 2020-2025
|
|
* Allwinner Technology Co., Ltd. <www.allwinnertech.com>
|
|
* Junyan Lin <junyanlin@allwinnertech.com>
|
|
*
|
|
* RPBuf core.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License as
|
|
* published by the Free Software Foundation; either version 2 of
|
|
* the License, or (at your option) any later version.
|
|
*
|
|
*/
|
|
#define pr_fmt(fmt) "[RPBUF](%s:%d) " fmt, __func__, __LINE__
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/list.h>
|
|
#include <linux/string.h>
|
|
#include <linux/of.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/dma-mapping.h>
|
|
|
|
#include "rpbuf_internal.h"
|
|
|
|
typedef int (*rpbuf_service_command_handler_t)(struct rpbuf_service *service,
|
|
enum rpbuf_service_command cmd,
|
|
void *content);
|
|
|
|
static LIST_HEAD(__rpbuf_links);
|
|
static DEFINE_MUTEX(__rpbuf_links_lock);
|
|
|
|
static void rpbuf_reset_controller(struct rpbuf_controller *controller);
|
|
|
|
/* Remember to use __rpbuf_links_lock outside to protect this function */
|
|
static inline struct rpbuf_link *rpbuf_find_link(void *token)
|
|
{
|
|
struct rpbuf_link *link;
|
|
|
|
list_for_each_entry(link, &__rpbuf_links, list) {
|
|
if (link->token == token)
|
|
return link;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static inline struct rpbuf_buffer *rpbuf_find_dummy_buffer_by_name(
|
|
struct list_head *dummy_buffers, const char *name)
|
|
{
|
|
struct rpbuf_buffer *buffer;
|
|
|
|
list_for_each_entry(buffer, dummy_buffers, dummy_list) {
|
|
if (0 == strncmp(buffer->name, name, RPBUF_NAME_SIZE))
|
|
return buffer;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static inline struct rpbuf_buffer *rpbuf_find_dummy_buffer_by_id(
|
|
struct list_head *dummy_buffers, int id)
|
|
{
|
|
struct rpbuf_buffer *buffer;
|
|
list_for_each_entry(buffer, dummy_buffers, dummy_list) {
|
|
if (buffer->id == id)
|
|
return buffer;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static struct rpbuf_buffer *rpbuf_alloc_buffer_instance(struct device *dev,
|
|
const char *name, int len)
|
|
{
|
|
struct rpbuf_buffer *buffer;
|
|
size_t name_len = strlen(name);
|
|
|
|
buffer = devm_kzalloc(dev, sizeof(struct rpbuf_buffer), GFP_KERNEL);
|
|
if (IS_ERR_OR_NULL(buffer)) {
|
|
dev_err(dev, "kzalloc for rpbuf_buffer failed\n");
|
|
goto err_out;
|
|
}
|
|
|
|
if (name_len == 0 || name_len >= RPBUF_NAME_SIZE) {
|
|
dev_err(dev, "invalid buffer name \"%s\", its length should be "
|
|
"in range [1, %d)\n", name, RPBUF_NAME_SIZE);
|
|
goto err_free_rpbuf_buffer;
|
|
}
|
|
strncpy(buffer->name, name, RPBUF_NAME_SIZE);
|
|
|
|
buffer->len = len;
|
|
init_waitqueue_head(&buffer->wait);
|
|
buffer->state = 0;
|
|
buffer->flags = 0;
|
|
buffer->offline = false;
|
|
|
|
return buffer;
|
|
|
|
err_free_rpbuf_buffer:
|
|
devm_kfree(dev, buffer);
|
|
err_out:
|
|
return NULL;
|
|
}
|
|
|
|
static void rpbuf_free_buffer_instance(struct device *dev,
|
|
struct rpbuf_buffer *buffer)
|
|
{
|
|
devm_kfree(dev, buffer);
|
|
}
|
|
|
|
static int rpbuf_alloc_buffer_payload_memory_default(struct rpbuf_controller *controller,
|
|
struct rpbuf_buffer *buffer)
|
|
{
|
|
struct device *dev = controller->dev;
|
|
void *va;
|
|
dma_addr_t pa;
|
|
int ret;
|
|
|
|
va = dma_alloc_coherent(dev, buffer->len, &pa, GFP_KERNEL);
|
|
if (IS_ERR_OR_NULL(va)) {
|
|
dev_err(dev, "dma_alloc_coherent for len %d failed\n", buffer->len);
|
|
ret = -ENOMEM;
|
|
goto err_out;
|
|
}
|
|
buffer->va = va;
|
|
buffer->pa = (phys_addr_t)pa;
|
|
buffer->da = (u64)pa;
|
|
|
|
return 0;
|
|
|
|
err_out:
|
|
return ret;
|
|
}
|
|
|
|
static int rpbuf_alloc_buffer_payload_memory(struct rpbuf_controller *controller,
|
|
struct rpbuf_buffer *buffer)
|
|
{
|
|
int ret;
|
|
|
|
if (buffer->ops && buffer->ops->alloc_payload_memory)
|
|
ret = buffer->ops->alloc_payload_memory(buffer, buffer->priv);
|
|
else if (controller->ops && controller->ops->alloc_payload_memory)
|
|
ret = controller->ops->alloc_payload_memory(controller, buffer,
|
|
controller->priv);
|
|
else
|
|
ret = rpbuf_alloc_buffer_payload_memory_default(controller, buffer);
|
|
|
|
dev_info(controller->dev, "\"%s\" allocate payload memory: " \
|
|
"va 0x%pK, pa %pad, len %d\n", buffer->name,
|
|
buffer->va, &buffer->pa, buffer->len);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void rpbuf_free_buffer_payload_memory_default(struct rpbuf_controller *controller,
|
|
struct rpbuf_buffer *buffer)
|
|
{
|
|
struct device *dev = controller->dev;
|
|
void *va;
|
|
dma_addr_t pa;
|
|
|
|
va = (void *)buffer->va;
|
|
pa = (dma_addr_t)buffer->pa;
|
|
|
|
dma_free_coherent(dev, buffer->len, va, pa);
|
|
}
|
|
|
|
static void rpbuf_free_buffer_payload_memory(struct rpbuf_controller *controller,
|
|
struct rpbuf_buffer *buffer)
|
|
{
|
|
if (buffer->ops && buffer->ops->free_payload_memory)
|
|
buffer->ops->free_payload_memory(buffer, buffer->priv);
|
|
else if (controller->ops && controller->ops->free_payload_memory)
|
|
controller->ops->free_payload_memory(controller, buffer,
|
|
controller->priv);
|
|
else
|
|
rpbuf_free_buffer_payload_memory_default(controller, buffer);
|
|
|
|
dev_info(controller->dev, "\"%s\" free payload memory: " \
|
|
"va %pK, pa %pad, len %d\n", buffer->name,
|
|
buffer->va, &buffer->pa, buffer->len);
|
|
}
|
|
|
|
static void *rpbuf_addr_remap_default(struct rpbuf_controller *controller,
|
|
struct rpbuf_buffer *buffer,
|
|
phys_addr_t pa, u64 da, int len)
|
|
{
|
|
/*
|
|
* TODO:
|
|
* How to translate pa to va? ioremap doesn't guarantee that the
|
|
* returned virtual address can be directly used?
|
|
*/
|
|
return ioremap_nocache(pa, len);
|
|
}
|
|
|
|
static void *rpbuf_addr_remap(struct rpbuf_controller *controller,
|
|
struct rpbuf_buffer *buffer,
|
|
phys_addr_t pa, u64 da, int len)
|
|
{
|
|
if (buffer->ops && buffer->ops->addr_remap)
|
|
return buffer->ops->addr_remap(buffer, pa, da, len, buffer->priv);
|
|
|
|
if (controller->ops && controller->ops->addr_remap)
|
|
return controller->ops->addr_remap(controller, pa, da, len,
|
|
controller->priv);
|
|
|
|
return rpbuf_addr_remap_default(controller, buffer, pa, da, len);
|
|
}
|
|
|
|
static struct rpbuf_link *rpbuf_create_link(void *token)
|
|
{
|
|
struct rpbuf_link *link;
|
|
|
|
link = kzalloc(sizeof(struct rpbuf_link), GFP_KERNEL);
|
|
if (IS_ERR_OR_NULL(link)) {
|
|
pr_err("kzalloc for rpbuf_link failed\n");
|
|
link = NULL;
|
|
goto out;
|
|
}
|
|
spin_lock_init(&link->lock);
|
|
mutex_init(&link->service_lock);
|
|
mutex_init(&link->controller_lock);
|
|
link->token = token;
|
|
link->ref_cnt = 0;
|
|
out:
|
|
return link;
|
|
}
|
|
|
|
static void rpbuf_destroy_link(struct rpbuf_link *link)
|
|
{
|
|
mutex_destroy(&link->service_lock);
|
|
mutex_destroy(&link->controller_lock);
|
|
kfree(link);
|
|
}
|
|
|
|
static
|
|
int device_match_of_node(struct device *dev, void *np)
|
|
{
|
|
return dev->of_node == np;
|
|
}
|
|
|
|
struct device *
|
|
rpbuf_bus_find_device_by_of_node(struct bus_type *bus, struct device_node *np)
|
|
{
|
|
return bus_find_device(bus, NULL, np, device_match_of_node);
|
|
}
|
|
EXPORT_SYMBOL(rpbuf_bus_find_device_by_of_node);
|
|
|
|
struct rpbuf_service *rpbuf_create_service(struct device *dev,
|
|
const struct rpbuf_service_ops *ops,
|
|
void *priv)
|
|
{
|
|
struct rpbuf_service *service;
|
|
|
|
service = devm_kzalloc(dev, sizeof(struct rpbuf_service), GFP_KERNEL);
|
|
if (IS_ERR_OR_NULL(service)) {
|
|
dev_err(dev, "kzalloc for rpbuf_service failed\n");
|
|
service = NULL;
|
|
goto out;
|
|
}
|
|
|
|
service->dev = dev;
|
|
|
|
service->ops = ops;
|
|
service->priv = priv;
|
|
|
|
out:
|
|
return service;
|
|
}
|
|
EXPORT_SYMBOL(rpbuf_create_service);
|
|
|
|
void rpbuf_destroy_service(struct rpbuf_service *service)
|
|
{
|
|
if (!service) {
|
|
pr_err("invalid service\n");
|
|
return;
|
|
}
|
|
|
|
service->ops = NULL;
|
|
service->priv = NULL;
|
|
|
|
devm_kfree(service->dev, service);
|
|
}
|
|
EXPORT_SYMBOL(rpbuf_destroy_service);
|
|
|
|
int rpbuf_register_service(struct rpbuf_service *service, void *token)
|
|
{
|
|
struct rpbuf_link *link;
|
|
unsigned long flags;
|
|
int ret;
|
|
|
|
mutex_lock(&__rpbuf_links_lock);
|
|
|
|
if (!service || !token) {
|
|
ret = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
pr_devel("register service 0x%px (token: 0x%px)\n", service, token);
|
|
|
|
link = rpbuf_find_link(token);
|
|
if (!link) {
|
|
link = rpbuf_create_link(token);
|
|
if (!link) {
|
|
pr_err("rpbuf_create_link failed\n");
|
|
ret = -EINVAL;
|
|
goto out;
|
|
}
|
|
pr_devel("create new link 0x%px (token: 0x%px)\n", link, token);
|
|
list_add_tail(&link->list, &__rpbuf_links);
|
|
}
|
|
|
|
mutex_lock(&link->service_lock);
|
|
spin_lock_irqsave(&link->lock, flags);
|
|
|
|
if (link->service && link->service != service) {
|
|
spin_unlock_irqrestore(&link->lock, flags);
|
|
pr_err("another service in the same token has been registered\n");
|
|
ret = -EINVAL;
|
|
goto unlock_service_lock;
|
|
}
|
|
|
|
link->service = service;
|
|
service->link = link;
|
|
link->ref_cnt++;
|
|
|
|
pr_devel("link: service: 0x%px, controller: 0x%px, ref_cnt: %d\n",
|
|
link->service, link->controller, link->ref_cnt);
|
|
|
|
spin_unlock_irqrestore(&link->lock, flags);
|
|
|
|
if (link->controller)
|
|
wake_up_interruptible(&link->controller->wq);
|
|
|
|
ret = 0;
|
|
unlock_service_lock:
|
|
mutex_unlock(&link->service_lock);
|
|
out:
|
|
mutex_unlock(&__rpbuf_links_lock);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(rpbuf_register_service);
|
|
|
|
void rpbuf_unregister_service(struct rpbuf_service *service)
|
|
{
|
|
struct rpbuf_link *link = service->link;
|
|
unsigned long flags;
|
|
|
|
mutex_lock(&__rpbuf_links_lock);
|
|
|
|
mutex_lock(&link->service_lock);
|
|
spin_lock_irqsave(&link->lock, flags);
|
|
|
|
pr_devel("unregister service 0x%px\n", service);
|
|
|
|
spin_unlock_irqrestore(&link->lock, flags);
|
|
/* we need to reset all connections, when unregister service */
|
|
if (link->controller)
|
|
rpbuf_reset_controller(link->controller);
|
|
spin_lock_irqsave(&link->lock, flags);
|
|
|
|
link->service = NULL;
|
|
service->link = NULL;
|
|
link->ref_cnt--;
|
|
|
|
if (link->ref_cnt > 0) {
|
|
spin_unlock_irqrestore(&link->lock, flags);
|
|
mutex_unlock(&link->service_lock);
|
|
mutex_unlock(&__rpbuf_links_lock);
|
|
return;
|
|
}
|
|
|
|
list_del(&link->list);
|
|
|
|
spin_unlock_irqrestore(&link->lock, flags);
|
|
mutex_unlock(&link->service_lock);
|
|
|
|
pr_devel("destroy link 0x%px\n", link);
|
|
rpbuf_destroy_link(link);
|
|
|
|
mutex_unlock(&__rpbuf_links_lock);
|
|
}
|
|
EXPORT_SYMBOL(rpbuf_unregister_service);
|
|
|
|
struct rpbuf_controller *rpbuf_create_controller(struct device *dev,
|
|
const struct rpbuf_controller_ops *ops,
|
|
void *priv)
|
|
{
|
|
struct rpbuf_controller *controller;
|
|
|
|
controller = devm_kzalloc(dev, sizeof(struct rpbuf_controller), GFP_KERNEL);
|
|
if (IS_ERR_OR_NULL(controller)) {
|
|
dev_err(dev, "kzalloc for rpbuf_controller failed\n");
|
|
controller = NULL;
|
|
goto out;
|
|
}
|
|
|
|
controller->dev = dev;
|
|
|
|
controller->ops = ops;
|
|
controller->priv = priv;
|
|
|
|
INIT_LIST_HEAD(&controller->remote_dummy_buffers);
|
|
INIT_LIST_HEAD(&controller->local_dummy_buffers);
|
|
idr_init(&controller->buffers);
|
|
idr_init(&controller->local_buffers);
|
|
init_waitqueue_head(&controller->wq);
|
|
out:
|
|
return controller;
|
|
}
|
|
EXPORT_SYMBOL(rpbuf_create_controller);
|
|
|
|
void rpbuf_destroy_controller(struct rpbuf_controller *controller)
|
|
{
|
|
struct device *dev = controller->dev;
|
|
struct rpbuf_buffer *buffer;
|
|
struct rpbuf_buffer *tmp;
|
|
enum rpbuf_role role;
|
|
int id;
|
|
|
|
if (!controller) {
|
|
pr_err("invalid controller\n");
|
|
return;
|
|
}
|
|
|
|
wake_up_interruptible(&controller->wq);
|
|
role = controller->role;
|
|
|
|
list_for_each_entry_safe(buffer, tmp, &controller->remote_dummy_buffers, dummy_list) {
|
|
list_del(&buffer->dummy_list);
|
|
if (buffer->allocated)
|
|
rpbuf_free_buffer_payload_memory(controller, buffer);
|
|
rpbuf_free_buffer_instance(dev, buffer);
|
|
}
|
|
list_for_each_entry_safe(buffer, tmp, &controller->local_dummy_buffers, dummy_list) {
|
|
list_del(&buffer->dummy_list);
|
|
if (buffer->allocated)
|
|
rpbuf_free_buffer_payload_memory(controller, buffer);
|
|
rpbuf_free_buffer_instance(dev, buffer);
|
|
if (role == RPBUF_ROLE_SLAVE)
|
|
idr_remove(&controller->local_buffers, buffer->id);
|
|
}
|
|
idr_for_each_entry(&controller->buffers, buffer, id) {
|
|
if (buffer->allocated)
|
|
rpbuf_free_buffer_payload_memory(controller, buffer);
|
|
rpbuf_free_buffer_instance(dev, buffer);
|
|
}
|
|
idr_destroy(&controller->buffers);
|
|
idr_destroy(&controller->local_buffers);
|
|
|
|
controller->priv = NULL;
|
|
|
|
devm_kfree(controller->dev, controller);
|
|
}
|
|
EXPORT_SYMBOL(rpbuf_destroy_controller);
|
|
|
|
static void rpbuf_reset_controller(struct rpbuf_controller *controller)
|
|
{
|
|
struct rpbuf_buffer *buffer, *tmp;
|
|
int id;
|
|
|
|
idr_for_each_entry(&controller->buffers, buffer, id) {
|
|
idr_remove(&controller->buffers, id);
|
|
buffer->offline = true;
|
|
list_add_tail(&buffer->dummy_list, &controller->local_dummy_buffers);
|
|
dev_dbg(controller->dev,
|
|
"buffer \"%s\" (id:%d): buffers -> local_dummy_buffers\n",
|
|
buffer->name, id);
|
|
}
|
|
|
|
list_for_each_entry_safe(buffer, tmp, &controller->remote_dummy_buffers, dummy_list) {
|
|
list_del(&buffer->dummy_list);
|
|
if (buffer->allocated) {
|
|
rpbuf_free_buffer_payload_memory(controller, buffer);
|
|
buffer->allocated = false;
|
|
}
|
|
dev_dbg(controller->dev, "buffer \"%s\": remote_dummy_buffers -> NULL\n",
|
|
buffer->name);
|
|
rpbuf_free_buffer_instance(controller->dev, buffer);
|
|
}
|
|
}
|
|
|
|
int rpbuf_register_controller(struct rpbuf_controller *controller,
|
|
void *token, enum rpbuf_role role)
|
|
{
|
|
struct rpbuf_link *link;
|
|
unsigned long flags;
|
|
int ret;
|
|
|
|
mutex_lock(&__rpbuf_links_lock);
|
|
|
|
if (!controller || !token) {
|
|
ret = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
pr_devel("register controller 0x%px (token: 0x%px)\n", controller, token);
|
|
|
|
link = rpbuf_find_link(token);
|
|
if (!link) {
|
|
link = rpbuf_create_link(token);
|
|
if (!link) {
|
|
pr_err("rpbuf_create_link failed\n");
|
|
ret = -EINVAL;
|
|
goto out;
|
|
}
|
|
pr_devel("create new link 0x%px (token: 0x%px)\n", link, token);
|
|
list_add_tail(&link->list, &__rpbuf_links);
|
|
}
|
|
|
|
mutex_lock(&link->controller_lock);
|
|
spin_lock_irqsave(&link->lock, flags);
|
|
|
|
if (link->controller && link->controller != controller) {
|
|
spin_unlock_irqrestore(&link->lock, flags);
|
|
pr_err("another controller in the same token has been registered\n");
|
|
ret = -EINVAL;
|
|
goto unlock_controller_lock;
|
|
}
|
|
|
|
link->controller = controller;
|
|
controller->link = link;
|
|
link->ref_cnt++;
|
|
|
|
pr_devel("link: service: 0x%px, controller: 0x%px, ref_cnt: %d\n",
|
|
link->service, link->controller, link->ref_cnt);
|
|
|
|
spin_unlock_irqrestore(&link->lock, flags);
|
|
|
|
controller->role = role;
|
|
|
|
ret = 0;
|
|
unlock_controller_lock:
|
|
mutex_unlock(&link->controller_lock);
|
|
out:
|
|
mutex_unlock(&__rpbuf_links_lock);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(rpbuf_register_controller);
|
|
|
|
void rpbuf_unregister_controller(struct rpbuf_controller *controller)
|
|
{
|
|
struct rpbuf_link *link = controller->link;
|
|
unsigned long flags;
|
|
|
|
mutex_lock(&__rpbuf_links_lock);
|
|
|
|
mutex_lock(&link->controller_lock);
|
|
spin_lock_irqsave(&link->lock, flags);
|
|
|
|
pr_devel("unregister controller 0x%px\n", controller);
|
|
|
|
link->controller = NULL;
|
|
controller->link = NULL;
|
|
link->ref_cnt--;
|
|
|
|
if (link->ref_cnt > 0) {
|
|
spin_unlock_irqrestore(&link->lock, flags);
|
|
mutex_unlock(&link->controller_lock);
|
|
mutex_unlock(&__rpbuf_links_lock);
|
|
return;
|
|
}
|
|
|
|
list_del(&link->list);
|
|
|
|
spin_unlock_irqrestore(&link->lock, flags);
|
|
mutex_unlock(&link->controller_lock);
|
|
|
|
pr_devel("destroy link 0x%px\n", link);
|
|
rpbuf_destroy_link(link);
|
|
|
|
mutex_unlock(&__rpbuf_links_lock);
|
|
}
|
|
EXPORT_SYMBOL(rpbuf_unregister_controller);
|
|
|
|
struct rpbuf_controller *rpbuf_get_controller_by_of_node(const struct device_node *np, int index)
|
|
{
|
|
struct device_node *rpbuf_np;
|
|
struct device *dev;
|
|
|
|
rpbuf_np = of_parse_phandle(np, "rpbuf", index);
|
|
if (!rpbuf_np) {
|
|
pr_err("no \"rpbuf\" node specified\n");
|
|
return NULL;
|
|
}
|
|
|
|
/* We always assume that the rpbuf controller is a platform device */
|
|
dev = rpbuf_bus_find_device_by_of_node(&platform_bus_type, rpbuf_np);
|
|
if (!dev) {
|
|
pr_err("cannot find rpbuf device\n");
|
|
return NULL;
|
|
}
|
|
|
|
return (struct rpbuf_controller *)dev_get_drvdata(dev);
|
|
}
|
|
EXPORT_SYMBOL(rpbuf_get_controller_by_of_node);
|
|
|
|
int rpbuf_wait_controller_ready(struct rpbuf_controller *controller, int timeout)
|
|
{
|
|
struct rpbuf_link *link;
|
|
|
|
if (!controller || !controller->link) {
|
|
pr_err("(%s:%d) invalid arguments (%p, %p), Maybe rpbuf is not init?\n", __func__, __LINE__,
|
|
controller, controller->link);
|
|
return -EINVAL;;
|
|
}
|
|
|
|
link = controller->link;
|
|
|
|
if (wait_event_interruptible_timeout(controller->wq,
|
|
link->service, msecs_to_jiffies(timeout)) < 0)
|
|
return -ERESTARTSYS;
|
|
|
|
if (!link->service)
|
|
return -ETIMEDOUT;
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(rpbuf_wait_controller_ready);
|
|
|
|
static const int rpbuf_service_message_content_len[RPBUF_SERVICE_CMD_MAX] = {
|
|
0, /* RPBUF_SERVICE_CMD_UNKNOWN */
|
|
sizeof(struct rpbuf_service_content_buffer_created),
|
|
sizeof(struct rpbuf_service_content_buffer_destroyed),
|
|
sizeof(struct rpbuf_service_content_buffer_transmitted),
|
|
sizeof(struct rpbuf_service_content_buffer_ack),
|
|
};
|
|
|
|
/*
|
|
* Return size of the rpbuf service message on success, otherwise a negative
|
|
* number on failure.
|
|
*/
|
|
static int rpbuf_compose_service_message(u8 *msg, enum rpbuf_service_command cmd,
|
|
void *content)
|
|
{
|
|
struct rpbuf_service_message_header header;
|
|
struct rpbuf_service_message_trailer trailer;
|
|
u8 *p;
|
|
|
|
if (cmd == RPBUF_SERVICE_CMD_UNKNOWN || cmd >= RPBUF_SERVICE_CMD_MAX) {
|
|
pr_err("invalid rpbuf service command\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
header.preamble = RPBUF_SERVICE_MESSAGE_PREAMBLE;
|
|
header.command = cmd;
|
|
|
|
header.content_len = rpbuf_service_message_content_len[cmd];
|
|
if (header.content_len > RPBUF_SERVICE_MESSAGE_LENGTH_MAX
|
|
- sizeof(struct rpbuf_service_message_header)
|
|
- sizeof(struct rpbuf_service_message_trailer)) {
|
|
pr_err("rpbuf service message content length too long\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
trailer.postamble = RPBUF_SERVICE_MESSAGE_POSTAMBLE;
|
|
|
|
p = msg;
|
|
memcpy(p, &header, sizeof(header));
|
|
p += sizeof(header);
|
|
memcpy(p, content, header.content_len);
|
|
p += header.content_len;
|
|
memcpy(p, &trailer, sizeof(trailer));
|
|
|
|
return sizeof(header) + header.content_len + sizeof(trailer);
|
|
}
|
|
|
|
static int rpbuf_notify_by_service(struct rpbuf_service *service,
|
|
enum rpbuf_service_command cmd, void *content)
|
|
{
|
|
int ret;
|
|
struct device *dev = service->dev;
|
|
u8 msg[RPBUF_SERVICE_MESSAGE_LENGTH_MAX];
|
|
int msg_len;
|
|
|
|
ret = rpbuf_compose_service_message(msg, cmd, content);
|
|
if (ret < 0) {
|
|
dev_err(dev, "failed to generate rpbuf service message\n");
|
|
return ret;
|
|
}
|
|
msg_len = ret;
|
|
|
|
if (!service->ops || !service->ops->notify) {
|
|
dev_err(dev, "service has not valid notify operation\n");
|
|
return -EINVAL;
|
|
}
|
|
ret = service->ops->notify(msg, msg_len, service->priv);
|
|
if (ret < 0) {
|
|
dev_err(dev, "notify error: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* NOTE:
|
|
* To avoid deadlock, this function CANNOT be run within 'link->controller_lock'.
|
|
*/
|
|
static int rpbuf_notify_by_link(struct rpbuf_link *link,
|
|
enum rpbuf_service_command cmd, void *content)
|
|
{
|
|
int ret;
|
|
unsigned long flags;
|
|
struct rpbuf_service *service;
|
|
|
|
mutex_lock(&link->service_lock);
|
|
|
|
spin_lock_irqsave(&link->lock, flags);
|
|
service = link->service;
|
|
if (!service) {
|
|
pr_err("link 0x%px has no service\n", link);
|
|
spin_unlock_irqrestore(&link->lock, flags);
|
|
ret = -ENOENT;
|
|
goto out;
|
|
}
|
|
spin_unlock_irqrestore(&link->lock, flags);
|
|
|
|
ret = rpbuf_notify_by_service(service, cmd, content);
|
|
if (ret < 0) {
|
|
dev_err(service->dev, "notify BUFFER_CREATED error\n");
|
|
goto out;
|
|
}
|
|
|
|
ret = 0;
|
|
out:
|
|
mutex_unlock(&link->service_lock);
|
|
return ret;
|
|
}
|
|
|
|
static int rpbuf_notify_remotebuffer_complete(struct rpbuf_controller *controller,
|
|
struct rpbuf_buffer *buffer)
|
|
{
|
|
int ret;
|
|
int msg_len;
|
|
struct rpbuf_link *link;
|
|
struct rpbuf_service *service;
|
|
struct rpbuf_service_content_buffer_ack content;
|
|
u8 msg[RPBUF_SERVICE_MESSAGE_LENGTH_MAX];
|
|
|
|
link = controller->link;
|
|
service = link->service;
|
|
|
|
content.id = buffer->id;
|
|
content.timediff = 0;
|
|
|
|
ret = rpbuf_compose_service_message(msg, RPBUF_SERVICE_CMD_BUFFER_ACK, &content);
|
|
if (ret < 0) {
|
|
dev_err(controller->dev, "failed to generate rpbuf service message\n");
|
|
return ret;
|
|
}
|
|
msg_len = ret;
|
|
|
|
if (!service->ops || !service->ops->notify) {
|
|
dev_err(service->dev, "service has not valid notify operation\n");
|
|
return -EINVAL;
|
|
}
|
|
ret = service->ops->notify(msg, msg_len, service->priv);
|
|
if (ret < 0) {
|
|
dev_err(service->dev, "notify error: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rpbuf_wait_for_remotebuffer_complete(struct rpbuf_controller *controller,
|
|
struct rpbuf_buffer *buffer)
|
|
{
|
|
/* already get notify? */
|
|
if (!(buffer->state & RPBUF_FLAGS_GETNOTIFY)) {
|
|
if (wait_event_interruptible(buffer->wait,
|
|
(buffer->state & RPBUF_FLAGS_GETNOTIFY)))
|
|
return -ERESTARTSYS;
|
|
}
|
|
|
|
/* clear notify bit */
|
|
buffer->state &= ~RPBUF_FLAGS_GETNOTIFY;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rpbuf_service_command_buffer_created_handler(struct rpbuf_service *service,
|
|
enum rpbuf_service_command cmd,
|
|
void *content)
|
|
{
|
|
int ret;
|
|
struct rpbuf_link *link = service->link;
|
|
struct rpbuf_controller *controller;
|
|
unsigned long flags;
|
|
enum rpbuf_role role;
|
|
struct rpbuf_service_content_buffer_created *cont = content;
|
|
struct rpbuf_service_content_buffer_created content_back;
|
|
struct rpbuf_buffer *buffer;
|
|
int id;
|
|
int is_available = 0;
|
|
|
|
mutex_lock(&link->controller_lock);
|
|
|
|
spin_lock_irqsave(&link->lock, flags);
|
|
controller = link->controller;
|
|
if (!controller) {
|
|
dev_err(service->dev, "service 0x%px not linked with controller\n",
|
|
service);
|
|
spin_unlock_irqrestore(&link->lock, flags);
|
|
ret = -ENOENT;
|
|
goto unlock_controller_lock;
|
|
}
|
|
spin_unlock_irqrestore(&link->lock, flags);
|
|
|
|
role = controller->role;
|
|
if (role != RPBUF_ROLE_MASTER && role != RPBUF_ROLE_SLAVE) {
|
|
dev_err(controller->dev, "unknown rpbuf role\n");
|
|
ret = -EINVAL;
|
|
goto unlock_controller_lock;
|
|
}
|
|
|
|
/*
|
|
* Check whether buffer with the same name already exists in 'buffers'.
|
|
* It should not exist normally. But if exists, it means that remote
|
|
* had some issues before and is re-allocating the buffer now. At this
|
|
* time if remote buffer is not available, we need to send BUFFER_CREATED
|
|
* command back to remote in order to complete its buffer creation.
|
|
*/
|
|
idr_for_each_entry(&controller->buffers, buffer, id) {
|
|
if (0 == strncmp(buffer->name, cont->name, RPBUF_NAME_SIZE)) {
|
|
dev_info(controller->dev,
|
|
"(unexpected) remote re-allocating buffer \"%s\" "
|
|
"(available: %d)\n",
|
|
buffer->name, cont->is_available);
|
|
|
|
if (cont->is_available) {
|
|
ret = 0;
|
|
goto unlock_controller_lock;
|
|
}
|
|
|
|
strncpy(content_back.name, buffer->name, RPBUF_NAME_SIZE);
|
|
content_back.id = buffer->id;
|
|
content_back.len = buffer->len;
|
|
content_back.pa = buffer->pa;
|
|
content_back.da = buffer->da;
|
|
content_back.is_available = 1;
|
|
|
|
mutex_unlock(&link->controller_lock);
|
|
|
|
ret = rpbuf_notify_by_link(link,
|
|
RPBUF_SERVICE_CMD_BUFFER_CREATED,
|
|
(void *)&content_back);
|
|
if (ret < 0) {
|
|
dev_err(controller->dev, "rpbuf_notify_by_link "
|
|
"BUFFER_CREATED failed: %d\n", ret);
|
|
goto out;
|
|
}
|
|
ret = 0;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
buffer = rpbuf_find_dummy_buffer_by_name(&controller->local_dummy_buffers,
|
|
cont->name);
|
|
if (buffer) {
|
|
/*
|
|
* A buffer with the same name is found in 'local_dummy_buffers',
|
|
* meaning that it has been allocated by local before. Now we
|
|
* update its information and move it to 'buffers'.
|
|
*/
|
|
if (buffer->len != cont->len) {
|
|
dev_err(controller->dev, "buffer length not match with remote "
|
|
"(local: %d, remote: %u)\n", buffer->len, cont->len);
|
|
ret = -EINVAL;
|
|
goto unlock_controller_lock;
|
|
}
|
|
|
|
if (role == RPBUF_ROLE_SLAVE)
|
|
id = buffer->id;
|
|
else
|
|
id = cont->id;
|
|
|
|
id = idr_alloc(&controller->buffers, buffer,
|
|
id, id + 1, GFP_KERNEL);
|
|
if (id < 0) {
|
|
dev_err(controller->dev, "idr_alloc for id %d failed: %d\n",
|
|
cont->id, id);
|
|
ret = id;
|
|
goto unlock_controller_lock;
|
|
}
|
|
buffer->id = id;
|
|
|
|
list_del(&buffer->dummy_list);
|
|
|
|
if (role == RPBUF_ROLE_SLAVE) {
|
|
buffer->pa = cont->pa;
|
|
buffer->da = cont->da;
|
|
buffer->va = rpbuf_addr_remap(controller, buffer,
|
|
buffer->pa, buffer->da,
|
|
buffer->len);
|
|
} else {
|
|
if (!buffer->allocated) {
|
|
ret = rpbuf_alloc_buffer_payload_memory(controller, buffer);
|
|
if (ret < 0) {
|
|
dev_err(controller->dev, "rpbuf_alloc_buffer_payload_memory failed\n");
|
|
ret = -ENOMEM;
|
|
idr_remove(&controller->buffers, buffer->id);
|
|
goto unlock_controller_lock;
|
|
}
|
|
buffer->allocated = true;
|
|
}
|
|
}
|
|
|
|
dev_dbg(controller->dev,
|
|
"buffer \"%s\" (id:%d): local_dummy_buffers -> buffers\n",
|
|
buffer->name, buffer->id);
|
|
is_available = 1;
|
|
} else {
|
|
/*
|
|
* If not found, create a new buffer to save this information
|
|
* and add it to 'remote_dummy_buffers', waiting for local to
|
|
* allocate it manually later.
|
|
*
|
|
* In this situation, if a buffer with the same name already
|
|
* exists in 'remote_dummy_buffers', it is an unexpected state.
|
|
* Maybe remote had some issues before and is re-allocating the
|
|
* buffer now. Anyway, we overwrite the previous information.
|
|
*/
|
|
|
|
buffer = rpbuf_find_dummy_buffer_by_name(&controller->remote_dummy_buffers,
|
|
cont->name);
|
|
if (buffer) {
|
|
dev_info(controller->dev, "buffer \"%s\": (unexpected) "
|
|
"overwrite information in 'remote_dummy_buffers'\n",
|
|
buffer->name);
|
|
strncpy(buffer->name, cont->name, RPBUF_NAME_SIZE);
|
|
buffer->len = cont->len;
|
|
buffer->id = cont->id;
|
|
buffer->pa = cont->pa;
|
|
buffer->da = cont->da;
|
|
} else {
|
|
buffer = rpbuf_alloc_buffer_instance(controller->dev,
|
|
cont->name, cont->len);
|
|
if (!buffer) {
|
|
dev_err(controller->dev, "rpbuf_alloc_buffer_instance failed\n");
|
|
ret = -ENOMEM;
|
|
goto unlock_controller_lock;
|
|
}
|
|
buffer->id = cont->id;
|
|
buffer->pa = cont->pa;
|
|
buffer->da = cont->da;
|
|
list_add_tail(&buffer->dummy_list,
|
|
&controller->remote_dummy_buffers);
|
|
|
|
dev_dbg(controller->dev, "buffer \"%s\": NULL -> remote_dummy_buffers\n",
|
|
buffer->name);
|
|
}
|
|
}
|
|
|
|
/* tell the remote controller, we are re-online */
|
|
if (is_available && buffer->offline) {
|
|
buffer->offline = false;
|
|
strncpy(content_back.name, buffer->name, RPBUF_NAME_SIZE);
|
|
content_back.id = buffer->id;
|
|
content_back.len = buffer->len;
|
|
content_back.pa = buffer->pa;
|
|
content_back.da = buffer->da;
|
|
content_back.is_available = 1;
|
|
|
|
mutex_unlock(&link->controller_lock);
|
|
|
|
ret = rpbuf_notify_by_link(link,
|
|
RPBUF_SERVICE_CMD_BUFFER_CREATED,
|
|
(void *)&content_back);
|
|
if (ret < 0) {
|
|
dev_err(controller->dev, "rpbuf_notify_by_link "
|
|
"BUFFER_CREATED failed: %d\n", ret);
|
|
goto out;
|
|
}
|
|
ret = 0;
|
|
goto out;
|
|
}
|
|
|
|
ret = 0;
|
|
unlock_controller_lock:
|
|
mutex_unlock(&link->controller_lock);
|
|
out:
|
|
if (is_available && buffer->cbs && buffer->cbs->available_cb)
|
|
buffer->cbs->available_cb(buffer, buffer->priv);
|
|
return ret;
|
|
}
|
|
|
|
static int rpbuf_service_command_buffer_destroyed_handler(struct rpbuf_service *service,
|
|
enum rpbuf_service_command cmd,
|
|
void *content)
|
|
{
|
|
int ret;
|
|
struct rpbuf_link *link = service->link;
|
|
struct rpbuf_controller *controller;
|
|
unsigned long flags;
|
|
enum rpbuf_role role;
|
|
struct rpbuf_service_content_buffer_destroyed *cont = content;
|
|
struct rpbuf_buffer *buffer;
|
|
|
|
mutex_lock(&link->controller_lock);
|
|
|
|
spin_lock_irqsave(&link->lock, flags);
|
|
controller = link->controller;
|
|
if (!controller) {
|
|
dev_err(service->dev, "service 0x%px not linked with controller\n", service);
|
|
spin_unlock_irqrestore(&link->lock, flags);
|
|
ret = -ENOENT;
|
|
goto err_out;
|
|
}
|
|
spin_unlock_irqrestore(&link->lock, flags);
|
|
|
|
role = controller->role;
|
|
if (role != RPBUF_ROLE_MASTER && role != RPBUF_ROLE_SLAVE) {
|
|
dev_err(controller->dev, "unknown rpbuf role\n");
|
|
ret = -EINVAL;
|
|
goto err_out;
|
|
}
|
|
|
|
buffer = rpbuf_find_dummy_buffer_by_id(&controller->remote_dummy_buffers, cont->id);
|
|
if (buffer) {
|
|
/*
|
|
* A buffer with the same name is found in 'remote_dummy_buffers',
|
|
* meaning that local doesn't allocate it or has freed it.
|
|
* Now we delete it from the list.
|
|
*/
|
|
list_del(&buffer->dummy_list);
|
|
|
|
if (buffer->allocated) {
|
|
rpbuf_free_buffer_payload_memory(controller, buffer);
|
|
buffer->allocated = false;
|
|
}
|
|
|
|
dev_dbg(controller->dev, "buffer \"%s\": remote_dummy_buffers -> NULL\n",
|
|
buffer->name);
|
|
|
|
/*
|
|
* Remember to free the instance after deleting a entry from
|
|
* 'remote_dummy_buffers'.
|
|
*/
|
|
rpbuf_free_buffer_instance(controller->dev, buffer);
|
|
buffer = NULL;
|
|
} else {
|
|
/*
|
|
* If not found, meaning that local has allocated it. Now it
|
|
* should be stored in 'buffers'. We just move it to
|
|
* 'local_dummy_buffers' and wait for local to free it manually
|
|
* later.
|
|
*/
|
|
buffer = idr_find(&controller->buffers, cont->id);
|
|
idr_remove(&controller->buffers, cont->id);
|
|
if (buffer) {
|
|
list_add_tail(&buffer->dummy_list, &controller->local_dummy_buffers);
|
|
dev_dbg(controller->dev,
|
|
"buffer \"%s\" (id:%d): buffers -> local_dummy_buffers\n",
|
|
buffer->name, cont->id);
|
|
} else {
|
|
dev_err(controller->dev, "buffer not found, "
|
|
"nothing handled with BUFFER_DESTROYED\n");
|
|
ret = -EINVAL;
|
|
goto err_out;
|
|
}
|
|
}
|
|
|
|
mutex_unlock(&link->controller_lock);
|
|
|
|
if (buffer && buffer->cbs && buffer->cbs->destroyed_cb)
|
|
return buffer->cbs->destroyed_cb(buffer, buffer->priv);
|
|
|
|
return 0;
|
|
|
|
err_out:
|
|
mutex_unlock(&link->controller_lock);
|
|
return ret;
|
|
}
|
|
|
|
static int rpbuf_service_command_buffer_transmitted_handler(struct rpbuf_service *service,
|
|
enum rpbuf_service_command cmd,
|
|
void *content)
|
|
{
|
|
int ret;
|
|
struct rpbuf_link *link = service->link;
|
|
struct rpbuf_controller *controller;
|
|
unsigned long flags;
|
|
struct rpbuf_service_content_buffer_transmitted *cont = content;
|
|
struct rpbuf_buffer *buffer;
|
|
|
|
mutex_lock(&link->controller_lock);
|
|
|
|
spin_lock_irqsave(&link->lock, flags);
|
|
controller = link->controller;
|
|
if (!controller) {
|
|
dev_err(service->dev, "service 0x%px not linked with controller\n", service);
|
|
spin_unlock_irqrestore(&link->lock, flags);
|
|
mutex_unlock(&link->controller_lock);
|
|
ret = -ENOENT;
|
|
goto err_out;
|
|
}
|
|
spin_unlock_irqrestore(&link->lock, flags);
|
|
|
|
buffer = idr_find(&controller->buffers, cont->id);
|
|
if (!buffer) {
|
|
dev_warn(controller->dev, "no buffer with id %d in local\n", cont->id);
|
|
mutex_unlock(&link->controller_lock);
|
|
ret = -ENOENT;
|
|
goto err_out;
|
|
}
|
|
|
|
dev_dbg(controller->dev, "buffer \"%s\" (id:%d) received from remote\n",
|
|
buffer->name, buffer->id);
|
|
|
|
mutex_unlock(&link->controller_lock);
|
|
|
|
if (buffer->cbs && buffer->cbs->rx_cb)
|
|
buffer->cbs->rx_cb(buffer, buffer->va + cont->offset,
|
|
cont->data_len, buffer->priv);
|
|
|
|
/* tell the remote buffer that we received data */
|
|
if ((cont->flags & BUFFER_SYNC_TRANSMIT)) {
|
|
ret = rpbuf_notify_remotebuffer_complete(controller, buffer);
|
|
if (ret < 0) {
|
|
dev_warn(controller->dev, "buffer \"%s\" (id:%d) notify remote failed\n",
|
|
buffer->name, buffer->id);
|
|
ret = -EFAULT;
|
|
goto err_out;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
|
|
err_out:
|
|
return ret;
|
|
}
|
|
|
|
static int rpbuf_service_command_buffer_ack_handler(struct rpbuf_service *service,
|
|
enum rpbuf_service_command cmd,
|
|
void *content)
|
|
{
|
|
int ret;
|
|
struct rpbuf_link *link = service->link;
|
|
struct rpbuf_controller *controller;
|
|
unsigned long flags;
|
|
struct rpbuf_service_content_buffer_ack *cont = content;
|
|
struct rpbuf_buffer *buffer;
|
|
|
|
mutex_lock(&link->controller_lock);
|
|
|
|
spin_lock_irqsave(&link->lock, flags);
|
|
controller = link->controller;
|
|
if (!controller) {
|
|
dev_err(service->dev, "service 0x%px not linked with controller\n", service);
|
|
spin_unlock_irqrestore(&link->lock, flags);
|
|
mutex_unlock(&link->controller_lock);
|
|
ret = -ENOENT;
|
|
goto err_out;
|
|
}
|
|
spin_unlock_irqrestore(&link->lock, flags);
|
|
|
|
buffer = idr_find(&controller->buffers, cont->id);
|
|
if (!buffer) {
|
|
dev_warn(controller->dev, "no buffer with id %d in local\n", cont->id);
|
|
mutex_unlock(&link->controller_lock);
|
|
ret = -ENOENT;
|
|
goto err_out;
|
|
}
|
|
|
|
buffer->state |= RPBUF_FLAGS_GETNOTIFY;
|
|
|
|
dev_dbg(controller->dev, "buffer \"%s\" (id:%d) ACK from remote\n",
|
|
buffer->name, buffer->id);
|
|
|
|
mutex_unlock(&link->controller_lock);
|
|
|
|
wake_up_interruptible(&buffer->wait);
|
|
|
|
return 0;
|
|
|
|
err_out:
|
|
return ret;
|
|
}
|
|
|
|
static const rpbuf_service_command_handler_t
|
|
rpbuf_service_command_handlers[RPBUF_SERVICE_CMD_MAX] = {
|
|
NULL, /* RPBUF_SERVICE_CMD_UNKNOWN */
|
|
rpbuf_service_command_buffer_created_handler,
|
|
rpbuf_service_command_buffer_destroyed_handler,
|
|
rpbuf_service_command_buffer_transmitted_handler,
|
|
rpbuf_service_command_buffer_ack_handler,
|
|
};
|
|
|
|
int rpbuf_service_get_notification(struct rpbuf_service *service, void *msg, int msg_len)
|
|
{
|
|
int ret;
|
|
struct device *dev = service->dev;
|
|
struct rpbuf_service_message_header *header = msg;
|
|
struct rpbuf_service_message_trailer *trailer
|
|
= msg + sizeof(*header) + header->content_len;
|
|
enum rpbuf_service_command cmd;
|
|
|
|
if (sizeof(*header) + header->content_len + sizeof(*trailer) != msg_len) {
|
|
dev_err(dev, "incorrecti rpbuf service message length\n");
|
|
ret = -EINVAL;
|
|
goto err_out;
|
|
}
|
|
if (header->preamble != RPBUF_SERVICE_MESSAGE_PREAMBLE
|
|
|| trailer->postamble != RPBUF_SERVICE_MESSAGE_POSTAMBLE) {
|
|
dev_err(dev, "invalid received rpbuf service message\n");
|
|
ret = -EINVAL;
|
|
goto err_out;
|
|
}
|
|
if (header->command == RPBUF_SERVICE_CMD_UNKNOWN
|
|
|| header->command >= RPBUF_SERVICE_CMD_MAX) {
|
|
pr_err("invalid rpbuf service command\n");
|
|
return -EINVAL;
|
|
}
|
|
cmd = header->command;
|
|
if (header->content_len != rpbuf_service_message_content_len[cmd]) {
|
|
dev_err(dev, "rpbuf service message content length not match "
|
|
"(command: %d, request: %d, actual: %d)\n",
|
|
cmd, rpbuf_service_message_content_len[cmd], header->content_len);
|
|
ret = -EINVAL;
|
|
goto err_out;
|
|
}
|
|
|
|
ret = rpbuf_service_command_handlers[cmd](service, cmd, msg + sizeof(*header));
|
|
if (ret < 0) {
|
|
dev_err(dev, "rpbuf_service_command_handlers[%d] error (%d)\n",
|
|
cmd, ret);
|
|
goto err_out;
|
|
}
|
|
|
|
return 0;
|
|
err_out:
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(rpbuf_service_get_notification);
|
|
|
|
static int rpbuf_free_buffer_internal(struct rpbuf_buffer *buffer, int do_notify)
|
|
{
|
|
int ret;
|
|
struct rpbuf_controller *controller;
|
|
struct rpbuf_link *link;
|
|
struct device *dev;
|
|
enum rpbuf_role role;
|
|
struct rpbuf_buffer *dummy_buffer;
|
|
struct rpbuf_service_content_buffer_destroyed content;
|
|
|
|
if (!buffer || !buffer->controller || !buffer->controller->link) {
|
|
pr_err("invalid arguments\n");
|
|
ret = -EINVAL;
|
|
goto err_out;
|
|
}
|
|
controller = buffer->controller;
|
|
link = controller->link;
|
|
dev = controller->dev;
|
|
role = controller->role;
|
|
|
|
if (role != RPBUF_ROLE_MASTER && role != RPBUF_ROLE_SLAVE) {
|
|
dev_err(dev, "unknown rpbuf role\n");
|
|
ret = -EINVAL;
|
|
goto err_out;
|
|
}
|
|
|
|
content.id = buffer->id;
|
|
|
|
if (do_notify) {
|
|
ret = rpbuf_notify_by_link(link,
|
|
RPBUF_SERVICE_CMD_BUFFER_DESTROYED,
|
|
(void *)&content);
|
|
if (ret < 0) {
|
|
dev_err(dev, "rpbuf_notify_by_link BUFFER_DESTROYED failed: %d\n", ret);
|
|
/*
|
|
* Though notifying failed, we go ahead to release the
|
|
* local buffer resources rather than return directly.
|
|
*/
|
|
}
|
|
}
|
|
|
|
mutex_lock(&link->controller_lock);
|
|
|
|
dummy_buffer = rpbuf_find_dummy_buffer_by_name(
|
|
&controller->local_dummy_buffers, buffer->name);
|
|
if (dummy_buffer) {
|
|
/*
|
|
* A buffer with the same name is found in 'local_dummy_buffers',
|
|
* meaning that remote doesn't allocate it or has freed it.
|
|
* Here the 'dummy_buffer' and 'buffer' should be the same.
|
|
*/
|
|
|
|
if (dummy_buffer != buffer) {
|
|
dev_err(dev, "unexpected buffer ptr 0x%p, it should be 0x%p\n",
|
|
buffer, dummy_buffer);
|
|
mutex_unlock(&link->controller_lock);
|
|
ret = -EINVAL;
|
|
goto err_out;
|
|
}
|
|
|
|
list_del(&buffer->dummy_list);
|
|
|
|
if (buffer->allocated) {
|
|
rpbuf_free_buffer_payload_memory(controller, buffer);
|
|
buffer->allocated = false;
|
|
}
|
|
|
|
dev_dbg(dev, "buffer \"%s\": local_dummy_buffers -> NULL\n",
|
|
buffer->name);
|
|
} else {
|
|
/*
|
|
* If not found, meaning that remote has allocated it. Now it
|
|
* should be stored in 'buffers' and we should move it
|
|
* to 'remote_dummy_buffers'.
|
|
*/
|
|
|
|
dummy_buffer = idr_find(&controller->buffers, buffer->id);
|
|
idr_remove(&controller->buffers, buffer->id);
|
|
if (!dummy_buffer) {
|
|
dev_err(dev, "buffer not found, maybe it is not allocated?\n");
|
|
mutex_unlock(&link->controller_lock);
|
|
ret = -EINVAL;
|
|
goto err_out;
|
|
} else if (dummy_buffer != buffer) {
|
|
dev_err(dev, "unexpected buffer ptr 0x%p, it should be 0x%p\n",
|
|
buffer, dummy_buffer);
|
|
mutex_unlock(&link->controller_lock);
|
|
ret = -EINVAL;
|
|
goto err_out;
|
|
}
|
|
|
|
dummy_buffer = rpbuf_find_dummy_buffer_by_name(
|
|
&controller->remote_dummy_buffers, buffer->name);
|
|
if (dummy_buffer) {
|
|
/*
|
|
* In this situation, if a buffer with the same name
|
|
* already exists in 'remote_dummy_buffers', it is an
|
|
* unexpected state. Anyway, we overwrite the previous
|
|
* information.
|
|
*/
|
|
dev_info(dev, "buffer \"%s\": (unexpected) "
|
|
"overwrite information in 'remote_dummy_buffers'\n",
|
|
buffer->name);
|
|
strncpy(dummy_buffer->name, buffer->name, RPBUF_NAME_SIZE);
|
|
dummy_buffer->len = buffer->len;
|
|
dummy_buffer->id = buffer->id;
|
|
dummy_buffer->pa = buffer->pa;
|
|
dummy_buffer->da = buffer->da;
|
|
dummy_buffer->allocated = buffer->allocated;
|
|
} else {
|
|
/*
|
|
* The instance of 'buffer' will be freed later, so we
|
|
* cannot directly add it to 'remote_dummy_buffers'.
|
|
* Instead, we allocate a new instance and copy 'buffer'
|
|
* information to it.
|
|
*/
|
|
dummy_buffer = rpbuf_alloc_buffer_instance(dev,
|
|
buffer->name,
|
|
buffer->len);
|
|
if (!dummy_buffer) {
|
|
dev_err(dev, "rpbuf_alloc_buffer_instance failed\n");
|
|
mutex_unlock(&link->controller_lock);
|
|
ret = -ENOMEM;
|
|
goto err_out;
|
|
}
|
|
dummy_buffer->id = buffer->id;
|
|
dummy_buffer->pa = buffer->pa;
|
|
dummy_buffer->da = buffer->da;
|
|
dummy_buffer->va = buffer->va;
|
|
dummy_buffer->allocated = buffer->allocated;
|
|
list_add_tail(&dummy_buffer->dummy_list,
|
|
&controller->remote_dummy_buffers);
|
|
|
|
dev_dbg(dev, "buffer \"%s\" (id:%d): buffers -> remote_dummy_buffers\n",
|
|
buffer->name, buffer->id);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Anyway MASTER frees payload memory here. Therefore users should ensure
|
|
* that SLAVE won't access the payload memory afterwards.
|
|
*/
|
|
if (role == RPBUF_ROLE_SLAVE)
|
|
idr_remove(&controller->local_buffers, buffer->id);
|
|
|
|
mutex_unlock(&link->controller_lock);
|
|
|
|
rpbuf_free_buffer_instance(dev, buffer);
|
|
|
|
return 0;
|
|
err_out:
|
|
return ret;
|
|
}
|
|
|
|
struct rpbuf_buffer *rpbuf_alloc_buffer(struct rpbuf_controller *controller,
|
|
const char *name, int len,
|
|
const struct rpbuf_buffer_ops *ops,
|
|
const struct rpbuf_buffer_cbs *cbs,
|
|
void *priv)
|
|
{
|
|
int ret;
|
|
struct device *dev;
|
|
struct rpbuf_link *link;
|
|
enum rpbuf_role role;
|
|
struct rpbuf_buffer *buffer;
|
|
struct rpbuf_buffer *dummy_buffer;
|
|
int id;
|
|
struct rpbuf_service_content_buffer_created content;
|
|
int is_available = 0;
|
|
|
|
if (!controller || !controller->link) {
|
|
pr_err("(%s:%d) invalid arguments (%p, %p)\n", __func__, __LINE__,
|
|
controller, controller->link);
|
|
goto err_out;
|
|
}
|
|
dev = controller->dev;
|
|
link = controller->link;
|
|
role = controller->role;
|
|
|
|
if (role != RPBUF_ROLE_MASTER && role != RPBUF_ROLE_SLAVE) {
|
|
dev_err(dev, "unknown rpbuf role\n");
|
|
goto err_out;
|
|
}
|
|
|
|
buffer = rpbuf_alloc_buffer_instance(dev, name, len);
|
|
if (!buffer) {
|
|
dev_err(dev, "rpbuf_alloc_buffer_instance failed\n");
|
|
goto err_out;
|
|
}
|
|
buffer->controller = controller;
|
|
buffer->ops = ops;
|
|
buffer->cbs = cbs;
|
|
buffer->priv = priv;
|
|
buffer->allocated = false;
|
|
|
|
mutex_lock(&link->controller_lock);
|
|
|
|
dummy_buffer = rpbuf_find_dummy_buffer_by_name(
|
|
&controller->local_dummy_buffers, name);
|
|
if (dummy_buffer) {
|
|
dev_err(dev, "buffer \"%s\" already exists in 'local_dummy_buffers'\n", name);
|
|
mutex_unlock(&link->controller_lock);
|
|
goto err_free_instance;
|
|
}
|
|
idr_for_each_entry(&controller->buffers, dummy_buffer, id) {
|
|
if (0 == strncmp(dummy_buffer->name, name, RPBUF_NAME_SIZE)) {
|
|
dev_err(dev, "buffer \"%s\" already exists in 'buffers'\n", name);
|
|
mutex_unlock(&link->controller_lock);
|
|
goto err_free_instance;
|
|
}
|
|
}
|
|
|
|
dummy_buffer = rpbuf_find_dummy_buffer_by_name(
|
|
&controller->remote_dummy_buffers, name);
|
|
if (dummy_buffer) {
|
|
/*
|
|
* A buffer with the same name is found in 'remote_dummy_buffers',
|
|
* meaning that remote has allocated it and notified local.
|
|
* Now we move it to 'buffers'.
|
|
*/
|
|
|
|
if (buffer->len != dummy_buffer->len) {
|
|
dev_err(dev, "buffer length not match with remote "
|
|
"(local: %d, remote: %d)\n",
|
|
buffer->len, dummy_buffer->len);
|
|
mutex_unlock(&link->controller_lock);
|
|
goto err_free_instance;
|
|
}
|
|
|
|
if (role == RPBUF_ROLE_SLAVE) {
|
|
buffer->pa = dummy_buffer->pa;
|
|
buffer->da = dummy_buffer->da;
|
|
buffer->va = rpbuf_addr_remap(controller, buffer,
|
|
buffer->pa, buffer->da,
|
|
buffer->len);
|
|
|
|
id = idr_alloc(&controller->local_buffers, buffer, 0, 0, GFP_KERNEL);
|
|
if (id < 0) {
|
|
dev_err(dev, "idr_alloc for new id failed: %d\n", id);
|
|
mutex_unlock(&link->controller_lock);
|
|
goto err_free_instance;
|
|
}
|
|
buffer->id = id;
|
|
|
|
id = idr_alloc(&controller->buffers, buffer, id, id + 1, GFP_KERNEL);
|
|
if (id < 0) {
|
|
dev_err(dev, "idr_alloc for new id %d failed: %d\n", dummy_buffer->id, id);
|
|
idr_remove(&controller->local_buffers, buffer->id);
|
|
mutex_unlock(&link->controller_lock);
|
|
goto err_free_instance;
|
|
}
|
|
} else {
|
|
id = idr_alloc(&controller->buffers, buffer, dummy_buffer->id,
|
|
dummy_buffer->id + 1, GFP_KERNEL);
|
|
if (id < 0) {
|
|
dev_err(dev, "idr_alloc for new id %d failed: %d\n", dummy_buffer->id, id);
|
|
mutex_unlock(&link->controller_lock);
|
|
goto err_free_instance;
|
|
}
|
|
buffer->id = id;
|
|
|
|
if (!dummy_buffer->allocated) {
|
|
ret = rpbuf_alloc_buffer_payload_memory(controller, buffer);
|
|
if (ret < 0) {
|
|
dev_err(dev, "rpbuf_alloc_buffer_payload_memory failed\n");
|
|
mutex_unlock(&link->controller_lock);
|
|
idr_remove(&controller->buffers, buffer->id);
|
|
goto err_free_instance;
|
|
}
|
|
dummy_buffer->allocated = true;
|
|
} else {
|
|
buffer->pa = dummy_buffer->pa;
|
|
buffer->da = dummy_buffer->da;
|
|
buffer->va = dummy_buffer->va;
|
|
}
|
|
buffer->allocated = dummy_buffer->allocated;
|
|
}
|
|
|
|
/*
|
|
* The instances of entries in 'remote_dummy_buffers' are not
|
|
* allocated/freed by rpbuf_alloc_buffer()/rpbuf_free_buffer().
|
|
* After deleting from the list, we should free the instance.
|
|
*/
|
|
list_del(&dummy_buffer->dummy_list);
|
|
rpbuf_free_buffer_instance(dev, dummy_buffer);
|
|
|
|
dev_dbg(dev, "buffer \"%s\" (id:%d): remote_dummy_buffers -> buffers\n",
|
|
buffer->name, buffer->id);
|
|
is_available = 1;
|
|
} else {
|
|
/*
|
|
* If not found, add this buffer to 'local_dummy_buffers',
|
|
* waiting for remote message to update it.
|
|
*/
|
|
list_add_tail(&buffer->dummy_list, &controller->local_dummy_buffers);
|
|
|
|
/*
|
|
* buffer->id is determined by slave, because the slave may run on
|
|
* bare metal and there no idr mechanism.
|
|
*/
|
|
if (role == RPBUF_ROLE_SLAVE) {
|
|
id = idr_alloc(&controller->local_buffers, buffer, 0, 0, GFP_KERNEL);
|
|
if (id < 0) {
|
|
dev_err(dev, "idr_alloc for new id failed: %d\n", id);
|
|
mutex_unlock(&link->controller_lock);
|
|
goto err_free_instance;
|
|
}
|
|
buffer->id = id;
|
|
} else {
|
|
ret = rpbuf_alloc_buffer_payload_memory(controller, buffer);
|
|
if (ret < 0) {
|
|
dev_err(dev, "rpbuf_alloc_buffer_payload_memory failed\n");
|
|
mutex_unlock(&link->controller_lock);
|
|
goto err_free_instance;
|
|
}
|
|
buffer->allocated = true;
|
|
}
|
|
dev_dbg(dev, "buffer \"%s\": NULL -> local_dummy_buffers\n",
|
|
buffer->name);
|
|
}
|
|
|
|
mutex_unlock(&link->controller_lock);
|
|
|
|
/* Copy buffer information to rpbuf message content */
|
|
strncpy(content.name, buffer->name, RPBUF_NAME_SIZE);
|
|
content.id = buffer->id;
|
|
content.len = buffer->len;
|
|
content.pa = buffer->pa;
|
|
content.da = buffer->da;
|
|
content.is_available = is_available;
|
|
|
|
/* Notify remote */
|
|
ret = rpbuf_notify_by_link(link,
|
|
RPBUF_SERVICE_CMD_BUFFER_CREATED,
|
|
(void *)&content);
|
|
if (ret < 0) {
|
|
dev_err(dev, "rpbuf_notify_by_link BUFFER_CREATED failed: %d\n", ret);
|
|
/* Free allocated resource, without sending BUFFER_DESTROYED to remote */
|
|
rpbuf_free_buffer_internal(buffer, 0);
|
|
goto err_out;
|
|
}
|
|
|
|
if (is_available && buffer->cbs && buffer->cbs->available_cb)
|
|
buffer->cbs->available_cb(buffer, buffer->priv);
|
|
|
|
return buffer;
|
|
|
|
err_free_instance:
|
|
rpbuf_free_buffer_instance(dev, buffer);
|
|
err_out:
|
|
return NULL;
|
|
}
|
|
EXPORT_SYMBOL(rpbuf_alloc_buffer);
|
|
|
|
int rpbuf_free_buffer(struct rpbuf_buffer *buffer)
|
|
{
|
|
return rpbuf_free_buffer_internal(buffer, 1);
|
|
}
|
|
EXPORT_SYMBOL(rpbuf_free_buffer);
|
|
|
|
int rpbuf_buffer_set_sync(struct rpbuf_buffer *buffer, bool sync)
|
|
{
|
|
uint32_t flags = buffer->flags;
|
|
|
|
if (sync)
|
|
flags |= BUFFER_SYNC_TRANSMIT;
|
|
else
|
|
flags &= ~(BUFFER_SYNC_TRANSMIT);
|
|
|
|
buffer->flags = flags;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int rpbuf_buffer_is_available(struct rpbuf_buffer *buffer)
|
|
{
|
|
struct rpbuf_controller *controller;
|
|
struct rpbuf_link *link;
|
|
struct rpbuf_buffer *tmp_buffer;
|
|
|
|
if (!buffer || !buffer->controller || !buffer->controller->link) {
|
|
pr_err("invalid arguments\n");
|
|
return 0;
|
|
}
|
|
controller = buffer->controller;
|
|
link = controller->link;
|
|
|
|
mutex_lock(&link->controller_lock);
|
|
|
|
tmp_buffer = idr_find(&controller->buffers, buffer->id);
|
|
if (!tmp_buffer) {
|
|
mutex_unlock(&link->controller_lock);
|
|
return 0;
|
|
} else if (tmp_buffer != buffer) {
|
|
dev_err(controller->dev, "unexpected buffer ptr 0x%p, it should be 0x%p\n",
|
|
buffer, tmp_buffer);
|
|
mutex_unlock(&link->controller_lock);
|
|
return 0;
|
|
}
|
|
|
|
mutex_unlock(&link->controller_lock);
|
|
|
|
return 1;
|
|
}
|
|
EXPORT_SYMBOL(rpbuf_buffer_is_available);
|
|
|
|
int rpbuf_transmit_buffer(struct rpbuf_buffer *buffer,
|
|
unsigned int offset, unsigned int data_len)
|
|
{
|
|
int ret;
|
|
struct rpbuf_controller *controller;
|
|
struct rpbuf_link *link;
|
|
struct device *dev;
|
|
struct rpbuf_service_content_buffer_transmitted content;
|
|
|
|
if (!buffer || !buffer->controller || !buffer->controller->link) {
|
|
pr_err("invalid arguments\n");
|
|
return -EINVAL;
|
|
}
|
|
controller = buffer->controller;
|
|
link = controller->link;
|
|
dev = controller->dev;
|
|
|
|
if (offset + data_len > buffer->len) {
|
|
dev_err(dev, "data (offset + len: %u + %u) over the buffer range (len: %d)\n",
|
|
offset, data_len, buffer->len);
|
|
return -EINVAL;
|
|
}
|
|
|
|
content.id = buffer->id;
|
|
content.offset = offset;
|
|
content.data_len = data_len;
|
|
content.flags = buffer->flags;
|
|
|
|
if (!rpbuf_buffer_is_available(buffer)) {
|
|
dev_err(dev, "buffer not available\n");
|
|
return -EACCES;
|
|
}
|
|
|
|
/* clear notify bit */
|
|
if ((buffer->flags & BUFFER_SYNC_TRANSMIT))
|
|
buffer->state &= ~RPBUF_FLAGS_GETNOTIFY;
|
|
|
|
ret = rpbuf_notify_by_link(link, RPBUF_SERVICE_CMD_BUFFER_TRANSMITTED,
|
|
(void *)&content);
|
|
if (ret < 0) {
|
|
dev_err(dev, "rpbuf_notify_by_link BUFFER_TRANSMITTED failed: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
if ((buffer->flags & BUFFER_SYNC_TRANSMIT)) {
|
|
ret = rpbuf_wait_for_remotebuffer_complete(controller, buffer);
|
|
if (ret < 0) {
|
|
dev_err(dev, "rpbuf_wait_for_remotebuffer_complete BUFFER_ACK failed: %d\n", ret);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(rpbuf_transmit_buffer);
|
|
|
|
const char *rpbuf_buffer_name(struct rpbuf_buffer *buffer)
|
|
{
|
|
return buffer->name;
|
|
}
|
|
EXPORT_SYMBOL(rpbuf_buffer_name);
|
|
|
|
int rpbuf_buffer_id(struct rpbuf_buffer *buffer)
|
|
{
|
|
return buffer->id;
|
|
}
|
|
EXPORT_SYMBOL(rpbuf_buffer_id);
|
|
|
|
void *rpbuf_buffer_va(struct rpbuf_buffer *buffer)
|
|
{
|
|
return buffer->va;
|
|
}
|
|
EXPORT_SYMBOL(rpbuf_buffer_va);
|
|
|
|
phys_addr_t rpbuf_buffer_pa(struct rpbuf_buffer *buffer)
|
|
{
|
|
return buffer->pa;
|
|
}
|
|
EXPORT_SYMBOL(rpbuf_buffer_pa);
|
|
|
|
u64 rpbuf_buffer_da(struct rpbuf_buffer *buffer)
|
|
{
|
|
return buffer->da;
|
|
}
|
|
EXPORT_SYMBOL(rpbuf_buffer_da);
|
|
|
|
int rpbuf_buffer_len(struct rpbuf_buffer *buffer)
|
|
{
|
|
return buffer->len;
|
|
}
|
|
EXPORT_SYMBOL(rpbuf_buffer_len);
|
|
|
|
void *rpbuf_buffer_priv(struct rpbuf_buffer *buffer)
|
|
{
|
|
return buffer->priv;
|
|
}
|
|
EXPORT_SYMBOL(rpbuf_buffer_priv);
|
|
|
|
void rpbuf_buffer_set_va(struct rpbuf_buffer *buffer, void *va)
|
|
{
|
|
buffer->va = va;
|
|
}
|
|
EXPORT_SYMBOL(rpbuf_buffer_set_va);
|
|
|
|
void rpbuf_buffer_set_pa(struct rpbuf_buffer *buffer, phys_addr_t pa)
|
|
{
|
|
buffer->pa = pa;
|
|
}
|
|
EXPORT_SYMBOL(rpbuf_buffer_set_pa);
|
|
|
|
void rpbuf_buffer_set_da(struct rpbuf_buffer *buffer, u64 da)
|
|
{
|
|
buffer->da = da;
|
|
}
|
|
EXPORT_SYMBOL(rpbuf_buffer_set_da);
|
|
|
|
MODULE_DESCRIPTION("RPBuf core");
|
|
MODULE_AUTHOR("Junyan Lin <junyanlin@allwinnertech.com>");
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_VERSION("1.0.0");
|