From 988b360c98ef157e37818f5f7db322c129d3dfb9 Mon Sep 17 00:00:00 2001 From: Shravan Kumar Ramani Date: Wed, 6 Jul 2022 07:37:22 -0400 Subject: [PATCH backport 5.10 12/63] UBUNTU: SAUCE: platform/mellanox: Updates to mlxbf-bootctl BugLink: https://launchpad.net/bugs/1980832 The driver supports the VPD fields in the EEPROM and exposes sysfs files for configuring and reading the same. Also address buffer overflow and exclusion issues. Signed-off-by: Shravan Kumar Ramani Signed-off-by: Ike Panhc --- drivers/platform/mellanox/mlxbf-bootctl.c | 1410 ++++++++++++++++++--- drivers/platform/mellanox/mlxbf-bootctl.h | 88 +- 2 files changed, 1268 insertions(+), 230 deletions(-) diff --git a/drivers/platform/mellanox/mlxbf-bootctl.c b/drivers/platform/mellanox/mlxbf-bootctl.c index 1c7a288b5..2302e1e09 100644 --- a/drivers/platform/mellanox/mlxbf-bootctl.c +++ b/drivers/platform/mellanox/mlxbf-bootctl.c @@ -1,51 +1,129 @@ -// SPDX-License-Identifier: GPL-2.0+ +// SPDX-License-Identifier: GPL-2.0 or BSD-3-Clause /* - * Mellanox boot control driver + * Mellanox boot control driver + * This driver provides a sysfs interface for systems management + * software to manage reset-time actions. * - * This driver provides a sysfs interface for systems management - * software to manage reset-time actions. + * Copyright (C) 2020 Mellanox Technologies. All rights reserved. * - * Copyright (C) 2019 Mellanox Technologies + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License v2.0 as published by + * the Free Software Foundation. + * + * 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. */ #include #include +#include +#include #include #include - #include "mlxbf-bootctl.h" -#define MLXBF_BOOTCTL_SB_SECURE_MASK 0x03 -#define MLXBF_BOOTCTL_SB_TEST_MASK 0x0c +#define DRIVER_NAME "mlxbf-bootctl" +#define DRIVER_VERSION "1.5" +#define DRIVER_DESCRIPTION "Mellanox boot control driver" + +#define SB_MODE_SECURE_MASK 0x03 +#define SB_MODE_TEST_MASK 0x0c +#define SB_MODE_DEV_MASK 0x10 -#define MLXBF_SB_KEY_NUM 4 +#define SB_KEY_NUM 4 + +struct boot_name { + int value; + const char name[12]; +}; -/* UUID used to probe ATF service. */ -static const char *mlxbf_bootctl_svc_uuid_str = - "89c036b4-e7d7-11e6-8797-001aca00bfc4"; +static struct boot_name boot_names[] = { + { MLNX_BOOT_EXTERNAL, "external" }, + { MLNX_BOOT_EMMC, "emmc" }, + { MLNX_BOOT_SWAP_EMMC, "swap_emmc" }, + { MLNX_BOOT_EMMC_LEGACY, "emmc_legacy" }, + { MLNX_BOOT_NONE, "none" }, + { -1, "" } +}; -struct mlxbf_bootctl_name { - u32 value; - const char *name; +enum { + SB_LIFECYCLE_PRODUCTION = 0, + SB_LIFECYCLE_GA_SECURE = 1, + SB_LIFECYCLE_GA_NON_SECURE = 2, + SB_LIFECYCLE_RMA = 3 }; -static struct mlxbf_bootctl_name boot_names[] = { - { MLXBF_BOOTCTL_EXTERNAL, "external" }, - { MLXBF_BOOTCTL_EMMC, "emmc" }, - { MLNX_BOOTCTL_SWAP_EMMC, "swap_emmc" }, - { MLXBF_BOOTCTL_EMMC_LEGACY, "emmc_legacy" }, - { MLXBF_BOOTCTL_NONE, "none" }, +static char lifecycle_states[][16] = { + [SB_LIFECYCLE_PRODUCTION] = "Production", + [SB_LIFECYCLE_GA_SECURE] = "GA Secured", + [SB_LIFECYCLE_GA_NON_SECURE] = "GA Non-Secured", + [SB_LIFECYCLE_RMA] = "RMA", }; -static const char * const mlxbf_bootctl_lifecycle_states[] = { - [0] = "Production", - [1] = "GA Secured", - [2] = "GA Non-Secured", - [3] = "RMA", +/* ctl/data register within the resource. */ +#define RSH_SCRATCH_BUF_CTL_OFF 0 +#define RSH_SCRATCH_BUF_DATA_OFF 0x10 + +static void __iomem *rsh_boot_data; +static void __iomem *rsh_boot_cnt; +static void __iomem *rsh_semaphore; +static void __iomem *rsh_scratch_buf_ctl; +static void __iomem *rsh_scratch_buf_data; + +static int rsh_log_clear_on_read; +module_param(rsh_log_clear_on_read, int, 0644); +MODULE_PARM_DESC(rsh_log_clear_on_read, "Clear rshim logging buffer after read."); + +/* + * Objects are stored within the MFG partition per type. Type 0 is not + * supported. + */ +enum { + MLNX_MFG_TYPE_OOB_MAC = 1, + MLNX_MFG_TYPE_OPN_0, + MLNX_MFG_TYPE_OPN_1, + MLNX_MFG_TYPE_OPN_2, + MLNX_MFG_TYPE_SKU_0, + MLNX_MFG_TYPE_SKU_1, + MLNX_MFG_TYPE_SKU_2, + MLNX_MFG_TYPE_MODL_0, + MLNX_MFG_TYPE_MODL_1, + MLNX_MFG_TYPE_MODL_2, + MLNX_MFG_TYPE_SN_0, + MLNX_MFG_TYPE_SN_1, + MLNX_MFG_TYPE_SN_2, + MLNX_MFG_TYPE_UUID_0, + MLNX_MFG_TYPE_UUID_1, + MLNX_MFG_TYPE_UUID_2, + MLNX_MFG_TYPE_UUID_3, + MLNX_MFG_TYPE_UUID_4, + MLNX_MFG_TYPE_REV, }; -/* ARM SMC call which is atomic and no need for lock. */ -static int mlxbf_bootctl_smc(unsigned int smc_op, int smc_arg) +/* This mutex is used to serialize MFG write and lock operations. */ +static DEFINE_MUTEX(mfg_ops_lock); + +#define MLNX_MFG_OOB_MAC_LEN ETH_ALEN +#define MLNX_MFG_OPN_VAL_LEN 24 +#define MLNX_MFG_SKU_VAL_LEN 24 +#define MLNX_MFG_MODL_VAL_LEN 24 +#define MLNX_MFG_SN_VAL_LEN 24 +#define MLNX_MFG_UUID_VAL_LEN 40 +#define MLNX_MFG_REV_VAL_LEN 8 +#define MLNX_MFG_VAL_QWORD_CNT(type) \ + (MLNX_MFG_##type##_VAL_LEN / sizeof(u64)) + +/* + * The MAC address consists of 6 bytes (2 digits each) separated by ':'. + * The expected format is: "XX:XX:XX:XX:XX:XX" + */ +#define MLNX_MFG_OOB_MAC_FORMAT_LEN \ + ((MLNX_MFG_OOB_MAC_LEN * 2) + (MLNX_MFG_OOB_MAC_LEN - 1)) + +/* The SMC calls in question are atomic, so we don't have to lock here. */ +static int smc_call1(unsigned int smc_op, int smc_arg) { struct arm_smccc_res res; @@ -54,268 +132,1212 @@ static int mlxbf_bootctl_smc(unsigned int smc_op, int smc_arg) return res.a0; } -/* Return the action in integer or an error code. */ -static int mlxbf_bootctl_reset_action_to_val(const char *action) +/* Syntactic sugar to avoid having to specify an unused argument. */ +#define smc_call0(smc_op) smc_call1(smc_op, 0) + +static int reset_action_to_val(const char *action, size_t len) { - int i; + struct boot_name *bn; + + /* Accept string either with or without a newline terminator */ + if (action[len-1] == '\n') + --len; - for (i = 0; i < ARRAY_SIZE(boot_names); i++) - if (sysfs_streq(boot_names[i].name, action)) - return boot_names[i].value; + for (bn = boot_names; bn->value >= 0; ++bn) + if (strncmp(bn->name, action, len) == 0) + break; - return -EINVAL; + return bn->value; } -/* Return the action in string. */ -static const char *mlxbf_bootctl_action_to_string(int action) +static const char *reset_action_to_string(int action) { - int i; + struct boot_name *bn; - for (i = 0; i < ARRAY_SIZE(boot_names); i++) - if (boot_names[i].value == action) - return boot_names[i].name; + for (bn = boot_names; bn->value >= 0; ++bn) + if (bn->value == action) + break; - return "invalid action"; + return bn->name; } -static ssize_t post_reset_wdog_show(struct device *dev, - struct device_attribute *attr, char *buf) +static ssize_t post_reset_wdog_show(struct device_driver *drv, + char *buf) { - int ret; + return snprintf(buf, PAGE_SIZE, "%d\n", + smc_call0(MLNX_GET_POST_RESET_WDOG)); +} - ret = mlxbf_bootctl_smc(MLXBF_BOOTCTL_GET_POST_RESET_WDOG, 0); - if (ret < 0) - return ret; +static ssize_t post_reset_wdog_store(struct device_driver *drv, + const char *buf, size_t count) +{ + int err; + unsigned long watchdog; + + err = kstrtoul(buf, 10, &watchdog); + if (err) + return err; + + if (smc_call1(MLNX_SET_POST_RESET_WDOG, watchdog) < 0) + return -EINVAL; - return sprintf(buf, "%d\n", ret); + return count; } -static ssize_t post_reset_wdog_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) +static ssize_t reset_action_show(struct device_driver *drv, + char *buf) { - unsigned long value; - int ret; + return snprintf(buf, PAGE_SIZE, "%s\n", reset_action_to_string( + smc_call0(MLNX_GET_RESET_ACTION))); +} - ret = kstrtoul(buf, 10, &value); - if (ret) - return ret; +static ssize_t reset_action_store(struct device_driver *drv, + const char *buf, size_t count) +{ + int action = reset_action_to_val(buf, count); - ret = mlxbf_bootctl_smc(MLXBF_BOOTCTL_SET_POST_RESET_WDOG, value); - if (ret < 0) - return ret; + if (action < 0 || action == MLNX_BOOT_NONE) + return -EINVAL; + + if (smc_call1(MLNX_SET_RESET_ACTION, action) < 0) + return -EINVAL; return count; } -static ssize_t mlxbf_bootctl_show(int smc_op, char *buf) +static ssize_t second_reset_action_show(struct device_driver *drv, + char *buf) { - int action; + return snprintf(buf, PAGE_SIZE, "%s\n", reset_action_to_string( + smc_call0(MLNX_GET_SECOND_RESET_ACTION))); +} + +static ssize_t second_reset_action_store(struct device_driver *drv, + const char *buf, size_t count) +{ + int action = reset_action_to_val(buf, count); - action = mlxbf_bootctl_smc(smc_op, 0); if (action < 0) - return action; + return -EINVAL; + + if (smc_call1(MLNX_SET_SECOND_RESET_ACTION, action) < 0) + return -EINVAL; - return sprintf(buf, "%s\n", mlxbf_bootctl_action_to_string(action)); + return count; } -static int mlxbf_bootctl_store(int smc_op, const char *buf, size_t count) +static ssize_t lifecycle_state_show(struct device_driver *drv, + char *buf) { - int ret, action; + int lc_state = smc_call1(MLNX_GET_TBB_FUSE_STATUS, + MLNX_FUSE_STATUS_LIFECYCLE); - action = mlxbf_bootctl_reset_action_to_val(buf); - if (action < 0) - return action; + if (lc_state < 0) + return -EINVAL; + + lc_state &= (SB_MODE_TEST_MASK | + SB_MODE_SECURE_MASK | + SB_MODE_DEV_MASK); - ret = mlxbf_bootctl_smc(smc_op, action); - if (ret < 0) - return ret; + /* + * If the test bits are set, we specify that the current state may be + * due to using the test bits. + */ + if ((lc_state & SB_MODE_TEST_MASK) != 0) { + + lc_state &= SB_MODE_SECURE_MASK; + + return snprintf(buf, PAGE_SIZE, "%s(test)\n", + lifecycle_states[lc_state]); + } else if ((lc_state & SB_MODE_SECURE_MASK) == SB_LIFECYCLE_GA_SECURE + && (lc_state & SB_MODE_DEV_MASK)) { + return snprintf(buf, PAGE_SIZE, "Secured (development)\n"); + } + + return snprintf(buf, PAGE_SIZE, "%s\n", lifecycle_states[lc_state]); +} + +static ssize_t secure_boot_fuse_state_show(struct device_driver *drv, + char *buf) +{ + int key; + int buf_len = 0; + int upper_key_used = 0; + int sb_key_state = smc_call1(MLNX_GET_TBB_FUSE_STATUS, + MLNX_FUSE_STATUS_KEYS); + + if (sb_key_state < 0) + return -EINVAL; + + for (key = SB_KEY_NUM - 1; key >= 0; key--) { + int burnt = ((sb_key_state & (1 << key)) != 0); + int valid = ((sb_key_state & (1 << (key + SB_KEY_NUM))) != 0); + + buf_len += sprintf(buf + buf_len, "Ver%d:", key); + if (upper_key_used) { + if (burnt) { + if (valid) + buf_len += sprintf(buf + buf_len, + "Used"); + else + buf_len += sprintf(buf + buf_len, + "Wasted"); + } else { + if (valid) + buf_len += sprintf(buf + buf_len, + "Invalid"); + else + buf_len += sprintf(buf + buf_len, + "Skipped"); + } + } else { + if (burnt) { + if (valid) { + upper_key_used = 1; + buf_len += sprintf(buf + buf_len, + "In use"); + } else + buf_len += sprintf(buf + buf_len, + "Burn incomplete"); + } else { + if (valid) + buf_len += sprintf(buf + buf_len, + "Invalid"); + else + buf_len += sprintf(buf + buf_len, + "Free"); + } + } + buf_len += sprintf(buf + buf_len, "\n"); + } + + return buf_len; +} + +static ssize_t fw_reset_store(struct device_driver *drv, + const char *buf, size_t count) +{ + int err; + unsigned long key; + + err = kstrtoul(buf, 16, &key); + if (err) + return err; + + if (smc_call1(MLNX_HANDLE_FW_RESET, key) < 0) + return -EINVAL; return count; } -static ssize_t reset_action_show(struct device *dev, - struct device_attribute *attr, char *buf) +static ssize_t oob_mac_show(struct device_driver *drv, char *buf) { - return mlxbf_bootctl_show(MLXBF_BOOTCTL_GET_RESET_ACTION, buf); + char mac_str[MLNX_MFG_OOB_MAC_FORMAT_LEN + 1] = { 0 }; + struct arm_smccc_res res; + u8 *mac_byte_ptr; + + mutex_lock(&mfg_ops_lock); + arm_smccc_smc(MLNX_HANDLE_GET_MFG_INFO, MLNX_MFG_TYPE_OOB_MAC, 0, 0, 0, + 0, 0, 0, &res); + mutex_unlock(&mfg_ops_lock); + if (res.a0) + return -EPERM; + + mac_byte_ptr = (u8 *)&res.a1; + + sprintf(mac_str, "%02X:%02X:%02X:%02X:%02X:%02X", + mac_byte_ptr[0], mac_byte_ptr[1], mac_byte_ptr[2], + mac_byte_ptr[3], mac_byte_ptr[4], mac_byte_ptr[5]); + + return snprintf(buf, PAGE_SIZE, "%s", mac_str); } -static ssize_t reset_action_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) +static ssize_t oob_mac_store(struct device_driver *drv, const char *buf, + size_t count) +{ + int byte[MLNX_MFG_OOB_MAC_FORMAT_LEN] = { 0 }; + struct arm_smccc_res res; + u64 mac_addr = 0; + u8 *mac_byte_ptr; + int byte_idx, len; + + if ((count - 1) != MLNX_MFG_OOB_MAC_FORMAT_LEN) + return -EINVAL; + + len = sscanf(buf, "%02x:%02x:%02x:%02x:%02x:%02x", + &byte[0], &byte[1], &byte[2], + &byte[3], &byte[4], &byte[5]); + if (len != MLNX_MFG_OOB_MAC_LEN) + return -EINVAL; + + mac_byte_ptr = (u8 *)&mac_addr; + + for (byte_idx = 0; byte_idx < MLNX_MFG_OOB_MAC_LEN; byte_idx++) + mac_byte_ptr[byte_idx] = (u8) byte[byte_idx]; + + mutex_lock(&mfg_ops_lock); + arm_smccc_smc(MLNX_HANDLE_SET_MFG_INFO, MLNX_MFG_TYPE_OOB_MAC, + MLNX_MFG_OOB_MAC_LEN, mac_addr, 0, 0, 0, 0, &res); + mutex_unlock(&mfg_ops_lock); + + return res.a0 ? -EPERM : count; +} + +static ssize_t opn_show(struct device_driver *drv, char *buf) { - return mlxbf_bootctl_store(MLXBF_BOOTCTL_SET_RESET_ACTION, buf, count); + u64 opn_data[MLNX_MFG_VAL_QWORD_CNT(OPN)] = { 0 }; + char opn[MLNX_MFG_OPN_VAL_LEN + 1] = { 0 }; + struct arm_smccc_res res; + int word; + + mutex_lock(&mfg_ops_lock); + for (word = 0; word < MLNX_MFG_VAL_QWORD_CNT(OPN); word++) { + arm_smccc_smc(MLNX_HANDLE_GET_MFG_INFO, + MLNX_MFG_TYPE_OPN_0 + word, + 0, 0, 0, 0, 0, 0, &res); + if (res.a0) { + mutex_unlock(&mfg_ops_lock); + return -EPERM; + } + opn_data[word] = res.a1; + } + mutex_unlock(&mfg_ops_lock); + memcpy(opn, opn_data, MLNX_MFG_OPN_VAL_LEN); + + return snprintf(buf, PAGE_SIZE, "%s", opn); } -static ssize_t second_reset_action_show(struct device *dev, - struct device_attribute *attr, - char *buf) +static ssize_t opn_store(struct device_driver *drv, const char *buf, + size_t count) { - return mlxbf_bootctl_show(MLXBF_BOOTCTL_GET_SECOND_RESET_ACTION, buf); + u64 opn[MLNX_MFG_VAL_QWORD_CNT(OPN)] = { 0 }; + struct arm_smccc_res res; + int word; + + if (count > MLNX_MFG_OPN_VAL_LEN) + return -EINVAL; + + memcpy(opn, buf, count); + + mutex_lock(&mfg_ops_lock); + for (word = 0; word < MLNX_MFG_VAL_QWORD_CNT(OPN); word++) { + arm_smccc_smc(MLNX_HANDLE_SET_MFG_INFO, + MLNX_MFG_TYPE_OPN_0 + word, + sizeof(u64), opn[word], 0, 0, 0, 0, &res); + if (res.a0) { + mutex_unlock(&mfg_ops_lock); + return -EPERM; + } + } + mutex_unlock(&mfg_ops_lock); + + return count; } -static ssize_t second_reset_action_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) +static ssize_t sku_show(struct device_driver *drv, char *buf) { - return mlxbf_bootctl_store(MLXBF_BOOTCTL_SET_SECOND_RESET_ACTION, buf, - count); + u64 sku_data[MLNX_MFG_VAL_QWORD_CNT(SKU)] = { 0 }; + char sku[MLNX_MFG_SKU_VAL_LEN + 1] = { 0 }; + struct arm_smccc_res res; + int word; + + mutex_lock(&mfg_ops_lock); + for (word = 0; word < MLNX_MFG_VAL_QWORD_CNT(SKU); word++) { + arm_smccc_smc(MLNX_HANDLE_GET_MFG_INFO, + MLNX_MFG_TYPE_SKU_0 + word, + 0, 0, 0, 0, 0, 0, &res); + if (res.a0) { + mutex_unlock(&mfg_ops_lock); + return -EPERM; + } + sku_data[word] = res.a1; + } + mutex_unlock(&mfg_ops_lock); + memcpy(sku, sku_data, MLNX_MFG_SKU_VAL_LEN); + + return snprintf(buf, PAGE_SIZE, "%s", sku); } -static ssize_t lifecycle_state_show(struct device *dev, - struct device_attribute *attr, char *buf) +static ssize_t sku_store(struct device_driver *drv, const char *buf, + size_t count) { - int lc_state; + u64 sku[MLNX_MFG_VAL_QWORD_CNT(SKU)] = { 0 }; + struct arm_smccc_res res; + int word; - lc_state = mlxbf_bootctl_smc(MLXBF_BOOTCTL_GET_TBB_FUSE_STATUS, - MLXBF_BOOTCTL_FUSE_STATUS_LIFECYCLE); - if (lc_state < 0) - return lc_state; + if (count > MLNX_MFG_SKU_VAL_LEN) + return -EINVAL; - lc_state &= - MLXBF_BOOTCTL_SB_TEST_MASK | MLXBF_BOOTCTL_SB_SECURE_MASK; + memcpy(sku, buf, count); - /* - * If the test bits are set, we specify that the current state may be - * due to using the test bits. - */ - if (lc_state & MLXBF_BOOTCTL_SB_TEST_MASK) { - lc_state &= MLXBF_BOOTCTL_SB_SECURE_MASK; + mutex_lock(&mfg_ops_lock); + for (word = 0; word < MLNX_MFG_VAL_QWORD_CNT(SKU); word++) { + arm_smccc_smc(MLNX_HANDLE_SET_MFG_INFO, + MLNX_MFG_TYPE_SKU_0 + word, + sizeof(u64), sku[word], 0, 0, 0, 0, &res); + if (res.a0) { + mutex_unlock(&mfg_ops_lock); + return -EPERM; + } + } + mutex_unlock(&mfg_ops_lock); - return sprintf(buf, "%s(test)\n", - mlxbf_bootctl_lifecycle_states[lc_state]); + return count; +} + +static ssize_t modl_show(struct device_driver *drv, char *buf) +{ + u64 modl_data[MLNX_MFG_VAL_QWORD_CNT(MODL)] = { 0 }; + char modl[MLNX_MFG_MODL_VAL_LEN + 1] = { 0 }; + struct arm_smccc_res res; + int word; + + mutex_lock(&mfg_ops_lock); + for (word = 0; word < MLNX_MFG_VAL_QWORD_CNT(MODL); word++) { + arm_smccc_smc(MLNX_HANDLE_GET_MFG_INFO, + MLNX_MFG_TYPE_MODL_0 + word, + 0, 0, 0, 0, 0, 0, &res); + if (res.a0) { + mutex_unlock(&mfg_ops_lock); + return -EPERM; + } + modl_data[word] = res.a1; } + mutex_unlock(&mfg_ops_lock); + memcpy(modl, modl_data, MLNX_MFG_MODL_VAL_LEN); - return sprintf(buf, "%s\n", mlxbf_bootctl_lifecycle_states[lc_state]); + return snprintf(buf, PAGE_SIZE, "%s", modl); } -static ssize_t secure_boot_fuse_state_show(struct device *dev, - struct device_attribute *attr, - char *buf) +static ssize_t modl_store(struct device_driver *drv, const char *buf, + size_t count) { - int burnt, valid, key, key_state, buf_len = 0, upper_key_used = 0; - const char *status; + u64 modl[MLNX_MFG_VAL_QWORD_CNT(MODL)] = { 0 }; + struct arm_smccc_res res; + int word; + + if (count > MLNX_MFG_MODL_VAL_LEN) + return -EINVAL; + + memcpy(modl, buf, count); + + mutex_lock(&mfg_ops_lock); + for (word = 0; word < MLNX_MFG_VAL_QWORD_CNT(MODL); word++) { + arm_smccc_smc(MLNX_HANDLE_SET_MFG_INFO, + MLNX_MFG_TYPE_MODL_0 + word, + sizeof(u64), modl[word], 0, 0, 0, 0, &res); + if (res.a0) { + mutex_unlock(&mfg_ops_lock); + return -EPERM; + } + } + mutex_unlock(&mfg_ops_lock); + + return count; +} + +static ssize_t sn_show(struct device_driver *drv, char *buf) +{ + u64 sn_data[MLNX_MFG_VAL_QWORD_CNT(SN)] = { 0 }; + char sn[MLNX_MFG_SN_VAL_LEN + 1] = { 0 }; + struct arm_smccc_res res; + int word; + + mutex_lock(&mfg_ops_lock); + for (word = 0; word < MLNX_MFG_VAL_QWORD_CNT(SN); word++) { + arm_smccc_smc(MLNX_HANDLE_GET_MFG_INFO, + MLNX_MFG_TYPE_SN_0 + word, + 0, 0, 0, 0, 0, 0, &res); + if (res.a0) { + mutex_unlock(&mfg_ops_lock); + return -EPERM; + } + sn_data[word] = res.a1; + } + mutex_unlock(&mfg_ops_lock); + memcpy(sn, sn_data, MLNX_MFG_SN_VAL_LEN); + + return snprintf(buf, PAGE_SIZE, "%s", sn); +} + +static ssize_t sn_store(struct device_driver *drv, const char *buf, + size_t count) +{ + u64 sn[MLNX_MFG_VAL_QWORD_CNT(SN)] = { 0 }; + struct arm_smccc_res res; + int word; + + if (count > MLNX_MFG_SN_VAL_LEN) + return -EINVAL; + + memcpy(sn, buf, count); + + mutex_lock(&mfg_ops_lock); + for (word = 0; word < MLNX_MFG_VAL_QWORD_CNT(SN); word++) { + arm_smccc_smc(MLNX_HANDLE_SET_MFG_INFO, + MLNX_MFG_TYPE_SN_0 + word, + sizeof(u64), sn[word], 0, 0, 0, 0, &res); + if (res.a0) { + mutex_unlock(&mfg_ops_lock); + return -EPERM; + } + } + mutex_unlock(&mfg_ops_lock); + + return count; +} + +static ssize_t uuid_show(struct device_driver *drv, char *buf) +{ + u64 uuid_data[MLNX_MFG_VAL_QWORD_CNT(UUID)] = { 0 }; + char uuid[MLNX_MFG_UUID_VAL_LEN + 1] = { 0 }; + struct arm_smccc_res res; + int word; + + mutex_lock(&mfg_ops_lock); + for (word = 0; word < MLNX_MFG_VAL_QWORD_CNT(UUID); word++) { + arm_smccc_smc(MLNX_HANDLE_GET_MFG_INFO, + MLNX_MFG_TYPE_UUID_0 + word, + 0, 0, 0, 0, 0, 0, &res); + if (res.a0) { + mutex_unlock(&mfg_ops_lock); + return -EPERM; + } + uuid_data[word] = res.a1; + } + mutex_unlock(&mfg_ops_lock); + memcpy(uuid, uuid_data, MLNX_MFG_UUID_VAL_LEN); + + return snprintf(buf, PAGE_SIZE, "%s", uuid); +} + +static ssize_t uuid_store(struct device_driver *drv, const char *buf, + size_t count) +{ + u64 uuid[MLNX_MFG_VAL_QWORD_CNT(UUID)] = { 0 }; + struct arm_smccc_res res; + int word; - key_state = mlxbf_bootctl_smc(MLXBF_BOOTCTL_GET_TBB_FUSE_STATUS, - MLXBF_BOOTCTL_FUSE_STATUS_KEYS); - if (key_state < 0) - return key_state; + if (count > MLNX_MFG_UUID_VAL_LEN) + return -EINVAL; + + memcpy(uuid, buf, count); + + mutex_lock(&mfg_ops_lock); + for (word = 0; word < MLNX_MFG_VAL_QWORD_CNT(UUID); word++) { + arm_smccc_smc(MLNX_HANDLE_SET_MFG_INFO, + MLNX_MFG_TYPE_UUID_0 + word, + sizeof(u64), uuid[word], 0, 0, 0, 0, &res); + if (res.a0) { + mutex_unlock(&mfg_ops_lock); + return -EPERM; + } + } + mutex_unlock(&mfg_ops_lock); + + return count; +} + +static ssize_t rev_show(struct device_driver *drv, char *buf) +{ + u64 rev_data[MLNX_MFG_VAL_QWORD_CNT(REV)] = { 0 }; + char rev[MLNX_MFG_REV_VAL_LEN + 1] = { 0 }; + struct arm_smccc_res res; + int word; + + mutex_lock(&mfg_ops_lock); + for (word = 0; word < MLNX_MFG_VAL_QWORD_CNT(REV); word++) { + arm_smccc_smc(MLNX_HANDLE_GET_MFG_INFO, + MLNX_MFG_TYPE_REV + word, + 0, 0, 0, 0, 0, 0, &res); + if (res.a0) { + mutex_unlock(&mfg_ops_lock); + return -EPERM; + } + rev_data[word] = res.a1; + } + mutex_unlock(&mfg_ops_lock); + memcpy(rev, rev_data, MLNX_MFG_REV_VAL_LEN); + + return snprintf(buf, PAGE_SIZE, "%s", rev); +} + +static ssize_t rev_store(struct device_driver *drv, const char *buf, + size_t count) +{ + u64 rev[MLNX_MFG_VAL_QWORD_CNT(REV)] = { 0 }; + struct arm_smccc_res res; + int word; + + if (count > MLNX_MFG_REV_VAL_LEN) + return -EINVAL; + + memcpy(rev, buf, count); + + mutex_lock(&mfg_ops_lock); + for (word = 0; word < MLNX_MFG_VAL_QWORD_CNT(REV); word++) { + arm_smccc_smc(MLNX_HANDLE_SET_MFG_INFO, + MLNX_MFG_TYPE_REV + word, + sizeof(u64), rev[word], 0, 0, 0, 0, &res); + if (res.a0) { + mutex_unlock(&mfg_ops_lock); + return -EPERM; + } + } + mutex_unlock(&mfg_ops_lock); + + return count; +} + +static ssize_t mfg_lock_store(struct device_driver *drv, const char *buf, + size_t count) +{ + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + if (val != 1) + return -EINVAL; + + mutex_lock(&mfg_ops_lock); + smc_call0(MLNX_HANDLE_LOCK_MFG_INFO); + mutex_unlock(&mfg_ops_lock); + + return count; +} + +/* Log header format. */ +#define RSH_LOG_TYPE_SHIFT 56 +#define RSH_LOG_LEN_SHIFT 48 +#define RSH_LOG_LEVEL_SHIFT 0 + +/* Module ID and type used here. */ +#define BF_RSH_LOG_TYPE_UNKNOWN 0x00ULL +#define BF_RSH_LOG_TYPE_PANIC 0x01ULL +#define BF_RSH_LOG_TYPE_EXCEPTION 0x02ULL +#define BF_RSH_LOG_TYPE_UNUSED 0x03ULL +#define BF_RSH_LOG_TYPE_MSG 0x04ULL + +/* Utility macro. */ +#define BF_RSH_LOG_MOD_MASK 0x0FULL +#define BF_RSH_LOG_MOD_SHIFT 60 +#define BF_RSH_LOG_TYPE_MASK 0x0FULL +#define BF_RSH_LOG_TYPE_SHIFT 56 +#define BF_RSH_LOG_LEN_MASK 0x7FULL +#define BF_RSH_LOG_LEN_SHIFT 48 +#define BF_RSH_LOG_ARG_MASK 0xFFFFFFFFULL +#define BF_RSH_LOG_ARG_SHIFT 16 +#define BF_RSH_LOG_HAS_ARG_MASK 0xFFULL +#define BF_RSH_LOG_HAS_ARG_SHIFT 8 +#define BF_RSH_LOG_LEVEL_MASK 0xFFULL +#define BF_RSH_LOG_LEVEL_SHIFT 0 +#define BF_RSH_LOG_PC_MASK 0xFFFFFFFFULL +#define BF_RSH_LOG_PC_SHIFT 0 +#define BF_RSH_LOG_SYNDROME_MASK 0xFFFFFFFFULL +#define BF_RSH_LOG_SYNDROME_SHIFT 0 + +#define BF_RSH_LOG_HEADER_GET(f, h) \ + (((h) >> BF_RSH_LOG_##f##_SHIFT) & BF_RSH_LOG_##f##_MASK) + +/* Log message level. */ +enum { + RSH_LOG_INFO, + RSH_LOG_WARN, + RSH_LOG_ERR +}; + +/* Log module */ +const char * const rsh_log_mod[] = { + "MISC", "BL1", "BL2", "BL2R", "BL31", "UEFI" +}; + +const char *rsh_log_level[] = {"INFO", "WARN", "ERR", "ASSERT"}; + +#define AARCH64_MRS_REG_SHIFT 5 +#define AARCH64_MRS_REG_MASK 0xffff +#define AARCH64_ESR_ELX_EXCEPTION_CLASS_SHIFT 26 + +struct rsh_log_reg { + char *name; + u32 opcode; +} rsh_log_reg; + +static struct rsh_log_reg rsh_log_regs[] = { + {"actlr_el1", 0b1100000010000001}, + {"actlr_el2", 0b1110000010000001}, + {"actlr_el3", 0b1111000010000001}, + {"afsr0_el1", 0b1100001010001000}, + {"afsr0_el2", 0b1110001010001000}, + {"afsr0_el3", 0b1111001010001000}, + {"afsr1_el1", 0b1100001010001001}, + {"afsr1_el2", 0b1110001010001001}, + {"afsr1_el3", 0b1111001010001001}, + {"amair_el1", 0b1100010100011000}, + {"amair_el2", 0b1110010100011000}, + {"amair_el3", 0b1111010100011000}, + {"ccsidr_el1", 0b1100100000000000}, + {"clidr_el1", 0b1100100000000001}, + {"cntkctl_el1", 0b1100011100001000}, + {"cntp_ctl_el0", 0b1101111100010001}, + {"cntp_cval_el0", 0b1101111100010010}, + {"cntv_ctl_el0", 0b1101111100011001}, + {"cntv_cval_el0", 0b1101111100011010}, + {"contextidr_el1", 0b1100011010000001}, + {"cpacr_el1", 0b1100000010000010}, + {"cptr_el2", 0b1110000010001010}, + {"cptr_el3", 0b1111000010001010}, + {"vtcr_el2", 0b1110000100001010}, + {"ctr_el0", 0b1101100000000001}, + {"currentel", 0b1100001000010010}, + {"dacr32_el2", 0b1110000110000000}, + {"daif", 0b1101101000010001}, + {"dczid_el0", 0b1101100000000111}, + {"dlr_el0", 0b1101101000101001}, + {"dspsr_el0", 0b1101101000101000}, + {"elr_el1", 0b1100001000000001}, + {"elr_el2", 0b1110001000000001}, + {"elr_el3", 0b1111001000000001}, + {"esr_el1", 0b1100001010010000}, + {"esr_el2", 0b1110001010010000}, + {"esr_el3", 0b1111001010010000}, + {"esselr_el1", 0b1101000000000000}, + {"far_el1", 0b1100001100000000}, + {"far_el2", 0b1110001100000000}, + {"far_el3", 0b1111001100000000}, + {"fpcr", 0b1101101000100000}, + {"fpexc32_el2", 0b1110001010011000}, + {"fpsr", 0b1101101000100001}, + {"hacr_el2", 0b1110000010001111}, + {"har_el2", 0b1110000010001000}, + {"hpfar_el2", 0b1110001100000100}, + {"hstr_el2", 0b1110000010001011}, + {"far_el1", 0b1100001100000000}, + {"far_el2", 0b1110001100000000}, + {"far_el3", 0b1111001100000000}, + {"hcr_el2", 0b1110000010001000}, + {"hpfar_el2", 0b1110001100000100}, + {"id_aa64afr0_el1", 0b1100000000101100}, + {"id_aa64afr1_el1", 0b1100000000101101}, + {"id_aa64dfr0_el1", 0b1100000000101100}, + {"id_aa64isar0_el1", 0b1100000000110000}, + {"id_aa64isar1_el1", 0b1100000000110001}, + {"id_aa64mmfr0_el1", 0b1100000000111000}, + {"id_aa64mmfr1_el1", 0b1100000000111001}, + {"id_aa64pfr0_el1", 0b1100000000100000}, + {"id_aa64pfr1_el1", 0b1100000000100001}, + {"ifsr32_el2", 0b1110001010000001}, + {"isr_el1", 0b1100011000001000}, + {"mair_el1", 0b1100010100010000}, + {"mair_el2", 0b1110010100010000}, + {"mair_el3", 0b1111010100010000}, + {"midr_el1", 0b1100000000000000}, + {"mpidr_el1", 0b1100000000000101}, + {"nzcv", 0b1101101000010000}, + {"revidr_el1", 0b1100000000000110}, + {"rmr_el3", 0b1111011000000010}, + {"par_el1", 0b1100001110100000}, + {"rvbar_el3", 0b1111011000000001}, + {"scr_el3", 0b1111000010001000}, + {"sctlr_el1", 0b1100000010000000}, + {"sctlr_el2", 0b1110000010000000}, + {"sctlr_el3", 0b1111000010000000}, + {"sp_el0", 0b1100001000001000}, + {"sp_el1", 0b1110001000001000}, + {"spsel", 0b1100001000010000}, + {"spsr_abt", 0b1110001000011001}, + {"spsr_el1", 0b1100001000000000}, + {"spsr_el2", 0b1110001000000000}, + {"spsr_el3", 0b1111001000000000}, + {"spsr_fiq", 0b1110001000011011}, + {"spsr_irq", 0b1110001000011000}, + {"spsr_und", 0b1110001000011010}, + {"tcr_el1", 0b1100000100000010}, + {"tcr_el2", 0b1110000100000010}, + {"tcr_el3", 0b1111000100000010}, + {"tpidr_el0", 0b1101111010000010}, + {"tpidr_el1", 0b1100011010000100}, + {"tpidr_el2", 0b1110011010000010}, + {"tpidr_el3", 0b1111011010000010}, + {"tpidpro_el0", 0b1101111010000011}, + {"vbar_el1", 0b1100011000000000}, + {"vbar_el2", 0b1110011000000000}, + {"vbar_el3", 0b1111011000000000}, + {"vmpidr_el2", 0b1110000000000101}, + {"vpidr_el2", 0b1110000000000000}, + {"ttbr0_el1", 0b1100000100000000}, + {"ttbr0_el2", 0b1110000100000000}, + {"ttbr0_el3", 0b1111000100000000}, + {"ttbr1_el1", 0b1100000100000001}, + {"vtcr_el2", 0b1110000100001010}, + {"vttbr_el2", 0b1110000100001000}, + {NULL, 0b0000000000000000}, +}; + +/* Size(8-byte words) of the log buffer. */ +#define RSH_SCRATCH_BUF_CTL_IDX_MASK 0x7f + +static int rsh_log_sem_lock(void) +{ + unsigned long timeout; + + /* Take the semaphore. */ + timeout = jiffies + msecs_to_jiffies(100); + while (readq(rsh_semaphore)) { + if (time_after(jiffies, timeout)) + return -ETIMEDOUT; + } + + return 0; +} + +static void rsh_log_sem_unlock(void) +{ + writeq(0, rsh_semaphore); +} + +static ssize_t rsh_log_store(struct device_driver *drv, const char *buf, + size_t count) +{ + int idx, num, len, size = (int)count, level = RSH_LOG_INFO, rc; + u64 data; + + if (!size) + return -EINVAL; + + if (!rsh_semaphore || !rsh_scratch_buf_ctl) + return -EOPNOTSUPP; + + /* Ignore line break at the end. */ + if (buf[size-1] == 0xa) + size--; + + /* Check the message prefix. */ + for (idx = 0; idx < ARRAY_SIZE(rsh_log_level); idx++) { + len = strlen(rsh_log_level[idx]); + if (len + 1 < size && !strncmp(buf, rsh_log_level[idx], len)) { + buf += len + 1; + size -= len + 1; + level = idx; + break; + } + } + + /* Ignore leading spaces. */ + while (size > 0 && buf[0] == ' ') { + size--; + buf++; + } + + /* Take the semaphore. */ + rc = rsh_log_sem_lock(); + if (rc) + return rc; + + /* Calculate how many words are available. */ + num = (size + sizeof(u64) - 1) / sizeof(u64); + idx = readq(rsh_scratch_buf_ctl); + if (idx + num + 1 >= RSH_SCRATCH_BUF_CTL_IDX_MASK) + num = RSH_SCRATCH_BUF_CTL_IDX_MASK - idx - 1; + if (num <= 0) + goto done; + + /* Write Header. */ + data = (BF_RSH_LOG_TYPE_MSG << RSH_LOG_TYPE_SHIFT) | + ((u64)num << RSH_LOG_LEN_SHIFT) | + ((u64)level << RSH_LOG_LEVEL_SHIFT); + writeq(data, rsh_scratch_buf_data); + + /* Write message. */ + for (idx = 0, len = size; idx < num && len > 0; idx++) { + if (len <= sizeof(u64)) { + data = 0; + memcpy(&data, buf, len); + len = 0; + } else { + memcpy(&data, buf, sizeof(u64)); + len -= sizeof(u64); + buf += sizeof(u64); + } + writeq(data, rsh_scratch_buf_data); + } + +done: + /* Release the semaphore. */ + rsh_log_sem_unlock(); + + /* Ignore the rest if no more space. */ + return count; +} + +static char *rsh_log_get_reg_name(u64 opcode) +{ + struct rsh_log_reg *reg = rsh_log_regs; + + while (reg->name) { + if (reg->opcode == opcode) + return reg->name; + reg++; + } + + return "unknown"; +} + +static int rsh_log_show_crash(u64 hdr, char *buf, int size) +{ + int i, module, type, len, n = 0; + u32 pc, syndrome, ec; + u64 opcode, data; + char *p = buf; + + module = BF_RSH_LOG_HEADER_GET(MOD, hdr); + if (module >= ARRAY_SIZE(rsh_log_mod)) + module = 0; + type = BF_RSH_LOG_HEADER_GET(TYPE, hdr); + len = BF_RSH_LOG_HEADER_GET(LEN, hdr); + + if (type == BF_RSH_LOG_TYPE_EXCEPTION) { + syndrome = BF_RSH_LOG_HEADER_GET(SYNDROME, hdr); + ec = syndrome >> AARCH64_ESR_ELX_EXCEPTION_CLASS_SHIFT; + n = snprintf(p, size, " Exception(%s): syndrome = 0x%x%s\n", + rsh_log_mod[module], syndrome, + (ec == 0x24 || ec == 0x25) ? "(Data Abort)" : + (ec == 0x2f) ? "(SError)" : ""); + } else if (type == BF_RSH_LOG_TYPE_PANIC) { + pc = BF_RSH_LOG_HEADER_GET(PC, hdr); + n = snprintf(p, size, + " PANIC(%s): PC = 0x%x\n", rsh_log_mod[module], + pc); + } + if (n > 0) { + p += n; + size -= n; + } /* - * key_state contains the bits for 4 Key versions, loaded from eFuses - * after a hard reset. Lower 4 bits are a thermometer code indicating - * key programming has started for key n (0000 = none, 0001 = version 0, - * 0011 = version 1, 0111 = version 2, 1111 = version 3). Upper 4 bits - * are a thermometer code indicating key programming has completed for - * key n (same encodings as the start bits). This allows for detection - * of an interruption in the programming process which has left the key - * partially programmed (and thus invalid). The process is to burn the - * eFuse for the new key start bit, burn the key eFuses, then burn the - * eFuse for the new key complete bit. - * - * For example 0000_0000: no key valid, 0001_0001: key version 0 valid, - * 0011_0011: key 1 version valid, 0011_0111: key version 2 started - * programming but did not complete, etc. The most recent key for which - * both start and complete bit is set is loaded. On soft reset, this - * register is not modified. + * Read the registers in a loop. 'len' is the total number of words in + * 8-bytes. Two words are read in each loop. */ - for (key = MLXBF_SB_KEY_NUM - 1; key >= 0; key--) { - burnt = key_state & BIT(key); - valid = key_state & BIT(key + MLXBF_SB_KEY_NUM); + for (i = 0; i < len/2; i++) { + opcode = readq(rsh_scratch_buf_data); + data = readq(rsh_scratch_buf_data); + + opcode = (opcode >> AARCH64_MRS_REG_SHIFT) & + AARCH64_MRS_REG_MASK; + n = snprintf(p, size, + " %-16s0x%llx\n", rsh_log_get_reg_name(opcode), + (unsigned long long)data); + if (n > 0) { + p += n; + size -= n; + } + } - if (burnt && valid) - upper_key_used = 1; + return p - buf; +} - if (upper_key_used) { - if (burnt) - status = valid ? "Used" : "Wasted"; - else - status = valid ? "Invalid" : "Skipped"; - } else { - if (burnt) - status = valid ? "InUse" : "Incomplete"; - else - status = valid ? "Invalid" : "Free"; +static int rsh_log_format_msg(char *buf, int size, const char *msg, ...) +{ + va_list args; + int len; + + va_start(args, msg); + len = vsnprintf(buf, size, msg, args); + va_end(args); + + return len; +} + +static int rsh_log_show_msg(u64 hdr, char *buf, int size) +{ + int has_arg = BF_RSH_LOG_HEADER_GET(HAS_ARG, hdr); + int level = BF_RSH_LOG_HEADER_GET(LEVEL, hdr); + int module = BF_RSH_LOG_HEADER_GET(MOD, hdr); + int len = BF_RSH_LOG_HEADER_GET(LEN, hdr); + u32 arg = BF_RSH_LOG_HEADER_GET(ARG, hdr); + char *msg, *p; + u64 data; + + if (len <= 0) + return -EINVAL; + + if (module >= ARRAY_SIZE(rsh_log_mod)) + module = 0; + + if (level >= ARRAY_SIZE(rsh_log_level)) + level = 0; + + msg = kmalloc(len * sizeof(u64) + 1, GFP_KERNEL); + if (!msg) + return 0; + p = msg; + + while (len--) { + data = readq(rsh_scratch_buf_data); + memcpy(p, &data, sizeof(data)); + p += sizeof(data); + } + *p = '\0'; + if (!has_arg) { + len = snprintf(buf, size, " %s[%s]: %s\n", rsh_log_level[level], + rsh_log_mod[module], msg); + } else { + len = snprintf(buf, size, " %s[%s]: ", rsh_log_level[level], + rsh_log_mod[module]); + len += rsh_log_format_msg(buf + len, size - len, msg, arg); + len += snprintf(buf + len, size - len, "\n"); + } + + kfree(msg); + return len; +} + +static ssize_t rsh_log_show(struct device_driver *drv, char *buf) +{ + u64 hdr; + char *p = buf; + int i, n, rc, idx, type, len, size = PAGE_SIZE; + + if (!rsh_semaphore || !rsh_scratch_buf_ctl) + return -EOPNOTSUPP; + + /* Take the semaphore. */ + rc = rsh_log_sem_lock(); + if (rc) + return rc; + + /* Save the current index and read from 0. */ + idx = readq(rsh_scratch_buf_ctl) & RSH_SCRATCH_BUF_CTL_IDX_MASK; + if (!idx) + goto done; + writeq(0, rsh_scratch_buf_ctl); + + i = 0; + while (i < idx) { + hdr = readq(rsh_scratch_buf_data); + type = BF_RSH_LOG_HEADER_GET(TYPE, hdr); + len = BF_RSH_LOG_HEADER_GET(LEN, hdr); + i += 1 + len; + if (i > idx) + break; + + switch (type) { + case BF_RSH_LOG_TYPE_PANIC: + case BF_RSH_LOG_TYPE_EXCEPTION: + n = rsh_log_show_crash(hdr, p, size); + p += n; + size -= n; + break; + case BF_RSH_LOG_TYPE_MSG: + n = rsh_log_show_msg(hdr, p, size); + p += n; + size -= n; + break; + default: + /* Drain this message. */ + while (len--) + (void) readq(rsh_scratch_buf_data); + break; } - buf_len += sprintf(buf + buf_len, "%d:%s ", key, status); } - buf_len += sprintf(buf + buf_len, "\n"); - return buf_len; + if (rsh_log_clear_on_read) + writeq(0, rsh_scratch_buf_ctl); + else + writeq(idx, rsh_scratch_buf_ctl); + +done: + /* Release the semaphore. */ + rsh_log_sem_unlock(); + + return p - buf; } -static DEVICE_ATTR_RW(post_reset_wdog); -static DEVICE_ATTR_RW(reset_action); -static DEVICE_ATTR_RW(second_reset_action); -static DEVICE_ATTR_RO(lifecycle_state); -static DEVICE_ATTR_RO(secure_boot_fuse_state); +static DRIVER_ATTR_RW(post_reset_wdog); +static DRIVER_ATTR_RW(reset_action); +static DRIVER_ATTR_RW(second_reset_action); +static DRIVER_ATTR_RO(lifecycle_state); +static DRIVER_ATTR_RO(secure_boot_fuse_state); +static DRIVER_ATTR_WO(fw_reset); +static DRIVER_ATTR_RW(oob_mac); +static DRIVER_ATTR_RW(opn); +static DRIVER_ATTR_RW(sku); +static DRIVER_ATTR_RW(modl); +static DRIVER_ATTR_RW(sn); +static DRIVER_ATTR_RW(uuid); +static DRIVER_ATTR_RW(rev); +static DRIVER_ATTR_WO(mfg_lock); +static DRIVER_ATTR_RW(rsh_log); -static struct attribute *mlxbf_bootctl_attrs[] = { - &dev_attr_post_reset_wdog.attr, - &dev_attr_reset_action.attr, - &dev_attr_second_reset_action.attr, - &dev_attr_lifecycle_state.attr, - &dev_attr_secure_boot_fuse_state.attr, +static struct attribute *mbc_dev_attrs[] = { + &driver_attr_post_reset_wdog.attr, + &driver_attr_reset_action.attr, + &driver_attr_second_reset_action.attr, + &driver_attr_lifecycle_state.attr, + &driver_attr_secure_boot_fuse_state.attr, + &driver_attr_fw_reset.attr, + &driver_attr_oob_mac.attr, + &driver_attr_opn.attr, + &driver_attr_sku.attr, + &driver_attr_modl.attr, + &driver_attr_sn.attr, + &driver_attr_uuid.attr, + &driver_attr_rev.attr, + &driver_attr_mfg_lock.attr, + &driver_attr_rsh_log.attr, NULL }; -ATTRIBUTE_GROUPS(mlxbf_bootctl); +static struct attribute_group mbc_attr_group = { + .attrs = mbc_dev_attrs +}; -static const struct acpi_device_id mlxbf_bootctl_acpi_ids[] = { +static const struct attribute_group *mbc_attr_groups[] = { + &mbc_attr_group, + NULL +}; + +static const struct of_device_id mbc_dt_ids[] = { + {.compatible = "mellanox,bootctl"}, + {}, +}; + +MODULE_DEVICE_TABLE(of, mbc_dt_ids); + +static const struct acpi_device_id mbc_acpi_ids[] = { {"MLNXBF04", 0}, - {} + {}, }; -MODULE_DEVICE_TABLE(acpi, mlxbf_bootctl_acpi_ids); +MODULE_DEVICE_TABLE(acpi, mbc_acpi_ids); -static bool mlxbf_bootctl_guid_match(const guid_t *guid, - const struct arm_smccc_res *res) +static ssize_t mbc_bootfifo_read_raw(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t pos, size_t count) { - guid_t id = GUID_INIT(res->a0, res->a1, res->a1 >> 16, - res->a2, res->a2 >> 8, res->a2 >> 16, - res->a2 >> 24, res->a3, res->a3 >> 8, - res->a3 >> 16, res->a3 >> 24); + unsigned long timeout = jiffies + HZ / 2; + char *p = buf; + int cnt = 0; + u64 data; - return guid_equal(guid, &id); + /* Give up reading if no more data within 500ms. */ + while (count >= sizeof(data)) { + if (!cnt) { + cnt = readq(rsh_boot_cnt); + if (!cnt) { + if (time_after(jiffies, timeout)) + break; + udelay(10); + continue; + } + } + + data = readq(rsh_boot_data); + memcpy(p, &data, sizeof(data)); + count -= sizeof(data); + p += sizeof(data); + cnt--; + timeout = jiffies + HZ / 2; + } + + return p - buf; } -static int mlxbf_bootctl_probe(struct platform_device *pdev) +static struct bin_attribute mbc_bootfifo_sysfs_attr = { + .attr = { .name = "bootfifo", .mode = 0400 }, + .read = mbc_bootfifo_read_raw, +}; + +static int mbc_probe(struct platform_device *pdev) { - struct arm_smccc_res res = { 0 }; - guid_t guid; - int ret; + struct resource *resource; + struct arm_smccc_res res; + void __iomem *data; + int err; - /* Ensure we have the UUID we expect for this service. */ - arm_smccc_smc(MLXBF_BOOTCTL_SIP_SVC_UID, 0, 0, 0, 0, 0, 0, 0, &res); - guid_parse(mlxbf_bootctl_svc_uuid_str, &guid); - if (!mlxbf_bootctl_guid_match(&guid, &res)) + resource = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!resource) return -ENODEV; + rsh_boot_data = devm_ioremap_resource(&pdev->dev, resource); + if (IS_ERR(rsh_boot_data)) + return PTR_ERR(rsh_boot_data); + + resource = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!resource) + return -ENODEV; + rsh_boot_cnt = devm_ioremap_resource(&pdev->dev, resource); + if (IS_ERR(rsh_boot_cnt)) + return PTR_ERR(rsh_boot_cnt); + + resource = platform_get_resource(pdev, IORESOURCE_MEM, 2); + if (resource) { + data = devm_ioremap_resource(&pdev->dev, resource); + if (!IS_ERR(data)) + rsh_semaphore = data; + } + + resource = platform_get_resource(pdev, IORESOURCE_MEM, 3); + if (resource) { + data = devm_ioremap_resource(&pdev->dev, resource); + if (!IS_ERR(data)) { + rsh_scratch_buf_ctl = data + RSH_SCRATCH_BUF_CTL_OFF; + rsh_scratch_buf_data = data + RSH_SCRATCH_BUF_DATA_OFF; + } + } /* - * When watchdog is used, it sets boot mode to MLXBF_BOOTCTL_SWAP_EMMC + * Ensure we have the UUID we expect for this service. + * Note that the functionality we want is present in the first + * released version of this service, so we don't check the version. + */ + arm_smccc_smc(MLNX_SIP_SVC_UID, 0, 0, 0, 0, 0, 0, 0, &res); + if (res.a0 != 0x89c036b4 || res.a1 != 0x11e6e7d7 || + res.a2 != 0x1a009787 || res.a3 != 0xc4bf00ca) + return -ENODEV; + + /* + * When watchdog is used, it sets the boot mode to MLNX_BOOT_SWAP_EMMC * in case of boot failures. However it doesn't clear the state if there * is no failure. Restore the default boot mode here to avoid any * unnecessary boot partition swapping. */ - ret = mlxbf_bootctl_smc(MLXBF_BOOTCTL_SET_RESET_ACTION, - MLXBF_BOOTCTL_EMMC); - if (ret < 0) - dev_warn(&pdev->dev, "Unable to reset the EMMC boot mode\n"); + if (smc_call1(MLNX_SET_RESET_ACTION, MLNX_BOOT_EMMC) < 0) + pr_err("Unable to reset the EMMC boot mode\n"); + + err = sysfs_create_bin_file(&pdev->dev.kobj, &mbc_bootfifo_sysfs_attr); + if (err) { + pr_err("Unable to create bootfifo sysfs file, error %d\n", err); + return err; + } + + pr_info("%s (version %s)\n", DRIVER_DESCRIPTION, DRIVER_VERSION); + + return 0; +} + +static int mbc_remove(struct platform_device *pdev) +{ + sysfs_remove_bin_file(&pdev->dev.kobj, &mbc_bootfifo_sysfs_attr); return 0; } -static struct platform_driver mlxbf_bootctl_driver = { - .probe = mlxbf_bootctl_probe, +static struct platform_driver mbc_driver = { + .probe = mbc_probe, + .remove = mbc_remove, .driver = { - .name = "mlxbf-bootctl", - .dev_groups = mlxbf_bootctl_groups, - .acpi_match_table = mlxbf_bootctl_acpi_ids, + .name = DRIVER_NAME, + .groups = mbc_attr_groups, + .of_match_table = mbc_dt_ids, + .acpi_match_table = ACPI_PTR(mbc_acpi_ids), } }; -module_platform_driver(mlxbf_bootctl_driver); +module_platform_driver(mbc_driver); -MODULE_DESCRIPTION("Mellanox boot control driver"); -MODULE_LICENSE("GPL v2"); -MODULE_AUTHOR("Mellanox Technologies"); +MODULE_DESCRIPTION(DRIVER_DESCRIPTION); +MODULE_VERSION(DRIVER_VERSION); +MODULE_AUTHOR("Shravan Kumar Ramani "); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/platform/mellanox/mlxbf-bootctl.h b/drivers/platform/mellanox/mlxbf-bootctl.h index 148fdb43b..3e9dda829 100644 --- a/drivers/platform/mellanox/mlxbf-bootctl.h +++ b/drivers/platform/mellanox/mlxbf-bootctl.h @@ -1,11 +1,22 @@ -/* SPDX-License-Identifier: GPL-2.0 */ +// SPDX-License-Identifier: GPL-2.0 or BSD-3-Clause /* - * Copyright (c) 2019, Mellanox Technologies. All rights reserved. + * Copyright (C) 2020 Mellanox Technologies. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License v2.0 as published by + * the Free Software Foundation. + * + * 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. */ #ifndef __MLXBF_BOOTCTL_H__ #define __MLXBF_BOOTCTL_H__ +/* BlueField-specific SMC function IDs */ + /* * Request that the on-chip watchdog be enabled, or disabled, after * the next chip soft reset. This call does not affect the current @@ -14,14 +25,14 @@ * will not be enabled after the next soft reset. Non-zero errors are * returned as documented below. */ -#define MLXBF_BOOTCTL_SET_POST_RESET_WDOG 0x82000000 +#define MLNX_SET_POST_RESET_WDOG 0x82000000 /* * Query the status which has been requested for the on-chip watchdog * after the next chip soft reset. Returns the interval as set by - * MLXBF_BOOTCTL_SET_POST_RESET_WDOG. + * MLNX_SET_POST_RESET_WDOG. */ -#define MLXBF_BOOTCTL_GET_POST_RESET_WDOG 0x82000001 +#define MLNX_GET_POST_RESET_WDOG 0x82000001 /* * Request that a specific boot action be taken at the next soft @@ -32,72 +43,77 @@ * invoked. See below for the available MLNX_BOOT_xxx parameter * values. Non-zero errors are returned as documented below. */ -#define MLXBF_BOOTCTL_SET_RESET_ACTION 0x82000002 +#define MLNX_SET_RESET_ACTION 0x82000002 /* * Return the specific boot action which will be taken at the next * soft reset. Returns the reset action (see below for the parameter - * values for MLXBF_BOOTCTL_SET_RESET_ACTION). + * values for MLNX_SET_RESET_ACTION). */ -#define MLXBF_BOOTCTL_GET_RESET_ACTION 0x82000003 +#define MLNX_GET_RESET_ACTION 0x82000003 /* * Request that a specific boot action be taken at the soft reset * after the next soft reset. For a specified valid boot mode, the * effect of this call is identical to that of invoking - * MLXBF_BOOTCTL_SET_RESET_ACTION after the next chip soft reset; in + * MLNX_SET_RESET_ACTION after the next chip soft reset; in * particular, after that reset, the action for the now next reset can - * be queried with MLXBF_BOOTCTL_GET_RESET_ACTION and modified with - * MLXBF_BOOTCTL_SET_RESET_ACTION. You may also specify the parameter as + * be queried with MLNX_GET_RESET_ACTION and modified with + * MLNX_SET_RESET_ACTION. You may also specify the parameter as * MLNX_BOOT_NONE, which is equivalent to specifying that no call to - * MLXBF_BOOTCTL_SET_RESET_ACTION be taken after the next chip soft reset. + * MLNX_SET_RESET_ACTION be taken after the next chip soft reset. * This call does not affect the action to be taken at the next soft * reset. Non-zero errors are returned as documented below. */ -#define MLXBF_BOOTCTL_SET_SECOND_RESET_ACTION 0x82000004 +#define MLNX_SET_SECOND_RESET_ACTION 0x82000004 /* * Return the specific boot action which will be taken at the soft * reset after the next soft reset; this will be one of the valid - * actions for MLXBF_BOOTCTL_SET_SECOND_RESET_ACTION. + * actions for MLNX_SET_SECOND_RESET_ACTION. */ -#define MLXBF_BOOTCTL_GET_SECOND_RESET_ACTION 0x82000005 +#define MLNX_GET_SECOND_RESET_ACTION 0x82000005 /* * Return the fuse status of the current chip. The caller should specify * with the second argument if the state of the lifecycle fuses or the * version of secure boot fuse keys left should be returned. */ -#define MLXBF_BOOTCTL_GET_TBB_FUSE_STATUS 0x82000006 +#define MLNX_GET_TBB_FUSE_STATUS 0x82000006 -/* Reset eMMC by programming the RST_N register. */ -#define MLXBF_BOOTCTL_SET_EMMC_RST_N 0x82000007 +/* + * Initiate Firmware Reset via TYU. This might be invoked during the reset + * flow in isolation mode. + */ +#define MLNX_HANDLE_FW_RESET 0x8200000D -#define MLXBF_BOOTCTL_GET_DIMM_INFO 0x82000008 +/* + * SMC function IDs to set, get and reset the manufacturing information + * stored within the eeprom. + */ +#define MLNX_HANDLE_SET_MFG_INFO 0x8200000E +#define MLNX_HANDLE_GET_MFG_INFO 0x8200000F +#define MLNX_HANDLE_LOCK_MFG_INFO 0x82000011 /* SMC function IDs for SiP Service queries */ -#define MLXBF_BOOTCTL_SIP_SVC_CALL_COUNT 0x8200ff00 -#define MLXBF_BOOTCTL_SIP_SVC_UID 0x8200ff01 -#define MLXBF_BOOTCTL_SIP_SVC_VERSION 0x8200ff03 - -/* ARM Standard Service Calls version numbers */ -#define MLXBF_BOOTCTL_SVC_VERSION_MAJOR 0x0 -#define MLXBF_BOOTCTL_SVC_VERSION_MINOR 0x2 +#define MLNX_SIP_SVC_CALL_COUNT 0x8200ff00 +#define MLNX_SIP_SVC_UID 0x8200ff01 +#define MLNX_SIP_SVC_VERSION 0x8200ff03 /* Number of svc calls defined. */ -#define MLXBF_BOOTCTL_NUM_SVC_CALLS 12 +#define MLNX_NUM_SVC_CALLS 16 -/* Valid reset actions for MLXBF_BOOTCTL_SET_RESET_ACTION. */ -#define MLXBF_BOOTCTL_EXTERNAL 0 /* Not boot from eMMC */ -#define MLXBF_BOOTCTL_EMMC 1 /* From primary eMMC boot partition */ -#define MLNX_BOOTCTL_SWAP_EMMC 2 /* Swap eMMC boot partitions and reboot */ -#define MLXBF_BOOTCTL_EMMC_LEGACY 3 /* From primary eMMC in legacy mode */ +/* Valid reset actions for MLNX_SET_RESET_ACTION. */ +#define MLNX_BOOT_EXTERNAL 0 /* Do not boot from eMMC */ +#define MLNX_BOOT_EMMC 1 /* Boot from primary eMMC boot partition */ +#define MLNX_BOOT_SWAP_EMMC 2 /* Swap eMMC boot partitions and reboot */ +#define MLNX_BOOT_EMMC_LEGACY 3 /* Boot from primary eMMC in legacy mode */ /* Valid arguments for requesting the fuse status. */ -#define MLXBF_BOOTCTL_FUSE_STATUS_LIFECYCLE 0 /* Return lifecycle status. */ -#define MLXBF_BOOTCTL_FUSE_STATUS_KEYS 1 /* Return secure boot key status */ +#define MLNX_FUSE_STATUS_LIFECYCLE 0 /* Return the lifecycle status. */ +#define MLNX_FUSE_STATUS_KEYS 1 /* Return secure boot key status */ -/* Additional value to disable the MLXBF_BOOTCTL_SET_SECOND_RESET_ACTION. */ -#define MLXBF_BOOTCTL_NONE 0x7fffffff /* Don't change next boot action */ +/* Additional parameter value to disable the MLNX_SET_SECOND_RESET_ACTION. */ +#define MLNX_BOOT_NONE 0x7fffffff /* Don't change next boot action */ #endif /* __MLXBF_BOOTCTL_H__ */ -- 2.20.1