162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (C) 2022, STMicroelectronics 462306a36Sopenharmony_ci * Copyright (c) 2016, Linaro Ltd. 562306a36Sopenharmony_ci * Copyright (c) 2012, Michal Simek <monstr@monstr.eu> 662306a36Sopenharmony_ci * Copyright (c) 2012, PetaLogix 762306a36Sopenharmony_ci * Copyright (c) 2011, Texas Instruments, Inc. 862306a36Sopenharmony_ci * Copyright (c) 2011, Google, Inc. 962306a36Sopenharmony_ci * 1062306a36Sopenharmony_ci * Based on rpmsg performance statistics driver by Michal Simek, which in turn 1162306a36Sopenharmony_ci * was based on TI & Google OMX rpmsg driver. 1262306a36Sopenharmony_ci */ 1362306a36Sopenharmony_ci 1462306a36Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_ci#include <linux/cdev.h> 1762306a36Sopenharmony_ci#include <linux/device.h> 1862306a36Sopenharmony_ci#include <linux/fs.h> 1962306a36Sopenharmony_ci#include <linux/idr.h> 2062306a36Sopenharmony_ci#include <linux/kernel.h> 2162306a36Sopenharmony_ci#include <linux/module.h> 2262306a36Sopenharmony_ci#include <linux/rpmsg.h> 2362306a36Sopenharmony_ci#include <linux/skbuff.h> 2462306a36Sopenharmony_ci#include <linux/slab.h> 2562306a36Sopenharmony_ci#include <linux/uaccess.h> 2662306a36Sopenharmony_ci#include <uapi/linux/rpmsg.h> 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci#include "rpmsg_char.h" 2962306a36Sopenharmony_ci#include "rpmsg_internal.h" 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_ci#define RPMSG_DEV_MAX (MINORMASK + 1) 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_cistatic dev_t rpmsg_major; 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_cistatic DEFINE_IDA(rpmsg_ctrl_ida); 3662306a36Sopenharmony_cistatic DEFINE_IDA(rpmsg_minor_ida); 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci#define dev_to_ctrldev(dev) container_of(dev, struct rpmsg_ctrldev, dev) 3962306a36Sopenharmony_ci#define cdev_to_ctrldev(i_cdev) container_of(i_cdev, struct rpmsg_ctrldev, cdev) 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_ci/** 4262306a36Sopenharmony_ci * struct rpmsg_ctrldev - control device for instantiating endpoint devices 4362306a36Sopenharmony_ci * @rpdev: underlaying rpmsg device 4462306a36Sopenharmony_ci * @cdev: cdev for the ctrl device 4562306a36Sopenharmony_ci * @dev: device for the ctrl device 4662306a36Sopenharmony_ci * @ctrl_lock: serialize the ioctrls. 4762306a36Sopenharmony_ci */ 4862306a36Sopenharmony_cistruct rpmsg_ctrldev { 4962306a36Sopenharmony_ci struct rpmsg_device *rpdev; 5062306a36Sopenharmony_ci struct cdev cdev; 5162306a36Sopenharmony_ci struct device dev; 5262306a36Sopenharmony_ci struct mutex ctrl_lock; 5362306a36Sopenharmony_ci}; 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_cistatic int rpmsg_ctrldev_open(struct inode *inode, struct file *filp) 5662306a36Sopenharmony_ci{ 5762306a36Sopenharmony_ci struct rpmsg_ctrldev *ctrldev = cdev_to_ctrldev(inode->i_cdev); 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci get_device(&ctrldev->dev); 6062306a36Sopenharmony_ci filp->private_data = ctrldev; 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci return 0; 6362306a36Sopenharmony_ci} 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_cistatic int rpmsg_ctrldev_release(struct inode *inode, struct file *filp) 6662306a36Sopenharmony_ci{ 6762306a36Sopenharmony_ci struct rpmsg_ctrldev *ctrldev = cdev_to_ctrldev(inode->i_cdev); 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ci put_device(&ctrldev->dev); 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci return 0; 7262306a36Sopenharmony_ci} 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_cistatic long rpmsg_ctrldev_ioctl(struct file *fp, unsigned int cmd, 7562306a36Sopenharmony_ci unsigned long arg) 7662306a36Sopenharmony_ci{ 7762306a36Sopenharmony_ci struct rpmsg_ctrldev *ctrldev = fp->private_data; 7862306a36Sopenharmony_ci void __user *argp = (void __user *)arg; 7962306a36Sopenharmony_ci struct rpmsg_endpoint_info eptinfo; 8062306a36Sopenharmony_ci struct rpmsg_channel_info chinfo; 8162306a36Sopenharmony_ci struct rpmsg_device *rpdev; 8262306a36Sopenharmony_ci int ret = 0; 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci if (copy_from_user(&eptinfo, argp, sizeof(eptinfo))) 8562306a36Sopenharmony_ci return -EFAULT; 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_ci memcpy(chinfo.name, eptinfo.name, RPMSG_NAME_SIZE); 8862306a36Sopenharmony_ci chinfo.name[RPMSG_NAME_SIZE - 1] = '\0'; 8962306a36Sopenharmony_ci chinfo.src = eptinfo.src; 9062306a36Sopenharmony_ci chinfo.dst = eptinfo.dst; 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ci mutex_lock(&ctrldev->ctrl_lock); 9362306a36Sopenharmony_ci switch (cmd) { 9462306a36Sopenharmony_ci case RPMSG_CREATE_EPT_IOCTL: 9562306a36Sopenharmony_ci ret = rpmsg_chrdev_eptdev_create(ctrldev->rpdev, &ctrldev->dev, chinfo); 9662306a36Sopenharmony_ci break; 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_ci case RPMSG_CREATE_DEV_IOCTL: 9962306a36Sopenharmony_ci rpdev = rpmsg_create_channel(ctrldev->rpdev, &chinfo); 10062306a36Sopenharmony_ci if (!rpdev) { 10162306a36Sopenharmony_ci dev_err(&ctrldev->dev, "failed to create %s channel\n", chinfo.name); 10262306a36Sopenharmony_ci ret = -ENXIO; 10362306a36Sopenharmony_ci } 10462306a36Sopenharmony_ci break; 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci case RPMSG_RELEASE_DEV_IOCTL: 10762306a36Sopenharmony_ci ret = rpmsg_release_channel(ctrldev->rpdev, &chinfo); 10862306a36Sopenharmony_ci if (ret) 10962306a36Sopenharmony_ci dev_err(&ctrldev->dev, "failed to release %s channel (%d)\n", 11062306a36Sopenharmony_ci chinfo.name, ret); 11162306a36Sopenharmony_ci break; 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci default: 11462306a36Sopenharmony_ci ret = -EINVAL; 11562306a36Sopenharmony_ci } 11662306a36Sopenharmony_ci mutex_unlock(&ctrldev->ctrl_lock); 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci return ret; 11962306a36Sopenharmony_ci}; 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_cistatic const struct file_operations rpmsg_ctrldev_fops = { 12262306a36Sopenharmony_ci .owner = THIS_MODULE, 12362306a36Sopenharmony_ci .open = rpmsg_ctrldev_open, 12462306a36Sopenharmony_ci .release = rpmsg_ctrldev_release, 12562306a36Sopenharmony_ci .unlocked_ioctl = rpmsg_ctrldev_ioctl, 12662306a36Sopenharmony_ci .compat_ioctl = compat_ptr_ioctl, 12762306a36Sopenharmony_ci}; 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_cistatic void rpmsg_ctrldev_release_device(struct device *dev) 13062306a36Sopenharmony_ci{ 13162306a36Sopenharmony_ci struct rpmsg_ctrldev *ctrldev = dev_to_ctrldev(dev); 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci ida_simple_remove(&rpmsg_ctrl_ida, dev->id); 13462306a36Sopenharmony_ci ida_simple_remove(&rpmsg_minor_ida, MINOR(dev->devt)); 13562306a36Sopenharmony_ci kfree(ctrldev); 13662306a36Sopenharmony_ci} 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_cistatic int rpmsg_ctrldev_probe(struct rpmsg_device *rpdev) 13962306a36Sopenharmony_ci{ 14062306a36Sopenharmony_ci struct rpmsg_ctrldev *ctrldev; 14162306a36Sopenharmony_ci struct device *dev; 14262306a36Sopenharmony_ci int ret; 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci ctrldev = kzalloc(sizeof(*ctrldev), GFP_KERNEL); 14562306a36Sopenharmony_ci if (!ctrldev) 14662306a36Sopenharmony_ci return -ENOMEM; 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ci ctrldev->rpdev = rpdev; 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci dev = &ctrldev->dev; 15162306a36Sopenharmony_ci device_initialize(dev); 15262306a36Sopenharmony_ci dev->parent = &rpdev->dev; 15362306a36Sopenharmony_ci dev->class = rpmsg_class; 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci mutex_init(&ctrldev->ctrl_lock); 15662306a36Sopenharmony_ci cdev_init(&ctrldev->cdev, &rpmsg_ctrldev_fops); 15762306a36Sopenharmony_ci ctrldev->cdev.owner = THIS_MODULE; 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci ret = ida_simple_get(&rpmsg_minor_ida, 0, RPMSG_DEV_MAX, GFP_KERNEL); 16062306a36Sopenharmony_ci if (ret < 0) 16162306a36Sopenharmony_ci goto free_ctrldev; 16262306a36Sopenharmony_ci dev->devt = MKDEV(MAJOR(rpmsg_major), ret); 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_ci ret = ida_simple_get(&rpmsg_ctrl_ida, 0, 0, GFP_KERNEL); 16562306a36Sopenharmony_ci if (ret < 0) 16662306a36Sopenharmony_ci goto free_minor_ida; 16762306a36Sopenharmony_ci dev->id = ret; 16862306a36Sopenharmony_ci dev_set_name(&ctrldev->dev, "rpmsg_ctrl%d", ret); 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci ret = cdev_device_add(&ctrldev->cdev, &ctrldev->dev); 17162306a36Sopenharmony_ci if (ret) 17262306a36Sopenharmony_ci goto free_ctrl_ida; 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_ci /* We can now rely on the release function for cleanup */ 17562306a36Sopenharmony_ci dev->release = rpmsg_ctrldev_release_device; 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_ci dev_set_drvdata(&rpdev->dev, ctrldev); 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci return ret; 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_cifree_ctrl_ida: 18262306a36Sopenharmony_ci ida_simple_remove(&rpmsg_ctrl_ida, dev->id); 18362306a36Sopenharmony_cifree_minor_ida: 18462306a36Sopenharmony_ci ida_simple_remove(&rpmsg_minor_ida, MINOR(dev->devt)); 18562306a36Sopenharmony_cifree_ctrldev: 18662306a36Sopenharmony_ci put_device(dev); 18762306a36Sopenharmony_ci kfree(ctrldev); 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci return ret; 19062306a36Sopenharmony_ci} 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_cistatic void rpmsg_ctrldev_remove(struct rpmsg_device *rpdev) 19362306a36Sopenharmony_ci{ 19462306a36Sopenharmony_ci struct rpmsg_ctrldev *ctrldev = dev_get_drvdata(&rpdev->dev); 19562306a36Sopenharmony_ci int ret; 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_ci mutex_lock(&ctrldev->ctrl_lock); 19862306a36Sopenharmony_ci /* Destroy all endpoints */ 19962306a36Sopenharmony_ci ret = device_for_each_child(&ctrldev->dev, NULL, rpmsg_chrdev_eptdev_destroy); 20062306a36Sopenharmony_ci if (ret) 20162306a36Sopenharmony_ci dev_warn(&rpdev->dev, "failed to nuke endpoints: %d\n", ret); 20262306a36Sopenharmony_ci mutex_unlock(&ctrldev->ctrl_lock); 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_ci cdev_device_del(&ctrldev->cdev, &ctrldev->dev); 20562306a36Sopenharmony_ci put_device(&ctrldev->dev); 20662306a36Sopenharmony_ci} 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_cistatic struct rpmsg_driver rpmsg_ctrldev_driver = { 20962306a36Sopenharmony_ci .probe = rpmsg_ctrldev_probe, 21062306a36Sopenharmony_ci .remove = rpmsg_ctrldev_remove, 21162306a36Sopenharmony_ci .drv = { 21262306a36Sopenharmony_ci .name = "rpmsg_ctrl", 21362306a36Sopenharmony_ci }, 21462306a36Sopenharmony_ci}; 21562306a36Sopenharmony_ci 21662306a36Sopenharmony_cistatic int rpmsg_ctrldev_init(void) 21762306a36Sopenharmony_ci{ 21862306a36Sopenharmony_ci int ret; 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_ci ret = alloc_chrdev_region(&rpmsg_major, 0, RPMSG_DEV_MAX, "rpmsg_ctrl"); 22162306a36Sopenharmony_ci if (ret < 0) { 22262306a36Sopenharmony_ci pr_err("failed to allocate char dev region\n"); 22362306a36Sopenharmony_ci return ret; 22462306a36Sopenharmony_ci } 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci ret = register_rpmsg_driver(&rpmsg_ctrldev_driver); 22762306a36Sopenharmony_ci if (ret < 0) { 22862306a36Sopenharmony_ci pr_err("failed to register rpmsg driver\n"); 22962306a36Sopenharmony_ci unregister_chrdev_region(rpmsg_major, RPMSG_DEV_MAX); 23062306a36Sopenharmony_ci } 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_ci return ret; 23362306a36Sopenharmony_ci} 23462306a36Sopenharmony_cipostcore_initcall(rpmsg_ctrldev_init); 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_cistatic void rpmsg_ctrldev_exit(void) 23762306a36Sopenharmony_ci{ 23862306a36Sopenharmony_ci unregister_rpmsg_driver(&rpmsg_ctrldev_driver); 23962306a36Sopenharmony_ci unregister_chrdev_region(rpmsg_major, RPMSG_DEV_MAX); 24062306a36Sopenharmony_ci} 24162306a36Sopenharmony_cimodule_exit(rpmsg_ctrldev_exit); 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_ciMODULE_DESCRIPTION("rpmsg control interface"); 24462306a36Sopenharmony_ciMODULE_ALIAS("rpmsg:" KBUILD_MODNAME); 24562306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 246