#include <linux/blkdev.h>
#include <linux/errno.h>
#include <linux/fcntl.h>
#include <linux/file.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/uaccess.h>
static bool debug = false;
module_param(debug, bool, 0644);
#define ptn_dbg(fmt, args...) \
do { \
if (debug) { \
printk(KERN_DEBUG "[oem][%s] " fmt, \
__func__ , ## args); \
} \
} while(0)
static int ptn_open_cnt = 0;
static loff_t g_ptn_size = 0;
static struct file *ptn_filp;
static struct kobject *ptn_kobj;
static unsigned char PTN_FILE[64] = {"/dev/block/mmcblk0p13"};
DEFINE_MUTEX(ptn_lock);
static loff_t ptn_llseek(struct file *file, loff_t offset, int origin)
{
int ret = 0;
mutex_lock(&ptn_lock);
switch (origin) {
case SEEK_SET:
if (offset < 0 || (unsigned int) offset > g_ptn_size) {
ret = -EINVAL;
break;
}
file->f_pos = offset;
ret = file->f_pos;
break;
case SEEK_CUR:
if ((file->f_pos + offset) < 0 || (file->f_pos + offset) > g_ptn_size) {
ret = -EINVAL;
break;
}
file->f_pos += offset;
ret = file->f_pos;
break;
case SEEK_END:
offset += g_ptn_size;
if (offset < 0 || offset > g_ptn_size) {
ret = -EINVAL;
break;
}
file->f_pos = offset;
ret = file->f_pos;
break;
default:
ret = -EINVAL;
}
mutex_unlock(&ptn_lock);
return ret;
}
static ssize_t ptn_read(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
{
loff_t offset = *ppos;
unsigned nread = 0;
if (IS_ERR(ptn_filp)) {
return -EFAULT;
}
if (*ppos > g_ptn_size || (*ppos + count ) > g_ptn_size) {
return 0;
}
mutex_lock(&ptn_lock);
nread = vfs_read(ptn_filp, buf, count, &offset);
if (nread < 0) {
mutex_unlock(&ptn_lock);
ptn_dbg("FATAL, ppos: %lld, offset: %lld, count: %d, nread: %d\n",
*ppos, offset, count, nread);
return nread;
}
ptn_dbg("ppos: %lld, count: %d, nread: %d\n", *ppos, count, nread);
*ppos += nread;
mutex_unlock(&ptn_lock);
return nread;
}
static ssize_t ptn_write(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
loff_t offset = *ppos;
int nwritten;
if (IS_ERR(ptn_filp)) {
return -EFAULT;
}
if (*ppos > g_ptn_size || (*ppos + count ) > g_ptn_size) {
return 0;
}
mutex_lock(&ptn_lock);
nwritten = vfs_write(ptn_filp, buf, count, &offset);
if (nwritten < 0) {
mutex_unlock(&ptn_lock);
ptn_dbg("FATAL, ppos: %lld, offset: %lld, count: %d, nwritten: %d\n",
*ppos, offset, count, nwritten);
return nwritten;
}
ptn_dbg("ppos: %lld, count: %d, nwritten: %d\n", *ppos, count, nwritten);
*ppos += nwritten;
mutex_unlock(&ptn_lock);
return nwritten;
}
static int ptn_open(struct inode *inode, struct file *file)
{
struct inode *ptn_inode = NULL;
loff_t size;
int ro = 0;
int rc = 0;
if (ptn_open_cnt > 0) {
return -EBUSY;
}
mutex_lock(&ptn_lock);
ptn_open_cnt++;
g_ptn_size = 0;
ptn_filp = filp_open(PTN_FILE, O_RDWR | O_LARGEFILE, 0);
if (PTR_ERR(ptn_filp) == -EROFS || PTR_ERR(ptn_filp) == -EACCES) {
ro = 1;
}
if (ro) {
ptn_filp = filp_open(PTN_FILE, O_RDONLY | O_LARGEFILE, 0);
}
if (IS_ERR(ptn_filp)) {
mutex_unlock(&ptn_lock);
ptn_dbg("unable to open backing file: %s\n", PTN_FILE);
return PTR_ERR(ptn_filp);
}
ptn_inode = file_inode(ptn_filp);
size = i_size_read(ptn_inode->i_mapping->host);
if (size < 0) {
ptn_dbg("unable to find file size: %s\n", PTN_FILE);
rc = (int) size;
goto drop_ptn_filp;
}
g_ptn_size = size;
mutex_unlock(&ptn_lock);
ptn_dbg("succeeded in opening the ptn file: %s, g_ptn_size: %lld\n",
PTN_FILE, g_ptn_size);
return 0;
drop_ptn_filp:
if (ptn_filp) {
filp_close(ptn_filp, NULL);
ptn_filp = NULL;
}
mutex_unlock(&ptn_lock);
return rc;
}
static int ptn_release(struct inode *inode, struct file *file)
{
mutex_lock(&ptn_lock);
if (ptn_open_cnt <= 0) {
goto drop_ptn_filp;
}
ptn_open_cnt--;
if (0 != ptn_open_cnt) {
mutex_unlock(&ptn_lock);
return 0;
}
drop_ptn_filp:
if (ptn_filp) {
filp_close(ptn_filp, NULL);
ptn_filp = NULL;
ptn_dbg("succeeded in dropping the ptn file: %s\n", PTN_FILE);
}
mutex_unlock(&ptn_lock);
return 0;
}
static const struct file_operations ptn_fops = {
.owner = THIS_MODULE,
.llseek = ptn_llseek,
.read = ptn_read,
.write = ptn_write,
.open = ptn_open,
.release = ptn_release,
};
static struct miscdevice ptn_dev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "misc-ptn",
.fops = &ptn_fops
};
static ssize_t ptn_show(struct kobject *obj,
struct kobj_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%s\n", PTN_FILE);
}
static ssize_t ptn_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t count)
{
mutex_lock(&ptn_lock);
strlcpy(PTN_FILE, buf, count);
mutex_unlock(&ptn_lock);
ptn_dbg("%s\n", PTN_FILE);
return strlen(PTN_FILE) + 1;
}
static struct kobj_attribute ptn_obj_attr = {
.attr = {
.mode = S_IRUGO | S_IWUGO,
.name = "blk-file",
},
.show = ptn_show,
.store = ptn_store,
};
static struct attribute *ptn_attr[] = {
&ptn_obj_attr.attr,
NULL,
};
static struct attribute_group ptn_grp = {
.attrs = ptn_attr,
};
static int ptn_add_sys_fs(void)
{
int rc;
ptn_kobj = kobject_create_and_add("misc-ptn", NULL);
if (!ptn_kobj) {
ptn_dbg("unable to create kobject\n");
return -ENOMEM;
}
rc = sysfs_create_group(ptn_kobj, &ptn_grp);
if (rc) {
ptn_dbg("failed to create attributes\n");
kobject_put(ptn_kobj);
return -ENOENT;
}
return rc;
}
static int __init ptn_init(void)
{
int ret;
ret = misc_register(&ptn_dev);
if (ret) {
ptn_dbg("can't misc_register\n");
goto out;
}
ret = ptn_add_sys_fs();
if (ret) {
ptn_dbg("can't create /sys/misc-ptn/blk-file\n");
}
out:
return ret;
}
static void __exit ptn_exit(void)
{
misc_deregister(&ptn_dev);
}
module_init(ptn_init);
module_exit(ptn_exit);
MODULE_AUTHOR("George Tso <[email protected]>");
MODULE_DESCRIPTION("PTN character-device wrapper driver");
MODULE_LICENSE("GPL");