/* * Copyright (C) 2015-2016 Intel Corp. All rights reserved * * 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; version 2 of the License. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include #include #include #include #include #include #include "rpmb-cdev.h" static dev_t rpmb_devt; #define RPMB_MAX_DEVS MINORMASK #define RPMB_DEV_OPEN 0 /** single open bit (position) */ /* from MMC_IOC_MAX_CMDS */ #define RPMB_MAX_FRAMES 255 /** * rpmb_open - the open function * * @inode: pointer to inode structure * @fp: pointer to file structure * * Return: 0 on success, <0 on error */ static int rpmb_open(struct inode *inode, struct file *fp) { struct rpmb_dev *rdev; rdev = container_of(inode->i_cdev, struct rpmb_dev, cdev); if (!rdev) return -ENODEV; /* the rpmb is single open! */ if (test_and_set_bit(RPMB_DEV_OPEN, &rdev->status)) return -EBUSY; mutex_lock(&rdev->lock); fp->private_data = rdev; mutex_unlock(&rdev->lock); return nonseekable_open(inode, fp); } /** * rpmb_open - the open function * * @inode: pointer to inode structure * @fp: pointer to file structure * * Return: 0 on success, <0 on error */ static int rpmb_release(struct inode *inode, struct file *fp) { struct rpmb_dev *rdev = fp->private_data; clear_bit(RPMB_DEV_OPEN, &rdev->status); return 0; } /** * rpmb_cmd_copy_from_user - copy rpmb command from the user space * * @cmd: internal cmd structure * @ucmd: user space cmd structure * * Return: 0 on success, <0 on error */ static int rpmb_cmd_copy_from_user(struct rpmb_cmd *cmd, struct rpmb_ioc_cmd __user *ucmd) { size_t sz; struct rpmb_frame *frames; u64 frames_ptr; if (get_user(cmd->flags, &ucmd->flags)) return -EFAULT; if (get_user(cmd->nframes, &ucmd->nframes)) return -EFAULT; if (cmd->nframes > RPMB_MAX_FRAMES) return -EOVERFLOW; if (!cmd->nframes) return -EINVAL; /* some archs have issues with 64bit get_user */ if (copy_from_user(&frames_ptr, &ucmd->frames_ptr, sizeof(frames_ptr))) return -EFAULT; sz = cmd->nframes * sizeof(struct rpmb_frame); frames = memdup_user((const void __user *)frames_ptr, sz); if (IS_ERR(frames)) return PTR_ERR(frames); cmd->frames = frames; return 0; } /** * rpmb_cmd_copy_to_user - copy rpmb command to the user space * * @ucmd: user space cmd structure * @cmd: internal cmd structure * * Return: 0 on success, <0 on error */ static int rpmb_cmd_copy_to_user(struct rpmb_ioc_cmd __user *ucmd, struct rpmb_cmd *cmd) { size_t sz; u64 frames_ptr; sz = cmd->nframes * sizeof(struct rpmb_frame); if (copy_from_user(&frames_ptr, &ucmd->frames_ptr, sizeof(frames_ptr))) return -EFAULT; /* some archs have issues with 64bit get_user */ if (copy_to_user((void __user *)frames_ptr, cmd->frames, sz)) return -EFAULT; return 0; } /** * rpmb_ioctl_seq_cmd: issue rpmb sequence * * @rdev: rpmb device * @ptr: rpmb cmd sequence * * RPMB_IOC_SEQ_CMD handler * * Return: 0 on success, <0 on error */ static long rpmb_ioctl_seq_cmd(struct rpmb_dev *rdev, struct rpmb_ioc_seq_cmd __user *ptr) { __u64 ncmds; struct rpmb_cmd *cmds; struct rpmb_ioc_cmd __user *ucmds; int i; int ret; /* The caller must have CAP_SYS_RAWIO, like mmc ioctl */ if (!capable(CAP_SYS_RAWIO)) return -EPERM; /* some archs have issues with 64bit get_user */ if (copy_from_user(&ncmds, &ptr->num_of_cmds, sizeof(ncmds))) return -EFAULT; if (ncmds > 3) { dev_notice(&rdev->dev, "supporting up to 3 packets (%llu)\n", ncmds); return -EINVAL; } cmds = kcalloc(ncmds, sizeof(*cmds), GFP_KERNEL); if (!cmds) return -ENOMEM; ucmds = (struct rpmb_ioc_cmd __user *)ptr->cmds; for (i = 0; i < ncmds; i++) { ret = rpmb_cmd_copy_from_user(&cmds[i], &ucmds[i]); if (ret) goto out; } ret = rpmb_cmd_seq(rdev, cmds, ncmds); if (ret) goto out; for (i = 0; i < ncmds; i++) { ret = rpmb_cmd_copy_to_user(&ucmds[i], &cmds[i]); if (ret) goto out; } out: for (i = 0; i < ncmds; i++) kfree(cmds[i].frames); kfree(cmds); return ret; } /** * rpmb_ioctl_req_cmd: issue rpmb request command * * @rdev: rpmb device * @ptr: rpmb request command * * RPMB_IOC_REQ_CMD handler * * Return: 0 on success; < 0 on error */ static long rpmb_ioctl_req_cmd(struct rpmb_dev *rdev, struct rpmb_ioc_req_cmd __user *ptr) { struct rpmb_data rpmbd; u64 req_type; int ret; /* some archs have issues with 64bit get_user */ if (copy_from_user(&req_type, &ptr->req_type, sizeof(req_type))) return -EFAULT; if (req_type >= U16_MAX) return -EINVAL; memset(&rpmbd, 0, sizeof(rpmbd)); rpmbd.req_type = req_type & 0xFFFF; ret = rpmb_cmd_copy_from_user(&rpmbd.icmd, &ptr->icmd); if (ret) goto out; ret = rpmb_cmd_copy_from_user(&rpmbd.ocmd, &ptr->ocmd); if (ret) goto out; ret = rpmb_cmd_req(rdev, &rpmbd); if (ret) goto out; ret = rpmb_cmd_copy_to_user(&ptr->ocmd, &rpmbd.ocmd); out: kfree(rpmbd.icmd.frames); kfree(rpmbd.ocmd.frames); return ret; } /** * rpmb_ioctl - rpmb ioctl dispatcher * * @fp: a file pointer * @cmd: ioctl command RPMB_IOC_REQ_CMD or RPMB_IOC_SEQ_CMD * @arg: ioctl data: rpmb_ioc_req_cmd or rpmb_ioc_seq_cmd * * Return: 0 on success; < 0 on error */ static long rpmb_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) { struct rpmb_dev *rdev = fp->private_data; void __user *ptr = (void __user *)arg; switch (cmd) { case RPMB_IOC_REQ_CMD: return rpmb_ioctl_req_cmd(rdev, ptr); case RPMB_IOC_SEQ_CMD: return rpmb_ioctl_seq_cmd(rdev, ptr); default: dev_notice(&rdev->dev, "unsupported ioctl 0x%x.\n", cmd); return -ENOIOCTLCMD; } } #ifdef CONFIG_COMPAT static long rpmb_compat_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) { return rpmb_ioctl(fp, cmd, (unsigned long)compat_ptr(arg)); } #endif /* CONFIG_COMPAT */ static const struct file_operations rpmb_fops = { .open = rpmb_open, .release = rpmb_release, .unlocked_ioctl = rpmb_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl = rpmb_compat_ioctl, #endif .owner = THIS_MODULE, .llseek = noop_llseek, }; void rpmb_cdev_prepare(struct rpmb_dev *rdev) { rdev->dev.devt = MKDEV(MAJOR(rpmb_devt), rdev->id); rdev->cdev.owner = THIS_MODULE; cdev_init(&rdev->cdev, &rpmb_fops); } void rpmb_cdev_add(struct rpmb_dev *rdev) { cdev_add(&rdev->cdev, rdev->dev.devt, 1); } void rpmb_cdev_del(struct rpmb_dev *rdev) { if (rdev->dev.devt) cdev_del(&rdev->cdev); } int __init rpmb_cdev_init(void) { int ret; ret = alloc_chrdev_region(&rpmb_devt, 0, RPMB_MAX_DEVS, "rpmb"); if (ret < 0) pr_notice("unable to allocate char dev region\n"); return ret; } void __exit rpmb_cdev_exit(void) { if (rpmb_devt) unregister_chrdev_region(rpmb_devt, RPMB_MAX_DEVS); }