| // SPDX-License-Identifier: GPL-2.0 | |
| /* Copyright (C) 2023-2026 Intel Corporation */ | |
| #include <linux/bug.h> | |
| #include <linux/cleanup.h> | |
| #include <linux/container_of.h> | |
| #include <linux/device.h> | |
| #include <linux/dev_printk.h> | |
| #include <linux/kobject.h> | |
| #include <linux/list.h> | |
| #include <linux/slab.h> | |
| #include <linux/sprintf.h> | |
| #include <linux/string.h> | |
| #include <linux/types.h> | |
| #include <linux/uuid.h> | |
| #include "issei_dev.h" | |
| #include "fw_client.h" | |
| /* | |
| * Specific attribute handlers for fw_clients kset. | |
| * Provide only show function as all fw_client attributes are read-only. | |
| */ | |
| struct issei_fw_cl_attr { | |
| struct attribute attr; | |
| ssize_t (*show)(struct issei_fw_client *fw_cl, const struct issei_fw_cl_attr *attr, | |
| char *buf); | |
| }; | |
| #define to_issei_fw_cl_attr(x) container_of_const(x, struct issei_fw_cl_attr, attr) | |
| static ssize_t fw_cl_attr_show(struct kobject *kobj, struct attribute *attr, char *buf) | |
| { | |
| const struct issei_fw_cl_attr *issei_attr; | |
| struct issei_fw_client *fw_cl; | |
| issei_attr = to_issei_fw_cl_attr(attr); | |
| fw_cl = to_issei_fw_client(kobj); | |
| if (!issei_attr->show) | |
| return -EIO; | |
| return issei_attr->show(fw_cl, issei_attr, buf); | |
| } | |
| static const struct sysfs_ops fw_cl_sysfs_ops = { | |
| .show = fw_cl_attr_show, | |
| }; | |
| #define FW_CL_ATTR_RO(_name) \ | |
| struct issei_fw_cl_attr fw_cl_attr_##_name = __ATTR_RO(_name) | |
| /* fw_client attributes */ | |
| static ssize_t id_show(struct issei_fw_client *fw_cl, | |
| const struct issei_fw_cl_attr *attr, char *buf) | |
| { | |
| return sysfs_emit(buf, "%u\n", fw_cl->id); | |
| } | |
| static FW_CL_ATTR_RO(id); | |
| static ssize_t ver_show(struct issei_fw_client *fw_cl, | |
| const struct issei_fw_cl_attr *attr, char *buf) | |
| { | |
| return sysfs_emit(buf, "%u\n", fw_cl->ver); | |
| } | |
| static FW_CL_ATTR_RO(ver); | |
| static ssize_t uuid_show(struct issei_fw_client *fw_cl, | |
| const struct issei_fw_cl_attr *attr, char *buf) | |
| { | |
| return sysfs_emit(buf, "%pUb\n", &fw_cl->uuid); | |
| } | |
| static FW_CL_ATTR_RO(uuid); | |
| static ssize_t mtu_show(struct issei_fw_client *fw_cl, | |
| const struct issei_fw_cl_attr *attr, char *buf) | |
| { | |
| return sysfs_emit(buf, "%u\n", fw_cl->mtu); | |
| } | |
| static FW_CL_ATTR_RO(mtu); | |
| static const struct attribute *const fw_cl_attrs[] = { | |
| &fw_cl_attr_id.attr, | |
| &fw_cl_attr_ver.attr, | |
| &fw_cl_attr_uuid.attr, | |
| &fw_cl_attr_mtu.attr, | |
| NULL, | |
| }; | |
| static const struct attribute_group fw_cl_group = { | |
| .attrs_const = fw_cl_attrs, | |
| }; | |
| __ATTRIBUTE_GROUPS(fw_cl); | |
| static void issei_fw_cl_init(struct issei_fw_client *fw_cl, u16 id, u8 ver, const uuid_t *uuid, | |
| u32 mtu, u32 flags) | |
| { | |
| INIT_LIST_HEAD(&fw_cl->list); | |
| fw_cl->id = id; | |
| fw_cl->ver = ver; | |
| fw_cl->uuid = *uuid; | |
| fw_cl->mtu = mtu; | |
| fw_cl->flags = flags; | |
| } | |
| static void fw_cl_release(struct kobject *kobj) | |
| { | |
| struct issei_fw_client *fw_cl = to_issei_fw_client(kobj); | |
| kfree(fw_cl); | |
| } | |
| static const struct kobj_type fw_client_ktype = { | |
| .sysfs_ops = &fw_cl_sysfs_ops, | |
| .release = fw_cl_release, | |
| .default_groups = fw_cl_groups, | |
| }; | |
| /** | |
| * issei_fw_cl_create - create firmware client object and add to list | |
| * @idev: issei device object | |
| * @id: firmware client id | |
| * @ver: firmware client version | |
| * @uuid: firmware client unique id | |
| * @mtu: firmware client maximum message size | |
| * @flags: firmware client flags | |
| * | |
| * Should be called under idev->client_lock | |
| * | |
| * Return: pointer to newly created object on success, ERR_PTR on failure | |
| */ | |
| struct issei_fw_client *issei_fw_cl_create(struct issei_device *idev, u16 id, u8 ver, | |
| const uuid_t *uuid, u32 mtu, u32 flags) | |
| { | |
| int ret; | |
| struct issei_fw_client *fw_cl = kzalloc_obj(*fw_cl); | |
| if (!fw_cl) | |
| return ERR_PTR(-ENOMEM); | |
| WARN_ON(!mutex_is_locked(&idev->client_lock)); | |
| issei_fw_cl_init(fw_cl, id, ver, uuid, mtu, flags); | |
| fw_cl->kobj.kset = idev->fw_clients; | |
| ret = kobject_init_and_add(&fw_cl->kobj, &fw_client_ktype, NULL, "%u", id); | |
| if (ret) { | |
| kobject_put(&fw_cl->kobj); | |
| return ERR_PTR(ret); | |
| } | |
| list_add_tail(&fw_cl->list, &idev->fw_client_list); | |
| dev_dbg(&idev->dev, "FW client %pUb created\n", uuid); | |
| kobject_uevent(&fw_cl->kobj, KOBJ_ADD); | |
| return fw_cl; | |
| } | |
| static void __issei_fw_cl_remove(struct issei_device *idev, struct issei_fw_client *fw_cl) | |
| { | |
| WARN(fw_cl->cl, "Removing connected client!\n"); | |
| dev_dbg(&idev->dev, "FW client %pUb will be removed\n", &fw_cl->uuid); | |
| list_del(&fw_cl->list); | |
| kobject_put(&fw_cl->kobj); | |
| } | |
| /** | |
| * issei_fw_cl_remove_all - remove all firmware client objects | |
| * @idev: issei device object | |
| */ | |
| void issei_fw_cl_remove_all(struct issei_device *idev) | |
| { | |
| struct issei_fw_client *fw_cl, *next; | |
| guard(mutex)(&idev->client_lock); | |
| list_for_each_entry_safe(fw_cl, next, &idev->fw_client_list, list) | |
| __issei_fw_cl_remove(idev, fw_cl); | |
| } | |
| /** | |
| * issei_fw_cl_find_by_uuid - find firmware client by uuid | |
| * @idev: issei device object | |
| * @uuid: uuid to search by it | |
| * | |
| * Should be called under idev->client_lock | |
| * | |
| * Return: pointer to firmware client object if found, NULL on failure | |
| */ | |
| struct issei_fw_client *issei_fw_cl_find_by_uuid(struct issei_device *idev, const uuid_t *uuid) | |
| { | |
| struct issei_fw_client *fw_cl; | |
| WARN_ON(!mutex_is_locked(&idev->client_lock)); | |
| list_for_each_entry(fw_cl, &idev->fw_client_list, list) { | |
| if (uuid_equal(&fw_cl->uuid, uuid)) { | |
| kobject_get(&fw_cl->kobj); | |
| return fw_cl; | |
| } | |
| } | |
| return NULL; | |
| } | |
| /** | |
| * issei_fw_cl_connect - connect firmware and host client | |
| * @fw_cl: firmware client | |
| * @cl: host client | |
| * | |
| * Should be called under idev->client_lock | |
| * | |
| * Return: 0 on success, -EBUSY if already connected | |
| */ | |
| int issei_fw_cl_connect(struct issei_fw_client *fw_cl, struct issei_host_client *cl) | |
| { | |
| if (fw_cl->cl) | |
| return -EBUSY; | |
| kobject_get(&fw_cl->kobj); | |
| fw_cl->cl = cl; | |
| return 0; | |
| } | |
| /** | |
| * issei_fw_cl_disconnect - disconnect firmware and host client | |
| * @fw_cl: firmware client | |
| * | |
| * Should be called under idev->client_lock | |
| */ | |
| void issei_fw_cl_disconnect(struct issei_fw_client *fw_cl) | |
| { | |
| WARN_ON(!fw_cl->cl); | |
| fw_cl->cl = NULL; | |
| kobject_put(&fw_cl->kobj); | |
| } |