// SPDX-License-Identifier: GPL-2.0+
/*
* Mellanox boot control driver
*
* This driver provides a sysfs interface for systems management
* software to manage reset-time actions.
*
* Copyright (C) 2019 Mellanox Technologies
*/
#include <linux/acpi.h>
#include <linux/arm-smccc.h>
#include <linux/delay.h>
#include <linux/if_ether.h>
#include <linux/iopoll.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include "mlxbf-bootctl.h"
#define MLXBF_BOOTCTL_SB_SECURE_MASK 0x03
#define MLXBF_BOOTCTL_SB_TEST_MASK 0x0c
#define MLXBF_BOOTCTL_SB_DEV_MASK BIT(4)
#define MLXBF_SB_KEY_NUM 4
/* UUID used to probe ATF service. */
static const char *mlxbf_bootctl_svc_uuid_str =
"89c036b4-e7d7-11e6-8797-001aca00bfc4";
struct mlxbf_bootctl_name {
u32 value;
const char *name;
};
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" },
};
enum {
MLXBF_BOOTCTL_SB_LIFECYCLE_PRODUCTION = 0,
MLXBF_BOOTCTL_SB_LIFECYCLE_GA_SECURE = 1,
MLXBF_BOOTCTL_SB_LIFECYCLE_GA_NON_SECURE = 2,
MLXBF_BOOTCTL_SB_LIFECYCLE_RMA = 3
};
static const char * const mlxbf_bootctl_lifecycle_states[] = {
[MLXBF_BOOTCTL_SB_LIFECYCLE_PRODUCTION] = "Production",
[MLXBF_BOOTCTL_SB_LIFECYCLE_GA_SECURE] = "GA Secured",
[MLXBF_BOOTCTL_SB_LIFECYCLE_GA_NON_SECURE] = "GA Non-Secured",
[MLXBF_BOOTCTL_SB_LIFECYCLE_RMA] = "RMA",
};
/* Log header format. */
#define MLXBF_RSH_LOG_TYPE_MASK GENMASK_ULL(59, 56)
#define MLXBF_RSH_LOG_LEN_MASK GENMASK_ULL(54, 48)
#define MLXBF_RSH_LOG_LEVEL_MASK GENMASK_ULL(7, 0)
/* Log module ID and type (only MSG type in Linux driver for now). */
#define MLXBF_RSH_LOG_TYPE_MSG 0x04ULL
/* Log ctl/data register offset. */
#define MLXBF_RSH_SCRATCH_BUF_CTL_OFF 0
#define MLXBF_RSH_SCRATCH_BUF_DATA_OFF 0x10
/* Log message levels. */
enum {
MLXBF_RSH_LOG_INFO,
MLXBF_RSH_LOG_WARN,
MLXBF_RSH_LOG_ERR,
MLXBF_RSH_LOG_ASSERT
};
/* Mapped pointer for RSH_BOOT_FIFO_DATA and RSH_BOOT_FIFO_COUNT register. */
static void __iomem *mlxbf_rsh_boot_data;
static void __iomem *mlxbf_rsh_boot_cnt;
/* Mapped pointer for rsh log semaphore/ctrl/data register. */
static void __iomem *mlxbf_rsh_semaphore;
static void __iomem *mlxbf_rsh_scratch_buf_ctl;
static void __iomem *mlxbf_rsh_scratch_buf_data;
/* Rsh log levels. */
static const char * const mlxbf_rsh_log_level[] = {
"INFO", "WARN", "ERR", "ASSERT"};
static DEFINE_MUTEX(icm_ops_lock);
static DEFINE_MUTEX(os_up_lock);
static DEFINE_MUTEX(mfg_ops_lock);
/*
* 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,
};
#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 \
((ETH_ALEN * 2) + (ETH_ALEN - 1))
/* ARM SMC call which is atomic and no need for lock. */
static int mlxbf_bootctl_smc(unsigned int smc_op, int smc_arg)
{
struct arm_smccc_res res;
arm_smccc_smc(smc_op, smc_arg, 0, 0, 0, 0, 0, 0, &res);
return res.a0;
}
/* Return the action in integer or an error code. */
static int mlxbf_bootctl_reset_action_to_val(const char *action)
{
int i;
for (i = 0; i < ARRAY_SIZE(boot_names); i++)
if (sysfs_streq(boot_names[i].name, action))
return boot_names[i].value;
return -EINVAL;
}
/* Return the action in string. */
static const char *mlxbf_bootctl_action_to_string(int action)
{
int i;
for (i = 0; i < ARRAY_SIZE(boot_names); i++)
if (boot_names[i].value == action)
return boot_names[i].name;
return "invalid action";
}
static ssize_t post_reset_wdog_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
int ret;
ret = mlxbf_bootctl_smc(MLXBF_BOOTCTL_GET_POST_RESET_WDOG, 0);
if (ret < 0)
return ret;
return sprintf(buf, "%d\n", ret);
}
static ssize_t post_reset_wdog_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
unsigned long value;
int ret;
ret = kstrtoul(buf, 10, &value);
if (ret)
return ret;
ret = mlxbf_bootctl_smc(MLXBF_BOOTCTL_SET_POST_RESET_WDOG, value);
if (ret < 0)
return ret;
return count;
}
static ssize_t mlxbf_bootctl_show(int smc_op, char *buf)
{
int action;
action = mlxbf_bootctl_smc(smc_op, 0);
if (action < 0)
return action;
return sprintf(buf, "%s\n", mlxbf_bootctl_action_to_string(action));
}
static int mlxbf_bootctl_store(int smc_op, const char *buf, size_t count)
{
int ret, action;
action = mlxbf_bootctl_reset_action_to_val(buf);
if (action < 0)
return action;
ret =<