/* * Copyright (c) 2021 EPAM Systems * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #include #include #include #include #include #include #include LOG_MODULE_REGISTER(uart_hvc_xen, CONFIG_UART_LOG_LEVEL); static struct hvc_xen_data xen_hvc_data = {0}; #ifdef CONFIG_UART_INTERRUPT_DRIVEN static void hvc_uart_evtchn_cb(void *priv); #endif /* CONFIG_UART_INTERRUPT_DRIVEN */ static int read_from_ring(const struct device *dev, char *str, int len) { int recv = 0; struct hvc_xen_data *hvc_data = dev->data; XENCONS_RING_IDX cons = hvc_data->intf->in_cons; XENCONS_RING_IDX prod = hvc_data->intf->in_prod; XENCONS_RING_IDX in_idx = 0; compiler_barrier(); __ASSERT((prod - cons) <= sizeof(hvc_data->intf->in), "Invalid input ring buffer"); while (cons != prod && recv < len) { in_idx = MASK_XENCONS_IDX(cons, hvc_data->intf->in); str[recv] = hvc_data->intf->in[in_idx]; recv++; cons++; } compiler_barrier(); hvc_data->intf->in_cons = cons; notify_evtchn(hvc_data->evtchn); return recv; } static int write_to_ring(const struct device *dev, const char *str, int len) { int sent = 0; struct hvc_xen_data *hvc_data = dev->data; XENCONS_RING_IDX cons = hvc_data->intf->out_cons; XENCONS_RING_IDX prod = hvc_data->intf->out_prod; XENCONS_RING_IDX out_idx = 0; compiler_barrier(); __ASSERT((prod - cons) <= sizeof(hvc_data->intf->out), "Invalid output ring buffer"); while ((sent < len) && ((prod - cons) < sizeof(hvc_data->intf->out))) { out_idx = MASK_XENCONS_IDX(prod, hvc_data->intf->out); hvc_data->intf->out[out_idx] = str[sent]; prod++; sent++; } compiler_barrier(); hvc_data->intf->out_prod = prod; if (sent) { notify_evtchn(hvc_data->evtchn); } return sent; } static int xen_hvc_poll_in(const struct device *dev, unsigned char *c) { int ret = 0; char temp; ret = read_from_ring(dev, &temp, sizeof(temp)); if (!ret) { /* Char was not received */ return -1; } *c = temp; return 0; } static void xen_hvc_poll_out(const struct device *dev, unsigned char c) { /* Not a good solution (notifying HV every time), but needed for poll_out */ (void) write_to_ring(dev, &c, sizeof(c)); } #ifdef CONFIG_UART_INTERRUPT_DRIVEN static int xen_hvc_fifo_fill(const struct device *dev, const uint8_t *tx_data, int len) { int ret = 0, sent = 0; while (len) { sent = write_to_ring(dev, tx_data, len); ret += sent; tx_data += sent; len -= sent; if (len) { /* Need to be able to read it from another domain */ HYPERVISOR_sched_op(SCHEDOP_yield, NULL); } } return ret; } static int xen_hvc_fifo_read(const struct device *dev, uint8_t *rx_data, const int size) { return read_from_ring(dev, rx_data, size); } static void xen_hvc_irq_tx_enable(const struct device *dev) { /* * Need to explicitly call UART callback on TX enabling to * process available buffered TX actions, because no HV events * will be generated on tx_enable. */ hvc_uart_evtchn_cb(dev->data); } static int xen_hvc_irq_tx_ready(const struct device *dev) { return 1; } static void xen_hvc_irq_rx_enable(const struct device *dev) { /* * Need to explicitly call UART callback on RX enabling to * process available buffered RX actions, because no HV events * will be generated on rx_enable. */ hvc_uart_evtchn_cb(dev->data); } static int xen_hvc_irq_tx_complete(const struct device *dev) { /* * TX is performed by copying in ring buffer by fifo_fill, * so it will be always completed. */ return 1; } static int xen_hvc_irq_rx_ready(const struct device *dev) { struct hvc_xen_data *data = dev->data; /* RX is ready only if data is available in ring buffer */ return (data->intf->in_prod != data->intf->in_cons); } static int xen_hvc_irq_is_pending(const struct device *dev) { return xen_hvc_irq_rx_ready(dev); } static int xen_hvc_irq_update(const struct device *dev) { /* Nothing needs to be updated before actual ISR */ return 1; } static void xen_hvc_irq_callback_set(const struct device *dev, uart_irq_callback_user_data_t cb, void *user_data) { struct hvc_xen_data *data = dev->data; data->irq_cb = cb; data->irq_cb_data = user_data; } #endif /* CONFIG_UART_INTERRUPT_DRIVEN */ static const struct uart_driver_api xen_hvc_api = { .poll_in = xen_hvc_poll_in, .poll_out = xen_hvc_poll_out, #ifdef CONFIG_UART_INTERRUPT_DRIVEN .fifo_fill = xen_hvc_fifo_fill, .fifo_read = xen_hvc_fifo_read, .irq_tx_enable = xen_hvc_irq_tx_enable, .irq_tx_ready = xen_hvc_irq_tx_ready, .irq_rx_enable = xen_hvc_irq_rx_enable, .irq_tx_complete = xen_hvc_irq_tx_complete, .irq_rx_ready = xen_hvc_irq_rx_ready, .irq_is_pending = xen_hvc_irq_is_pending, .irq_update = xen_hvc_irq_update, .irq_callback_set = xen_hvc_irq_callback_set, #endif /* CONFIG_UART_INTERRUPT_DRIVEN */ }; #ifdef CONFIG_UART_INTERRUPT_DRIVEN static void hvc_uart_evtchn_cb(void *priv) { struct hvc_xen_data *data = priv; if (data->irq_cb) { data->irq_cb(data->dev, data->irq_cb_data); } } #endif /* CONFIG_UART_INTERRUPT_DRIVEN */ int xen_console_init(const struct device *dev) { int ret = 0; uint64_t console_pfn = 0; uintptr_t console_addr = 0; struct hvc_xen_data *data = dev->data; data->dev = dev; ret = hvm_get_parameter(HVM_PARAM_CONSOLE_EVTCHN, DOMID_SELF, &data->evtchn); if (ret) { LOG_ERR("%s: failed to get Xen console evtchn, ret = %d\n", __func__, ret); return ret; } ret = hvm_get_parameter(HVM_PARAM_CONSOLE_PFN, DOMID_SELF, &console_pfn); if (ret) { LOG_ERR("%s: failed to get Xen console PFN, ret = %d\n", __func__, ret); return ret; } console_addr = (uintptr_t) (console_pfn << XEN_PAGE_SHIFT); device_map(DEVICE_MMIO_RAM_PTR(dev), console_addr, XEN_PAGE_SIZE, K_MEM_CACHE_WB); data->intf = (struct xencons_interface *) DEVICE_MMIO_GET(dev); #ifdef CONFIG_UART_INTERRUPT_DRIVEN bind_event_channel(data->evtchn, hvc_uart_evtchn_cb, data); #endif /* CONFIG_UART_INTERRUPT_DRIVEN */ LOG_INF("Xen HVC inited successfully\n"); return 0; } DEVICE_DT_DEFINE(DT_NODELABEL(xen_hvc), xen_console_init, NULL, &xen_hvc_data, NULL, PRE_KERNEL_1, CONFIG_XEN_HVC_INIT_PRIORITY, &xen_hvc_api); #ifdef CONFIG_XEN_EARLY_CONSOLEIO extern void __printk_hook_install(int (*fn)(int)); extern void __stdout_hook_install(int (*fn)(int)); int xen_consoleio_putc(int c) { char symbol = (char) c; HYPERVISOR_console_io(CONSOLEIO_write, sizeof(symbol), &symbol); return c; } int consoleio_hooks_set(void) { /* Will be replaced with poll_in/poll_out by uart_console.c later on boot */ __stdout_hook_install(xen_consoleio_putc); __printk_hook_install(xen_consoleio_putc); return 0; } SYS_INIT(consoleio_hooks_set, PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT); #endif /* CONFIG_XEN_EARLY_CONSOLEIO */