// SPDX-License-Identifier: GPL-2.0 /* * (C) 2018 NXP * (C) 2020 EPAM Systems Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 };