Loading...
// SPDX-License-Identifier: GPL-2.0+
/*
 * Copyright (c) 2025 Linaro Ltd.
 * Author: Sam Protsenko <semen.protsenko@linaro.org>
 *
 * Routines for checking current boot device.
 */

#include <linux/arm-smccc.h>
#include <vsprintf.h>
#include "bootdev.h"

/* Flag from BL2 bootloader in RAM */
#define BL2_TAG_ADDR			0x80000000	/* DRAM base */
#define BL2_TAG				0xabcdef

/* Boot device info location in iRAM (only accessible from EL3) */
#define IRAM_BASE			0x02020000
#define BOOTDEVICE_INFO_ADDR		(IRAM_BASE + 0x64)

/* SMC call for getting boot device information from EL3 monitor */
#define SMC_CMD_CHECK_SECOND_BOOT	-233

/* Boot device constants for the encoded boot device info value */
#define BD_NO_DEVICE			0x0
#define BD_UFS				0x1
#define BD_EMMC				0x2
#define BD_ERROR			0x3
#define BD_USB				0x4
#define BD_SDMMC			0x5
#define BD_UFS_CARD			0x6
#define BD_SPI				0x7

/* If BL2 bootloader wasn't executed, it means U-Boot is running via JTAG */
static bool bootdev_is_jtag_session(void)
{
	u32 bl2_tag_val = *(u32 *)BL2_TAG_ADDR;

	return bl2_tag_val != BL2_TAG;
}

/* Obtain boot device information encoded in 32-bit value */
static u32 bootdev_get_info(void)
{
	u32 info;

	/*
	 * On regular boot U-Boot is executed by BL2 bootloader, and is running
	 * in EL1 mode, so the boot device information has to be obtained via
	 * SMC call from EL3 software (EL3 monitor), which can read that info
	 * from the protected iRAM memory. If U-Boot is running via TRACE32 JTAG
	 * (in EL3 mode), read the boot device info directly from iRAM, as EL3
	 * software might not be available.
	 */
	if (bootdev_is_jtag_session()) {
		info = *(u32 *)BOOTDEVICE_INFO_ADDR;
	} else {
		struct arm_smccc_res res;

		arm_smccc_smc(SMC_CMD_CHECK_SECOND_BOOT, 0, 0, 0, 0, 0, 0, 0,
			      &res);
		info = (u32)res.a2;
	}

	return info;
}

enum bootdev bootdev_get_current(void)
{
	u32 info, magic, order, dev;

	info = bootdev_get_info();
	magic = info >> 24;
	order = info & 0xf;
	dev = (info >> (4 * order)) & 0xf;

	if (magic != 0xcb)
		panic("Abnormal boot");

	switch (dev) {
	case BD_UFS:
		return BOOTDEV_UFS;
	case BD_EMMC:
		return BOOTDEV_EMMC;
	case BD_USB:
		return BOOTDEV_USB;
	case BD_SDMMC:
		return BOOTDEV_SD;
	default:
		return BOOTDEV_ERROR;
	}

	return BOOTDEV_ERROR;
}

bool bootdev_is_usb(void)
{
	return bootdev_get_current() == BOOTDEV_USB;
}