blob: 10c00fdce278ab5c4399fee9afbfe25bd993c67e [file] [log] [blame]
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
* Copyright (c) 2019 Amlogic, Inc. All rights reserved.
*/
//#define DEBUG
#include <linux/module.h>
#include <linux/seq_file.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/list.h>
#include <linux/vmalloc.h>
#include <linux/highmem.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/file.h>
#define DEVICE_NAME "audio_utils"
#define TEST_IOC_MAGIC 'T'
#define TEST_IOC_SET_LIB_SIZE _IOW(TEST_IOC_MAGIC, 0x00, uint32_t)
#define TEST_IOC_WRITE_LIB _IOW(TEST_IOC_MAGIC, 0x01, uint32_t)
#define TEST_IOC_FREE_LIB _IOW(TEST_IOC_MAGIC, 0x02, uint32_t)
static LIST_HEAD(code_list);
static int written;
static int write_size;
static int lib_size;
static int audio_utils_open(struct inode *inode, struct file *file)
{
pr_info("%s\n", __func__);
return 0;
}
static ssize_t audio_utils_write(struct file *file, const char __user *buffer,
size_t _count, loff_t *ppos)
{
struct page *page;
int ret, count, write = 0;
void *tmp;
write_size = 0;
count = _count;
while (count > 0) {
page = alloc_page(GFP_HIGHUSER);
if (!page) {
pr_err("%s-%d\n", __func__, __LINE__);
return -ENOMEM;
}
pr_debug("%s page: %lx, count: %d\n",
__func__, page_to_pfn(page), count);
tmp = kmap(page);
WARN_ON(!tmp);
if (count >= PAGE_SIZE) {
ret = copy_from_user(tmp, buffer + write, PAGE_SIZE);
if (ret) {
kunmap(tmp);
__free_page(page);
pr_err("%s-%d write offset=%d size=%d total size=%zu ret=%d\n",
__func__, __LINE__, write, (int)PAGE_SIZE, _count, ret);
return -EINVAL;
}
count -= PAGE_SIZE;
write += PAGE_SIZE;
} else if (count) { /* remain */
ret = copy_from_user(tmp, buffer + write, count);
if (ret) {
kunmap(tmp);
__free_page(page);
pr_err("%s-%d write offset=%d size=%d total size=%zu ret=%d\n",
__func__, __LINE__, write, count, _count, ret);
return -EINVAL;
}
count -= count;
write += count;
}
list_add_tail(&page->lru, &code_list);
kunmap(tmp);
}
write_size = _count;
file->f_inode->i_size = write_size;
pr_debug("%s write_size: %d\n", __func__, write_size);
return _count;
}
static long audio_utils_ioctl(struct file *f,
unsigned int cmd, unsigned long arg)
{
struct page *page, *next;
void __user *argp = (void __user *)arg;
/*
* TODO: here just free maintained pages, need more ioctrl commands
*/
switch (cmd) {
case TEST_IOC_SET_LIB_SIZE:
pr_info("%s SET_LIB_SIZE %ld\n", __func__, arg);
lib_size = arg;
break;
case TEST_IOC_WRITE_LIB:
pr_info("%s WRITE_LIB\n", __func__);
if (written) {
pr_err("%s have written\n", __func__);
return -EINVAL;
}
if (audio_utils_write(f, argp, lib_size, NULL) != lib_size) {
pr_err("%s, %d\n", __func__, __LINE__);
list_for_each_entry_safe(page, next, &code_list, lru) {
__free_page(page);
}
INIT_LIST_HEAD(&code_list);
f->f_inode->i_size = 0;
lib_size = 0;
return -ENOMEM;
}
written = 1;
break;
case TEST_IOC_FREE_LIB:
pr_info("%s FREE_LIB\n", __func__);
written = 0;
list_for_each_entry_safe(page, next, &code_list, lru) {
__free_page(page);
}
INIT_LIST_HEAD(&code_list);
f->f_inode->i_size = 0;
lib_size = 0;
break;
default:
break;
}
return 0;
}
static int audio_utils_mmap(struct file *file, struct vm_area_struct *vma)
{
struct page *page;
unsigned long addr, size;
int ret, total_pages;
addr = vma->vm_start;
size = vma->vm_end - vma->vm_start;
total_pages = ALIGN(write_size, PAGE_SIZE) / PAGE_SIZE;
if (vma->vm_pgoff > total_pages) {
pr_err("%s wrong page off:%ld, total:%d, fsize:%d\n",
__func__, vma->vm_pgoff, total_pages, write_size);
return -EINVAL;
}
page = list_first_entry(&code_list, struct page, lru);
for (ret = 0; ret < vma->vm_pgoff; ret++) { /* move to right page */
page = list_next_entry(page, lru);
}
while (addr < vma->vm_end) {
ret = vm_insert_page(vma, addr, page);
pr_debug("%s insert page %5lx for %lx, ret:%d\n",
__func__, page_to_pfn(page), addr, ret);
if (ret < 0) {
pr_err("%s-%d\n", __func__, __LINE__);
return ret;
}
addr += PAGE_SIZE;
page = list_next_entry(page, lru);
if (&page->lru == &code_list)
break;
}
return 0;
}
#ifdef CONFIG_COMPAT
static long audio_utils_compact_ioctl(struct file *f,
unsigned int cmd, unsigned long arg)
{
arg = (unsigned long)compat_ptr(arg);
return audio_utils_ioctl(f, cmd, arg);
}
#endif
ssize_t audio_utils_read(struct file *file, char __user *buf,
size_t size, loff_t *ppos)
{
int off = 0, off1, rd_size = 0, can_read, to = 0;
struct page *page;
void *tmp;
pr_debug("%s off:%lx, size:%ld\n",
__func__, (unsigned long)*ppos, (unsigned long)size);
page = list_first_entry(&code_list, struct page, lru);
while (off < *ppos) {
off += PAGE_SIZE;
page = list_next_entry(page, lru);
}
off1 = *ppos & ~(PAGE_MASK);
if (off1)
can_read = PAGE_SIZE - off1;
else
can_read = PAGE_SIZE;
rd_size = size;
if (can_read >= rd_size) /* 1st read align to page size */
can_read = rd_size;
while (rd_size > 0) {
tmp = kmap(page);
if (!tmp) {
pr_err("%s-%d\n", __func__, __LINE__);
return -EINVAL;
}
if (copy_to_user(buf + to, tmp, can_read)) {
kunmap(tmp);
pr_err("%s-%d\n", __func__, __LINE__);
return -EINVAL;
}
pr_debug("%s buf:%p, to:%d, canread:%d, rdsize:%d, page:%lx\n",
__func__, buf, to, can_read,
rd_size, page_to_pfn(page));
rd_size -= can_read;
to += can_read;
kunmap(tmp);
if (rd_size >= PAGE_SIZE)
can_read = PAGE_SIZE;
else
can_read = rd_size;
page = list_next_entry(page, lru);
if (&page->lru == &code_list)
break;
}
*ppos += size;
return size;
}
loff_t audio_utils_seek(struct file *file, loff_t offset, int whence)
{
pr_debug("%s where:%d, off:%lx\n",
__func__, whence, (unsigned long)offset);
return 0;
}
static int audio_utils_release(struct inode *inode, struct file *file)
{
pr_info("%s\n", __func__);
return 0;
}
static const struct file_operations audio_utils_fops = {
.open = audio_utils_open,
.read = audio_utils_read,
.llseek = audio_utils_seek,
.unlocked_ioctl = audio_utils_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = audio_utils_compact_ioctl,
#endif
.mmap = audio_utils_mmap,
.release = audio_utils_release,
};
static struct class audio_utils_class = {
.name = DEVICE_NAME,
.owner = THIS_MODULE,
};
static int major; /* major number we get from the kernel */
static int __init audio_utils_init(void)
{
int r;
struct device *cdev;
r = class_register(&audio_utils_class);
if (r) {
pr_err("%s regist class failed\n", __func__);
return -EINVAL;
}
major = register_chrdev(0, DEVICE_NAME, &audio_utils_fops);
if (major < 0) {
pr_err("%s register cdev failed\n", __func__);
return -EINVAL;
}
cdev = device_create(&audio_utils_class, NULL,
MKDEV(major, 0), NULL, DEVICE_NAME);
if (IS_ERR_OR_NULL(cdev)) {
pr_err("%s alloc cdev failed, cdev:%p\n", __func__, cdev);
return -EINVAL;
}
return 0;
}
static void __exit audio_utils_exit(void)
{
device_destroy(&audio_utils_class, MKDEV(major, 0));
unregister_chrdev(major, DEVICE_NAME);
class_unregister(&audio_utils_class);
}
module_init(audio_utils_init);
module_exit(audio_utils_exit);
MODULE_LICENSE("GPL");