// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
|
/*
|
*
|
* (C) COPYRIGHT 2020-2021 ARM Limited. All rights reserved.
|
*
|
* This program is free software and is provided to you under the terms of the
|
* GNU General Public License version 2 as published by the Free Software
|
* Foundation, and any use by you of this program is subject to the terms
|
* of such GNU 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, you can access it online at
|
* http://www.gnu.org/licenses/gpl-2.0.html.
|
*
|
*/
|
|
#include <mali_kbase.h>
|
#include "mali_kbase_csf_firmware_cfg.h"
|
#include <mali_kbase_reset_gpu.h>
|
|
#if CONFIG_SYSFS
|
#define CSF_FIRMWARE_CFG_SYSFS_DIR_NAME "firmware_config"
|
|
/**
|
* struct firmware_config - Configuration item within the MCU firmware
|
*
|
* The firmware may expose configuration options. Each option has a name, the
|
* address where the option is controlled and the minimum and maximum values
|
* that the option can take.
|
*
|
* @node: List head linking all options to
|
* kbase_device:csf.firmware_config
|
* @kbdev: Pointer to the Kbase device
|
* @kobj: Kobject corresponding to the sysfs sub-directory,
|
* inside CSF_FIRMWARE_CFG_SYSFS_DIR_NAME directory,
|
* representing the configuration option @name.
|
* @kobj_inited: kobject initialization state
|
* @updatable: Indicates whether config items can be updated with
|
* FIRMWARE_CONFIG_UPDATE
|
* @name: NUL-terminated string naming the option
|
* @address: The address in the firmware image of the configuration option
|
* @min: The lowest legal value of the configuration option
|
* @max: The maximum legal value of the configuration option
|
* @cur_val: The current value of the configuration option
|
*/
|
struct firmware_config {
|
struct list_head node;
|
struct kbase_device *kbdev;
|
struct kobject kobj;
|
bool kobj_inited;
|
bool updatable;
|
char *name;
|
u32 address;
|
u32 min;
|
u32 max;
|
u32 cur_val;
|
};
|
|
#define FW_CFG_ATTR(_name, _mode) \
|
struct attribute fw_cfg_attr_##_name = { \
|
.name = __stringify(_name), \
|
.mode = VERIFY_OCTAL_PERMISSIONS(_mode), \
|
}
|
|
static FW_CFG_ATTR(min, S_IRUGO);
|
static FW_CFG_ATTR(max, S_IRUGO);
|
static FW_CFG_ATTR(cur, S_IRUGO | S_IWUSR);
|
|
static void fw_cfg_kobj_release(struct kobject *kobj)
|
{
|
struct firmware_config *config =
|
container_of(kobj, struct firmware_config, kobj);
|
|
kfree(config);
|
}
|
|
static ssize_t show_fw_cfg(struct kobject *kobj,
|
struct attribute *attr, char *buf)
|
{
|
struct firmware_config *config =
|
container_of(kobj, struct firmware_config, kobj);
|
struct kbase_device *kbdev = config->kbdev;
|
u32 val = 0;
|
|
if (!kbdev)
|
return -ENODEV;
|
|
if (attr == &fw_cfg_attr_max)
|
val = config->max;
|
else if (attr == &fw_cfg_attr_min)
|
val = config->min;
|
else if (attr == &fw_cfg_attr_cur) {
|
unsigned long flags;
|
|
spin_lock_irqsave(&kbdev->hwaccess_lock, flags);
|
val = config->cur_val;
|
spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
|
} else {
|
dev_warn(kbdev->dev,
|
"Unexpected read from entry %s/%s",
|
config->name, attr->name);
|
return -EINVAL;
|
}
|
|
return snprintf(buf, PAGE_SIZE, "%u\n", val);
|
}
|
|
static ssize_t store_fw_cfg(struct kobject *kobj,
|
struct attribute *attr,
|
const char *buf,
|
size_t count)
|
{
|
struct firmware_config *config =
|
container_of(kobj, struct firmware_config, kobj);
|
struct kbase_device *kbdev = config->kbdev;
|
|
if (!kbdev)
|
return -ENODEV;
|
|
if (attr == &fw_cfg_attr_cur) {
|
unsigned long flags;
|
u32 val;
|
int ret = kstrtouint(buf, 0, &val);
|
|
if (ret) {
|
dev_err(kbdev->dev,
|
"Couldn't process %s/%s write operation.\n"
|
"Use format <value>\n",
|
config->name, attr->name);
|
return -EINVAL;
|
}
|
|
if ((val < config->min) || (val > config->max))
|
return -EINVAL;
|
|
spin_lock_irqsave(&kbdev->hwaccess_lock, flags);
|
if (config->cur_val == val) {
|
spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
|
return count;
|
}
|
|
/* If configuration update cannot be performed with
|
* FIRMWARE_CONFIG_UPDATE then we need to do a
|
* silent reset before we update the memory.
|
*/
|
if (!config->updatable) {
|
/*
|
* If there is already a GPU reset pending then inform
|
* the User to retry the write.
|
*/
|
if (kbase_reset_gpu_silent(kbdev)) {
|
spin_unlock_irqrestore(&kbdev->hwaccess_lock,
|
flags);
|
return -EAGAIN;
|
}
|
}
|
|
/*
|
* GPU reset request has been placed, now update the
|
* firmware image. GPU reset will take place only after
|
* hwaccess_lock is released.
|
* Update made to firmware image in memory would not
|
* be lost on GPU reset as configuration entries reside
|
* in the RONLY section of firmware image, which is not
|
* reloaded on firmware reboot due to GPU reset.
|
*/
|
kbase_csf_update_firmware_memory(
|
kbdev, config->address, val);
|
|
config->cur_val = val;
|
|
spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
|
|
/* If we can update the config without firmware reset then
|
* we need to just trigger FIRMWARE_CONFIG_UPDATE.
|
*/
|
if (config->updatable) {
|
ret = kbase_csf_trigger_firmware_config_update(kbdev);
|
if (ret)
|
return ret;
|
}
|
|
/* Wait for the config update to take effect */
|
if (!config->updatable)
|
kbase_reset_gpu_wait(kbdev);
|
} else {
|
dev_warn(kbdev->dev,
|
"Unexpected write to entry %s/%s",
|
config->name, attr->name);
|
return -EINVAL;
|
}
|
|
return count;
|
}
|
|
static const struct sysfs_ops fw_cfg_ops = {
|
.show = &show_fw_cfg,
|
.store = &store_fw_cfg,
|
};
|
|
static struct attribute *fw_cfg_attrs[] = {
|
&fw_cfg_attr_min,
|
&fw_cfg_attr_max,
|
&fw_cfg_attr_cur,
|
NULL,
|
};
|
|
static struct kobj_type fw_cfg_kobj_type = {
|
.release = &fw_cfg_kobj_release,
|
.sysfs_ops = &fw_cfg_ops,
|
.default_attrs = fw_cfg_attrs,
|
};
|
|
int kbase_csf_firmware_cfg_init(struct kbase_device *kbdev)
|
{
|
struct firmware_config *config;
|
|
kbdev->csf.fw_cfg_kobj = kobject_create_and_add(
|
CSF_FIRMWARE_CFG_SYSFS_DIR_NAME, &kbdev->dev->kobj);
|
if (!kbdev->csf.fw_cfg_kobj) {
|
kobject_put(kbdev->csf.fw_cfg_kobj);
|
dev_err(kbdev->dev,
|
"Creation of %s sysfs sub-directory failed\n",
|
CSF_FIRMWARE_CFG_SYSFS_DIR_NAME);
|
return -ENOMEM;
|
}
|
|
list_for_each_entry(config, &kbdev->csf.firmware_config, node) {
|
int err;
|
|
kbase_csf_read_firmware_memory(kbdev, config->address,
|
&config->cur_val);
|
|
err = kobject_init_and_add(&config->kobj, &fw_cfg_kobj_type,
|
kbdev->csf.fw_cfg_kobj, "%s", config->name);
|
if (err) {
|
kobject_put(&config->kobj);
|
dev_err(kbdev->dev,
|
"Creation of %s sysfs sub-directory failed\n",
|
config->name);
|
return err;
|
}
|
|
config->kobj_inited = true;
|
}
|
|
return 0;
|
}
|
|
void kbase_csf_firmware_cfg_term(struct kbase_device *kbdev)
|
{
|
while (!list_empty(&kbdev->csf.firmware_config)) {
|
struct firmware_config *config;
|
|
config = list_first_entry(&kbdev->csf.firmware_config,
|
struct firmware_config, node);
|
list_del(&config->node);
|
|
if (config->kobj_inited) {
|
kobject_del(&config->kobj);
|
kobject_put(&config->kobj);
|
} else
|
kfree(config);
|
}
|
|
kobject_del(kbdev->csf.fw_cfg_kobj);
|
kobject_put(kbdev->csf.fw_cfg_kobj);
|
}
|
|
int kbase_csf_firmware_cfg_option_entry_parse(struct kbase_device *kbdev,
|
const struct firmware *fw,
|
const u32 *entry,
|
unsigned int size, bool updatable)
|
{
|
const char *name = (char *)&entry[3];
|
struct firmware_config *config;
|
const unsigned int name_len = size - CONFIGURATION_ENTRY_NAME_OFFSET;
|
|
/* Allocate enough space for struct firmware_config and the
|
* configuration option name (with NULL termination)
|
*/
|
config = kzalloc(sizeof(*config) + name_len + 1, GFP_KERNEL);
|
|
if (!config)
|
return -ENOMEM;
|
|
config->kbdev = kbdev;
|
config->updatable = updatable;
|
config->name = (char *)(config+1);
|
config->address = entry[0];
|
config->min = entry[1];
|
config->max = entry[2];
|
|
memcpy(config->name, name, name_len);
|
config->name[name_len] = 0;
|
|
list_add(&config->node, &kbdev->csf.firmware_config);
|
|
dev_dbg(kbdev->dev, "Configuration option '%s' at 0x%x range %u-%u",
|
config->name, config->address,
|
config->min, config->max);
|
|
return 0;
|
}
|
#else
|
int kbase_csf_firmware_cfg_init(struct kbase_device *kbdev)
|
{
|
return 0;
|
}
|
|
void kbase_csf_firmware_cfg_term(struct kbase_device *kbdev)
|
{
|
/* !CONFIG_SYSFS: Nothing to do here */
|
}
|
|
int kbase_csf_firmware_cfg_option_entry_parse(struct kbase_device *kbdev,
|
const struct firmware *fw,
|
const u32 *entry, unsigned int size)
|
{
|
return 0;
|
}
|
#endif /* CONFIG_SYSFS */
|