[v2,2/2] Platform: Add support for arm64 platform.

Message ID 20180409022202.16476-3-ge.song@hxt-semitech.com
State New
Headers show
Series
  • Add support for arm64 efi-based platform
Related show

Commit Message

Ge Song April 9, 2018, 2:22 a.m.
Platform: Add a platform file which is suitable for arm64 platform.

Besides, this can applies to x86/x64 platforms which adopt efi as
their underlying firmware.

Signed-off-by: Ge Song <ge.song@hxt-semitech.com>
---
 discover/Makefile.am      |   3 +-
 discover/platform-arm64.c | 744 ++++++++++++++++++++
 2 files changed, 746 insertions(+), 1 deletion(-)

Patch

diff --git a/discover/Makefile.am b/discover/Makefile.am
index 4a6cbd094737..db26c14e0e46 100644
--- a/discover/Makefile.am
+++ b/discover/Makefile.am
@@ -81,7 +81,8 @@  discover_platform_ro_SOURCES = \
 	discover/dt.c \
 	discover/dt.h \
 	discover/hostboot.h \
-	discover/platform-powerpc.c
+	discover/platform-powerpc.c \
+	discover/platform-arm64.c
 
 discover_platform_ro_CPPFLAGS = \
 	$(AM_CPPFLAGS)
