[PATCH 10/17] xen: Port Xen bus driver from mini-os
Anastasiia Lukianenko
vicooodin at gmail.com
Wed Jul 1 18:29:52 CEST 2020
From: Oleksandr Andrushchenko <oleksandr_andrushchenko at epam.com>
Make required updates to run on u-boot and strip test code.
Signed-off-by: Anastasiia Lukianenko <anastasiia_lukianenko at epam.com>
Signed-off-by: Oleksandr Andrushchenko <oleksandr_andrushchenko at epam.com>
---
arch/arm/Kconfig | 1 +
board/xen/xenguest_arm64/xenguest_arm64.c | 16 +-
drivers/xen/Makefile | 1 +
drivers/xen/hypervisor.c | 2 +
drivers/xen/xenbus.c | 547 ++++++++++++++++++++++
include/xen/xenbus.h | 86 ++++
6 files changed, 652 insertions(+), 1 deletion(-)
create mode 100644 drivers/xen/xenbus.c
create mode 100644 include/xen/xenbus.h
diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
index d4de1139aa..bcd9ab5c9d 100644
--- a/arch/arm/Kconfig
+++ b/arch/arm/Kconfig
@@ -1724,6 +1724,7 @@ config TARGET_XENGUEST_ARM64
select OF_CONTROL
select LINUX_KERNEL_IMAGE_HEADER
select XEN_SERIAL
+ select SSCANF
endchoice
config ARCH_SUPPORT_TFABOOT
diff --git a/board/xen/xenguest_arm64/xenguest_arm64.c b/board/xen/xenguest_arm64/xenguest_arm64.c
index fd10a002e9..e8621f7174 100644
--- a/board/xen/xenguest_arm64/xenguest_arm64.c
+++ b/board/xen/xenguest_arm64/xenguest_arm64.c
@@ -67,7 +67,7 @@ static int setup_mem_map(void)
/*
* Add "magic" region which is used by Xen to provide some essentials
- * for the guest: we need console.
+ * for the guest: we need console and xenstore.
*/
ret = hvm_get_parameter_maintain_dcache(HVM_PARAM_CONSOLE_PFN, &gfn);
if (ret < 0) {
@@ -83,6 +83,20 @@ static int setup_mem_map(void)
PTE_BLOCK_INNER_SHARE);
i++;
+ ret = hvm_get_parameter_maintain_dcache(HVM_PARAM_STORE_PFN, &gfn);
+ if (ret < 0) {
+ printf("%s: Can't get HVM_PARAM_STORE_PFN, ret %d\n",
+ __func__, ret);
+ return -EINVAL;
+ }
+
+ xen_mem_map[i].virt = PFN_PHYS(gfn);
+ xen_mem_map[i].phys = PFN_PHYS(gfn);
+ xen_mem_map[i].size = PAGE_SIZE;
+ xen_mem_map[i].attrs = (PTE_BLOCK_MEMTYPE(MT_NORMAL) |
+ PTE_BLOCK_INNER_SHARE);
+ i++;
+
mem = get_next_memory_node(blob, -1);
if (mem < 0) {
printf("%s: Missing /memory node\n", __func__);
diff --git a/drivers/xen/Makefile b/drivers/xen/Makefile
index 0ad35edefb..9d0f604aaa 100644
--- a/drivers/xen/Makefile
+++ b/drivers/xen/Makefile
@@ -4,3 +4,4 @@
obj-y += hypervisor.o
obj-y += events.o
+obj-y += xenbus.o
diff --git a/drivers/xen/hypervisor.c b/drivers/xen/hypervisor.c
index 975e552242..d7fbacb08e 100644
--- a/drivers/xen/hypervisor.c
+++ b/drivers/xen/hypervisor.c
@@ -38,6 +38,7 @@
#include <xen/hvm.h>
#include <xen/events.h>
+#include <xen/xenbus.h>
#include <xen/interface/memory.h>
#define active_evtchns(cpu, sh, idx) \
@@ -273,5 +274,6 @@ void xen_init(void)
map_shared_info(NULL);
init_events();
+ init_xenbus();
}
diff --git a/drivers/xen/xenbus.c b/drivers/xen/xenbus.c
new file mode 100644
index 0000000000..64eb28e843
--- /dev/null
+++ b/drivers/xen/xenbus.c
@@ -0,0 +1,547 @@
+/*
+ ****************************************************************************
+ * (C) 2006 - Cambridge University
+ * (C) 2020 - EPAM Systems Inc.
+ ****************************************************************************
+ *
+ * File: xenbus.c
+ * Author: Steven Smith (sos22 at cam.ac.uk)
+ * Changes: Grzegorz Milos (gm281 at cam.ac.uk)
+ * Changes: John D. Ramsdell
+ *
+ * Date: Jun 2006, chages Aug 2005
+ *
+ * Environment: Xen Minimal OS
+ * Description: Minimal implementation of xenbus
+ *
+ ****************************************************************************
+ **/
+
+#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);
+}
+
+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__);
+}
diff --git a/include/xen/xenbus.h b/include/xen/xenbus.h
new file mode 100644
index 0000000000..e2e3ef9292
--- /dev/null
+++ b/include/xen/xenbus.h
@@ -0,0 +1,86 @@
+#ifndef XENBUS_H__
+#define XENBUS_H__
+
+#include <xen/interface/xen.h>
+#include <xen/interface/io/xenbus.h>
+
+typedef unsigned long xenbus_transaction_t;
+#define XBT_NIL ((xenbus_transaction_t)0)
+
+extern u32 xenbus_evtchn;
+
+/* Initialize the XenBus system. */
+void init_xenbus(void);
+/* Finalize the XenBus system. */
+void fini_xenbus(void);
+
+/* Read the value associated with a path. Returns a malloc'd error
+ * string on failure and sets *value to NULL. On success, *value is
+ * set to a malloc'd copy of the value.
+ */
+char *xenbus_read(xenbus_transaction_t xbt, const char *path, char **value);
+
+char *xenbus_wait_for_state_change(const char *path, XenbusState *state);
+char *xenbus_switch_state(xenbus_transaction_t xbt, const char *path,
+ XenbusState state);
+
+/* Associates a value with a path. Returns a malloc'd error string on
+ * failure.
+ */
+char *xenbus_write(xenbus_transaction_t xbt, const char *path,
+ const char *value);
+
+/* Removes the value associated with a path. Returns a malloc'd error
+ * string on failure.
+ */
+char *xenbus_rm(xenbus_transaction_t xbt, const char *path);
+
+/* List the contents of a directory. Returns a malloc'd error string
+ * on failure and sets *contents to NULL. On success, *contents is
+ * set to a malloc'd array of pointers to malloc'd strings. The array
+ * is NULL terminated. May block.
+ */
+char *xenbus_ls(xenbus_transaction_t xbt, const char *prefix, char ***contents);
+
+/* Reads permissions associated with a path. Returns a malloc'd error
+ * string on failure and sets *value to NULL. On success, *value is
+ * set to a malloc'd copy of the value.
+ */
+char *xenbus_get_perms(xenbus_transaction_t xbt, const char *path, char **value);
+
+/* Sets the permissions associated with a path. Returns a malloc'd
+ * error string on failure.
+ */
+char *xenbus_set_perms(xenbus_transaction_t xbt, const char *path, domid_t dom,
+ char perm);
+
+/* Start a xenbus transaction. Returns the transaction in xbt on
+ * success or a malloc'd error string otherwise.
+ */
+char *xenbus_transaction_start(xenbus_transaction_t *xbt);
+
+/* End a xenbus transaction. Returns a malloc'd error string if it
+ * fails. abort says whether the transaction should be aborted.
+ * Returns 1 in *retry iff the transaction should be retried.
+ */
+char *xenbus_transaction_end(xenbus_transaction_t, int abort,
+ int *retry);
+
+/* Read path and parse it as an integer. Returns -1 on error. */
+int xenbus_read_integer(const char *path);
+
+/* Read path and parse it as 16 byte uuid. Returns 1 if
+ * read and parsing were successful, 0 if not
+ */
+int xenbus_read_uuid(const char *path, unsigned char uuid[16]);
+
+/* Contraction of snprintf and xenbus_write(path/node). */
+char *xenbus_printf(xenbus_transaction_t xbt,
+ const char *node, const char *path,
+ const char *fmt, ...)
+ __attribute__((__format__(printf, 4, 5)));
+
+/* Utility function to figure out our domain id */
+domid_t xenbus_get_self_id(void);
+
+#endif /* XENBUS_H__ */
--
2.17.1
More information about the U-Boot
mailing list