blob: 0d386ce4e2275c91571e8265a5962f154c1a7d8d [file] [log] [blame]
/*
* drivers/amlogic/media/stream_input/parser/thread_rw.c
*
* Copyright (C) 2016 Amlogic, Inc. All rights reserved.
*
* 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.
*
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/interrupt.h>
#include <linux/timer.h>
#include <linux/kfifo.h>
#include <linux/workqueue.h>
#include <linux/dma-mapping.h>
#include <linux/dma-contiguous.h>
#include <linux/uaccess.h>
#include <linux/fs.h>
#include <linux/vmalloc.h>
#include <linux/amlogic/media/codec_mm/codec_mm.h>
/* #include <mach/am_regs.h> */
#include <linux/delay.h>
#include "streambuf.h"
#include "amports_priv.h"
#include "thread_rw.h"
#define BUF_NAME "fetchbuf"
#define DEFAULT_BLOCK_SIZE (64*1024)
struct threadrw_buf {
void *vbuffer;
dma_addr_t dma_handle;
int write_off;
int data_size;
int buffer_size;
int from_cma;
};
#define MAX_MM_BUFFER_NUM 16
struct threadrw_write_task {
struct file *file;
struct delayed_work write_work;
DECLARE_KFIFO_PTR(datafifo, void *);
DECLARE_KFIFO_PTR(freefifo, void *);
int bufs_num;
int max_bufs;
int errors;
spinlock_t lock;
struct mutex mutex;
struct stream_buf_s *sbuf;
int buffered_data_size;
int passed_data_len;
int buffer_size;
int def_block_size;
int data_offset;
int writework_on;
unsigned long codec_mm_buffer[MAX_MM_BUFFER_NUM];
int manual_write;
int failed_onmore;
wait_queue_head_t wq;
ssize_t (*write)(struct file *,
struct stream_buf_s *,
const char __user *,
size_t, int);
struct threadrw_buf buf[1];
/*don't add any after buf[] define */
};
static int free_task_buffers(struct threadrw_write_task *task);
static struct workqueue_struct *threadrw_wq_get(void)
{
static struct workqueue_struct *threadrw_wq;
if (!threadrw_wq)
threadrw_wq = create_singlethread_workqueue("threadrw");
return threadrw_wq;
}
static int threadrw_schedule_delayed_work(
struct threadrw_write_task *task,
unsigned long delay)
{
bool ret;
if (threadrw_wq_get()) {
ret = queue_delayed_work(threadrw_wq_get(),
&task->write_work, delay);
} else
ret = schedule_delayed_work(&task->write_work, delay);
if (!ret) {
cancel_delayed_work(&task->write_work);
if (threadrw_wq_get())
ret = queue_delayed_work(threadrw_wq_get(),
&task->write_work, 0);
else
ret = schedule_delayed_work(&task->write_work, 0);
}
return 0;
}
static ssize_t threadrw_write_onece(
struct threadrw_write_task *task,
struct file *file,
struct stream_buf_s *stbuf,
const char __user *buf, size_t count)
{
struct threadrw_buf *rwbuf = NULL;
int ret = 0;
int to_write;
if (!kfifo_get(&task->freefifo, (void *)&rwbuf)) {
if (task->errors)
return task->errors;
return -EAGAIN;
}
to_write = min_t(u32, rwbuf->buffer_size, count);
if (copy_from_user(rwbuf->vbuffer, buf, to_write)) {
kfifo_put(&task->freefifo, (const void *)buf);
ret = -EFAULT;
goto err;
}
rwbuf->data_size = to_write;
rwbuf->write_off = 0;
kfifo_put(&task->datafifo, (const void *)rwbuf);
threadrw_schedule_delayed_work(task, 0);
return to_write;
err:
return ret;
}
static ssize_t threadrw_write_in(
struct threadrw_write_task *task,
struct stream_buf_s *stbuf,
const char __user *buf, size_t count)
{
int ret = 0;
int off = 0;
/* int change to size_t for buffer overflow on OTT-5057 */
size_t left = count;
int wait_num = 0;
unsigned long flags;
while (left > 0) {
ret = threadrw_write_onece(task,
task->file,
stbuf, buf + off, left);
/* firstly check ret < 0, avoid the risk of -EAGAIN in ret
* implicit convert to size_t when compare with "size_t left".
*/
if (ret < 0) {
if (off > 0) {
break; /*have write ok some data. */
} else if (ret == -EAGAIN) {
if (!(task->file->f_flags & O_NONBLOCK) &&
(++wait_num < 10)) {
wait_event_interruptible_timeout(
task->wq,
!kfifo_is_empty(
&task->freefifo),
HZ / 100);
continue; /* write again. */
}
ret = -EAGAIN;
break;
}
break; /*to end */
} else if (ret >= left) {
off = count;
left = 0;
} else if (ret > 0) {
off += ret;
left -= ret;
}
}
/*end: */
spin_lock_irqsave(&task->lock, flags);
if (off > 0) {
task->buffered_data_size += off;
task->data_offset += off;
}
spin_unlock_irqrestore(&task->lock, flags);
if (off > 0)
return off;
else
return ret;
}
static int do_write_work_in(struct threadrw_write_task *task)
{
struct threadrw_buf *rwbuf = NULL;
int ret;
int need_re_write = 0;
int write_len = 0;
unsigned long flags;
if (kfifo_is_empty(&task->datafifo))
return 0;
if (!kfifo_peek(&task->datafifo, (void *)&rwbuf))
return 0;
if (!task->manual_write &&
rwbuf->from_cma &&
!rwbuf->write_off)
codec_mm_dma_flush(rwbuf->vbuffer,
rwbuf->buffer_size,
DMA_TO_DEVICE);
if (task->manual_write) {
ret = task->write(task->file, task->sbuf,
(const char __user *)rwbuf->vbuffer + rwbuf->write_off,
rwbuf->data_size,
2); /* noblock,virtual addr */
} else {
ret = task->write(task->file, task->sbuf,
(const char __user *)rwbuf->dma_handle + rwbuf->write_off,
rwbuf->data_size,
3); /* noblock,phy addr */
}
if (ret == -EAGAIN) {
need_re_write = 0;
/*do later retry. */
} else if (ret >= rwbuf->data_size) {
write_len += rwbuf->data_size;
if (kfifo_get(&task->datafifo, (void *)&rwbuf)) {
rwbuf->data_size = 0;
kfifo_put(&task->freefifo, (const void *)rwbuf);
/*wakeup write thread. */
wake_up_interruptible(&task->wq);
} else
pr_err("write ok,but kfifo_get data failed.!!!\n");
need_re_write = 1;
} else if (ret > 0) {
rwbuf->data_size -= ret; /* half data write */
rwbuf->write_off += ret;
write_len += ret;
need_re_write = 1;
} else { /*ret <=0 */
pr_err("get errors ret=%d size=%d\n", ret,
rwbuf->data_size);
task->errors = ret;
}
if (write_len > 0) {
spin_lock_irqsave(&task->lock, flags);
task->passed_data_len += write_len;
spin_unlock_irqrestore(&task->lock, flags);
}
return need_re_write;
}
static void do_write_work(struct work_struct *work)
{
struct threadrw_write_task *task = container_of(work,
struct threadrw_write_task,
write_work.work);
int need_retry = 1;
task->writework_on = 1;
while (need_retry) {
mutex_lock(&task->mutex);
need_retry = do_write_work_in(task);
mutex_unlock(&task->mutex);
}
threadrw_schedule_delayed_work(task, HZ / 10);
task->writework_on = 0;
}
static int alloc_task_buffers_inlock(struct threadrw_write_task *task,
int new_bubffers,
int block_size)
{
struct threadrw_buf *rwbuf;
int i;
int used_codec_mm = task->manual_write ? 0 : 1;
int new_num = new_bubffers;
int mm_slot = -1;
int start_idx = task->bufs_num;
int total_mm = 0;
unsigned long addr;
if (codec_mm_get_total_size() < 80 ||
codec_mm_get_free_size() < 40)
used_codec_mm = 0;
if (task->bufs_num + new_num > task->max_bufs)
new_num = task->max_bufs - task->bufs_num;
for (i = 0; i < MAX_MM_BUFFER_NUM; i++) {
if (task->codec_mm_buffer[i] == 0) {
mm_slot = i;
break;
}
}
if (mm_slot < 0)
used_codec_mm = 0;
if (block_size <= 0)
block_size = DEFAULT_BLOCK_SIZE;
if (used_codec_mm && (block_size * new_num) >= 128 * 1024) {
total_mm = ALIGN(block_size * new_num, (1 << 17));
addr =
codec_mm_alloc_for_dma(BUF_NAME,
total_mm / PAGE_SIZE, 0,
CODEC_MM_FLAGS_DMA_CPU);
if (addr != 0) {
task->codec_mm_buffer[mm_slot] = addr;
task->buffer_size += total_mm;
} else {
used_codec_mm = 0;
}
}
for (i = 0; i < new_num; i++) {
int bufidx = start_idx + i;
rwbuf = &task->buf[bufidx];
rwbuf->buffer_size = block_size;
if (used_codec_mm) {
unsigned long start_addr =
task->codec_mm_buffer[mm_slot];
if (i == new_num - 1)
rwbuf->buffer_size = total_mm -
block_size * i;
rwbuf->dma_handle = (dma_addr_t) start_addr +
block_size * i;
rwbuf->vbuffer = codec_mm_phys_to_virt(
rwbuf->dma_handle);
rwbuf->from_cma = 1;
} else {
rwbuf->vbuffer = dma_alloc_coherent(
amports_get_dma_device(),
rwbuf->buffer_size,
&rwbuf->dma_handle, GFP_KERNEL);
if (!rwbuf->vbuffer) {
rwbuf->buffer_size = 0;
rwbuf->dma_handle = 0;
task->bufs_num = bufidx;
break;
}
rwbuf->from_cma = 0;
task->buffer_size += rwbuf->buffer_size;
}
kfifo_put(&task->freefifo, (const void *)rwbuf);
task->bufs_num = bufidx + 1;
}
if (start_idx > 0 ||/*have buffers before*/
task->bufs_num >= 3 ||
task->bufs_num == new_num) {
if (!task->def_block_size)
task->def_block_size = task->buf[0].buffer_size;
return 0; /*must >=3 for swap buffers. */
}
if (task->bufs_num > 0)
free_task_buffers(task);
return -1;
}
static int free_task_buffers(struct threadrw_write_task *task)
{
int i;
for (i = 0; i < MAX_MM_BUFFER_NUM; i++) {
if (task->codec_mm_buffer[i])
codec_mm_free_for_dma(BUF_NAME,
task->codec_mm_buffer[i]);
}
for (i = 0; i < task->bufs_num; i++) {
if (task->buf[i].vbuffer && task->buf[i].from_cma == 0)
dma_free_coherent(amports_get_dma_device(),
task->buf[i].buffer_size,
task->buf[i].vbuffer,
task->buf[i].dma_handle);
}
return 0;
}
static struct threadrw_write_task *threadrw_alloc_in(int num,
int block_size,
ssize_t (*write)(struct file *,
struct stream_buf_s *,
const char __user *, size_t, int),
int flags)
{
int max_bufs = num;
int task_buffer_size;
struct threadrw_write_task *task;
int ret;
if (!(flags & 1)) /*not audio*/
max_bufs = 300; /*can great for video bufs.*/
task_buffer_size = sizeof(struct threadrw_write_task) +
sizeof(struct threadrw_buf) * max_bufs;
task = vmalloc(task_buffer_size);
if (!task)
return NULL;
memset(task, 0, task_buffer_size);
spin_lock_init(&task->lock);
mutex_init(&task->mutex);
INIT_DELAYED_WORK(&task->write_work, do_write_work);
init_waitqueue_head(&task->wq);
ret = kfifo_alloc(&task->datafifo, max_bufs, GFP_KERNEL);
if (ret)
goto err1;
ret = kfifo_alloc(&task->freefifo, max_bufs, GFP_KERNEL);
if (ret)
goto err2;
task->write = write;
task->file = NULL;
task->buffer_size = 0;
task->manual_write = flags & 1;
task->max_bufs = max_bufs;
mutex_lock(&task->mutex);
ret = alloc_task_buffers_inlock(task, num, block_size);
mutex_unlock(&task->mutex);
if (ret < 0)
goto err3;
threadrw_wq_get(); /*start thread. */
return task;
err3:
kfifo_free(&task->freefifo);
err2:
kfifo_free(&task->datafifo);
err1:
vfree(task);
pr_err("alloc threadrw failed num:%d,block:%d\n", num, block_size);
return NULL;
}
/*
*fifo data size;
*/
void threadrw_update_buffer_level(struct stream_buf_s *stbuf,
int parsed_size)
{
struct threadrw_write_task *task = stbuf->write_thread;
unsigned long flags;
if (task)
{
spin_lock_irqsave(&task->lock, flags);
task->buffered_data_size -= parsed_size;
spin_unlock_irqrestore(&task->lock, flags);
}
}
EXPORT_SYMBOL(threadrw_update_buffer_level);
int threadrw_buffer_level(struct stream_buf_s *stbuf)
{
struct threadrw_write_task *task = stbuf->write_thread;
if (task)
return task->buffered_data_size;
return 0;
}
int threadrw_buffer_size(struct stream_buf_s *stbuf)
{
struct threadrw_write_task *task = stbuf->write_thread;
if (task)
return task->buffer_size;
return 0;
}
int threadrw_datafifo_len(struct stream_buf_s *stbuf)
{
struct threadrw_write_task *task = stbuf->write_thread;
if (task)
return kfifo_len(&task->datafifo);
return 0;
}
int threadrw_freefifo_len(struct stream_buf_s *stbuf)
{
struct threadrw_write_task *task = stbuf->write_thread;
if (task)
return kfifo_len(&task->freefifo);
return 0;
}
int threadrw_support_more_buffers(struct stream_buf_s *stbuf)
{
struct threadrw_write_task *task = stbuf->write_thread;
if (!task)
return 0;
if (task->failed_onmore)
return 0;
return task->max_bufs - task->bufs_num;
}
/*
*data len out fifo;
*/
int threadrw_passed_len(struct stream_buf_s *stbuf)
{
struct threadrw_write_task *task = stbuf->write_thread;
if (task)
return task->passed_data_len;
return 0;
}
/*
*all data writed.;
*/
int threadrw_dataoffset(struct stream_buf_s *stbuf)
{
struct threadrw_write_task *task = stbuf->write_thread;
int offset = 0;
if (task)
return task->data_offset;
return offset;
}
ssize_t threadrw_write(struct file *file, struct stream_buf_s *stbuf,
const char __user *buf, size_t count)
{
struct threadrw_write_task *task = stbuf->write_thread;
ssize_t size;
if (!task->file) {
task->file = file;
task->sbuf = stbuf;
}
mutex_lock(&task->mutex);
size = threadrw_write_in(task, stbuf, buf, count);
mutex_unlock(&task->mutex);
return size;
}
int threadrw_flush_buffers(struct stream_buf_s *stbuf)
{
struct threadrw_write_task *task = stbuf->write_thread;
int max_retry = 20;
if (!task)
return 0;
while (!kfifo_is_empty(&task->datafifo) && max_retry-- > 0) {
threadrw_schedule_delayed_work(task, 0);
msleep(20);
}
if (!kfifo_is_empty(&task->datafifo))
return -1;/*data not flushed*/
return 0;
}
int threadrw_alloc_more_buffer_size(
struct stream_buf_s *stbuf,
int size)
{
struct threadrw_write_task *task = stbuf->write_thread;
int block_size;
int new_num;
int ret = -1;
int old_num;
if (!task)
return -1;
mutex_lock(&task->mutex);
block_size = task->def_block_size;
if (block_size == 0)
block_size = 32 * 1024;
new_num = size / block_size;
old_num = task->bufs_num;
if (new_num == 0)
new_num = 1;
else if (new_num > task->max_bufs - task->bufs_num)
new_num = task->max_bufs - task->bufs_num;
if (new_num != 0)
ret = alloc_task_buffers_inlock(task, new_num,
block_size);
mutex_unlock(&task->mutex);
pr_info("threadrw add more buffer from %d -> %d for size %d\n",
old_num, task->bufs_num,
size);
if (ret < 0 || old_num == task->bufs_num)
task->failed_onmore = 1;
return ret;
}
void *threadrw_alloc(int num,
int block_size,
ssize_t (*write)(struct file *,
struct stream_buf_s *,
const char __user *,
size_t, int),
int flags)
{
return threadrw_alloc_in(num, block_size, write, flags);
}
void threadrw_release(struct stream_buf_s *stbuf)
{
struct threadrw_write_task *task = stbuf->write_thread;
if (task) {
wake_up_interruptible(&task->wq);
cancel_delayed_work_sync(&task->write_work);
mutex_lock(&task->mutex);
free_task_buffers(task);
mutex_unlock(&task->mutex);
kfifo_free(&task->freefifo);
kfifo_free(&task->datafifo);
vfree(task);
}
stbuf->write_thread = NULL;
}