blob: dbd19a6f96c80b9cb38f1d7d3187cc150d692583 [file] [log] [blame]
/*
* drivers/amlogic/media/vin/tvin/hdmirx/hdcp_main.c
*
* (C) COPYRIGHT 2014 - 2015 SYNOPSYS, INC.
*
* 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.
*
* is 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/random.h>
#include <linux/miscdevice.h>
#include <linux/dma-mapping.h>
#include <linux/uaccess.h>
#include <linux/fs.h>
#include <linux/delay.h>
#include <linux/moduleparam.h>
#include <linux/netlink.h>
#include <linux/proc_fs.h>
#include <linux/debugfs.h>
#include <linux/version.h>
#include "hdcp_rx_main.h"
#include "hdmi_rx_repeater.h"
#include "hdmi_rx_drv.h"
#include "hdmi_rx_hw.h"
#define MAX_ESM_DEVICES 6
#define ESM_RX_HPI_BASE 0xd0076000
static bool randomize_mem;
static int is_esmmem_created;
/*
* module_param(randomize_mem, bool, 0664);
* MODULE_PARM_DESC(noverify, "Wipe mem allocations on startup (for debug)");
*/
struct esm_device {
int allocated, initialized;
int code_loaded;
int code_is_phys_mem;
dma_addr_t code_base;
uint32_t code_size;
uint8_t *code;
int data_is_phys_mem;
dma_addr_t data_base;
uint32_t data_size;
uint8_t *data;
struct debugfs_blob_wrapper blob;
struct resource *hpi_resource;
uint8_t __iomem *hpi;
};
static struct esm_device esm_devices[MAX_ESM_DEVICES];
static const char *MY_TAG = "ESM HLD: ";
/* get_meminfo - ESM_IOC_MEMINFO implementation */
static long get_meminfo(struct esm_device *esm, void __user *arg)
{
struct esm_ioc_meminfo info = {
.hpi_base = esm->hpi_resource->start,
.code_base = esm->code_base,
.code_size = esm->code_size,
.data_base = esm->data_base,
.data_size = esm->data_size,
};
if (copy_to_user(arg, &info, sizeof(info)) != 0)
return -EFAULT;
return 0;
}
/* load_code - ESM_IOC_LOAD_CODE implementation */
static long load_code(struct esm_device *esm,
struct esm_ioc_code __user *arg)
{
struct esm_ioc_code head;
if (copy_from_user(&head, arg, sizeof(head)) != 0)
return -EFAULT;
if (head.len > esm->code_size)
return -ENOSPC;
/* if (esm->code_loaded) */
/* return -EBUSY; */
if (copy_from_user(esm->code, &arg->data, head.len) != 0)
return -EFAULT;
pr_info("load code...\n");
return 0;
}
/* write_data - ESM_IOC_WRITE_DATA implementation */
static long write_data(struct esm_device *esm,
struct esm_ioc_data __user *arg)
{
struct esm_ioc_data head;
if (copy_from_user(&head, arg, sizeof(head)) != 0)
return -EFAULT;
if (esm->data_size < head.len)
return -ENOSPC;
if (esm->data_size - head.len < head.offset)
return -ENOSPC;
if (copy_from_user(esm->data + head.offset, &arg->data, head.len) != 0)
return -EFAULT;
return 0;
}
/* read_data - ESM_IOC_READ_DATA implementation */
static long read_data(struct esm_device *esm,
struct esm_ioc_data __user *arg)
{
struct esm_ioc_data head;
if (copy_from_user(&head, arg, sizeof(head)) != 0)
return -EFAULT;
if (esm->data_size < head.len)
return -ENOSPC;
if (esm->data_size - head.len < head.offset)
return -ENOSPC;
if (copy_to_user(&arg->data, esm->data + head.offset, head.len) != 0)
return -EFAULT;
return 0;
}
/* set_data - ESM_IOC_MEMSET_DATA implementation */
static long set_data(struct esm_device *esm, void __user *arg)
{
union {
struct esm_ioc_data data;
unsigned char buf[sizeof(struct esm_ioc_data) + 1];
} u;
if (copy_from_user(&u.data, arg, sizeof(u.data)) != 0)
return -EFAULT;
if (esm->data_size < u.data.len)
return -ENOSPC;
if (esm->data_size - u.data.len < u.data.offset)
return -ENOSPC;
memset(esm->data + u.data.offset, u.data.data[0], u.data.len);
return 0;
}
/* hpi_read - ESM_IOC_READ_HPI implementation */
static long hpi_read(struct esm_device *esm, void __user *arg)
{
struct esm_ioc_hpi_reg reg;
if (copy_from_user(&reg, arg, sizeof(reg)) != 0)
return -EFAULT;
if ((reg.offset & 3) || reg.offset >= resource_size(esm->hpi_resource))
return -EINVAL;
reg.value = rx_hdcp22_rd(reg.offset);
if (copy_to_user(arg, &reg, sizeof(reg)) != 0)
return -EFAULT;
return 0;
}
/* hpi_write - ESM_IOC_WRITE_HPI implementation */
static long hpi_write(struct esm_device *esm, void __user *arg)
{
struct esm_ioc_hpi_reg reg;
if (copy_from_user(&reg, arg, sizeof(reg)) != 0)
return -EFAULT;
if ((reg.offset & 3) || reg.offset >= resource_size(esm->hpi_resource))
return -EINVAL;
rx_hdcp22_wr_reg(reg.offset, reg.value);
return 0;
}
/* alloc_esm_slot - alloc_esm_slot*/
static struct esm_device *alloc_esm_slot(
const struct esm_ioc_meminfo *info)
{
int i;
/* Check if we have a matching device (same HPI base) */
for (i = 0; i < MAX_ESM_DEVICES; i++) {
struct esm_device *slot = &esm_devices[i];
if ((slot->allocated) && (info->hpi_base) ==
(slot->hpi_resource->start))
return slot;
}
/* Find unused slot */
for (i = 0; i < MAX_ESM_DEVICES; i++) {
struct esm_device *slot = &esm_devices[i];
if (!slot->allocated) {
slot->allocated = 1;
return slot;
}
}
return NULL;
}
/* free_dma_areas - free_dma_areas*/
static void free_dma_areas(struct esm_device *esm)
{
if (!esm->code_is_phys_mem && esm->code) {
dma_free_coherent(hdmirx_dev, esm->code_size,
esm->code, esm->code_base);
esm->code = NULL;
}
if (!esm->data_is_phys_mem && esm->data) {
dma_free_coherent(hdmirx_dev, esm->data_size,
esm->data, esm->data_base);
esm->data = NULL;
}
}
static struct dentry *esm_rx_debugfs;
struct dentry *esm_rx_blob;
static int alloc_dma_areas(struct esm_device *esm,
const struct esm_ioc_meminfo *info)
{
esm->code_size = info->code_size;
esm->code_is_phys_mem = (info->code_base != 0);
if (esm->code_is_phys_mem) {
/* TODO: support highmem */
esm->code_base = info->code_base;
esm->code = phys_to_virt(esm->code_base);
} else {
esm->code = dma_alloc_coherent(hdmirx_dev, esm->code_size,
&esm->code_base, GFP_KERNEL);
if (!esm->code)
return -ENOMEM;
}
esm->data_size = info->data_size;
esm->data_is_phys_mem = (info->data_base != 0);
if (esm->data_is_phys_mem) {
esm->data_base = info->data_base;
esm->data = phys_to_virt(esm->data_base);
} else {
esm->data = dma_alloc_coherent(hdmirx_dev, esm->data_size,
&esm->data_base, GFP_KERNEL);
if (!esm->data) {
free_dma_areas(esm);
return -ENOMEM;
}
}
esm_rx_debugfs = debugfs_create_dir("esm_rx", NULL);
if (!esm_rx_debugfs)
return -ENOENT;
esm->blob.data = (void *)esm->data;
esm->blob.size = esm->data_size;
esm_rx_blob = debugfs_create_blob("blob",
0644, esm_rx_debugfs, &esm->blob);
if (randomize_mem) {
prandom_bytes(esm->code, esm->code_size);
prandom_bytes(esm->data, esm->data_size);
}
if (!is_esmmem_created) {
if ((esm->data_base) && (esm->code_base)) {
is_esmmem_created = 1;
pr_info("create /dev/esmmem\n");
}
}
return 0;
}
/* init - ESM_IOC_INIT implementation */
static long init(struct file *f, void __user *arg)
{
struct resource *hpi_mem;
struct esm_ioc_meminfo info;
struct esm_device *esm;
int rc;
pr_info("HDCP_RX22 VERSION: %s\n", HDCP_RX22_VER);
pr_info("ESM VERSION: %s\n", ESM_VERSION);
if (copy_from_user(&info, arg, sizeof(info)) != 0)
return -EFAULT;
esm = alloc_esm_slot(&info);
if (!esm)
return -EMFILE;
if (!esm->initialized) {
rc = alloc_dma_areas(esm, &info);
if (rc < 0)
goto err_free;
hpi_mem = request_mem_region(info.hpi_base, 128, "esm-hpi");
if (!hpi_mem) {
rc = -EADDRNOTAVAIL;
goto err_free;
}
esm->hpi = ioremap_nocache(hpi_mem->start,
resource_size(hpi_mem));
if (!esm->hpi) {
rc = -ENOMEM;
goto err_release_region;
}
esm->hpi_resource = hpi_mem;
esm->initialized = 1;
}
f->private_data = esm;
return 0;
err_release_region:
release_resource(hpi_mem);
err_free:
free_dma_areas(esm);
esm->initialized = 0;
esm->allocated = 0;
return rc;
}
/* free_esm_slot - free_esm_slot*/
static void free_esm_slot(struct esm_device *slot)
{
if (!slot->allocated)
return;
if (slot->initialized) {
iounmap(slot->hpi);
release_resource(slot->hpi_resource);
free_dma_areas(slot);
}
slot->initialized = 0;
slot->allocated = 0;
}
/* hld_ioctl - hld_ioctl*/
static long hld_ioctl(struct file *f,
unsigned int cmd, unsigned long arg)
{
struct esm_device *esm = f->private_data;
void __user *data = (void __user *)arg;
if (cmd == ESM_IOC_INIT)
return init(f, data);
else if (!esm)
return -EAGAIN;
switch (cmd) {
case ESM_IOC_INIT:
return init(f, data);
case ESM_IOC_MEMINFO:
return get_meminfo(esm, data);
case ESM_IOC_READ_HPI:
return hpi_read(esm, data);
case ESM_IOC_WRITE_HPI:
return hpi_write(esm, data);
case ESM_IOC_LOAD_CODE:
return load_code(esm, data);
case ESM_IOC_WRITE_DATA:
return write_data(esm, data);
case ESM_IOC_READ_DATA:
return read_data(esm, data);
case ESM_IOC_MEMSET_DATA:
return set_data(esm, data);
}
return -ENOTTY;
}
static const struct file_operations hld_file_operations = {
.unlocked_ioctl = hld_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = hld_ioctl,
#endif
.owner = THIS_MODULE,
};
static struct miscdevice hld_device = {
.minor = MISC_DYNAMIC_MINOR,
.name = "esm_rx",
.fops = &hld_file_operations,
};
static int __init hld_init(void)
{
pr_info("%sInitializing...\n", MY_TAG);
return misc_register(&hld_device);
}
module_init(hld_init);
static void __exit hld_exit(void)
{
int i;
misc_deregister(&hld_device);
for (i = 0; i < MAX_ESM_DEVICES; i++)
free_esm_slot(&esm_devices[i]);
}
module_exit(hld_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Synopsys, Inc.");
MODULE_DESCRIPTION("ESM Linux Host Library Driver");