1104 lines
28 KiB
C
Executable file
1104 lines
28 KiB
C
Executable file
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* ALSA SoC - Samsung Abox Internal Buffer Dumping driver
|
|
*
|
|
* Copyright (c) 2016 Samsung Electronics Co. Ltd.
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/vmalloc.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <linux/sched/clock.h>
|
|
#include <sound/samsung/abox.h>
|
|
|
|
#include "abox_util.h"
|
|
#include "abox.h"
|
|
#include "abox_proc.h"
|
|
#include "abox_log.h"
|
|
#include "abox_dump.h"
|
|
#include "abox_memlog.h"
|
|
|
|
#define NAME_LENGTH (SZ_32)
|
|
|
|
struct abox_dump_info {
|
|
struct device *dev;
|
|
struct list_head list;
|
|
int c_gid;
|
|
int c_id;
|
|
int id;
|
|
char name[NAME_LENGTH];
|
|
bool registered;
|
|
struct mutex lock;
|
|
struct snd_dma_buffer buffer;
|
|
struct snd_pcm_substream *substream;
|
|
size_t pointer;
|
|
bool started;
|
|
|
|
struct proc_dir_entry *file;
|
|
bool file_started;
|
|
size_t file_pointer;
|
|
wait_queue_head_t file_waitqueue;
|
|
|
|
bool auto_started;
|
|
ssize_t auto_pointer;
|
|
struct memlog_obj *auto_fobj;
|
|
struct work_struct auto_work;
|
|
|
|
bool memlog_started;
|
|
ssize_t memlog_pointer;
|
|
struct work_struct memlog_work;
|
|
int memlog_idx;
|
|
};
|
|
|
|
static struct proc_dir_entry *dir_dump;
|
|
static struct device *abox_dump_dev_abox;
|
|
static LIST_HEAD(abox_dump_list_head);
|
|
static DEFINE_SPINLOCK(abox_dump_lock);
|
|
|
|
static struct abox_dump_info *abox_dump_get_info(int id)
|
|
{
|
|
struct abox_dump_info *info;
|
|
|
|
list_for_each_entry(info, &abox_dump_list_head, list) {
|
|
if (info->id == id)
|
|
return info;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct abox_dump_info *abox_dump_get_info_by_name(const char *name)
|
|
{
|
|
struct abox_dump_info *info;
|
|
|
|
list_for_each_entry(info, &abox_dump_list_head, list) {
|
|
if (strncmp(info->name, name, sizeof(info->name)) == 0)
|
|
return info;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void abox_dump_request_dump(int id)
|
|
{
|
|
struct abox_dump_info *info = abox_dump_get_info(id);
|
|
ABOX_IPC_MSG msg;
|
|
struct IPC_SYSTEM_MSG *system = &msg.msg.system;
|
|
bool start = info->started || info->file_started ||
|
|
info->auto_started || info->memlog_started;
|
|
|
|
abox_dbg(info->dev, "%s(%d)\n", __func__, id);
|
|
|
|
msg.ipcid = IPC_SYSTEM;
|
|
system->msgtype = ABOX_REQUEST_DUMP;
|
|
system->param1 = info->c_id;
|
|
system->param2 = start ? 1 : 0;
|
|
system->param3 = info->c_gid;
|
|
abox_request_ipc(abox_dump_dev_abox, msg.ipcid, &msg, sizeof(msg),
|
|
1, 0);
|
|
}
|
|
|
|
struct abox_dump_memlog_entity {
|
|
const char *name;
|
|
struct memlog_obj *fobj;
|
|
size_t buf_size;
|
|
};
|
|
|
|
static struct abox_dump_memlog_entity abox_dump_memlogs[] = {
|
|
{ .name = "rd0", .buf_size = SZ_16M, },
|
|
{ .name = "rd1", .buf_size = SZ_16M, },
|
|
{ .name = "rd3", .buf_size = SZ_16M, },
|
|
{ .name = "rd4", .buf_size = SZ_16M, },
|
|
{ .name = "rd7", .buf_size = SZ_16M, },
|
|
{ .name = "rdb", .buf_size = SZ_16M, },
|
|
{ .name = "wr0", .buf_size = SZ_16M, },
|
|
{ .name = "wr1", .buf_size = SZ_16M, },
|
|
{ .name = "wr2", .buf_size = SZ_16M, },
|
|
{ .name = "wr3", .buf_size = SZ_16M, },
|
|
{ .name = "wr4", .buf_size = SZ_16M, },
|
|
{ .name = "wr6", .buf_size = SZ_16M, },
|
|
{ .name = "uou", .buf_size = SZ_16M, },
|
|
{ .name = "uin", .buf_size = SZ_16M, },
|
|
};
|
|
|
|
static int abox_dump_memlog_file_completed(struct memlog_obj *obj, u32 flags)
|
|
{
|
|
/* NOP */
|
|
return 0;
|
|
}
|
|
|
|
static int abox_dump_memlog_status_notify(struct memlog_obj *obj, u32 flags)
|
|
{
|
|
/* NOP */
|
|
return 0;
|
|
}
|
|
|
|
static int abox_dump_memlog_level_notify(struct memlog_obj *obj, u32 flags)
|
|
{
|
|
/* NOP */
|
|
return 0;
|
|
}
|
|
|
|
static int abox_dump_memlog_enable_notify(struct memlog_obj *obj, u32 flags)
|
|
{
|
|
struct abox_data *data = dev_get_drvdata(abox_dump_dev_abox);
|
|
struct memlog_obj *_memlog_obj;
|
|
struct abox_dump_info *info = NULL;
|
|
int idx;
|
|
|
|
for (idx = 0; idx < ARRAY_SIZE(abox_dump_memlogs); idx++) {
|
|
_memlog_obj = abox_dump_memlogs[idx].fobj;
|
|
if (_memlog_obj == obj) {
|
|
info = abox_dump_get_info_by_name(
|
|
abox_dump_memlogs[idx].name);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!info) {
|
|
abox_info(data->dev, "dump info of %s is not found\n",
|
|
obj->file->file_name);
|
|
return 0;
|
|
}
|
|
|
|
abox_info(info->dev, "abox dump log(%s) %s is sent\n",
|
|
info->name, obj->enabled ? "ENABLE" : "DISABLE");
|
|
|
|
if (info->memlog_started != obj->enabled) {
|
|
if (obj->enabled)
|
|
pm_runtime_get_sync(info->dev);
|
|
else
|
|
pm_runtime_put(info->dev);
|
|
}
|
|
|
|
info->memlog_started = obj->enabled;
|
|
if (obj->enabled)
|
|
info->pointer = info->memlog_pointer = 0;
|
|
info->memlog_idx = idx;
|
|
abox_dump_request_dump(info->id);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct memlog_ops abox_dump_memlog_ops = {
|
|
.file_ops_completed = abox_dump_memlog_file_completed,
|
|
.log_status_notify = abox_dump_memlog_status_notify,
|
|
.log_level_notify = abox_dump_memlog_level_notify,
|
|
.log_enable_notify = abox_dump_memlog_enable_notify,
|
|
};
|
|
|
|
|
|
void abox_dump_memlog_register(struct device *dev_abox)
|
|
{
|
|
struct abox_data *data = dev_get_drvdata(dev_abox);
|
|
char name[32] = {0,};
|
|
int i, ret;
|
|
|
|
ret = memlog_register("abox-dump", data->dev, &data->dump_desc);
|
|
if (ret) {
|
|
abox_err(data->dev, "failed to register dump memlog\n");
|
|
return;
|
|
}
|
|
|
|
data->dump_desc->ops = abox_dump_memlog_ops;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(abox_dump_memlogs); i++) {
|
|
snprintf(name, sizeof(name), "%s.pcm",
|
|
abox_dump_memlogs[i].name);
|
|
abox_dump_memlogs[i].fobj = memlog_alloc_file(data->dump_desc,
|
|
name, SZ_128K,
|
|
abox_dump_memlogs[i].buf_size, 20, 10);
|
|
}
|
|
}
|
|
|
|
static void abox_dump_memlog_work_func(struct work_struct *work)
|
|
{
|
|
struct abox_dump_info *info = container_of(work,
|
|
struct abox_dump_info, memlog_work);
|
|
struct memlog_obj *fobj;
|
|
struct device *dev;
|
|
const char *name;
|
|
void *area;
|
|
size_t bytes;
|
|
size_t pointer;
|
|
int ret;
|
|
|
|
if (!info) {
|
|
abox_info(abox_dump_dev_abox, "dump information is not found\n");
|
|
return;
|
|
}
|
|
|
|
fobj = abox_dump_memlogs[info->memlog_idx].fobj;
|
|
if (!fobj->enabled)
|
|
return;
|
|
|
|
dev = info->dev;
|
|
name = info->name;
|
|
area = info->buffer.area;
|
|
bytes = info->buffer.bytes;
|
|
pointer = info->pointer;
|
|
|
|
if (pointer < info->memlog_pointer) {
|
|
ret = memlog_write_file(fobj, area + info->memlog_pointer,
|
|
bytes - info->memlog_pointer);
|
|
if (ret < 0)
|
|
abox_err(dev, "Failed to write pcm dump (%d)\n", ret);
|
|
abox_dbg(dev, "memlog_write(%pK, %zx)\n",
|
|
area + info->memlog_pointer,
|
|
bytes - info->memlog_pointer);
|
|
info->memlog_pointer = 0;
|
|
}
|
|
ret = memlog_write_file(fobj, area + info->memlog_pointer,
|
|
pointer - info->memlog_pointer);
|
|
if (ret < 0)
|
|
abox_err(dev, "Failed to write pcm dump (%d)\n", ret);
|
|
abox_dbg(dev, "memlog_write(%pK, %zx)\n",
|
|
area + info->memlog_pointer,
|
|
pointer - info->memlog_pointer);
|
|
info->memlog_pointer = pointer;
|
|
}
|
|
|
|
static void abox_dump_memlog_wait_for_flush(struct abox_dump_info *info)
|
|
{
|
|
/* memlog timeout + 1 */
|
|
u64 limit = local_clock() + (NSEC_PER_SEC * (5 + 1));
|
|
|
|
while (memlog_is_data_remaining(info->auto_fobj)) {
|
|
if (local_clock() > limit) {
|
|
abox_warn(abox_dump_dev_abox, "memlog flush timeout\n");
|
|
break;
|
|
}
|
|
msleep(100);
|
|
}
|
|
}
|
|
|
|
static ssize_t abox_dump_auto_read(struct file *file, char __user *data,
|
|
size_t count, loff_t *ppos, bool enable)
|
|
{
|
|
const size_t sz_buffer = PAGE_SIZE;
|
|
struct abox_dump_info *info;
|
|
char *buffer, *p_buffer;
|
|
ssize_t ret;
|
|
|
|
abox_dbg(abox_dump_dev_abox, "%s(%zu, %lld, %d)\n", __func__, count,
|
|
*ppos, enable);
|
|
|
|
p_buffer = buffer = kmalloc(sz_buffer, GFP_KERNEL);
|
|
if (!buffer)
|
|
return -ENOMEM;
|
|
|
|
list_for_each_entry(info, &abox_dump_list_head, list) {
|
|
if (info->auto_started == enable) {
|
|
p_buffer += snprintf(p_buffer, sz_buffer -
|
|
(p_buffer - buffer),
|
|
"%d(%s) ", info->id, info->name);
|
|
}
|
|
}
|
|
snprintf(p_buffer, 2, "\n");
|
|
|
|
ret = simple_read_from_buffer(data, count, ppos, buffer,
|
|
p_buffer - buffer);
|
|
kfree(buffer);
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t abox_dump_auto_write(struct file *file, const char __user *data,
|
|
size_t count, loff_t *ppos, bool enable)
|
|
{
|
|
const size_t sz_buffer = PAGE_SIZE;
|
|
struct abox_dump_info *info;
|
|
char *buffer, *p_buffer, *token;
|
|
ssize_t ret;
|
|
|
|
abox_dbg(abox_dump_dev_abox, "%s(%zu, %lld, %d)\n", __func__, count,
|
|
*ppos, enable);
|
|
|
|
p_buffer = buffer = kzalloc(sz_buffer, GFP_KERNEL);
|
|
if (!buffer)
|
|
return -ENOMEM;
|
|
|
|
ret = simple_write_to_buffer(buffer, sz_buffer - 1, ppos, data, count);
|
|
if (ret < 0)
|
|
goto err;
|
|
|
|
while ((token = strsep(&p_buffer, " ")) != NULL) {
|
|
char name[NAME_LENGTH];
|
|
int id;
|
|
|
|
if (sscanf(token, "%11d", &id) == 1)
|
|
info = abox_dump_get_info(id);
|
|
else if (sscanf(token, "%31s", name) == 1)
|
|
info = abox_dump_get_info_by_name(name);
|
|
else
|
|
info = NULL;
|
|
|
|
if (IS_ERR_OR_NULL(info)) {
|
|
abox_err(abox_dump_dev_abox, "invalid argument\n");
|
|
continue;
|
|
}
|
|
|
|
if (info->auto_started != enable) {
|
|
if (enable)
|
|
pm_runtime_get_sync(info->dev);
|
|
else
|
|
pm_runtime_put(info->dev);
|
|
}
|
|
|
|
info->auto_started = enable;
|
|
if (enable) {
|
|
char filename[SZ_64];
|
|
struct abox_data *data =
|
|
dev_get_drvdata(abox_dump_dev_abox);
|
|
if (!info->auto_fobj) {
|
|
snprintf(filename, sizeof(filename),
|
|
"%s-auto_pcm", info->name);
|
|
info->auto_fobj = memlog_alloc_file(
|
|
data->dump_desc,
|
|
filename, SZ_4M,
|
|
SZ_1G, 20, 1);
|
|
if (!info->auto_fobj) {
|
|
abox_err(abox_dump_dev_abox,
|
|
"auto file %s open error\n",
|
|
info->name);
|
|
goto err;
|
|
}
|
|
/* enable file write */
|
|
info->auto_fobj->enabled = true;
|
|
}
|
|
|
|
info->pointer = info->auto_pointer = 0;
|
|
}
|
|
|
|
abox_dump_request_dump(info->id);
|
|
}
|
|
|
|
/* clear stopped dump */
|
|
list_for_each_entry(info, &abox_dump_list_head, list) {
|
|
if (!info->auto_started && info->auto_fobj) {
|
|
abox_dump_memlog_wait_for_flush(info);
|
|
memlog_free(info->auto_fobj);
|
|
info->auto_fobj = NULL;
|
|
}
|
|
}
|
|
|
|
err:
|
|
kfree(buffer);
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t abox_dump_auto_start_read(struct file *file,
|
|
char __user *data, size_t count, loff_t *ppos)
|
|
{
|
|
return abox_dump_auto_read(file, data, count, ppos, true);
|
|
}
|
|
|
|
static ssize_t abox_dump_auto_start_write(struct file *file,
|
|
const char __user *data, size_t count, loff_t *ppos)
|
|
{
|
|
return abox_dump_auto_write(file, data, count, ppos, true);
|
|
}
|
|
|
|
static ssize_t abox_dump_auto_stop_read(struct file *file,
|
|
char __user *data, size_t count, loff_t *ppos)
|
|
{
|
|
return abox_dump_auto_read(file, data, count, ppos, false);
|
|
}
|
|
|
|
static ssize_t abox_dump_auto_stop_write(struct file *file,
|
|
const char __user *data, size_t count, loff_t *ppos)
|
|
{
|
|
return abox_dump_auto_write(file, data, count, ppos, false);
|
|
}
|
|
|
|
static const struct proc_ops abox_dump_auto_start_fops = {
|
|
.proc_read = abox_dump_auto_start_read,
|
|
.proc_write = abox_dump_auto_start_write,
|
|
};
|
|
|
|
static const struct proc_ops abox_dump_auto_stop_fops = {
|
|
.proc_read = abox_dump_auto_stop_read,
|
|
.proc_write = abox_dump_auto_stop_write,
|
|
};
|
|
|
|
static void abox_dump_auto_dump_work_func(struct work_struct *work)
|
|
{
|
|
struct abox_dump_info *info = container_of(work,
|
|
struct abox_dump_info, auto_work);
|
|
struct device *dev = info->dev;
|
|
void *area = info->buffer.area;
|
|
size_t bytes = info->buffer.bytes;
|
|
size_t pointer = info->pointer;
|
|
int ret;
|
|
|
|
if (pointer < info->auto_pointer) {
|
|
ret = memlog_write_file(info->auto_fobj,
|
|
area + info->auto_pointer,
|
|
bytes - info->auto_pointer);
|
|
if (ret < 0)
|
|
abox_err(dev, "Failed to write pcm dump (%d)\n", ret);
|
|
abox_dbg(dev, "auto_write(%pK, %zx)\n",
|
|
area + info->auto_pointer,
|
|
bytes - info->auto_pointer);
|
|
info->auto_pointer = 0;
|
|
}
|
|
ret = memlog_write_file(info->auto_fobj,
|
|
area + info->auto_pointer,
|
|
pointer - info->auto_pointer);
|
|
if (ret < 0)
|
|
abox_err(dev, "Failed to write pcm dump (%d)\n", ret);
|
|
abox_dbg(dev, "auto_write(%pK, %zx)\n",
|
|
area + info->auto_pointer,
|
|
pointer - info->auto_pointer);
|
|
info->auto_pointer = pointer;
|
|
}
|
|
|
|
static ssize_t abox_dump_file_read(struct file *file, char __user *data,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
struct abox_dump_info *info = file->private_data;
|
|
struct device *dev = info->dev;
|
|
size_t end, pointer;
|
|
ssize_t size;
|
|
int ret;
|
|
|
|
abox_dbg(dev, "%s(%#zx)\n", __func__, count);
|
|
|
|
do {
|
|
pointer = READ_ONCE(info->pointer);
|
|
end = (info->file_pointer <= pointer) ? pointer :
|
|
info->buffer.bytes;
|
|
size = min(end - info->file_pointer, count);
|
|
abox_dbg(dev, "pointer=%#zx file_pointer=%#zx size=%#zx\n",
|
|
pointer, info->file_pointer, size);
|
|
if (!size) {
|
|
if (file->f_flags & O_NONBLOCK)
|
|
return -EAGAIN;
|
|
|
|
ret = wait_event_interruptible(info->file_waitqueue,
|
|
pointer != READ_ONCE(info->pointer));
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
} while (!size);
|
|
|
|
if (copy_to_user(data, info->buffer.area + info->file_pointer, size))
|
|
return -EFAULT;
|
|
|
|
info->file_pointer += size;
|
|
info->file_pointer %= info->buffer.bytes;
|
|
|
|
return size;
|
|
}
|
|
|
|
static int abox_dump_file_open(struct inode *i, struct file *f)
|
|
{
|
|
struct abox_dump_info *info = abox_dump_get_data(f);
|
|
struct device *dev = info->dev;
|
|
|
|
abox_dbg(dev, "%s\n", __func__);
|
|
|
|
if (info->file_started)
|
|
return -EBUSY;
|
|
|
|
pm_runtime_get(dev);
|
|
|
|
f->private_data = info;
|
|
info->file_started = true;
|
|
info->pointer = info->file_pointer = 0;
|
|
abox_dump_request_dump(info->id);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int abox_dump_file_release(struct inode *i, struct file *f)
|
|
{
|
|
struct abox_dump_info *info = f->private_data;
|
|
struct device *dev = info->dev;
|
|
|
|
abox_dbg(dev, "%s\n", __func__);
|
|
|
|
info->file_started = false;
|
|
abox_dump_request_dump(info->id);
|
|
|
|
pm_runtime_put(dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static unsigned int abox_dump_file_poll(struct file *file, poll_table *wait)
|
|
{
|
|
struct abox_dump_info *info = file->private_data;
|
|
|
|
abox_dbg(info->dev, "%s\n", __func__);
|
|
|
|
poll_wait(file, &info->file_waitqueue, wait);
|
|
return POLLIN | POLLRDNORM;
|
|
}
|
|
|
|
static const struct proc_ops abox_dump_fops = {
|
|
.proc_lseek = generic_file_llseek,
|
|
.proc_read = abox_dump_file_read,
|
|
.proc_poll = abox_dump_file_poll,
|
|
.proc_open = abox_dump_file_open,
|
|
.proc_release = abox_dump_file_release,
|
|
};
|
|
|
|
static struct snd_pcm_hardware abox_dump_hardware = {
|
|
.info = SNDRV_PCM_INFO_INTERLEAVED
|
|
| SNDRV_PCM_INFO_BLOCK_TRANSFER
|
|
| SNDRV_PCM_INFO_MMAP
|
|
| SNDRV_PCM_INFO_MMAP_VALID,
|
|
.formats = ABOX_SAMPLE_FORMATS,
|
|
.rates = SNDRV_PCM_RATE_8000_192000 | SNDRV_PCM_RATE_KNOT,
|
|
.rate_min = 8000,
|
|
.rate_max = 384000,
|
|
.channels_min = 1,
|
|
.channels_max = 8,
|
|
.periods_min = 2,
|
|
.periods_max = 32,
|
|
};
|
|
|
|
void abox_dump_period_elapsed(int id, size_t pointer)
|
|
{
|
|
struct abox_dump_info *info = abox_dump_get_info(id);
|
|
struct device *dev = info->dev;
|
|
|
|
abox_dbg(dev, "%s[%d](%zx)\n", __func__, id, pointer);
|
|
|
|
info->pointer = pointer;
|
|
if (info->auto_started)
|
|
schedule_work(&info->auto_work);
|
|
if (info->memlog_started)
|
|
schedule_work(&info->memlog_work);
|
|
if (info->file_started)
|
|
wake_up_interruptible(&info->file_waitqueue);
|
|
if (info->started)
|
|
snd_pcm_period_elapsed(info->substream);
|
|
}
|
|
|
|
static int abox_dump_open(struct snd_soc_component *component,
|
|
struct snd_pcm_substream *substream)
|
|
{
|
|
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
|
|
int id = rtd->dai_link->id;
|
|
struct abox_dump_info *info = abox_dump_get_info(id);
|
|
struct device *dev = info->dev;
|
|
struct snd_dma_buffer *dmab = &substream->dma_buffer;
|
|
|
|
abox_dbg(dev, "%s[%d]\n", __func__, id);
|
|
|
|
pm_runtime_get(dev);
|
|
|
|
abox_dump_hardware.buffer_bytes_max = dmab->bytes;
|
|
abox_dump_hardware.period_bytes_min = dmab->bytes /
|
|
abox_dump_hardware.periods_max;
|
|
abox_dump_hardware.period_bytes_max = dmab->bytes /
|
|
abox_dump_hardware.periods_min;
|
|
|
|
snd_soc_set_runtime_hwparams(substream, &abox_dump_hardware);
|
|
|
|
info->substream = substream;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int abox_dump_close(struct snd_soc_component *component,
|
|
struct snd_pcm_substream *substream)
|
|
{
|
|
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
|
|
int id = rtd->dai_link->id;
|
|
struct abox_dump_info *info = abox_dump_get_info(id);
|
|
struct device *dev = info->dev;
|
|
|
|
abox_dbg(dev, "%s[%d]\n", __func__, id);
|
|
|
|
info->substream = NULL;
|
|
pm_runtime_put(dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int abox_dump_hw_params(struct snd_soc_component *component,
|
|
struct snd_pcm_substream *substream,
|
|
struct snd_pcm_hw_params *params)
|
|
{
|
|
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
|
|
int id = rtd->dai_link->id;
|
|
struct abox_dump_info *info = abox_dump_get_info(id);
|
|
struct device *dev = info->dev;
|
|
|
|
abox_dbg(dev, "%s[%d]\n", __func__, id);
|
|
|
|
return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
|
|
}
|
|
|
|
static int abox_dump_hw_free(struct snd_soc_component *component,
|
|
struct snd_pcm_substream *substream)
|
|
{
|
|
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
|
|
int id = rtd->dai_link->id;
|
|
struct abox_dump_info *info = abox_dump_get_info(id);
|
|
struct device *dev = info->dev;
|
|
|
|
abox_dbg(dev, "%s[%d]\n", __func__, id);
|
|
|
|
return snd_pcm_lib_free_pages(substream);
|
|
}
|
|
|
|
static int abox_dump_prepare(struct snd_soc_component *component,
|
|
struct snd_pcm_substream *substream)
|
|
{
|
|
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
|
|
int id = rtd->dai_link->id;
|
|
struct abox_dump_info *info = abox_dump_get_info(id);
|
|
struct device *dev = info->dev;
|
|
|
|
abox_dbg(dev, "%s[%d]\n", __func__, id);
|
|
|
|
info->pointer = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int abox_dump_trigger(struct snd_soc_component *component,
|
|
struct snd_pcm_substream *substream, int cmd)
|
|
{
|
|
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
|
|
int id = rtd->dai_link->id;
|
|
struct abox_dump_info *info = abox_dump_get_info(id);
|
|
struct device *dev = info->dev;
|
|
|
|
abox_dbg(dev, "%s[%d](%d)\n", __func__, id, cmd);
|
|
|
|
switch (cmd) {
|
|
case SNDRV_PCM_TRIGGER_START:
|
|
case SNDRV_PCM_TRIGGER_RESUME:
|
|
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
|
info->started = true;
|
|
break;
|
|
case SNDRV_PCM_TRIGGER_STOP:
|
|
case SNDRV_PCM_TRIGGER_SUSPEND:
|
|
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
|
info->started = false;
|
|
break;
|
|
default:
|
|
abox_err(dev, "invalid command: %d\n", cmd);
|
|
return -EINVAL;
|
|
}
|
|
|
|
abox_dump_request_dump(id);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void abox_dump_transfer(int id, const char *buf, size_t bytes)
|
|
{
|
|
struct abox_dump_info *info = abox_dump_get_info(id);
|
|
struct device *dev = info->dev;
|
|
size_t size, pointer;
|
|
|
|
abox_dbg(dev, "%s[%d](%pK, %zx): %zx\n", __func__, id, buf, bytes,
|
|
info->pointer);
|
|
|
|
size = min(bytes, info->buffer.bytes - info->pointer);
|
|
memcpy(info->buffer.area + info->pointer, buf, size);
|
|
if (bytes - size > 0)
|
|
memcpy(info->buffer.area, buf + size, bytes - size);
|
|
|
|
pointer = (info->pointer + bytes) % info->buffer.bytes;
|
|
abox_dump_period_elapsed(id, pointer);
|
|
}
|
|
|
|
static snd_pcm_uframes_t abox_dump_pointer(struct snd_soc_component *component,
|
|
struct snd_pcm_substream *substream)
|
|
{
|
|
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
|
|
int id = rtd->dai_link->id;
|
|
struct abox_dump_info *info = abox_dump_get_info(id);
|
|
struct device *dev = info->dev;
|
|
|
|
abox_dbg(dev, "%s[%d]\n", __func__, id);
|
|
|
|
return bytes_to_frames(substream->runtime, info->pointer);
|
|
}
|
|
|
|
static int abox_dump_probe(struct snd_soc_component *component)
|
|
{
|
|
struct device *dev = component->dev;
|
|
int id = to_platform_device(dev)->id;
|
|
|
|
abox_dbg(dev, "%s[%d]\n", __func__, id);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int abox_dump_pcm_new(struct snd_soc_component *component,
|
|
struct snd_soc_pcm_runtime *rtd)
|
|
{
|
|
const size_t default_size = SZ_128K;
|
|
int id = rtd->dai_link->id;
|
|
struct abox_dump_info *info = abox_dump_get_info(id);
|
|
struct device *dev = info->dev;
|
|
struct snd_pcm *pcm = rtd->pcm;
|
|
struct snd_pcm_str *stream = &pcm->streams[SNDRV_PCM_STREAM_CAPTURE];
|
|
struct snd_pcm_substream *substream = stream->substream;
|
|
struct snd_dma_buffer *dmab = &substream->dma_buffer;
|
|
|
|
abox_dbg(dev, "%s[%d]\n", __func__, id);
|
|
|
|
if (info->buffer.area) {
|
|
dmab->dev.type = SNDRV_DMA_TYPE_DEV;
|
|
dmab->dev.dev = dev;
|
|
dmab->area = info->buffer.area;
|
|
dmab->addr = info->buffer.addr;
|
|
dmab->bytes = info->buffer.bytes;
|
|
} else {
|
|
snd_pcm_lib_preallocate_pages(substream,
|
|
SNDRV_DMA_TYPE_CONTINUOUS,
|
|
(struct device *)GFP_KERNEL,
|
|
default_size, default_size);
|
|
info->buffer = *dmab;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void abox_dump_pcm_free(struct snd_soc_component *component,
|
|
struct snd_pcm *pcm)
|
|
{
|
|
struct snd_soc_pcm_runtime *rtd = pcm->private_data;
|
|
struct snd_pcm_str *stream = &pcm->streams[SNDRV_PCM_STREAM_CAPTURE];
|
|
struct snd_pcm_substream *substream = stream->substream;
|
|
int id = rtd->dai_link->id;
|
|
struct abox_dump_info *info = abox_dump_get_info(id);
|
|
struct device *dev = info->dev;
|
|
|
|
abox_dbg(dev, "%s[%d]\n", __func__, id);
|
|
|
|
switch (substream->dma_buffer.dev.type) {
|
|
case SNDRV_DMA_TYPE_DEV:
|
|
memset(&substream->dma_buffer, 0, sizeof(substream->dma_buffer));
|
|
break;
|
|
case SNDRV_DMA_TYPE_CONTINUOUS:
|
|
snd_pcm_lib_free_pages(substream);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static const struct snd_soc_component_driver abox_dump_component = {
|
|
.probe = abox_dump_probe,
|
|
.pcm_construct = abox_dump_pcm_new,
|
|
.pcm_destruct = abox_dump_pcm_free,
|
|
.open = abox_dump_open,
|
|
.close = abox_dump_close,
|
|
.hw_params = abox_dump_hw_params,
|
|
.hw_free = abox_dump_hw_free,
|
|
.prepare = abox_dump_prepare,
|
|
.trigger = abox_dump_trigger,
|
|
.pointer = abox_dump_pointer,
|
|
};
|
|
|
|
static struct snd_soc_card abox_dump_card = {
|
|
.name = "abox_dump",
|
|
.owner = THIS_MODULE,
|
|
.num_links = 0,
|
|
};
|
|
|
|
struct proc_dir_entry *abox_dump_register_file(const char *name, void *data,
|
|
const struct proc_ops *fops)
|
|
{
|
|
return abox_proc_create_file(name, 0664, dir_dump, fops, data, 0);
|
|
}
|
|
|
|
void abox_dump_unregister_file(struct proc_dir_entry *file)
|
|
{
|
|
abox_proc_remove_file(file);
|
|
}
|
|
|
|
void *abox_dump_get_data(const struct file *file)
|
|
{
|
|
return abox_proc_data(file);
|
|
}
|
|
|
|
static int abox_dump_register_work_single(void)
|
|
{
|
|
struct abox_dump_info *info, *_info;
|
|
unsigned long flags;
|
|
|
|
abox_dbg(abox_dump_dev_abox, "%s\n", __func__);
|
|
|
|
spin_lock_irqsave(&abox_dump_lock, flags);
|
|
list_for_each_entry_reverse(_info, &abox_dump_list_head, list) {
|
|
if (!_info->registered)
|
|
break;
|
|
}
|
|
spin_unlock_irqrestore(&abox_dump_lock, flags);
|
|
|
|
if (&_info->list == &abox_dump_list_head)
|
|
return -EINVAL;
|
|
|
|
info = devm_kmemdup(_info->dev, _info, sizeof(*_info), GFP_KERNEL);
|
|
abox_info(info->dev, "%s(%d, %s, %#zx)\n", __func__, info->id,
|
|
info->name, info->buffer.bytes);
|
|
info->file = abox_dump_register_file(info->name, info, &abox_dump_fops);
|
|
init_waitqueue_head(&info->file_waitqueue);
|
|
INIT_WORK(&info->auto_work, abox_dump_auto_dump_work_func);
|
|
INIT_WORK(&info->memlog_work, abox_dump_memlog_work_func);
|
|
info->registered = true;
|
|
|
|
spin_lock_irqsave(&abox_dump_lock, flags);
|
|
list_replace(&_info->list, &info->list);
|
|
spin_unlock_irqrestore(&abox_dump_lock, flags);
|
|
|
|
platform_device_register_data(info->dev, "abox-dump",
|
|
info->id, NULL, 0);
|
|
|
|
kfree(_info);
|
|
return 0;
|
|
}
|
|
|
|
static void abox_dump_register_work_func(struct work_struct *work)
|
|
{
|
|
abox_dbg(abox_dump_dev_abox, "%s\n", __func__);
|
|
|
|
do {} while (abox_dump_register_work_single() >= 0);
|
|
}
|
|
|
|
static DECLARE_WORK(abox_dump_register_work, abox_dump_register_work_func);
|
|
|
|
static int abox_dump_allocate_id(int gid, int id)
|
|
{
|
|
struct abox_dump_info *info;
|
|
unsigned long flags;
|
|
int ret = id;
|
|
|
|
do {
|
|
spin_lock_irqsave(&abox_dump_lock, flags);
|
|
list_for_each_entry(info, &abox_dump_list_head, list) {
|
|
if (info->id == ret) {
|
|
ret++;
|
|
break;
|
|
}
|
|
}
|
|
spin_unlock_irqrestore(&abox_dump_lock, flags);
|
|
} while (&info->list != &abox_dump_list_head);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int abox_dump_register(struct abox_data *data, int gid, int id,
|
|
const char *name, void *area, phys_addr_t addr, size_t bytes)
|
|
{
|
|
struct device *dev = data->dev;
|
|
struct abox_dump_info *info;
|
|
unsigned long flags;
|
|
|
|
abox_dbg(dev, "%s[%d](%s, %#zx)\n", __func__, id, name, bytes);
|
|
|
|
spin_lock_irqsave(&abox_dump_lock, flags);
|
|
info = abox_dump_get_info(id);
|
|
spin_unlock_irqrestore(&abox_dump_lock, flags);
|
|
if (info) {
|
|
abox_dbg(dev, "already registered dump: %d\n", id);
|
|
return 0;
|
|
}
|
|
|
|
info = kzalloc(sizeof(*info), GFP_ATOMIC);
|
|
if (!info)
|
|
return -ENOMEM;
|
|
|
|
mutex_init(&info->lock);
|
|
info->c_gid = gid;
|
|
info->c_id = id;
|
|
info->id = abox_dump_allocate_id(info->c_gid, info->c_id);
|
|
strlcpy(info->name, name, sizeof(info->name));
|
|
info->buffer.area = area;
|
|
info->buffer.addr = addr;
|
|
info->buffer.bytes = bytes;
|
|
abox_dump_dev_abox = info->dev = dev;
|
|
|
|
spin_lock_irqsave(&abox_dump_lock, flags);
|
|
list_add_tail(&info->list, &abox_dump_list_head);
|
|
spin_unlock_irqrestore(&abox_dump_lock, flags);
|
|
|
|
schedule_work(&abox_dump_register_work);
|
|
|
|
return 0;
|
|
}
|
|
|
|
SND_SOC_DAILINK_DEF(dailink_comp_dummy, DAILINK_COMP_ARRAY(COMP_DUMMY()));
|
|
|
|
static void abox_dump_register_card_work_func(struct work_struct *work)
|
|
{
|
|
int i;
|
|
|
|
pr_debug("%s\n", __func__);
|
|
|
|
for (i = 0; i < abox_dump_card.num_links; i++) {
|
|
struct snd_soc_dai_link *link = &abox_dump_card.dai_link[i];
|
|
|
|
if (link->name)
|
|
continue;
|
|
|
|
link->name = link->stream_name =
|
|
kasprintf(GFP_KERNEL, "dummy%d", i);
|
|
link->cpus = dailink_comp_dummy;
|
|
link->num_cpus = ARRAY_SIZE(dailink_comp_dummy);
|
|
link->codecs = dailink_comp_dummy;
|
|
link->num_codecs = ARRAY_SIZE(dailink_comp_dummy);
|
|
link->no_pcm = 1;
|
|
}
|
|
abox_register_extra_sound_card(abox_dump_card.dev, &abox_dump_card, 2);
|
|
}
|
|
|
|
static DECLARE_DELAYED_WORK(abox_dump_register_card_work,
|
|
abox_dump_register_card_work_func);
|
|
|
|
static int abox_dump_add_dai_link(struct device *dev)
|
|
{
|
|
int id = to_platform_device(dev)->id;
|
|
struct abox_dump_info *info = abox_dump_get_info(id);
|
|
struct snd_soc_dai_link *link;
|
|
|
|
abox_dbg(dev, "%s[%d]\n", __func__, id);
|
|
|
|
cancel_delayed_work_sync(&abox_dump_register_card_work);
|
|
|
|
if (abox_dump_card.num_links <= id) {
|
|
link = krealloc(abox_dump_card.dai_link,
|
|
sizeof(abox_dump_card.dai_link[0]) * (id + 1),
|
|
GFP_KERNEL | __GFP_ZERO);
|
|
if (!link)
|
|
return -ENOMEM;
|
|
|
|
abox_dump_card.dai_link = link;
|
|
}
|
|
|
|
link = &abox_dump_card.dai_link[id];
|
|
kfree(link->name);
|
|
link->name = link->stream_name = kstrdup(info->name, GFP_KERNEL);
|
|
link->id = id;
|
|
link->cpus = dailink_comp_dummy;
|
|
link->num_cpus = ARRAY_SIZE(dailink_comp_dummy);
|
|
link->platforms = devm_kcalloc(dev, 1, sizeof(*link->platforms),
|
|
GFP_KERNEL);
|
|
if (!link->platforms)
|
|
return -ENOMEM;
|
|
link->platforms->name = dev_name(dev);
|
|
link->num_platforms = 1;
|
|
link->codecs = dailink_comp_dummy;
|
|
link->num_codecs = ARRAY_SIZE(dailink_comp_dummy);
|
|
link->ignore_suspend = 1;
|
|
link->ignore_pmdown_time = 1;
|
|
link->no_pcm = 0;
|
|
link->capture_only = true;
|
|
|
|
if (abox_dump_card.num_links <= id)
|
|
abox_dump_card.num_links = id + 1;
|
|
|
|
schedule_delayed_work(&abox_dump_register_card_work, HZ);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int samsung_abox_dump_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
int id = to_platform_device(dev)->id;
|
|
struct abox_dump_info *info = abox_dump_get_info(id);
|
|
int ret = 0;
|
|
|
|
abox_dbg(dev, "%s[%d]\n", __func__, id);
|
|
|
|
if (id < 0) {
|
|
abox_dump_card.dev = &pdev->dev;
|
|
schedule_delayed_work(&abox_dump_register_card_work, 0);
|
|
} else {
|
|
info->dev = dev;
|
|
pm_runtime_no_callbacks(dev);
|
|
pm_runtime_enable(dev);
|
|
|
|
ret = devm_snd_soc_register_component(dev, &abox_dump_component,
|
|
NULL, 0);
|
|
if (ret < 0)
|
|
abox_err(dev, "register component failed: %d\n", ret);
|
|
|
|
ret = abox_dump_add_dai_link(dev);
|
|
if (ret < 0)
|
|
abox_err(dev, "add dai link failed: %d\n", ret);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int samsung_abox_dump_remove(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
int id = to_platform_device(dev)->id;
|
|
|
|
abox_dbg(dev, "%s[%d]\n", __func__, id);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void samsung_abox_dump_shutdown(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
|
|
abox_dbg(dev, "%s\n", __func__);
|
|
pm_runtime_disable(dev);
|
|
}
|
|
|
|
static const struct platform_device_id samsung_abox_dump_driver_ids[] = {
|
|
{
|
|
.name = "abox-dump",
|
|
},
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(platform, samsung_abox_dump_driver_ids);
|
|
|
|
struct platform_driver samsung_abox_dump_driver = {
|
|
.probe = samsung_abox_dump_probe,
|
|
.remove = samsung_abox_dump_remove,
|
|
.shutdown = samsung_abox_dump_shutdown,
|
|
.driver = {
|
|
.name = "abox-dump",
|
|
.owner = THIS_MODULE,
|
|
},
|
|
.id_table = samsung_abox_dump_driver_ids,
|
|
};
|
|
|
|
void abox_dump_init(struct device *dev_abox)
|
|
{
|
|
static struct platform_device *pdev;
|
|
static struct proc_dir_entry *auto_start, *auto_stop;
|
|
|
|
abox_info(dev_abox, "%s\n", __func__);
|
|
|
|
abox_dump_dev_abox = dev_abox;
|
|
|
|
if (IS_ERR_OR_NULL(auto_start))
|
|
auto_start = abox_proc_create_file("dump_auto_start", 0660,
|
|
NULL, &abox_dump_auto_start_fops, dev_abox, 0);
|
|
|
|
if (IS_ERR_OR_NULL(auto_stop))
|
|
auto_stop = abox_proc_create_file("dump_auto_stop", 0660,
|
|
NULL, &abox_dump_auto_stop_fops, dev_abox, 0);
|
|
|
|
if (IS_ERR_OR_NULL(dir_dump))
|
|
dir_dump = abox_proc_mkdir("dump", NULL);
|
|
|
|
if (IS_ERR_OR_NULL(pdev))
|
|
pdev = platform_device_register_data(dev_abox,
|
|
"abox-dump", -1, NULL, 0);
|
|
|
|
abox_dump_memlog_register(dev_abox);
|
|
}
|