// 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 #include #include #include #include #include #include #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); }