| // SPDX-License-Identifier: GPL-2.0-only | |
| /* | |
| * Copyright (C) 2015 Google, Inc. | |
| */ | |
| #include <linux/platform_device.h> | |
| #include <linux/trusty/smcall.h> | |
| #include <linux/trusty/trusty.h> | |
| #include <linux/notifier.h> | |
| #include <linux/scatterlist.h> | |
| #include <linux/slab.h> | |
| #include <linux/mm.h> | |
| #include <linux/mod_devicetable.h> | |
| #include <linux/module.h> | |
| #include <linux/moduleparam.h> | |
| #include <linux/log2.h> | |
| #include <linux/miscdevice.h> | |
| #include <linux/poll.h> | |
| #include <linux/seq_file.h> | |
| #include <linux/version.h> | |
| #include <linux/vmalloc.h> | |
| #if (KERNEL_VERSION(5, 14, 0) <= LINUX_VERSION_CODE) | |
| #include <linux/panic_notifier.h> | |
| #endif | |
| #include <asm/page.h> | |
| #include "trusty-log.h" | |
| /* | |
| * Rationale for the chosen default log buffer size: | |
| * - the log buffer shall contain unthrottled Trusty crash dump. | |
| * - the register list portion of a crash dump is about 1KB | |
| * - the memory-around-registers portion of a crash dump can be up to 12 KB | |
| * - an average size backtrace is about 1 KB | |
| * - average length of non-crash trusty logs during boot is about 85 characters | |
| * - a crash dump with 50 lines of context therefore requires up to 18 KB | |
| * - buffer size needs to be power-of-two number of bytes | |
| * - rounding up to power of two from 18 KB gives 32 KB | |
| * The log size can be adjusted by setting the "trusty_log.log_size" parameter | |
| * on the kernel command line. The specified value will be adjusted as needed. | |
| */ | |
| #define TRUSTY_LOG_DEFAULT_SIZE (32768) | |
| #define TRUSTY_LOG_MIN_SIZE (PAGE_SIZE / 2) | |
| #define TRUSTY_LOG_MAX_SIZE (1 * 1024 * 1024 * 1024) | |
| #define TRUSTY_LINE_BUFFER_SIZE (256) | |
| static size_t log_size_param = TRUSTY_LOG_DEFAULT_SIZE; | |
| static int trusty_log_size_set(const char *val, const struct kernel_param *kp) | |
| { | |
| unsigned long long requested = memparse(val, NULL); | |
| if (requested < TRUSTY_LOG_MIN_SIZE) | |
| requested = TRUSTY_LOG_MIN_SIZE; | |
| if (requested > TRUSTY_LOG_MAX_SIZE) | |
| requested = TRUSTY_LOG_MAX_SIZE; | |
| requested = rounddown_pow_of_two(requested); | |
| log_size_param = requested; | |
| return 0; | |
| } | |
| static int trusty_log_size_get(char *buffer, const struct kernel_param *kp) | |
| { | |
| return scnprintf(buffer, PAGE_SIZE, "%zu\n", log_size_param); | |
| } | |
| module_param_call(log_size, trusty_log_size_set, trusty_log_size_get, NULL, | |
| 0644); | |
| /* | |
| * The "log_to_dmesg" parameter can have three values: "never", "always", | |
| * and "until_first_reader". "never" indicates Trusty logs will never be | |
| * copied to the linux kernel log, while "always" indicates they will always | |
| * be copied to the linux kernel log. In both cases Trusty logs can still | |
| * be read from the /dev/trusty-logX virtual file. In the case of "always" | |
| * that may mean logs show up duplicated in logcat. | |
| * The third option, "until_first_reader", copies Trusty logs to the linux | |
| * kernel log, but only until /dev/trusty-logX is first opened. After that | |
| * Trusty logs will no longer be copied to the kernel log and are only | |
| * available from /dev/trusty-logX. | |
| */ | |
| enum log_to_dmesg_options { | |
| NEVER, | |
| ALWAYS, | |
| UNTIL_FIRST_READER | |
| }; | |
| static const char * const log_to_dmesg_opt_names[] = { | |
| "never", "always", "until_first_reader" | |
| }; | |
| static int log_to_dmesg_param = NEVER; | |
| static int trusty_log_mode_set(const char *val, const struct kernel_param *kp) | |
| { | |
| int i = sysfs_match_string(log_to_dmesg_opt_names, val); | |
| if (i < 0) | |
| return i; | |
| log_to_dmesg_param = i; | |
| return 0; | |
| } | |
| static int trusty_log_mode_get(char *buffer, const struct kernel_param *kp) | |
| { | |
| int i; | |
| /* | |
| * Output buffer is PAGE_SIZE, of which we'll use only 35 bytes, | |
| * so bounds checks are not necessary in the following code. | |
| */ | |
| *buffer = 0; | |
| for (i = 0; i < ARRAY_SIZE(log_to_dmesg_opt_names); i++) { | |
| if (log_to_dmesg_param == i) | |
| strcat(buffer, "["); | |
| strcat(buffer, log_to_dmesg_opt_names[i]); | |
| if (log_to_dmesg_param == i) | |
| strcat(buffer, "]"); | |
| strcat(buffer, " "); | |
| } | |
| strcat(buffer, "\n"); | |
| return strlen(buffer); | |
| } | |
| module_param_call(log_to_dmesg, trusty_log_mode_set, trusty_log_mode_get, NULL, | |
| 0644); | |
| /* | |
| * If we log too much and a UART or other slow source is connected, we can stall | |
| * out another thread which is doing printk. | |
| * | |
| * Trusty crash logs are currently ~16 lines, so 100 should include context and | |
| * the crash most of the time. | |
| */ | |
| static struct ratelimit_state trusty_log_rate_limit = | |
| RATELIMIT_STATE_INIT("trusty_log", 1 * HZ, 100); | |
| module_param_named(log_ratelimit_burst, trusty_log_rate_limit.burst, int, 0644); | |
| module_param_named(log_ratelimit_interval, trusty_log_rate_limit.interval, int, | |
| 0644); | |
| /** | |
| * struct trusty_log_sfile - trusty log misc device state | |
| * | |
| * @misc: misc device created for the trusty log virtual file | |
| * @device_name: misc device name following the convention | |
| * "trusty-<name><id>" | |
| */ | |
| struct trusty_log_sfile { | |
| struct miscdevice misc; | |
| char device_name[64]; | |
| }; | |
| /** | |
| * struct trusty_log_sink_state - trusty log sink state | |
| * | |
| * @get: current read unwrapped index | |
| * @last_successful_next: | |
| * index for the next line after the last successful get | |
| * @trusty_panicked: trusty panic status at the start of the sink interation | |
| * (only used for kernel log sink) | |
| * @sfile: seq_file used for sinking to a virtual file (misc device); | |
| * set to NULL for the kernel log sink. | |
| * @ignore_overflow: ignore_overflow used to coalesce overflow messages and | |
| * avoid reporting an overflow when sinking the oldest | |
| * line to the virtual file (only used for virtual file sink) | |
| * | |
| * A sink state structure is used for both the kernel log sink | |
| * and the virtual device sink. | |
| * An instance of the sink state structure is dynamically created | |
| * for each read iteration of the trusty log virtual file (misc device). | |
| * | |
| */ | |
| struct trusty_log_sink_state { | |
| u32 get; | |
| u32 last_successful_next; | |
| bool trusty_panicked; | |
| /* virtual file sink specific attributes */ | |
| struct seq_file *sfile; | |
| bool ignore_overflow; | |
| }; | |
| struct trusty_log_state { | |
| struct device *dev; | |
| struct device *trusty_dev; | |
| struct trusty_log_sfile log_sfile; | |
| struct kref refcount; | |
| /* | |
| * This lock is here to ensure only one consumer will read | |
| * from the log ring buffer at a time. | |
| */ | |
| spinlock_t lock; | |
| struct log_rb *log; | |
| struct trusty_log_sink_state klog_sink; | |
| u32 log_num_pages; | |
| struct scatterlist *sg; | |
| trusty_shared_mem_id_t log_pages_shared_mem_id; | |
| struct notifier_block call_notifier; | |
| struct notifier_block panic_notifier; | |
| char line_buffer[TRUSTY_LINE_BUFFER_SIZE]; | |
| wait_queue_head_t poll_waiters; | |
| /* this lock protects access to wake_put */ | |
| spinlock_t wake_up_lock; | |
| u32 last_wake_put; | |
| bool have_first_reader; | |
| bool registered; | |
| }; | |
| static inline u32 u32_add_overflow(u32 a, u32 b) | |
| { | |
| u32 d; | |
| if (check_add_overflow(a, b, &d)) { | |
| /* | |
| * silence the overflow, | |
| * what matters in the log buffer context | |
| * is the casted addition | |
| */ | |
| } | |
| return d; | |
| } | |
| static inline u32 u32_sub_overflow(u32 a, u32 b) | |
| { | |
| u32 d; | |
| if (check_sub_overflow(a, b, &d)) { | |
| /* | |
| * silence the overflow, | |
| * what matters in the log buffer context | |
| * is the casted substraction | |
| */ | |
| } | |
| return d; | |
| } | |
| static int log_read_line(struct trusty_log_state *s, u32 put, u32 get) | |
| { | |
| struct log_rb *log = s->log; | |
| int i; | |
| char c = '\0'; | |
| size_t max_to_read = | |
| min_t(size_t, | |
| u32_sub_overflow(put, get), | |
| sizeof(s->line_buffer) - 1); | |
| size_t mask = log->sz - 1; | |
| for (i = 0; i < max_to_read && c != '\n';) { | |
| c = log->data[get & mask]; | |
| s->line_buffer[i++] = c; | |
| get = u32_add_overflow(get, 1); | |
| } | |
| s->line_buffer[i] = '\0'; | |
| return i; | |
| } | |
| /** | |
| * trusty_log_has_data() - returns true when more data is available to sink | |
| * @s: Current log state. | |
| * @sink: trusty_log_sink_state holding the get index on a given sink | |
| * | |
| * Return: true if data is available. | |
| */ | |
| static bool trusty_log_has_data(struct trusty_log_state *s, | |
| struct trusty_log_sink_state *sink) | |
| { | |
| struct log_rb *log = s->log; | |
| return (log->put != sink->get); | |
| } | |
| /** | |
| * trusty_log_start() - initialize the sink iteration either to kernel log | |
| * or to secondary log_sfile | |
| * @s: Current log state. | |
| * @sink: trusty_log_sink_state holding the get index on a given sink | |
| * @index: Unwrapped ring buffer index from where iteration shall start | |
| * | |
| * Return: 0 if successful, negative error code otherwise | |
| */ | |
| static int trusty_log_start(struct trusty_log_state *s, | |
| struct trusty_log_sink_state *sink, | |
| u32 index) | |
| { | |
| struct log_rb *log; | |
| if (WARN_ON(!s)) | |
| return -EINVAL; | |
| log = s->log; | |
| if (WARN_ON(!is_power_of_2(log->sz))) | |
| return -EINVAL; | |
| sink->get = index; | |
| return 0; | |
| } | |
| /** | |
| * trusty_log_show() - sink log entry at current iteration | |
| * @s: Current log state. | |
| * @sink: trusty_log_sink_state holding the get index on a given sink | |
| */ | |
| static void trusty_log_show(struct trusty_log_state *s, | |
| struct trusty_log_sink_state *sink) | |
| { | |
| struct log_rb *log = s->log; | |
| u32 alloc, put, get; | |
| int read_chars; | |
| if (sink->sfile && sink == &s->klog_sink) | |
| dev_warn(s->dev, "klog_sink has seq_file\n"); | |
| /* | |
| * For this ring buffer, at any given point, alloc >= put >= get. | |
| * The producer side of the buffer is not locked, so the put and alloc | |
| * pointers must be read in a defined order (put before alloc) so | |
| * that the above condition is maintained. A read barrier is needed | |
| * to make sure the hardware and compiler keep the reads ordered. | |
| */ | |
| get = sink->get; | |
| put = log->put; | |
| /* Make sure that the read of put occurs before the read of log data */ | |
| rmb(); | |
| /* Read a line from the log */ | |
| read_chars = log_read_line(s, put, get); | |
| /* Force the loads from log_read_line to complete. */ | |
| rmb(); | |
| alloc = log->alloc; | |
| /* | |
| * Discard the line that was just read if the data could | |
| * have been corrupted by the producer. | |
| */ | |
| if (u32_sub_overflow(alloc, get) > log->sz) { | |
| /* | |
| * this condition is acceptable in the case of the sfile sink | |
| * when attempting to read the oldest entry (at alloc-log->sz) | |
| * which may be overrun by a new one when ring buffer write | |
| * index wraps around. | |
| * So the overrun is not reported in case the oldest line | |
| * was being read. | |
| */ | |
| if (sink->sfile) { | |
| if (!sink->ignore_overflow) | |
| seq_puts(sink->sfile, "log overflow.\n"); | |
| /* coalesce subsequent contiguous overflows. */ | |
| sink->ignore_overflow = true; | |
| } else { | |
| dev_err(s->dev, "log overflow.\n"); | |
| } | |
| sink->get = u32_sub_overflow(alloc, log->sz); | |
| return; | |
| } | |
| /* compute next line index */ | |
| sink->get = u32_add_overflow(get, read_chars); | |
| /* once a line is valid, ignore_overflow must be disabled */ | |
| sink->ignore_overflow = false; | |
| if (sink->sfile) { | |
| seq_printf(sink->sfile, "%s", s->line_buffer); | |
| } else { | |
| if (sink->trusty_panicked || | |
| __ratelimit(&trusty_log_rate_limit)) { | |
| dev_info(s->dev, "%s", s->line_buffer); | |
| /* next line after last successful get */ | |
| sink->last_successful_next = sink->get; | |
| } | |
| } | |
| } | |
| static void *trusty_log_seq_start(struct seq_file *sfile, loff_t *pos) | |
| { | |
| struct trusty_log_sfile *lb; | |
| struct trusty_log_state *s; | |
| struct log_rb *log; | |
| struct trusty_log_sink_state *log_sfile_sink; | |
| u32 index; | |
| int rc; | |
| if (WARN_ON(!pos)) | |
| return ERR_PTR(-EINVAL); | |
| lb = sfile->private; | |
| if (WARN_ON(!lb)) | |
| return ERR_PTR(-EINVAL); | |
| log_sfile_sink = kzalloc(sizeof(*log_sfile_sink), GFP_KERNEL); | |
| if (!log_sfile_sink) | |
| return ERR_PTR(-ENOMEM); | |
| s = container_of(lb, struct trusty_log_state, log_sfile); | |
| log_sfile_sink->sfile = sfile; | |
| log = s->log; | |
| if (*pos == 0) { | |
| /* start at the oldest line */ | |
| index = 0; | |
| if (log->alloc > log->sz) | |
| index = u32_sub_overflow(log->alloc, log->sz); | |
| } else { | |
| /* | |
| * '*pos>0': pos hold the 32bits unwrapped index from where | |
| * to start iterating | |
| */ | |
| index = (u32)*pos; | |
| } | |
| pr_debug("%s start=%u\n", __func__, index); | |
| log_sfile_sink->ignore_overflow = true; | |
| rc = trusty_log_start(s, log_sfile_sink, index); | |
| if (rc < 0) | |
| goto free_sink; | |
| if (!trusty_log_has_data(s, log_sfile_sink)) | |
| goto free_sink; | |
| return log_sfile_sink; | |
| free_sink: | |
| pr_debug("%s kfree\n", __func__); | |
| kfree(log_sfile_sink); | |
| return rc < 0 ? ERR_PTR(rc) : NULL; | |
| } | |
| static void *trusty_log_seq_next(struct seq_file *sfile, void *v, loff_t *pos) | |
| { | |
| struct trusty_log_sfile *lb; | |
| struct trusty_log_state *s; | |
| struct trusty_log_sink_state *log_sfile_sink = v; | |
| int rc = 0; | |
| if (WARN_ON(!log_sfile_sink)) | |
| return ERR_PTR(-EINVAL); | |
| lb = sfile->private; | |
| if (WARN_ON(!lb)) { | |
| rc = -EINVAL; | |
| goto end_of_iter; | |
| } | |
| s = container_of(lb, struct trusty_log_state, log_sfile); | |
| if (WARN_ON(!pos)) { | |
| rc = -EINVAL; | |
| goto end_of_iter; | |
| } | |
| /* | |
| * When starting a virtual file sink, the start function is invoked | |
| * with a pos argument which value is set to zero. | |
| * Subsequent starts are invoked with pos being set to | |
| * the unwrapped read index (get). | |
| * Upon u32 wraparound, the get index could be reset to zero. | |
| * Thus a msb is used to distinguish the `get` zero value | |
| * from the `start of file` zero value. | |
| */ | |
| *pos = (1UL << 32) + log_sfile_sink->get; | |
| if (!trusty_log_has_data(s, log_sfile_sink)) | |
| goto end_of_iter; | |
| return log_sfile_sink; | |
| end_of_iter: | |
| pr_debug("%s kfree\n", __func__); | |
| kfree(log_sfile_sink); | |
| return rc < 0 ? ERR_PTR(rc) : NULL; | |
| } | |
| static void trusty_log_seq_stop(struct seq_file *sfile, void *v) | |
| { | |
| /* | |
| * When iteration completes or on error, the next callback frees | |
| * the sink structure and returns NULL/error-code. | |
| * In that case stop (being invoked with void* v set to the last next | |
| * return value) would be invoked with v == NULL or error code. | |
| * When user space stops the iteration earlier than the end | |
| * (in case of user-space memory allocation limit for example) | |
| * then the stop function receives a non NULL get pointer | |
| * and is in charge or freeing the sink structure. | |
| */ | |
| struct trusty_log_sink_state *log_sfile_sink = v; | |
| /* nothing to do - sink structure already freed */ | |
| if (IS_ERR_OR_NULL(log_sfile_sink)) | |
| return; | |
| kfree(log_sfile_sink); | |
| pr_debug("%s kfree\n", __func__); | |
| } | |
| static int trusty_log_seq_show(struct seq_file *sfile, void *v) | |
| { | |
| struct trusty_log_sfile *lb; | |
| struct trusty_log_state *s; | |
| struct trusty_log_sink_state *log_sfile_sink = v; | |
| if (WARN_ON(!log_sfile_sink)) | |
| return -EINVAL; | |
| lb = sfile->private; | |
| if (WARN_ON(!lb)) | |
| return -EINVAL; | |
| s = container_of(lb, struct trusty_log_state, log_sfile); | |
| trusty_log_show(s, log_sfile_sink); | |
| return 0; | |
| } | |
| static void trusty_dump_logs(struct trusty_log_state *s) | |
| { | |
| u32 start; | |
| int rc; | |
| /* | |
| * note: klopg_sink.get and last_successful_next | |
| * initialized to zero by kzalloc | |
| */ | |
| s->klog_sink.trusty_panicked = trusty_get_panic_status(s->trusty_dev); | |
| start = s->klog_sink.trusty_panicked ? | |
| s->klog_sink.last_successful_next : | |
| s->klog_sink.get; | |
| rc = trusty_log_start(s, &s->klog_sink, start); | |
| if (rc < 0) | |
| return; | |
| while (trusty_log_has_data(s, &s->klog_sink)) | |
| trusty_log_show(s, &s->klog_sink); | |
| } | |
| static int trusty_log_call_notify(struct notifier_block *nb, | |
| unsigned long action, void *data) | |
| { | |
| struct trusty_log_state *s; | |
| unsigned long flags; | |
| u32 cur_put; | |
| if (action != TRUSTY_CALL_RETURNED) | |
| return NOTIFY_DONE; | |
| s = container_of(nb, struct trusty_log_state, call_notifier); | |
| spin_lock_irqsave(&s->wake_up_lock, flags); | |
| cur_put = s->log->put; | |
| if (cur_put != s->last_wake_put) { | |
| s->last_wake_put = cur_put; | |
| wake_up_all(&s->poll_waiters); | |
| } | |
| spin_unlock_irqrestore(&s->wake_up_lock, flags); | |
| if (log_to_dmesg_param == ALWAYS || | |
| (log_to_dmesg_param == UNTIL_FIRST_READER && | |
| !s->have_first_reader)) { | |
| spin_lock_irqsave(&s->lock, flags); | |
| trusty_dump_logs(s); | |
| spin_unlock_irqrestore(&s->lock, flags); | |
| } | |
| return NOTIFY_OK; | |
| } | |
| static int trusty_log_panic_notify(struct notifier_block *nb, | |
| unsigned long action, void *data) | |
| { | |
| struct trusty_log_state *s; | |
| /* | |
| * Don't grab the spin lock to hold up the panic notifier, even | |
| * though this is racy. | |
| */ | |
| s = container_of(nb, struct trusty_log_state, panic_notifier); | |
| dev_info(s->dev, "panic notifier - trusty version %s", | |
| trusty_version_str_get(s->trusty_dev)); | |
| trusty_dump_logs(s); | |
| return NOTIFY_OK; | |
| } | |
| const struct seq_operations trusty_log_seq_ops = { | |
| .start = trusty_log_seq_start, | |
| .stop = trusty_log_seq_stop, | |
| .next = trusty_log_seq_next, | |
| .show = trusty_log_seq_show, | |
| }; | |
| static int trusty_log_sfile_dev_open(struct inode *inode, struct file *file) | |
| { | |
| struct trusty_log_sfile *ls; | |
| struct trusty_log_state *s; | |
| struct seq_file *sfile; | |
| int rc; | |
| /* | |
| * file->private_data contains a pointer to the misc_device struct | |
| * passed to misc_register() | |
| */ | |
| if (WARN_ON(!file->private_data)) | |
| return -EINVAL; | |
| ls = container_of(file->private_data, struct trusty_log_sfile, misc); | |
| /* | |
| * seq_open uses file->private_data to store the seq_file associated | |
| * with the struct file, but it must be NULL when seq_open is called | |
| */ | |
| file->private_data = NULL; | |
| rc = seq_open(file, &trusty_log_seq_ops); | |
| if (rc < 0) | |
| return rc; | |
| sfile = file->private_data; | |
| if (WARN_ON(!sfile)) | |
| return -EINVAL; | |
| sfile->private = ls; | |
| s = container_of(ls, struct trusty_log_state, log_sfile); | |
| s->have_first_reader = true; | |
| kref_get(&s->refcount); | |
| return 0; | |
| } | |
| static unsigned int trusty_log_sfile_dev_poll(struct file *filp, | |
| struct poll_table_struct *wait) | |
| { | |
| struct seq_file *sfile; | |
| struct trusty_log_sfile *lb; | |
| struct trusty_log_state *s; | |
| struct log_rb *log; | |
| /* | |
| * trusty_log_sfile_dev_open() pointed filp->private_data to a | |
| * seq_file, and that seq_file->private to the trusty_log_sfile | |
| * field of a trusty_log_state | |
| */ | |
| sfile = filp->private_data; | |
| lb = sfile->private; | |
| s = container_of(lb, struct trusty_log_state, log_sfile); | |
| if (!s->registered) { | |
| dev_err(s->dev, "invalid poll fd\n"); | |
| return -EINVAL; | |
| } | |
| poll_wait(filp, &s->poll_waiters, wait); | |
| log = s->log; | |
| /* | |
| * Userspace has read up to sfile->index so far. Update klog_sink | |
| * to indicate that, so that we don't end up dumping the entire | |
| * Trusty log in case of panic. Only do this when not logging to | |
| * klog_sink, since logging to klog_sink already updates this. | |
| */ | |
| if (log_to_dmesg_param != ALWAYS) | |
| s->klog_sink.last_successful_next = (u32)sfile->index; | |
| if (log->put != (u32)sfile->index) { | |
| /* data ready to read */ | |
| return EPOLLIN | EPOLLRDNORM; | |
| } | |
| /* no data available, go to sleep */ | |
| return 0; | |
| } | |
| static void trusty_log_cleanup(struct kref *ref); | |
| static int trusty_log_sfile_dev_release(struct inode *inode, | |
| struct file *filp) | |
| { | |
| struct seq_file *sfile; | |
| struct trusty_log_sfile *lb; | |
| struct trusty_log_state *s; | |
| sfile = filp->private_data; | |
| lb = sfile->private; | |
| s = container_of(lb, struct trusty_log_state, log_sfile); | |
| kref_put(&s->refcount, trusty_log_cleanup); | |
| seq_release(inode, filp); | |
| return 0; | |
| } | |
| ssize_t trusty_log_sfile_dev_read(struct file *filp, char __user *buf, | |
| size_t size, loff_t *ppos) | |
| { | |
| struct seq_file *sfile; | |
| struct trusty_log_sfile *lb; | |
| struct trusty_log_state *s; | |
| sfile = filp->private_data; | |
| lb = sfile->private; | |
| s = container_of(lb, struct trusty_log_state, log_sfile); | |
| if (!s->registered) { | |
| dev_err(s->dev, "invalid read fd\n"); | |
| return -EINVAL; | |
| } | |
| return seq_read(filp, buf, size, ppos); | |
| } | |
| static const struct file_operations log_sfile_dev_operations = { | |
| .owner = THIS_MODULE, | |
| .open = trusty_log_sfile_dev_open, | |
| .poll = trusty_log_sfile_dev_poll, | |
| .read = trusty_log_sfile_dev_read, | |
| .release = trusty_log_sfile_dev_release, | |
| }; | |
| static int trusty_log_sfile_register(struct trusty_log_state *s) | |
| { | |
| int ret; | |
| struct trusty_log_sfile *ls = &s->log_sfile; | |
| if (WARN_ON(!ls)) | |
| return -EINVAL; | |
| snprintf(ls->device_name, sizeof(ls->device_name), | |
| "trusty-log%d", s->dev->id); | |
| ls->misc.minor = MISC_DYNAMIC_MINOR; | |
| ls->misc.name = ls->device_name; | |
| ls->misc.fops = &log_sfile_dev_operations; | |
| ret = misc_register(&ls->misc); | |
| if (ret) { | |
| dev_err(s->dev, | |
| "log_sfile error while doing misc_register ret=%d\n", | |
| ret); | |
| return ret; | |
| } | |
| s->registered = true; | |
| dev_info(s->dev, "/dev/%s registered\n", | |
| ls->device_name); | |
| return 0; | |
| } | |
| static void trusty_log_sfile_unregister(struct trusty_log_state *s) | |
| { | |
| struct trusty_log_sfile *ls = &s->log_sfile; | |
| misc_deregister(&ls->misc); | |
| if (s->dev) { | |
| dev_info(s->dev, "/dev/%s unregistered\n", | |
| ls->misc.name); | |
| s->registered = false; | |
| } | |
| } | |
| static bool trusty_supports_logging(struct device *device) | |
| { | |
| int result; | |
| result = trusty_std_call32(device, SMC_SC_SHARED_LOG_VERSION, | |
| TRUSTY_LOG_API_VERSION, 0, 0); | |
| if (result == SM_ERR_UNDEFINED_SMC) { | |
| dev_info(device, "trusty-log not supported on secure side.\n"); | |
| return false; | |
| } else if (result < 0) { | |
| dev_err(device, | |
| "trusty std call (SMC_SC_SHARED_LOG_VERSION) failed: %d\n", | |
| result); | |
| return false; | |
| } | |
| if (result != TRUSTY_LOG_API_VERSION) { | |
| dev_info(device, "unsupported api version: %d, supported: %d\n", | |
| result, TRUSTY_LOG_API_VERSION); | |
| return false; | |
| } | |
| return true; | |
| } | |
| static int trusty_log_init(struct platform_device *pdev) | |
| { | |
| struct trusty_log_state *s; | |
| struct scatterlist *sg; | |
| unsigned char *mem; | |
| int i; | |
| int result; | |
| trusty_shared_mem_id_t mem_id; | |
| int log_size; | |
| s = kzalloc(sizeof(*s), GFP_KERNEL); | |
| if (!s) { | |
| result = -ENOMEM; | |
| goto error_alloc_state; | |
| } | |
| spin_lock_init(&s->lock); | |
| s->dev = &pdev->dev; | |
| s->trusty_dev = s->dev->parent; | |
| s->log_num_pages = DIV_ROUND_UP(log_size_param + sizeof(struct log_rb), | |
| PAGE_SIZE); | |
| s->sg = kcalloc(s->log_num_pages, sizeof(*s->sg), GFP_KERNEL); | |
| if (!s->sg) { | |
| result = -ENOMEM; | |
| goto error_alloc_sg; | |
| } | |
| log_size = s->log_num_pages * PAGE_SIZE; | |
| mem = vzalloc(log_size); | |
| if (!mem) { | |
| result = -ENOMEM; | |
| goto error_alloc_log; | |
| } | |
| s->log = (struct log_rb *)mem; | |
| sg_init_table(s->sg, s->log_num_pages); | |
| for_each_sg(s->sg, sg, s->log_num_pages, i) { | |
| struct page *pg = vmalloc_to_page(mem + (i * PAGE_SIZE)); | |
| if (!pg) { | |
| result = -ENOMEM; | |
| goto err_share_memory; | |
| } | |
| sg_set_page(sg, pg, PAGE_SIZE, 0); | |
| } | |
| /* | |
| * This will fail for Trusty api version < TRUSTY_API_VERSION_MEM_OBJ | |
| * if s->log_num_pages > 1 | |
| * Use trusty_share_memory_compat instead of trusty_share_memory in case | |
| * s->log_num_pages == 1 and api version < TRUSTY_API_VERSION_MEM_OBJ, | |
| * In that case SMC_SC_SHARED_LOG_ADD expects a different value than | |
| * what trusty_share_memory returns | |
| */ | |
| result = trusty_share_memory_compat(s->trusty_dev, &mem_id, s->sg, | |
| s->log_num_pages, PAGE_KERNEL); | |
| if (result) { | |
| dev_err(s->dev, "trusty_share_memory failed: %d\n", result); | |
| goto err_share_memory; | |
| } | |
| s->log_pages_shared_mem_id = mem_id; | |
| result = trusty_std_call32(s->trusty_dev, | |
| SMC_SC_SHARED_LOG_ADD, | |
| (u32)(mem_id), (u32)(mem_id >> 32), | |
| log_size); | |
| if (result < 0) { | |
| dev_err(s->dev, | |
| "trusty std call (SMC_SC_SHARED_LOG_ADD) failed: %d 0x%llx\n", | |
| result, mem_id); | |
| goto error_std_call; | |
| } | |
| init_waitqueue_head(&s->poll_waiters); | |
| spin_lock_init(&s->wake_up_lock); | |
| s->call_notifier.notifier_call = trusty_log_call_notify; | |
| result = trusty_call_notifier_register(s->trusty_dev, | |
| &s->call_notifier); | |
| if (result < 0) { | |
| dev_err(&pdev->dev, | |
| "failed to register trusty call notifier\n"); | |
| goto error_call_notifier; | |
| } | |
| s->panic_notifier.notifier_call = trusty_log_panic_notify; | |
| result = atomic_notifier_chain_register(&panic_notifier_list, | |
| &s->panic_notifier); | |
| if (result < 0) { | |
| dev_err(&pdev->dev, | |
| "failed to register panic notifier\n"); | |
| goto error_panic_notifier; | |
| } | |
| result = trusty_log_sfile_register(s); | |
| if (result < 0) { | |
| dev_err(&pdev->dev, "failed to register log_sfile\n"); | |
| goto error_log_sfile; | |
| } | |
| kref_init(&s->refcount); | |
| platform_set_drvdata(pdev, s); | |
| return 0; | |
| error_log_sfile: | |
| atomic_notifier_chain_unregister(&panic_notifier_list, | |
| &s->panic_notifier); | |
| error_panic_notifier: | |
| trusty_call_notifier_unregister(s->trusty_dev, &s->call_notifier); | |
| error_call_notifier: | |
| trusty_std_call32(s->trusty_dev, SMC_SC_SHARED_LOG_RM, | |
| (u32)mem_id, (u32)(mem_id >> 32), 0); | |
| error_std_call: | |
| if (WARN_ON(trusty_reclaim_memory(s->trusty_dev, mem_id, s->sg, | |
| s->log_num_pages))) { | |
| dev_err(&pdev->dev, "trusty_revoke_memory failed: %d 0x%llx\n", | |
| result, mem_id); | |
| /* | |
| * It is not safe to free this memory if trusty_revoke_memory | |
| * fails. Leak it in that case. | |
| */ | |
| } else { | |
| err_share_memory: | |
| vfree(s->log); | |
| } | |
| error_alloc_log: | |
| kfree(s->sg); | |
| error_alloc_sg: | |
| kfree(s); | |
| error_alloc_state: | |
| return result; | |
| } | |
| static int trusty_log_probe(struct platform_device *pdev) | |
| { | |
| int rc; | |
| if (!trusty_supports_logging(pdev->dev.parent)) | |
| return -ENXIO; | |
| rc = trusty_log_init(pdev); | |
| if (rc && log_size_param > TRUSTY_LOG_MIN_SIZE) { | |
| dev_warn(&pdev->dev, "init failed, retrying with 1-page log\n"); | |
| log_size_param = TRUSTY_LOG_MIN_SIZE; | |
| rc = trusty_log_init(pdev); | |
| } | |
| return rc; | |
| } | |
| static | |
| #if (KERNEL_VERSION(6, 12, 0) > LINUX_VERSION_CODE) | |
| int | |
| #else | |
| void | |
| #endif | |
| trusty_log_remove(struct platform_device *pdev) | |
| { | |
| struct trusty_log_state *s = platform_get_drvdata(pdev); | |
| trusty_log_sfile_unregister(s); | |
| kref_put(&s->refcount, trusty_log_cleanup); | |
| #if (KERNEL_VERSION(6, 12, 0) > LINUX_VERSION_CODE) | |
| return 0; | |
| #endif | |
| } | |
| static void trusty_log_cleanup(struct kref *ref) | |
| { | |
| int result; | |
| struct trusty_log_state *s; | |
| trusty_shared_mem_id_t mem_id; | |
| s = container_of(ref, struct trusty_log_state, refcount); | |
| dev_info(s->dev, "log_cleanup\n"); | |
| mem_id = s->log_pages_shared_mem_id; | |
| atomic_notifier_chain_unregister(&panic_notifier_list, | |
| &s->panic_notifier); | |
| trusty_call_notifier_unregister(s->trusty_dev, &s->call_notifier); | |
| result = trusty_std_call32(s->trusty_dev, SMC_SC_SHARED_LOG_RM, | |
| (u32)mem_id, (u32)(mem_id >> 32), 0); | |
| if (result) { | |
| dev_err(s->dev, | |
| "trusty std call (SMC_SC_SHARED_LOG_RM) failed: %d\n", | |
| result); | |
| } | |
| result = trusty_reclaim_memory(s->trusty_dev, mem_id, s->sg, | |
| s->log_num_pages); | |
| if (WARN_ON(result)) { | |
| dev_err(s->dev, | |
| "trusty failed to remove shared memory: %d\n", result); | |
| } else { | |
| /* | |
| * It is not safe to free this memory if trusty_revoke_memory | |
| * fails. Leak it in that case. | |
| */ | |
| vfree(s->log); | |
| } | |
| kfree(s->sg); | |
| kfree(s); | |
| } | |
| static const struct of_device_id trusty_test_of_match[] = { | |
| { .compatible = "android,trusty-log-v1", }, | |
| {}, | |
| }; | |
| MODULE_DEVICE_TABLE(trusty, trusty_test_of_match); | |
| static struct platform_driver trusty_log_driver = { | |
| .probe = trusty_log_probe, | |
| .remove = trusty_log_remove, | |
| .driver = { | |
| .name = "trusty-log", | |
| .of_match_table = trusty_test_of_match, | |
| }, | |
| }; | |
| module_platform_driver(trusty_log_driver); | |
| MODULE_LICENSE("GPL v2"); | |
| MODULE_DESCRIPTION("Trusty logging driver"); |