363 lines
8.8 KiB
C
Executable file
363 lines
8.8 KiB
C
Executable file
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (c) 2020 Samsung Electronics Co., Ltd.
|
|
* http://www.samsung.com
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 and
|
|
* only version 2 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 <linux/module.h>
|
|
#include <linux/string.h>
|
|
#include <linux/firmware.h>
|
|
#include <linux/io.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/memblock.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/suspend.h>
|
|
#include <linux/rwsem.h>
|
|
#include <linux/sysfs.h>
|
|
#include <linux/err.h>
|
|
#include <linux/idr.h>
|
|
#include <linux/of_irq.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/io.h>
|
|
#include <asm/setup.h>
|
|
|
|
#include <soc/samsung/imgloader.h>
|
|
|
|
#if IS_ENABLED(CONFIG_EXYNOS_S2MPU)
|
|
#include <soc/samsung/exynos-s2mpu.h>
|
|
#endif
|
|
|
|
#define IMGLOADER_NUM_DESC 36
|
|
|
|
#define imgloader_err(desc, fmt, ...) \
|
|
dev_err(desc->dev, "%s: " fmt, desc->name, ##__VA_ARGS__)
|
|
#define imgloader_info(desc, fmt, ...) \
|
|
dev_info(desc->dev, "%s: " fmt, desc->name, ##__VA_ARGS__)
|
|
|
|
/**
|
|
* struct imgloader_priv - Private state for a imgloader_desc
|
|
* @desc: pointer to imgloader_desc this is private data for
|
|
* @fw_phys_base_addr: physical address where processor starts booting at
|
|
* @fw_bin_size: firmware binary size
|
|
* @fw_mem_size: firmware used size
|
|
*
|
|
* This struct contains data for a imgloader_desc that should not be exposed outside
|
|
* of this file. This structure points to the descriptor and the descriptor
|
|
* pointr to this structure so that imgloader drivers can't access the private
|
|
* data of a descriptor but this file can access both.
|
|
*/
|
|
struct imgloader_priv {
|
|
struct imgloader_desc *desc;
|
|
phys_addr_t fw_phys_base;
|
|
size_t fw_bin_size;
|
|
size_t fw_mem_size;
|
|
int id;
|
|
};
|
|
|
|
/**
|
|
* imgloader_get_phys_base - Retrieve the physical base address of a peripheral image
|
|
* @desc: descriptor from imgloader_desc_init()
|
|
*
|
|
* Returns the physical address where the image boots at or 0 if unknown.
|
|
*/
|
|
phys_addr_t imgloader_get_phys_base(struct imgloader_desc *desc)
|
|
{
|
|
return desc->priv ? desc->priv->fw_phys_base : 0;
|
|
}
|
|
EXPORT_SYMBOL(imgloader_get_phys_base);
|
|
|
|
static int imgloader_parse_dt(struct imgloader_desc *desc)
|
|
{
|
|
struct device_node *ofnode = desc->dev->of_node;
|
|
|
|
if (!ofnode)
|
|
return -EINVAL;
|
|
|
|
desc->s2mpu_support = of_property_read_bool(ofnode,
|
|
"samsung,imgloader-s2mpu-support");
|
|
return 0;
|
|
}
|
|
|
|
static int imgloader_notify(struct imgloader_desc *desc, char *status)
|
|
{
|
|
if (!desc->notify_signal)
|
|
return 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int imgloader_allow_permission(struct imgloader_desc *desc)
|
|
{
|
|
unsigned long ret = 0;
|
|
|
|
#if IS_ENABLED(CONFIG_EXYNOS_S2MPU)
|
|
ret = exynos_s2mpu_request_fw_stage2_ap(desc->name);
|
|
#endif
|
|
return (int)ret;
|
|
}
|
|
|
|
static int imgloader_verify_fw(struct imgloader_desc *desc)
|
|
{
|
|
unsigned long ret = 0;
|
|
#if IS_ENABLED(CONFIG_EXYNOS_S2MPU)
|
|
struct imgloader_priv *priv = desc->priv;
|
|
|
|
ret = exynos_s2mpu_verify_subsystem_fw(desc->name,
|
|
desc->fw_id,
|
|
priv->fw_phys_base,
|
|
priv->fw_bin_size,
|
|
priv->fw_mem_size);
|
|
#endif
|
|
return (int)ret;
|
|
}
|
|
|
|
static int imgloader_release_fw_permission(struct imgloader_desc *desc)
|
|
{
|
|
unsigned long ret = 0;
|
|
|
|
#if IS_ENABLED(CONFIG_EXYNOS_S2MPU)
|
|
ret = exynos_s2mpu_release_fw_stage2_ap(desc->name,
|
|
desc->fw_id);
|
|
#endif
|
|
return (int)ret;
|
|
}
|
|
|
|
/**
|
|
* imgloader_boot() - Load a peripheral image into memory and boot it
|
|
* @desc: descriptor from imgloader_desc_init()
|
|
*
|
|
* Returns 0 on success or -ERROR on failure.
|
|
*/
|
|
int imgloader_boot(struct imgloader_desc *desc)
|
|
{
|
|
int ret;
|
|
const struct firmware *fw;
|
|
struct imgloader_priv *priv = desc->priv;
|
|
size_t fw_bin_size = 0;
|
|
size_t fw_mem_size = 0;
|
|
phys_addr_t fw_phys_base = 0;
|
|
size_t fw_size = 0;
|
|
const u8 *fw_data = NULL;
|
|
|
|
/* Notify to other device */
|
|
ret = imgloader_notify(desc, "on");
|
|
if (ret < 0) {
|
|
imgloader_err(desc, "Failed to noify - rc:%d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
/* Warning when Subsystem shutdown is failed previously */
|
|
if (desc->shutdown_fail)
|
|
imgloader_err(desc, "shutdown failed previously!\n");
|
|
|
|
/* 1 Step - request firmware */
|
|
if (!desc->skip_request_firmware) {
|
|
ret = request_firmware(&fw, desc->fw_name, desc->dev);
|
|
if (ret) {
|
|
imgloader_err(desc, "Failed to locate %s(rc:%d)\n",
|
|
desc->fw_name, ret);
|
|
goto out;
|
|
}
|
|
fw_size = fw->size;
|
|
fw_data = fw->data;
|
|
}
|
|
|
|
/*
|
|
* 2nd Step - memcpy to own memory area, and
|
|
* then it must be returned their memory area.
|
|
*/
|
|
if (desc->ops->mem_setup)
|
|
ret = desc->ops->mem_setup(desc,
|
|
fw_data,
|
|
fw_size,
|
|
&fw_phys_base,
|
|
&fw_bin_size,
|
|
&fw_mem_size);
|
|
if (ret) {
|
|
imgloader_err(desc,
|
|
"firmware memory setup failed(rc:%d)\n", ret);
|
|
goto err_boot;
|
|
}
|
|
|
|
if (!fw_phys_base || !fw_bin_size | !fw_mem_size) {
|
|
imgloader_err(desc,
|
|
"Failed to return fw address/size\n");
|
|
goto err_boot;
|
|
}
|
|
|
|
priv->fw_phys_base = fw_phys_base;
|
|
priv->fw_bin_size = fw_bin_size;
|
|
priv->fw_mem_size = fw_mem_size;
|
|
|
|
/* 3rd Step - authentication & allow permission by S2MPU or NOT */
|
|
if (desc->s2mpu_support) {
|
|
ret = imgloader_verify_fw(desc);
|
|
if (ret) {
|
|
imgloader_err(desc,
|
|
"Failed to verify firmare (rc:%d)\n", ret);
|
|
goto err_deinit_image;
|
|
}
|
|
|
|
if (desc->ops->blk_pwron) {
|
|
ret = desc->ops->blk_pwron(desc);
|
|
if (ret) {
|
|
imgloader_err(desc,
|
|
"Failed to blk pwr on (rc:%d)\n",
|
|
ret);
|
|
goto err_deinit_image;
|
|
}
|
|
}
|
|
ret = imgloader_allow_permission(desc);
|
|
if (ret) {
|
|
imgloader_err(desc,
|
|
"Failed to allow permission (rc:%d)\n", ret);
|
|
goto err_deinit_image;
|
|
}
|
|
} else {
|
|
if (desc->ops->verify_fw) {
|
|
ret = desc->ops->verify_fw(desc,
|
|
priv->fw_phys_base,
|
|
priv->fw_bin_size,
|
|
priv->fw_mem_size);
|
|
if (ret) {
|
|
imgloader_err(desc,
|
|
"Failed to verify firmare (rc:%d)\n",
|
|
ret);
|
|
goto err_deinit_image;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* 4th Step - init image (Run system */
|
|
if (desc->ops->init_image)
|
|
ret = desc->ops->init_image(desc);
|
|
if (ret) {
|
|
imgloader_err(desc, "Initializing image failed(rc:%d)\n", ret);
|
|
goto err_deinit_image;
|
|
}
|
|
err_deinit_image:
|
|
if (ret && desc->ops->deinit_image)
|
|
desc->ops->deinit_image(desc);
|
|
err_boot:
|
|
if (!desc->skip_request_firmware)
|
|
release_firmware(fw);
|
|
out:
|
|
if (!ret)
|
|
imgloader_notify(desc, "off");
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(imgloader_boot);
|
|
|
|
/**
|
|
* imgloader_shutdown() - Shutdown a peripheral
|
|
* @desc: descriptor from imgloader_desc_init()
|
|
*
|
|
* Once the FW permission is released successfully,
|
|
* the FW cannot have the access permission again
|
|
* even if desc->ops->shutdown() and imgloader_notify()
|
|
* fail. Each driver must call imgloader_boot() to run
|
|
* FW again.
|
|
*/
|
|
void imgloader_shutdown(struct imgloader_desc *desc)
|
|
{
|
|
int ret;
|
|
|
|
if (desc->s2mpu_support) {
|
|
ret = imgloader_release_fw_permission(desc);
|
|
if (ret) {
|
|
imgloader_err(desc,
|
|
"Failed to release fw permission (rc:%d)\n",
|
|
ret);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (desc->ops->shutdown) {
|
|
if (desc->ops->shutdown(desc))
|
|
desc->shutdown_fail = true;
|
|
else
|
|
desc->shutdown_fail = false;
|
|
}
|
|
|
|
ret = imgloader_notify(desc, "off");
|
|
if (ret < 0)
|
|
imgloader_err(desc,
|
|
"failed to send OFF message to AOP rc:%d\n", ret);
|
|
}
|
|
EXPORT_SYMBOL(imgloader_shutdown);
|
|
|
|
static DEFINE_IDA(imgloader_ida);
|
|
|
|
/**
|
|
* imgloader_desc_init() - Initialize a imgloader descriptor
|
|
* @desc: descriptor to initialize
|
|
*
|
|
* Initialize a imgloader descriptor for use by other imgloader functions. This function
|
|
* must be called before calling imgloader_boot() or imgloader_shutdown().
|
|
*
|
|
* Returns 0 for success and -ERROR on failure.
|
|
*/
|
|
int imgloader_desc_init(struct imgloader_desc *desc)
|
|
{
|
|
struct imgloader_priv *priv;
|
|
int ret;
|
|
|
|
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
|
|
if (!priv)
|
|
return -ENOMEM;
|
|
|
|
desc->priv = priv;
|
|
priv->desc = desc;
|
|
|
|
priv->id = ret = ida_simple_get(&imgloader_ida,
|
|
0, IMGLOADER_NUM_DESC, GFP_KERNEL);
|
|
if (priv->id < 0)
|
|
goto err;
|
|
|
|
ret = imgloader_parse_dt(desc);
|
|
if (ret)
|
|
goto err_parse_dt;
|
|
|
|
return 0;
|
|
err_parse_dt:
|
|
ida_simple_remove(&imgloader_ida, priv->id);
|
|
err:
|
|
kfree(priv);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(imgloader_desc_init);
|
|
|
|
/**
|
|
* imgloader_desc_release() - Release a imgloader descriptor
|
|
* @desc: descriptor to free
|
|
*/
|
|
void imgloader_desc_release(struct imgloader_desc *desc)
|
|
{
|
|
struct imgloader_priv *priv = desc->priv;
|
|
|
|
desc->priv = NULL;
|
|
kfree(priv);
|
|
}
|
|
EXPORT_SYMBOL(imgloader_desc_release);
|
|
|
|
static int __init imgloader_init(void)
|
|
{
|
|
return 0;
|
|
}
|
|
subsys_initcall(imgloader_init);
|
|
|
|
MODULE_AUTHOR("Hosung Kim <hosung0.kim@samsung.com>");
|
|
MODULE_AUTHOR("Changki Kim <changki.kim@samsung.com>");
|
|
MODULE_AUTHOR("Donghyeok Choe <d7271.choe@samsung.com>");
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_DESCRIPTION("Image Loader");
|