diff --git a/discover/platform-arm64.c b/discover/platform-arm64.c
new file mode 100644
index 000000000000..34588d8ffc1f
--- /dev/null
+++ b/discover/platform-arm64.c
@@ -0,0 +1,744 @@ 
+/*
+ *  Defines an arm64 platform, based on the powerpc platform.
+ *
+ *  Actually, this is apply to platforms that adopt efi as its underlying
+ *  firmware(x86/x64/arm64).
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; version 2 of the License.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ *  Copyright (C) 2018 Huaxintong Semiconductor Technology Co.,Ltd. All rights
+ *  reserved.
+ *  Author: Ge Song <ge.song@hxt-semitech.com>
+ */
+#include <errno.h>
+#include <file/file.h>
+#include <limits.h>
+#include <list/list.h>
+#include <log/log.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/fcntl.h>
+#include <sys/statfs.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <types/types.h>
+
+#include "efi/efivar.h"
+#include "ipmi.h"
+#include "platform.h"
+#include "talloc/talloc.h"
+
+#define DEF_ATTR	(EFI_VARIABLE_NON_VOLATILE | \
+	EFI_VARIABLE_RUNTIME_ACCESS | EFI_VARIABLE_BOOTSERVICE_ACCESS)
+static const char *pb_vars_guid = "fb78ab4b-bd43-41a0-99a2-4e74bef9169b";
+static const int ipmi_timeout = 10000; /* milliseconds. */
+
+struct param {
+	char *name;
+	char *value;
+	bool modified;
+	struct list_item list;
+};
+
+struct platform_arm64 {
+	struct list params;
+	struct ipmi *ipmi;
+};
+
+static const char *known_params[] = {
+	"auto-boot?",
+	"network",
+	"timeout",
+	"bootdevs",
+	"language",
+	"debug?",
+	"write?",
+	"snapshots?",
+	"console",
+	"http_proxy",
+	"https_proxy",
+	NULL,
+};
+
+static inline struct platform_arm64 *to_platform_arm64(struct platform *p)
+{
+	return (struct platform_arm64 *)(p->platform_data);
+}
+
+static void parse_nvram(struct platform_arm64 *platform)
+{
+	int i, rc;
+	size_t size;
+	uint32_t attr;
+	uint8_t *data;
+	const char *pb_guid = pb_vars_guid;
+	char *known_param;
+	struct param *param;
+
+	for (i = 0; known_params[i]; i++) {
+		known_param = known_params[i];
+		rc = efi_get_variable(platform, pb_guid, known_param,
+				&data, &size, &attr);
+		if (!rc) {
+			param = talloc(platform, struct param);
+			param->modified = false;
+			param->name = talloc_strdup(platform, known_param);
+			param->value = talloc_strdup(platform, (char *)data);
+			list_add(&platform->params, &param->list);
+		}
+	}
+}
+
+static void write_nvram(struct platform_arm64 *platform)
+{
+	struct param *param;
+	const char *pb_guid = pb_vars_guid;
+	uint32_t attr = DEF_ATTR;
+	size_t data_size;
+
+	list_for_each_entry(&platform->params, param, list) {
+		if (param->modified) {
+			data_size = strlen(param->value) + 1;
+			efi_set_variable(platform, pb_guid, param->name,
+				(uint8_t *)param->value, data_size, attr);
+		}
+	}
+}
+
+static const char *get_param(struct platform_arm64 *platform,
+		const char *name)
+{
+	struct param *param;
+
+	list_for_each_entry(&platform->params, param, list)
+		if (!strcmp(param->name, name))
+			return param->value;
+	return NULL;
+}
+
+static void set_param(struct platform_arm64 *platform, const char *name,
+		const char *value)
+{
+	struct param *param;
+
+	list_for_each_entry(&platform->params, param, list) {
+		if (strcmp(param->name, name))
+			continue;
+
+		if (!strcmp(param->value, value))
+			return;
+
+		talloc_free(param->value);
+		param->value = talloc_strdup(param, value);
+		param->modified = true;
+		return;
+	}
+
+	param = talloc(platform, struct param);
+	param->modified = true;
+	param->name = talloc_strdup(platform, name);
+	param->value = talloc_strdup(platform, value);
+	list_add(&platform->params, &param->list);
+}
+
+static int parse_hwaddr(struct interface_config *ifconf, char *str)
+{
+	int i;
+
+	if (strlen(str) != strlen("00:00:00:00:00:00"))
+		return -1;
+
+	for (i = 0; i < HWADDR_SIZE; i++) {
+		char byte[3], *endp;
+		unsigned long x;
+
+		byte[0] = str[i * 3 + 0];
+		byte[1] = str[i * 3 + 1];
+		byte[2] = '\0';
+
+		x = strtoul(byte, &endp, 16);
+		if (endp != byte + 2)
+			return -1;
+
+		ifconf->hwaddr[i] = x & 0xff;
+	}
+
+	return 0;
+}
+
+static int parse_one_interface_config(struct config *config,
+		char *confstr)
+{
+	struct interface_config *ifconf;
+	char *tok, *saveptr;
+
+	ifconf = talloc_zero(config, struct interface_config);
+
+	if (!confstr || !strlen(confstr))
+		goto out_err;
+
+	/* first token should be the mac address */
+	tok = strtok_r(confstr, ",", &saveptr);
+	if (!tok)
+		goto out_err;
+
+	if (parse_hwaddr(ifconf, tok))
+		goto out_err;
+
+	/* second token is the method */
+	tok = strtok_r(NULL, ",", &saveptr);
+	if (!tok || !strlen(tok) || !strcmp(tok, "ignore")) {
+		ifconf->ignore = true;
+
+	} else if (!strcmp(tok, "dhcp")) {
+		ifconf->method = CONFIG_METHOD_DHCP;
+
+	} else if (!strcmp(tok, "static")) {
+		ifconf->method = CONFIG_METHOD_STATIC;
+
+		/* ip/mask, [optional] gateway, [optional] url */
+		tok = strtok_r(NULL, ",", &saveptr);
+		if (!tok)
+			goto out_err;
+		ifconf->static_config.address =
+			talloc_strdup(ifconf, tok);
+
+		tok = strtok_r(NULL, ",", &saveptr);
+		if (tok) {
+			ifconf->static_config.gateway =
+				talloc_strdup(ifconf, tok);
+		}
+
+		tok = strtok_r(NULL, ",", &saveptr);
+		if (tok) {
+			ifconf->static_config.url =
+				talloc_strdup(ifconf, tok);
+		}
+
+	} else {
+		pb_log("Unknown network configuration method %s\n", tok);
+		goto out_err;
+	}
+
+	config->network.interfaces = talloc_realloc(config,
+			config->network.interfaces,
+			struct interface_config *,
+			++config->network.n_interfaces);
+
+	config->network.interfaces[config->network.n_interfaces - 1] = ifconf;
+
+	return 0;
+out_err:
+	talloc_free(ifconf);
+	return -1;
+}
+
+static int parse_one_dns_config(struct config *config,
+		char *confstr)
+{
+	char *tok, *saveptr = NULL;
+
+	for (tok = strtok_r(confstr, ",", &saveptr); tok;
+			tok = strtok_r(NULL, ",", &saveptr)) {
+
+		char *server = talloc_strdup(config, tok);
+
+		config->network.dns_servers = talloc_realloc(config,
+				config->network.dns_servers, const char *,
+				++config->network.n_dns_servers);
+
+		config->network.dns_servers[config->network.n_dns_servers - 1]
+				= server;
+	}
+
+	return 0;
+}
+
+static void populate_network_config(struct platform_arm64 *platform,
+		struct config *config)
+{
+	char *val, *saveptr = NULL;
+	const char *cval;
+	int i;
+
+	cval = get_param(platform, "network");
+	if (!cval || !strlen(cval))
+		return;
+
+	val = talloc_strdup(config, cval);
+
+	for (i = 0; ; i++) {
+		char *tok;
+
+		tok = strtok_r(i == 0 ? val : NULL, " ", &saveptr);
+		if (!tok)
+			break;
+
+		if (!strncasecmp(tok, "dns,", strlen("dns,")))
+			parse_one_dns_config(config, tok + strlen("dns,"));
+		else
+			parse_one_interface_config(config, tok);
+
+	}
+
+	talloc_free(val);
+}
+
+static int read_bootdev(void *ctx, char **pos, struct autoboot_option *opt)
+{
+	char *delim = strchr(*pos, ' ');
+	int len, prefix = 0, rc = -1;
+	enum device_type type;
+
+	if (!strncmp(*pos, "uuid:", strlen("uuid:"))) {
+		prefix = strlen("uuid:");
+		opt->boot_type = BOOT_DEVICE_UUID;
+	} else if (!strncmp(*pos, "mac:", strlen("mac:"))) {
+		prefix = strlen("mac:");
+		opt->boot_type = BOOT_DEVICE_UUID;
+	} else {
+		type = find_device_type(*pos);
+		if (type != DEVICE_TYPE_UNKNOWN) {
+			opt->type = type;
+			opt->boot_type = BOOT_DEVICE_TYPE;
+			rc = 0;
+		}
+	}
+
+	if (opt->boot_type == BOOT_DEVICE_UUID) {
+		if (delim)
+			len = (int)(delim - *pos) - prefix;
+		else
+			len = strlen(*pos) - prefix;
+
+		if (len) {
+			opt->uuid = talloc_strndup(ctx, *pos + prefix, len);
+			rc = 0;
+		}
+	}
+
+	/* Always advance pointer to next option or end */
+	if (delim)
+		*pos = delim + 1;
+	else
+		*pos += strlen(*pos);
+
+	return rc;
+}
+
+static void populate_bootdev_config(struct platform_arm64 *platform,
+		struct config *config)
+{
+	struct autoboot_option *opt, *new = NULL;
+	char *pos, *end;
+	unsigned int n_new = 0;
+	const char *val;
+
+	/* Check for ordered bootdevs */
+	val = get_param(platform, "bootdevs");
+	if (!val || !strlen(val)) {
+		pos = end = NULL;
+	} else {
+		pos = talloc_strdup(config, val);
+		end = strchr(pos, '\0');
+	}
+
+	while (pos && pos < end) {
+		opt = talloc(config, struct autoboot_option);
+
+		if (read_bootdev(config, &pos, opt)) {
+			pb_log("bootdev config is in an unknown format "
+			       "(expected uuid:... or mac:...)\n");
+			talloc_free(opt);
+			continue;
+		}
+
+		new = talloc_realloc(config, new, struct autoboot_option,
+				     n_new + 1);
+		new[n_new] = *opt;
+		n_new++;
+		talloc_free(opt);
+
+	}
+
+	if (!n_new) {
+		/* If autoboot has been disabled, clear the default options */
+		if (!config->autoboot_enabled) {
+			talloc_free(config->autoboot_opts);
+			config->n_autoboot_opts = 0;
+		}
+		return;
+	}
+
+	talloc_free(config->autoboot_opts);
+	config->autoboot_opts = new;
+	config->n_autoboot_opts = n_new;
+}
+
+static void populate_config(struct platform_arm64 *platform,
+		struct config *config)
+{
+	const char *val;
+	char *end;
+	unsigned long timeout;
+
+	/* if the "auto-boot?' property is present and "false", disable auto
+	 * boot */
+	val = get_param(platform, "auto-boot?");
+	config->autoboot_enabled = !val || strcmp(val, "false");
+
+	val = get_param(platform, "timeout");
+	if (val) {
+		timeout = strtoul(val, &end, 10);
+		if (end != val) {
+			if (timeout >= INT_MAX)
+				timeout = INT_MAX;
+			config->autoboot_timeout_sec = (int)timeout;
+		}
+	}
+
+	val = get_param(platform, "language");
+	config->lang = val ? talloc_strdup(config, val) : NULL;
+
+	populate_network_config(platform, config);
+
+	populate_bootdev_config(platform, config);
+
+	if (!config->debug) {
+		val = get_param(platform, "debug?");
+		config->debug = val && !strcmp(val, "true");
+	}
+
+	val = get_param(platform, "write?");
+	if (val)
+		config->allow_writes = !strcmp(val, "true");
+
+	val = get_param(platform, "snapshots?");
+	if (val)
+		config->disable_snapshots = !strcmp(val, "false");
+
+	val = get_param(platform, "console");
+	if (val)
+		config->boot_console = talloc_strdup(config, val);
+	/* If a full path is already set we don't want to override it */
+	config->manual_console = config->boot_console &&
+					!strchr(config->boot_console, '[');
+
+	val = get_param(platform, "http_proxy");
+	if (val)
+		config->http_proxy = talloc_strdup(config, val);
+	val = get_param(platform, "https_proxy");
+	if (val)
+		config->https_proxy = talloc_strdup(config, val);
+}
+
+static char *iface_config_str(void *ctx, struct interface_config *config)
+{
+	char *str;
+
+	/* todo: HWADDR size is hardcoded as 6, but we may need to handle
+	 * different hardware address formats */
+	str = talloc_asprintf(ctx, "%02x:%02x:%02x:%02x:%02x:%02x,",
+			config->hwaddr[0], config->hwaddr[1],
+			config->hwaddr[2], config->hwaddr[3],
+			config->hwaddr[4], config->hwaddr[5]);
+
+	if (config->ignore) {
+		str = talloc_asprintf_append(str, "ignore");
+
+	} else if (config->method == CONFIG_METHOD_DHCP) {
+		str = talloc_asprintf_append(str, "dhcp");
+
+	} else if (config->method == CONFIG_METHOD_STATIC) {
+		str = talloc_asprintf_append(str, "static,%s%s%s%s%s",
+				config->static_config.address,
+				config->static_config.gateway ? "," : "",
+				config->static_config.gateway ?: "",
+				config->static_config.url ? "," : "",
+				config->static_config.url ?: "");
+	}
+	return str;
+}
+
+static char *dns_config_str(void *ctx, const char **dns_servers, int n)
+{
+	char *str;
+	int i;
+
+	str = talloc_strdup(ctx, "dns,");
+	for (i = 0; i < n; i++) {
+		str = talloc_asprintf_append(str, "%s%s",
+				i == 0 ? "" : ",",
+				dns_servers[i]);
+	}
+
+	return str;
+}
+
+static void update_string_config(struct platform_arm64 *platform,
+		const char *name, const char *value)
+{
+	const char *cur;
+
+	cur = get_param(platform, name);
+
+	/* don't set an empty parameter if it doesn't already exist */
+	if (!cur && !strlen(value))
+		return;
+
+	set_param(platform, name, value);
+}
+
+static void update_network_config(struct platform_arm64 *platform,
+	struct config *config)
+{
+	unsigned int i;
+	char *val;
+
+	/*
+	 * Don't store IPMI overrides to NVRAM. If this was a persistent
+	 * override it was already stored in NVRAM by
+	 * get_ipmi_network_override()
+	 */
+	if (config->network.n_interfaces &&
+		config->network.interfaces[0]->override)
+		return;
+
+	val = talloc_strdup(platform, "");
+
+	for (i = 0; i < config->network.n_interfaces; i++) {
+		char *iface_str = iface_config_str(platform,
+					config->network.interfaces[i]);
+		val = talloc_asprintf_append(val, "%s%s",
+				*val == '\0' ? "" : " ", iface_str);
+		talloc_free(iface_str);
+	}
+
+	if (config->network.n_dns_servers) {
+		char *dns_str = dns_config_str(platform,
+						config->network.dns_servers,
+						config->network.n_dns_servers);
+		val = talloc_asprintf_append(val, "%s%s",
+				*val == '\0' ? "" : " ", dns_str);
+		talloc_free(dns_str);
+	}
+
+	update_string_config(platform, "network", val);
+
+	talloc_free(val);
+}
+
+static void update_bootdev_config(struct platform_arm64 *platform,
+		struct config *config)
+{
+	char *val = NULL, *boot_str = NULL, *tmp = NULL;
+	struct autoboot_option *opt;
+	const char delim = ' ';
+	unsigned int i;
+
+	if (!config->n_autoboot_opts)
+		val = "";
+
+	for (i = 0; i < config->n_autoboot_opts; i++) {
+		opt = &config->autoboot_opts[i];
+		switch (opt->boot_type) {
+			case BOOT_DEVICE_TYPE:
+				boot_str = talloc_asprintf(config, "%s%c",
+						device_type_name(opt->type),
+						delim);
+				break;
+			case BOOT_DEVICE_UUID:
+				boot_str = talloc_asprintf(config, "uuid:%s%c",
+						opt->uuid, delim);
+				break;
+			}
+			tmp = val = talloc_asprintf_append(val, "%s", boot_str);
+	}
+
+	update_string_config(platform, "bootdevs", val);
+	talloc_free(tmp);
+	if (boot_str)
+		talloc_free(boot_str);
+}
+
+static void update_config(struct platform_arm64 *platform,
+		struct config *config, struct config *defaults)
+{
+	char *tmp = NULL;
+	const char *val;
+
+	if (config->autoboot_enabled == defaults->autoboot_enabled)
+		val = "";
+	else
+		val = config->autoboot_enabled ? "true" : "false";
+	update_string_config(platform, "auto-boot?", val);
+
+	if (config->autoboot_timeout_sec == defaults->autoboot_timeout_sec)
+		val = "";
+	else
+		val = tmp = talloc_asprintf(platform, "%d",
+				config->autoboot_timeout_sec);
+
+	update_string_config(platform, "timeout", val);
+	if (tmp)
+		talloc_free(tmp);
+
+	val = config->lang ?: "";
+	update_string_config(platform, "language", val);
+
+	if (config->allow_writes == defaults->allow_writes)
+		val = "";
+	else
+		val = config->allow_writes ? "true" : "false";
+	update_string_config(platform, "write?", val);
+
+	if (!config->manual_console) {
+		val = config->boot_console ?: "";
+		update_string_config(platform, "console", val);
+	}
+
+	val = config->http_proxy ?: "";
+	update_string_config(platform, "http_proxy", val);
+	val = config->https_proxy ?: "";
+	update_string_config(platform, "https_proxy", val);
+
+	update_network_config(platform, config);
+
+	update_bootdev_config(platform, config);
+
+	write_nvram(platform);
+}
+
+static void get_ipmi_bmc_mac(struct platform *p, uint8_t *buf)
+{
+	struct platform_arm64 *platform = p->platform_data;
+	uint16_t resp_len = 8;
+	uint8_t resp[8];
+	uint8_t req[] = { 0x1, 0x5, 0x0, 0x0 };
+	int i, rc;
+
+	rc = ipmi_transaction(platform->ipmi, IPMI_NETFN_TRANSPORT,
+			IPMI_CMD_TRANSPORT_GET_LAN_PARAMS,
+			req, sizeof(req),
+			resp, &resp_len,
+			ipmi_timeout);
+
+	pb_debug("BMC MAC resp [%d][%d]:\n", rc, resp_len);
+
+	if (rc == 0 && resp_len > 0) {
+		for (i = 2; i < resp_len; i++) {
+		        pb_debug(" %x", resp[i]);
+			buf[i - 2] = resp[i];
+		}
+		pb_debug("\n");
+	}
+
+}
+
+static void get_active_consoles(struct config *config)
+{
+	config->n_consoles = 3;
+	config->consoles = talloc_array(config, char *, config->n_consoles);
+	if (!config->consoles)
+		goto err;
+
+	config->consoles[0] = talloc_asprintf(config->consoles,
+					"/dev/hvc0 [IPMI / Serial]");
+	config->consoles[1] = talloc_asprintf(config->consoles,
+					"/dev/ttyAMA0 [Serial]");
+	config->consoles[2] = talloc_asprintf(config->consoles,
+					"/dev/tty1 [VGA]");
+
+	return;
+err:
+	config->n_consoles = 0;
+	pb_log("Failed to allocate memory for consoles\n");
+}
+
+static int load_config(struct platform *p, struct config *config)
+{
+	struct platform_arm64 *platform = to_platform_arm64(p);
+
+	parse_nvram(platform);
+
+	populate_config(platform, config);
+
+	get_active_consoles(config);
+
+	return 0;
+}
+
+static int save_config(struct platform *p, struct config *config)
+{
+	struct platform_arm64 *platform = to_platform_arm64(p);
+	struct config *defaults;
+
+	defaults = talloc_zero(platform, struct config);
+	config_set_defaults(defaults);
+
+	update_config(platform, config, defaults);
+
+	talloc_free(defaults);
+
+	return 0;
+}
+
+static int get_sysinfo(struct platform *p, struct system_info *sysinfo)
+{
+	struct platform_arm64 *platform = p->platform_data;
+
+	sysinfo->bmc_mac = talloc_zero_size(sysinfo, HWADDR_SIZE);
+
+	if (platform->ipmi)
+		get_ipmi_bmc_mac(p, sysinfo->bmc_mac);
+
+	return 0;
+}
+
+static bool probe(struct platform *p, void *ctx)
+{
+	struct platform_arm64 *platform;
+	const char *path = "/sys/firmware/efi/efivars/";
+	int rc = 0;
+	struct statfs buf;
+
+	if (access(path, F_OK))
+		return false;
+
+	memset(&buf, '\0', sizeof(buf));
+	rc = statfs(path, &buf);
+	if (rc)
+		return false;
+
+	typeof(buf.f_type) magic = EFIVARFS_MAGIC;
+	if (!memcmp(&buf.f_type, &magic, sizeof (magic))) {
+		platform = talloc_zero(ctx, struct platform_arm64);
+		list_init(&platform->params);
+		p->platform_data = platform;
+		return true;
+	}
+
+	return false;
+}
+
+static struct platform platform_arm64 = {
+	.name			= "arm64",
+	.dhcp_arch_id		= 0x000d,
+	.probe			= probe,
+	.load_config		= load_config,
+	.save_config		= save_config,
+	.get_sysinfo		= get_sysinfo,
+};
+
+register_platform(platform_arm64);