// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2019 MediaTek Inc. */ #include #include #include #include #include #include #include "adsp_reserved_mem.h" #include "adsp_feature_define.h" #include "adsp_platform_driver.h" #include "adsp_logger.h" #include "adsp_excep.h" #include "adsp_core.h" /* ----------------------------- sys fs ------------------------------------ */ static inline ssize_t dev_dump_show(struct device *dev, struct device_attribute *attr, char *buf) { struct adsp_priv *pdata = container_of(dev_get_drvdata(dev), struct adsp_priv, mdev); int n = 0; n += scnprintf(buf + n, PAGE_SIZE - n, "name:%s id = %d\n", pdata->name, pdata->id); n += scnprintf(buf + n, PAGE_SIZE - n, "cfg = %p, size = %zu\n", adspsys->cfg, adspsys->cfg_size); n += scnprintf(buf + n, PAGE_SIZE - n, "cfg2 = %p, size = %zu\n", adspsys->cfg2, adspsys->cfg2_size); n += scnprintf(buf + n, PAGE_SIZE - n, "cfg_secure = %p, size = %zu\n", adspsys->cfg_secure, adspsys->cfg_secure_size); n += scnprintf(buf + n, PAGE_SIZE - n, "itcm = %p, size = %zu\n", pdata->itcm, pdata->itcm_size); n += scnprintf(buf + n, PAGE_SIZE - n, "dtcm = %p, size = %zu\n", pdata->dtcm, pdata->dtcm_size); n += scnprintf(buf + n, PAGE_SIZE - n, "sysram = %p, size = %zu\n", pdata->sysram, pdata->sysram_size); n += scnprintf(buf + n, PAGE_SIZE - n, "irq = %d, %d, %d\n", pdata->irq[0].seq, pdata->irq[1].seq, pdata->irq[2].seq); n += scnprintf(buf + n, PAGE_SIZE - n, "status = %d, feature_set = %X\n", pdata->state, pdata->feature_set); if (pdata->send_mbox) n += scnprintf(buf + n, PAGE_SIZE - n, "mailbox send = %d\n", pdata->send_mbox->mbox); if (pdata->recv_mbox) n += scnprintf(buf + n, PAGE_SIZE - n, "mailbox recv = %d\n", pdata->recv_mbox->mbox); return n; } DEVICE_ATTR_RO(dev_dump); static void ipi_test_handler(int id, void *data, unsigned int len) { unsigned long long ns = *(unsigned long long *)data; pr_info("%s, receive ipi_test from adsp, adsp_time[%llu.%llu]", __func__, ns / 1000000000, (ns / 1000000) % 1000); } static inline ssize_t ipi_test_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct adsp_priv *pdata = container_of(dev_get_drvdata(dev), struct adsp_priv, mdev); int value = 0; if (kstrtoint(buf, 10, &value)) return -EINVAL; if (_adsp_register_feature(pdata->id, SYSTEM_FEATURE_ID, 0) == 0) { adsp_push_message(ADSP_IPI_TEST1, &value, sizeof(value), 20, pdata->id); _adsp_deregister_feature(pdata->id, SYSTEM_FEATURE_ID, 0); } return count; } static inline ssize_t ipi_test_show(struct device *dev, struct device_attribute *attr, char *buf) { struct adsp_priv *pdata = container_of(dev_get_drvdata(dev), struct adsp_priv, mdev); unsigned int value = 0x5A5A; int ret; adsp_ipi_registration(ADSP_IPI_TEST1, ipi_test_handler, "ipi_test"); if (_adsp_register_feature(pdata->id, SYSTEM_FEATURE_ID, 0) == 0) { pr_info("%s, send ipi_test to adsp%d", __func__, pdata->id); ret = adsp_push_message(ADSP_IPI_TEST1, &value, sizeof(value), 20, pdata->id); _adsp_deregister_feature(pdata->id, SYSTEM_FEATURE_ID, 0); return scnprintf(buf, PAGE_SIZE, "ADSP ipi send ret=%d\n", ret); } else return scnprintf(buf, PAGE_SIZE, "ADSP register fail\n"); } DEVICE_ATTR_RW(ipi_test); static inline ssize_t suspend_cmd_show(struct device *dev, struct device_attribute *attr, char *buf) { struct adsp_priv *pdata = container_of(dev_get_drvdata(dev), struct adsp_priv, mdev); return adsp_dump_feature_state(pdata->id, buf, PAGE_SIZE); } static inline ssize_t suspend_cmd_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { uint32_t fid = 0; char *temp = NULL, *token1 = NULL, *token2 = NULL; char *pin = NULL; char delim[] = " ,\t\n"; struct adsp_priv *pdata = container_of(dev_get_drvdata(dev), struct adsp_priv, mdev); temp = kstrdup(buf, GFP_KERNEL); pin = temp; token1 = strsep(&pin, delim); token2 = strsep(&pin, delim); if (!token1 || !token2) goto EXIT; fid = adsp_get_feature_index(token2); if (fid == SYSTEM_FEATURE_ID) goto EXIT; if (!is_feature_in_set(pdata->id, fid)) goto EXIT; if (strcmp(token1, "regi") == 0) _adsp_register_feature(pdata->id, fid, 0); if (strcmp(token1, "deregi") == 0) _adsp_deregister_feature(pdata->id, fid, 0); EXIT: kfree(temp); return count; } DEVICE_ATTR_RW(suspend_cmd); static inline ssize_t log_enable_show(struct device *dev, struct device_attribute *attr, char *buf) { struct adsp_priv *pdata = container_of(dev_get_drvdata(dev), struct adsp_priv, mdev); return adsp_dump_log_state(pdata->log_ctrl, buf, PAGE_SIZE); } static inline ssize_t log_enable_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { unsigned int enable = 0; struct adsp_priv *pdata = container_of(dev_get_drvdata(dev), struct adsp_priv, mdev); if (kstrtouint(buf, 0, &enable) != 0) return -EINVAL; adsp_log_enable(pdata->log_ctrl, pdata->id, enable); return count; } DEVICE_ATTR_RW(log_enable); static inline ssize_t wakelock_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { unsigned int input = 0; int ret = 0; struct adsp_priv *pdata = container_of(dev_get_drvdata(dev), struct adsp_priv, mdev); if (kstrtouint(buf, 0, &input) != 0) return -EINVAL; if (input == 1) ret = adsp_awake_lock(pdata->id); else if (input == 0) ret = adsp_awake_unlock(pdata->id); pr_info("%s, input %u, ret = %d", __func__, input, ret); return count; } DEVICE_ATTR_WO(wakelock); static struct attribute *adsp_default_attrs[] = { &dev_attr_dev_dump.attr, &dev_attr_ipi_test.attr, &dev_attr_suspend_cmd.attr, &dev_attr_log_enable.attr, &dev_attr_wakelock.attr, NULL, }; struct attribute_group adsp_default_attr_group = { .attrs = adsp_default_attrs, }; /* ---------------------------- debug fs ----------------------------------- */ static ssize_t adsp_debug_read(struct file *filp, char __user *buf, size_t count, loff_t *pos) { char *buffer = NULL; size_t n = 0, max_size; struct adsp_priv *pdata = filp->private_data; u32 memid = ADSP_A_DEBUG_DUMP_MEM_ID; if (pdata->id == ADSP_B_ID) memid = ADSP_B_DEBUG_DUMP_MEM_ID; buffer = adsp_get_reserve_mem_virt(memid); max_size = adsp_get_reserve_mem_size(memid); if (buffer) n = strnlen(buffer, max_size); return simple_read_from_buffer(buf, count, pos, buffer, n); } static ssize_t adsp_debug_write(struct file *filp, const char __user *buffer, size_t count, loff_t *ppos) { char buf[64]; struct adsp_priv *pdata = filp->private_data; if (copy_from_user(buf, buffer, min(count, sizeof(buf)))) return -EFAULT; if (_adsp_register_feature(pdata->id, SYSTEM_FEATURE_ID, 0) == 0) { adsp_push_message(ADSP_IPI_ADSP_TIMER, buf, min(count, sizeof(buf)), 0, pdata->id); _adsp_deregister_feature(pdata->id, SYSTEM_FEATURE_ID, 0); } return count; } const struct file_operations adsp_debug_ops = { .open = simple_open, .read = adsp_debug_read, .write = adsp_debug_write, }; /* ------------------------------ misc device ----------------------------- */ /*============================================================================== * ioctl *============================================================================== */ #define AUDIO_DSP_IOC_MAGIC 'a' #define AUDIO_DSP_IOCTL_ADSP_REG_FEATURE \ _IOW(AUDIO_DSP_IOC_MAGIC, 0, unsigned int) #define AUDIO_DSP_IOCTL_ADSP_QUERY_STATUS \ _IOR(AUDIO_DSP_IOC_MAGIC, 1, unsigned int) #define AUDIO_DSP_IOCTL_ADSP_RESET_CBK \ _IOR(AUDIO_DSP_IOC_MAGIC, 2, unsigned int) union ioctl_param { struct { uint16_t enable; uint16_t fid; } cmd0; struct { int16_t flag; uint16_t cid; } cmd1; }; /* user-space feature reset */ static int adsp_hal_feature_table[ADSP_NUM_FEATURE_ID]; void reset_hal_feature_table(void) { int i; for (i = 0; i < ADSP_NUM_FEATURE_ID; i++) { while (adsp_hal_feature_table[i] > 0) { adsp_deregister_feature(i); adsp_hal_feature_table[i]--; } } } EXPORT_SYMBOL(reset_hal_feature_table); /* file operations */ static ssize_t adsp_driver_read(struct file *filp, char __user *data, size_t len, loff_t *ppos) { struct adsp_priv *pdata = container_of(filp->private_data, struct adsp_priv, mdev); return adsp_log_read(pdata->log_ctrl, data, len); } static int adsp_driver_open(struct inode *inode, struct file *file) { return nonseekable_open(inode, file); } static unsigned int adsp_driver_poll(struct file *filp, struct poll_table_struct *poll) { struct adsp_priv *pdata = container_of(filp->private_data, struct adsp_priv, mdev); if (!pdata->log_ctrl || !pdata->log_ctrl->inited) return 0; if (!(filp->f_mode & FMODE_READ)) return 0; return adsp_log_poll(pdata->log_ctrl); } static long adsp_driver_ioctl( struct file *filp, unsigned int cmd, unsigned long arg) { int ret = 0; union ioctl_param t; switch (cmd) { case AUDIO_DSP_IOCTL_ADSP_REG_FEATURE: { if (copy_from_user(&t, (void *)arg, sizeof(t))) { ret = -EFAULT; break; } if (t.cmd0.fid == SYSTEM_FEATURE_ID) { pr_debug("%s(), reg/dreg SYSTEM_FEATURE_ID is not permitted!\n", __func__); ret = -EPERM; break; } if (t.cmd0.enable) ret = adsp_register_feature(t.cmd0.fid); else ret = adsp_deregister_feature(t.cmd0.fid); if (ret == 0) { if (t.cmd0.enable) adsp_hal_feature_table[t.cmd0.fid]++; else adsp_hal_feature_table[t.cmd0.fid]--; if (adsp_hal_feature_table[t.cmd0.fid] < 0) adsp_hal_feature_table[t.cmd0.fid] = 0; } break; } case AUDIO_DSP_IOCTL_ADSP_QUERY_STATUS: { if (copy_from_user(&t, (void *)arg, sizeof(t))) { ret = -EFAULT; break; } t.cmd1.flag = is_adsp_ready(t.cmd1.cid); if (copy_to_user((void __user *)arg, &t, sizeof(t))) { ret = -EFAULT; break; } break; } default: pr_debug("%s(), invalid ioctl cmd\n", __func__); } if (ret < 0) pr_info("%s(), ioctl error %d\n", __func__, ret); return ret; } static long adsp_driver_compat_ioctl( struct file *file, unsigned int cmd, unsigned long arg) { if (!file->f_op || !file->f_op->unlocked_ioctl) { pr_notice("op null\n"); return -ENOTTY; } return file->f_op->unlocked_ioctl(file, cmd, arg); } const struct file_operations adspsys_file_ops = { .owner = THIS_MODULE, .open = adsp_driver_open, .unlocked_ioctl = adsp_driver_ioctl, #if IS_ENABLED(CONFIG_COMPAT) .compat_ioctl = adsp_driver_compat_ioctl, #endif }; const struct file_operations adsp_core_file_ops = { .owner = THIS_MODULE, .read = adsp_driver_read, .open = adsp_driver_open, .poll = adsp_driver_poll, };