| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * (C) 2018 NXP | |
| * (C) 2020 EPAM Systems Inc. | |
| */ | |
| #include <cpu_func.h> | |
| #include <dm.h> | |
| #include <serial.h> | |
| #include <watchdog.h> | |
| #include <asm/global_data.h> | |
| #include <linux/bug.h> | |
| #include <xen/hvm.h> | |
| #include <xen/events.h> | |
| #include <xen/interface/sched.h> | |
| #include <xen/interface/hvm/hvm_op.h> | |
| #include <xen/interface/hvm/params.h> | |
| #include <xen/interface/io/console.h> | |
| #include <xen/interface/io/ring.h> | |
| DECLARE_GLOBAL_DATA_PTR; | |
| u32 console_evtchn; | |
| /* | |
| * struct xen_uart_priv - Structure representing a Xen UART info | |
| * @intf: Console I/O interface for Xen guest OSes | |
| * @evtchn: Console event channel | |
| */ | |
| struct xen_uart_priv { | |
| struct xencons_interface *intf; | |
| u32 evtchn; | |
| }; | |
| int xen_serial_setbrg(struct udevice *dev, int baudrate) | |
| { | |
| return 0; | |
| } | |
| static int xen_serial_probe(struct udevice *dev) | |
| { | |
| struct xen_uart_priv *priv = dev_get_priv(dev); | |
| u64 val = 0; | |
| unsigned long gfn; | |
| int ret; | |
| ret = hvm_get_parameter(HVM_PARAM_CONSOLE_EVTCHN, &val); | |
| if (ret < 0 || val == 0) | |
| return ret; | |
| priv->evtchn = val; | |
| console_evtchn = val; | |
| ret = hvm_get_parameter(HVM_PARAM_CONSOLE_PFN, &val); | |
| if (ret < 0) | |
| return ret; | |
| if (!val) | |
| return -EINVAL; | |
| gfn = val; | |
| priv->intf = (struct xencons_interface *)(gfn << XEN_PAGE_SHIFT); | |
| return 0; | |
| } | |
| static int xen_serial_pending(struct udevice *dev, bool input) | |
| { | |
| struct xen_uart_priv *priv = dev_get_priv(dev); | |
| struct xencons_interface *intf = priv->intf; | |
| if (!input || intf->in_cons == intf->in_prod) | |
| return 0; | |
| return 1; | |
| } | |
| static int xen_serial_getc(struct udevice *dev) | |
| { | |
| struct xen_uart_priv *priv = dev_get_priv(dev); | |
| struct xencons_interface *intf = priv->intf; | |
| XENCONS_RING_IDX cons; | |
| char c; | |
| while (intf->in_cons == intf->in_prod) | |
| mb(); /* wait */ | |
| cons = intf->in_cons; | |
| mb(); /* get pointers before reading ring */ | |
| c = intf->in[MASK_XENCONS_IDX(cons++, intf->in)]; | |
| mb(); /* read ring before consuming */ | |
| intf->in_cons = cons; | |
| notify_remote_via_evtchn(priv->evtchn); | |
| return c; | |
| } | |
| static int __write_console(struct udevice *dev, const char *data, int len) | |
| { | |
| struct xen_uart_priv *priv = dev_get_priv(dev); | |
| struct xencons_interface *intf = priv->intf; | |
| XENCONS_RING_IDX cons, prod; | |
| int sent = 0; | |
| cons = intf->out_cons; | |
| prod = intf->out_prod; | |
| mb(); /* Update pointer */ | |
| WARN_ON((prod - cons) > sizeof(intf->out)); | |
| while ((sent < len) && ((prod - cons) < sizeof(intf->out))) | |
| intf->out[MASK_XENCONS_IDX(prod++, intf->out)] = data[sent++]; | |
| mb(); /* Update data before pointer */ | |
| intf->out_prod = prod; | |
| if (sent) | |
| notify_remote_via_evtchn(priv->evtchn); | |
| return sent; | |
| } | |
| static int write_console(struct udevice *dev, const char *data, int len) | |
| { | |
| /* | |
| * Make sure the whole buffer is emitted, polling if | |
| * necessary. We don't ever want to rely on the hvc daemon | |
| * because the most interesting console output is when the | |
| * kernel is crippled. | |
| */ | |
| while (len) { | |
| int sent = __write_console(dev, data, len); | |
| data += sent; | |
| len -= sent; | |
| if (unlikely(len)) | |
| HYPERVISOR_sched_op(SCHEDOP_yield, NULL); | |
| } | |
| return 0; | |
| } | |
| static int xen_serial_putc(struct udevice *dev, const char ch) | |
| { | |
| write_console(dev, &ch, 1); | |
| return 0; | |
| } | |
| static const struct dm_serial_ops xen_serial_ops = { | |
| .putc = xen_serial_putc, | |
| .getc = xen_serial_getc, | |
| .pending = xen_serial_pending, | |
| }; | |
| #if CONFIG_IS_ENABLED(OF_CONTROL) | |
| static const struct udevice_id xen_serial_ids[] = { | |
| { .compatible = "xen,xen" }, | |
| { } | |
| }; | |
| #endif | |
| U_BOOT_DRIVER(serial_xen) = { | |
| .name = "serial_xen", | |
| .id = UCLASS_SERIAL, | |
| #if CONFIG_IS_ENABLED(OF_CONTROL) | |
| .of_match = xen_serial_ids, | |
| #endif | |
| .priv_auto = sizeof(struct xen_uart_priv), | |
| .probe = xen_serial_probe, | |
| .ops = &xen_serial_ops, | |
| #if !CONFIG_IS_ENABLED(OF_CONTROL) | |
| .flags = DM_FLAG_PRE_RELOC, | |
| #endif | |
| }; |