All of lore.kernel.org
 help / color / mirror / Atom feed
From: Julien Grall <julien.grall@citrix.com>
To: Chen Baozi <baozich@gmail.com>
Cc: Keir Fraser <keir@xen.org>,
	Ian Campbell <Ian.Campbell@citrix.com>,
	xen-devel@lists.xen.org
Subject: Re: [PATCH 4/4] xen/arm: enable ns16550 uart driver for OMAP5432
Date: Fri, 19 Jul 2013 18:06:12 +0100	[thread overview]
Message-ID: <51E97204.2030108@citrix.com> (raw)
In-Reply-To: <1374238666-5870-5-git-send-email-baozich@gmail.com>

On 07/19/2013 01:57 PM, Chen Baozi wrote:
> 
> Signed-off-by: Chen Baozi <baozich@gmail.com>
> ---
>  xen/arch/arm/Rules.mk      |   1 +
>  xen/drivers/char/ns16550.c | 155 +++++++++++++++++++++++++++++++++++++++------
>  xen/include/asm-arm/io.h   |  49 ++++++++++++++
>  xen/include/xen/serial.h   |   7 +-
>  4 files changed, 191 insertions(+), 21 deletions(-)
> 
> diff --git a/xen/arch/arm/Rules.mk b/xen/arch/arm/Rules.mk
> index eaa03fb..8604e34 100644
> --- a/xen/arch/arm/Rules.mk
> +++ b/xen/arch/arm/Rules.mk
> @@ -9,6 +9,7 @@
>  HAS_DEVICE_TREE := y
>  HAS_VIDEO := y
>  HAS_ARM_HDLCD := y
> +HAS_NS16550 := y
>  
>  CFLAGS += -fno-builtin -fno-common -Wredundant-decls
>  CFLAGS += -iwithprefix include -Werror -Wno-pointer-arith -pipe
> diff --git a/xen/drivers/char/ns16550.c b/xen/drivers/char/ns16550.c
> index 60512be..b8690eb 100644
> --- a/xen/drivers/char/ns16550.c
> +++ b/xen/drivers/char/ns16550.c
> @@ -1,10 +1,10 @@
>  /******************************************************************************
>   * ns16550.c
> - * 
> + *

Spurious change.
>   * Driver for 16550-series UARTs. This driver is to be kept within Xen as
>   * it permits debugging of seriously-toasted machines (e.g., in situations
>   * where a device driver within a guest OS would be inaccessible).
> - * 
> + *
Spurious change.

>   * Copyright (c) 2003-2005, K A Fraser
>   */
>  
> @@ -25,6 +25,13 @@
>  #include <asm/fixmap.h>
>  #endif
>  
> +#ifdef CONFIG_ARM
> +#include <xen/device_tree.h>
> +#include <xen/errno.h>

The above 2 includes can be moved outside the ifdef.

