822 lines
19 KiB
C
822 lines
19 KiB
C
/* SPDX-License-Identifier: GPL-2.0 */
|
|
/*
|
|
* sunxi's rpmsg ctrl driver
|
|
*
|
|
* the driver register the rpmsg_ctrl device node,which
|
|
* controls the creation and release of rpmsg device nodes.
|
|
*
|
|
* Copyright (C) 2022 Allwinnertech - All Rights Reserved
|
|
*
|
|
* Author: lijiajian <lijiajian@allwinnertech.com>
|
|
*
|
|
* 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.
|
|
*/
|
|
#include <linux/cdev.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/device.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/idr.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/poll.h>
|
|
#include <linux/rpmsg.h>
|
|
#include <linux/skbuff.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/jiffies.h>
|
|
#include <uapi/linux/rpmsg.h>
|
|
|
|
#include "rpmsg_internal.h"
|
|
#include "rpmsg_master.h"
|
|
|
|
#define WAIT_TIMEOUT (msecs_to_jiffies(500))
|
|
|
|
static dev_t rpmsg_major;
|
|
static struct class *rpmsg_class;
|
|
|
|
static DEFINE_MUTEX(g_list_lock);
|
|
static LIST_HEAD(g_rpmsg_ctrldev_list);
|
|
|
|
static DEFINE_IDA(rpmsg_ctrl_ida);
|
|
static DEFINE_IDA(rpmsg_ept_ida);
|
|
static DEFINE_IDA(rpmsg_minor_ida);
|
|
|
|
#define dev_to_ctrldev(dev) container_of(dev, struct rpmsg_ctrldev, dev)
|
|
#define cdev_to_ctrldev(i_cdev) container_of(i_cdev, struct rpmsg_ctrldev, cdev)
|
|
|
|
struct rpmsg_ept_notify {
|
|
struct rpmsg_ctrldev *ctrl;
|
|
char name[32];
|
|
int id;
|
|
struct list_head list;
|
|
struct list_head notify;
|
|
struct completion complete;
|
|
uint32_t status;
|
|
};
|
|
|
|
/**
|
|
* struct rpmsg_ctrldev - control device for instantiating endpoint devices
|
|
* @rpdev: underlaying rpmsg device
|
|
* @cdev: cdev for the ctrl device
|
|
* @dev: device for the ctrl device
|
|
*/
|
|
struct rpmsg_ctrldev {
|
|
struct rpmsg_device *rpdev;
|
|
struct device dev;
|
|
struct cdev cdev;
|
|
struct list_head list;
|
|
u32 alive:1;
|
|
|
|
struct mutex lock;
|
|
struct list_head epts;
|
|
struct list_head wait;
|
|
|
|
struct rpmsg_ept_notify ctrl_notify;
|
|
};
|
|
|
|
struct class *rpmsg_ctrldev_get_class(void)
|
|
{
|
|
return rpmsg_class;
|
|
}
|
|
EXPORT_SYMBOL(rpmsg_ctrldev_get_class);
|
|
|
|
dev_t rpmsg_ctrldev_get_devt(void)
|
|
{
|
|
int ret;
|
|
ret = ida_simple_get(&rpmsg_minor_ida, 0, RPMSG_DEV_MAX, GFP_KERNEL);
|
|
if (ret < 0)
|
|
return ret;
|
|
return MKDEV(MAJOR(rpmsg_major), ret);
|
|
}
|
|
EXPORT_SYMBOL(rpmsg_ctrldev_get_devt);
|
|
|
|
void rpmsg_ctrldev_put_devt(dev_t devt)
|
|
{
|
|
ida_simple_remove(&rpmsg_minor_ida, MINOR(devt));
|
|
}
|
|
EXPORT_SYMBOL(rpmsg_ctrldev_put_devt);
|
|
|
|
static struct rpmsg_ctrldev *rpmsg_ctrldev_get_ctrl(int id)
|
|
{
|
|
struct rpmsg_ctrldev *pos, *tmp;
|
|
|
|
mutex_lock(&g_list_lock);
|
|
list_for_each_entry_safe(pos, tmp, &g_rpmsg_ctrldev_list, list) {
|
|
if (id == pos->dev.id) {
|
|
mutex_unlock(&g_list_lock);
|
|
return pos;
|
|
}
|
|
}
|
|
mutex_unlock(&g_list_lock);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void rpmsg_print_ack_str(struct device *dev, uint32_t ack)
|
|
{
|
|
switch (ack) {
|
|
case RPMSG_ACK_FAILED:
|
|
dev_err(dev, "Operation failed\r\n");
|
|
break;
|
|
case RPMSG_ACK_NOLISTEN:
|
|
dev_err(dev, "Remote don't listen this name\r\n");
|
|
break;
|
|
case RPMSG_ACK_BUSY:
|
|
dev_err(dev, "Remote is busy\r\n");
|
|
break;
|
|
case RPMSG_ACK_NOMEM:
|
|
dev_err(dev, "Remote not enought memory\r\n");
|
|
break;
|
|
case RPMSG_ACK_NOENT:
|
|
dev_err(dev, "Remote is full\r\n");
|
|
break;
|
|
}
|
|
}
|
|
|
|
static struct rpmsg_ept_notify *
|
|
rpmsg_ctrldev_notify_find(struct rpmsg_ctrldev *ctrldev, int id)
|
|
{
|
|
struct rpmsg_ept_notify *pos, *tmp;
|
|
|
|
mutex_lock(&ctrldev->lock);
|
|
list_for_each_entry_safe(pos, tmp, &ctrldev->wait, notify) {
|
|
if (id == pos->id) {
|
|
mutex_unlock(&ctrldev->lock);
|
|
return pos;
|
|
}
|
|
}
|
|
mutex_unlock(&ctrldev->lock);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void rpmsg_ctrldev_notify_add(struct rpmsg_ctrldev *ctrldev,
|
|
struct rpmsg_ept_notify *notify)
|
|
{
|
|
mutex_lock(&ctrldev->lock);
|
|
list_add(¬ify->notify, &ctrldev->wait);
|
|
mutex_unlock(&ctrldev->lock);
|
|
}
|
|
|
|
static void rpmsg_ctrldev_notify_del(struct rpmsg_ctrldev *ctrldev,
|
|
struct rpmsg_ept_notify *notify)
|
|
{
|
|
mutex_lock(&ctrldev->lock);
|
|
if (!list_empty(¬ify->notify))
|
|
list_del_init(¬ify->notify);
|
|
mutex_unlock(&ctrldev->lock);
|
|
}
|
|
|
|
static struct rpmsg_ept_notify *
|
|
rpmsg_ctrldev_epts_find(struct rpmsg_ctrldev *ctrldev, int id)
|
|
{
|
|
struct rpmsg_ept_notify *pos, *tmp;
|
|
|
|
mutex_lock(&ctrldev->lock);
|
|
list_for_each_entry_safe(pos, tmp, &ctrldev->epts, list) {
|
|
if (id == pos->id) {
|
|
mutex_unlock(&ctrldev->lock);
|
|
return pos;
|
|
}
|
|
}
|
|
mutex_unlock(&ctrldev->lock);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void rpmsg_ctrldev_epts_add(struct rpmsg_ctrldev *ctrldev,
|
|
struct rpmsg_ept_notify *notify)
|
|
{
|
|
mutex_lock(&ctrldev->lock);
|
|
list_add(¬ify->list, &ctrldev->epts);
|
|
mutex_unlock(&ctrldev->lock);
|
|
}
|
|
|
|
static void rpmsg_ctrldev_epts_del(struct rpmsg_ctrldev *ctrldev,
|
|
struct rpmsg_ept_notify *notify)
|
|
{
|
|
mutex_lock(&ctrldev->lock);
|
|
if (!list_empty(¬ify->list))
|
|
list_del_init(¬ify->list);
|
|
mutex_unlock(&ctrldev->lock);
|
|
}
|
|
|
|
int rpmsg_ctrldev_notify(int ctrl_id, int id)
|
|
{
|
|
struct rpmsg_ctrldev *ctrldev;
|
|
struct rpmsg_ept_notify *notify;
|
|
|
|
ctrldev = rpmsg_ctrldev_get_ctrl(ctrl_id);
|
|
if (!ctrldev) {
|
|
pr_err("/dev/rpmsg_ctrl%d dev not exits \n", ctrl_id);
|
|
return -ENOENT;
|
|
}
|
|
|
|
notify = rpmsg_ctrldev_epts_find(ctrldev, id);
|
|
if (!notify) {
|
|
dev_err(&ctrldev->dev, "/dev/rpmsg%d dev not exits \n", id);
|
|
return -ENOENT;
|
|
}
|
|
|
|
rpmsg_ctrldev_epts_del(ctrldev, notify);
|
|
|
|
devm_kfree(¬ify->ctrl->dev, notify);
|
|
|
|
ida_simple_remove(&rpmsg_ept_ida, id);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(rpmsg_ctrldev_notify);
|
|
|
|
static int rpmsg_eptdev_release(struct rpmsg_ctrldev *ctrldev, int id)
|
|
{
|
|
int ret;
|
|
struct rpmsg_ctrl_msg msg;
|
|
struct rpmsg_ept_notify *notify;
|
|
|
|
msg.ctrl_id = ctrldev->dev.id;
|
|
msg.id = id;
|
|
msg.cmd = RPMSG_CLOSE_CLIENT;
|
|
|
|
notify = rpmsg_ctrldev_epts_find(ctrldev, id);
|
|
if (!notify) {
|
|
dev_err(&ctrldev->dev, "/dev/rpmsg%d dev not exits \n", id);
|
|
return -ENODEV;
|
|
}
|
|
|
|
reinit_completion(¬ify->complete);
|
|
/* add to notify chain */
|
|
rpmsg_ctrldev_notify_add(ctrldev, notify);
|
|
|
|
strncpy(msg.name, notify->name, 32);
|
|
/* tell remote close this endpoint */
|
|
ret = rpmsg_send(ctrldev->rpdev->ept, &msg, sizeof(msg));
|
|
if (ret)
|
|
goto out;
|
|
|
|
/* wait endpoint notify */
|
|
ret = wait_for_completion_timeout(¬ify->complete, WAIT_TIMEOUT);
|
|
if (!ret) {
|
|
dev_err(&ctrldev->dev, "close %s eptdev failed(timeout)\n", notify->name);
|
|
ret = -ETIMEDOUT;
|
|
goto out;
|
|
}
|
|
|
|
if (notify->status != RPMSG_ACK_OK) {
|
|
rpmsg_print_ack_str(&ctrldev->dev, notify->status);
|
|
ret = -EFAULT;
|
|
goto out;
|
|
}
|
|
|
|
ret = 0;
|
|
/* delete will complete by rpmsg_ctrldev_notify */
|
|
|
|
out:
|
|
rpmsg_ctrldev_notify_del(ctrldev, notify);
|
|
return ret;
|
|
}
|
|
|
|
static int rpmsg_eptdev_create(struct rpmsg_ctrldev *ctrldev, const char *name, int id)
|
|
{
|
|
int ret;
|
|
struct rpmsg_ctrl_msg msg;
|
|
struct rpmsg_ept_notify *notify = NULL;
|
|
|
|
notify = devm_kzalloc(&ctrldev->dev, sizeof(*notify), GFP_KERNEL);
|
|
if (!notify)
|
|
return -ENOMEM;
|
|
|
|
notify->ctrl = ctrldev;
|
|
notify->id = id;
|
|
strncpy(notify->name, name, 32);
|
|
init_completion(¬ify->complete);
|
|
|
|
/* fill message struct */
|
|
strncpy(msg.name, name, 32);
|
|
msg.ctrl_id = ctrldev->dev.id;
|
|
msg.id = id;
|
|
msg.cmd = RPMSG_CREATE_CLIENT;
|
|
|
|
/* add ctrl to notify chain */
|
|
rpmsg_ctrldev_notify_add(ctrldev, notify);
|
|
|
|
/* tell remote to create a new endpoint */
|
|
ret = rpmsg_send(ctrldev->rpdev->ept, &msg, sizeof(msg));
|
|
if (ret) {
|
|
dev_err(&ctrldev->dev, "send data failed\n");
|
|
ret = -EFAULT;
|
|
goto del_notify;
|
|
}
|
|
|
|
/* wait endpoint notify */
|
|
notify->status = RPMSG_ACK_FAILED;
|
|
ret = wait_for_completion_timeout(¬ify->complete, WAIT_TIMEOUT);
|
|
if (!ret) {
|
|
dev_err(&ctrldev->dev, "create %s eptdev failed(timeout)\n", name);
|
|
ret = -ETIMEDOUT;
|
|
goto del_notify;
|
|
}
|
|
|
|
if (notify->status != RPMSG_ACK_OK) {
|
|
rpmsg_print_ack_str(&ctrldev->dev, notify->status);
|
|
ret = -EFAULT;
|
|
goto del_notify;
|
|
}
|
|
|
|
rpmsg_ctrldev_notify_del(ctrldev, notify);
|
|
|
|
/* success create client,add it to epts list */
|
|
rpmsg_ctrldev_epts_add(ctrldev, notify);
|
|
|
|
return 0;
|
|
|
|
del_notify:
|
|
rpmsg_ctrldev_notify_del(ctrldev, notify);
|
|
|
|
devm_kfree(¬ify->ctrl->dev, notify);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int rpmsg_eptdev_clear(struct rpmsg_ctrldev *ctrldev, const char *name)
|
|
{
|
|
int ret;
|
|
struct rpmsg_ctrl_msg msg;
|
|
struct rpmsg_ept_notify *notify;
|
|
|
|
msg.ctrl_id = ctrldev->dev.id;
|
|
msg.id = 0;
|
|
msg.cmd = RPMSG_RESET_GRP_CLIENT;
|
|
|
|
notify = &ctrldev->ctrl_notify;
|
|
|
|
/* add to notify chain */
|
|
rpmsg_ctrldev_notify_add(ctrldev, notify);
|
|
strncpy(msg.name, name, 32);
|
|
reinit_completion(¬ify->complete);
|
|
/* tell remote close this endpoint */
|
|
ret = rpmsg_trysend(ctrldev->rpdev->ept, &msg, sizeof(msg));
|
|
if (ret)
|
|
goto out;
|
|
|
|
/* wait endpoint notify */
|
|
ret = wait_for_completion_timeout(¬ify->complete, WAIT_TIMEOUT);
|
|
if (!ret) {
|
|
dev_err(&ctrldev->dev, "clear %s group failed(timeout)\n", name);
|
|
ret = -ETIMEDOUT;
|
|
goto out;
|
|
}
|
|
|
|
if (notify->status != RPMSG_ACK_OK) {
|
|
rpmsg_print_ack_str(&ctrldev->dev, notify->status);
|
|
ret = -EFAULT;
|
|
goto out;
|
|
}
|
|
|
|
ret = 0;
|
|
out:
|
|
rpmsg_ctrldev_notify_del(ctrldev, notify);
|
|
return ret;
|
|
}
|
|
|
|
static int rpmsg_eptdev_reset(struct rpmsg_ctrldev *ctrldev)
|
|
{
|
|
int ret;
|
|
struct rpmsg_ctrl_msg msg;
|
|
struct rpmsg_ept_notify *notify;
|
|
|
|
notify = &ctrldev->ctrl_notify;
|
|
|
|
msg.ctrl_id = ctrldev->dev.id;
|
|
msg.id = 0;
|
|
msg.cmd = RPMSG_RESET_ALL_CLIENT;
|
|
|
|
reinit_completion(¬ify->complete);
|
|
/* add to notify chain */
|
|
rpmsg_ctrldev_notify_add(ctrldev, notify);
|
|
/* tell remote close this endpoint */
|
|
ret = rpmsg_trysend(ctrldev->rpdev->ept, &msg, sizeof(msg));
|
|
if (ret)
|
|
goto out;
|
|
|
|
/* wait endpoint notify */
|
|
ret = wait_for_completion_timeout(¬ify->complete, WAIT_TIMEOUT);
|
|
if (!ret) {
|
|
dev_err(&ctrldev->dev, "reset rpmsg_ctrl group failed(timeout)\n");
|
|
ret = -ETIMEDOUT;
|
|
goto out;
|
|
}
|
|
|
|
if (notify->status != RPMSG_ACK_OK) {
|
|
rpmsg_print_ack_str(&ctrldev->dev, notify->status);
|
|
ret = -EFAULT;
|
|
goto out;
|
|
}
|
|
|
|
ret = 0;
|
|
out:
|
|
rpmsg_ctrldev_notify_del(ctrldev, notify);
|
|
return ret;
|
|
}
|
|
|
|
static int rpmsg_ctrldev_open(struct inode *inode, struct file *filp)
|
|
{
|
|
struct rpmsg_ctrldev *ctrldev = cdev_to_ctrldev(inode->i_cdev);
|
|
|
|
get_device(&ctrldev->dev);
|
|
filp->private_data = ctrldev;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rpmsg_ctrldev_release(struct inode *inode, struct file *filp)
|
|
{
|
|
struct rpmsg_ctrldev *ctrldev = cdev_to_ctrldev(inode->i_cdev);
|
|
|
|
put_device(&ctrldev->dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static long rpmsg_ctrldev_ioctl(struct file *fp, unsigned int cmd,
|
|
unsigned long arg)
|
|
{
|
|
int ret;
|
|
struct rpmsg_ctrldev *ctrldev = fp->private_data;
|
|
void __user *argp = (void __user *)arg;
|
|
struct rpmsg_ept_info info;
|
|
|
|
if (copy_from_user(&info, argp, sizeof(info)))
|
|
return -EFAULT;
|
|
|
|
switch (cmd) {
|
|
case RPMSG_CREATE_EPT_IOCTL: {
|
|
if (!argp)
|
|
return -EINVAL;
|
|
/* get unique id for ept */
|
|
ret = ida_simple_get(&rpmsg_ept_ida, 1, 0, GFP_KERNEL);
|
|
if (ret < 0)
|
|
return -EFAULT;
|
|
|
|
info.id = ret;
|
|
/* notify remote create endpoint */
|
|
ret = rpmsg_eptdev_create(ctrldev, info.name, info.id);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (copy_to_user(argp, &info, sizeof(info)))
|
|
return -EFAULT;
|
|
return 0;
|
|
} break;
|
|
|
|
case RPMSG_DESTROY_EPT_IOCTL: {
|
|
if (!argp)
|
|
return -EINVAL;
|
|
dev_info(&ctrldev->dev, "close /dev/rpmsg%d endpoint\r\n", info.id);
|
|
ret = rpmsg_eptdev_release(ctrldev, info.id);
|
|
if (ret)
|
|
return ret;
|
|
return 0;
|
|
} break;
|
|
|
|
case RPMSG_REST_EPT_GRP_IOCTL: {
|
|
if (!argp)
|
|
return -EINVAL;
|
|
dev_info(&ctrldev->dev, "clear %s group\r\n", info.name);
|
|
ret = rpmsg_eptdev_clear(ctrldev, info.name);
|
|
if (ret)
|
|
return ret;
|
|
return 0;
|
|
} break;
|
|
case RPMSG_DESTROY_ALL_EPT_IOCTL: {
|
|
dev_info(&ctrldev->dev, "reset %s\r\n", dev_name(&ctrldev->dev));
|
|
ret = rpmsg_eptdev_reset(ctrldev);
|
|
if (ret)
|
|
return ret;
|
|
return 0;
|
|
} break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
dev_warn(&ctrldev->dev, "Undown konw cmd=0x%x\r\n", cmd);
|
|
|
|
return -EINVAL;
|
|
};
|
|
|
|
static const struct file_operations rpmsg_ctrldev_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = rpmsg_ctrldev_open,
|
|
.release = rpmsg_ctrldev_release,
|
|
.unlocked_ioctl = rpmsg_ctrldev_ioctl,
|
|
.compat_ioctl = rpmsg_ctrldev_ioctl,
|
|
};
|
|
|
|
static int rpmsg_ctrldev_cb(struct rpmsg_device *rpdev, void *data, int len,
|
|
void *priv, u32 src)
|
|
{
|
|
struct rpmsg_ctrldev *ctrldev = dev_get_drvdata(&rpdev->dev);
|
|
struct rpmsg_ctrl_msg_ack *cell = data;
|
|
struct rpmsg_ept_notify *notify = NULL;
|
|
|
|
if (len != sizeof(*cell)) {
|
|
dev_err(&rpdev->dev, "Invalid len:expect:%d,Rx:%d\n", sizeof(*cell), len);
|
|
return 0;
|
|
}
|
|
|
|
dev_dbg(&ctrldev->dev, "cb rpmsg%d ack=0x%x\n", cell->id, cell->ack);
|
|
|
|
notify = rpmsg_ctrldev_notify_find(ctrldev, cell->id);
|
|
if (!notify) {
|
|
dev_err(&ctrldev->dev, "/dev/rpmsg%d dev not exits \n", cell->id);
|
|
return -ENOENT;
|
|
}
|
|
|
|
notify->status = cell->ack;
|
|
complete(¬ify->complete);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void rpmsg_ctrldev_release_device(struct device *dev)
|
|
{
|
|
ida_simple_remove(&rpmsg_ctrl_ida, dev->id);
|
|
rpmsg_ctrldev_put_devt(MINOR(dev->devt));
|
|
}
|
|
|
|
static ssize_t open_store(struct device *dev, struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
int ret;
|
|
struct rpmsg_ctrldev *ctrl = dev_get_drvdata(dev);
|
|
char name[32];
|
|
|
|
if (count == 0)
|
|
return count;
|
|
|
|
memcpy(name, buf, count > 32 ? 32:count);
|
|
if (name[count - 1] == '\n')
|
|
name[count - 1] = '\0';
|
|
else
|
|
name[count] = '\0';
|
|
|
|
/* get unique id for ept */
|
|
ret = ida_simple_get(&rpmsg_ept_ida, 1, 0, GFP_KERNEL);
|
|
if (ret < 0)
|
|
return -EFAULT;
|
|
|
|
dev_dbg(&ctrl->dev, "create /dev/rpmsg%d endpoint\r\n", ret);
|
|
|
|
/* notify remote create endpoint */
|
|
ret = rpmsg_eptdev_create(ctrl, name, ret);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return count;
|
|
}
|
|
static DEVICE_ATTR_WO(open);
|
|
|
|
static ssize_t close_store(struct device *dev, struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
int ret;
|
|
struct rpmsg_ctrldev *ctrl = dev_get_drvdata(dev);
|
|
int id;
|
|
|
|
if (count == 0)
|
|
return count;
|
|
|
|
ret = sscanf(buf, "%d", &id);;
|
|
|
|
if (ret != 1)
|
|
return -EINVAL;
|
|
|
|
if (id < 0 || id == -1)
|
|
return -EINVAL;
|
|
|
|
dev_dbg(&ctrl->dev, "close /dev/rpmsg%d endpoint\r\n", id);
|
|
|
|
/* notify remote close endpoint */
|
|
ret = rpmsg_eptdev_release(ctrl, id);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return count;
|
|
}
|
|
static DEVICE_ATTR_WO(close);
|
|
|
|
static ssize_t clear_store(struct device *dev, struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
int ret;
|
|
struct rpmsg_ctrldev *ctrl = dev_get_drvdata(dev);
|
|
char name[32];
|
|
|
|
if (count == 0)
|
|
return count;
|
|
|
|
memcpy(name, buf, count > 32 ? 32:count);
|
|
if (name[count - 1] == '\n')
|
|
name[count - 1] = '\0';
|
|
else
|
|
name[count] = '\0';
|
|
|
|
dev_dbg(&ctrl->dev, "clear %s endpoint\r\n", name);
|
|
|
|
/* notify remote create endpoint */
|
|
ret = rpmsg_eptdev_clear(ctrl, name);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return count;
|
|
|
|
}
|
|
static DEVICE_ATTR_WO(clear);
|
|
|
|
static ssize_t reset_store(struct device *dev, struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
int ret;
|
|
struct rpmsg_ctrldev *ctrl = dev_get_drvdata(dev);
|
|
char name[32];
|
|
|
|
if (count == 0)
|
|
return count;
|
|
|
|
memcpy(name, buf, count > 32 ? 32:count);
|
|
if (name[count - 1] == '\n')
|
|
name[count - 1] = '\0';
|
|
else
|
|
name[count] = '\0';
|
|
|
|
dev_dbg(&ctrl->dev, "reset rpmsg_ctrl\r\n");
|
|
|
|
/* notify remote create endpoint */
|
|
ret = rpmsg_eptdev_reset(ctrl);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return count;
|
|
|
|
}
|
|
static DEVICE_ATTR_WO(reset);
|
|
|
|
static int rpmsg_ctrldev_probe(struct rpmsg_device *rpdev)
|
|
{
|
|
struct rpmsg_ctrldev *ctrldev;
|
|
struct device *dev;
|
|
int ret;
|
|
|
|
ctrldev = kzalloc(sizeof(*ctrldev), GFP_KERNEL);
|
|
if (!ctrldev)
|
|
return -ENOMEM;
|
|
|
|
ctrldev->rpdev = rpdev;
|
|
|
|
dev = &ctrldev->dev;
|
|
device_initialize(dev);
|
|
dev->parent = &rpdev->dev;
|
|
dev->class = rpmsg_class;
|
|
INIT_LIST_HEAD(&ctrldev->epts);
|
|
INIT_LIST_HEAD(&ctrldev->wait);
|
|
mutex_init(&ctrldev->lock);
|
|
|
|
cdev_init(&ctrldev->cdev, &rpmsg_ctrldev_fops);
|
|
ctrldev->cdev.owner = THIS_MODULE;
|
|
|
|
ret = rpmsg_ctrldev_get_devt();
|
|
if (ret < 0)
|
|
goto free_ctrldev;
|
|
dev->devt = ret;
|
|
|
|
ret = ida_simple_get(&rpmsg_ctrl_ida, 0, 0, GFP_KERNEL);
|
|
if (ret < 0)
|
|
goto free_minor_ida;
|
|
dev->id = ret;
|
|
dev_set_name(&ctrldev->dev, "rpmsg_ctrl%d", ret);
|
|
|
|
ret = cdev_add(&ctrldev->cdev, dev->devt, 1);
|
|
if (ret)
|
|
goto free_ctrl_ida;
|
|
|
|
/* We can now rely on the release function for cleanup */
|
|
dev->release = rpmsg_ctrldev_release_device;
|
|
|
|
ret = device_add(dev);
|
|
if (ret) {
|
|
dev_err(&rpdev->dev, "device_add failed: %d\n", ret);
|
|
put_device(dev);
|
|
}
|
|
|
|
device_create_file(&ctrldev->dev, &dev_attr_open);
|
|
device_create_file(&ctrldev->dev, &dev_attr_close);
|
|
device_create_file(&ctrldev->dev, &dev_attr_clear);
|
|
device_create_file(&ctrldev->dev, &dev_attr_reset);
|
|
|
|
dev_set_drvdata(&rpdev->dev, ctrldev);
|
|
dev_set_drvdata(&ctrldev->dev, ctrldev);
|
|
|
|
list_add(&ctrldev->list, &g_rpmsg_ctrldev_list);
|
|
if (mutex_is_locked(&g_list_lock))
|
|
mutex_unlock(&g_list_lock);
|
|
|
|
init_completion(&ctrldev->ctrl_notify.complete);
|
|
rpmsg_ctrldev_notify_add(ctrldev, &ctrldev->ctrl_notify);
|
|
|
|
/* wo need to announce the new ept to remote */
|
|
rpdev->announce = rpdev->src != RPMSG_ADDR_ANY;
|
|
|
|
return ret;
|
|
|
|
free_ctrl_ida:
|
|
ida_simple_remove(&rpmsg_ctrl_ida, dev->id);
|
|
free_minor_ida:
|
|
rpmsg_ctrldev_put_devt(MINOR(dev->devt));
|
|
free_ctrldev:
|
|
put_device(dev);
|
|
kfree(ctrldev);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void rpmsg_ctrldev_remove(struct rpmsg_device *rpdev)
|
|
{
|
|
struct rpmsg_ctrldev *ctrldev = dev_get_drvdata(&rpdev->dev);
|
|
struct rpmsg_ept_notify *pos, *tmp;
|
|
|
|
dev_dbg(&rpdev->dev, "%s is removed\n", dev_name(&rpdev->dev));
|
|
|
|
/* remove all sub rpmsg client */
|
|
list_for_each_entry_safe(pos, tmp, &ctrldev->epts, list) {
|
|
rpmsg_ctrldev_epts_del(ctrldev, pos);
|
|
devm_kfree(&ctrldev->dev, pos);
|
|
ida_simple_remove(&rpmsg_ept_ida, pos->id);
|
|
}
|
|
|
|
rpmsg_ctrldev_notify_del(ctrldev, &ctrldev->ctrl_notify);
|
|
if (!list_empty(&ctrldev->list))
|
|
list_del_init(&ctrldev->list);
|
|
device_del(&ctrldev->dev);
|
|
put_device(&ctrldev->dev);
|
|
cdev_del(&ctrldev->cdev);
|
|
kfree(ctrldev);
|
|
}
|
|
|
|
static struct rpmsg_device_id rpmsg_driver_ctrldev_id_table[] = {
|
|
{ .name = "sunxi,rpmsg_ctrl" },
|
|
{ },
|
|
};
|
|
MODULE_DEVICE_TABLE(rpmsg, rpmsg_driver_crtldev_id_table);
|
|
|
|
static struct rpmsg_driver rpmsg_ctrldev_driver = {
|
|
.probe = rpmsg_ctrldev_probe,
|
|
.remove = rpmsg_ctrldev_remove,
|
|
.callback = rpmsg_ctrldev_cb,
|
|
.drv = {
|
|
.name = "sunxi,rpmsg_ctrl",
|
|
},
|
|
.id_table = rpmsg_driver_ctrldev_id_table,
|
|
};
|
|
|
|
static int rpmsg_ctrldev_init(void)
|
|
{
|
|
int ret;
|
|
|
|
ret = alloc_chrdev_region(&rpmsg_major, 0, RPMSG_DEV_MAX, "rpmsg");
|
|
if (ret < 0) {
|
|
pr_err("rpmsg: failed to allocate char dev region\n");
|
|
return ret;
|
|
}
|
|
|
|
rpmsg_class = class_create(THIS_MODULE, "rpmsg");
|
|
if (IS_ERR(rpmsg_class)) {
|
|
pr_err("failed to create rpmsg class\n");
|
|
unregister_chrdev_region(rpmsg_major, RPMSG_DEV_MAX);
|
|
return PTR_ERR(rpmsg_class);
|
|
}
|
|
|
|
ret = register_rpmsg_driver(&rpmsg_ctrldev_driver);
|
|
if (ret < 0) {
|
|
pr_err("rpmsgchr: failed to register rpmsg driver\n");
|
|
class_destroy(rpmsg_class);
|
|
unregister_chrdev_region(rpmsg_major, RPMSG_DEV_MAX);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
postcore_initcall(rpmsg_ctrldev_init);
|
|
|
|
static void rpmsg_ctrldev_exit(void)
|
|
{
|
|
unregister_rpmsg_driver(&rpmsg_ctrldev_driver);
|
|
class_destroy(rpmsg_class);
|
|
unregister_chrdev_region(rpmsg_major, RPMSG_DEV_MAX);
|
|
}
|
|
module_exit(rpmsg_ctrldev_exit);
|
|
|
|
MODULE_ALIAS("rpmsg:rpmsg_ctrl");
|
|
MODULE_LICENSE("GPL v2");
|