/*++
* WonderMedia Codec Lock driver
*
* Copyright (c) 2008-2013 WonderMedia Technologies, Inc.
*
* 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, either version 2 of the License, or
* (at your option) any later version.
*
* 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, see .
*
* WonderMedia Technologies, Inc.
* 4F, 533, Chung-Cheng Road, Hsin-Tien, Taipei 231, R.O.C
--*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "com-lock.h"
#define WMT_LOCK_NAME "wmt-lock"
#define WMT_LOCK_MAJOR MB_MAJOR /* same as memblock driver */
#define WMT_LOCK_MINOR 1 /* 0 is /dev/mbdev, 1 is /dev/wmt-lock */
#define MAX_LOCK_JDEC 1 /* Max lock number for JPEG decoder */
/* Even for multi-decoding, this value should <= 4 */
#define MAX_LOCK_VDEC 4
#define MAX_LOCK_TYPE 3 /* JPEG, MSVD & encoders */
#define MAX_LOCK_OWNER (MAX_LOCK_JDEC + MAX_LOCK_VDEC)
static struct class *wmt_lock_class;
#undef WMT_LOCK_DEBUG
/*#define WMT_LOCK_DEBUG*/
#ifdef WMT_LOCK_DEBUG
#define P_DEBUG(fmt, args...) printk(KERN_INFO "["WMT_LOCK_NAME"] " fmt , ## args)
#else
#define P_DEBUG(fmt, args...) ((void)(0))
#endif
#define P_INFO(fmt, args...) printk(KERN_INFO "[wmt-lock] " fmt , ## args)
#define P_WARN(fmt, args...) printk(KERN_WARNING "[wmt-lock] *W* " fmt, ## args)
#define P_ERROR(fmt, args...) printk(KERN_ERR "[wmt-lock] *E* " fmt , ## args)
static struct cdev wmt_lock_cdev;
struct lock_owner {
pid_t pid;
void *private_data;
char comm[TASK_COMM_LEN];
const char *type;
};
struct semaphore lock_sem_jpeg;
struct semaphore lock_sem_video;
struct semaphore lock_sem_encoder;
struct lock_owner_s {
const char *type;
struct semaphore *sem;
unsigned int max_lock_num;
struct lock_owner owners[MAX_LOCK_OWNER];
};
struct lock_owner_s gLockers[MAX_LOCK_TYPE];
static spinlock_t gSpinlock;
/*!*************************************************************************
* get_sema
*
* Private Function
*
* \brief
*
* \retval ponters to specified semaphore
*/
static struct semaphore *get_sema(long lock_type)
{
return gLockers[lock_type].sem;
}
/*!*************************************************************************
* set_owner
*
* Private Function
*
* \brief
*
* \retval 0 if success, error code if fail
*/
static struct lock_owner *set_owner(long lock_type, struct file *filp)
{
struct lock_owner *o;
int i;
for (i = 0; i < gLockers[lock_type].max_lock_num; i++) {
if (gLockers[lock_type].owners[i].pid == 0) {
o = &gLockers[lock_type].owners[i];
o->private_data = filp->private_data;
o->pid = current->pid;
memcpy(o->comm, current->comm, TASK_COMM_LEN);
return o;
}
}
P_ERROR("Set owner fail because of %s lock full\n",
gLockers[lock_type].type);
return 0;
}
/*!*************************************************************************
* get_owner
*
* Private Function
*
* \brief
*
* \retval 0 if success, error code if fail
*/
static struct lock_owner *get_owner(long lock_type)
{
struct lock_owner *o;
int i;
for (i = 0; i < gLockers[lock_type].max_lock_num; i++) {
o = &gLockers[lock_type].owners[i];
if (o->pid == current->pid)
return o;
}
return NULL;
}
/*!*************************************************************************
* check_busy
*
* Private Function
*
* \brief
*
* \retval 0 if not busy, otherwise return 1
*/
static int check_busy(long lock_type)
{
struct lock_owner *o;
int i;
for (i = 0; i < gLockers[lock_type].max_lock_num; i++) {
o = &gLockers[lock_type].owners[i];
if (o->pid == current->pid)
return 1; /* Busy */
}
return 0;
}
/*!*************************************************************************
* lock_read_proc
*
* Private Function
*
* \brief
*
* \retval 0 if success, error code if fail
*/
static int lock_read_proc(
char *page, char **start,
off_t offset,
int len,
int *eof,
void *data)
{
char *p = page;
struct lock_owner *o;
int i, j;
for (i = 0; i < MAX_LOCK_TYPE; i++) {
p += sprintf(p, "------ %s lock ------\n", gLockers[i].type);
for (j = 0; j < gLockers[i].max_lock_num; j++) {
o = &gLockers[i].owners[j];
if (o->pid != 0)
p += sprintf(p, "%s lock : occupied by [%s,%d]\n",
gLockers[i].type, o->comm, o->pid);
else
p += sprintf(p, "%s lock : Free\n", gLockers[i].type);
}
}
return p - page;
}
/*!*************************************************************************
* wmt_lock_ioctl
*
* Private Function
*
* \brief
*
* \retval 0 if success, error code if fail
*/
static long wmt_lock_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
int ret = 0;
struct lock_owner *o;
struct semaphore *sem;
struct wmt_lock *lock_arg;
long timeout;
unsigned long flags = 0;
/* check ioctl type and number, if fail return EINVAL */
if (_IOC_TYPE(cmd) != LOCK_IOC_MAGIC) {
P_WARN("ioctl unknown cmd %X, type %X by [%s,%d]\n",
cmd, _IOC_TYPE(cmd), current->comm, current->pid);
return -EINVAL;
}
if (!access_ok(VERIFY_READ, (void __user *)arg, sizeof(struct wmt_lock))) {
P_WARN("ioctl access_ok failed, cmd %X, type %X by [%s,%d]\n",
cmd, _IOC_TYPE(cmd), current->comm, current->pid);
return -EFAULT;
}
lock_arg = (struct wmt_lock *)arg;
if (lock_arg->lock_type >= MAX_LOCK_TYPE || lock_arg->lock_type < 0) {
P_WARN("invalid lock type %ld by [%s,%d]\n",
lock_arg->lock_type, current->comm, current->pid);
return -E2BIG;
}
spin_lock_irqsave(&gSpinlock, flags);
sem = get_sema(lock_arg->lock_type);
switch (cmd) {
case IO_WMT_LOCK:
timeout = lock_arg->arg2;
/* check if the current thread already get the lock */
if (check_busy(lock_arg->lock_type)) {
P_WARN("Recursive %s lock by [%s,%d]\n",
gLockers[lock_arg->lock_type].type,
current->comm, current->pid);
return -EBUSY;
}
if (timeout == 0) {
ret = down_trylock(sem);
if (ret)
ret = -ETIME; /* reasonable if lock holded by other */
} else if (timeout == -1) {
ret = down_interruptible(sem);
if (ret)
P_INFO("Require %s lock error %d by [%s,%d]\n",
gLockers[lock_arg->lock_type].type, ret,
current->comm, current->pid);
} else {
/* require lock with a timeout value, please beware
this function can't exit when interrupt */
spin_unlock_irqrestore(&gSpinlock, flags);
ret = down_timeout(sem, msecs_to_jiffies(timeout));
spin_lock_irqsave(&gSpinlock, flags);
}
if (ret == 0) {
o = set_owner(lock_arg->lock_type, filp);
if (o)
P_DEBUG("%s is locked by [%s,%d], seq %d\n",
o->type, o->comm, o->pid, (int)filp->private_data);
}
break;
case IO_WMT_UNLOCK:
o = get_owner(lock_arg->lock_type);
if (o == NULL) {
P_WARN("Unnecessary %s unlock from [%s,%d] when lock is free\n",
gLockers[lock_arg->lock_type].type,
current->comm, current->pid);
ret = -EACCES;
} else if (filp->private_data == o->private_data) {
P_DEBUG("%s is unlocked by [%s,%d], seq %d\n",
o->type, o->comm, o->pid, (int)filp->private_data);
o->pid = 0;
o->private_data = 0;
up(sem);
ret = 0;
} else{
P_WARN("Unexpected %s unlock from [%s,%d], hold by [%s,%d] now\n",
o->type, current->comm, current->pid, o->comm, o->pid);
ret = -EACCES;
}
break;
case IO_WMT_CHECK_WAIT:
if (sem->count > 0)
lock_arg->is_wait = 0;
else {
if ((sem->wait_list.prev == 0) && (sem->wait_list.next == 0))
lock_arg->is_wait = 0;
else
lock_arg->is_wait = 1;
}
P_INFO("sem->count: %d\n", sem->count);
P_INFO("lock_arg->is_wait: %d\n", lock_arg->is_wait);
break;
default:
P_WARN("ioctl unknown cmd 0x%X by [%s,%d]\n",
cmd, current->comm, current->pid);
ret = -EINVAL;
}
spin_unlock_irqrestore(&gSpinlock, flags);
return ret;
}
/*!*************************************************************************
* wmt_lock_open
*
* Private Function
*
* \brief
*
* \retval 0 if success, error code if fail
*/
static int wmt_lock_open(struct inode *inode, struct file *filp)
{
static atomic_t lock_seq_id = ATOMIC_INIT(0);
/* use a sequence number as the file open id */
filp->private_data = (void *)(atomic_add_return(1, &lock_seq_id));
P_DEBUG("open by [%s,%d], seq %d\n",
current->comm, current->pid, (int)filp->private_data);
return 0;
}
/*!*************************************************************************
* wmt_lock_release
*
* Private Function
*
* \brief
*
* \retval 0 if success, error code if fail
*/
static int wmt_lock_release(struct inode *inode, struct file *filp)
{
int i, j;
for (i = 0; i < MAX_LOCK_TYPE; i++) {
for (j = 0; j < gLockers[i].max_lock_num; j++) {
struct lock_owner *o = &gLockers[i].owners[j];
if (o->pid != 0 && filp->private_data == o->private_data) {
P_WARN("Auto free %s lock hold by [%s,%d]\n",
o->type, o->comm, o->pid);
o->pid = 0;
o->private_data = 0;
up(get_sema(i));
}
}
}
P_DEBUG("Release by [%s,%d], seq %d\n",
current->comm, current->pid, (int)filp->private_data);
return 0;
}
static const struct file_operations wmt_lock_fops = {
.owner = THIS_MODULE,
.open = wmt_lock_open,
.unlocked_ioctl = wmt_lock_ioctl,
.release = wmt_lock_release,
};
static void check_multi_vd_count(int *max_vd_count)
{
extern int wmt_getsyspara(char *varname, unsigned char *varval, int *varlen);
char buf[80] = {0};
int varlen = 80;
int max_count = 1;
/* Read u-boot parameter to decide value of wmt_codec_debug */
/*----------------------------------------------------------------------
Check wmt.codec.debug
----------------------------------------------------------------------*/
if (wmt_getsyspara("wmt.multi.vd.max", buf, &varlen) == 0)
max_count = simple_strtol(buf, NULL, 10);
if (max_count < 1)
max_count = 1;
if (max_count > MAX_LOCK_VDEC)
max_count = MAX_LOCK_VDEC;
*max_vd_count = max_count;
return;
} /* End of check_debug_option() */
/*!*************************************************************************
* wmt_lock_init
*
* Private Function
*
* \brief
*
* \retval 0 if success, error code if fail
*/
static int __init wmt_lock_init(void)
{
dev_t dev_id;
int max_vd_count;
int ret, i, j;
dev_id = MKDEV(WMT_LOCK_MAJOR, WMT_LOCK_MINOR);
ret = register_chrdev_region(dev_id, 1, WMT_LOCK_NAME);
if (ret < 0) {
P_ERROR("can't register %s device %d:%d, ret %d\n",
WMT_LOCK_NAME, WMT_LOCK_MAJOR, WMT_LOCK_MINOR, ret);
return ret;
}
cdev_init(&wmt_lock_cdev, &wmt_lock_fops);
ret = cdev_add(&wmt_lock_cdev, dev_id, 1);
if (ret) {
P_ERROR("cdev add error(%d).\n", ret);
unregister_chrdev_region(dev_id, 1);
return ret;
}
/* let udev to handle /dev/wmt-lock */
wmt_lock_class = class_create(THIS_MODULE, WMT_LOCK_NAME);
device_create(wmt_lock_class, NULL, dev_id, NULL, "%s", WMT_LOCK_NAME);
create_proc_read_entry(WMT_LOCK_NAME, 0, NULL, lock_read_proc, NULL);
P_INFO("init ok, major=%d, minor=%d\n", WMT_LOCK_MAJOR, WMT_LOCK_MINOR);
spin_lock_init(&gSpinlock);
check_multi_vd_count(&max_vd_count);
/* Init sema for JPEG decoder */
sema_init(&lock_sem_jpeg, MAX_LOCK_JDEC);
gLockers[lock_jpeg].type = "jdec";
gLockers[lock_jpeg].sem = &lock_sem_jpeg;
gLockers[lock_jpeg].max_lock_num = MAX_LOCK_JDEC;
/* Init sema for MSVD decoders */
sema_init(&lock_sem_video, max_vd_count);
gLockers[lock_video].type = "vdec";
gLockers[lock_video].sem = &lock_sem_video;
gLockers[lock_video].max_lock_num = max_vd_count;
for (i = 0; i < MAX_LOCK_TYPE; i++) {
for (j = 0; j < gLockers[i].max_lock_num; j++) {
struct lock_owner *o = &gLockers[i].owners[j];
o->type = gLockers[i].type;
}
}
return ret;
}
/*!*************************************************************************
* wmt_lock_cleanup
*
* Private Function
*
* \brief
*
* \retval 0 if success, error code if fail
*/
static void __exit wmt_lock_cleanup(void)
{
dev_t dev_id = MKDEV(WMT_LOCK_MAJOR, WMT_LOCK_MINOR);
cdev_del(&wmt_lock_cdev);
/* let udev to handle /dev/wmt-lock */
device_destroy(wmt_lock_class, dev_id);
class_destroy(wmt_lock_class);
unregister_chrdev_region(dev_id, 1);
remove_proc_entry(WMT_LOCK_NAME, NULL);
P_INFO("cleanup done\n");
}
module_init(wmt_lock_init);
module_exit(wmt_lock_cleanup);
MODULE_AUTHOR("WonderMedia Technologies, Inc.");
MODULE_DESCRIPTION("WMT Codec Lock device driver");
MODULE_LICENSE("GPL");