> +#include <asm/early_printk.h>
> +#include <asm/device.h>
> +#endif
> +
>  /*
>   * Configure serial port with a string:
>   *   <baud>[/<clock_hz>][,DPS[,<io-base>[,<irq>[,<port-bdf>[,<bridge-bdf>]]]]].
> @@ -39,8 +46,11 @@ string_param("com1", opt_com1);
>  string_param("com2", opt_com2);
>  
>  static struct ns16550 {
> -    int baud, clock_hz, data_bits, parity, stop_bits, fifo_size, irq;
> -    unsigned long io_base;   /* I/O port or memory-mapped I/O address. */
> +    int baud, data_bits, parity, stop_bits, fifo_size;
> +    u32 clock_hz;
> +    struct dt_irq irq;
> +    u64 io_base;   /* I/O port or memory-mapped I/O address. */
> +    u64 io_size;
>      char __iomem *remapped_io_base;  /* Remapped virtual address of MMIO. */
>      /* UART with IRQ line: interrupt-driven I/O. */
>      struct irqaction irqaction;
> @@ -61,16 +71,24 @@ static struct ns16550 {
>  
>  static char ns_read_reg(struct ns16550 *uart, int reg)
>  {
> +#ifdef CONFIG_X86
>      if ( uart->remapped_io_base == NULL )
>          return inb(uart->io_base + reg);
>      return readb(uart->remapped_io_base + reg);
> +#else
> +    return readb(uart->remapped_io_base + (reg << 2));
> +#endif
>  }
>  

Is it possible to add a new private field "shift"? So the both readb can
be merged.

>  static void ns_write_reg(struct ns16550 *uart, int reg, char c)
>  {
> +#ifdef CONFIG_X86
>      if ( uart->remapped_io_base == NULL )
>          return outb(c, uart->io_base + reg);
>      writeb(c, uart->remapped_io_base + reg);
> +#else
> +    writeb(c, uart->remapped_io_base + (reg << 2));
> +#endif

Same here for readb.

>  }
>  
>  static void ns16550_interrupt(
> @@ -145,11 +163,12 @@ static int ns16550_getc(struct serial_port *port, char *pc)
>      return 1;
>  }
>  
> +#ifdef CONFIG_X86
>  static void pci_serial_early_init(struct ns16550 *uart)
>  {
>      if ( !uart->ps_bdf_enable || uart->io_base >= 0x10000 )
>          return;
> -    
> +
Spurious change.

>      if ( uart->pb_bdf_enable )
>          pci_conf_write16(0, uart->pb_bdf[0], uart->pb_bdf[1], uart->pb_bdf[2],
>                           PCI_IO_BASE,
> @@ -161,7 +180,13 @@ static void pci_serial_early_init(struct ns16550 *uart)
>                       uart->io_base | PCI_BASE_ADDRESS_SPACE_IO);
>      pci_conf_write16(0, uart->ps_bdf[0], uart->ps_bdf[1], uart->ps_bdf[2],
>                       PCI_COMMAND, PCI_COMMAND_IO);
> +
> +}
> +#else
> +static void pci_serial_early_init(struct ns16550 *uart)
> +{
>  }
> +#endif
>  
>  static void ns16550_setup_preirq(struct ns16550 *uart)
>  {
> @@ -217,7 +242,9 @@ static void __init ns16550_init_preirq(struct serial_port *port)
>          uart->remapped_io_base = (void __iomem *)fix_to_virt(idx);
>          uart->remapped_io_base += uart->io_base & ~PAGE_MASK;
>  #else
> -        uart->remapped_io_base = (char *)ioremap(uart->io_base, 8);
> +        uart->remapped_io_base = ioremap_attr(uart->io_base,
> +                                              uart->io_size,
> +                                              PAGE_HYPERVISOR_NOCACHE);
>  #endif
>      }
>  
> @@ -231,7 +258,7 @@ static void __init ns16550_init_preirq(struct serial_port *port)
>  
>  static void ns16550_setup_postirq(struct ns16550 *uart)
>  {
> -    if ( uart->irq > 0 )
> +    if ( uart->irq.irq > 0 )
>      {
>          /* Master interrupt enable; also keep DTR/RTS asserted. */
>          ns_write_reg(uart,
> @@ -241,7 +268,7 @@ static void ns16550_setup_postirq(struct ns16550 *uart)
>          ns_write_reg(uart, UART_IER, UART_IER_ERDAI | UART_IER_ELSI);
>      }
>  
> -    if ( uart->irq >= 0 )
> +    if ( uart->irq.irq >= 0 )
>          set_timer(&uart->timer, NOW() + MILLISECS(uart->timeout_ms));
>  }
>  
> @@ -250,7 +277,7 @@ static void __init ns16550_init_postirq(struct serial_port *port)
>      struct ns16550 *uart = port->uart;
>      int rc, bits;
>  
> -    if ( uart->irq < 0 )
> +    if ( uart->irq.irq < 0 )
>          return;
>  
>      serial_async_transmit(port);
> @@ -262,20 +289,26 @@ static void __init ns16550_init_postirq(struct serial_port *port)
>      uart->timeout_ms = max_t(
>          unsigned int, 1, (bits * uart->fifo_size * 1000) / uart->baud);
>  
> -    if ( uart->irq > 0 )
> +    if ( uart->irq.irq > 0 )
>      {
>          uart->irqaction.handler = ns16550_interrupt;
>          uart->irqaction.name    = "ns16550";
>          uart->irqaction.dev_id  = port;
> -        if ( (rc = setup_irq(uart->irq, &uart->irqaction)) != 0 )
> -            printk("ERROR: Failed to allocate ns16550 IRQ %d\n", uart->irq);
> +#ifdef CONFIG_X86
> +        if ( (rc = setup_irq(uart->irq.irq, &uart->irqaction)) != 0 )
> +#else
> +        if ( (rc = setup_dt_irq(&uart->irq, &uart->irqaction)) != 0 )
> +#endif
> +            printk("ERROR: Failed to allocate ns16550 IRQ %d\n", uart->irq.irq);
>      }
>  
>      ns16550_setup_postirq(uart);
>  
> +#ifdef CONFIG_X86
>      if ( uart->bar || uart->ps_bdf_enable )
>          pci_hide_device(uart->ps_bdf[0], PCI_DEVFN(uart->ps_bdf[1],
>                                                     uart->ps_bdf[2]));
> +#endif
>  }
>  
>  static void ns16550_suspend(struct serial_port *port)
> @@ -284,13 +317,16 @@ static void ns16550_suspend(struct serial_port *port)
>  
>      stop_timer(&uart->timer);
>  
> +#ifdef CONFIG_X86
>      if ( uart->bar )
>         uart->cr = pci_conf_read16(0, uart->ps_bdf[0], uart->ps_bdf[1],
>                                    uart->ps_bdf[2], PCI_COMMAND);
> +#endif
>  }
>  
>  static void _ns16550_resume(struct serial_port *port)
>  {
> +#ifdef CONFIG_X86
>      struct ns16550 *uart = port->uart;
>  
>      if ( uart->bar )
> @@ -300,6 +336,7 @@ static void _ns16550_resume(struct serial_port *port)
>         pci_conf_write16(0, uart->ps_bdf[0], uart->ps_bdf[1], uart->ps_bdf[2],
>                          PCI_COMMAND, uart->cr);
>      }
> +#endif
>  
>      ns16550_setup_preirq(port->uart);
>      ns16550_setup_postirq(port->uart);
> @@ -370,9 +407,17 @@ static void __init ns16550_endboot(struct serial_port *port)
>  static int __init ns16550_irq(struct serial_port *port)
>  {
>      struct ns16550 *uart = port->uart;
> -    return ((uart->irq > 0) ? uart->irq : -1);
> +    return ((uart->irq.irq > 0) ? uart->irq.irq : -1);
>  }
>  
> +#ifdef CONFIG_ARM
> +static const struct dt_irq __init *ns16550_dt_irq(struct serial_port *port)
> +{
> +    struct ns16550 *uart = port->uart;
> +    return &uart->irq;
> +}
> +#endif

