blob: 2ea82a73be4a19a451aa976c1789a6a50bea8f79 [file] [log] [blame]
// ------------------------------------------------------------------------
//
// (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.
//
// 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.
//
// 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
//
// ------------------------------------------------------------------------
//
// Project:
//
// ESM Host Library
//
// Description:
//
// ESM Host Library Driver: Linux kernel module
//
// ------------------------------------------------------------------------
#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/of_device.h>
#include <linux/debugfs.h>
#include "hdcp.h"
#define ELP_DEBUG() pr_info("esm %s[%d]\n", __func__, __LINE__)
#define MAX_ESM_DEVICES 6
static int verbose;
static bool randomize_mem;
module_param(randomize_mem, bool, 0644);
MODULE_PARM_DESC(noverify, "Wipe memory allocations on startup (for debug)");
struct esm_device {
int allocated, initialized;
int code_loaded;
int code_is_phys_mem;
dma_addr_t code_base;
u32 code_size;
u8 *code;
int data_is_phys_mem;
dma_addr_t data_base;
u32 data_size;
u8 *data;
struct dentry *esm_blob;
struct debugfs_blob_wrapper blob;
struct resource *hpi_resource;
u8 __iomem *hpi;
struct device esm_code_dev;
struct device esm_data_dev;
};
static struct esm_device esm_devices[MAX_ESM_DEVICES];
/* 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;
}
/* 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 0; /* return -EBUSY; */
if (copy_from_user(esm->code, &arg->data, head.len) != 0)
return -EFAULT;
/* esm->code_loaded = 1; */
return 0;
}
/* 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;
}
/* 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;
}
/* 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;
}
/* 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 = ioread32(esm->hpi + reg.offset);
if (verbose)
pr_info("R reg.value = 0x%x, reg.offset = 0x%x\n",
reg.value, reg.offset);
if (copy_to_user(arg, &reg, sizeof(reg)) != 0)
return -EFAULT;
return 0;
}
/* 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;
if (verbose)
pr_info("W reg.value = 0x%x, reg.offset = 0x%x\n",
reg.value, reg.offset);
iowrite32(reg.value, esm->hpi + reg.offset);
return 0;
}
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;
}
static struct dentry *esm_debugfs;
/*static struct dentry *esm_blob;*/
static void free_dma_areas(struct esm_device *esm)
{
if (!esm->code_is_phys_mem && esm->code) {
dma_free_coherent(NULL, esm->code_size, esm->code,
esm->code_base);
esm->code = NULL;
}
if (!esm->data_is_phys_mem && esm->data) {
dma_free_coherent(NULL, esm->data_size, esm->data,
esm->data_base);
esm->data = NULL;
}
debugfs_remove_recursive(esm_debugfs);
}
static int alloc_dma_areas(struct esm_device *esm,
const struct esm_ioc_meminfo *info)
{
char blobname[32];
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->esm_code_dev.coherent_dma_mask = DMA_BIT_MASK(32);
esm->esm_code_dev.dma_mask =
&esm->esm_code_dev.coherent_dma_mask;
of_dma_configure(&esm->esm_code_dev, esm->esm_code_dev.of_node,
true);
esm->code = dma_alloc_coherent(&esm->esm_code_dev,
esm->code_size,
&esm->code_base,
GFP_KERNEL);
pr_info("the esm code address is %p\n", esm->code);
if (!esm->code) {
free_dma_areas(esm);
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->esm_data_dev.coherent_dma_mask = DMA_BIT_MASK(32);
esm->esm_data_dev.dma_mask =
&esm->esm_data_dev.coherent_dma_mask;
of_dma_configure(&esm->esm_data_dev, esm->esm_data_dev.of_node,
true);
esm->data = dma_alloc_coherent(&esm->esm_data_dev,
esm->data_size,
&esm->data_base,
GFP_KERNEL);
pr_info("the esm data address is %p\n", esm->data);
if (!esm->data) {
free_dma_areas(esm);
return -ENOMEM;
}
}
if (randomize_mem) {
prandom_bytes(esm->code, esm->code_size);
prandom_bytes(esm->data, esm->data_size);
}
if (!esm_debugfs) {
esm_debugfs = debugfs_create_dir("esm", NULL);
if (!esm_debugfs)
return -ENOENT;
}
memset(blobname, 0, sizeof(blobname));
sprintf(blobname, "blob.%x", info->hpi_base);
esm->blob.data = (void *)esm->data;
esm->blob.size = esm->data_size;
esm->esm_blob = debugfs_create_blob(blobname, 0644, esm_debugfs,
&esm->blob);
return 0;
}
/* 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;
char region_name[20];
int rc;
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;
/* pr_info("info.hpi_base = 0x%x\n", info.hpi_base); */
/* hpi_mem =
* request_mem_region(info.hpi_base, 128, "esm-hpi");
*/
sprintf(region_name, "ESM-%X", info.hpi_base);
pr_info("info.hpi_base = 0x%x region_name:%s\n",
info.hpi_base, region_name);
hpi_mem = request_mem_region(info.hpi_base, 0x100, region_name);
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;
}
/*every time clear the data buff*/
if (esm->data)
memset(esm->data, 0, esm->data_size);
pr_info("esm data = %p size:%d\n",
esm->data, esm->data_size);
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;
}
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;
}
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);
default:
break;
}
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",
.fops = &hld_file_operations,
};
int __init esm_init(void)
{
return misc_register(&hld_device);
}
void __exit esm_exit(void)
{
int i;
misc_deregister(&hld_device);
for (i = 0; i < MAX_ESM_DEVICES; i++)
free_esm_slot(&esm_devices[i]);
}
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Synopsys, Inc.");
MODULE_DESCRIPTION("ESM Linux Host Library Driver");
module_param(verbose, int, 0644);
MODULE_PARM_DESC(verbose, "Enable (1) or disable (0) the debug traces.");