* drivers/rpmsg/rpmsg_char.c
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <assert.h>
#include <debug.h>
#include <fcntl.h>
#include <poll.h>
#include <sys/param.h>
#include <nuttx/compiler.h>
#include <nuttx/kmalloc.h>
#include <nuttx/list.h>
#include <nuttx/mutex.h>
#include <nuttx/nuttx.h>
#include <nuttx/rpmsg/rpmsg.h>
#include <nuttx/semaphore.h>
#include "rpmsg.h"
* Private Types
****************************************************************************/
struct rpmsg_eptdev_s
{
struct list_node node;
FAR struct rpmsg_char_s *chr;
struct rpmsg_endpoint ept;
struct rpmsg_endpoint_info info;
struct list_node rxbuf;
spinlock_t lock;
sem_t wait;
sem_t rxsem;
FAR struct pollfd *fds[CONFIG_RPMSG_CHAR_NPOLLWAITERS];
bool created;
};
struct rpmsg_eptdev_buf_s
{
struct list_node node;
size_t len;
FAR uint8_t data[1];
};
* Private Function Prototypes
****************************************************************************/
static int rpmsg_eptdev_open(FAR struct file *filep);
static int rpmsg_eptdev_close(FAR struct file *filep);
static ssize_t rpmsg_eptdev_read(FAR struct file *filep, FAR char *buffer,
size_t buflen);
static ssize_t rpmsg_eptdev_write(FAR struct file *filep,
FAR const char *buffer, size_t buflen);
static int rpmsg_eptdev_ioctl(FAR struct file *filep, int cmd,
unsigned long arg);
static int rpmsg_eptdev_poll(FAR struct file *filep, FAR struct pollfd *fds,
bool setup);
static void rpmsg_eptdev_device_created(FAR struct rpmsg_device *rdev,
FAR void *priv);
static void rpmsg_eptdev_device_destroy(FAR struct rpmsg_device *rdev,
FAR void *priv);
* Private data
****************************************************************************/
static const struct file_operations g_rpmsg_eptdev_fops =
{
rpmsg_eptdev_open,
rpmsg_eptdev_close,
rpmsg_eptdev_read,
rpmsg_eptdev_write,
NULL,
rpmsg_eptdev_ioctl,
NULL,
NULL,
rpmsg_eptdev_poll,
};
* Private Functions
****************************************************************************/
* Name: rpmsg_char_get_rdev
****************************************************************************/
static inline_function FAR struct rpmsg_device *
rpmsg_char_get_rdev(FAR struct rpmsg_char_s *chr)
{
return rpmsg_get_rdev_by_rpmsg(container_of(chr, struct rpmsg_s, chr));
}
* Name: rpmsg_char_add_eptdev
****************************************************************************/
static inline_function void
rpmsg_char_add_eptdev(FAR struct rpmsg_char_s *chr,
FAR struct rpmsg_eptdev_s *eptdev)
{
nxrmutex_lock(&chr->lock);
list_add_tail(&chr->eptdevs, &eptdev->node);
nxrmutex_unlock(&chr->lock);
}
* Name: rpmsg_char_del_eptdev
****************************************************************************/
static inline_function void
rpmsg_char_del_eptdev(FAR struct rpmsg_char_s *chr,
FAR struct rpmsg_eptdev_s *eptdev)
{
nxrmutex_lock(&chr->lock);
if (list_in_list(&eptdev->node))
{
list_delete(&eptdev->node);
}
nxrmutex_unlock(&chr->lock);
}
* Name: rpmsg_char_in_eptdev_list
****************************************************************************/
static inline_function bool
rpmsg_char_in_eptdev_list(FAR struct rpmsg_char_s *chr,
FAR struct rpmsg_eptdev_s *eptdev)
{
bool inlist;
nxrmutex_lock(&chr->lock);
inlist = list_in_list(&eptdev->node);
nxrmutex_unlock(&chr->lock);
return inlist;
}
* Name: rpmsg_char_find_eptdev
****************************************************************************/
static FAR struct rpmsg_eptdev_s *
rpmsg_char_find_eptdev(FAR struct rpmsg_char_s *chr,
FAR const struct rpmsg_endpoint_info *info)
{
FAR struct rpmsg_eptdev_s *eptdev;
bool found = false;
nxrmutex_lock(&chr->lock);
list_for_every_entry(&chr->eptdevs, eptdev, struct rpmsg_eptdev_s, node)
{
if (strcmp(eptdev->info.name, info->name) == 0 &&
eptdev->info.src == info->src && eptdev->info.dst == info->dst)
{
found = true;
break;
}
}
nxrmutex_unlock(&chr->lock);
return found ? eptdev : NULL;
}
* Name: rpmsg_eptdev_wakeup_rx
****************************************************************************/
static inline_function void
rpmsg_eptdev_wakeup_rx(FAR struct rpmsg_eptdev_s *eptdev)
{
int val;
nxsem_get_value(&eptdev->rxsem, &val);
while (val++ < 1)
{
nxsem_post(&eptdev->rxsem);
}
}
* Name: rpmsg_eptdev_destroy
****************************************************************************/
static inline_function void
rpmsg_eptdev_destroy(FAR struct rpmsg_eptdev_s *eptdev)
{
if (rpmsg_char_in_eptdev_list(eptdev->chr, eptdev))
{
rpmsg_unregister_callback(eptdev, rpmsg_eptdev_device_created,
rpmsg_eptdev_device_destroy,
NULL, NULL);
}
}
* Name: rpmsg_eptdev_open
****************************************************************************/
static int rpmsg_eptdev_open(FAR struct file *filep)
{
FAR struct rpmsg_eptdev_s *eptdev;
DEBUGASSERT(filep != NULL && filep->f_inode != NULL);
eptdev = filep->f_inode->i_private;
filep->f_priv = eptdev;
rpmsg_ept_incref(&eptdev->ept);
return OK;
}
* Name: rpmsg_eptdev_close
****************************************************************************/
static int rpmsg_eptdev_close(FAR struct file *filep)
{
FAR struct rpmsg_eptdev_s *eptdev = filep->f_priv;
rpmsg_ept_decref(&eptdev->ept);
return OK;
}
* Name: rpmsg_eptdev_read
****************************************************************************/
static ssize_t rpmsg_eptdev_read(FAR struct file *filep, FAR char *buffer,
size_t buflen)
{
FAR struct rpmsg_eptdev_buf_s *buf;
FAR struct rpmsg_eptdev_s *eptdev = filep->f_priv;
irqstate_t flags;
ssize_t ret = 0;
int val;
flags = spin_lock_irqsave(&eptdev->lock);
while (list_is_empty(&eptdev->rxbuf))
{
if (filep->f_oflags & O_NONBLOCK)
{
ret = -EAGAIN;
break;
}
spin_unlock_irqrestore(&eptdev->lock, flags);
ret = nxsem_wait(&eptdev->rxsem);
flags = spin_lock_irqsave(&eptdev->lock);
nxsem_get_value(&eptdev->wait, &val);
if (val <= 0)
{
ret = -ECONNRESET;
}
if (ret < 0)
{
break;
}
}
buf = list_remove_head_type(&eptdev->rxbuf, struct rpmsg_eptdev_buf_s,
node);
spin_unlock_irqrestore(&eptdev->lock, flags);
if (buf != NULL)
{
ret = MIN(buflen, buf->len);
memcpy(buffer, buf->data, ret);
kmm_free(buf);
}
return ret;
}
* Name: rpmsg_eptdev_write
****************************************************************************/
static ssize_t rpmsg_eptdev_write(FAR struct file *filep,
FAR const char *buffer, size_t buflen)
{
FAR struct rpmsg_eptdev_s *eptdev = filep->f_priv;
int ret = OK;
if (filep->f_oflags & O_NONBLOCK)
{
ret = rpmsg_trysend(&eptdev->ept, buffer, buflen);
if (ret < 0)
{
ret = -EAGAIN;
}
}
else
{
int semcount;
nxsem_get_value(&eptdev->wait, &semcount);
if (semcount <= 0)
{
rpmsg_wait(&eptdev->ept, &eptdev->wait);
rpmsg_post(&eptdev->ept, &eptdev->wait);
}
ret = rpmsg_send(&eptdev->ept, buffer, buflen);
}
return ret;
}
* Name: rpmsg_eptdev_ioctl
****************************************************************************/
static int rpmsg_eptdev_ioctl(FAR struct file *filep, int cmd,
unsigned long arg)
{
FAR struct rpmsg_eptdev_s *eptdev = filep->f_priv;
int ret = OK;
switch (cmd)
{
case RPMSG_DESTROY_EPT_IOCTL:
rpmsg_eptdev_destroy(eptdev);
break;
default:
ret = -ENOTTY;
break;
}
return ret;
}
* Name: rpmsg_eptdev_poll
****************************************************************************/
static int rpmsg_eptdev_poll(FAR struct file *filep, FAR struct pollfd *fds,
bool setup)
{
FAR struct rpmsg_eptdev_s *eptdev = filep->f_priv;
pollevent_t eventset = 0;
irqstate_t flags;
int ret = OK;
int i;
flags = spin_lock_irqsave_nopreempt(&eptdev->lock);
if (setup)
{
for (i = 0; i < CONFIG_RPMSG_CHAR_NPOLLWAITERS; i++)
{
if (eptdev->fds[i] == NULL)
{
eptdev->fds[i] = fds;
fds->priv = &eptdev->fds[i];
break;
}
}
if (i < CONFIG_RPMSG_CHAR_NPOLLWAITERS)
{
if (!list_is_empty(&eptdev->rxbuf))
{
eventset |= POLLIN;
}
eventset |= POLLOUT;
poll_notify(&fds, 1, eventset);
}
else
{
ret = -EBUSY;
}
}
else if (fds->priv != NULL)
{
FAR struct pollfd **slot = (FAR struct pollfd **)fds->priv;
if (slot != NULL)
{
*slot = NULL;
fds->priv = NULL;
}
else
{
ret = -EIO;
}
}
spin_unlock_irqrestore_nopreempt(&eptdev->lock, flags);
return ret;
}
* Name: rpmsg_eptdev_release_cb
****************************************************************************/
static void rpmsg_eptdev_release_cb(FAR struct rpmsg_endpoint *ept)
{
FAR struct rpmsg_eptdev_s *eptdev =
container_of(ept, struct rpmsg_eptdev_s, ept);
rpmsg_char_del_eptdev(eptdev->chr, eptdev);
kmm_free(eptdev);
}
* Name: rpmsg_eptdev_ns_bound_cb
****************************************************************************/
static void rpmsg_eptdev_ns_bound_cb(FAR struct rpmsg_endpoint *ept)
{
FAR struct rpmsg_eptdev_s *eptdev =
container_of(ept, struct rpmsg_eptdev_s, ept);
nxsem_post(&eptdev->wait);
}
* Name: rpmsg_eptdev_ns_unbind_cb
****************************************************************************/
static void rpmsg_eptdev_ns_unbind_cb(FAR struct rpmsg_endpoint *ept)
{
FAR struct rpmsg_eptdev_s *eptdev =
container_of(ept, struct rpmsg_eptdev_s, ept);
irqstate_t flags;
nxsem_wait(&eptdev->wait);
flags = spin_lock_irqsave_nopreempt(&eptdev->lock);
rpmsg_eptdev_wakeup_rx(eptdev);
poll_notify(eptdev->fds, CONFIG_RPMSG_CHAR_NPOLLWAITERS, POLLIN | POLLHUP);
spin_unlock_irqrestore_nopreempt(&eptdev->lock, flags);
}
* Name: rpmsg_eptdev_ept_cb
****************************************************************************/
static int rpmsg_eptdev_ept_cb(FAR struct rpmsg_endpoint *ept,
FAR void *data, size_t len, uint32_t src,
FAR void *priv)
{
FAR struct rpmsg_eptdev_s *eptdev = (FAR struct rpmsg_eptdev_s *)priv;
FAR struct rpmsg_eptdev_buf_s *buf;
irqstate_t flags;
int ret = OK;
buf = kmm_malloc(sizeof(*buf) + len - 1);
if (buf != NULL)
{
buf->len = len;
memcpy(buf->data, data, len);
flags = spin_lock_irqsave_nopreempt(&eptdev->lock);
list_add_tail(&eptdev->rxbuf, &buf->node);
rpmsg_eptdev_wakeup_rx(eptdev);
poll_notify(eptdev->fds, CONFIG_RPMSG_CHAR_NPOLLWAITERS, POLLIN);
spin_unlock_irqrestore_nopreempt(&eptdev->lock, flags);
}
else
{
rpmsgerr("Failed to allocate buffer\n");
ret = -ENOMEM;
}
return ret;
}
* Name: rpmsg_eptdev_device_created
****************************************************************************/
static void rpmsg_eptdev_device_created(FAR struct rpmsg_device *rdev,
FAR void *priv)
{
FAR struct rpmsg_eptdev_s *eptdev = (FAR struct rpmsg_eptdev_s *)priv;
FAR struct rpmsg_endpoint_info *info = &eptdev->info;
FAR struct rpmsg_char_s *chr = eptdev->chr;
char path[PATH_MAX];
int ret;
nxrmutex_lock(&chr->lock);
if (rpmsg_char_get_rdev(chr) == rdev)
{
eptdev->ept.priv = eptdev;
eptdev->ept.release_cb = rpmsg_eptdev_release_cb;
eptdev->ept.ns_bound_cb = rpmsg_eptdev_ns_bound_cb;
ret = rpmsg_create_ept(&eptdev->ept, rdev, info->name, info->src,
info->dst, rpmsg_eptdev_ept_cb,
rpmsg_eptdev_ns_unbind_cb);
if (ret >= 0)
{
snprintf(path, PATH_MAX, "/dev/rpmsg-%s", info->name);
ret = register_driver(path, &g_rpmsg_eptdev_fops, 0666, eptdev);
if (ret >= 0)
{
eptdev->created = true;
}
else
{
rpmsgerr("Failed to register driver %s %d\n", path, ret);
rpmsg_destroy_ept(&eptdev->ept);
}
}
else
{
rpmsgerr("Failed to create endpoint %s %d\n", info->name, ret);
}
}
nxrmutex_unlock(&chr->lock);
}
* Name: rpmsg_eptdev_device_destroy
****************************************************************************/
static void rpmsg_eptdev_device_destroy(FAR struct rpmsg_device *rdev,
FAR void *priv)
{
FAR struct rpmsg_eptdev_s *eptdev = (FAR struct rpmsg_eptdev_s *)priv;
FAR struct rpmsg_char_s *chr = eptdev->chr;
char path[PATH_MAX];
nxrmutex_lock(&chr->lock);
if (rpmsg_char_get_rdev(chr) == rdev && eptdev->created)
{
eptdev->created = false;
snprintf(path, PATH_MAX, "/dev/rpmsg-%s", eptdev->info.name);
unregister_driver(path);
rpmsg_destroy_ept(&eptdev->ept);
}
nxrmutex_unlock(&chr->lock);
}
* Public Functions
****************************************************************************/
* Name: rpmsg_char_create_eptdev
****************************************************************************/
int rpmsg_char_create_eptdev(FAR struct rpmsg_char_s *chr,
FAR const struct rpmsg_endpoint_info *info)
{
FAR struct rpmsg_eptdev_s *eptdev;
int ret = -EEXIST;
if (rpmsg_char_find_eptdev(chr, info) == NULL)
{
eptdev = kmm_zalloc(sizeof(*eptdev));
if (eptdev != NULL)
{
eptdev->chr = chr;
if (info->src == RPMSG_ADDR_ANY && info->dst == RPMSG_ADDR_ANY)
{
nxsem_init(&eptdev->wait, 0, 0);
}
else
{
nxsem_init(&eptdev->wait, 0, 1);
}
nxsem_init(&eptdev->rxsem, 0, 0);
spin_lock_init(&eptdev->lock);
list_initialize(&eptdev->rxbuf);
memcpy(&eptdev->info, info, sizeof(*info));
rpmsg_char_add_eptdev(chr, eptdev);
ret = rpmsg_register_callback(eptdev, rpmsg_eptdev_device_created,
rpmsg_eptdev_device_destroy,
NULL, NULL);
if (ret < 0)
{
rpmsg_char_del_eptdev(chr, eptdev);
nxsem_destroy(&eptdev->rxsem);
nxsem_destroy(&eptdev->wait);
kmm_free(eptdev);
}
}
else
{
ret = -ENOMEM;
}
}
return ret;
}
* Name: rpmsg_char_destroy_eptdev
****************************************************************************/
void rpmsg_char_destroy_eptdev(FAR struct rpmsg_char_s *chr,
FAR const struct rpmsg_endpoint_info *info)
{
FAR struct rpmsg_eptdev_s *eptdev;
eptdev = rpmsg_char_find_eptdev(chr, info);
if (eptdev != NULL)
{
rpmsg_eptdev_destroy(eptdev);
}
else
{
rpmsgwarn("No such endpoint %s\n", info->name);
}
}
* Name: rpmsg_char_init
****************************************************************************/
void rpmsg_char_init(FAR struct rpmsg_char_s *chr)
{
nxrmutex_init(&chr->lock);
list_initialize(&chr->eptdevs);
}
* Name: rpmsg_char_deinit
****************************************************************************/
void rpmsg_char_deinit(FAR struct rpmsg_char_s *chr)
{
FAR struct rpmsg_eptdev_s *eptdev;
FAR struct rpmsg_eptdev_s *tmp;
nxrmutex_lock(&chr->lock);
list_for_every_entry_safe(&chr->eptdevs, eptdev, tmp,
struct rpmsg_eptdev_s, node)
{
rpmsg_eptdev_destroy(eptdev);
}
nxrmutex_unlock(&chr->lock);
nxrmutex_destroy(&chr->lock);
}