You don't need to surround this function by ifdef.

>  static struct uart_driver __read_mostly ns16550_driver = {
>      .init_preirq  = ns16550_init_preirq,
>      .init_postirq = ns16550_init_postirq,
> @@ -382,7 +427,10 @@ static struct uart_driver __read_mostly ns16550_driver = {
>      .tx_ready     = ns16550_tx_ready,
>      .putc         = ns16550_putc,
>      .getc         = ns16550_getc,
> -    .irq          = ns16550_irq
> +    .irq          = ns16550_irq,
> +#ifdef CONFIG_ARM
> +    .dt_irq_get   = ns16550_dt_irq
> +#endif
>  };

same here.

>  static int __init parse_parity_char(int c)
> @@ -403,6 +451,7 @@ static int __init parse_parity_char(int c)
>      return 0;
>  }
>  
> +#ifdef CONFIG_X86
>  static void __init parse_pci_bdf(const char **conf, unsigned int bdf[3])
>  {
>      bdf[0] = simple_strtoul(*conf, conf, 16);
> @@ -415,6 +464,7 @@ static void __init parse_pci_bdf(const char **conf, unsigned int bdf[3])
>      (*conf)++;
>      bdf[2] = simple_strtoul(*conf, conf, 16);
>  }
> +#endif
>  
>  static int __init check_existence(struct ns16550 *uart)
>  {
> @@ -428,7 +478,7 @@ static int __init check_existence(struct ns16550 *uart)
>          return 1;
>  
>      pci_serial_early_init(uart);
> -    
> +

spurious change.

>      /*
>       * Do a simple existence test first; if we fail this,
>       * there's no point trying anything else.
> @@ -445,7 +495,7 @@ static int __init check_existence(struct ns16550 *uart)
>      scratch3 = ns_read_reg(uart, UART_IER) & 0x0f;
>      ns_write_reg(uart, UART_IER, scratch);
>      if ( (scratch2 != 0) || (scratch3 != 0x0F) )
> -        return 0;
> +            return 0;

spurious change.

>  
>      /*
>       * Check to see if a UART is really there.
> @@ -456,6 +506,7 @@ static int __init check_existence(struct ns16550 *uart)
>      return (status == 0x90);
>  }
>  
> +#ifdef CONFIG_X86
>  static int
>  pci_uart_config (struct ns16550 *uart, int skip_amt, int bar_idx)
>  {
> @@ -507,7 +558,7 @@ pci_uart_config (struct ns16550 *uart, int skip_amt, int bar_idx)
>                  uart->bar = bar;
>                  uart->bar_idx = bar_idx;
>                  uart->io_base = bar & ~PCI_BASE_ADDRESS_SPACE_IO;
> -                uart->irq = pci_conf_read8(0, b, d, f, PCI_INTERRUPT_PIN) ?
> +                uart->irq.irq = pci_conf_read8(0, b, d, f, PCI_INTERRUPT_PIN) ?
>                      pci_conf_read8(0, b, d, f, PCI_INTERRUPT_LINE) : 0;
>  
>                  return 0;
> @@ -519,11 +570,12 @@ pci_uart_config (struct ns16550 *uart, int skip_amt, int bar_idx)
>          return -1;
>  
>      uart->io_base = 0x3f8;
> -    uart->irq = 0;
> +    uart->irq.irq = 0;
>      uart->clock_hz  = UART_CLOCK_HZ;
>  
>      return 0;
>  }
> +#endif
>  
>  #define PARSE_ERR(_f, _a...)                 \
>      do {                                     \
> @@ -534,6 +586,7 @@ pci_uart_config (struct ns16550 *uart, int skip_amt, int bar_idx)
>  static void __init ns16550_parse_port_config(
>      struct ns16550 *uart, const char *conf)
>  {
> +#ifdef CONFIG_X86
>      int baud;
>  
>      /* No user-specified configuration? */
> @@ -589,7 +642,7 @@ static void __init ns16550_parse_port_config(
>      }
>  
>      if ( *conf == ',' && *++conf != ',' )
> -        uart->irq = simple_strtol(conf, &conf, 10);
> +        uart->irq.irq = simple_strtol(conf, &conf, 10);
>  
>      if ( *conf == ',' && *++conf != ',' )
>      {
> @@ -604,6 +657,8 @@ static void __init ns16550_parse_port_config(
>      }
>  
>   config_parsed:
> +#endif
> +

You have disabled nearly 80% of the code for ARM. Perhaps you can split
this function in 2:
	- Parsing pci/user configuration
        - Register the UART

>      /* Sanity checks. */
>      if ( (uart->baud != BAUD_AUTO) &&
>           ((uart->baud < 1200) || (uart->baud > 115200)) )
> @@ -633,18 +688,80 @@ void __init ns16550_init(int index, struct ns16550_defaults *defaults)
>      uart->baud      = (defaults->baud ? :
>                         console_has((index == 0) ? "com1" : "com2")
>                         ? BAUD_AUTO : 0);
> +#ifdef CONFIG_ARM
> +    uart->clock_hz = defaults->clock_hz;
> +#else
>      uart->clock_hz  = UART_CLOCK_HZ;
> +#endif
>      uart->data_bits = defaults->data_bits;
>      uart->parity    = parse_parity_char(defaults->parity);
>      uart->stop_bits = defaults->stop_bits;
>      uart->irq       = defaults->irq;
>      uart->io_base   = defaults->io_base;
> +#ifdef CONFIG_ARM
> +    uart->io_size   = defaults->io_size;
> +#endif
> +
>      /* Default is no transmit FIFO. */
>      uart->fifo_size = 1;
>  
>      ns16550_parse_port_config(uart, (index == 0) ? opt_com1 : opt_com2);
> +
>  }
>  
> +#ifdef CONFIG_ARM
> +
> +static int __init ns16550_dt_init(struct dt_device_node *dev,
> +                                  const void *data)
> +{
> +    struct ns16550_defaults uart;
> +    int res;
> +    const __be32 *clkspec;
> +
> +    uart.baud      = 115200;
> +    uart.data_bits = 8;
> +    uart.parity    = 'n';
> +    uart.stop_bits = 1;
> +
> +    res = dt_device_get_address(dev, 0, &uart.io_base, &uart.io_size);
> +    if (res) {
> +        early_printk("ns16550: Unable to retrieve the base"
> +                     " address of the UART\n");
> +        return res;
> +    }
> +
> +    res = dt_device_get_irq(dev, 0, &uart.irq);
> +    if (res) {
> +        early_printk("ns16550: Unable to retrieve the IRQ\n");
> +        return res;
> +    }
> +
> +    clkspec = dt_get_property(dev, "clock-frequency", NULL);
> +    if (clkspec == NULL) {
> +        early_printk("ns16550: Unable to retrieve the clock frequency\n");
> +        return -EINVAL;
> +    }
> +    uart.clock_hz = be32_to_cpup(clkspec);
> +
> +    ns16550_init(0, &uart);
> +    dt_device_set_used_by(dev, DOMID_XEN);
> +
> +    return 0;
> +}
> +
> +static const char const *ns16550_dt_compat[] __initdata =
> +{
> +    "ti,omap4-uart",
> +    NULL
> +};
> +
> +DT_DEVICE_START(ns16550, "NS16550 UART", DEVICE_SERIAL)
> +    .compatible = ns16550_dt_compat,
> +    .init = ns16550_dt_init,
> +DT_DEVICE_END
> +
> +#endif /* CONFIG_ARM */
> +
>  /*
>   * Local variables:
>   * mode: C
> diff --git a/xen/include/asm-arm/io.h b/xen/include/asm-arm/io.h
> index aea5233..33674bc 100644
> --- a/xen/include/asm-arm/io.h
> +++ b/xen/include/asm-arm/io.h
> @@ -1,6 +1,55 @@
>  #ifndef _ASM_IO_H
>  #define _ASM_IO_H
>  
> +static inline void __raw_writeb(u8 val, volatile void __iomem *addr)
> +{
> +        asm volatile("strb %1, %0"
> +                     : "+Qo" (*(volatile u8 __force *)addr)
> +                     : "r" (val));
> +}
> +
> +static inline void __raw_writel(u32 val, volatile void __iomem *addr)
> +{
> +        asm volatile("str %1, %0"
> +                     : "+Qo" (*(volatile u32 __force *)addr)
> +                     : "r" (val));
> +}
> +
> +static inline u8 __raw_readb(const volatile void __iomem *addr)
> +{
> +        u8 val;
> +        asm volatile("ldrb %1, %0"
> +                     : "+Qo" (*(volatile u8 __force *)addr),
> +                       "=r" (val));
> +        return val;
> +}
> +
> +static inline u32 __raw_readl(const volatile void __iomem *addr)
> +{
> +        u32 val;
> +        asm volatile("ldr %1, %0"
> +                     : "+Qo" (*(volatile u32 __force *)addr),
> +                       "=r" (val));
> +        return val;
> +}
> +
> +#define __iormb()               rmb()
> +#define __iowmb()               wmb()
> +
> +#define readb_relaxed(c) ({ u8  __r = __raw_readb(c); __r; })
> +#define readl_relaxed(c) ({ u32 __r = le32_to_cpu((__force __le32) \
> +                                        __raw_readl(c)); __r; })
> +
> +#define writeb_relaxed(v,c)     __raw_writeb(v,c)
> +#define writel_relaxed(v,c)     __raw_writel((__force u32) cpu_to_le32(v),c)
> +
> +#define readb(c)                ({ u8  __v = readb_relaxed(c); __iormb(); __v; })
> +#define readl(c)                ({ u32 __v = readl_relaxed(c); __iormb(); __v; })
> +
> +#define writeb(v,c)             ({ __iowmb(); writeb_relaxed(v,c); })
> +#define writel(v,c)             ({ __iowmb(); writel_relaxed(v,c); })
> +
> +
>  #endif
>  /*
>   * Local variables:

Theses changes are not in the right place. The asm-arm/io.h must only
contain common code between arm32 and arm64. Your code is arm32
specific, so it should be moved in asm-arm/arm32/io.h.

There is already an implementation for (io)readl and (io)writel with
another name. I will be happy if you rename/alias these 2 functions, it
will avoid ifdef... in ns16550 code.

> diff --git a/xen/include/xen/serial.h b/xen/include/xen/serial.h
> index 9caf776..09ca855 100644
> --- a/xen/include/xen/serial.h
> +++ b/xen/include/xen/serial.h
> @@ -11,6 +11,7 @@
>  
>  #include <xen/init.h>
>  #include <xen/spinlock.h>
> +#include <xen/device_tree.h>

>  
>  struct cpu_user_regs;
>  
> @@ -151,8 +152,10 @@ struct ns16550_defaults {
>      int data_bits; /* default data bits (5, 6, 7 or 8) */
>      int parity;    /* default parity (n, o, e, m or s) */
>      int stop_bits; /* default stop bits (1 or 2) */
> -    int irq;       /* default irq */
> -    unsigned long io_base; /* default io_base address */
> +    struct dt_irq irq;     /* default irq */

This change will break build on x86. This structure is filled by
arch/x86/setup.c

> +    u64 io_base;   /* default io_base address */
> +    u64 io_size;
> +    u32 clock_hz;  /* UART clock rate */
>  };

