From: "Ilpo Järvinen" <ilpo.jarvinen@linux.intel.com>
To: Dan Raymond <draymond@foxvalley.net>
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>,
linux-serial <linux-serial@vger.kernel.org>
Subject: Re: [PATCH] tty/serial: create debugfs interface for UART register tracing
Date: Wed, 23 Aug 2023 11:30:13 +0300 (EEST) [thread overview]
Message-ID: <9c63a3a-2720-4e2b-5155-eb6e36aef257@linux.intel.com> (raw)
In-Reply-To: <68ad2521-f902-b0d3-16d6-4d2a36ac656e@foxvalley.net>
[-- Attachment #1: Type: text/plain, Size: 22057 bytes --]
On Tue, 22 Aug 2023, Dan Raymond wrote:
> Implement a UART register tracing facility using the debugfs. This can be
> used as a "serial port sniffer" to monitor UART traffic and line settings
> with timestamps at microsecond granularity. This can be useful for general
> serial port debugging or to debug the UART driver itself.
Thanks, looks useful (although it might have challenge in tracing hw
during early init).
> Signed-off-by: Dan Raymond<draymond@foxvalley.net>
> ---
> diff -uprN -X a/Documentation/dontdiff a/drivers/tty/serial/8250/8250.h
> b/drivers/tty/serial/8250/8250.h
> --- a/drivers/tty/serial/8250/8250.h 2022-11-16 02:04:15.000000000 -0700
> +++ b/drivers/tty/serial/8250/8250.h 2023-08-21 16:15:51.429933400 -0600
> @@ -403,3 +403,6 @@ static inline int serial_index(struct ua
> {
> return port->minor - 64;
> }
> +
> +extern void uart_debug_create(struct uart_port *port);
> +extern void uart_debug_destroy(struct uart_port *port);
> diff -uprN -X a/Documentation/dontdiff a/drivers/tty/serial/8250/8250_core.c
> b/drivers/tty/serial/8250/8250_core.c
> --- a/drivers/tty/serial/8250/8250_core.c 2022-11-16 02:04:15.000000000
> -0700
> +++ b/drivers/tty/serial/8250/8250_core.c 2023-08-21 16:16:47.502740700
> -0600
> @@ -1094,6 +1094,7 @@ int serial8250_register_8250_port(const
> if (ret)
> goto err;
> + uart_debug_create(&uart->port);
> ret = uart->port.line;
> } else {
> dev_info(uart->port.dev,
> @@ -1151,6 +1152,7 @@ void serial8250_unregister_port(int line
> spin_unlock_irqrestore(&uart->port.lock, flags);
> }
> + uart_debug_destroy(&uart->port);
> uart_remove_one_port(&serial8250_reg, &uart->port);
> if (serial8250_isa_devs) {
> uart->port.flags &= ~UPF_BOOT_AUTOCONF;
> diff -uprN -X a/Documentation/dontdiff a/drivers/tty/serial/8250/8250_debug.c
> b/drivers/tty/serial/8250/8250_debug.c
> --- a/drivers/tty/serial/8250/8250_debug.c 1969-12-31 17:00:00.000000000
> -0700
> +++ b/drivers/tty/serial/8250/8250_debug.c 2023-08-22 13:19:12.234454600
> -0600
> @@ -0,0 +1,519 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * This module creates a debugfs interface that can be used to hook the low
> level serial
> + * I/O routines and generate timestamped traces of UART register reads and
> writes. This
> + * feature provides an accurate and reliable "serial port sniffer" that can
> be used for
> + * debugging problems with the serial port.
> + *
> + * Each serial port ("ttyS0", "ttyS1", etc.) will have its own directory in
> the root of
> + * the debug filesystem. Inside each directory will be the following
> pseudo-files:
> + *
> + * trace_all - toggles tracing of all registers vs. just the RX/TX
> registers
> + * trace_buffer - for consuming the trace data
> + * trace_enable - enables/disables tracing
> + * trace_size - configures the size of the trace buffer
> + *
> + * When tracing is disabled there will be zero impact to the operation of
> the UART
> + * driver. When tracing is enabled a small cost will be incurred after
> every register
> + * access to write an entry to the trace buffer in RAM. The buffer size is
> limited
> + * only by the amount of virtual memory available to the kernel. This is
> 128 MiB by
> + * default but it can be increased using vmalloc=xxx on the kernel command
> line. The
> + * memory will be freed after tracing is disabled and all data in the buffer
> has been
> + * consumed.
> + *
> + * Example session:
> + *
> + * > mount -t debugfs debugfs /sys/kernel/debug
> + * > echo '1' > /sys/kernel/debug/ttyS1/trace_all
> + * > echo '1' > /sys/kernel/debug/ttyS1/trace_enable
> + *
> + * (perform UART operation)
> + *
> + * > cat /sys/kernel/debug/ttyS1/trace_buffer
> + *
> + * 00:03:04.822166 IER <- 07
> + * 00:03:04.822175 IIR -> C2
> + * 00:03:04.822177 LSR -> 60
> + * 00:03:04.822178 MSR -> 00
> + * 00:03:04.822180 THR <- C9
> + * 00:03:04.822184 IER <- 05
> + * 00:03:04.822185 IIR -> C1
> + * 00:03:05.062943 IIR -> CC
> + * 00:03:05.062945 LSR -> 61
> + * 00:03:05.062948 RBR -> C9
> + * 00:03:05.062950 LSR -> 61
> + * 00:03:05.062952 RBR -> ED
> + * 00:03:05.062953 LSR -> 61
> + * 00:03:05.062954 RBR -> 00
> + * 00:03:05.062955 LSR -> 60
> + * 00:03:05.062965 MSR -> 00
> + * 00:03:05.062967 IIR -> C1
> + */
> +
> +#include <linux/cpufreq.h>
> +#include <linux/debugfs.h>
> +#include <linux/fs.h>
> +#include <linux/math64.h>
> +#include <linux/serial_8250.h>
> +
> +struct reg_event {
> + uint32_t cycle_lo; /* CPU cycle count (lower 32-bits) */
> + uint16_t cycle_hi; /* CPU cycle count (upper 16-bits) */
> + uint8_t access; /* write flag + register number */
> + uint8_t data; /* register data */
Some HW-specific registers are larger than 8 bits.
> +};
> +
> +struct reg_queue {
> + uint32_t read_idx; /* first full (written) slot */
> + uint32_t write_idx; /* first empty (unwritten) slot */
> + struct reg_event *buf; /* array to hold the data */
> + uint32_t size; /* array size (number of entries - must
> be power of 2) */
> + bool wrap; /* true if write_idx wrapped around and
> reached read_idx */
> +};
> +
> +struct uart_debug {
> + spinlock_t lock;
> + struct mutex mutex;
> + bool trace_enable;
> + bool trace_all;
> + uint32_t trace_size;
> + unsigned int (*original_serial_in)(struct uart_port *p, int
> offset);
> + void (*original_serial_out)(struct uart_port *p, int
> offset, int value);
> + struct reg_queue register_queue;
> + char line[64]; /* buffer to hold text of last item
> removed from queue */
> + uint32_t offset; /* offset of first unread byte in line */
> +};
> +
> +static ssize_t all_read(struct file *filp, char __user *buf, size_t
> size, loff_t *ppos);
> +static ssize_t all_write(struct file *filp,
> + const char __user *buf, size_t size, loff_t *ppos);
> +static ssize_t buffer_read(struct file *filp, char __user *buf, size_t
> size, loff_t *ppos);
> +static ssize_t enable_read(struct file *filp, char __user *buf, size_t
> size, loff_t *ppos);
> +static ssize_t enable_write(struct file *filp,
> + const char __user *buf, size_t size, loff_t *ppos);
> +static void format_cycle(uint64_t cpu_cycles, char *buf, uint32_t
> size);
> +static void queue_add(struct uart_port *port, uint8_t reg, uint8_t
> data, bool write);
> +static bool queue_alloc(struct uart_port *port);
> +static void queue_free(struct uart_port *port, bool force);
> +static void queue_remove(struct uart_port *port, char *buf, uint32_t
> size);
> +static unsigned int serial_in_wrapper(struct uart_port *port, int offset);
> +static void serial_out_wrapper(struct uart_port *port, int offset,
> int value);
> +static ssize_t size_read(struct file *filp, char __user *buf, size_t
> size, loff_t *ppos);
> +static ssize_t size_write(struct file *filp,
> + const char __user *buf, size_t size, loff_t *ppos);
> +
> +static const struct file_operations all_fops = {
> + .open = simple_open,
> + .read = all_read,
> + .write = all_write,
> +};
> +
> +static const struct file_operations buffer_fops = {
> + .open = simple_open,
> + .read = buffer_read,
> +};
> +
> +static const struct file_operations enable_fops = {
> + .open = simple_open,
> + .read = enable_read,
> + .write = enable_write,
> +};
> +
> +static const struct file_operations size_fops = {
> + .open = simple_open,
> + .read = size_read,
> + .write = size_write,
> +};
> +
> +/*
> + * Handle reads from the 'trace_all' pseudo-file. Report the state of the
> trace_all flag.
> + */
> +static ssize_t all_read(struct file *filp, char __user *buf, size_t size,
> loff_t *ppos)
> +{
> + struct uart_port *port = filp->private_data;
> + struct uart_debug *uart_debug = port->private_data;
> + char text[16];
> + uint32_t len;
> +
> + len = snprintf(text, sizeof(text), "%u\n", uart_debug->trace_all);
> + return simple_read_from_buffer(buf, size, ppos, text, len);
> +}
> +
> +/*
> + * Handle writes to the 'trace_all' pseudo-file. Set the state of the
> trace_all flag.
> + */
> +static ssize_t all_write(struct file *filp, const char __user *buf, size_t
> size, loff_t *ppos)
> +{
> + struct uart_port *port = filp->private_data;
> + struct uart_debug *uart_debug = port->private_data;
> + int error;
> +
> + error = kstrtobool_from_user(buf, size, &uart_debug->trace_all);
> + if (error)
> + return error;
> +
> + return size;
> +}
> +
> +/*
> + * Handle reads from the 'trace_buffer' pseudo-file. This function consumes
> content in
> + * the register event queue. A best effort will be made to avoid splitting
> lines of text.
> + */
> +static ssize_t buffer_read(struct file *filp, char __user *buf, size_t size,
> loff_t *ppos)
> +{
> + struct uart_port *port = filp->private_data;
> + struct uart_debug *uart_debug = port->private_data;
> + uint32_t len, num_bytes, total_bytes = 0;
> + const char *ptr;
> + int error = 0;
> +
> + if (mutex_lock_interruptible(&uart_debug->mutex))
> + return -ERESTARTSYS;
> +
> + while (size) {
> + if (uart_debug->line[uart_debug->offset] == '\0') {
> + uart_debug->offset = 0;
> + queue_remove(port, uart_debug->line,
> sizeof(uart_debug->line));
> + if (uart_debug->line[0] == '\0')
> + break;
> + }
> +
> + ptr = uart_debug->line + uart_debug->offset;
> + len = strlen(ptr);
Why you need to calculate length? Shouldn't queue_remove() be able to return
this information?
> + num_bytes = (size < len) ? size : len;
min_t().
> +
> + if (num_bytes < len && total_bytes)
> + break;
> + if (copy_to_user(buf, ptr, num_bytes)) {
> + error = -EFAULT;
> + break;
> + }
> +
> + buf += num_bytes;
> + size -= num_bytes;
> + total_bytes += num_bytes;
> + uart_debug->offset += num_bytes;
> + }
> +
> + if (!uart_debug->trace_enable)
> + queue_free(port, false);
> +
> + mutex_unlock(&uart_debug->mutex);
> + if (error)
> + return total_bytes ? total_bytes : error;
> + return total_bytes;
So this is same as:
if (!total_bytes)
return error;
return total_bytes;
> +}
> +
> +/*
> + * Handle reads from the 'trace_enable' pseudo-file. Report the current
> state of the
> + * trace_enable flag.
> + */
> +static ssize_t enable_read(struct file *filp, char __user *buf, size_t size,
> loff_t *ppos)
> +{
> + struct uart_port *port = filp->private_data;
> + struct uart_debug *uart_debug = port->private_data;
> + char text[16];
> + uint32_t len;
> +
> + len = snprintf(text, sizeof(text), "%u\n", uart_debug->trace_enable);
> + return simple_read_from_buffer(buf, size, ppos, text, len);
> +}
> +
> +/*
> + * Handle writes to the 'trace_enable' pseudo-file. When tracing is enabled
> allocate
> + * memory for the register event queue and install our hooks. When tracing
> is disabled
> + * remove our hooks and free the memory used by the register event queue if
> it is empty.
> + */
> +static ssize_t enable_write(struct file *filp, const char __user *buf, size_t
> size, loff_t *ppos)
> +{
> + struct uart_port *port = filp->private_data;
> + struct uart_debug *uart_debug = port->private_data;
> + bool trace_enable;
> + int error;
> +
> + error = kstrtobool_from_user(buf, size, &trace_enable);
> + if (error)
> + return error;
> +
> + if (mutex_lock_interruptible(&uart_debug->mutex))
> + return -ERESTARTSYS;
> + if (trace_enable == uart_debug->trace_enable)
> + goto exit;
> +
> + if (trace_enable) {
> + uart_debug->line[0] = '\0';
> + uart_debug->offset = 0;
> + queue_free(port, true);
> + if (!queue_alloc(port))
> + goto exit;
> +
> + uart_debug->original_serial_out = port->serial_out;
> + uart_debug->original_serial_in = port->serial_in;
> + port->serial_out = serial_out_wrapper;
> + port->serial_in = serial_in_wrapper;
> + } else {
> + port->serial_out = uart_debug->original_serial_out;
> + port->serial_in = uart_debug->original_serial_in;
> +
> + queue_free(port, false);
> + }
> +
> + uart_debug->trace_enable = trace_enable;
> +exit:
> + mutex_unlock(&uart_debug->mutex);
> + return size;
> +}
> +
> +/*
> + * Convert 'cpu_cycles' to a string that reports the time elapsed since boot
> in
> + * hours/mins/seconds/microseconds.
> + */
> +static void format_cycle(uint64_t cpu_cycles, char *buf, uint32_t size)
For in-kernel interfaces, use u64 and u32, uintxx_t is for userspace
interactions.
> +{
> + static uint64_t cpu_freq; /* cycles per second */
> + uint32_t h, m, s, us;
> +
> + if (cpu_freq == 0)
> + cpu_freq = arch_freq_get_on_cpu(0) * 1000ULL;
> +
> + s = div64_u64_rem(cpu_cycles, cpu_freq, &cpu_cycles);
> + us = div64_u64(cpu_cycles * 1000 * 1000 + 500 * 1000, cpu_freq);
> +
> + m = s / 60; s = s % 60;
> + h = m / 60; m = m % 60;
> +
> + snprintf(buf, size, "%02d:%02d:%02d.%06u", h, m, s, us);
seconds.us is enough. If some additional formatting is to happen, it
should be done in userspace.
> +}
> +
> +/*
> + * Add an entry to the register event queue. This can run in interrupt
> context
> + * so it needs to be as fast as possible.
> + */
> +static void queue_add(struct uart_port *port, uint8_t reg, uint8_t data, bool
> write)
> +{
> + struct uart_debug *uart_debug = port->private_data;
> + struct reg_queue *queue = &uart_debug->register_queue;
> + uint64_t cpu_cycle;
> + struct reg_event event;
> + unsigned long flags;
> +
> + if (!uart_debug->trace_all && reg != 0x00)
> + return;
> +
> + cpu_cycle = rdtsc();
> + event.cycle_lo = (uint32_t)cpu_cycle;
> + event.cycle_hi = (uint16_t)(cpu_cycle >> 32);
> + event.access = (write ? 0x08 : 0x00) | reg;
Use defines for these literals.
> + event.data = data;
> +
> + spin_lock_irqsave(&uart_debug->lock, flags);
> +
> + queue->buf[queue->write_idx & (queue->size - 1)] = event;
> + queue->write_idx++;
> +
> + if (queue->write_idx == queue->read_idx)
> + queue->wrap = true;
> +
> + spin_unlock_irqrestore(&uart_debug->lock, flags);
> +}
> +
> +/*
> + * Allocate memory for the register event queue. We use vmalloc() instead
> of kmalloc()
> + * because we don't need physically contiguous memory and kmalloc() is
> limited to 4 MiB.
> + * If memory allocation fails we will try again with a smaller buffer. The
> user can
> + * detect this by reading 'trace_size' after enabling tracing.
> + */
> +static bool queue_alloc(struct uart_port *port)
> +{
> + struct uart_debug *uart_debug = port->private_data;
> + struct reg_queue *queue = &uart_debug->register_queue;
> +
> + queue->size = uart_debug->trace_size;
> + queue->buf = vmalloc(queue->size * sizeof(struct reg_event));
> +
> + while (queue->buf == NULL) {
> + queue->size >>= 1;
> + if (queue->size == 0)
> + return false;
> + queue->buf = vmalloc(queue->size * sizeof(struct reg_event));
> + }
> +
> + uart_debug->trace_size = queue->size;
> + return true;
> +}
> +
> +/*
> + * Free the memory used by the register event queue if the queue is empty.
> If the 'force'
> + * flag is 'true' free the memory even if the queue is not empty.
> + *
> + * The noinline attribute suppresses a bug in gcc 11.3.0 that generates a
> bogus error:
> + *
> + * error: ‘__builtin_memset’ offset [0, 19] is out of the bounds [0, 0]
> [-Werror=array-bounds]
> + */
> +static noinline void queue_free(struct uart_port *port, bool force)
> +{
> + struct uart_debug *uart_debug = port->private_data;
> + struct reg_queue *queue = &uart_debug->register_queue;
> +
> + if (force || queue->read_idx == queue->write_idx) {
Why cannot the only place where force=true just reset the indexes before
making the call so no force parameter is required? ...I think there's a
bug anyway with the indexes not getting properly reset in that case.
> + vfree(queue->buf);
> + memset(queue, 0, sizeof(*queue));
> + }
> +}
> +
> +/*
> + * Remove an entry from the register event queue. The entry will be
> converted to a text
> + * string and written to the caller's buffer. If no entries are available
> an empty string
> + * will be written.
> + */
> +static void queue_remove(struct uart_port *port, char *buf, uint32_t size)
> +{
> + struct uart_debug *uart_debug = port->private_data;
> + struct reg_queue *queue = &uart_debug->register_queue;
> + struct reg_event event;
> + const char * const in_regs[] = { "RBR", "IER", "IIR", "LCR", "MCR",
> "LSR", "MSR", "SCR" };
> + const char * const out_regs[] = { "THR", "IER", "FCR", "LCR", "MCR",
> "LSR", "MSR", "SCR" };
> + uint8_t reg;
> + const char *sym, *dir;
> + char elapsed[sizeof("hh:mm:ss.mmmuuu")];
> + uint32_t num_events, events_lost;
> + unsigned long flags;
> +
> + spin_lock_irqsave(&uart_debug->lock, flags);
> +
> + num_events = queue->write_idx - queue->read_idx;
> + events_lost = num_events > queue->size ? num_events - queue->size : 0;
> + events_lost = queue->wrap ? 0xFFFFFFFF : events_lost;
> +
> + if (events_lost) {
> + queue->read_idx += num_events - queue->size;
> + queue->wrap = false;
> + } else if (num_events) {
> + event = queue->buf[queue->read_idx & (queue->size - 1)];
> + queue->read_idx++;
> + }
> +
> + spin_unlock_irqrestore(&uart_debug->lock, flags);
> +
> + if (events_lost) {
> + snprintf(buf, size, "queue overflow (%u events lost)\n",
> events_lost);
> + } else if (num_events) {
> + reg = event.access & 0x07;
> + sym = event.access & 0x08 ? out_regs[reg] : in_regs[reg];
Some uarts have registers beyond 0x07 so this doesn't seem enough.
It would be nice if the driver could provide alternative set of names for
the registers.
> + dir = event.access & 0x08 ? "<-" : "->";
> + format_cycle((((uint64_t)event.cycle_hi << 32) |
> event.cycle_lo),
> + elapsed, sizeof(elapsed));
> + snprintf(buf, size, "%s %s %s %02X\n", elapsed, sym, dir,
> event.data);
> + } else if (size) {
> + buf[0] = '\0';
> + }
> +}
> +
> +/*
> + * This is the hook we install for register reads when tracing is enabled.
> It calls
> + * the original serial_in function and logs the read to the register event
> queue.
> + */
> +static unsigned int serial_in_wrapper(struct uart_port *port, int offset)
> +{
> + struct uart_debug *uart_debug = port->private_data;
> + uint8_t value;
> +
> + value = uart_debug->original_serial_in(port, offset);
> + queue_add(port, offset, value, false);
> + return value;
> +}
> +
> +/*
> + * This is the hook we install for register writes when tracing is enabled.
> It calls
> + * the original serial_out function and logs the write to the register event
> queue.
> + */
> +static void serial_out_wrapper(struct uart_port *port, int offset, int value)
> +{
> + struct uart_debug *uart_debug = port->private_data;
> +
> + uart_debug->original_serial_out(port, offset, value);
> + queue_add(port, offset, value, true);
> +}
> +
> +/*
> + * Handle reads from the 'trace_size' pseudo-file. Report the configured
> trace size.
> + * If it changes between (partial) reads then the output will likely be
> corrupted. The
> + * only way to fix this is to store the text buffer in the file private
> data. Users
> + * should be encouraged to provide a buffer large enough to read the entire
> value at once.
> + */
> +static ssize_t size_read(struct file *filp, char __user *buf, size_t size,
> loff_t *ppos)
> +{
> + struct uart_port *port = filp->private_data;
> + struct uart_debug *uart_debug = port->private_data;
> + char text[16];
> + uint32_t len;
> +
> + len = snprintf(text, sizeof(text), "%u\n", uart_debug->trace_size);
> + return simple_read_from_buffer(buf, size, ppos, text, len);
> +}
> +
> +/*
> + * Handle writes to the 'trace_size' pseudo-file. Set the configured trace
> size (maximum
> + * number of log entries).
> + */
> +static ssize_t size_write(struct file *filp, const char __user *buf, size_t
> size, loff_t *ppos)
> +{
> + struct uart_port *port = filp->private_data;
> + struct uart_debug *uart_debug = port->private_data;
> + uint32_t trace_size, rounded_size = 1;
> + int error;
> +
> + error = kstrtouint_from_user(buf, size, 0, &trace_size);
> + if (error)
> + return error;
> +
> + if (trace_size == 0)
> + return -EINVAL;
> + while (trace_size >>= 1)
> + rounded_size <<= 1; /* round down to nearest power of 2 */
Comment is certainly misplaces as it's the whole while loop which
calculates that.
> + uart_debug->trace_size = rounded_size;
> + return size;
> +}
> +
> +/*
> + * Create the debugfs interface. This should be called during port
> registration after
> + * port->name, port->serial_in, and port->serial_out have been initialized.
> We are
> + * using port->private_data to store a pointer to our data structure. That
> field appears
> + * to be otherwise unused. If this is wrong we will need to create a new
> field.
> + */
> +void uart_debug_create(struct uart_port *port)
> +{
> + struct uart_debug *uart_debug;
> + struct dentry *dir;
> +
> + uart_debug = port->private_data = kzalloc(sizeof(struct uart_debug),
> GFP_KERNEL);
How about the drivers which use port->private_data?
It would make this look cleaner if you split the double assignment.
> + if (uart_debug == NULL)
> + return;
> +
> + spin_lock_init(&uart_debug->lock);
> + mutex_init(&uart_debug->mutex);
> + uart_debug->trace_size = 4096;
SZ_4K
> +
> + dir = debugfs_create_dir(port->name, NULL); /* ie. "ttyS0" */
> + debugfs_create_file("trace_all", 0644, dir, port, &all_fops);
> + debugfs_create_file("trace_buffer", 0444, dir, port, &buffer_fops);
> + debugfs_create_file("trace_enable", 0644, dir, port, &enable_fops);
> + debugfs_create_file("trace_size", 0644, dir, port, &size_fops);
> +}
> +
> +/*
> + * Destroy the debugfs interface. This should be called during port
> deregistration.
> + */
> +void uart_debug_destroy(struct uart_port *port)
> +{
> + struct uart_debug *uart_debug;
> +
> + uart_debug = port->private_data;
> + port->private_data = NULL;
> +
> + if (uart_debug) {
> + debugfs_lookup_and_remove(port->name, NULL); /* ie. "ttyS0 */
> + queue_free(port, true);
> + kfree(uart_debug);
> + }
> +}
--
i.
next prev parent reply other threads:[~2023-08-23 8:34 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2023-08-22 20:50 [PATCH] tty/serial: create debugfs interface for UART register tracing Dan Raymond
2023-08-23 7:01 ` Greg KH
2023-08-24 6:22 ` Dan Raymond
2023-08-23 8:30 ` Ilpo Järvinen [this message]
2023-08-24 6:24 ` Dan Raymond
2023-08-24 12:13 ` Ilpo Järvinen
2023-08-24 21:18 ` Dan Raymond
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=9c63a3a-2720-4e2b-5155-eb6e36aef257@linux.intel.com \
--to=ilpo.jarvinen@linux.intel.com \
--cc=draymond@foxvalley.net \
--cc=gregkh@linuxfoundation.org \
--cc=linux-serial@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox