18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Character device interface driver for Remoteproc framework. 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (c) 2020, The Linux Foundation. All rights reserved. 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#include <linux/cdev.h> 98c2ecf20Sopenharmony_ci#include <linux/compat.h> 108c2ecf20Sopenharmony_ci#include <linux/fs.h> 118c2ecf20Sopenharmony_ci#include <linux/module.h> 128c2ecf20Sopenharmony_ci#include <linux/remoteproc.h> 138c2ecf20Sopenharmony_ci#include <linux/uaccess.h> 148c2ecf20Sopenharmony_ci#include <uapi/linux/remoteproc_cdev.h> 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_ci#include "remoteproc_internal.h" 178c2ecf20Sopenharmony_ci 188c2ecf20Sopenharmony_ci#define NUM_RPROC_DEVICES 64 198c2ecf20Sopenharmony_cistatic dev_t rproc_major; 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_cistatic ssize_t rproc_cdev_write(struct file *filp, const char __user *buf, size_t len, loff_t *pos) 228c2ecf20Sopenharmony_ci{ 238c2ecf20Sopenharmony_ci struct rproc *rproc = container_of(filp->f_inode->i_cdev, struct rproc, cdev); 248c2ecf20Sopenharmony_ci int ret = 0; 258c2ecf20Sopenharmony_ci char cmd[10]; 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_ci if (!len || len > sizeof(cmd)) 288c2ecf20Sopenharmony_ci return -EINVAL; 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ci ret = copy_from_user(cmd, buf, len); 318c2ecf20Sopenharmony_ci if (ret) 328c2ecf20Sopenharmony_ci return -EFAULT; 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_ci if (!strncmp(cmd, "start", len)) { 358c2ecf20Sopenharmony_ci if (rproc->state == RPROC_RUNNING) 368c2ecf20Sopenharmony_ci return -EBUSY; 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_ci ret = rproc_boot(rproc); 398c2ecf20Sopenharmony_ci } else if (!strncmp(cmd, "stop", len)) { 408c2ecf20Sopenharmony_ci if (rproc->state != RPROC_RUNNING) 418c2ecf20Sopenharmony_ci return -EINVAL; 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_ci rproc_shutdown(rproc); 448c2ecf20Sopenharmony_ci } else { 458c2ecf20Sopenharmony_ci dev_err(&rproc->dev, "Unrecognized option\n"); 468c2ecf20Sopenharmony_ci ret = -EINVAL; 478c2ecf20Sopenharmony_ci } 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_ci return ret ? ret : len; 508c2ecf20Sopenharmony_ci} 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_cistatic long rproc_device_ioctl(struct file *filp, unsigned int ioctl, unsigned long arg) 538c2ecf20Sopenharmony_ci{ 548c2ecf20Sopenharmony_ci struct rproc *rproc = container_of(filp->f_inode->i_cdev, struct rproc, cdev); 558c2ecf20Sopenharmony_ci void __user *argp = (void __user *)arg; 568c2ecf20Sopenharmony_ci s32 param; 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_ci switch (ioctl) { 598c2ecf20Sopenharmony_ci case RPROC_SET_SHUTDOWN_ON_RELEASE: 608c2ecf20Sopenharmony_ci if (copy_from_user(¶m, argp, sizeof(s32))) 618c2ecf20Sopenharmony_ci return -EFAULT; 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_ci rproc->cdev_put_on_release = !!param; 648c2ecf20Sopenharmony_ci break; 658c2ecf20Sopenharmony_ci case RPROC_GET_SHUTDOWN_ON_RELEASE: 668c2ecf20Sopenharmony_ci param = (s32)rproc->cdev_put_on_release; 678c2ecf20Sopenharmony_ci if (copy_to_user(argp, ¶m, sizeof(s32))) 688c2ecf20Sopenharmony_ci return -EFAULT; 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_ci break; 718c2ecf20Sopenharmony_ci default: 728c2ecf20Sopenharmony_ci dev_err(&rproc->dev, "Unsupported ioctl\n"); 738c2ecf20Sopenharmony_ci return -EINVAL; 748c2ecf20Sopenharmony_ci } 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_ci return 0; 778c2ecf20Sopenharmony_ci} 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_cistatic int rproc_cdev_release(struct inode *inode, struct file *filp) 808c2ecf20Sopenharmony_ci{ 818c2ecf20Sopenharmony_ci struct rproc *rproc = container_of(inode->i_cdev, struct rproc, cdev); 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_ci if (rproc->cdev_put_on_release && rproc->state == RPROC_RUNNING) 848c2ecf20Sopenharmony_ci rproc_shutdown(rproc); 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_ci return 0; 878c2ecf20Sopenharmony_ci} 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_cistatic const struct file_operations rproc_fops = { 908c2ecf20Sopenharmony_ci .write = rproc_cdev_write, 918c2ecf20Sopenharmony_ci .unlocked_ioctl = rproc_device_ioctl, 928c2ecf20Sopenharmony_ci .compat_ioctl = compat_ptr_ioctl, 938c2ecf20Sopenharmony_ci .release = rproc_cdev_release, 948c2ecf20Sopenharmony_ci}; 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_ciint rproc_char_device_add(struct rproc *rproc) 978c2ecf20Sopenharmony_ci{ 988c2ecf20Sopenharmony_ci int ret; 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_ci cdev_init(&rproc->cdev, &rproc_fops); 1018c2ecf20Sopenharmony_ci rproc->cdev.owner = THIS_MODULE; 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_ci rproc->dev.devt = MKDEV(MAJOR(rproc_major), rproc->index); 1048c2ecf20Sopenharmony_ci cdev_set_parent(&rproc->cdev, &rproc->dev.kobj); 1058c2ecf20Sopenharmony_ci ret = cdev_add(&rproc->cdev, rproc->dev.devt, 1); 1068c2ecf20Sopenharmony_ci if (ret < 0) 1078c2ecf20Sopenharmony_ci dev_err(&rproc->dev, "Failed to add char dev for %s\n", rproc->name); 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_ci return ret; 1108c2ecf20Sopenharmony_ci} 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_civoid rproc_char_device_remove(struct rproc *rproc) 1138c2ecf20Sopenharmony_ci{ 1148c2ecf20Sopenharmony_ci cdev_del(&rproc->cdev); 1158c2ecf20Sopenharmony_ci} 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_civoid __init rproc_init_cdev(void) 1188c2ecf20Sopenharmony_ci{ 1198c2ecf20Sopenharmony_ci int ret; 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_ci ret = alloc_chrdev_region(&rproc_major, 0, NUM_RPROC_DEVICES, "remoteproc"); 1228c2ecf20Sopenharmony_ci if (ret < 0) 1238c2ecf20Sopenharmony_ci pr_err("Failed to alloc rproc_cdev region, err %d\n", ret); 1248c2ecf20Sopenharmony_ci} 125