As I understand, you use these modifications only in ns16550.c to fill
it with device tree value. Considering the number of ifdef you have
added in ns16550_init, I think it's better to clone the function and
directly fill ns16550, instead of ns16550_defaults. Anyone got any other
thoughts?

>  void ns16550_init(int index, struct ns16550_defaults *defaults);
>  void ehci_dbgp_init(void);
> 

Cheers,

-- 
Julien

  reply	other threads:[~2013-07-19 17:06 UTC|newest]

Thread overview: 27+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2013-07-19 12:57 [PATCH 0/4] Support 8250 compatible UART for OMAP5432 Chen Baozi
2013-07-19 12:57 ` [PATCH 1/4] xen: extract register definitions from ns16550 into a separated header Chen Baozi
2013-07-19 15:35   ` Julien Grall
2013-07-19 15:37     ` Chen Baozi
2013-07-19 15:43       ` Julien Grall
2013-07-19 12:57 ` [PATCH 2/4] xen/arm: add OMAP5432 UART support for early_printk Chen Baozi
2013-07-19 15:49   ` Julien Grall
2013-07-19 12:57 ` [PATCH 3/4] xen: set the right flags when enabling interrupts for 8250 Chen Baozi
2013-07-19 14:15   ` Ian Campbell
2013-07-19 15:18     ` Chen Baozi
2013-07-22 12:59       ` Chen Baozi
2013-07-23 11:47         ` Chen Baozi
2013-07-24 10:04           ` Chen Baozi
2013-07-25  9:14             ` Chen Baozi
2013-07-25 11:17               ` Julien Grall
2013-07-25 11:31                 ` Chen Baozi
2013-07-25 12:33                   ` Julien Grall
2013-07-26  1:14                     ` Chen Baozi
2013-07-26  3:31                       ` Chen Baozi
2013-07-26  9:30                         ` Chen Baozi
2013-07-26 10:42                         ` Julien Grall
2013-07-26 11:01                           ` Chen Baozi
2013-07-26 12:01                             ` Julien Grall
2013-07-26 12:32                               ` Chen Baozi
2013-07-19 12:57 ` [PATCH 4/4] xen/arm: enable ns16550 uart driver for OMAP5432 Chen Baozi
2013-07-19 17:06   ` Julien Grall [this message]
2013-07-22 13:06     ` Chen Baozi

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=51E97204.2030108@citrix.com \
    --to=julien.grall@citrix.com \
    --cc=Ian.Campbell@citrix.com \
    --cc=baozich@gmail.com \
    --cc=keir@xen.org \
    --cc=xen-devel@lists.xen.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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.