diff options
author | 2023-10-10 14:33:42 +0000 | |
---|---|---|
committer | 2023-10-10 14:33:42 +0000 | |
commit | af1a266670d040d2f4083ff309d732d648afba2a (patch) | |
tree | 2fc46203448ddcc6f81546d379abfaeb323575e9 /roms/u-boot/drivers/xen | |
parent | e02cda008591317b1625707ff8e115a4841aa889 (diff) |
Change-Id: Iaf8d18082d3991dec7c0ebbea540f092188eb4ec
Diffstat (limited to 'roms/u-boot/drivers/xen')
-rw-r--r-- | roms/u-boot/drivers/xen/Kconfig | 10 | ||||
-rw-r--r-- | roms/u-boot/drivers/xen/Makefile | 10 | ||||
-rw-r--r-- | roms/u-boot/drivers/xen/events.c | 199 | ||||
-rw-r--r-- | roms/u-boot/drivers/xen/gnttab.c | 218 | ||||
-rw-r--r-- | roms/u-boot/drivers/xen/hypervisor.c | 254 | ||||
-rw-r--r-- | roms/u-boot/drivers/xen/pvblock.c | 868 | ||||
-rw-r--r-- | roms/u-boot/drivers/xen/xenbus.c | 557 |
7 files changed, 2116 insertions, 0 deletions
diff --git a/roms/u-boot/drivers/xen/Kconfig b/roms/u-boot/drivers/xen/Kconfig new file mode 100644 index 000000000..6ad2a9366 --- /dev/null +++ b/roms/u-boot/drivers/xen/Kconfig @@ -0,0 +1,10 @@ +config PVBLOCK + bool "Xen para-virtualized block device" + depends on DM + select BLK + select HAVE_BLOCK_DEVICE + help + This driver implements the front-end of the Xen virtual + block device driver. It communicates with a back-end driver + in another domain which drives the actual block device. + diff --git a/roms/u-boot/drivers/xen/Makefile b/roms/u-boot/drivers/xen/Makefile new file mode 100644 index 000000000..87157df69 --- /dev/null +++ b/roms/u-boot/drivers/xen/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0+ +# +# (C) Copyright 2020 EPAM Systems Inc. + +obj-y += hypervisor.o +obj-y += events.o +obj-y += xenbus.o +obj-y += gnttab.o + +obj-$(CONFIG_PVBLOCK) += pvblock.o diff --git a/roms/u-boot/drivers/xen/events.c b/roms/u-boot/drivers/xen/events.c new file mode 100644 index 000000000..c490f87b2 --- /dev/null +++ b/roms/u-boot/drivers/xen/events.c @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * (C) 2003 - Rolf Neugebauer - Intel Research Cambridge + * (C) 2005 - Grzegorz Milos - Intel Research Cambridge + * (C) 2020 - EPAM Systems Inc. + * + * File: events.c [1] + * Author: Rolf Neugebauer (neugebar@dcs.gla.ac.uk) + * Changes: Grzegorz Milos (gm281@cam.ac.uk) + * + * Date: Jul 2003, changes Jun 2005 + * + * Description: Deals with events received on event channels + * + * [1] - http://xenbits.xen.org/gitweb/?p=mini-os.git;a=summary + */ +#include <common.h> +#include <log.h> + +#include <asm/io.h> +#include <asm/xen/system.h> + +#include <xen/events.h> +#include <xen/hvm.h> + +extern u32 console_evtchn; + +#define NR_EVS 1024 + +/** + * struct _ev_action - represents a event handler. + * + * Chaining or sharing is not allowed + */ +struct _ev_action { + void (*handler)(evtchn_port_t port, struct pt_regs *regs, void *data); + void *data; + u32 count; +}; + +static struct _ev_action ev_actions[NR_EVS]; +void default_handler(evtchn_port_t port, struct pt_regs *regs, void *data); + +static unsigned long bound_ports[NR_EVS / (8 * sizeof(unsigned long))]; + +void unbind_all_ports(void) +{ + int i; + int cpu = 0; + struct shared_info *s = HYPERVISOR_shared_info; + struct vcpu_info *vcpu_info = &s->vcpu_info[cpu]; + + for (i = 0; i < NR_EVS; i++) { + if (i == console_evtchn) + continue; + if (test_and_clear_bit(i, bound_ports)) { + printf("port %d still bound!\n", i); + unbind_evtchn(i); + } + } + vcpu_info->evtchn_upcall_pending = 0; + vcpu_info->evtchn_pending_sel = 0; +} + +int do_event(evtchn_port_t port, struct pt_regs *regs) +{ + struct _ev_action *action; + + clear_evtchn(port); + + if (port >= NR_EVS) { + printk("WARN: do_event(): Port number too large: %d\n", port); + return 1; + } + + action = &ev_actions[port]; + action->count++; + + /* call the handler */ + action->handler(port, regs, action->data); + + return 1; +} + +evtchn_port_t bind_evtchn(evtchn_port_t port, + void (*handler)(evtchn_port_t, struct pt_regs *, void *), + void *data) +{ + if (ev_actions[port].handler != default_handler) + printf("WARN: Handler for port %d already registered, replacing\n", + port); + + ev_actions[port].data = data; + wmb(); + ev_actions[port].handler = handler; + synch_set_bit(port, bound_ports); + + return port; +} + +/** + * unbind_evtchn() - Unbind event channel for selected port + */ +void unbind_evtchn(evtchn_port_t port) +{ + struct evtchn_close close; + int rc; + + if (ev_actions[port].handler == default_handler) + debug("Default handler for port %d when unbinding\n", port); + mask_evtchn(port); + clear_evtchn(port); + + ev_actions[port].handler = default_handler; + wmb(); + ev_actions[port].data = NULL; + synch_clear_bit(port, bound_ports); + + close.port = port; + rc = HYPERVISOR_event_channel_op(EVTCHNOP_close, &close); + if (rc) + printf("WARN: close_port %d failed rc=%d. ignored\n", port, rc); +} + +void default_handler(evtchn_port_t port, struct pt_regs *regs, void *ignore) +{ + debug("[Port %d] - event received\n", port); +} + +/** + * evtchn_alloc_unbound() - Create a port available to the pal for + * exchanging notifications. + * + * Unfortunate confusion of terminology: the port is unbound as far + * as Xen is concerned, but we automatically bind a handler to it. + * + * Return: The result of the hypervisor call. + */ +int evtchn_alloc_unbound(domid_t pal, + void (*handler)(evtchn_port_t, struct pt_regs *, void *), + void *data, evtchn_port_t *port) +{ + int rc; + + struct evtchn_alloc_unbound op; + + op.dom = DOMID_SELF; + op.remote_dom = pal; + rc = HYPERVISOR_event_channel_op(EVTCHNOP_alloc_unbound, &op); + if (rc) { + printf("ERROR: alloc_unbound failed with rc=%d", rc); + return rc; + } + if (!handler) + handler = default_handler; + *port = bind_evtchn(op.port, handler, data); + return rc; +} + +/** + * eventchn_poll() - Event channel polling function + * + * Check and process any pending events + */ +void eventchn_poll(void) +{ + do_hypervisor_callback(NULL); +} + +/** + * init_events() - Initialize event handler + * + * Initially all events are without a handler and disabled. + */ +void init_events(void) +{ + int i; + + debug("%s\n", __func__); + + for (i = 0; i < NR_EVS; i++) { + ev_actions[i].handler = default_handler; + mask_evtchn(i); + } +} + +/** + * fini_events() - Close all ports + * + * Mask and clear event channels. Close port using EVTCHNOP_close + * hypercall. + */ +void fini_events(void) +{ + debug("%s\n", __func__); + /* Dealloc all events */ + unbind_all_ports(); +} + diff --git a/roms/u-boot/drivers/xen/gnttab.c b/roms/u-boot/drivers/xen/gnttab.c new file mode 100644 index 000000000..778729d64 --- /dev/null +++ b/roms/u-boot/drivers/xen/gnttab.c @@ -0,0 +1,218 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * (C) 2006 - Cambridge University + * (C) 2020 - EPAM Systems Inc. + * + * File: gnttab.c [1] + * Author: Steven Smith (sos22@cam.ac.uk) + * Changes: Grzegorz Milos (gm281@cam.ac.uk) + * + * Date: July 2006 + * + * Description: Simple grant tables implementation. About as stupid as it's + * possible to be and still work. + * + * [1] - http://xenbits.xen.org/gitweb/?p=mini-os.git;a=summary + */ +#include <common.h> +#include <asm/global_data.h> +#include <linux/compiler.h> +#include <log.h> +#include <malloc.h> + +#include <asm/armv8/mmu.h> +#include <asm/io.h> +#include <asm/xen/system.h> + +#include <linux/bug.h> + +#include <xen/gnttab.h> +#include <xen/hvm.h> + +#include <xen/interface/memory.h> + +DECLARE_GLOBAL_DATA_PTR; + +#define NR_RESERVED_ENTRIES 8 + +/* NR_GRANT_FRAMES must be less than or equal to that configured in Xen */ +#define NR_GRANT_FRAMES 1 +#define NR_GRANT_ENTRIES (NR_GRANT_FRAMES * PAGE_SIZE / sizeof(struct grant_entry_v1)) + +static struct grant_entry_v1 *gnttab_table; +static grant_ref_t gnttab_list[NR_GRANT_ENTRIES]; + +static void put_free_entry(grant_ref_t ref) +{ + unsigned long flags; + + local_irq_save(flags); + gnttab_list[ref] = gnttab_list[0]; + gnttab_list[0] = ref; + local_irq_restore(flags); +} + +static grant_ref_t get_free_entry(void) +{ + unsigned int ref; + unsigned long flags; + + local_irq_save(flags); + ref = gnttab_list[0]; + BUG_ON(ref < NR_RESERVED_ENTRIES || ref >= NR_GRANT_ENTRIES); + gnttab_list[0] = gnttab_list[ref]; + local_irq_restore(flags); + return ref; +} + +/** + * gnttab_grant_access() - Allow access to the given frame. + * The function creates an entry in the grant table according + * to the specified parameters. + * @domid: the id of the domain for which access is allowed + * @frame: the number of the shared frame + * @readonly: determines whether the frame is shared read-only or read-write + * + * Return: relevant grant reference + */ +grant_ref_t gnttab_grant_access(domid_t domid, unsigned long frame, int readonly) +{ + grant_ref_t ref; + + ref = get_free_entry(); + gnttab_table[ref].frame = frame; + gnttab_table[ref].domid = domid; + wmb(); + readonly *= GTF_readonly; + gnttab_table[ref].flags = GTF_permit_access | readonly; + + return ref; +} + +/** + * gnttab_end_access() - End of memory sharing. The function invalidates + * the entry in the grant table. + */ +int gnttab_end_access(grant_ref_t ref) +{ + u16 flags, nflags; + + BUG_ON(ref >= NR_GRANT_ENTRIES || ref < NR_RESERVED_ENTRIES); + + nflags = gnttab_table[ref].flags; + do { + flags = nflags; + if ((flags) & (GTF_reading | GTF_writing)) { + printf("WARNING: g.e. still in use! (%x)\n", flags); + return 0; + } + } while ((nflags = synch_cmpxchg(&gnttab_table[ref].flags, flags, 0)) != + flags); + + put_free_entry(ref); + return 1; +} + +grant_ref_t gnttab_alloc_and_grant(void **map) +{ + unsigned long mfn; + grant_ref_t gref; + + *map = (void *)memalign(PAGE_SIZE, PAGE_SIZE); + mfn = virt_to_mfn(*map); + gref = gnttab_grant_access(0, mfn, 0); + return gref; +} + +static const char * const gnttabop_error_msgs[] = GNTTABOP_error_msgs; + +const char *gnttabop_error(int16_t status) +{ + status = -status; + if (status < 0 || status >= ARRAY_SIZE(gnttabop_error_msgs)) + return "bad status"; + else + return gnttabop_error_msgs[status]; +} + +/* Get Xen's suggested physical page assignments for the grant table. */ +void get_gnttab_base(phys_addr_t *gnttab_base, phys_size_t *gnttab_sz) +{ + const void *blob = gd->fdt_blob; + struct fdt_resource res; + int mem; + + mem = fdt_node_offset_by_compatible(blob, -1, "xen,xen"); + if (mem < 0) { + printf("No xen,xen compatible found\n"); + BUG(); + } + + mem = fdt_get_resource(blob, mem, "reg", 0, &res); + if (mem == -FDT_ERR_NOTFOUND) { + printf("No grant table base in the device tree\n"); + BUG(); + } + + *gnttab_base = (phys_addr_t)res.start; + if (gnttab_sz) + *gnttab_sz = (phys_size_t)(res.end - res.start + 1); + + debug("FDT suggests grant table base at %llx\n", + *gnttab_base); +} + +void init_gnttab(void) +{ + struct xen_add_to_physmap xatp; + struct gnttab_setup_table setup; + xen_pfn_t frames[NR_GRANT_FRAMES]; + int i, rc; + + debug("%s\n", __func__); + + for (i = NR_RESERVED_ENTRIES; i < NR_GRANT_ENTRIES; i++) + put_free_entry(i); + + get_gnttab_base((phys_addr_t *)&gnttab_table, NULL); + + for (i = 0; i < NR_GRANT_FRAMES; i++) { + xatp.domid = DOMID_SELF; + xatp.size = 0; + xatp.space = XENMAPSPACE_grant_table; + xatp.idx = i; + xatp.gpfn = PFN_DOWN((unsigned long)gnttab_table) + i; + rc = HYPERVISOR_memory_op(XENMEM_add_to_physmap, &xatp); + if (rc) + printf("XENMEM_add_to_physmap failed; status = %d\n", + rc); + BUG_ON(rc != 0); + } + + setup.dom = DOMID_SELF; + setup.nr_frames = NR_GRANT_FRAMES; + set_xen_guest_handle(setup.frame_list, frames); +} + +void fini_gnttab(void) +{ + struct xen_remove_from_physmap xrtp; + struct gnttab_setup_table setup; + int i, rc; + + debug("%s\n", __func__); + + for (i = 0; i < NR_GRANT_FRAMES; i++) { + xrtp.domid = DOMID_SELF; + xrtp.gpfn = PFN_DOWN((unsigned long)gnttab_table) + i; + rc = HYPERVISOR_memory_op(XENMEM_remove_from_physmap, &xrtp); + if (rc) + printf("XENMEM_remove_from_physmap failed; status = %d\n", + rc); + BUG_ON(rc != 0); + } + + setup.dom = DOMID_SELF; + setup.nr_frames = 0; +} + diff --git a/roms/u-boot/drivers/xen/hypervisor.c b/roms/u-boot/drivers/xen/hypervisor.c new file mode 100644 index 000000000..256089483 --- /dev/null +++ b/roms/u-boot/drivers/xen/hypervisor.c @@ -0,0 +1,254 @@ +// SPDX-License-Identifier: MIT License +/* + * hypervisor.c + * + * Communication to/from hypervisor. + * + * Copyright (c) 2002-2003, K A Fraser + * Copyright (c) 2005, Grzegorz Milos, gm281@cam.ac.uk,Intel Research Cambridge + * Copyright (c) 2020, EPAM Systems Inc. + */ +#include <common.h> +#include <cpu_func.h> +#include <log.h> +#include <memalign.h> + +#include <asm/io.h> +#include <asm/armv8/mmu.h> +#include <asm/xen/system.h> + +#include <linux/bug.h> + +#include <xen/hvm.h> +#include <xen/events.h> +#include <xen/gnttab.h> +#include <xen/xenbus.h> +#include <xen/interface/memory.h> + +#define active_evtchns(cpu, sh, idx) \ + ((sh)->evtchn_pending[idx] & \ + ~(sh)->evtchn_mask[idx]) + +int in_callback; + +/* + * Shared page for communicating with the hypervisor. + * Events flags go here, for example. + */ +struct shared_info *HYPERVISOR_shared_info; + +static const char *param_name(int op) +{ +#define PARAM(x)[HVM_PARAM_##x] = #x + static const char *const names[] = { + PARAM(CALLBACK_IRQ), + PARAM(STORE_PFN), + PARAM(STORE_EVTCHN), + PARAM(PAE_ENABLED), + PARAM(IOREQ_PFN), + PARAM(VPT_ALIGN), + PARAM(CONSOLE_PFN), + PARAM(CONSOLE_EVTCHN), + }; +#undef PARAM + + if (op >= ARRAY_SIZE(names)) + return "unknown"; + + if (!names[op]) + return "reserved"; + + return names[op]; +} + +/** + * hvm_get_parameter_maintain_dcache - function to obtain a HVM + * parameter value. + * @idx: HVM parameter index + * @value: Value to fill in + * + * According to Xen on ARM ABI (xen/include/public/arch-arm.h): + * all memory which is shared with other entities in the system + * (including the hypervisor and other guests) must reside in memory + * which is mapped as Normal Inner Write-Back Outer Write-Back + * Inner-Shareable. + * + * Thus, page attributes must be equally set for all the entities + * working with that page. + * + * Before MMU setup the data cache is turned off, so it means that + * manual data cache maintenance is required, because of the + * difference of page attributes. + */ +int hvm_get_parameter_maintain_dcache(int idx, uint64_t *value) +{ + struct xen_hvm_param xhv; + int ret; + + invalidate_dcache_range((unsigned long)&xhv, + (unsigned long)&xhv + sizeof(xhv)); + xhv.domid = DOMID_SELF; + xhv.index = idx; + invalidate_dcache_range((unsigned long)&xhv, + (unsigned long)&xhv + sizeof(xhv)); + + ret = HYPERVISOR_hvm_op(HVMOP_get_param, &xhv); + if (ret < 0) { + pr_err("Cannot get hvm parameter %s (%d): %d!\n", + param_name(idx), idx, ret); + BUG(); + } + invalidate_dcache_range((unsigned long)&xhv, + (unsigned long)&xhv + sizeof(xhv)); + + *value = xhv.value; + + return ret; +} + +int hvm_get_parameter(int idx, uint64_t *value) +{ + struct xen_hvm_param xhv; + int ret; + + xhv.domid = DOMID_SELF; + xhv.index = idx; + ret = HYPERVISOR_hvm_op(HVMOP_get_param, &xhv); + if (ret < 0) { + pr_err("Cannot get hvm parameter %s (%d): %d!\n", + param_name(idx), idx, ret); + BUG(); + } + + *value = xhv.value; + + return ret; +} + +struct shared_info *map_shared_info(void *p) +{ + struct xen_add_to_physmap xatp; + + HYPERVISOR_shared_info = (struct shared_info *)memalign(PAGE_SIZE, + PAGE_SIZE); + if (!HYPERVISOR_shared_info) + BUG(); + + xatp.domid = DOMID_SELF; + xatp.idx = 0; + xatp.space = XENMAPSPACE_shared_info; + xatp.gpfn = virt_to_pfn(HYPERVISOR_shared_info); + if (HYPERVISOR_memory_op(XENMEM_add_to_physmap, &xatp) != 0) + BUG(); + + return HYPERVISOR_shared_info; +} + +void do_hypervisor_callback(struct pt_regs *regs) +{ + unsigned long l1, l2, l1i, l2i; + unsigned int port; + int cpu = 0; + struct shared_info *s = HYPERVISOR_shared_info; + struct vcpu_info *vcpu_info = &s->vcpu_info[cpu]; + + in_callback = 1; + + vcpu_info->evtchn_upcall_pending = 0; + l1 = xchg(&vcpu_info->evtchn_pending_sel, 0); + + while (l1 != 0) { + l1i = __ffs(l1); + l1 &= ~(1UL << l1i); + + while ((l2 = active_evtchns(cpu, s, l1i)) != 0) { + l2i = __ffs(l2); + l2 &= ~(1UL << l2i); + + port = (l1i * (sizeof(unsigned long) * 8)) + l2i; + do_event(port, regs); + } + } + + in_callback = 0; +} + +void force_evtchn_callback(void) +{ +#ifdef XEN_HAVE_PV_UPCALL_MASK + int save; +#endif + struct vcpu_info *vcpu; + + vcpu = &HYPERVISOR_shared_info->vcpu_info[smp_processor_id()]; +#ifdef XEN_HAVE_PV_UPCALL_MASK + save = vcpu->evtchn_upcall_mask; +#endif + + while (vcpu->evtchn_upcall_pending) { +#ifdef XEN_HAVE_PV_UPCALL_MASK + vcpu->evtchn_upcall_mask = 1; +#endif + do_hypervisor_callback(NULL); +#ifdef XEN_HAVE_PV_UPCALL_MASK + vcpu->evtchn_upcall_mask = save; +#endif + }; +} + +void mask_evtchn(uint32_t port) +{ + struct shared_info *s = HYPERVISOR_shared_info; + + synch_set_bit(port, &s->evtchn_mask[0]); +} + +void unmask_evtchn(uint32_t port) +{ + struct shared_info *s = HYPERVISOR_shared_info; + struct vcpu_info *vcpu_info = &s->vcpu_info[smp_processor_id()]; + + synch_clear_bit(port, &s->evtchn_mask[0]); + + /* + * Just like a real IO-APIC we 'lose the interrupt edge' if the + * channel is masked. + */ + if (synch_test_bit(port, &s->evtchn_pending[0]) && + !synch_test_and_set_bit(port / (sizeof(unsigned long) * 8), + &vcpu_info->evtchn_pending_sel)) { + vcpu_info->evtchn_upcall_pending = 1; +#ifdef XEN_HAVE_PV_UPCALL_MASK + if (!vcpu_info->evtchn_upcall_mask) +#endif + force_evtchn_callback(); + } +} + +void clear_evtchn(uint32_t port) +{ + struct shared_info *s = HYPERVISOR_shared_info; + + synch_clear_bit(port, &s->evtchn_pending[0]); +} + +int xen_init(void) +{ + debug("%s\n", __func__); + + map_shared_info(NULL); + init_events(); + init_xenbus(); + init_gnttab(); + + return 0; +} + +void xen_fini(void) +{ + debug("%s\n", __func__); + + fini_gnttab(); + fini_xenbus(); + fini_events(); +} diff --git a/roms/u-boot/drivers/xen/pvblock.c b/roms/u-boot/drivers/xen/pvblock.c new file mode 100644 index 000000000..1c5d039ef --- /dev/null +++ b/roms/u-boot/drivers/xen/pvblock.c @@ -0,0 +1,868 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) 2007-2008 Samuel Thibault. + * (C) Copyright 2020 EPAM Systems Inc. + */ +#include <blk.h> +#include <common.h> +#include <dm.h> +#include <dm/device-internal.h> +#include <malloc.h> +#include <part.h> + +#include <asm/armv8/mmu.h> +#include <asm/global_data.h> +#include <asm/io.h> +#include <asm/xen/system.h> + +#include <linux/bug.h> +#include <linux/compat.h> + +#include <xen/events.h> +#include <xen/gnttab.h> +#include <xen/hvm.h> +#include <xen/xenbus.h> + +#include <xen/interface/io/ring.h> +#include <xen/interface/io/blkif.h> +#include <xen/interface/io/protocols.h> + +#define DRV_NAME "pvblock" +#define DRV_NAME_BLK "pvblock_blk" + +#define O_RDONLY 00 +#define O_RDWR 02 +#define WAIT_RING_TO_MS 10 + +struct blkfront_info { + u64 sectors; + unsigned int sector_size; + int mode; + int info; + int barrier; + int flush; +}; + +/** + * struct blkfront_dev - Struct representing blkfront device + * @dom: Domain id + * @ring: Front_ring structure + * @ring_ref: The grant reference, allowing us to grant access + * to the ring to the other end/domain + * @evtchn: Event channel used to signal ring events + * @handle: Events handle + * @nodename: Device XenStore path in format "device/vbd/" + @devid + * @backend: Backend XenStore path + * @info: Private data + * @devid: Device id + */ +struct blkfront_dev { + domid_t dom; + + struct blkif_front_ring ring; + grant_ref_t ring_ref; + evtchn_port_t evtchn; + blkif_vdev_t handle; + + char *nodename; + char *backend; + struct blkfront_info info; + unsigned int devid; + u8 *bounce_buffer; +}; + +struct blkfront_plat { + unsigned int devid; +}; + +/** + * struct blkfront_aiocb - AIO сontrol block + * @aio_dev: Blockfront device + * @aio_buf: Memory buffer, which must be sector-aligned for + * @aio_dev sector + * @aio_nbytes: Size of AIO, which must be less than @aio_dev + * sector-sized amounts + * @aio_offset: Offset, which must not go beyond @aio_dev + * sector-aligned location + * @data: Data used to receiving response from ring + * @gref: Array of grant references + * @n: Number of segments + * @aio_cb: Represents one I/O request. + */ +struct blkfront_aiocb { + struct blkfront_dev *aio_dev; + u8 *aio_buf; + size_t aio_nbytes; + off_t aio_offset; + void *data; + + grant_ref_t gref[BLKIF_MAX_SEGMENTS_PER_REQUEST]; + int n; + + void (*aio_cb)(struct blkfront_aiocb *aiocb, int ret); +}; + +static void blkfront_sync(struct blkfront_dev *dev); + +static void free_blkfront(struct blkfront_dev *dev) +{ + mask_evtchn(dev->evtchn); + free(dev->backend); + + gnttab_end_access(dev->ring_ref); + free(dev->ring.sring); + + unbind_evtchn(dev->evtchn); + + free(dev->bounce_buffer); + free(dev->nodename); + free(dev); +} + +static int init_blkfront(unsigned int devid, struct blkfront_dev *dev) +{ + xenbus_transaction_t xbt; + char *err = NULL; + char *message = NULL; + struct blkif_sring *s; + int retry = 0; + char *msg = NULL; + char *c; + char nodename[32]; + char path[ARRAY_SIZE(nodename) + strlen("/backend-id") + 1]; + + sprintf(nodename, "device/vbd/%d", devid); + + memset(dev, 0, sizeof(*dev)); + dev->nodename = strdup(nodename); + dev->devid = devid; + + snprintf(path, sizeof(path), "%s/backend-id", nodename); + dev->dom = xenbus_read_integer(path); + evtchn_alloc_unbound(dev->dom, NULL, dev, &dev->evtchn); + + s = (struct blkif_sring *)memalign(PAGE_SIZE, PAGE_SIZE); + if (!s) { + printf("Failed to allocate shared ring\n"); + goto error; + } + + SHARED_RING_INIT(s); + FRONT_RING_INIT(&dev->ring, s, PAGE_SIZE); + + dev->ring_ref = gnttab_grant_access(dev->dom, virt_to_pfn(s), 0); + +again: + err = xenbus_transaction_start(&xbt); + if (err) { + printf("starting transaction\n"); + free(err); + } + + err = xenbus_printf(xbt, nodename, "ring-ref", "%u", dev->ring_ref); + if (err) { + message = "writing ring-ref"; + goto abort_transaction; + } + err = xenbus_printf(xbt, nodename, "event-channel", "%u", dev->evtchn); + if (err) { + message = "writing event-channel"; + goto abort_transaction; + } + err = xenbus_printf(xbt, nodename, "protocol", "%s", + XEN_IO_PROTO_ABI_NATIVE); + if (err) { + message = "writing protocol"; + goto abort_transaction; + } + + snprintf(path, sizeof(path), "%s/state", nodename); + err = xenbus_switch_state(xbt, path, XenbusStateConnected); + if (err) { + message = "switching state"; + goto abort_transaction; + } + + err = xenbus_transaction_end(xbt, 0, &retry); + free(err); + if (retry) { + goto again; + printf("completing transaction\n"); + } + + goto done; + +abort_transaction: + free(err); + err = xenbus_transaction_end(xbt, 1, &retry); + printf("Abort transaction %s\n", message); + goto error; + +done: + snprintf(path, sizeof(path), "%s/backend", nodename); + msg = xenbus_read(XBT_NIL, path, &dev->backend); + if (msg) { + printf("Error %s when reading the backend path %s\n", + msg, path); + goto error; + } + + dev->handle = strtoul(strrchr(nodename, '/') + 1, NULL, 0); + + { + XenbusState state; + char path[strlen(dev->backend) + + strlen("/feature-flush-cache") + 1]; + + snprintf(path, sizeof(path), "%s/mode", dev->backend); + msg = xenbus_read(XBT_NIL, path, &c); + if (msg) { + printf("Error %s when reading the mode\n", msg); + goto error; + } + if (*c == 'w') + dev->info.mode = O_RDWR; + else + dev->info.mode = O_RDONLY; + free(c); + + snprintf(path, sizeof(path), "%s/state", dev->backend); + + msg = NULL; + state = xenbus_read_integer(path); + while (!msg && state < XenbusStateConnected) + msg = xenbus_wait_for_state_change(path, &state); + if (msg || state != XenbusStateConnected) { + printf("backend not available, state=%d\n", state); + goto error; + } + + snprintf(path, sizeof(path), "%s/info", dev->backend); + dev->info.info = xenbus_read_integer(path); + + snprintf(path, sizeof(path), "%s/sectors", dev->backend); + /* + * FIXME: read_integer returns an int, so disk size + * limited to 1TB for now + */ + dev->info.sectors = xenbus_read_integer(path); + + snprintf(path, sizeof(path), "%s/sector-size", dev->backend); + dev->info.sector_size = xenbus_read_integer(path); + + snprintf(path, sizeof(path), "%s/feature-barrier", + dev->backend); + dev->info.barrier = xenbus_read_integer(path); + + snprintf(path, sizeof(path), "%s/feature-flush-cache", + dev->backend); + dev->info.flush = xenbus_read_integer(path); + } + unmask_evtchn(dev->evtchn); + + dev->bounce_buffer = memalign(dev->info.sector_size, + dev->info.sector_size); + if (!dev->bounce_buffer) { + printf("Failed to allocate bouncing buffer\n"); + goto error; + } + + debug("%llu sectors of %u bytes, bounce buffer at %p\n", + dev->info.sectors, dev->info.sector_size, + dev->bounce_buffer); + + return 0; + +error: + free(msg); + free(err); + free_blkfront(dev); + return -ENODEV; +} + +static void shutdown_blkfront(struct blkfront_dev *dev) +{ + char *err = NULL, *err2; + XenbusState state; + + char path[strlen(dev->backend) + strlen("/state") + 1]; + char nodename[strlen(dev->nodename) + strlen("/event-channel") + 1]; + + debug("Close " DRV_NAME ", device ID %d\n", dev->devid); + + blkfront_sync(dev); + + snprintf(path, sizeof(path), "%s/state", dev->backend); + snprintf(nodename, sizeof(nodename), "%s/state", dev->nodename); + + err = xenbus_switch_state(XBT_NIL, nodename, XenbusStateClosing); + if (err) { + printf("%s: error changing state to %d: %s\n", __func__, + XenbusStateClosing, err); + goto close; + } + + state = xenbus_read_integer(path); + while (!err && state < XenbusStateClosing) + err = xenbus_wait_for_state_change(path, &state); + free(err); + + err = xenbus_switch_state(XBT_NIL, nodename, XenbusStateClosed); + if (err) { + printf("%s: error changing state to %d: %s\n", __func__, + XenbusStateClosed, err); + goto close; + } + + state = xenbus_read_integer(path); + while (state < XenbusStateClosed) { + err = xenbus_wait_for_state_change(path, &state); + free(err); + } + + err = xenbus_switch_state(XBT_NIL, nodename, XenbusStateInitialising); + if (err) { + printf("%s: error changing state to %d: %s\n", __func__, + XenbusStateInitialising, err); + goto close; + } + + state = xenbus_read_integer(path); + while (!err && + (state < XenbusStateInitWait || state >= XenbusStateClosed)) + err = xenbus_wait_for_state_change(path, &state); + +close: + free(err); + + snprintf(nodename, sizeof(nodename), "%s/ring-ref", dev->nodename); + err2 = xenbus_rm(XBT_NIL, nodename); + free(err2); + snprintf(nodename, sizeof(nodename), "%s/event-channel", dev->nodename); + err2 = xenbus_rm(XBT_NIL, nodename); + free(err2); + + if (!err) + free_blkfront(dev); +} + +/** + * blkfront_aio_poll() - AIO polling function. + * @dev: Blkfront device + * + * Here we receive response from the ring and check its status. This happens + * until we read all data from the ring. We read the data from consumed pointer + * to the response pointer. Then increase consumed pointer to make it clear that + * the data has been read. + * + * Return: Number of consumed bytes. + */ +static int blkfront_aio_poll(struct blkfront_dev *dev) +{ + RING_IDX rp, cons; + struct blkif_response *rsp; + int more; + int nr_consumed; + +moretodo: + rp = dev->ring.sring->rsp_prod; + rmb(); /* Ensure we see queued responses up to 'rp'. */ + cons = dev->ring.rsp_cons; + + nr_consumed = 0; + while ((cons != rp)) { + struct blkfront_aiocb *aiocbp; + int status; + + rsp = RING_GET_RESPONSE(&dev->ring, cons); + nr_consumed++; + + aiocbp = (void *)(uintptr_t)rsp->id; + status = rsp->status; + + switch (rsp->operation) { + case BLKIF_OP_READ: + case BLKIF_OP_WRITE: + { + int j; + + if (status != BLKIF_RSP_OKAY) + printf("%s error %d on %s at offset %llu, num bytes %llu\n", + rsp->operation == BLKIF_OP_READ ? + "read" : "write", + status, aiocbp->aio_dev->nodename, + (unsigned long long)aiocbp->aio_offset, + (unsigned long long)aiocbp->aio_nbytes); + + for (j = 0; j < aiocbp->n; j++) + gnttab_end_access(aiocbp->gref[j]); + + break; + } + + case BLKIF_OP_WRITE_BARRIER: + if (status != BLKIF_RSP_OKAY) + printf("write barrier error %d\n", status); + break; + case BLKIF_OP_FLUSH_DISKCACHE: + if (status != BLKIF_RSP_OKAY) + printf("flush error %d\n", status); + break; + + default: + printf("unrecognized block operation %d response (status %d)\n", + rsp->operation, status); + break; + } + + dev->ring.rsp_cons = ++cons; + /* Nota: callback frees aiocbp itself */ + if (aiocbp && aiocbp->aio_cb) + aiocbp->aio_cb(aiocbp, status ? -EIO : 0); + if (dev->ring.rsp_cons != cons) + /* We reentered, we must not continue here */ + break; + } + + RING_FINAL_CHECK_FOR_RESPONSES(&dev->ring, more); + if (more) + goto moretodo; + + return nr_consumed; +} + +static void blkfront_wait_slot(struct blkfront_dev *dev) +{ + /* Wait for a slot */ + if (RING_FULL(&dev->ring)) { + while (true) { + blkfront_aio_poll(dev); + if (!RING_FULL(&dev->ring)) + break; + wait_event_timeout(NULL, !RING_FULL(&dev->ring), + WAIT_RING_TO_MS); + } + } +} + +/** + * blkfront_aio_poll() - Issue an aio. + * @aiocbp: AIO control block structure + * @write: Describes is it read or write operation + * 0 - read + * 1 - write + * + * We check whether the AIO parameters meet the requirements of the device. + * Then receive request from ring and define its arguments. After this we + * grant access to the grant references. The last step is notifying about AIO + * via event channel. + */ +static void blkfront_aio(struct blkfront_aiocb *aiocbp, int write) +{ + struct blkfront_dev *dev = aiocbp->aio_dev; + struct blkif_request *req; + RING_IDX i; + int notify; + int n, j; + uintptr_t start, end; + + /* Can't io at non-sector-aligned location */ + BUG_ON(aiocbp->aio_offset & (dev->info.sector_size - 1)); + /* Can't io non-sector-sized amounts */ + BUG_ON(aiocbp->aio_nbytes & (dev->info.sector_size - 1)); + /* Can't io non-sector-aligned buffer */ + BUG_ON(((uintptr_t)aiocbp->aio_buf & (dev->info.sector_size - 1))); + + start = (uintptr_t)aiocbp->aio_buf & PAGE_MASK; + end = ((uintptr_t)aiocbp->aio_buf + aiocbp->aio_nbytes + + PAGE_SIZE - 1) & PAGE_MASK; + n = (end - start) / PAGE_SIZE; + aiocbp->n = n; + + BUG_ON(n > BLKIF_MAX_SEGMENTS_PER_REQUEST); + + blkfront_wait_slot(dev); + i = dev->ring.req_prod_pvt; + req = RING_GET_REQUEST(&dev->ring, i); + + req->operation = write ? BLKIF_OP_WRITE : BLKIF_OP_READ; + req->nr_segments = n; + req->handle = dev->handle; + req->id = (uintptr_t)aiocbp; + req->sector_number = aiocbp->aio_offset / dev->info.sector_size; + + for (j = 0; j < n; j++) { + req->seg[j].first_sect = 0; + req->seg[j].last_sect = PAGE_SIZE / dev->info.sector_size - 1; + } + req->seg[0].first_sect = ((uintptr_t)aiocbp->aio_buf & ~PAGE_MASK) / + dev->info.sector_size; + req->seg[n - 1].last_sect = (((uintptr_t)aiocbp->aio_buf + + aiocbp->aio_nbytes - 1) & ~PAGE_MASK) / dev->info.sector_size; + for (j = 0; j < n; j++) { + uintptr_t data = start + j * PAGE_SIZE; + + if (!write) { + /* Trigger CoW if needed */ + *(char *)(data + (req->seg[j].first_sect * + dev->info.sector_size)) = 0; + barrier(); + } + req->seg[j].gref = gnttab_grant_access(dev->dom, + virt_to_pfn((void *)data), + write); + aiocbp->gref[j] = req->seg[j].gref; + } + + dev->ring.req_prod_pvt = i + 1; + + wmb(); + RING_PUSH_REQUESTS_AND_CHECK_NOTIFY(&dev->ring, notify); + + if (notify) + notify_remote_via_evtchn(dev->evtchn); +} + +static void blkfront_aio_cb(struct blkfront_aiocb *aiocbp, int ret) +{ + aiocbp->data = (void *)1; + aiocbp->aio_cb = NULL; +} + +static void blkfront_io(struct blkfront_aiocb *aiocbp, int write) +{ + aiocbp->aio_cb = blkfront_aio_cb; + blkfront_aio(aiocbp, write); + aiocbp->data = NULL; + + while (true) { + blkfront_aio_poll(aiocbp->aio_dev); + if (aiocbp->data) + break; + cpu_relax(); + } +} + +static void blkfront_push_operation(struct blkfront_dev *dev, u8 op, + uint64_t id) +{ + struct blkif_request *req; + int notify, i; + + blkfront_wait_slot(dev); + i = dev->ring.req_prod_pvt; + req = RING_GET_REQUEST(&dev->ring, i); + req->operation = op; + req->nr_segments = 0; + req->handle = dev->handle; + req->id = id; + req->sector_number = 0; + dev->ring.req_prod_pvt = i + 1; + wmb(); + RING_PUSH_REQUESTS_AND_CHECK_NOTIFY(&dev->ring, notify); + if (notify) + notify_remote_via_evtchn(dev->evtchn); +} + +static void blkfront_sync(struct blkfront_dev *dev) +{ + if (dev->info.mode == O_RDWR) { + if (dev->info.barrier == 1) + blkfront_push_operation(dev, + BLKIF_OP_WRITE_BARRIER, 0); + + if (dev->info.flush == 1) + blkfront_push_operation(dev, + BLKIF_OP_FLUSH_DISKCACHE, 0); + } + + while (true) { + blkfront_aio_poll(dev); + if (RING_FREE_REQUESTS(&dev->ring) == RING_SIZE(&dev->ring)) + break; + cpu_relax(); + } +} + +/** + * pvblock_iop() - Issue an aio. + * @udev: Pvblock device + * @blknr: Block number to read from / write to + * @blkcnt: Amount of blocks to read / write + * @buffer: Memory buffer with data to be read / write + * @write: Describes is it read or write operation + * 0 - read + * 1 - write + * + * Depending on the operation - reading or writing, data is read / written from the + * specified address (@buffer) to the sector (@blknr). + */ +static ulong pvblock_iop(struct udevice *udev, lbaint_t blknr, + lbaint_t blkcnt, void *buffer, int write) +{ + struct blkfront_dev *blk_dev = dev_get_priv(udev); + struct blk_desc *desc = dev_get_uclass_plat(udev); + struct blkfront_aiocb aiocb; + lbaint_t blocks_todo; + bool unaligned; + + if (blkcnt == 0) + return 0; + + if ((blknr + blkcnt) > desc->lba) { + printf(DRV_NAME ": block number 0x" LBAF " exceeds max(0x" LBAF ")\n", + blknr + blkcnt, desc->lba); + return 0; + } + + unaligned = (uintptr_t)buffer & (blk_dev->info.sector_size - 1); + + aiocb.aio_dev = blk_dev; + aiocb.aio_offset = blknr * desc->blksz; + aiocb.aio_cb = NULL; + aiocb.data = NULL; + blocks_todo = blkcnt; + do { + aiocb.aio_buf = unaligned ? blk_dev->bounce_buffer : buffer; + + if (write && unaligned) + memcpy(blk_dev->bounce_buffer, buffer, desc->blksz); + + aiocb.aio_nbytes = unaligned ? desc->blksz : + min((size_t)(BLKIF_MAX_SEGMENTS_PER_REQUEST * PAGE_SIZE), + (size_t)(blocks_todo * desc->blksz)); + + blkfront_io(&aiocb, write); + + if (!write && unaligned) + memcpy(buffer, blk_dev->bounce_buffer, desc->blksz); + + aiocb.aio_offset += aiocb.aio_nbytes; + buffer += aiocb.aio_nbytes; + blocks_todo -= aiocb.aio_nbytes / desc->blksz; + } while (blocks_todo > 0); + + return blkcnt; +} + +ulong pvblock_blk_read(struct udevice *udev, lbaint_t blknr, lbaint_t blkcnt, + void *buffer) +{ + return pvblock_iop(udev, blknr, blkcnt, buffer, 0); +} + +ulong pvblock_blk_write(struct udevice *udev, lbaint_t blknr, lbaint_t blkcnt, + const void *buffer) +{ + return pvblock_iop(udev, blknr, blkcnt, (void *)buffer, 1); +} + +static int pvblock_blk_bind(struct udevice *udev) +{ + struct blk_desc *desc = dev_get_uclass_plat(udev); + int devnum; + + desc->if_type = IF_TYPE_PVBLOCK; + /* + * Initialize the devnum to -ENODEV. This is to make sure that + * blk_next_free_devnum() works as expected, since the default + * value 0 is a valid devnum. + */ + desc->devnum = -ENODEV; + devnum = blk_next_free_devnum(IF_TYPE_PVBLOCK); + if (devnum < 0) + return devnum; + desc->devnum = devnum; + desc->part_type = PART_TYPE_UNKNOWN; + desc->bdev = udev; + + strncpy(desc->vendor, "Xen", sizeof(desc->vendor)); + strncpy(desc->revision, "1", sizeof(desc->revision)); + strncpy(desc->product, "Virtual disk", sizeof(desc->product)); + + return 0; +} + +static int pvblock_blk_probe(struct udevice *udev) +{ + struct blkfront_dev *blk_dev = dev_get_priv(udev); + struct blkfront_plat *plat = dev_get_plat(udev); + struct blk_desc *desc = dev_get_uclass_plat(udev); + int ret, devid; + + devid = plat->devid; + free(plat); + + ret = init_blkfront(devid, blk_dev); + if (ret < 0) + return ret; + + desc->blksz = blk_dev->info.sector_size; + desc->lba = blk_dev->info.sectors; + desc->log2blksz = LOG2(blk_dev->info.sector_size); + + return 0; +} + +static int pvblock_blk_remove(struct udevice *udev) +{ + struct blkfront_dev *blk_dev = dev_get_priv(udev); + + shutdown_blkfront(blk_dev); + return 0; +} + +static const struct blk_ops pvblock_blk_ops = { + .read = pvblock_blk_read, + .write = pvblock_blk_write, +}; + +U_BOOT_DRIVER(pvblock_blk) = { + .name = DRV_NAME_BLK, + .id = UCLASS_BLK, + .ops = &pvblock_blk_ops, + .bind = pvblock_blk_bind, + .probe = pvblock_blk_probe, + .remove = pvblock_blk_remove, + .priv_auto = sizeof(struct blkfront_dev), + .flags = DM_FLAG_OS_PREPARE, +}; + +/******************************************************************************* + * Para-virtual block device class + *******************************************************************************/ + +typedef int (*enum_vbd_callback)(struct udevice *parent, unsigned int devid); + +static int on_new_vbd(struct udevice *parent, unsigned int devid) +{ + struct driver_info info; + struct udevice *udev; + struct blkfront_plat *plat; + int ret; + + debug("New " DRV_NAME_BLK ", device ID %d\n", devid); + + plat = malloc(sizeof(struct blkfront_plat)); + if (!plat) { + printf("Failed to allocate platform data\n"); + return -ENOMEM; + } + + plat->devid = devid; + + info.name = DRV_NAME_BLK; + info.plat = plat; + + ret = device_bind_by_name(parent, false, &info, &udev); + if (ret < 0) { + printf("Failed to bind " DRV_NAME_BLK " to device with ID %d, ret: %d\n", + devid, ret); + free(plat); + } + return ret; +} + +static int xenbus_enumerate_vbd(struct udevice *udev, enum_vbd_callback clb) +{ + char **dirs, *msg; + int i, ret; + + msg = xenbus_ls(XBT_NIL, "device/vbd", &dirs); + if (msg) { + printf("Failed to read device/vbd directory: %s\n", msg); + free(msg); + return -ENODEV; + } + + for (i = 0; dirs[i]; i++) { + int devid; + + sscanf(dirs[i], "%d", &devid); + ret = clb(udev, devid); + if (ret < 0) + goto fail; + + free(dirs[i]); + } + ret = 0; + +fail: + for (; dirs[i]; i++) + free(dirs[i]); + free(dirs); + return ret; +} + +static void print_pvblock_devices(void) +{ + struct udevice *udev; + bool first = true; + const char *class_name; + + class_name = uclass_get_name(UCLASS_PVBLOCK); + for (blk_first_device(IF_TYPE_PVBLOCK, &udev); udev; + blk_next_device(&udev), first = false) { + struct blk_desc *desc = dev_get_uclass_plat(udev); + + if (!first) + puts(", "); + printf("%s: %d", class_name, desc->devnum); + } + printf("\n"); +} + +void pvblock_init(void) +{ + struct driver_info info; + struct udevice *udev; + struct uclass *uc; + int ret; + + /* + * At this point Xen drivers have already initialized, + * so we can instantiate the class driver and enumerate + * virtual block devices. + */ + info.name = DRV_NAME; + ret = device_bind_by_name(gd->dm_root, false, &info, &udev); + if (ret < 0) + printf("Failed to bind " DRV_NAME ", ret: %d\n", ret); + + /* Bootstrap virtual block devices class driver */ + ret = uclass_get(UCLASS_PVBLOCK, &uc); + if (ret) + return; + uclass_foreach_dev_probe(UCLASS_PVBLOCK, udev); + + print_pvblock_devices(); +} + +static int pvblock_probe(struct udevice *udev) +{ + struct uclass *uc; + int ret; + + if (xenbus_enumerate_vbd(udev, on_new_vbd) < 0) + return -ENODEV; + + ret = uclass_get(UCLASS_BLK, &uc); + if (ret) + return ret; + uclass_foreach_dev_probe(UCLASS_BLK, udev) { + if (_ret) + return _ret; + }; + return 0; +} + +U_BOOT_DRIVER(pvblock_drv) = { + .name = DRV_NAME, + .id = UCLASS_PVBLOCK, + .probe = pvblock_probe, +}; + +UCLASS_DRIVER(pvblock) = { + .name = DRV_NAME, + .id = UCLASS_PVBLOCK, +}; diff --git a/roms/u-boot/drivers/xen/xenbus.c b/roms/u-boot/drivers/xen/xenbus.c new file mode 100644 index 000000000..177d14472 --- /dev/null +++ b/roms/u-boot/drivers/xen/xenbus.c @@ -0,0 +1,557 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * (C) 2006 - Cambridge University + * (C) 2020 - EPAM Systems Inc. + * + * File: xenbus.c [1] + * Author: Steven Smith (sos22@cam.ac.uk) + * Changes: Grzegorz Milos (gm281@cam.ac.uk) + * Changes: John D. Ramsdell + * + * Date: Jun 2006, changes Aug 2006 + * + * Description: Minimal implementation of xenbus + * + * [1] - http://xenbits.xen.org/gitweb/?p=mini-os.git;a=summary + */ + +#include <common.h> +#include <log.h> + +#include <asm/armv8/mmu.h> +#include <asm/io.h> +#include <asm/xen/system.h> + +#include <linux/bug.h> +#include <linux/compat.h> + +#include <xen/events.h> +#include <xen/hvm.h> +#include <xen/xenbus.h> + +#include <xen/interface/io/xs_wire.h> + +#define map_frame_virt(v) (v << PAGE_SHIFT) + +#define SCNd16 "d" + +/* Wait for reply time out, ms */ +#define WAIT_XENBUS_TO_MS 5000 +/* Polling time out, ms */ +#define WAIT_XENBUS_POLL_TO_MS 1 + +static struct xenstore_domain_interface *xenstore_buf; + +static char *errmsg(struct xsd_sockmsg *rep); + +u32 xenbus_evtchn; + +struct write_req { + const void *data; + unsigned int len; +}; + +static void memcpy_from_ring(const void *r, void *d, int off, int len) +{ + int c1, c2; + const char *ring = r; + char *dest = d; + + c1 = min(len, XENSTORE_RING_SIZE - off); + c2 = len - c1; + memcpy(dest, ring + off, c1); + memcpy(dest + c1, ring, c2); +} + +/** + * xenbus_get_reply() - Receive reply from xenbus + * @req_reply: reply message structure + * + * Wait for reply message event from the ring and copy received message + * to input xsd_sockmsg structure. Repeat until full reply is + * proceeded. + * + * Return: false - timeout + * true - reply is received + */ +static bool xenbus_get_reply(struct xsd_sockmsg **req_reply) +{ + struct xsd_sockmsg msg; + unsigned int prod = xenstore_buf->rsp_prod; + +again: + if (!wait_event_timeout(NULL, prod != xenstore_buf->rsp_prod, + WAIT_XENBUS_TO_MS)) { + printk("%s: wait_event timeout\n", __func__); + return false; + } + + prod = xenstore_buf->rsp_prod; + if (xenstore_buf->rsp_prod - xenstore_buf->rsp_cons < sizeof(msg)) + goto again; + + rmb(); + memcpy_from_ring(xenstore_buf->rsp, &msg, + MASK_XENSTORE_IDX(xenstore_buf->rsp_cons), + sizeof(msg)); + + if (xenstore_buf->rsp_prod - xenstore_buf->rsp_cons < sizeof(msg) + msg.len) + goto again; + + /* We do not support and expect any Xen bus wathes. */ + BUG_ON(msg.type == XS_WATCH_EVENT); + + *req_reply = malloc(sizeof(msg) + msg.len); + memcpy_from_ring(xenstore_buf->rsp, *req_reply, + MASK_XENSTORE_IDX(xenstore_buf->rsp_cons), + msg.len + sizeof(msg)); + mb(); + xenstore_buf->rsp_cons += msg.len + sizeof(msg); + + wmb(); + notify_remote_via_evtchn(xenbus_evtchn); + return true; +} + +char *xenbus_switch_state(xenbus_transaction_t xbt, const char *path, + XenbusState state) +{ + char *current_state; + char *msg = NULL; + char *msg2 = NULL; + char value[2]; + XenbusState rs; + int xbt_flag = 0; + int retry = 0; + + do { + if (xbt == XBT_NIL) { + msg = xenbus_transaction_start(&xbt); + if (msg) + goto exit; + xbt_flag = 1; + } + + msg = xenbus_read(xbt, path, ¤t_state); + if (msg) + goto exit; + + rs = (XenbusState)(current_state[0] - '0'); + free(current_state); + if (rs == state) { + msg = NULL; + goto exit; + } + + snprintf(value, 2, "%d", state); + msg = xenbus_write(xbt, path, value); + +exit: + if (xbt_flag) { + msg2 = xenbus_transaction_end(xbt, 0, &retry); + xbt = XBT_NIL; + } + if (msg == NULL && msg2 != NULL) + msg = msg2; + else + free(msg2); + } while (retry); + + return msg; +} + +char *xenbus_wait_for_state_change(const char *path, XenbusState *state) +{ + for (;;) { + char *res, *msg; + XenbusState rs; + + msg = xenbus_read(XBT_NIL, path, &res); + if (msg) + return msg; + + rs = (XenbusState)(res[0] - 48); + free(res); + + if (rs == *state) { + wait_event_timeout(NULL, false, WAIT_XENBUS_POLL_TO_MS); + } else { + *state = rs; + break; + } + } + return NULL; +} + +/* Send data to xenbus. This can block. All of the requests are seen + * by xenbus as if sent atomically. The header is added + * automatically, using type %type, req_id %req_id, and trans_id + * %trans_id. + */ +static void xb_write(int type, int req_id, xenbus_transaction_t trans_id, + const struct write_req *req, int nr_reqs) +{ + XENSTORE_RING_IDX prod; + int r; + int len = 0; + const struct write_req *cur_req; + int req_off; + int total_off; + int this_chunk; + struct xsd_sockmsg m = { + .type = type, + .req_id = req_id, + .tx_id = trans_id + }; + struct write_req header_req = { + &m, + sizeof(m) + }; + + for (r = 0; r < nr_reqs; r++) + len += req[r].len; + m.len = len; + len += sizeof(m); + + cur_req = &header_req; + + BUG_ON(len > XENSTORE_RING_SIZE); + prod = xenstore_buf->req_prod; + /* We are running synchronously, so it is a bug if we do not + * have enough room to send a message: please note that a message + * can occupy multiple slots in the ring buffer. + */ + BUG_ON(prod + len - xenstore_buf->req_cons > XENSTORE_RING_SIZE); + + total_off = 0; + req_off = 0; + while (total_off < len) { + this_chunk = min(cur_req->len - req_off, + XENSTORE_RING_SIZE - MASK_XENSTORE_IDX(prod)); + memcpy((char *)xenstore_buf->req + MASK_XENSTORE_IDX(prod), + (char *)cur_req->data + req_off, this_chunk); + prod += this_chunk; + req_off += this_chunk; + total_off += this_chunk; + if (req_off == cur_req->len) { + req_off = 0; + if (cur_req == &header_req) + cur_req = req; + else + cur_req++; + } + } + + BUG_ON(req_off != 0); + BUG_ON(total_off != len); + BUG_ON(prod > xenstore_buf->req_cons + XENSTORE_RING_SIZE); + + /* Remote must see entire message before updating indexes */ + wmb(); + + xenstore_buf->req_prod += len; + + /* Send evtchn to notify remote */ + notify_remote_via_evtchn(xenbus_evtchn); +} + +/* Send a message to xenbus, in the same fashion as xb_write, and + * block waiting for a reply. The reply is malloced and should be + * freed by the caller. + */ +struct xsd_sockmsg *xenbus_msg_reply(int type, + xenbus_transaction_t trans, + struct write_req *io, + int nr_reqs) +{ + struct xsd_sockmsg *rep; + + /* We do not use request identifier which is echoed in daemon's response. */ + xb_write(type, 0, trans, io, nr_reqs); + /* Now wait for the message to arrive. */ + if (!xenbus_get_reply(&rep)) + return NULL; + return rep; +} + +static char *errmsg(struct xsd_sockmsg *rep) +{ + char *res; + + if (!rep) { + char msg[] = "No reply"; + size_t len = strlen(msg) + 1; + + return memcpy(malloc(len), msg, len); + } + if (rep->type != XS_ERROR) + return NULL; + res = malloc(rep->len + 1); + memcpy(res, rep + 1, rep->len); + res[rep->len] = 0; + free(rep); + return res; +} + +/* List the contents of a directory. Returns a malloc()ed array of + * pointers to malloc()ed strings. The array is NULL terminated. May + * block. + */ +char *xenbus_ls(xenbus_transaction_t xbt, const char *pre, char ***contents) +{ + struct xsd_sockmsg *reply, *repmsg; + struct write_req req[] = { { pre, strlen(pre) + 1 } }; + int nr_elems, x, i; + char **res, *msg; + + repmsg = xenbus_msg_reply(XS_DIRECTORY, xbt, req, ARRAY_SIZE(req)); + msg = errmsg(repmsg); + if (msg) { + *contents = NULL; + return msg; + } + reply = repmsg + 1; + for (x = nr_elems = 0; x < repmsg->len; x++) + nr_elems += (((char *)reply)[x] == 0); + res = malloc(sizeof(res[0]) * (nr_elems + 1)); + for (x = i = 0; i < nr_elems; i++) { + int l = strlen((char *)reply + x); + + res[i] = malloc(l + 1); + memcpy(res[i], (char *)reply + x, l + 1); + x += l + 1; + } + res[i] = NULL; + free(repmsg); + *contents = res; + return NULL; +} + +char *xenbus_read(xenbus_transaction_t xbt, const char *path, char **value) +{ + struct write_req req[] = { {path, strlen(path) + 1} }; + struct xsd_sockmsg *rep; + char *res, *msg; + + rep = xenbus_msg_reply(XS_READ, xbt, req, ARRAY_SIZE(req)); + msg = errmsg(rep); + if (msg) { + *value = NULL; + return msg; + } + res = malloc(rep->len + 1); + memcpy(res, rep + 1, rep->len); + res[rep->len] = 0; + free(rep); + *value = res; + return NULL; +} + +char *xenbus_write(xenbus_transaction_t xbt, const char *path, + const char *value) +{ + struct write_req req[] = { + {path, strlen(path) + 1}, + {value, strlen(value)}, + }; + struct xsd_sockmsg *rep; + char *msg; + + rep = xenbus_msg_reply(XS_WRITE, xbt, req, ARRAY_SIZE(req)); + msg = errmsg(rep); + if (msg) + return msg; + free(rep); + return NULL; +} + +char *xenbus_rm(xenbus_transaction_t xbt, const char *path) +{ + struct write_req req[] = { {path, strlen(path) + 1} }; + struct xsd_sockmsg *rep; + char *msg; + + rep = xenbus_msg_reply(XS_RM, xbt, req, ARRAY_SIZE(req)); + msg = errmsg(rep); + if (msg) + return msg; + free(rep); + return NULL; +} + +char *xenbus_get_perms(xenbus_transaction_t xbt, const char *path, char **value) +{ + struct write_req req[] = { {path, strlen(path) + 1} }; + struct xsd_sockmsg *rep; + char *res, *msg; + + rep = xenbus_msg_reply(XS_GET_PERMS, xbt, req, ARRAY_SIZE(req)); + msg = errmsg(rep); + if (msg) { + *value = NULL; + return msg; + } + res = malloc(rep->len + 1); + memcpy(res, rep + 1, rep->len); + res[rep->len] = 0; + free(rep); + *value = res; + return NULL; +} + +#define PERM_MAX_SIZE 32 +char *xenbus_set_perms(xenbus_transaction_t xbt, const char *path, + domid_t dom, char perm) +{ + char value[PERM_MAX_SIZE]; + struct write_req req[] = { + {path, strlen(path) + 1}, + {value, 0}, + }; + struct xsd_sockmsg *rep; + char *msg; + + snprintf(value, PERM_MAX_SIZE, "%c%hu", perm, dom); + req[1].len = strlen(value) + 1; + rep = xenbus_msg_reply(XS_SET_PERMS, xbt, req, ARRAY_SIZE(req)); + msg = errmsg(rep); + if (msg) + return msg; + free(rep); + return NULL; +} + +char *xenbus_transaction_start(xenbus_transaction_t *xbt) +{ + /* Xenstored becomes angry if you send a length 0 message, so just + * shove a nul terminator on the end + */ + struct write_req req = { "", 1}; + struct xsd_sockmsg *rep; + char *err; + + rep = xenbus_msg_reply(XS_TRANSACTION_START, 0, &req, 1); + err = errmsg(rep); + if (err) + return err; + sscanf((char *)(rep + 1), "%lu", xbt); + free(rep); + return NULL; +} + +char *xenbus_transaction_end(xenbus_transaction_t t, int abort, int *retry) +{ + struct xsd_sockmsg *rep; + struct write_req req; + char *err; + + *retry = 0; + + req.data = abort ? "F" : "T"; + req.len = 2; + rep = xenbus_msg_reply(XS_TRANSACTION_END, t, &req, 1); + err = errmsg(rep); + if (err) { + if (!strcmp(err, "EAGAIN")) { + *retry = 1; + free(err); + return NULL; + } else { + return err; + } + } + free(rep); + return NULL; +} + +int xenbus_read_integer(const char *path) +{ + char *res, *buf; + int t; + + res = xenbus_read(XBT_NIL, path, &buf); + if (res) { + printk("Failed to read %s.\n", path); + free(res); + return -1; + } + sscanf(buf, "%d", &t); + free(buf); + return t; +} + +int xenbus_read_uuid(const char *path, unsigned char uuid[16]) +{ + char *res, *buf; + + res = xenbus_read(XBT_NIL, path, &buf); + if (res) { + printk("Failed to read %s.\n", path); + free(res); + return 0; + } + if (strlen(buf) != ((2 * 16) + 4) /* 16 hex bytes and 4 hyphens */ + || sscanf(buf, + "%2hhx%2hhx%2hhx%2hhx-" + "%2hhx%2hhx-" + "%2hhx%2hhx-" + "%2hhx%2hhx-" + "%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx", + uuid, uuid + 1, uuid + 2, uuid + 3, + uuid + 4, uuid + 5, uuid + 6, uuid + 7, + uuid + 8, uuid + 9, uuid + 10, uuid + 11, + uuid + 12, uuid + 13, uuid + 14, uuid + 15) != 16) { + printk("Xenbus path %s value %s is not a uuid!\n", path, buf); + free(buf); + return 0; + } + free(buf); + return 1; +} + +char *xenbus_printf(xenbus_transaction_t xbt, + const char *node, const char *path, + const char *fmt, ...) +{ +#define BUFFER_SIZE 256 + char fullpath[BUFFER_SIZE]; + char val[BUFFER_SIZE]; + va_list args; + + BUG_ON(strlen(node) + strlen(path) + 1 >= BUFFER_SIZE); + sprintf(fullpath, "%s/%s", node, path); + va_start(args, fmt); + vsprintf(val, fmt, args); + va_end(args); + return xenbus_write(xbt, fullpath, val); +} + +domid_t xenbus_get_self_id(void) +{ + char *dom_id; + domid_t ret; + + BUG_ON(xenbus_read(XBT_NIL, "domid", &dom_id)); + sscanf(dom_id, "%"SCNd16, &ret); + + return ret; +} + +void init_xenbus(void) +{ + u64 v; + + debug("%s\n", __func__); + if (hvm_get_parameter(HVM_PARAM_STORE_EVTCHN, &v)) + BUG(); + xenbus_evtchn = v; + + if (hvm_get_parameter(HVM_PARAM_STORE_PFN, &v)) + BUG(); + xenstore_buf = (struct xenstore_domain_interface *)map_frame_virt(v); +} + +void fini_xenbus(void) +{ + debug("%s\n", __func__); +} |