162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci// 362306a36Sopenharmony_ci// Copyright(c) 2021-2022 Intel Corporation. All rights reserved. 462306a36Sopenharmony_ci// 562306a36Sopenharmony_ci// Authors: Cezary Rojewski <cezary.rojewski@intel.com> 662306a36Sopenharmony_ci// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> 762306a36Sopenharmony_ci// 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci#include <linux/firmware.h> 1062306a36Sopenharmony_ci#include <linux/kfifo.h> 1162306a36Sopenharmony_ci#include <linux/slab.h> 1262306a36Sopenharmony_ci#include "avs.h" 1362306a36Sopenharmony_ci#include "messages.h" 1462306a36Sopenharmony_ci 1562306a36Sopenharmony_ci/* Caller responsible for holding adev->modres_mutex. */ 1662306a36Sopenharmony_cistatic int avs_module_entry_index(struct avs_dev *adev, const guid_t *uuid) 1762306a36Sopenharmony_ci{ 1862306a36Sopenharmony_ci int i; 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci for (i = 0; i < adev->mods_info->count; i++) { 2162306a36Sopenharmony_ci struct avs_module_entry *module; 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci module = &adev->mods_info->entries[i]; 2462306a36Sopenharmony_ci if (guid_equal(&module->uuid, uuid)) 2562306a36Sopenharmony_ci return i; 2662306a36Sopenharmony_ci } 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci return -ENOENT; 2962306a36Sopenharmony_ci} 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_ci/* Caller responsible for holding adev->modres_mutex. */ 3262306a36Sopenharmony_cistatic int avs_module_id_entry_index(struct avs_dev *adev, u32 module_id) 3362306a36Sopenharmony_ci{ 3462306a36Sopenharmony_ci int i; 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_ci for (i = 0; i < adev->mods_info->count; i++) { 3762306a36Sopenharmony_ci struct avs_module_entry *module; 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci module = &adev->mods_info->entries[i]; 4062306a36Sopenharmony_ci if (module->module_id == module_id) 4162306a36Sopenharmony_ci return i; 4262306a36Sopenharmony_ci } 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_ci return -ENOENT; 4562306a36Sopenharmony_ci} 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_ciint avs_get_module_entry(struct avs_dev *adev, const guid_t *uuid, struct avs_module_entry *entry) 4862306a36Sopenharmony_ci{ 4962306a36Sopenharmony_ci int idx; 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_ci mutex_lock(&adev->modres_mutex); 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_ci idx = avs_module_entry_index(adev, uuid); 5462306a36Sopenharmony_ci if (idx >= 0) 5562306a36Sopenharmony_ci memcpy(entry, &adev->mods_info->entries[idx], sizeof(*entry)); 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci mutex_unlock(&adev->modres_mutex); 5862306a36Sopenharmony_ci return (idx < 0) ? idx : 0; 5962306a36Sopenharmony_ci} 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ciint avs_get_module_id_entry(struct avs_dev *adev, u32 module_id, struct avs_module_entry *entry) 6262306a36Sopenharmony_ci{ 6362306a36Sopenharmony_ci int idx; 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci mutex_lock(&adev->modres_mutex); 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci idx = avs_module_id_entry_index(adev, module_id); 6862306a36Sopenharmony_ci if (idx >= 0) 6962306a36Sopenharmony_ci memcpy(entry, &adev->mods_info->entries[idx], sizeof(*entry)); 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci mutex_unlock(&adev->modres_mutex); 7262306a36Sopenharmony_ci return (idx < 0) ? idx : 0; 7362306a36Sopenharmony_ci} 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ciint avs_get_module_id(struct avs_dev *adev, const guid_t *uuid) 7662306a36Sopenharmony_ci{ 7762306a36Sopenharmony_ci struct avs_module_entry module; 7862306a36Sopenharmony_ci int ret; 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci ret = avs_get_module_entry(adev, uuid, &module); 8162306a36Sopenharmony_ci return !ret ? module.module_id : -ENOENT; 8262306a36Sopenharmony_ci} 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_cibool avs_is_module_ida_empty(struct avs_dev *adev, u32 module_id) 8562306a36Sopenharmony_ci{ 8662306a36Sopenharmony_ci bool ret = false; 8762306a36Sopenharmony_ci int idx; 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci mutex_lock(&adev->modres_mutex); 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci idx = avs_module_id_entry_index(adev, module_id); 9262306a36Sopenharmony_ci if (idx >= 0) 9362306a36Sopenharmony_ci ret = ida_is_empty(adev->mod_idas[idx]); 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_ci mutex_unlock(&adev->modres_mutex); 9662306a36Sopenharmony_ci return ret; 9762306a36Sopenharmony_ci} 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_ci/* Caller responsible for holding adev->modres_mutex. */ 10062306a36Sopenharmony_cistatic void avs_module_ida_destroy(struct avs_dev *adev) 10162306a36Sopenharmony_ci{ 10262306a36Sopenharmony_ci int i = adev->mods_info ? adev->mods_info->count : 0; 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci while (i--) { 10562306a36Sopenharmony_ci ida_destroy(adev->mod_idas[i]); 10662306a36Sopenharmony_ci kfree(adev->mod_idas[i]); 10762306a36Sopenharmony_ci } 10862306a36Sopenharmony_ci kfree(adev->mod_idas); 10962306a36Sopenharmony_ci} 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci/* Caller responsible for holding adev->modres_mutex. */ 11262306a36Sopenharmony_cistatic int 11362306a36Sopenharmony_ciavs_module_ida_alloc(struct avs_dev *adev, struct avs_mods_info *newinfo, bool purge) 11462306a36Sopenharmony_ci{ 11562306a36Sopenharmony_ci struct avs_mods_info *oldinfo = adev->mods_info; 11662306a36Sopenharmony_ci struct ida **ida_ptrs; 11762306a36Sopenharmony_ci u32 tocopy_count = 0; 11862306a36Sopenharmony_ci int i; 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci if (!purge && oldinfo) { 12162306a36Sopenharmony_ci if (oldinfo->count >= newinfo->count) 12262306a36Sopenharmony_ci dev_warn(adev->dev, "refreshing %d modules info with %d\n", 12362306a36Sopenharmony_ci oldinfo->count, newinfo->count); 12462306a36Sopenharmony_ci tocopy_count = oldinfo->count; 12562306a36Sopenharmony_ci } 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci ida_ptrs = kcalloc(newinfo->count, sizeof(*ida_ptrs), GFP_KERNEL); 12862306a36Sopenharmony_ci if (!ida_ptrs) 12962306a36Sopenharmony_ci return -ENOMEM; 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci if (tocopy_count) 13262306a36Sopenharmony_ci memcpy(ida_ptrs, adev->mod_idas, tocopy_count * sizeof(*ida_ptrs)); 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci for (i = tocopy_count; i < newinfo->count; i++) { 13562306a36Sopenharmony_ci ida_ptrs[i] = kzalloc(sizeof(**ida_ptrs), GFP_KERNEL); 13662306a36Sopenharmony_ci if (!ida_ptrs[i]) { 13762306a36Sopenharmony_ci while (i--) 13862306a36Sopenharmony_ci kfree(ida_ptrs[i]); 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci kfree(ida_ptrs); 14162306a36Sopenharmony_ci return -ENOMEM; 14262306a36Sopenharmony_ci } 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci ida_init(ida_ptrs[i]); 14562306a36Sopenharmony_ci } 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_ci /* If old elements have been reused, don't wipe them. */ 14862306a36Sopenharmony_ci if (tocopy_count) 14962306a36Sopenharmony_ci kfree(adev->mod_idas); 15062306a36Sopenharmony_ci else 15162306a36Sopenharmony_ci avs_module_ida_destroy(adev); 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci adev->mod_idas = ida_ptrs; 15462306a36Sopenharmony_ci return 0; 15562306a36Sopenharmony_ci} 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ciint avs_module_info_init(struct avs_dev *adev, bool purge) 15862306a36Sopenharmony_ci{ 15962306a36Sopenharmony_ci struct avs_mods_info *info; 16062306a36Sopenharmony_ci int ret; 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci ret = avs_ipc_get_modules_info(adev, &info); 16362306a36Sopenharmony_ci if (ret) 16462306a36Sopenharmony_ci return AVS_IPC_RET(ret); 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci mutex_lock(&adev->modres_mutex); 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ci ret = avs_module_ida_alloc(adev, info, purge); 16962306a36Sopenharmony_ci if (ret < 0) { 17062306a36Sopenharmony_ci dev_err(adev->dev, "initialize module idas failed: %d\n", ret); 17162306a36Sopenharmony_ci goto exit; 17262306a36Sopenharmony_ci } 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_ci /* Refresh current information with newly received table. */ 17562306a36Sopenharmony_ci kfree(adev->mods_info); 17662306a36Sopenharmony_ci adev->mods_info = info; 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ciexit: 17962306a36Sopenharmony_ci mutex_unlock(&adev->modres_mutex); 18062306a36Sopenharmony_ci return ret; 18162306a36Sopenharmony_ci} 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_civoid avs_module_info_free(struct avs_dev *adev) 18462306a36Sopenharmony_ci{ 18562306a36Sopenharmony_ci mutex_lock(&adev->modres_mutex); 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_ci avs_module_ida_destroy(adev); 18862306a36Sopenharmony_ci kfree(adev->mods_info); 18962306a36Sopenharmony_ci adev->mods_info = NULL; 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci mutex_unlock(&adev->modres_mutex); 19262306a36Sopenharmony_ci} 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_ciint avs_module_id_alloc(struct avs_dev *adev, u16 module_id) 19562306a36Sopenharmony_ci{ 19662306a36Sopenharmony_ci int ret, idx, max_id; 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_ci mutex_lock(&adev->modres_mutex); 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_ci idx = avs_module_id_entry_index(adev, module_id); 20162306a36Sopenharmony_ci if (idx == -ENOENT) { 20262306a36Sopenharmony_ci dev_err(adev->dev, "invalid module id: %d", module_id); 20362306a36Sopenharmony_ci ret = -EINVAL; 20462306a36Sopenharmony_ci goto exit; 20562306a36Sopenharmony_ci } 20662306a36Sopenharmony_ci max_id = adev->mods_info->entries[idx].instance_max_count - 1; 20762306a36Sopenharmony_ci ret = ida_alloc_max(adev->mod_idas[idx], max_id, GFP_KERNEL); 20862306a36Sopenharmony_ciexit: 20962306a36Sopenharmony_ci mutex_unlock(&adev->modres_mutex); 21062306a36Sopenharmony_ci return ret; 21162306a36Sopenharmony_ci} 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_civoid avs_module_id_free(struct avs_dev *adev, u16 module_id, u8 instance_id) 21462306a36Sopenharmony_ci{ 21562306a36Sopenharmony_ci int idx; 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ci mutex_lock(&adev->modres_mutex); 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_ci idx = avs_module_id_entry_index(adev, module_id); 22062306a36Sopenharmony_ci if (idx == -ENOENT) { 22162306a36Sopenharmony_ci dev_err(adev->dev, "invalid module id: %d", module_id); 22262306a36Sopenharmony_ci goto exit; 22362306a36Sopenharmony_ci } 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_ci ida_free(adev->mod_idas[idx], instance_id); 22662306a36Sopenharmony_ciexit: 22762306a36Sopenharmony_ci mutex_unlock(&adev->modres_mutex); 22862306a36Sopenharmony_ci} 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_ci/* 23162306a36Sopenharmony_ci * Once driver loads FW it should keep it in memory, so we are not affected 23262306a36Sopenharmony_ci * by FW removal from filesystem or even worse by loading different FW at 23362306a36Sopenharmony_ci * runtime suspend/resume. 23462306a36Sopenharmony_ci */ 23562306a36Sopenharmony_ciint avs_request_firmware(struct avs_dev *adev, const struct firmware **fw_p, const char *name) 23662306a36Sopenharmony_ci{ 23762306a36Sopenharmony_ci struct avs_fw_entry *entry; 23862306a36Sopenharmony_ci int ret; 23962306a36Sopenharmony_ci 24062306a36Sopenharmony_ci /* first check in list if it is not already loaded */ 24162306a36Sopenharmony_ci list_for_each_entry(entry, &adev->fw_list, node) { 24262306a36Sopenharmony_ci if (!strcmp(name, entry->name)) { 24362306a36Sopenharmony_ci *fw_p = entry->fw; 24462306a36Sopenharmony_ci return 0; 24562306a36Sopenharmony_ci } 24662306a36Sopenharmony_ci } 24762306a36Sopenharmony_ci 24862306a36Sopenharmony_ci /* FW is not loaded, let's load it now and add to the list */ 24962306a36Sopenharmony_ci entry = kzalloc(sizeof(*entry), GFP_KERNEL); 25062306a36Sopenharmony_ci if (!entry) 25162306a36Sopenharmony_ci return -ENOMEM; 25262306a36Sopenharmony_ci 25362306a36Sopenharmony_ci entry->name = kstrdup(name, GFP_KERNEL); 25462306a36Sopenharmony_ci if (!entry->name) { 25562306a36Sopenharmony_ci kfree(entry); 25662306a36Sopenharmony_ci return -ENOMEM; 25762306a36Sopenharmony_ci } 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_ci ret = request_firmware(&entry->fw, name, adev->dev); 26062306a36Sopenharmony_ci if (ret < 0) { 26162306a36Sopenharmony_ci kfree(entry->name); 26262306a36Sopenharmony_ci kfree(entry); 26362306a36Sopenharmony_ci return ret; 26462306a36Sopenharmony_ci } 26562306a36Sopenharmony_ci 26662306a36Sopenharmony_ci *fw_p = entry->fw; 26762306a36Sopenharmony_ci 26862306a36Sopenharmony_ci list_add_tail(&entry->node, &adev->fw_list); 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_ci return 0; 27162306a36Sopenharmony_ci} 27262306a36Sopenharmony_ci 27362306a36Sopenharmony_ci/* 27462306a36Sopenharmony_ci * Release single FW entry, used to handle errors in functions calling 27562306a36Sopenharmony_ci * avs_request_firmware() 27662306a36Sopenharmony_ci */ 27762306a36Sopenharmony_civoid avs_release_last_firmware(struct avs_dev *adev) 27862306a36Sopenharmony_ci{ 27962306a36Sopenharmony_ci struct avs_fw_entry *entry; 28062306a36Sopenharmony_ci 28162306a36Sopenharmony_ci entry = list_last_entry(&adev->fw_list, typeof(*entry), node); 28262306a36Sopenharmony_ci 28362306a36Sopenharmony_ci list_del(&entry->node); 28462306a36Sopenharmony_ci release_firmware(entry->fw); 28562306a36Sopenharmony_ci kfree(entry->name); 28662306a36Sopenharmony_ci kfree(entry); 28762306a36Sopenharmony_ci} 28862306a36Sopenharmony_ci 28962306a36Sopenharmony_ci/* 29062306a36Sopenharmony_ci * Release all FW entries, used on driver removal 29162306a36Sopenharmony_ci */ 29262306a36Sopenharmony_civoid avs_release_firmwares(struct avs_dev *adev) 29362306a36Sopenharmony_ci{ 29462306a36Sopenharmony_ci struct avs_fw_entry *entry, *tmp; 29562306a36Sopenharmony_ci 29662306a36Sopenharmony_ci list_for_each_entry_safe(entry, tmp, &adev->fw_list, node) { 29762306a36Sopenharmony_ci list_del(&entry->node); 29862306a36Sopenharmony_ci release_firmware(entry->fw); 29962306a36Sopenharmony_ci kfree(entry->name); 30062306a36Sopenharmony_ci kfree(entry); 30162306a36Sopenharmony_ci } 30262306a36Sopenharmony_ci} 303