blob: ad4ae74c7569b40cfb6113eb810212fe3f63c4ba [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
/*
*
* (C) COPYRIGHT 2020-2022 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>
#include <linux/version.h>
#if CONFIG_SYSFS
#define CSF_FIRMWARE_CFG_SYSFS_DIR_NAME "firmware_config"
/**
* struct firmware_config - Configuration item within the MCU firmware
*
* @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
*
* 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.
*/
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, 0444);
static FW_CFG_ATTR(max, 0444);
static FW_CFG_ATTR(cur, 0644);
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,
};
#if (KERNEL_VERSION(5, 2, 0) <= LINUX_VERSION_CODE)
ATTRIBUTE_GROUPS(fw_cfg);
#endif
static struct kobj_type fw_cfg_kobj_type = {
.release = &fw_cfg_kobj_release,
.sysfs_ops = &fw_cfg_ops,
#if (KERNEL_VERSION(5, 2, 0) <= LINUX_VERSION_CODE)
.default_groups = fw_cfg_groups,
#else
.default_attrs = fw_cfg_attrs,
#endif
};
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 kbase_csf_mcu_fw *const 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 kbase_csf_mcu_fw *const fw,
const u32 *entry, unsigned int size)
{
return 0;
}
#endif /* CONFIG_SYSFS */