// SPDX-License-Identifier: GPL-2.0-or-later /* * ALSA SoC - Samsung Abox Failsafe 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. */ /* #define DEBUG */ #include #include #include #include #include #include #include #include "abox_util.h" #include "abox.h" #include "abox_proc.h" #include "abox_log.h" #include "abox_memlog.h" #include "abox_dbg.h" #define SMART_FAILSAFE static int abox_failsafe_reset_count; static struct device *abox_failsafe_dev; static struct device *abox_failsafe_dev_abox; static struct abox_data *abox_failsafe_abox_data; static atomic_t abox_failsafe_reported; static bool abox_failsafe_service; static int abox_failsafe_start(struct device *dev, struct abox_data *data) { int ret = 0; abox_dbg(dev, "%s\n", __func__); BUG_ON(data->error && (data->debug_mode == DEBUG_MODE_FILE)); if (atomic_read(&abox_failsafe_reported)) { /* Set AUD_OPTION[2] for abox silent reset in PMU */ abox_silent_reset(data, true); if (abox_failsafe_service) pm_runtime_put(dev); abox_info(dev, "%s\n", __func__); abox_clear_cpu_gear_requests(dev); abox_clear_mif_requests(dev); } return ret; } static int abox_failsafe_end(struct device *dev) { int ret = 0; abox_dbg(dev, "%s\n", __func__); if (atomic_cmpxchg(&abox_failsafe_reported, 1, 0)) { abox_info(dev, "%s\n", __func__); abox_failsafe_abox_data->failsafe = false; abox_failsafe_abox_data->error = false; wake_up_interruptible(&abox_failsafe_abox_data->offline_poll_wait); } return ret; } static void abox_failsafe_report_work_func(struct work_struct *work) { struct device *dev = abox_failsafe_dev; char env[32] = {0,}; char *envp[2] = {env, NULL}; abox_dbg(dev, "%s\n", __func__); pm_runtime_barrier(dev); snprintf(env, sizeof(env), "COUNT=%d", abox_failsafe_reset_count); kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp); } DECLARE_WORK(abox_failsafe_report_work, abox_failsafe_report_work_func); #ifdef SMART_FAILSAFE void abox_failsafe_report(struct device *dev, bool error) { abox_dbg(dev, "%s\n", __func__); BUG_ON(error && (abox_failsafe_abox_data->debug_mode == DEBUG_MODE_DRAM)); abox_failsafe_dev = dev; abox_failsafe_reset_count++; if (!atomic_cmpxchg(&abox_failsafe_reported, 0, 1)) { abox_failsafe_abox_data->failsafe = true; abox_failsafe_abox_data->error = error; if (abox_failsafe_service) pm_runtime_get(dev); schedule_work(&abox_failsafe_report_work); wake_up_interruptible(&abox_failsafe_abox_data->offline_poll_wait); } } #else /* TODO: Use SMART_FAILSAFE. * SMART_FAILSAFE needs support from user space. */ void abox_failsafe_report(struct device *dev, bool error) { struct abox_data *data = dev_get_drvdata(dev); abox_dbg(dev, "%s\n", __func__); BUG_ON(error && (abox_failsafe_abox_data->debug_mode == DEBUG_MODE_DRAM)); abox_failsafe_start(dev, data); } #endif void abox_failsafe_report_reset(struct device *dev) { abox_dbg(dev, "%s\n", __func__); abox_failsafe_end(dev); } static int abox_failsafe_reset(struct device *dev, struct abox_data *data) { abox_dbg(dev, "%s\n", __func__); return abox_failsafe_start(data->dev, data); } static ssize_t abox_failsafe_reset_write(struct file *file, const char __user *_buf, size_t count, loff_t *ppos) { static const char code[] = "CALLIOPE"; struct device *dev = abox_proc_data(file); struct abox_data *data = dev_get_drvdata(dev); char buf[64] = {0,}; ssize_t ret; abox_dbg(dev, "%s(%zu)\n", __func__, count); ret = simple_write_to_buffer(buf, sizeof(buf) - 1, ppos, _buf, count); if (ret < 0) return ret; if (!strncmp(code, buf, min(sizeof(code) - 1, count))) { ret = abox_failsafe_reset(dev, data); if (ret < 0) return ret; } return count; } static const struct proc_ops abox_failsafe_reset_fops = { .proc_write = abox_failsafe_reset_write, }; static ssize_t abox_failsafe_online_read(struct file *file, char __user *data, size_t count, loff_t *ppos) { int len; char buffer[64]; /* make sure offline is updated prior to wake up */ rmb(); len = snprintf(buffer, sizeof(buffer), "%s\n", abox_failsafe_abox_data->failsafe ? "OFFLINE" : "ONLINE"); return simple_read_from_buffer(data, count, ppos, buffer, len); } static __poll_t abox_failsafe_online_poll(struct file *file, poll_table *wait) { poll_wait(file, &abox_failsafe_abox_data->offline_poll_wait, wait); if (abox_failsafe_abox_data->failsafe) return POLLIN | POLLPRI | POLLRDNORM; else return 0; } static int abox_failsafe_online_release(struct inode *i, struct file *f) { wake_up_interruptible(&abox_failsafe_abox_data->offline_poll_wait); return 0; } static DEVICE_BOOL_ATTR(service, 0660, abox_failsafe_service); static DEVICE_INT_ATTR(reset_count, 0660, abox_failsafe_reset_count); static const struct proc_ops abox_failsafe_online_fops = { .proc_read = abox_failsafe_online_read, .proc_poll = abox_failsafe_online_poll, .proc_release = abox_failsafe_online_release, }; void abox_failsafe_init(struct device *dev) { struct proc_dir_entry *dir, *ret_dir; int ret; abox_failsafe_dev_abox = dev; abox_failsafe_abox_data = (struct abox_data *)dev_get_drvdata(dev); abox_failsafe_service = true; dir = abox_proc_mkdir("failsafe", NULL); ret = device_create_file(dev, &dev_attr_service.attr); if (ret < 0) abox_warn(dev, "%s: %s file creation failed: %d\n", __func__, "service", ret); abox_proc_symlink_attr(dev, "service", dir); ret = device_create_file(dev, &dev_attr_reset_count.attr); if (ret < 0) abox_warn(dev, "%s: %s file creation failed: %d\n", __func__, "reset_count", ret); abox_proc_symlink_attr(dev, "reset_count", dir); ret_dir = abox_proc_create_file("online", 0660, dir, &abox_failsafe_online_fops, dev, 0); if (IS_ERR_OR_NULL(ret_dir)) abox_warn(dev, "%s: %s file creation failed: %d\n", __func__, "online", PTR_ERR(ret_dir)); ret_dir = abox_proc_create_file("reset", 0660, dir, &abox_failsafe_reset_fops, dev, 0); if (IS_ERR_OR_NULL(ret_dir)) abox_warn(dev, "%s: %s file creation failed: %d\n", __func__, "reset", PTR_ERR(ret_dir)); }