root/drivers/firmware/qcom/qcom_qseecom.c
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Driver for Qualcomm Secure Execution Environment (SEE) interface (QSEECOM).
 * Responsible for setting up and managing QSEECOM client devices.
 *
 * Copyright (C) 2023 Maximilian Luz <luzmaximilian@gmail.com>
 */
#include <linux/auxiliary_bus.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/types.h>

#include <linux/firmware/qcom/qcom_qseecom.h>
#include <linux/firmware/qcom/qcom_scm.h>

struct qseecom_app_desc {
        const char *app_name;
        const char *dev_name;
};

static void qseecom_client_release(struct device *dev)
{
        struct qseecom_client *client;

        client = container_of(dev, struct qseecom_client, aux_dev.dev);
        kfree(client);
}

static void qseecom_client_remove(void *data)
{
        struct qseecom_client *client = data;

        auxiliary_device_delete(&client->aux_dev);
        auxiliary_device_uninit(&client->aux_dev);
}

static int qseecom_client_register(struct platform_device *qseecom_dev,
                                   const struct qseecom_app_desc *desc)
{
        struct qseecom_client *client;
        u32 app_id;
        int ret;

        /* Try to find the app ID, skip device if not found */
        ret = qcom_scm_qseecom_app_get_id(desc->app_name, &app_id);
        if (ret)
                return ret == -ENOENT ? 0 : ret;

        dev_info(&qseecom_dev->dev, "setting up client for %s\n", desc->app_name);

        /* Allocate and set-up the client device */
        client = kzalloc_obj(*client);
        if (!client)
                return -ENOMEM;

        client->aux_dev.name = desc->dev_name;
        client->aux_dev.dev.parent = &qseecom_dev->dev;
        client->aux_dev.dev.release = qseecom_client_release;
        client->app_id = app_id;

        ret = auxiliary_device_init(&client->aux_dev);
        if (ret) {
                kfree(client);
                return ret;
        }

        ret = auxiliary_device_add(&client->aux_dev);
        if (ret) {
                auxiliary_device_uninit(&client->aux_dev);
                return ret;
        }

        ret = devm_add_action_or_reset(&qseecom_dev->dev, qseecom_client_remove, client);
        if (ret)
                return ret;

        return 0;
}

/*
 * List of supported applications. One client device will be created per entry,
 * assuming the app has already been loaded (usually by firmware bootloaders)
 * and its ID can be queried successfully.
 */
static const struct qseecom_app_desc qcom_qseecom_apps[] = {
        { "qcom.tz.uefisecapp", "uefisecapp" },
};

static int qcom_qseecom_probe(struct platform_device *qseecom_dev)
{
        int ret;
        int i;

        /* Set up client devices for each base application */
        for (i = 0; i < ARRAY_SIZE(qcom_qseecom_apps); i++) {
                ret = qseecom_client_register(qseecom_dev, &qcom_qseecom_apps[i]);
                if (ret)
                        return ret;
        }

        return 0;
}

static struct platform_driver qcom_qseecom_driver = {
        .driver = {
                .name   = "qcom_qseecom",
        },
        .probe = qcom_qseecom_probe,
};

static int __init qcom_qseecom_init(void)
{
        return platform_driver_register(&qcom_qseecom_driver);
}
subsys_initcall(qcom_qseecom_init);

MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
MODULE_DESCRIPTION("Driver for the Qualcomm SEE (QSEECOM) interface");
MODULE_LICENSE("GPL");