#include "sof-priv.h"
 #include "ops.h"
 
+/* The module ID includes the id of the library it is part of at offset 12 */
+#define SOF_IPC4_MOD_LIB_ID_SHIFT      12
+
 static size_t sof_ipc4_fw_parse_ext_man(struct snd_sof_dev *sdev,
                                        struct sof_ipc4_fw_library *fw_lib)
 {
                return -EINVAL;
        }
 
-       dev_info(sdev->dev, "Loaded firmware version: %u.%u.%u.%u\n",
-                fw_header->major_version, fw_header->minor_version,
+       dev_info(sdev->dev, "Loaded firmware library: %s, version: %u.%u.%u.%u\n",
+                fw_header->name, fw_header->major_version, fw_header->minor_version,
                 fw_header->hotfix_version, fw_header->build_version);
-       dev_dbg(sdev->dev, "Firmware name: %s, header length: %u, module count: %u\n",
-               fw_header->name, fw_header->len, fw_header->num_module_entries);
+       dev_dbg(sdev->dev, "Header length: %u, module count: %u\n",
+               fw_header->len, fw_header->num_module_entries);
 
        fw_lib->modules = devm_kmalloc_array(sdev->dev, fw_header->num_module_entries,
                                             sizeof(*fw_module), GFP_KERNEL);
        if (!fw_lib->modules)
                return -ENOMEM;
 
+       fw_lib->name = fw_header->name;
        fw_lib->num_modules = fw_header->num_module_entries;
        fw_module = fw_lib->modules;
 
        return payload_offset;
 }
 
+static int sof_ipc4_load_library_by_uuid(struct snd_sof_dev *sdev,
+                                        unsigned long lib_id, const guid_t *uuid)
+{
+       struct sof_ipc4_fw_data *ipc4_data = sdev->private;
+       struct sof_ipc4_fw_library *fw_lib;
+       const char *fw_filename;
+       size_t payload_offset;
+       int ret, i, err;
+
+       if (!sdev->pdata->fw_lib_prefix) {
+               dev_err(sdev->dev,
+                       "Library loading is not supported due to not set library path\n");
+               return -EINVAL;
+       }
+
+       if (!ipc4_data->load_library) {
+               dev_err(sdev->dev, "Library loading is not supported on this platform\n");
+               return -EOPNOTSUPP;
+       }
+
+       fw_lib = devm_kzalloc(sdev->dev, sizeof(*fw_lib), GFP_KERNEL);
+       if (!fw_lib)
+               return -ENOMEM;
+
+       fw_filename = kasprintf(GFP_KERNEL, "%s/%pUL.bin",
+                               sdev->pdata->fw_lib_prefix, uuid);
+       if (!fw_filename) {
+               ret = -ENOMEM;
+               goto free_fw_lib;
+       }
+
+       ret = request_firmware(&fw_lib->sof_fw.fw, fw_filename, sdev->dev);
+       if (ret < 0) {
+               dev_err(sdev->dev, "Library file '%s' is missing\n", fw_filename);
+               goto free_filename;
+       } else {
+               dev_dbg(sdev->dev, "Library file '%s' loaded\n", fw_filename);
+       }
+
+       payload_offset = sof_ipc4_fw_parse_ext_man(sdev, fw_lib);
+       if (payload_offset <= 0) {
+               if (!payload_offset)
+                       ret = -EINVAL;
+               else
+                       ret = payload_offset;
+
+               goto release;
+       }
+
+       fw_lib->sof_fw.payload_offset = payload_offset;
+       fw_lib->id = lib_id;
+
+       /* Fix up the module ID numbers within the library */
+       for (i = 0; i < fw_lib->num_modules; i++)
+               fw_lib->modules[i].man4_module_entry.id |= (lib_id << SOF_IPC4_MOD_LIB_ID_SHIFT);
+
+       /*
+        * Make sure that the DSP is booted and stays up while attempting the
+        * loading the library for the first time
+        */
+       ret = pm_runtime_resume_and_get(sdev->dev);
+       if (ret < 0 && ret != -EACCES) {
+               dev_err_ratelimited(sdev->dev, "%s: pm_runtime resume failed: %d\n",
+                                   __func__, ret);
+               goto release;
+       }
+
+       ret = ipc4_data->load_library(sdev, fw_lib, false);
+
+       pm_runtime_mark_last_busy(sdev->dev);
+       err = pm_runtime_put_autosuspend(sdev->dev);
+       if (err < 0)
+               dev_err_ratelimited(sdev->dev, "%s: pm_runtime idle failed: %d\n",
+                                   __func__, err);
+
+       if (ret)
+               goto release;
+
+       ret = xa_insert(&ipc4_data->fw_lib_xa, lib_id, fw_lib, GFP_KERNEL);
+       if (unlikely(ret))
+               goto release;
+
+       kfree(fw_filename);
+
+       return 0;
+
+release:
+       release_firmware(fw_lib->sof_fw.fw);
+       /* Allocated within sof_ipc4_fw_parse_ext_man() */
+       devm_kfree(sdev->dev, fw_lib->modules);
+free_filename:
+       kfree(fw_filename);
+free_fw_lib:
+       devm_kfree(sdev->dev, fw_lib);
+
+       return ret;
+}
+
 struct sof_ipc4_fw_module *sof_ipc4_find_module_by_uuid(struct snd_sof_dev *sdev,
                                                        const guid_t *uuid)
 {
        struct sof_ipc4_fw_data *ipc4_data = sdev->private;
        struct sof_ipc4_fw_library *fw_lib;
        unsigned long lib_id;
-       int i;
+       int i, ret;
 
        if (guid_is_null(uuid))
                return NULL;
                }
        }
 
+       /*
+        * Do not attempt to load external library in case the maximum number of
+        * firmware libraries have been already loaded
+        */
+       if ((lib_id + 1) == ipc4_data->max_libs_count) {
+               dev_err(sdev->dev,
+                       "%s: Maximum allowed number of libraries reached (%u)\n",
+                       __func__, ipc4_data->max_libs_count);
+               return NULL;
+       }
+
+       /* The module cannot be found, try to load it as a library */
+       ret = sof_ipc4_load_library_by_uuid(sdev, lib_id + 1, uuid);
+       if (ret)
+               return NULL;
+
+       /* Look for the module in the newly loaded library, it should be available now */
+       xa_for_each_start(&ipc4_data->fw_lib_xa, lib_id, fw_lib, lib_id) {
+               for (i = 0; i < fw_lib->num_modules; i++) {
+                       if (guid_equal(uuid, &fw_lib->modules[i].man4_module_entry.uuid))
+                               return &fw_lib->modules[i];
+               }
+       }
+
        return NULL;
 }
 
        return ret;
 }
 
+int sof_ipc4_reload_fw_libraries(struct snd_sof_dev *sdev)
+{
+       struct sof_ipc4_fw_data *ipc4_data = sdev->private;
+       struct sof_ipc4_fw_library *fw_lib;
+       unsigned long lib_id;
+       int ret = 0;
+
+       xa_for_each_start(&ipc4_data->fw_lib_xa, lib_id, fw_lib, 1) {
+               ret = ipc4_data->load_library(sdev, fw_lib, true);
+               if (ret) {
+                       dev_err(sdev->dev, "%s: Failed to reload library: %s, %d\n",
+                               __func__, fw_lib->name, ret);
+                       break;
+               }
+       }
+
+       return ret;
+}
+
 const struct sof_ipc_fw_loader_ops ipc4_loader_ops = {
        .validate = sof_ipc4_validate_firmware,
        .parse_ext_manifest = sof_ipc4_fw_parse_basefw_ext_man,