diff --git a/drivers/ub/ubfi/irq.c b/drivers/ub/ubfi/irq.c index 16b18db3af6505d7d37e206a219972e43e57637f..d47e4ce67bd6f08676042723e091dfc0a4899d68 100644 --- a/drivers/ub/ubfi/irq.c +++ b/drivers/ub/ubfi/irq.c @@ -9,6 +9,37 @@ #include #include +int ub_update_msi_domain(struct device *dev, + enum irq_domain_bus_token bus_token) +{ + struct fwnode_handle *fwnode; + struct irq_domain *domain; + + domain = dev->msi.domain; + if (!domain) { + dev_err(dev, "find base irq domain failed!\n"); + return -ENODEV; + } + + fwnode = domain->fwnode; + if (!fwnode) { + dev_err(dev, "find fwnode failed!\n"); + return -ENODEV; + } + + domain = irq_find_matching_fwnode(fwnode, bus_token); + if (!domain) { + dev_err(dev, "find irq domain failed!\n"); + return -ENODEV; + } + + /* Update msi domain with new bus_token */ + dev_set_msi_domain(dev, domain); + + return 0; +} +EXPORT_SYMBOL_GPL(ub_update_msi_domain); + int ubrt_register_gsi(u32 hwirq, int trigger, int polarity, const char *name, struct resource *res) { diff --git a/drivers/ub/ubfi/ubc.c b/drivers/ub/ubfi/ubc.c index 03fe0ead069abd73fac2f718038a13b369e05d75..e89aeeafb913d649a0fef03687b5510c020b19a7 100644 --- a/drivers/ub/ubfi/ubc.c +++ b/drivers/ub/ubfi/ubc.c @@ -51,7 +51,41 @@ static bool cluster_mode; static acpi_status acpi_processor_ubc(acpi_handle handle, u32 lvl, void *context, void **rv) { - return AE_OK; + struct ub_bus_controller *ubc = context; + struct acpi_device *adev; + unsigned long long uid; + acpi_status status; + struct device *dev; + int ret; + + status = acpi_evaluate_integer(handle, "_UID", NULL, &uid); + if (ACPI_FAILURE(status)) + return AE_CTRL_TERMINATE; + + pr_info("ubc acpi ubc->ctl_no %u, uid: %llu\n", ubc->ctl_no, uid); + if (ubc->ctl_no != (u32)uid) + return AE_OK; + + adev = acpi_get_acpi_dev(handle); + if (!adev) + return AE_CTRL_TERMINATE; + + dev = bus_find_device_by_acpi_dev(&platform_bus_type, adev); + if (!dev) { + status = AE_CTRL_TERMINATE; + goto out; + } + ret = ub_update_msi_domain(dev, DOMAIN_BUS_UB_MSI); + if (ret) { + status = AE_CTRL_TERMINATE; + goto out; + } + dev_set_msi_domain(&ubc->dev, dev->msi.domain); + pr_debug("set ubc msi domain success by acpi\n"); + +out: + acpi_put_acpi_dev(adev); + return status; } static int acpi_update_ubc_msi_domain(void) @@ -71,10 +105,28 @@ static int acpi_update_ubc_msi_domain(void) return 0; } +static struct irq_domain *of_usi_get_domain(struct device_node *np, + enum irq_domain_bus_token token) +{ + struct device_node *usi_np; + struct irq_domain *d; + + usi_np = of_parse_phandle(np, "msi-parent", 0); + if (!usi_np) + return NULL; + + d = irq_find_matching_host(usi_np, token); + if (!d) + of_node_put(usi_np); + + return d; +} + static int dts_update_ubc_msi_domain(void) { struct ub_bus_controller *ubc; struct device_node *np; + struct irq_domain *d; u32 ctl_no; bool find; int ret; @@ -97,6 +149,15 @@ static int dts_update_ubc_msi_domain(void) pr_err("can't find ubc no=%u\n", ctl_no); continue; } + + d = of_usi_get_domain(np, DOMAIN_BUS_UB_MSI); + if (!d) { + pr_err("can't find ub irq domain\n"); + continue; + } + + dev_set_msi_domain(&ubc->dev, d); + pr_debug("set ubc[%u] msi domain success\n", ctl_no); } return 0; } diff --git a/drivers/ub/ubus/Makefile b/drivers/ub/ubus/Makefile index 36ab665f4f849fa286dac92ee517a4669d98420d..76cfef6f30e136c4912ae7f8ca1166721d02e850 100644 --- a/drivers/ub/ubus/Makefile +++ b/drivers/ub/ubus/Makefile @@ -4,8 +4,11 @@ obj-$(CONFIG_UB_UBUS) += ub-driver.o controller.o config.o entity.o ras.o obj-$(CONFIG_UB_UBUS) += msi/ ubus-y := ubus_driver.o sysfs.o ubus_controller.o msg.o ubus_config.o port.o cc.o eid.o cna.o route.o -ubus-y += enum.o resource.o ubus_entity.o reset.o cap.o interrupt.o decoder.o omm.o ioctl.o eu.o +ubus-y += enum.o resource.o ubus_entity.o reset.o cap.o interrupt.o decoder.o omm.o ioctl.o eu.o link.o +ubus-y += instance.o pool.o -ubus-y += services/ras.o +ubus-y += services/ras.o services/service.o services/gucd.o +ubus-y += services/hotplug/hotplug_core.o services/hotplug/hotplug_ctrl.o +ubus-y += services/hotplug/hotplug_route.o services/hotplug/hotplug_msg.o obj-$(CONFIG_UB_UBUS_BUS) += ubus.o diff --git a/drivers/ub/ubus/entity.c b/drivers/ub/ubus/entity.c index 5d91ee2c2c3ebaeaefa4d6641c164701287a0e60..a288634f30c8ee7283b2a541f8b70ba7e98d837c 100644 --- a/drivers/ub/ubus/entity.c +++ b/drivers/ub/ubus/entity.c @@ -19,6 +19,8 @@ int ub_get_dst_eid(struct ub_entity *dev) return -ENODEV; eid = ubc->uent->eid; + if (dev->bi) + eid = dev->bi->info.eid; return eid; } diff --git a/drivers/ub/ubus/enum.c b/drivers/ub/ubus/enum.c index 872bb0de1582bd69fe66275f99a3b73468dbdb8f..48c37e30ca3155a93b3e15e9ee3cdea6ab307fe4 100644 --- a/drivers/ub/ubus/enum.c +++ b/drivers/ub/ubus/enum.c @@ -1080,6 +1080,57 @@ static void ub_enum_topo_ent_uninit(struct ub_entity *uent) } } +static int ub_enum_ports(struct ub_entity *uent, u16 start_port, u16 num, + void *buf) +{ + u16 left = num, start = start_port, count; + struct enum_pld_scan_pdu_common *pc; + struct enum_tlv_info info = {}; + u8 total_slice, slice_id = 0; + int tlv_len, ret, nums = 0; + void *rsp, *tlv; + + do { + rsp = (void *)ub_enum_topo_query_and_check(uent, buf, slice_id); + if (!rsp) + return -EBUSY; + + pc = rsp; + tlv_len = (int)pc->pdu_len * SZ_4 - ENUM_PLD_SCAN_PDU_COMMON_SIZE; + tlv = rsp + ENUM_PLD_SCAN_PDU_COMMON_SIZE; + + ret = ub_enum_tlv_parse(tlv, tlv_len, &info); + if (ret) + return ret; + + if (slice_id == 0) { + if (!info.si || !info.pi || !info.pn || !info.ci) { + dev_err(&uent->ubc->dev, "port tlv si/pi/pn/ci null\n"); + return -EINVAL; + } + + total_slice = info.si->total_slice; + } + + if (start >= nums + info.port_nums) + goto next; + + info.pi += start - nums; + count = min(left, nums + info.port_nums - start); + ub_enum_parse_port(uent, info.pi, count); + start += count; + left -= count; + + if (left == 0) + break; +next: + nums += info.port_nums; + slice_id++; + } while (slice_id < total_slice); + + return 0; +} + static int ub_enum_and_configure_ent(struct ub_entity *uent, void *buf) { int ret; @@ -1281,6 +1332,37 @@ static int ub_enum_do_topo_scan(struct ub_entity *root, struct list_head *dev_li return ret; } +int ub_enum_topo_scan_ports(struct ub_entity *uent, u16 start_port, u16 num, + struct list_head *dev_list, void *buf) +{ + int ret; + + if (!uent || !dev_list || !buf) + return -EINVAL; + + ret = ub_enum_ports(uent, start_port, num, buf); + if (ret) + return ret; + + return ub_enum_do_topo_scan(uent, dev_list, buf); +} + +struct ub_entity *ub_enum_get_port_r_uent(struct ub_port *port, void *buf) +{ + struct ub_bus_controller *ubc; + int ret; + + if (!port || !buf) + return NULL; + + ret = ub_enum_ports(port->uent, port->index, 1, buf); + if (ret) + return NULL; + + ubc = port->uent->ubc; + return ub_enum_get_ent(&port->r_guid, &ubc->devs); +} + /* * During topo scan, just alloc ub_entity, alloc ub_port, alloc cna, * so, when topo scan failed, just go to free devs in topo_scan.uents diff --git a/drivers/ub/ubus/enum.h b/drivers/ub/ubus/enum.h index 2ab04e0f705fa7cfc2f858a30a1568b4a8f654ed..5c20eb68603a010f80f534c50596f5b4440c023e 100644 --- a/drivers/ub/ubus/enum.h +++ b/drivers/ub/ubus/enum.h @@ -181,6 +181,11 @@ struct enum_tlv_cap_info { }; #define ENUM_TLV_CAP_INFO_SZ 8 +void ub_enum_clear_ent_list(struct list_head *dev_list); +int ub_enum_entities_active(struct list_head *dev_list); +int ub_enum_topo_scan_ports(struct ub_entity *uent, u16 start_port, u16 num, + struct list_head *dev_list, void *buf); +struct ub_entity *ub_enum_get_port_r_uent(struct ub_port *port, void *buf); size_t calc_enum_pld_header_size(struct enum_pld_scan_header *header, bool req); int ub_cfg_read_guid(struct ub_entity *uent); int ub_enum_bfs_route_cal(struct list_head *uent_list); diff --git a/drivers/ub/ubus/instance.c b/drivers/ub/ubus/instance.c new file mode 100644 index 0000000000000000000000000000000000000000..8c49c04388e366513acc0642930199726b97a82c --- /dev/null +++ b/drivers/ub/ubus/instance.c @@ -0,0 +1,960 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#define pr_fmt(fmt) "ubus instance: " fmt + +#include +#include + +#include "ubus.h" +#include "eid.h" +#include "resource.h" +#include "eu.h" +#include "ubus_driver.h" +#include "instance.h" + +#define DYNAMIC_DEFAULT_UBC 0 + +static LIST_HEAD(ubi_list); +static DEFINE_MUTEX(ubi_list_mutex); +static DEFINE_MUTEX(dynamic_mutex); +static u32 instance_count; +static u32 instance_start; + +static ssize_t instance_store(const struct bus_type *bus, const char *buf, + size_t count) +{ + unsigned long val; + ssize_t ret; + + ret = kstrtoul(buf, 0, &val); + if (ret < 0) + return ret; + + mutex_lock(&ubi_list_mutex); + if (val >= instance_count) { + pr_err("store instance %#lx over %#x\n", val, instance_count); + mutex_unlock(&ubi_list_mutex); + return -EINVAL; + } + + instance_start = val; + mutex_unlock(&ubi_list_mutex); + return count; +} + +static ssize_t instance_show(const struct bus_type *bus, char *buf) +{ + struct ub_bus_instance_info *info; + struct ub_bus_instance *bi; + u32 show, left, end, i = 0; + struct ub_guid *guid; + ssize_t len; + +#define MAX_DUMP 50 + mutex_lock(&ubi_list_mutex); + left = instance_count - instance_start; + show = left > MAX_DUMP ? MAX_DUMP : left; + end = instance_start + show; + len = sysfs_emit(buf, "count %#x, from %#x, show %#x\n", instance_count, + instance_start, show); + + list_for_each_entry(bi, &ubi_list, node) { + if (i < instance_start) { + i++; + continue; + } + + if (i == end) + break; + + info = &bi->info; + guid = &info->guid; + len += sysfs_emit_at(buf, len, "guid:"); + len += ub_show_guid(guid, buf + len); + len += sysfs_emit_at(buf, len, " type:%01x eid:%05x upi:%04x\n", + info->type, info->eid, info->upi); + i++; + } + + instance_start = 0; + mutex_unlock(&ubi_list_mutex); + return len; +} +BUS_ATTR_RW(instance); + +static void ub_unregister_bus_instance(struct ub_bus_instance *bi); +static void ub_bus_instance_destroy(struct ub_bus_instance *bi) +{ + guid_t *tar = (guid_t *)&guid_null; + + if (is_dynamic(bi)) + tar = &bi->info.guid.id; + + ummu_core_del_eid(tar, bi->info.eid, EID_BYPASS); + ub_unregister_bus_instance(bi); +} + +static void ub_release_bus_instance(struct kref *kref) +{ + struct ub_bus_instance *bi = + container_of(kref, struct ub_bus_instance, kref); + + if (bi->registered) + ub_bus_instance_destroy(bi); + + kfree(bi); +} + +static struct ub_bus_instance *ub_bus_instance_get(struct ub_bus_instance *bi) +{ + if (bi) + kref_get(&bi->kref); + + return bi; +} + +void ub_bus_instance_put(struct ub_bus_instance *bi) +{ + if (bi) + kref_put(&bi->kref, ub_release_bus_instance); +} +EXPORT_SYMBOL_GPL(ub_bus_instance_put); + +bool eid_match(struct ub_bus_instance *bi, void *arg) +{ + if (bi == NULL || arg == NULL) + return false; + + u32 *eid = (u32 *)arg; + + return bi->info.eid == *eid; +} +EXPORT_SYMBOL_GPL(eid_match); + +static struct ub_bus_instance *ub_alloc_bus_instance(void) +{ + struct ub_bus_instance *bi; + + bi = kzalloc(sizeof(*bi), GFP_KERNEL); + if (!bi) + return NULL; + + INIT_LIST_HEAD(&bi->node); + kref_init(&bi->kref); + INIT_LIST_HEAD(&bi->uents); + mutex_init(&bi->lock); + + return bi; +} + +static bool guid_match(struct ub_bus_instance *bi, void *arg) +{ + guid_t *guid = (guid_t *)arg; + + return guid_equal(&bi->info.guid.id, guid); +} + +struct ub_bus_instance *ub_find_bus_instance(instance_match match, void *arg) +{ + struct ub_bus_instance *bi; + + if (!match || !arg) { + pr_err("instance match or arg is null\n"); + return NULL; + } + + mutex_lock(&ubi_list_mutex); + list_for_each_entry(bi, &ubi_list, node) + if (match(bi, arg)) + goto out; + + bi = NULL; +out: + ub_bus_instance_get(bi); + mutex_unlock(&ubi_list_mutex); + + return bi; +} +EXPORT_SYMBOL_GPL(ub_find_bus_instance); + +static int bi_duplicate_check(struct ub_bus_instance_info *n) +{ + struct ub_bus_instance_info *old; + struct ub_bus_instance *bi; + int ret; + + bi = ub_find_bus_instance(guid_match, &n->guid.id); + if (!bi) + return 0; + + old = &bi->info; + + if (n->type != old->type) { + pr_err("bus instance guid exist with different type\n"); + ub_bus_instance_put(bi); + return -EINVAL; + } + + if (n->eid) { + if (n->upi) + ret = (n->eid == old->eid && n->upi == old->upi) ? + -EEXIST : + -EINVAL; + else + ret = (n->eid == old->eid) ? -EEXIST : -EINVAL; + } else { + if (n->upi) + ret = (n->upi == old->upi) ? -EEXIST : -EINVAL; + else + ret = -EEXIST; + } + + if (ret == -EEXIST) + pr_warn("bus instance guid exist\n"); + else + pr_err("bus instance guid exist, but eid/upi invalid\n"); + + ub_bus_instance_put(bi); + + return ret; +} + +static int bi_valid_check(struct ub_bus_instance *bi) +{ + struct ub_bus_instance_info *info = &bi->info; + + if (info->guid.bits.type != UB_TYPE_BUS_INSTANCE || + guid_is_null((const guid_t *)&info->guid)) { + pr_err("guid type is not bus instance or guid_null\n"); + return -EINVAL; + } + + if (is_cluster(bi) && (info->eid == 0 || info->upi == 0 || + info->eid <= ubc_eid_end)) { + pr_err("cluster bi eid or upi invalid, eid=%#x, upi=%#x\n", + info->eid, info->upi); + return -EINVAL; + } + + if (is_server(bi) && info->eid && info->eid <= ubc_eid_end) { + pr_err("server bi eid within the local scope, eid=%#x\n", + info->eid); + return -EINVAL; + } + + return 0; +} + +static int ub_cfg_bus_instance_eid(struct ub_bus_instance *bi, bool alloc) +{ + struct ub_bus_instance_info *info = &bi->info; + struct ub_guid guid = info->guid; + u32 eid; + int ret; + + if (is_cluster(bi)) + return 0; + + if (alloc) { + if (info->eid) + return 0; + + ret = ub_eid_request(&guid.id, &eid); + if (ret) { + pr_err("bus instance eid cfg failed, ret=%d\n", ret); + return ret; + } + + info->eid = eid; + } else { + if (info->eid > ubc_eid_end) + return 0; + + ub_eid_release(info->eid); + info->eid = 0; + } + + return 0; +} + +static void ub_cfg_bus_instance_upi(struct ub_bus_instance *bi) +{ + struct ub_bus_instance_info *info = &bi->info; + + if (is_cluster(bi) || info->upi) + return; + + info->upi = UB_CP_UPI; +} + +static int ub_register_bus_instance(struct ub_bus_instance *bi) +{ + struct ub_bus_instance_info *info = &bi->info; + struct ub_bus_controller *ubc; + int ret, i = 0, count = 0; + + ret = bi_duplicate_check(info); + if (ret) + return ret; + + ret = bi_valid_check(bi); + if (ret) + return ret; + + ret = ub_cfg_bus_instance_eid(bi, true); + if (ret) + return ret; + + ub_cfg_bus_instance_upi(bi); + + if (is_static_server(bi)) { + ret = ub_cfg_eu_table(bi->major, true, bi->info.eid, + bi->info.upi); + if (ret) + goto out; + } else { + list_for_each_entry(ubc, &ubc_list, node) { + ret = ub_cfg_eu_table(ubc, true, bi->info.eid, + bi->info.upi); + if (ret) + goto cfg_fail; + count++; + } + } + + mutex_lock(&ubi_list_mutex); + bi->registered = true; + list_add_tail(&bi->node, &ubi_list); + instance_count++; + mutex_unlock(&ubi_list_mutex); + return 0; +cfg_fail: + list_for_each_entry(ubc, &ubc_list, node) { + if (i == count) + break; + + (void)ub_cfg_eu_table(ubc, false, bi->info.eid, bi->info.upi); + i++; + } +out: + /* upi no need unconfigure */ + (void)ub_cfg_bus_instance_eid(bi, false); + return ret; +} + +static void ub_unregister_bus_instance(struct ub_bus_instance *bi) +{ + struct ub_bus_controller *ubc; + + mutex_lock(&ubi_list_mutex); + if (!bi->registered) { + mutex_unlock(&ubi_list_mutex); + return; + } + + instance_count--; + list_del(&bi->node); + bi->registered = false; + mutex_unlock(&ubi_list_mutex); + + if (is_static_server(bi)) { + (void)ub_cfg_eu_table(bi->major, false, bi->info.eid, + bi->info.upi); + } else { + list_for_each_entry(ubc, &ubc_list, node) + (void)ub_cfg_eu_table(ubc, false, bi->info.eid, + bi->info.upi); + } + + /* upi no need unconfigure */ + (void)ub_cfg_bus_instance_eid(bi, false); +} + +int ub_static_bus_instance_init(struct ub_bus_controller *ubc) +{ + struct ub_bus_instance *bi; + struct ub_bus_controller *tmp; + int ret; + + if (ubc->ctl_no != 0) { + tmp = ub_find_bus_controller(0); + if (!tmp || !tmp->bi) + return -EINVAL; + + ubc->bi = ub_bus_instance_get(tmp->bi); + + return ub_cfg_eu_table(ubc, true, ubc->bi->info.eid, + ubc->bi->info.upi); + } + + bi = ub_alloc_bus_instance(); + if (!bi) + return -ENOMEM; + + bi->info.type = UBUS_INSTANCE_STATIC_SERVER; + guid_copy(&bi->info.guid.id, &ubc->uent->guid.id); + bi->info.guid.bits.type = UB_TYPE_BUS_INSTANCE; + bi->major = ubc; + + ret = ub_register_bus_instance(bi); + if (ret) { + dev_err(&ubc->dev, "static server register bi failed ret=%d\n", + ret); + goto put; + } + + ret = ummu_core_add_eid((guid_t *)&guid_null, bi->info.eid, + EID_BYPASS); + if (ret) { + dev_err(&ubc->dev, "static server ummu core add eid, ret=%d\n", + ret); + goto unregister; + } + + ubc->bi = ub_bus_instance_get(bi); + + ub_bus_instance_put(bi); /* put the init one */ + + return 0; +unregister: + ub_unregister_bus_instance(bi); +put: + ub_bus_instance_put(bi); + return ret; +} + +void ub_static_bus_instance_uninit(struct ub_bus_controller *ubc) +{ + if (ubc->ctl_no != 0) + (void)ub_cfg_eu_table(ubc, false, ubc->bi->info.eid, + ubc->bi->info.upi); + + ub_bus_instance_put(ubc->bi); + ubc->bi = NULL; +} + +static struct ub_bus_instance * +ub_dynamic_bus_instance_create(struct ub_bus_instance_info *info, enum eid_type type) +{ + struct ub_bus_instance *bi; + struct ub_bus_controller *ubc; + int ret; + + ubc = ub_find_bus_controller(DYNAMIC_DEFAULT_UBC); + if (!ubc) { + pr_err("ubc 0 not exist\n"); + return (struct ub_bus_instance *)ERR_PTR(-ENODEV); + } + + bi = ub_alloc_bus_instance(); + if (!bi) + return (struct ub_bus_instance *)ERR_PTR(-ENOMEM); + + if (ubc->cluster) + info->type = UBUS_INSTANCE_DYNAMIC_CLUSTER; + else + info->type = UBUS_INSTANCE_DYNAMIC_SERVER; + + bi->info = *info; + bi->major = ubc; + + ret = ub_register_bus_instance(bi); + if (ret) + goto put; + + ret = ummu_core_add_eid(&bi->info.guid.id, bi->info.eid, type); + if (ret) { + pr_err("bus instance add eid, ret=%d\n", ret); + goto unregister; + } + + return bi; + +unregister: + ub_unregister_bus_instance(bi); +put: + ub_bus_instance_put(bi); + return (struct ub_bus_instance *)ERR_PTR(ret); +} + +static void bi_info_init(struct ub_bus_instance_info *info, + const struct ubus_cmd_bi_create *create) +{ + info->type = create->type; + info->upi = create->upi; + info->eid = create->eid; + guid_copy(&info->guid.id, (const guid_t *)create->guid); +} + +int ub_ioctl_bus_instance_create(void __user *uptr) +{ + size_t size = sizeof(struct ubus_cmd_bi_create); + struct ubus_cmd_bi_create create = {}; + struct ub_bus_instance_info info = {}; + struct ub_bus_instance *bi; + + if (copy_from_user(&create, uptr + UBUS_IOCTL_HEADER_SIZE, size)) + return -EFAULT; + + bi_info_init(&info, &create); + + mutex_lock(&dynamic_mutex); + bi = ub_dynamic_bus_instance_create(&info, EID_BYPASS); + if (IS_ERR(bi)) { + pr_err("bus instance create failed, ret=%ld\n", PTR_ERR(bi)); + mutex_unlock(&dynamic_mutex); + return PTR_ERR(bi); + } + + create.eid = bi->info.eid; + create.upi = bi->info.upi; + + if (copy_to_user(uptr + UBUS_IOCTL_HEADER_SIZE, &create, size)) { + ub_bus_instance_put(bi); + pr_err("bus instance copy to user failed\n"); + mutex_unlock(&dynamic_mutex); + return -EFAULT; + } + + mutex_unlock(&dynamic_mutex); + return 0; +} + +int ub_ioctl_bus_instance_destroy(void __user *uptr) +{ + size_t size = sizeof(struct ubus_cmd_bi_destroy); + struct ubus_cmd_bi_destroy destroy = {}; + struct ub_bus_instance *bi; + char b_str[SZ_64]; + + if (copy_from_user(&destroy, uptr + UBUS_IOCTL_HEADER_SIZE, size)) + return -EFAULT; + + (void)snprintf(b_str, SZ_64, "%#llx %llx", + *((u64 *)&destroy.guid[SZ_8]), + *((u64 *)&destroy.guid[0])); + + mutex_lock(&dynamic_mutex); + bi = ub_find_bus_instance(guid_match, destroy.guid); + if (!bi) { + pr_err("bus instance destroy invalid, guid=%s\n", b_str); + mutex_unlock(&dynamic_mutex); + return -ENODEV; + } + + if (!is_dynamic(bi)) { + pr_err("instance %s is not dynamic\n", b_str); + ub_bus_instance_put(bi); + mutex_unlock(&dynamic_mutex); + return -EINVAL; + } + + if (kref_read(&bi->kref) != 2) { /* 2 is original + find */ + pr_err("instance %s is still in use\n", b_str); + ub_bus_instance_put(bi); + mutex_unlock(&dynamic_mutex); + return -EBUSY; + } + + ub_bus_instance_put(bi); /* put find */ + ub_bus_instance_put(bi); /* real put */ + + mutex_unlock(&dynamic_mutex); + return 0; +} + +int ub_msg_bus_instance_create(struct ub_bus_controller *ubc, u32 *guid, u32 eid, + u16 upi, enum eid_type type) +{ + struct ub_bus_instance_info info = {}; + struct ub_bus_instance *bi; + int ret = 0; + + info.upi = upi; + info.eid = eid; + guid_copy(&info.guid.id, (const guid_t *)guid); + + mutex_lock(&dynamic_mutex); + bi = ub_dynamic_bus_instance_create(&info, type); + if (IS_ERR(bi)) { + ret = PTR_ERR(bi); + dev_err(&ubc->dev, "msg bus instance create failed, ret=%d\n", + ret); + } + + mutex_unlock(&dynamic_mutex); + return ret; +} +EXPORT_SYMBOL_GPL(ub_msg_bus_instance_create); + +int ub_msg_bus_instance_destroy(struct ub_bus_controller *ubc, u32 *guid) +{ + struct ub_bus_instance *bi; + char b_str[SZ_64]; + + (void)snprintf(b_str, SZ_64, "%#llx %llx", *((u64 *)&guid[SZ_2]), + *((u64 *)&guid[0])); + + mutex_lock(&dynamic_mutex); + bi = ub_find_bus_instance(guid_match, guid); + if (!bi) { + dev_err(&ubc->dev, + "msg bus instance destroy invalid, guid=%s\n", b_str); + mutex_unlock(&dynamic_mutex); + return -ENODEV; + } + + if (!is_dynamic_cluster(bi)) { + dev_err(&ubc->dev, "msg destroy instance %s type invalid\n", + b_str); + ub_bus_instance_put(bi); + mutex_unlock(&dynamic_mutex); + return -EINVAL; + } + + if (kref_read(&bi->kref) != 2) { /* 2 is original + find */ + dev_err(&ubc->dev, "msg instance %s still in use\n", b_str); + ub_bus_instance_put(bi); + mutex_unlock(&dynamic_mutex); + return -EBUSY; + } + + ub_bus_instance_put(bi); /* put find */ + ub_bus_instance_put(bi); /* real put */ + + mutex_unlock(&dynamic_mutex); + return 0; +} + +void ub_dynamic_bus_instance_drain(void) +{ + struct ub_bus_instance *bi, *tmp; + + mutex_lock(&dynamic_mutex); + + list_for_each_entry_safe(bi, tmp, &ubi_list, node) + if (is_dynamic(bi)) + ub_bus_instance_put(bi); + + mutex_unlock(&dynamic_mutex); +} + +static int +ub_static_cluster_instance_create(struct ub_bus_controller *ubc, u32 *guid, + u32 eid, u16 upi) +{ + struct ub_bus_instance *bi; + int ret; + + bi = ub_alloc_bus_instance(); + if (!bi) + return -ENOMEM; + + bi->info.type = UBUS_INSTANCE_STATIC_CLUSTER; + guid_copy(&bi->info.guid.id, (guid_t *)guid); + bi->info.eid = eid; + bi->info.upi = upi; + bi->major = ubc; + + ret = ub_register_bus_instance(bi); + if (ret) + goto put; + + ret = ummu_core_add_eid((guid_t *)&guid_null, bi->info.eid, + EID_BYPASS); + if (ret) { + dev_err(&ubc->dev, "bus instance add eid failed, ret=%d\n", ret); + goto unregister; + } + + ubc->cluster_bi = bi; + return 0; + +unregister: + ub_unregister_bus_instance(bi); +put: + ub_bus_instance_put(bi); + return ret; +} + +int ub_notify_bus_instance_handle(struct ub_bus_controller *ubc, bool flag, + u32 *guid, u32 eid, u16 upi) +{ + struct ub_bus_instance *bi; + struct ub_guid *tmp; + int ret; + + if (!flag) { + bi = ub_find_bus_instance(guid_match, guid); + if (!bi) { + dev_err(&ubc->dev, "notify can't find bus instance\n"); + return -ENODEV; + } + + if (!is_static_cluster(bi) || bi->info.eid != eid || + bi->info.upi != upi) { + dev_err(&ubc->dev, "notify bus instance is invalid\n"); + ub_bus_instance_put(bi); + return -EINVAL; + } + + /* May receive the same UBC Notify message */ + if (ubc->cluster_bi) + ub_bus_instance_put(bi); + else + ubc->cluster_bi = bi; + + return 0; + } + + tmp = (struct ub_guid *)guid; + if (tmp->bits.type != UB_TYPE_BUS_INSTANCE) { + dev_err(&ubc->dev, "notify msg guid type is invalid\n"); + return -EINVAL; + } + + /* BI may have already been created in other UBC Notify messages */ + ret = ub_static_cluster_instance_create(ubc, guid, eid, upi); + if (ret && ret != -EEXIST) { + dev_err(&ubc->dev, "create static cluster instance failed\n"); + return ret; + } + + if (!ubc->cluster_bi) + ubc->cluster_bi = ub_find_bus_instance(guid_match, guid); + + return 0; +} + +void ub_static_cluster_instance_drain(void) +{ + struct ub_bus_controller *ubc; + + list_for_each_entry(ubc, &ubc_list, node) { + ub_bus_instance_put(ubc->cluster_bi); + ubc->cluster_bi = NULL; + } +} + +/* Only for dynamic use */ +static int get_bus_instance_and_uent(u8 *b_guid, u8 *d_guid, + struct ub_bus_instance **p_bi, + struct ub_entity **p_uent) +{ + char b_str[SZ_64], d_str[SZ_64]; + struct ub_bus_instance *bi; + struct ub_entity *uent; + int ret = -ENODEV; + + (void)snprintf(b_str, SZ_64, "%#llx %llx", *((u64 *)&b_guid[SZ_8]), + *((u64 *)&b_guid[0])); + (void)snprintf(d_str, SZ_64, "%#llx %llx", *((u64 *)&d_guid[SZ_8]), + *((u64 *)&d_guid[0])); + + bi = ub_find_bus_instance(guid_match, b_guid); + if (!bi) + goto err; + + if (!is_dynamic(bi)) { + ret = -EINVAL; + goto put; + } + + uent = ub_get_ent_by_guid((struct ub_guid *)d_guid); + if (!uent) + goto put; + + if (is_p_device(uent)) { + pr_err("fad dev not support bind again\n"); + ub_entity_put(uent); + goto put; + } + + *p_bi = bi; + *p_uent = uent; + return 0; +put: + ub_bus_instance_put(bi); +err: + pr_err("get bi and uent failed, bi_guid=%s, dev_guid=%s\n", b_str, d_str); + return ret; +} + +static int ub_bind_bus_instance(struct ub_entity *uent, struct ub_bus_instance *bi) +{ + if (!uent || uent->bi || !bi || !bi->registered) + return -EINVAL; + + uent->bi = ub_bus_instance_get(bi); + + mutex_lock(&bi->lock); + list_add_tail(&uent->instance_node, &bi->uents); + mutex_unlock(&bi->lock); + + ub_entity_decoder_map_mmio(uent); + return 0; +} + +static void ub_unbind_bus_instance(struct ub_entity *uent) +{ + if (!uent || !uent->bi) + return; + + mutex_lock(&uent->bi->lock); + list_del(&uent->instance_node); + mutex_unlock(&uent->bi->lock); + + ub_bus_instance_put(uent->bi); + uent->bi = NULL; + ub_entity_decoder_unmap_mmio(uent); +} + +int ub_ioctl_bus_instance_bind(void __user *uptr) +{ + size_t size = sizeof(struct ubus_cmd_bi_bind); + struct ubus_cmd_bi_bind bind = {}; + struct ub_bus_instance *bi, *old; + struct ub_entity *uent; + char b_str[SZ_64]; + int ret; + + if (copy_from_user(&bind, uptr + UBUS_IOCTL_HEADER_SIZE, size)) + return -EFAULT; + + mutex_lock(&dynamic_mutex); + ret = get_bus_instance_and_uent(bind.instance_guid, bind.dev_guid, &bi, + &uent); + if (ret) { + mutex_unlock(&dynamic_mutex); + return ret; + } + + (void)snprintf(b_str, SZ_64, "%#llx %llx", + *((u64 *)&bind.instance_guid[SZ_8]), + *((u64 *)&bind.instance_guid[0])); + + mutex_lock(&uent->instance_lock); + old = uent->bi; + if (!old) { + ub_err(uent, "dev has no static instance\n"); + ret = -EINVAL; + goto out; + } + + if (is_static(old)) { + ub_unbind_bus_instance(uent); + ret = ub_bind_bus_instance(uent, bi); + if (ret) { + ub_err(uent, "bind instance failed, guid=%s\n", b_str); + (void)ub_bind_bus_instance(uent, uent->ubc->bi); + } + } else { + /* If bind the same again, do nothing */ + if (old != bi) { + ub_err(uent, "dev already bind instance\n"); + ret = -EBUSY; + } + } + +out: + ub_entity_put(uent); + ub_bus_instance_put(bi); + mutex_unlock(&uent->instance_lock); + mutex_unlock(&dynamic_mutex); + return ret; +} + +int ub_ioctl_bus_instance_unbind(void __user *uptr) +{ + size_t size = sizeof(struct ubus_cmd_bi_unbind); + struct ubus_cmd_bi_unbind unbind = {}; + struct ub_bus_instance *bi; + struct ub_entity *uent; + char b_str[SZ_64]; + int ret; + + if (copy_from_user(&unbind, uptr + UBUS_IOCTL_HEADER_SIZE, size)) + return -EFAULT; + + mutex_lock(&dynamic_mutex); + ret = get_bus_instance_and_uent(unbind.instance_guid, unbind.dev_guid, + &bi, &uent); + if (ret) { + mutex_unlock(&dynamic_mutex); + return ret; + } + + (void)snprintf(b_str, SZ_64, "%#llx %llx", + *((u64 *)&unbind.instance_guid[SZ_8]), + *((u64 *)&unbind.instance_guid[0])); + + mutex_lock(&uent->instance_lock); + if (uent->bi != bi) { + ub_err(uent, "dev not bind bus instance, guid=%s\n", b_str); + ret = -EINVAL; + goto out; + } + + ub_unbind_bus_instance(uent); + ret = ub_bind_bus_instance(uent, uent->ubc->bi); + if (ret) { + ub_err(uent, "unbind instance failed, guid=%s\n", b_str); + (void)ub_bind_bus_instance(uent, bi); + } + +out: + ub_entity_put(uent); + ub_bus_instance_put(bi); + mutex_unlock(&uent->instance_lock); + mutex_unlock(&dynamic_mutex); + return ret; +} + +int ub_default_bus_instance_init(struct ub_entity *uent) +{ + bool m_idev = is_p_idevice(uent); + bool fad = is_p_device(uent); + struct ub_bus_instance *bi; + int ret; + + if (is_switch(uent)) + return 0; + + if (fad || m_idev) { + mutex_lock(&dynamic_mutex); + bi = ub_find_bus_instance(eid_match, &uent->user_eid); + } else { + bi = uent->ubc->bi; + } + + if (!bi) { + if (fad || m_idev) + mutex_unlock(&dynamic_mutex); + ub_err(uent, "get default bi NULL\n"); + return -EINVAL; + } + + mutex_lock(&uent->instance_lock); + ret = ub_bind_bus_instance(uent, bi); + mutex_unlock(&uent->instance_lock); + + if (fad || m_idev) { + ub_bus_instance_put(bi); + mutex_unlock(&dynamic_mutex); + } + + return ret; +} + +void ub_default_bus_instance_uninit(struct ub_entity *uent) +{ + if (is_switch(uent)) + return; + + mutex_lock(&uent->instance_lock); + ub_unbind_bus_instance(uent); + mutex_unlock(&uent->instance_lock); +} diff --git a/drivers/ub/ubus/instance.h b/drivers/ub/ubus/instance.h new file mode 100644 index 0000000000000000000000000000000000000000..6aac58df77bc5c0f0c4889007430b60546557fb0 --- /dev/null +++ b/drivers/ub/ubus/instance.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#ifndef __INSTANCE_H__ +#define __INSTANCE_H__ + +#include + +extern struct bus_attribute bus_attr_instance; + +typedef bool (*instance_match)(struct ub_bus_instance *bi, void *arg); + +#define UBUS_INSTANCE_STATIC_SERVER 0 +#define UBUS_INSTANCE_STATIC_CLUSTER 1 + +#define is_static_server(bi) ((bi)->info.type == UBUS_INSTANCE_STATIC_SERVER) +#define is_static_cluster(bi) ((bi)->info.type == UBUS_INSTANCE_STATIC_CLUSTER) +#define is_dynamic_server(bi) ((bi)->info.type == UBUS_INSTANCE_DYNAMIC_SERVER) +#define is_dynamic_cluster(bi) ((bi)->info.type == UBUS_INSTANCE_DYNAMIC_CLUSTER) +#define is_static(bi) (is_static_server(bi) || is_static_cluster(bi)) +#define is_dynamic(bi) (is_dynamic_server(bi) || is_dynamic_cluster(bi)) +#define is_server(bi) (is_static_server(bi) || is_dynamic_server(bi)) +#define is_cluster(bi) (is_static_cluster(bi) || is_dynamic_cluster(bi)) + +int ub_static_bus_instance_init(struct ub_bus_controller *ubc); +void ub_static_bus_instance_uninit(struct ub_bus_controller *ubc); +void ub_bus_instance_put(struct ub_bus_instance *bi); +struct ub_bus_instance *ub_find_bus_instance(instance_match match, void *arg); +int ub_ioctl_bus_instance_create(void __user *uptr); +int ub_ioctl_bus_instance_destroy(void __user *uptr); +int ub_ioctl_bus_instance_bind(void __user *uptr); +int ub_ioctl_bus_instance_unbind(void __user *uptr); +int ub_msg_bus_instance_create(struct ub_bus_controller *ubc, u32 *guid, u32 eid, + u16 upi, enum eid_type type); +int ub_msg_bus_instance_destroy(struct ub_bus_controller *ubc, u32 *guid); +void ub_dynamic_bus_instance_drain(void); +int ub_notify_bus_instance_handle(struct ub_bus_controller *ubc, bool flag, + u32 *guid, u32 eid, u16 upi); +void ub_static_cluster_instance_drain(void); +int ub_default_bus_instance_init(struct ub_entity *uent); +void ub_default_bus_instance_uninit(struct ub_entity *uent); +bool eid_match(struct ub_bus_instance *bi, void *arg); + +#endif /* __INSTANCE_H__ */ diff --git a/drivers/ub/ubus/ioctl.c b/drivers/ub/ubus/ioctl.c index 88da1eccbb5c1e633eb68b3cd7407f89de386500..abcd4e87875513dc2324101481a52a0e5c96d839 100644 --- a/drivers/ub/ubus/ioctl.c +++ b/drivers/ub/ubus/ioctl.c @@ -9,6 +9,7 @@ #include #include "ubus.h" +#include "instance.h" #define UBUS_MAX_DEVICES 1 #define UBUS_DEVICE_NAME "unified_bus" @@ -32,12 +33,47 @@ static int ubus_fops_release(struct inode *inode, struct file *filep) return 0; } +static int ub_ioctl_bus_instance(void __user *uptr) +{ + struct ubus_ioctl_bus_instance bi; + + if (copy_from_user(&bi, uptr, UBUS_IOCTL_HEADER_SIZE)) + return -EFAULT; + + pr_info("ub ioctl bus instance, sub_cmd=%#x\n", bi.sub_cmd); + switch (bi.sub_cmd) { + case UBUS_CMD_BI_CREATE: + if (bi.argsz != sizeof(struct ubus_cmd_bi_create)) + return -EINVAL; + return ub_ioctl_bus_instance_create(uptr); + case UBUS_CMD_BI_DESTROY: + if (bi.argsz != sizeof(struct ubus_cmd_bi_destroy)) + return -EINVAL; + return ub_ioctl_bus_instance_destroy(uptr); + case UBUS_CMD_BI_BIND: + if (bi.argsz != sizeof(struct ubus_cmd_bi_bind)) + return -EINVAL; + return ub_ioctl_bus_instance_bind(uptr); + case UBUS_CMD_BI_UNBIND: + if (bi.argsz != sizeof(struct ubus_cmd_bi_unbind)) + return -EINVAL; + return ub_ioctl_bus_instance_unbind(uptr); + default: + pr_err("ubus bi sub cmd not support, cmd=%#x\n", bi.sub_cmd); + return -EINVAL; + } +} + static long ubus_fops_unl_ioctl(struct file *filep, unsigned int cmd, unsigned long arg) { + void __user *uptr = (void __user *)arg; + switch (cmd) { case UBUS_IOCTL_GET_API_VERSION: return UBUS_API_VERSION; + case UBUS_IOCTL_BUS_INSTANCE: + return ub_ioctl_bus_instance(uptr); default: pr_err("ubus ioctl cmd %#x not support\n", cmd); return -ENOTTY; diff --git a/drivers/ub/ubus/link.c b/drivers/ub/ubus/link.c new file mode 100644 index 0000000000000000000000000000000000000000..266d8e828143d5591d78c051f9bfa0ccc90cb8da --- /dev/null +++ b/drivers/ub/ubus/link.c @@ -0,0 +1,453 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#define pr_fmt(fmt) "ubus link: " fmt + +#include + +#include "ubus.h" +#include "msg.h" +#include "enum.h" +#include "port.h" +#include "route.h" +#include "ubus_entity.h" +#include "ubus_driver.h" +#include "services/hotplug/hotplug.h" +#include "link.h" + +static struct ub_entity *ublc_get_port_r_uent(struct ub_port *port) +{ + struct ub_entity *r_uent = NULL; + void *buf; + +#define UB_TOPO_BUF_SZ SZ_4K + buf = kzalloc(UB_TOPO_BUF_SZ, GFP_KERNEL); + if (!buf) + return NULL; + + r_uent = ub_enum_get_port_r_uent(port, buf); + if (!r_uent) { + port->r_index = 0; + guid_copy(&port->r_guid, &guid_null); + } + + kfree(buf); + return r_uent; +} + +static void ublc_stop_devices(struct list_head *dev_list) +{ + struct ub_entity *uent; + + list_for_each_entry_reverse(uent, dev_list, node) + ub_stop_ent(uent); +} + +static void ublc_remove_devices(struct list_head *dev_list) +{ + struct ub_entity *uent, *tmp; + + list_for_each_entry_safe_reverse(uent, tmp, dev_list, node) + ub_remove_ent(uent); +} + +static void ublc_update_route_link_down(struct ub_port *port) +{ + struct ub_entity *uent = port->uent; + + ub_route_clear_port(port); + + if (ub_entity_support_forward(uent)) + ub_route_del_bfs(uent); + + ub_route_sync_all(); +} + +/** + * ublc_enum_at_port() - enum at port to find new devices + * @port: the port that has new device plugged in or all port down remove + * @dev_list: a list to store the new found devices + * + * this func use bfs to enum devices and put them into dev_list, + * which means the previous device in dev_list is enumerated previous + */ +static int ublc_enum_at_port(struct ub_port *port, struct list_head *dev_list) +{ + void *buf; + int ret; + +#define UB_TOPO_BUF_SZ SZ_4K + buf = kzalloc(UB_TOPO_BUF_SZ, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = ub_enum_topo_scan_ports(port->uent, port->index, 1, dev_list, buf); + if (ret) { + port->r_index = 0; + guid_copy(&port->r_guid, &guid_null); + } + + kfree(buf); + return ret; +} + +static int ublc_update_route_link_up(struct ub_port *port) +{ + struct ub_entity *uent = port->uent, *r_uent = port->r_uent; + struct ub_port *r_port; + int ret; + + r_port = port->r_uent->ports + port->r_index; + ret = ub_route_mod_neighbor(port, r_port); + if (ret) + return ret; + + ret = ub_route_mod_neighbor(r_port, port); + if (ret) + return ret; + + /* for uent that can't forward, there's no need to update other uent */ + if (ub_entity_support_forward(uent)) + ub_route_mod_bfs(uent); + + if (ub_entity_support_forward(r_uent)) + ub_route_mod_bfs(r_uent); + + ub_route_sync_all(); + return 0; +} + +/** + * a simple example for a new device link up, which is hotplug or reset + * for a given topo like + * +-------------+ +---------+ +---------+ +---------+ + * | controller0 |p0:--:p0| switch0 |p1:--slot0--:p0| switch1 |p1:--:p0| device0 | + * +-------------+ +---------+ +---------+ +---------+ + * when port0 is calling handle link up + * 1. enum at port0 to create switch1 and device0, put them in dev_list + * 2. route dev_list to set up route between these two devices + * 3. handle route link up at port0, add route of left(controller0 & switch0) + * into right(switch1 & device0) and route of right into left + * 4. start switch1 and device0 + */ +static int ublc_handle_new_device_link_up(struct ub_port *port) +{ + struct list_head dev_list; + int ret; + + INIT_LIST_HEAD(&dev_list); + + ret = ublc_enum_at_port(port, &dev_list); + if (ret) { + ub_err(port->uent, "link up enum at port%u failed, ret=%d\n", + port->index, ret); + return ret; + } + + if (list_empty(&dev_list)) { + ub_warn(port->uent, "link up without remote dev\n"); + if (port->link_state == LINK_STATE_RESETING) + port->link_state = LINK_STATE_DONE; + return -ENXIO; + } + + ret = ub_route_entities(&dev_list); + if (ret) { + ub_err(port->uent, "link up cal route failed, ret=%d\n", ret); + goto err_route; + } + + if (port->slot) + port->slot->r_uent = port->r_uent; + + ret = ublc_update_route_link_up(port); + if (ret) { + ub_err(port->uent, "link up update route failed, ret=%d\n", ret); + goto err_link_up; + } + + ret = ub_enum_entities_active(&dev_list); + if (ret) { + ub_err(port->uent, "link up start devices failed, ret=%d\n", ret); + goto err_link_up; + } + + if (port->link_state == LINK_STATE_RESETING) + port->link_state = LINK_STATE_DONE; + + return 0; + +err_link_up: + ublc_update_route_link_down(port); + port->r_uent = NULL; + if (port->slot) + port->slot->r_uent = NULL; +err_route: + ub_enum_clear_ent_list(&dev_list); + return ret; +} + +/** + * ublc_mark_detached_devices() - mark devices as detached and put them into dev_list + * @root: a device that needs to be removed + * @dev_list: a list to store the detached devices + * + * this func use bfs to mark devices and put them into dev_list, all devices + * that connected with root will be marked as detached + */ +static void ublc_mark_detached_devices(struct ub_entity *root, + struct list_head *dev_list) +{ +#define UB_LC_KFIFO_DEPTH SZ_16 + DECLARE_KFIFO(kfifo, struct ub_entity *, UB_LC_KFIFO_DEPTH); + struct ub_entity *uent, *r_uent; + struct ub_port *port; + + INIT_KFIFO(kfifo); + ub_entity_assign_priv_flag(root, UB_ENTITY_DETACHED, true); + kfifo_put(&kfifo, root); + + down_write(&ub_bus_sem); + while (kfifo_get(&kfifo, &uent)) { + for_each_uent_port(port, uent) { + if (!port->r_uent) + continue; + + r_uent = port->r_uent; + if (ub_entity_test_priv_flag(r_uent, UB_ENTITY_DETACHED)) + continue; + + ub_entity_assign_priv_flag(r_uent, UB_ENTITY_DETACHED, true); + if (!kfifo_put(&kfifo, r_uent)) + ub_err(r_uent, "%s kfifo put failed!\n", __func__); + } + + list_del(&uent->node); + list_add_tail(&uent->node, dev_list); + } + up_write(&ub_bus_sem); +} + +/** + * a simple example for link down + * for a given topo like + * +-------------+ +---------+ +---------+ +---------+ + * | controller0 |p0:--:p0| switch0 |p1:--slot0--:p0| switch1 |p1:--:p0| device0 | + * +-------------+ +---------+ +---------+ +---------+ + * when slot0's final port is calling handle link down + * 1. disconnect p1 and p0 so that p1 and p0 is not connected in software + * 2. put switch1 and device0 into dev_list, mark them as detached + * 3. stop switch1 and device0 + * 4. remove switch1 and device0 + * 5. handle route link down at p1, del route of right(switch1 & device0) + * from left(controller0 & switch0) + */ +static void ublc_handle_all_link_down(struct ub_port *port, struct ub_entity *r_uent) +{ + struct list_head dev_list; + + INIT_LIST_HEAD(&dev_list); + ub_port_disconnect(port); + + if (port->slot) + port->slot->r_uent = NULL; + + ublc_mark_detached_devices(r_uent, &dev_list); + ublc_stop_devices(&dev_list); + ublc_remove_devices(&dev_list); + ublc_update_route_link_down(port); +} + +static bool ublc_device_is_down(struct ub_port *port) +{ + struct ub_entity *r_uent = port->r_uent; + struct ub_entity *uent = port->uent; + struct ub_port *tmp; + + for_each_uent_port(tmp, uent) + if (tmp->index != port->index && tmp->r_uent == r_uent) + return false; + + return true; +} + +static void port_link_state_change(struct ub_port *port, struct ub_port *r_port) +{ + if (port->link_state == LINK_STATE_RESETING) + port->link_state = LINK_STATE_DONE; + + if (r_port->link_state == LINK_STATE_RESETING) + r_port->link_state = LINK_STATE_DONE; +} + +void ublc_link_up_handle(struct ub_port *port) +{ + struct ub_entity *uent = port->uent; + struct ub_port *r_port; + struct ub_entity *r_uent; + int ret; + + if (port->r_uent) { + ub_err(uent, "port%u is already up\n", port->index); + return; + } + + device_lock(&uent->dev); + + r_uent = ublc_get_port_r_uent(port); + if (!r_uent) { + ret = ublc_handle_new_device_link_up(port); + if (ret) { + ubhp_handle_power(port->slot, false); + } else { + ub_info(uent, "port%u link up and create device\n", + port->index); + ubhp_handle_power(port->slot, true); + } + goto out; + } + + if (!ub_check_and_connect(port, r_uent)) + goto out; + + r_port = port->r_uent->ports + port->r_index; + ret = ub_route_table_set_for_port(port, r_port); + if (ret) { + ub_err(uent, "port%u up set route table failed! ret=%d\n", + port->index, ret); + goto out; + } + + port_link_state_change(port, r_port); + ub_info(uent, "port%u link up\n", port->index); +out: + device_unlock(&uent->dev); +} + +void ublc_link_down_handle(struct ub_port *port) +{ + struct ub_entity *uent = port->uent; + struct ub_port *r_port; + + if (!port->r_uent) { + ub_err(uent, "port%u is already down\n", port->index); + return; + } + + device_lock(&uent->dev); + + if (ublc_device_is_down(port)) { + ub_info(uent, "port%u link down\n", port->index); + ublc_handle_all_link_down(port, port->r_uent); + ub_info(uent, "all port link down and remove device\n"); + device_unlock(&uent->dev); + + return; + } + + r_port = port->r_uent->ports + port->r_index; + + ub_route_table_clear_for_port(port, r_port); + ub_port_disconnect(port); + + device_unlock(&uent->dev); + + ub_info(uent, "port%u link down\n", port->index); +} + +void ub_link_change_handler(struct work_struct *work) +{ + struct ub_port *port = container_of(work, struct ub_port, link_work); + + if (port->link_event == UB_LINK_UP) + ublc_link_up_handle(port); + else + ublc_link_down_handle(port); +} + +static void ub_link_handle_event(struct ub_port *port, enum ub_link_event event) +{ + if (event == UB_LINK_UP) + ublc_link_up_handle(port); + else + ublc_link_down_handle(port); +} + +static struct ub_port *ub_link_get_port_from_msg(void *pkt) +{ + struct msg_pkt_header *header = (struct msg_pkt_header *)pkt; + struct link_msg_payload *payload; + struct ub_port *port = NULL; + struct ub_entity *uent; + u32 seid; + + seid = eid_gen(header->seid_h, header->seid_l); + uent = ub_get_ent_by_eid(seid); + if (!uent) { + pr_warn("get no device by eid %u\n", seid); + return NULL; + } + + payload = (struct link_msg_payload *)header->payload; + if (payload->port_idx >= uent->port_nums) { + pr_err("link port idx %u exceeds uent port num %u\n", + payload->port_idx, uent->port_nums); + goto out; + } + + port = uent->ports + payload->port_idx; + +out: + ub_entity_put(uent); + return port; +} + +static void ub_link_event_handler(struct ub_bus_controller *ubc, void *pkt, u16 len) +{ + struct msg_pkt_header *header = (struct msg_pkt_header *)pkt; + struct ub_port *port; + + if (len < UB_LINK_MSG_SIZE) { + dev_err(&ubc->dev, "lc msg len[%#x] invalid\n", len); + return; + } + + port = ub_link_get_port_from_msg(pkt); + if (!port) + return; + + ub_link_handle_event(port, + (enum ub_link_event)header->msgetah.sub_msg_code); +} + +/** + * for an incoming msg, the following procedure is performed: + * 1. parse relating link event from msg + * 2. parse slot/port that has this event + * 3. if hotplug event is button pressed, queue a button work; if hotplug event + * is card presence, queue a present work with 0s delay, if the work + * already exists, modify it's delay time to 0s + */ + +static rx_msg_handler_t link_msg_handler[UB_SUB_MSG_CODE_NUM] = { + ub_link_event_handler, + ub_link_event_handler, + ubhp_event_handler, + ubhp_event_handler, +}; + +void ub_link_msg_handler(struct ub_bus_controller *ubc, void *pkt, u16 len) +{ + struct msg_pkt_header *header = (struct msg_pkt_header *)pkt; + u8 sub_msg_code = header->msgetah.sub_msg_code; + rx_msg_handler_t handler; + + handler = link_msg_handler[sub_msg_code]; + if (handler) + handler(ubc, pkt, len); + else + dev_err(&ubc->dev, "link sub msg code[%#x] not support\n", + sub_msg_code); +} diff --git a/drivers/ub/ubus/link.h b/drivers/ub/ubus/link.h new file mode 100644 index 0000000000000000000000000000000000000000..004d06b948e2cb3503dce6f654a581f841fa36c3 --- /dev/null +++ b/drivers/ub/ubus/link.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#ifndef __LINK_H__ +#define __LINK_H__ + +struct link_msg_payload { + /* DW0 */ + u32 scna : 24; + u32 rsv0 : 8; + /* DW1 */ + u32 port_idx : 16; + u32 rsv1 : 16; +}; + +#define UB_LINK_MSG_SIZE 40 + +enum ub_link_event { + UB_LINK_UP, + UB_LINK_DOWN +}; + +void ub_link_change_handler(struct work_struct *work); +void ublc_link_up_handle(struct ub_port *port); +void ublc_link_down_handle(struct ub_port *port); +void ub_link_msg_handler(struct ub_bus_controller *ubc, void *pkt, u16 len); + +#endif /* __LINK_H__ */ diff --git a/drivers/ub/ubus/msg.c b/drivers/ub/ubus/msg.c index 67dc56ab8ce3ded834a3c22e36e15a232665be0d..5b0e31e670c7299494c1476a78839b5f54cb9970 100644 --- a/drivers/ub/ubus/msg.c +++ b/drivers/ub/ubus/msg.c @@ -9,6 +9,8 @@ #include #include +#include "pool.h" +#include "link.h" #include "msg.h" u8 err_to_msg_rsp(int err) @@ -135,6 +137,16 @@ int message_send(struct message_device *mdev, struct msg_info *info, return -ENOTTY; } +int message_response(struct message_device *mdev, struct msg_info *info, + u8 code) +{ + if (mdev->ops->response) + return mdev->ops->response(mdev, info, code); + + return -ENOTTY; +} +EXPORT_SYMBOL_GPL(message_response); + int message_sync_enum(struct message_device *mdev, struct msg_info *info, u8 cmd) { @@ -143,3 +155,172 @@ int message_sync_enum(struct message_device *mdev, struct msg_info *info, return -ENOTTY; } + +static struct workqueue_struct *rx_msg_wq[UB_MSG_CODE_NUM]; +struct workqueue_struct *get_rx_msg_wq(u8 msg_code) +{ + return rx_msg_wq[msg_code]; +} + +static bool msg_rx_flag; + +int message_rx_init(void) +{ + const char *msg_name[UB_MSG_CODE_NUM] = { + NULL, "link wq", NULL, NULL, + NULL, NULL, "pool wq", NULL + }; + struct workqueue_struct *q; + int i; + + for (i = 0; i < UB_MSG_CODE_NUM; i++) { + if (!msg_name[i]) + continue; + q = create_singlethread_workqueue(msg_name[i]); + if (!q) { + pr_err("alloc workqueue[%d] failed\n", i); + message_rx_uninit(); + return -ENOMEM; + } + + rx_msg_wq[i] = q; + } + + msg_rx_flag = true; + + return 0; +} + +void message_rx_uninit(void) +{ +#define MSG_RX_WAIT_US 1000 + struct workqueue_struct *q; + int i; + + msg_rx_flag = false; + /* For cpus still handle rx msg in interrupt context */ + udelay(MSG_RX_WAIT_US); + + for (i = 0; i < UB_MSG_CODE_NUM; i++) { + q = rx_msg_wq[i]; + if (q) { + flush_workqueue(q); + destroy_workqueue(q); + rx_msg_wq[i] = NULL; + } + } +} + +static struct ub_rx_msg_task * +message_rx_task_alloc_and_init(struct ub_bus_controller *ubc, void *pkt, u16 len, + work_func_t func) +{ + struct ub_rx_msg_task *task; + + task = kzalloc(sizeof(*task), GFP_ATOMIC); + if (!task) + return (struct ub_rx_msg_task *)ERR_PTR(-ENOMEM); + + task->pkt = kzalloc(len, GFP_ATOMIC); + if (!task->pkt) { + kfree(task); + return (struct ub_rx_msg_task *)ERR_PTR(-ENOMEM); + } + + memcpy(task->pkt, pkt, len); + task->len = len; + task->ubc = ubc; + INIT_WORK(&task->work, func); + + return task; +} + +static void message_rx_task_free(struct ub_rx_msg_task *task) +{ + kfree(task->pkt); + kfree(task); +} + +static bool msg_rx_code_valid(struct ub_bus_controller *ubc, u8 code) +{ + u8 msg = msg_code(code); + + if (msg_type(code) == MSG_RSP) + return false; + + if (msg == UB_MSG_CODE_RAS || msg == UB_MSG_CODE_CFG || + msg == UB_MSG_CODE_EXCH || msg == UB_MSG_CODE_MAX) + return false; + + if (!ubc->cluster && msg == UB_MSG_CODE_POOL) + return false; + + return true; +} + +static rx_msg_handler_t rx_msg_handler[UB_MSG_CODE_NUM] = { + NULL, + ub_link_msg_handler, + NULL, + NULL, + NULL, + NULL, + ub_pool_rx_msg_handler, +}; + +static void message_rx_work(struct work_struct *work) +{ + struct ub_rx_msg_task *task = container_of(work, struct ub_rx_msg_task, + work); + struct msg_pkt_header *header = (struct msg_pkt_header *)task->pkt; + u8 msg_code = header->msgetah.msg_code; + struct ub_bus_controller *ubc = task->ubc; + rx_msg_handler_t handler; + + handler = rx_msg_handler[msg_code]; + + if (handler) + handler(ubc, task->pkt, task->len); + else + dev_err(&ubc->dev, "rx msg code not support, code=%#x\n", + msg_code); + + message_rx_task_free(task); +} + +int message_rx_handler(struct ub_bus_controller *ubc, void *pkt, u16 len) +{ + struct msg_pkt_header *header = (struct msg_pkt_header *)pkt; + struct msg_extended_header *msgetah = &header->msgetah; + struct ub_rx_msg_task *task; + + if (!msg_rx_flag) + return -EBUSY; + + if (len < MSG_PKT_HEADER_SIZE) { + dev_err(&ubc->dev, "rx msg len invalid, len=%#x\n", len); + return -EINVAL; + } + + if (msgetah->plen != len - MSG_PKT_HEADER_SIZE) { + dev_err(&ubc->dev, "rx msg plen invalid, len=%#x, plen=%#x\n", + len, msgetah->plen); + return -EINVAL; + } + + if (!msg_rx_code_valid(ubc, msgetah->code)) { + dev_err(&ubc->dev, "rx msg code invalid, code=%#x\n", + msgetah->code); + return -EINVAL; + } + + dev_info(&ubc->dev, "rx msg coming, code=%#x\n", msgetah->code); + + task = message_rx_task_alloc_and_init(ubc, pkt, len, message_rx_work); + if (IS_ERR(task)) + return PTR_ERR(task); + + queue_work(rx_msg_wq[msgetah->msg_code], &task->work); + return 0; +} +EXPORT_SYMBOL_GPL(message_rx_handler); diff --git a/drivers/ub/ubus/msg.h b/drivers/ub/ubus/msg.h index c980033803bc3cc83577fbb31bcef5e01c4bc507..858782c2fead0ada7df0fa284804ad75c290eecc 100644 --- a/drivers/ub/ubus/msg.h +++ b/drivers/ub/ubus/msg.h @@ -186,12 +186,15 @@ enum message_tx_type { #define msg_size_gen(req_size, rsp_size) ((u32)((req_size) << 16) | (rsp_size)) +typedef void (*rx_msg_handler_t)(struct ub_bus_controller *ubc, void *pkt, u16 len); + /** * struct message_ops - message ops and capabilities * @probe_dev: probe ub_entity to init message * @remove_dev: remove ub_entity to uninit message * @sync_request: send message to target ub_entity and wait response * @send: send message to target ub_entity but not wait response + * @response: send response message to target * @sync_enum: send enum message to target ub_entity and wait response * @owner: Driver module providing these ops */ @@ -202,6 +205,8 @@ struct message_ops { u8 code); int (*send)(struct message_device *mdev, struct msg_info *info, u8 code); + int (*response)(struct message_device *mdev, struct msg_info *info, + u8 code); int (*sync_enum)(struct message_device *mdev, struct msg_info *info, u8 cmd); struct module *owner; @@ -256,7 +261,21 @@ int message_sync_request(struct message_device *mdev, struct msg_info *info, u8 code); int message_send(struct message_device *mdev, struct msg_info *info, u8 code); +int message_response(struct message_device *mdev, struct msg_info *info, + u8 code); int message_sync_enum(struct message_device *mdev, struct msg_info *info, u8 cmd); +struct ub_rx_msg_task { + struct ub_bus_controller *ubc; + void *pkt; + u16 len; + struct work_struct work; +}; + +struct workqueue_struct *get_rx_msg_wq(u8 msg_code); +int message_rx_handler(struct ub_bus_controller *ubc, void *pkt, u16 len); +int message_rx_init(void); +void message_rx_uninit(void); + #endif /* __MSG_H__ */ diff --git a/drivers/ub/ubus/pool.c b/drivers/ub/ubus/pool.c new file mode 100644 index 0000000000000000000000000000000000000000..2aeb8d57ee9aea5461f402b0e6eea1909451c514 --- /dev/null +++ b/drivers/ub/ubus/pool.c @@ -0,0 +1,647 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#define pr_fmt(fmt) "ubus pool: " fmt + +#include "ubus.h" +#include "ubus_controller.h" +#include "ubus_entity.h" +#include "resource.h" +#include "msg.h" +#include "enum.h" +#include "port.h" +#include "reset.h" +#include "instance.h" +#include "ubus_inner.h" +#include "pool.h" + +struct cfg_cpl_notify_pld { + u32 flag : 1; + u32 rsvd : 31; + u32 guid[UB_GUID_DW_NUM]; + u32 eid[4]; +}; +#define CFG_CPL_NOTIFY_PLD_SIZE 36 + +struct bi_create_pld { + u32 guid[UB_GUID_DW_NUM]; + u32 eid[4]; + u32 upi : 15; + u32 rsvd1 : 17; +}; +#define BI_CREATE_PLD_SIZE 36 + +struct bi_destroy_pld { + u32 guid[UB_GUID_DW_NUM]; +}; +#define BI_DESTROY_PLD_SIZE 16 + +struct pool_msg_pkt { + struct msg_pkt_header header; + union { + struct entity_reg_msg_pld reg; + struct entity_rls_msg_pld rls; + struct cfg_cpl_notify_pld notify; + struct bi_create_pld create; + struct bi_destroy_pld destroy; + struct port_reset_notify_pld port_reset; + }; +}; + +#define MSG_ENTITY_REG_SIZE (MSG_PKT_HEADER_SIZE + ENTITY_REG_PLD_SIZE) +#define MSG_ENTITY_RLS_SIZE (MSG_PKT_HEADER_SIZE + ENTITY_RLS_PLD_SIZE) +#define MSG_BI_CREATE_SIZE (MSG_PKT_HEADER_SIZE + BI_CREATE_PLD_SIZE) +#define MSG_BI_DESTROY_SIZE (MSG_PKT_HEADER_SIZE + BI_DESTROY_PLD_SIZE) +#define MSG_CFG_CPL_NOTIFY_SIZE (MSG_PKT_HEADER_SIZE + CFG_CPL_NOTIFY_PLD_SIZE) +#define MSG_PORT_RESET_SIZE (MSG_PKT_HEADER_SIZE + PORT_RESET_NOTIFY_PLD_SIZE) + +static DEFINE_SPINLOCK(ub_fad_lock); + +struct ub_fad_collection { + struct list_head list; + struct list_head node; +}; + +static struct ub_fad_collection ub_fad = { + .list = LIST_HEAD_INIT(ub_fad.list), + .node = LIST_HEAD_INIT(ub_fad.node), +}; + +static struct pool_fad *ub_get_fad(u32 eid) +{ + struct pool_fad *fad; + unsigned long flags; + + spin_lock_irqsave(&ub_fad_lock, flags); + list_for_each_entry(fad, &ub_fad.node, node) + if (fad->base.eid[0] == eid) + goto out; + + fad = NULL; +out: + spin_unlock_irqrestore(&ub_fad_lock, flags); + return fad; +} + +struct ub_entity *ub_get_fad_ent_by_eid(unsigned int eid) +{ + struct pool_fad *fad; + + fad = ub_get_fad(eid); + if (fad) + return fad->uent; + + return NULL; +} + +static int ub_fad_res_init(struct pool_fad *fad, struct ub_entity *uent) +{ + u32 source_support_map[MAX_UB_RES_NUM] = { UB_ERS0S_SUPPORT, + UB_ERS1S_SUPPORT, + UB_ERS2S_SUPPORT }; + u32 support_feature = 0; + size_t pg_size; + u8 sys_pg = 0; + int ret, i; + + if (!fad->ers_valid) + return 0; + + ret = ub_cfg_read_byte(uent, UB_SYS_PGS, &sys_pg); + if (ret) { + ub_err(uent, "entity reg read UB_SYS_PGS failed, ret=%d\n", ret); + return ret; + } + pg_size = (sys_pg & UB_SYS_PGS_SIZE) ? SZ_64K : SZ_4K; + + ret = ub_cfg_read_dword(uent, UB_CFG1_SUPPORT_FEATURE_L, &support_feature); + if (ret) { + ub_err(uent, "read cfg1 feature_l failed, ret=%d\n", ret); + return ret; + } + + for (i = 0; i < MAX_UB_RES_NUM; i++) { + if (!(support_feature & source_support_map[i])) + continue; + + uent->zone[i].res.start = ubba_gen(fad->ers[i].sa_h, + fad->ers[i].sa_l); + uent->zone[i].res.end = uent->zone[i].res.start + + ((u64)fad->ers[i].ss * pg_size - 1); + uent->zone[i].res.flags = IORESOURCE_MEM; + uent->zone[i].res.name = ub_name(uent); + uent->zone[i].sa_used = 1; + uent->zone[i].ubba_used = 0; + ub_info(uent, "mmio_idx=%d, res=%pR\n", i, &uent->zone[i].res); + + ret = ub_insert_resource(uent, i); + if (ret) { + ub_err(uent, "mmio[%d] insert res failed, ret=%d\n", i, + ret); + goto fail; + } + + ub_info(uent, "MMIO[%d]: ubba=%dx, size=%#llx, hpa=%#llx, wr_attr=%01u, prefetchable=%01u, order_type=%01u\n", + i, 0, ub_resource_len(uent, i), + uent->zone[i].res.start, 0, 0, 0); + uent->zone[i].init_succ = 1; + } + + return 0; + +fail: + for (i -= 1; i >= 0; i--) + if (uent->zone[i].init_succ) + ub_entity_free_mmio_idx(uent, i); + return ret; +} + +static void ub_fad_uent_init(struct pool_fad *fad, struct ub_entity *uent) +{ + uent->pool = true; + uent->eid = fad->base.eid[0]; + uent->user_eid = fad->base.ueid[0]; + uent->cna = fad->base.cna; + uent->upi = fad->base.upi; + uent->entity_idx = fad->base.entity_idx; + uent->ubc = ub_ubc_get(fad->ubc); + memcpy(uent->guid.dw, fad->base.guid, UB_GUID_SIZE); + uent->pue = uent; + uent->is_mue = 1; +} + +static int ub_fad_attach(struct pool_fad *fad) +{ + struct ub_entity *uent; + int ret; + + if (fad->attach) + return 0; + + uent = ub_alloc_ent(); + if (!uent) + return -ENOMEM; + + ub_fad_uent_init(fad, uent); + + ret = ub_setup_ent(uent); + if (ret) + goto fail; + + ret = ub_fad_res_init(fad, uent); + WARN_ON(ret); + + ub_entity_add(uent, uent->ubc); + + fad->uent = uent; + fad->attach = true; + return 0; +fail: + ub_ubc_put(uent->ubc); + kfree(uent); + return ret; +} + +static void ub_fad_detach(struct pool_fad *fad) +{ + ub_stop_and_remove_ent(fad->uent); + fad->uent = NULL; +} + +bool ub_rsp_msg_init(struct msg_pkt_header *header, u8 status, u32 plen) +{ + struct compact_network_header *cnth = &header->nth; + u32 seid = header->deid; + u32 deid = eid_gen(header->seid_h, header->seid_l); + u16 dcna = cnth->scna; + u16 scna = cnth->dcna; + + cnth->scna = scna; + cnth->dcna = dcna; + + header->seid_h = seid_high(seid); + header->seid_l = seid_low(seid); + header->deid = deid; + + header->msgetah.plen = plen; + header->msgetah.rsp_status = status; + header->msgetah.type = MSG_RSP; + + return (scna == dcna); +} +EXPORT_SYMBOL_GPL(ub_rsp_msg_init); + +static void ub_fad_para_init(struct pool_fad *fad, struct entity_reg_msg_pld *pld) +{ + struct ub_guid *guid = (struct ub_guid *)fad->base.guid; + char buf[SZ_64] = {}; + + memcpy(&fad->base, &pld->base, sizeof(struct entity_base_info)); + + (void)ub_show_guid(guid, buf); + + dev_info(&fad->ubc->dev, + "fad_add_idx=%u, eid=%#x, cna=%#x, upi=%#x, u_eid=%#05x, guid=%s\n", + fad->base.entity_idx, fad->base.eid[0], fad->base.cna, + fad->base.upi, fad->base.ueid[0], buf); + + if (fad->ers_valid) + memcpy(fad->ers, pld->ers, ENTITY_RS_PLD_SIZE); +} + +static int ub_entity_reg_check(struct ub_bus_controller *ubc, + struct entity_reg_msg_pld *pld, bool ers_valid) +{ + struct entity_rs_info *ers; + struct device *dev = &ubc->dev; + int i; + + if (pld->base.eid[0] == 0 || pld->base.ueid[0] == 0) { + dev_err(dev, "entity reg eid=%#x u_eid=%#x is invalid\n", + pld->base.eid[0], pld->base.ueid[0]); + return -EINVAL; + } + + if (!ers_valid) + return 0; + + ers = (struct entity_rs_info *)pld->ers; + for (i = 0; i < MAX_UB_RES_NUM; i++) { + if (ers[i].ss == 0) { + dev_err(dev, "entity reg ers[%d] size is 0\n", i); + return -EINVAL; + } + } + + return 0; +} + +static u8 ub_entity_reg_handle(struct ub_bus_controller *ubc, + struct pool_msg_pkt *pkt, bool ers_valid, + struct pool_fad **start_fad) +{ + struct entity_reg_msg_pld *pld = &pkt->reg; + struct pool_fad *fad; + struct ub_entity *uent; + int ret; + + uent = ub_get_ent_by_eid(pld->base.eid[0]); + if (uent) { + ub_entity_put(uent); + return UB_MSG_RSP_EXEC_EEXIST; + } + + ret = ub_entity_reg_check(ubc, pld, ers_valid); + if (ret) + return UB_MSG_RSP_EXEC_EINVAL; + + fad = kzalloc(sizeof(*fad), GFP_KERNEL); + if (!fad) + return UB_MSG_RSP_EXEC_ENOMEM; + + fad->ubc = ubc; + fad->ers_valid = ers_valid; + ub_fad_para_init(fad, pld); + + ret = ub_fad_attach(fad); + if (ret) { + dev_err(&ubc->dev, "fad idx[%u] add failed\n", fad->base.entity_idx); + kfree(fad); + return UB_MSG_RSP_EXEC_ENOEXEC; + } + + spin_lock(&ub_fad_lock); + list_add_tail(&fad->node, &ub_fad.node); + spin_unlock(&ub_fad_lock); + *start_fad = fad; + + return UB_MSG_RSP_SUCCESS; +} + +static u8 +ub_entity_rls_handle(struct ub_bus_controller *ubc, struct pool_msg_pkt *pkt) +{ + struct entity_rls_msg_pld *pld = &pkt->rls; + struct pool_fad *fad; + + fad = ub_get_fad(pld->eid[0]); + if (!fad) + return UB_MSG_RSP_EXEC_ENODEV; + + spin_lock(&ub_fad_lock); + list_del(&fad->node); + spin_unlock(&ub_fad_lock); + + if (fad->attach) { + ub_info(fad->uent, "fad detach, eid=%#x, reason=%#x\n", + fad->uent->eid, pld->reason); + ub_fad_detach(fad); + fad->attach = false; + } + + kfree(fad); + + return UB_MSG_RSP_SUCCESS; +} + +static void +ub_entity_rls_msg_handler(struct ub_bus_controller *ubc, void *msg, u16 p_len) +{ + struct pool_msg_pkt *pkt = (struct pool_msg_pkt *)msg; + struct msg_pkt_header *header = &pkt->header; + struct msg_info info = {}; + bool local; + u8 status; + int ret; + + if (p_len != MSG_ENTITY_RLS_SIZE) { + dev_err(&ubc->dev, "entity rls msg len is wrong, len=%#x\n", + p_len); + status = UB_MSG_RSP_CMD_LEN_ERR; + goto rsp; + } + + status = ub_entity_rls_handle(ubc, pkt); + +rsp: + local = ub_rsp_msg_init(header, status, 0); + message_info_init(&info, local ? ubc->uent : NULL, pkt, NULL, + (MSG_PKT_HEADER_SIZE << MSG_REQ_SIZE_OFFSET)); + + ret = message_response(ubc->mdev, &info, header->msgetah.code); + if (ret) + dev_err(&ubc->dev, "send entity rls rsp, ret=%d\n", ret); +} + +static void +ub_entity_reg_msg_handler(struct ub_bus_controller *ubc, void *msg, u16 p_len) +{ + struct pool_msg_pkt *pkt = (struct pool_msg_pkt *)msg; + struct msg_pkt_header *header = &pkt->header; + struct msg_info info = {}; + struct pool_fad *start_fad; + bool local, flag; + u32 feature = 0; + u8 status; + int ret; + + if (p_len != MSG_ENTITY_REG_SIZE) { + dev_err(&ubc->dev, "entity reg len err, len=%#x\n", p_len); + status = UB_MSG_RSP_CMD_LEN_ERR; + goto rsp; + } + + ret = ub_cfg_read_dword(ubc->uent, UB_CFG1_SUPPORT_FEATURE_L, &feature); + if (ret) { + dev_err(&ubc->dev, "entity reg feature read failed, ret=%d\n", ret); + status = UB_MSG_RSP_EXEC_ENOEXEC; + goto rsp; + } + + flag = !!(feature & UB_DECODER_JURIS); + status = ub_entity_reg_handle(ubc, pkt, flag, &start_fad); + +rsp: + local = ub_rsp_msg_init(header, status, 0); + message_info_init(&info, local ? ubc->uent : NULL, pkt, NULL, + (MSG_PKT_HEADER_SIZE << MSG_REQ_SIZE_OFFSET)); + + ret = message_response(ubc->mdev, &info, header->msgetah.code); + if (ret) + dev_err(&ubc->dev, "send pool rsp msg, ret=%d\n", ret); + + if (status == UB_MSG_RSP_SUCCESS) + ub_start_ent(start_fad->uent); +} + +static void ub_cfg_cpl_notify_msg_rsp(struct ub_bus_controller *ubc, + struct msg_pkt_header *header) +{ + struct msg_info info = {}; + u32 tmp_eid, tmp_cna; + bool local; + int ret; + + tmp_eid = header->deid; + header->deid = eid_gen(header->seid_h, header->seid_l); + header->seid_h = seid_high(tmp_eid); + header->seid_l = seid_low(tmp_eid); + + tmp_cna = header->nth.scna; + header->nth.scna = header->nth.dcna; + header->nth.dcna = tmp_cna; + header->msgetah.type = MSG_RSP; + header->msgetah.plen = 0; + + local = (header->nth.scna == header->nth.dcna); + message_info_init(&info, local ? ubc->uent : NULL, header, NULL, + (MSG_PKT_HEADER_SIZE << MSG_REQ_SIZE_OFFSET)); + + ret = message_response(ubc->mdev, &info, header->msgetah.code); + if (ret) + dev_err(&ubc->dev, "send notify rsp failed, ret=%d\n", ret); +} + +int ub_fm_flush_ubc_info(struct ub_bus_controller *ubc) +{ + struct device *dev = &ubc->dev; + int ret = -ENOMEM; + u32 eid, fm_cna; + char *buf; + u16 upi; + + buf = kzalloc(SZ_4K, GFP_KERNEL); + if (!buf) + goto out; + + ret = ub_cfg_read_word(ubc->uent, UB_UPI, &upi); + if (ret) { + dev_err(dev, "update cluster upi failed, ret=%d\n", ret); + goto free_buf; + } + + ubc->uent->upi = upi & UB_UPI_MASK; + dev_info(dev, "update cluster ubc upi to %#x\n", ubc->uent->upi); + + ret = ub_cfg_read_dword(ubc->uent, UB_EID_0, &eid); + if (ret) { + dev_err(dev, "update cluster ubc eid failed, ret=%d\n", ret); + goto free_buf; + } + + if (eid <= ubc_eid_end) { + dev_err(dev, "update cluster ubc wrong, eid=%#x\n", eid); + ret = -EINVAL; + goto free_buf; + } + + ubc->uent->eid = eid & UB_COMPACT_EID_MASK; + dev_info(dev, "update cluster ubc eid to %#x\n", ubc->uent->eid); + + ret = ub_cfg_read_dword(ubc->uent, UB_FM_CNA, &fm_cna); + if (ret) { + dev_err(dev, "read fm cna failed, ret=%d\n", ret); + goto free_buf; + } + + ubc->uent->fm_cna = fm_cna & UB_FM_CNA_MASK; + dev_info(dev, "update cluster ubc fm cna to %#x\n", ubc->uent->fm_cna); + + ret = ub_query_ent_na(ubc->uent, buf); + if (ret) { + dev_err(dev, "update cluster ubc cna failed, ret=%d\n", ret); + goto free_buf; + } + + ret = ub_query_port_na(ubc->uent, buf); + +free_buf: + kfree(buf); +out: + return ret; +} + +static void ub_cfg_cpl_notify_handler(struct ub_bus_controller *ubc, void *msg, + u16 p_len) +{ + struct pool_msg_pkt *pkt = (struct pool_msg_pkt *)msg; + struct cfg_cpl_notify_pld *notify = &pkt->notify; + struct msg_pkt_header *header = &pkt->header; + u8 rsp_status = UB_MSG_RSP_SUCCESS; + int ret; + + if (p_len != MSG_CFG_CPL_NOTIFY_SIZE) { + dev_err(&ubc->dev, "notify msg len is wrong, len=%#x\n", p_len); + rsp_status = UB_MSG_RSP_CMD_LEN_ERR; + goto rsp; + } + + ret = ub_fm_flush_ubc_info(ubc); + if (ret) { + rsp_status = err_to_msg_rsp(ret); + goto rsp; + } + + ret = ub_notify_bus_instance_handle(ubc, notify->flag, notify->guid, + notify->eid[0], ubc->uent->upi); + if (ret) { + dev_err(&ubc->dev, "handle notify bi failed, ret=%d\n", ret); + rsp_status = err_to_msg_rsp(ret); + } +rsp: + header->msgetah.rsp_status = rsp_status; + ub_cfg_cpl_notify_msg_rsp(ubc, header); +} + +static void ub_pool_bi_handler(struct ub_bus_controller *ubc, void *msg, u16 p_len) +{ + struct pool_msg_pkt *pkt = (struct pool_msg_pkt *)msg; + u32 size = MSG_PKT_HEADER_SIZE << MSG_REQ_SIZE_OFFSET; + struct msg_pkt_header *header = &pkt->header; + struct bi_create_pld *pld = &pkt->create; + u8 status = UB_MSG_RSP_SUCCESS; + struct device *dev = &ubc->dev; + struct msg_info info = {}; + bool local; + int ret; + + if (header->msgetah.sub_msg_code == UB_BI_CREATE) { + if (p_len != MSG_BI_CREATE_SIZE) { + dev_err(dev, "bi create msg len is wrong, len=%#x\n", + p_len); + status = UB_MSG_RSP_CMD_LEN_ERR; + goto rsp; + } + + ret = ub_msg_bus_instance_create(ubc, pld->guid, pld->eid[0], + pld->upi, EID_BYPASS); + } else { + if (p_len != MSG_BI_DESTROY_SIZE) { + dev_err(dev, "bi destroy msg len is wrong, len=%#x\n", + p_len); + status = UB_MSG_RSP_CMD_LEN_ERR; + goto rsp; + } + ret = ub_msg_bus_instance_destroy(ubc, pld->guid); + } + + if (ret) + status = UB_MSG_RSP_EXEC_ENOEXEC; + +rsp: + local = ub_rsp_msg_init(header, status, 0); + message_info_init(&info, local ? ubc->uent : NULL, pkt, pkt, size); + + ret = message_response(ubc->mdev, &info, header->msgetah.code); + if (ret) + dev_err(dev, "send bi rsp msg, ret=%d\n", ret); +} + +static void ub_port_reset_notify_handler(struct ub_bus_controller *ubc, void *msg, + u16 p_len) +{ + u32 size = MSG_PKT_HEADER_SIZE << MSG_REQ_SIZE_OFFSET; + struct pool_msg_pkt *pkt = (struct pool_msg_pkt *)msg; + struct msg_pkt_header *header = &pkt->header; + struct port_reset_notify_pld *pld; + u8 status = UB_MSG_RSP_SUCCESS; + struct msg_info info = {}; + struct ub_port *port = NULL; + bool local; + int ret; + + pld = &pkt->port_reset; + if (p_len != MSG_PORT_RESET_SIZE) { + dev_err(&ubc->dev, + "ub fm port reset notify msg len is wrong, len=%#x\n", + p_len); + status = UB_MSG_RSP_CMD_LEN_ERR; + goto rsp; + } + + if (ub_port_reset_check(ubc->uent, pld->port_index)) { + status = UB_MSG_RSP_EXEC_ENOEXEC; + goto rsp; + } + + port = ubc->uent->ports + pld->port_index; + if (port->shareable && port->domain_boundary) { + if (pld->type == RESET_PREPARE) + ub_notify_share_port(port, RESET_PREPARE); + else if (pld->type == RESET_DONE) + ub_notify_share_port(port, RESET_DONE); + } + +rsp: + local = ub_rsp_msg_init(header, status, 0); + message_info_init(&info, local ? ubc->uent : NULL, header, NULL, size); + ret = message_response(ubc->mdev, &info, header->msgetah.code); + if (ret) + dev_err(&ubc->dev, + "send ub fm port reset notify error, ret=%d\n", ret); +} + +static rx_msg_handler_t pool_rx_msg_handler[UB_SUB_MSG_CODE_NUM] = { + ub_entity_reg_msg_handler, + ub_entity_rls_msg_handler, + ub_pool_bi_handler, + ub_pool_bi_handler, + ub_cfg_cpl_notify_handler, + ub_port_reset_notify_handler, +}; + +void ub_pool_rx_msg_handler(struct ub_bus_controller *ubc, void *pkt, u16 len) +{ + struct msg_pkt_header *header = (struct msg_pkt_header *)pkt; + u8 sub_msg_code = header->msgetah.sub_msg_code; + rx_msg_handler_t handler; + + handler = pool_rx_msg_handler[sub_msg_code]; + if (handler) + handler(ubc, pkt, len); + else + dev_err(&ubc->dev, "pool sub msg code not support, code=%#x\n", + sub_msg_code); +} diff --git a/drivers/ub/ubus/pool.h b/drivers/ub/ubus/pool.h new file mode 100644 index 0000000000000000000000000000000000000000..6d9e68c982f9a488bfafeba1ddfc0432db42dc51 --- /dev/null +++ b/drivers/ub/ubus/pool.h @@ -0,0 +1,74 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#ifndef __POOL_H__ +#define __POOL_H__ + +#include "msg.h" + +struct entity_base_info { + /* DW0 */ + u32 entity_idx : 16; + u32 upi : 15; + u32 rsvd0 : 1; + /* DW1~DW4 */ + u32 eid[4]; + /* DW5~DW8 */ + u32 guid[UB_GUID_DW_NUM]; + /* DW9 */ + u32 cna : 24; + u32 rsvd2 : 8; + /* DW10~DW13 */ + u32 ueid[4]; +}; + +struct entity_rs_info { + u32 ss; + u32 sa_l; + u32 sa_h; +}; + +struct pool_fad { + struct entity_base_info base; + struct entity_rs_info ers[MAX_UB_RES_NUM]; + + bool attach; + bool ers_valid; + struct ub_entity *uent; + struct list_head node; + struct ub_bus_controller *ubc; +}; + +struct entity_reg_msg_pld { + /* DW0~DW13 */ + struct entity_base_info base; + /* DW14~DW22 */ + struct entity_rs_info ers[MAX_UB_RES_NUM]; +}; +#define ENTITY_BASE_PLD_SIZE 56 +#define ENTITY_RS_PLD_SIZE 36 +#define ENTITY_REG_PLD_SIZE (ENTITY_BASE_PLD_SIZE + ENTITY_RS_PLD_SIZE) + +struct entity_rls_msg_pld { + /* DW0~DW3 */ + u32 eid[4]; + /* DW4 */ + u32 reason : 8; + u32 rsvd1 : 24; +}; +#define ENTITY_RLS_PLD_SIZE 20 + +struct port_reset_notify_pld { + u32 type : 1; + u32 rsvd0 : 15; + u32 port_index : 16; +}; +#define PORT_RESET_NOTIFY_PLD_SIZE 4 + +struct ub_entity *ub_get_fad_ent_by_eid(unsigned int eid); +bool ub_rsp_msg_init(struct msg_pkt_header *header, u8 status, u32 plen); +void ub_pool_rx_msg_handler(struct ub_bus_controller *ubc, void *pkt, u16 len); + +#endif /* __POOL_H__ */ diff --git a/drivers/ub/ubus/port.c b/drivers/ub/ubus/port.c index 74158f9b0d5520a0546a18304768febf32d656a5..36950c24e3443ee5c67b305c065eee582ec5932b 100644 --- a/drivers/ub/ubus/port.c +++ b/drivers/ub/ubus/port.c @@ -9,6 +9,7 @@ #include "ubus.h" #include "reset.h" +#include "link.h" #include "port.h" struct ub_port_attribute { @@ -536,6 +537,7 @@ static void ub_port_init(struct ub_entity *uent, struct ub_port *port) bitmap_zero(port->cna_maps, UB_MAX_CNA_NUM); bitmap_zero(port->cap_map, UB_PORT_CAP_NUM); kobject_init(&port->kobj, &ub_port_ktype); + INIT_WORK(&port->link_work, ub_link_change_handler); } int ub_ports_setup(struct ub_entity *uent) @@ -704,3 +706,16 @@ void ub_notify_share_port(struct ub_port *port, } up_read(&ub_share_port_notify_list_rwsem); } + +bool ub_port_check_link_up(struct ub_port *port) +{ + u8 val; + + if (!port) + return false; + + if (ub_port_read_byte(port, UB_PORT_PHYSICAL_PORT_LINK_STATUS, &val)) + return false; + + return !!(val & UB_PORT_LINK_STATE); +} diff --git a/drivers/ub/ubus/port.h b/drivers/ub/ubus/port.h index 3135d25ebdf435867fc2f0c2d61c471824f6ab39..0350908c531abb606f93cbfc11250f7c45f198f6 100644 --- a/drivers/ub/ubus/port.h +++ b/drivers/ub/ubus/port.h @@ -27,5 +27,6 @@ void ub_notify_share_port(struct ub_port *port, enum ub_share_port_notify_type type); int ub_port_write_dword(struct ub_port *port, u32 pos, u32 val); +bool ub_port_check_link_up(struct ub_port *port); #endif /* __PORT_H__ */ diff --git a/drivers/ub/ubus/resource.c b/drivers/ub/ubus/resource.c index ebb1a529af7727f3743e27bc951c4412448f0f75..b57117e02415c4a178f79b07baebdd0be7957a4f 100644 --- a/drivers/ub/ubus/resource.c +++ b/drivers/ub/ubus/resource.c @@ -11,6 +11,8 @@ #include "ubus.h" #include "msg.h" +#include "decoder.h" +#include "omm.h" #include "resource.h" struct query_token_msg_pld_req { @@ -305,6 +307,98 @@ void ub_entity_free_mmio_idx(struct ub_entity *dev, int idx) dev->zone[idx].sa_used = 0; } +static void fill_decoder_map_info(struct ub_entity *uent, int idx, + struct decoder_map_info *info) +{ + info->pa = uent->zone[idx].res.start; + info->uba = uent->zone[idx].region.start; + info->size = uent->zone[idx].region.size; + info->eid_low = uent->eid; + info->eid_high = 0; + info->tpg_num = is_primary(uent) ? uent->cna : uent->pue->cna; + info->order_id = 0; + info->order_type = 0; + info->token_id = uent->token_id; + info->token_value = uent->token_value; + info->upi = uent->bi ? uent->bi->info.upi : 0; + info->src_eid = uent->bi ? uent->bi->info.eid : 0; +} + +static int ub_entity_decoder_map_mmio_idx(struct ub_entity *uent, int idx) +{ + struct decoder_map_info info = {}; + struct ub_decoder *decoder; + int ret; + + fill_decoder_map_info(uent, idx, &info); + decoder = is_primary(uent) ? uent->ubc->decoder : + uent->pue->ubc->decoder; + + ret = ub_decoder_map(decoder, &info); + if (ret) + ub_err(uent, "resource%d decoder map failed.\n", idx); + + info.token_value = 0; + return ret; +} + +static void ub_entity_decoder_unmap_mmio_idx(struct ub_entity *uent, int idx) +{ + struct ub_decoder *decoder; + + if (!uent->zone[idx].sa_used) + return; + + decoder = is_primary(uent) ? uent->ubc->decoder : + uent->pue->ubc->decoder; + if (ub_decoder_unmap(decoder, uent->zone[idx].res.start, + uent->zone[idx].region.size)) + ub_warn(uent, "resource%d decoder unmap failed.\n", idx); +} + +void ub_entity_decoder_unmap_mmio(struct ub_entity *dev) +{ + int i; + + for (i = 0; i < MAX_UB_RES_NUM; i++) + if (dev->zone[i].sa_used && dev->zone[i].init_succ && + dev->zone[i].decoder_mapped) { + ub_entity_decoder_unmap_mmio_idx(dev, i); + dev->zone[i].decoder_mapped = 0; + } +} + +void ub_entity_decoder_map_mmio(struct ub_entity *dev) +{ + int i, ret; + + if (is_ibus_controller(dev)) + return; + + for (i = 0; i < MAX_UB_RES_NUM; i++) { + if (!dev->zone[i].sa_used || !dev->zone[i].init_succ) + continue; + if (dev->zone[i].decoder_mapped) { + ub_err(dev, "mmio idx[%d] has been mapped\n", i); + continue; + } + ret = ub_entity_decoder_map_mmio_idx(dev, i); + if (ret) { + ub_err(dev, "failed to establish ommu map, mmio_idx=%d, size=%#lx, error_code=%d\n", + i, dev->zone[i].region.size, ret); + goto fail; + } + dev->zone[i].decoder_mapped = 1; + } + + return; +fail: + for (i -= 1; i >= 0; i--) { + ub_entity_decoder_unmap_mmio_idx(dev, i); + dev->zone[i].decoder_mapped = 0; + } +} + static int ub_query_token_rsp_handle(struct ub_entity *uent, struct query_token_msg_pkt *pkt) { diff --git a/drivers/ub/ubus/resource.h b/drivers/ub/ubus/resource.h index 90da3c402dc03c13f1d11df7ba4bf45a8325d898..da032502abe83841c48302db5d7bf25fcabc9097 100644 --- a/drivers/ub/ubus/resource.h +++ b/drivers/ub/ubus/resource.h @@ -12,6 +12,8 @@ int ub_entity_setup_mmio(struct ub_entity *dev); void ub_entity_unset_mmio(struct ub_entity *dev); int ub_mmap_resource_range(struct ub_entity *uent, unsigned long idx, struct vm_area_struct *vma, int write_combine); +void ub_entity_decoder_map_mmio(struct ub_entity *dev); +void ub_entity_decoder_unmap_mmio(struct ub_entity *uent); int ub_insert_resource(struct ub_entity *dev, int idx); void ub_entity_free_mmio_idx(struct ub_entity *dev, int idx); diff --git a/drivers/ub/ubus/route.c b/drivers/ub/ubus/route.c index f4be537021d5bb9f7f328b26012761aed0970748..73e0df5436dc7c14da6322e71c69c77e514a86e8 100644 --- a/drivers/ub/ubus/route.c +++ b/drivers/ub/ubus/route.c @@ -5,12 +5,18 @@ #define pr_fmt(fmt) "ubus route: " fmt +#include + #include "ubus.h" +#include "msg.h" +#include "enum.h" #include "port.h" +#include "ubus_driver.h" #define UB_ROUTE_TABLE_ENTRY_START (UB_ROUTE_TABLE_SLICE_START + (0x10 << 2)) #define EBW(port_nums) ((((port_nums) - 1) >> 5) + 1) /* Entry Bit Width */ #define UB_ROUTE_TABLE_ENTRY_BITS SZ_128 +#define UB_ROUTE_KFIFO_DEPTH SZ_16 /* node to update routing table */ struct cna_node { @@ -48,6 +54,12 @@ static void ub_cna_node_release(struct kref *kref) kfree(cna); } +static struct cna_node *ub_cna_node_get(struct cna_node *cna) +{ + kref_get(&cna->ref); + return cna; +} + static void ub_cna_node_put(struct cna_node *cna) { kref_put(&cna->ref, ub_cna_node_release); @@ -83,6 +95,23 @@ static int ub_add_cna_node(u32 cna, short distance, struct list_head *cna_list) return 0; } +static struct cna_node *ub_find_cna_node(u32 cna, struct list_head *cna_list) +{ + struct cna_node *cna_node; + + list_for_each_entry(cna_node, cna_list, node) { + if (cna_node->cna < cna) + continue; + + if (cna_node->cna > cna) + break; + + return ub_cna_node_get(cna_node); + } + + return NULL; +} + static void ub_clear_cna_list(struct list_head *cna_list) { struct cna_node *cna, *tmp; @@ -93,6 +122,27 @@ static void ub_clear_cna_list(struct list_head *cna_list) } } +/* the cna_list had better be empty since it'll be clear when err comes */ +static int ub_entity_copy_cna_list(struct ub_entity *uent, struct list_head *cna_list, + bool update_only) +{ + struct cna_node *cna, *new_cna; + + list_for_each_entry(cna, &uent->cna_list, node) { + if (update_only && !cna->need_update) + continue; + + new_cna = ub_create_cna_node(cna->cna, cna->distance); + if (!new_cna) { + ub_clear_cna_list(cna_list); + return -ENOMEM; + } + list_add_tail(&new_cna->node, cna_list); + } + + return 0; +} + void ub_route_clear(struct ub_entity *uent) { struct ub_port *port; @@ -118,6 +168,332 @@ int ub_route_add_entry(struct ub_port *port, u32 cna, short distance) return ub_add_cna_node(cna, distance, &port->uent->cna_list); } +static void ub_route_del_entry(struct ub_port *port, u32 cna) +{ + struct cna_node *node; + + clear_bit(cna, port->cna_maps); + node = ub_find_cna_node(cna, &port->uent->cna_list); + if (!node) { + ub_err(port->uent, "can not find cna %#x from uent cna list\n", + cna); + return; + } + + node->need_update = true; + node->count--; + ub_cna_node_put(node); +} + +static bool ub_route_del_entries(struct ub_port *port, + struct list_head *cna_list) +{ + struct ub_entity *uent = port->uent; + bool route_updated = false; + struct cna_node *cna; + + list_for_each_entry(cna, cna_list, node) { + if (!test_bit(cna->cna, port->cna_maps)) + continue; + + ub_route_del_entry(port, cna->cna); + route_updated = true; + } + + if (route_updated) + ub_entity_assign_priv_flag(uent, UB_ENTITY_ROUTE_UPDATED, true); + + return route_updated; +} + +static bool ub_entity_has_cna(u32 cna, struct ub_entity *uent) +{ + struct ub_port *port; + + if (uent->cna == cna) + return true; + + for_each_uent_port(port, uent) + if (port->cna == cna) + return true; + + return false; +} + +/* try update routing table from cna_list, return true if updated */ +static bool ub_route_mod_entries(struct ub_port *port, + struct list_head *cna_list, short off) +{ + struct ub_entity *uent = port->uent; + struct cna_node *cna, *uent_cna; + bool route_updated = false; + short new_distance; + int ret; + + list_for_each_entry(cna, cna_list, node) { + if (test_bit(cna->cna, port->cna_maps)) + continue; + + if (ub_entity_has_cna(cna->cna, uent)) + continue; + + new_distance = cna->distance + off; + + uent_cna = ub_find_cna_node(cna->cna, &uent->cna_list); + if (!uent_cna) { + ret = ub_route_add_entry(port, cna->cna, new_distance); + if (!ret) + goto set_flag; + + ub_err(uent, "add route entry for %#x failed with %d\n", + cna->cna, ret); + return false; + } + + if (uent_cna->distance < new_distance) { + ub_cna_node_put(uent_cna); + continue; + } + + /* in bfs we will never encounter a shorter path */ + set_bit(cna->cna, port->cna_maps); + uent_cna->need_update = true; + uent_cna->count++; + ub_cna_node_put(uent_cna); +set_flag: + route_updated = true; + } + + if (route_updated) + ub_entity_assign_priv_flag(uent, UB_ENTITY_ROUTE_UPDATED, true); + + return route_updated; +} + +bool ub_entity_support_forward(struct ub_entity *uent) +{ + if (!uent) + return false; + + if (is_ibus_controller(uent)) + return true; + + if (is_switch(uent)) + return true; + + return false; +} + +static bool ub_check_path(struct ub_port *from, struct ub_port *to) +{ + if (to == from) + return false; + + if (!to->r_uent) + return false; + + if (to->r_uent->port_nums <= 1) + return false; + + return true; +} + +/** + * consider a given topo like + * +-------------+ +---------+ + * | controller0 |p0:---:p1| device0 | + * +-------------+ +---------+ + * when ub_route_mod_neighbor is called for p0, p1, device0's routing table + * will be updated with p0'cna and controller0's cna. If controller0 supports + * forward, device0 will also know all route in controller0's routing table + */ +int ub_route_mod_neighbor(struct ub_port *port, struct ub_port *r_port) +{ + struct list_head cna_list; + int ret; + + if (!port || !r_port) + return -EINVAL; + + INIT_LIST_HEAD(&cna_list); + + /* if uent can not forward, only need to update uent & port's cna */ + if (ub_entity_support_forward(port->uent)) { + ret = ub_entity_copy_cna_list(port->uent, &cna_list, false); + if (ret) + return ret; + } + + ret = ub_add_cna_node(port->uent->cna, 0, &cna_list); + if (ret) + goto clear_cna; + + ret = ub_add_cna_node(port->cna, 0, &cna_list); + if (ret) + goto clear_cna; + + ub_route_mod_entries(r_port, &cna_list, 1); + +clear_cna: + ub_clear_cna_list(&cna_list); + return ret; +} + +/** + * consider a given topo like + * +-------------+ +---------+ + * | controller0 |p0:---:p1| device0 | + * +-------------+ +---------+ + * when device0 is being removed, ub_route_clear_port is called for p0 + * 1. all route that pass though this p0 will be del from controller0's cna_list + * 2. p0's cna_maps will be clear + */ +void ub_route_clear_port(struct ub_port *port) +{ + struct list_head *cna_list; + struct cna_node *cna_node; + bool route_update = false; + struct ub_entity *uent; + u32 cna; /* use u32 to avoid wraparound */ + + if (!port) + return; + + uent = port->uent; + cna_list = &uent->cna_list; + cna_node = list_first_entry(cna_list, struct cna_node, node); + cna = find_next_bit(port->cna_maps, UB_MAX_CNA_NUM, 0); + + while (cna < UB_MAX_CNA_NUM) { + if (list_entry_is_head(cna_node, cna_list, node)) + goto clear_bit; + + while (cna_node->cna < cna) { + cna_node = list_next_entry(cna_node, node); + if (list_entry_is_head(cna_node, cna_list, node)) { + ub_warn(uent, "can't find cna %u in list\n", cna); + goto clear_bit; + } + } + + if (cna_node->cna == cna) { + route_update = true; + cna_node->count--; + cna_node->need_update = true; + } +clear_bit: + clear_bit(cna, port->cna_maps); + cna = find_next_bit(port->cna_maps, UB_MAX_CNA_NUM, cna + 1); + } + + if (route_update) + ub_entity_assign_priv_flag(uent, UB_ENTITY_ROUTE_UPDATED, true); +} + +struct bfs_node { + struct ub_port *from; + short off; +}; + +/** + * consider a given topo like + * +-------------+ +---------+ +--------+ + * | controller0 |p0:---:p0| switch0 |p1:---slot0---:p0| device0| + * +-------------+ +---------+ +--------+ + * after device0 is plugged in, switch0 updates route with p0 and device0's cna, + * ub_route_mod_bfs tries to use the updated route in switch0's routing + * table to update controller0 through p0. If success, then pass through + * controller0's other ports to update neighbor + */ +void ub_route_mod_bfs(struct ub_entity *uent) +{ + DECLARE_KFIFO(kfifo, struct bfs_node, UB_ROUTE_KFIFO_DEPTH); + struct bfs_node curr, next; + struct list_head cna_list; + struct ub_port *from, *to; + + if (!uent) + return; + + INIT_KFIFO(kfifo); + INIT_LIST_HEAD(&cna_list); + + if (ub_entity_copy_cna_list(uent, &cna_list, true)) + goto clear_cna; + + for_each_uent_port(to, uent) { + if (to->r_uent && to->r_uent->port_nums > 1) { + next.from = to->r_uent->ports + to->r_index; + next.off = 1; + kfifo_put(&kfifo, next); + } + } + + while (kfifo_get(&kfifo, &curr)) { + from = curr.from; + if (!ub_route_mod_entries(from, &cna_list, curr.off)) + continue; + + if (!ub_entity_support_forward(from->uent)) + continue; + + for_each_uent_port(to, from->uent) { + if (!ub_check_path(from, to)) + continue; + + next.from = to->r_uent->ports + to->r_index; + next.off = curr.off + 1; + if (!kfifo_put(&kfifo, next)) + ub_err(next.from->uent, + "%s kfifo put failed!\n", __func__); + } + } + +clear_cna: + ub_clear_cna_list(&cna_list); +} + +/* same as ub_route_mod_bfs, but used to del route instead of update route */ +void ub_route_del_bfs(struct ub_entity *uent) +{ + DECLARE_KFIFO(kfifo, struct ub_port *, UB_ROUTE_KFIFO_DEPTH); + struct ub_port *from, *to; + struct list_head cna_list; + + if (!uent) + return; + + INIT_KFIFO(kfifo); + INIT_LIST_HEAD(&cna_list); + + if (ub_entity_copy_cna_list(uent, &cna_list, true)) + goto clear_cna; + + for_each_uent_port(to, uent) + if (to->r_uent && to->r_uent->port_nums > 1) + kfifo_put(&kfifo, to->r_uent->ports + to->r_index); + + while (kfifo_get(&kfifo, &from)) { + if (!ub_route_del_entries(from, &cna_list)) + continue; + + if (!ub_entity_support_forward(from->uent)) + continue; + + for_each_uent_port(to, from->uent) { + if (!ub_check_path(from, to)) + continue; + + if (!kfifo_put(&kfifo, to->r_uent->ports + to->r_index)) + ub_err(to->r_uent, + "%s kfifo put failed!\n", __func__); + } + } + +clear_cna: + ub_clear_cna_list(&cna_list); +} + static void ub_set_route_table_entry(struct ub_entity *uent, u32 dst_cna, u32 *route_table_entry) { @@ -175,3 +551,66 @@ void ub_route_sync_dev(struct ub_entity *uent) ub_entity_assign_priv_flag(uent, UB_ENTITY_ROUTE_UPDATED, false); } + +/** + * this function sync all updated routing table entry to config space + * for devices that aren't in ubc dev list, this function will not update them + */ +void ub_route_sync_all(void) +{ + struct ub_bus_controller *ubc; + struct ub_entity *uent; + + down_read(&ub_bus_sem); + list_for_each_entry(ubc, &ubc_list, node) + list_for_each_entry(uent, &ubc->devs, node) + ub_route_sync_dev(uent); + up_read(&ub_bus_sem); +} + +int ub_route_entities(struct list_head *dev_list) +{ + if (!dev_list) + return -EINVAL; + + return ub_enum_bfs_route_cal(dev_list); +} + +void ub_route_table_clear_for_port(struct ub_port *port, struct ub_port *r_port) +{ + ub_route_clear_port(r_port); + + ub_route_clear_port(port); + + if (ub_entity_support_forward(r_port->uent)) + ub_route_del_bfs(r_port->uent); + + if (ub_entity_support_forward(port->uent)) + ub_route_del_bfs(port->uent); + + ub_route_sync_all(); +} + +int ub_route_table_set_for_port(struct ub_port *port, + struct ub_port *r_port) +{ + int ret; + + ret = ub_route_mod_neighbor(port, r_port); + if (ret) + return ret; + + ret = ub_route_mod_neighbor(r_port, port); + if (ret) + return ret; + + if (ub_entity_support_forward(port->uent)) + ub_route_mod_bfs(port->uent); + + if (ub_entity_support_forward(r_port->uent)) + ub_route_mod_bfs(r_port->uent); + + ub_route_sync_all(); + + return 0; +} diff --git a/drivers/ub/ubus/route.h b/drivers/ub/ubus/route.h index 04bd052729a0a27936428acd3681ccced264ce47..d7d96d86d4de3b8741c3ccc3dd67f8a181218029 100644 --- a/drivers/ub/ubus/route.h +++ b/drivers/ub/ubus/route.h @@ -10,6 +10,16 @@ struct ub_entity; struct ub_port; void ub_route_clear(struct ub_entity *uent); int ub_route_add_entry(struct ub_port *port, u32 cna, short distance); +bool ub_entity_support_forward(struct ub_entity *uent); +int ub_route_mod_neighbor(struct ub_port *port, struct ub_port *r_port); +void ub_route_clear_port(struct ub_port *port); +void ub_route_mod_bfs(struct ub_entity *uent); +void ub_route_del_bfs(struct ub_entity *uent); void ub_route_sync_dev(struct ub_entity *uent); +void ub_route_sync_all(void); +int ub_route_entities(struct list_head *dev_list); +void ub_route_table_clear_for_port(struct ub_port *port, + struct ub_port *r_port); +int ub_route_table_set_for_port(struct ub_port *port, struct ub_port *r_port); #endif /* __ROUTE_H__ */ diff --git a/drivers/ub/ubus/services.h b/drivers/ub/ubus/services.h new file mode 100644 index 0000000000000000000000000000000000000000..71fe6aa5735795ee5c53f632899463dbda1d2960 --- /dev/null +++ b/drivers/ub/ubus/services.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ +#ifndef __SERVICES_H__ +#define __SERVICES_H__ + +int ub_services_init(void); +void ub_services_exit(void); + +#endif /* __SERVICES_H__ */ diff --git a/drivers/ub/ubus/services/gucd.c b/drivers/ub/ubus/services/gucd.c new file mode 100644 index 0000000000000000000000000000000000000000..35a0cf35e20a4488219c8e0b5fe436f4c5695b9c --- /dev/null +++ b/drivers/ub/ubus/services/gucd.c @@ -0,0 +1,220 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#define pr_fmt(fmt) "ubus gucd: " fmt + +#include "../ubus.h" +#include "../decoder.h" +#include "../ubus_driver.h" +#include "service.h" + +static const struct ub_device_id component_device_ids[] = { + { UB_ENTITY_CLASS(UB_CLASS_BUS_CONTROLLER, (u16)~0)}, + { UB_ENTITY_CLASS(UB_CLASS_SWITCH_UB, (u16)~0)}, + {} +}; +MODULE_DEVICE_TABLE(ub, component_device_ids); + +static int get_component_service_capability(struct ub_entity *uent) +{ + int services = 0; + u32 val; + int ret; + + ret = ub_cfg_read_dword(uent, UB_CFG0_CAP_BITMAP, &val); + if (ret) + return services; + + if (val & (1 << UB_SHP_CAP)) + services |= UB_SERVICE_HP; + + return services; +} + +static void release_service_device(struct device *dev) +{ + kfree(to_ub_service_device(dev)); +} + +static int ub_component_service_init(struct ub_entity *uent, int service) +{ + struct ub_service_device *sdev; + struct device *dev; + int ret; + + sdev = kzalloc(sizeof(*sdev), GFP_KERNEL); + if (!sdev) + return -ENOMEM; + + sdev->uent = uent; + sdev->service = service; + + dev = &sdev->device; + dev->bus = &ub_service_bus_type; + dev->release = release_service_device; + dev_set_name(dev, "%s:service%03x", ub_name(uent), service); + dev->parent = &uent->dev; + + ret = device_register(dev); + if (ret) + put_device(dev); + + return ret; +} + +static int ub_component_service_register(struct ub_entity *uent) +{ + int nr_service = 0; + int capabilities; + int i; + + /* Get and check component services */ + capabilities = get_component_service_capability(uent); + if (!capabilities) + return 0; + + for (i = 0; i < UB_MAXSERIVCES; i++) { + int service = 1 << i; + + if (!(capabilities & service)) + continue; + if (!ub_component_service_init(uent, service)) + nr_service++; + } + + if (!nr_service) + return -ENODEV; + + return 0; +} + +static void ub_enable_err_msq_ctrl(struct ub_entity *uent) +{ + int ret; + + ret = ub_cfg_write_dword(uent, EMQ_CAP_START + UB_CAP_ERR_MSG_QUE_CTL, + UB_CAP_INTERRUPT_GEN_ENA); + if (ret) + ub_err(uent, "enable error msq controller failed\n"); +} + +static void ub_disable_err_msq_ctrl(struct ub_entity *uent) +{ + int ret; + + ret = ub_cfg_write_dword(uent, EMQ_CAP_START + UB_CAP_ERR_MSG_QUE_CTL, + 0x0); + if (ret) + ub_err(uent, "disable error msq controller failed\n"); +} + +static void ub_setup_bus_controller(struct ub_entity *uent) +{ + u32 vec_num_max; + int usi_count; + + if (ub_cc_supported(uent)) + ub_cc_enable(uent); + + ub_set_user_info(uent); + ub_enable_err_msq_ctrl(uent); + vec_num_max = ub_int_type1_vec_count(uent); + usi_count = ub_alloc_irq_vectors(uent, vec_num_max, vec_num_max); + if (usi_count < 0) { + ub_err(uent, "alloc irq for ub bus controller failed, usi_count=%d\n", + usi_count); + return; + } + + if ((u32)usi_count < vec_num_max) + ub_err(uent, "alloc irq vectors failed, usi count=%d, vec_num_max=%u\n", + usi_count, vec_num_max); + else + ub_init_decoder_usi(uent); +} + +static void ub_unset_bus_controller(struct ub_entity *uent) +{ + ub_uninit_decoder_usi(uent); + ub_disable_intr(uent); + ub_disable_err_msq_ctrl(uent); + ub_unset_user_info(uent); + + if (ub_cc_supported(uent)) + ub_cc_disable(uent); +} + +static ub_ers_result_t ub_gucd_err_detected(struct ub_entity *uent, ub_channel_state_t state) +{ + if (state == ub_channel_io_normal) + return UB_ERS_RESULT_NEED_RESET; + + if (state == ub_channel_io_frozen) + return UB_ERS_RESULT_NEED_RESET; + + return UB_ERS_RESULT_CAN_RECOVER; +} + +static const struct ub_error_handlers gucd_err_handler = { + .ub_error_detected = ub_gucd_err_detected, +}; + +static int ub_component_probe(struct ub_entity *uent, + const struct ub_device_id *id) +{ + if (is_ibus_controller(uent)) + ub_setup_bus_controller(uent); + + return ub_component_service_register(uent); +} + +static int remove_iter(struct device *dev, void *data) +{ + if (dev->bus == &ub_service_bus_type) + device_unregister(dev); + + return 0; +} + +static void ub_component_remove(struct ub_entity *uent) +{ + device_for_each_child(&uent->dev, NULL, remove_iter); + if (is_ibus_controller(uent)) + ub_unset_bus_controller(uent); +} + +static struct ub_driver ub_component_device_driver = { + .name = "ub_generic_component", + .id_table = component_device_ids, + .probe = ub_component_probe, + .remove = ub_component_remove, + .err_handler = &gucd_err_handler, + .driver_managed_dma = true, +}; + +static void ub_init_services(void) +{ + ub_ras_init(); + ubhp_service_init(); +} + +static void ub_uninit_services(void) +{ + ubhp_service_uninit(); + ub_ras_uninit(); +} + +int ub_services_init(void) +{ + ub_init_services(); + + return ub_register_driver(&ub_component_device_driver); +} + +void ub_services_exit(void) +{ + ub_unregister_driver(&ub_component_device_driver); + ub_uninit_services(); +} diff --git a/drivers/ub/ubus/services/hotplug/hotplug.h b/drivers/ub/ubus/services/hotplug/hotplug.h new file mode 100644 index 0000000000000000000000000000000000000000..93c8e3c798b9917636b5a3ef5b552920c2ff2898 --- /dev/null +++ b/drivers/ub/ubus/services/hotplug/hotplug.h @@ -0,0 +1,125 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#ifndef __HOTPLUG_H__ +#define __HOTPLUG_H__ + +/** + * struct ub_slot - UB hotplug slot + * @uent: pointer to the ub dev who has this slot + * @r_uent: pointer to the ub dev who is plugged in this slot + * @ports: port array slice of uent's port array + * @kobj: kobject of slot to provide sysfs + * @node: node of slot in uent slot_list; + * + * @slot_id: id of this slot + * @port_start: start port_idx of ports belong to this slot + * @port_num: number of ports belong to this slot + * + * @button_work: work to queue for button pressed event + * @present_work: work to queue for card present event + * @power_work: work to queue for power state update + * @state: current state machine position + * @state_lock: mutex lock for slot state + */ +struct ub_slot { + /* slot info */ + struct ub_entity *uent; + struct ub_entity *r_uent; + struct ub_port *ports; + struct kobject kobj; + struct list_head node; + + /* cap info */ + u8 slot_id; + u16 port_start; + u16 port_num; + u32 slot_cap; + + /* hotplug info */ + struct work_struct button_work; + struct delayed_work present_work; + struct delayed_work power_work; + u8 state; + struct mutex state_lock; +}; + +#define for_each_slot_port(p, s) \ + for ((p) = (s)->ports; ((p) - (s)->ports) < (s)->port_num; (p)++) + +#define BUTTON(slot) ((slot)->slot_cap & UB_SLOT_PPS) +#define WORK_LED(slot) ((slot)->slot_cap & UB_SLOT_WLPS) +#define PWR_LED(slot) ((slot)->slot_cap & UB_SLOT_PLPS) +#define PRESENT(slot) ((slot)->slot_cap & UB_SLOT_PDSS) + +struct ubhp_msg_payload { + u16 slot_id; + u16 rsv0; + u32 rsv1; +}; + +#define UB_HP_MSG_SIZE 40 + +enum power_state { + POWER_OFF, + POWER_ON +}; + +enum indicator_state { + INDICATOR_OFF, /* set indicator off */ + INDICATOR_ON, /* set indicator on */ + INDICATOR_BLINKING, /* set indicator blinking */ + INDICATOR_NOOP /* left indicator unchanged */ +}; + +enum hotplug_event { + HPE_BUTTON_PRESSED = 2, + HPE_PRESENCE_DETECT, + HPE_OTHER +}; + +/** + * a slot must be get before any of its work is queued into workqueue + * and must be put after the work is end + * so that when a slot is released, its works are idle and don't need to cancel + */ +static inline struct ub_slot *ubhp_get_slot(struct ub_slot *slot) +{ + if (slot) + kobject_get(&slot->kobj); + + return slot; +} + +static inline void ubhp_put_slot(struct ub_slot *slot) +{ + if (slot) + kobject_put(&slot->kobj); +} + +/* ctrl */ +int ub_slot_read_dword(struct ub_slot *slot, u32 pos, u32 *val); +void ubhp_set_indicators(struct ub_slot *slot, u8 power, u8 work); +void ubhp_set_slot_power(struct ub_slot *slot, enum power_state power); +bool ubhp_confirm_event(struct ub_slot *slot, enum hotplug_event event); +bool ubhp_wait_linkup(struct ub_slot *slot); +bool ubhp_card_present(struct ub_slot *slot); +void ubhp_start_slots(struct ub_entity *uent); +void ubhp_stop_slots(struct ub_entity *uent); + +/* msg */ +void ubhp_event_handler(struct ub_bus_controller *ubc, void *pkt, u16 len); + +/* core */ +void ubhp_handle_power(struct ub_slot *slot, bool power_on); + +/* route */ +int ubhp_update_route_link_up(struct ub_slot *slot); +void ubhp_update_route_link_down(struct ub_slot *slot); +void ubhp_mark_detached_entities(struct ub_entity *root, struct list_head *dev_list); +void ubhp_stop_entities(struct list_head *dev_list); +void ubhp_remove_entities(struct list_head *dev_list); + +#endif /* __HOTPLUG_H__ */ diff --git a/drivers/ub/ubus/services/hotplug/hotplug_core.c b/drivers/ub/ubus/services/hotplug/hotplug_core.c new file mode 100644 index 0000000000000000000000000000000000000000..157e7b1d4396753e689f277201973483c3b62cb0 --- /dev/null +++ b/drivers/ub/ubus/services/hotplug/hotplug_core.c @@ -0,0 +1,580 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#include "../../ubus.h" +#include "../../msg.h" +#include "../../enum.h" +#include "../../route.h" +#include "../../port.h" +#include "../service.h" +#include "hotplug.h" + +#define to_ub_slot(s) container_of(s, struct ub_slot, kobj) + +static void ubhp_destory_slot(struct ub_slot *slot) +{ + mutex_destroy(&slot->state_lock); + kfree(slot); +} + +static void ubhp_slot_release(struct kobject *kobj) +{ + struct ub_slot *slot = to_ub_slot(kobj); + + ubhp_destory_slot(slot); +} + +enum slot_state { + SLOT_ON, /* slot on, device running */ + SLOT_POWERON, /* from slot off to slot on */ + SLOT_POWEROFF, /* from slot on to slot off */ + SLOT_OFF, /* slot off, device not present */ +}; + +struct ub_slot_attribute { + struct attribute attr; + ssize_t (*show)(struct ub_slot *slot, char *buf); + ssize_t (*store)(struct ub_slot *slot, const char *buf, size_t count); +}; + +#define to_ub_slot_attr(s) container_of(s, struct ub_slot_attribute, attr) + +static void ubhp_enable_slot(struct ub_slot *slot) +{ + bool queued = false; + + ubhp_get_slot(slot); + mutex_lock(&slot->state_lock); + + if (slot->state == SLOT_OFF) + queued = queue_work(get_rx_msg_wq(UB_MSG_CODE_LINK), + &slot->button_work); + else + ub_info(slot->uent, "ignore slot poweron\n"); + + mutex_unlock(&slot->state_lock); + if (!queued) + ubhp_put_slot(slot); +} + +static void ubhp_disable_slot(struct ub_slot *slot) +{ + bool queued = false; + + ubhp_get_slot(slot); + mutex_lock(&slot->state_lock); + + if (slot->state == SLOT_ON) + queued = queue_work(get_rx_msg_wq(UB_MSG_CODE_LINK), + &slot->button_work); + else + ub_info(slot->uent, "ignore slot poweroff\n"); + + mutex_unlock(&slot->state_lock); + if (!queued) + ubhp_put_slot(slot); +} + +static ssize_t power_show(struct ub_slot *slot, char *buf) +{ + ssize_t ret; + + mutex_lock(&slot->state_lock); + switch (slot->state) { + case SLOT_ON: + ret = sysfs_emit(buf, "slot is %s\n", "on"); + break; + case SLOT_POWERON: + ret = sysfs_emit(buf, "slot is %s\n", "poweron"); + break; + case SLOT_POWEROFF: + ret = sysfs_emit(buf, "slot is %s\n", "poweroff"); + break; + case SLOT_OFF: + ret = sysfs_emit(buf, "slot is %s\n", "off"); + break; + default: + ret = sysfs_emit(buf, "unknown state %u\n", slot->state); + break; + } + mutex_unlock(&slot->state_lock); + + return ret; +} + +static ssize_t power_store(struct ub_slot *slot, const char *buf, size_t count) +{ + unsigned long power; + int ret; + + ret = kstrtoul(buf, 0, &power); + if (ret) { + ub_err(slot->uent, "Invalid val for power\n"); + return ret; + } + + switch (power) { + case 0: + ubhp_disable_slot(slot); + break; + case 1: + ubhp_enable_slot(slot); + break; + default: + ub_err(slot->uent, "Invalid val %lu for power\n", power); + return -EINVAL; + } + + return count; +} +static struct ub_slot_attribute ub_slot_attr_power = __ATTR_RW(power); + +static struct attribute *ub_slot_default_attrs[] = { + &ub_slot_attr_power.attr, + NULL +}; +ATTRIBUTE_GROUPS(ub_slot_default); + +static ssize_t ub_slot_attr_show(struct kobject *kobj, struct attribute *attr, + char *buf) +{ + struct ub_slot_attribute *attribute = to_ub_slot_attr(attr); + struct ub_slot *slot = to_ub_slot(kobj); + + if (!attribute->show) + return -EIO; + + return attribute->show(slot, buf); +} + +static ssize_t ub_slot_attr_store(struct kobject *kobj, struct attribute *attr, + const char *buf, size_t count) +{ + struct ub_slot_attribute *attribute = to_ub_slot_attr(attr); + struct ub_slot *slot = to_ub_slot(kobj); + + if (!attribute->store) + return -EIO; + + return attribute->store(slot, buf, count); +} + +static const struct sysfs_ops ub_slot_sysfs_ops = { + .show = ub_slot_attr_show, + .store = ub_slot_attr_store, +}; + +static const struct kobj_type ub_slot_ktype = { + .release = ubhp_slot_release, + .sysfs_ops = &ub_slot_sysfs_ops, + .default_groups = ub_slot_default_groups, +}; + +static void ubhp_button_handler(struct work_struct *work); +static void ubhp_present_handler(struct work_struct *work); +static void ubhp_power_handler(struct work_struct *work); +static struct ub_slot *ubhp_create_slot(void) +{ + struct ub_slot *slot; + + slot = kzalloc(sizeof(*slot), GFP_KERNEL); + if (!slot) + return NULL; + + slot->ports = NULL; + INIT_LIST_HEAD(&slot->node); + INIT_WORK(&slot->button_work, ubhp_button_handler); + INIT_DELAYED_WORK(&slot->present_work, ubhp_present_handler); + INIT_DELAYED_WORK(&slot->power_work, ubhp_power_handler); + mutex_init(&slot->state_lock); + + return slot; +} + +static int ubhp_slot_port_check(struct ub_entity *uent, int idx, u32 val) +{ + struct ub_port *ports, *tmp; + u16 port_start, port_end; + u16 port_num; + + port_start = (u16)(val & UB_SLOT_START_PORT); + port_end = (u16)(val >> SZ_16); + + if (port_start > port_end || port_end >= uent->port_nums) { + ub_err(uent, "slot%d port range err, start=%u, end=%u, total=%u\n", + idx, port_start, port_end, uent->port_nums); + return -EINVAL; + } + + port_num = port_end - port_start + 1; + ports = uent->ports + port_start; + for (tmp = ports; (tmp - ports) < port_num; tmp++) { + if (tmp->type == VIRTUAL) { + ub_err(uent, "slot%d port%u type is virtual\n", + idx, tmp->index); + return -EINVAL; + } + + if (tmp->slot) { + ub_err(uent, "slot%d port%u already in slot%u\n", + idx, tmp->index, tmp->slot->slot_id); + return -EINVAL; + } + } + + return 0; +} + +static int ubhp_setup_slot(struct ub_slot *slot, struct ub_entity *uent, int idx) +{ + u16 port_start, port_end; + u32 val, cap; + int ret; + + slot->uent = uent; + slot->slot_id = idx; + + ret = ub_slot_read_dword(slot, UB_SLOT_PORT, &val); + if (ret) + return ret; + + ret = ubhp_slot_port_check(uent, idx, val); + if (ret) + return ret; + + ret = ub_slot_read_dword(slot, UB_SLOT_CAP, &cap); + if (ret) + return ret; + + port_start = (u16)(val & UB_SLOT_START_PORT); + port_end = (u16)(val >> SZ_16); + + slot->port_start = port_start; + slot->port_num = port_end - port_start + 1; + slot->ports = uent->ports + port_start; + slot->slot_cap = cap; + + ub_info(uent, "slot%u port[%u:%u] cap[%#x] setup\n", + slot->slot_id, slot->port_start, port_end, slot->slot_cap); + + return 0; +} + +static void ubhp_del_slot(struct ub_slot *slot) +{ + struct ub_port *port; + + cancel_work_sync(&slot->button_work); + cancel_delayed_work_sync(&slot->power_work); + cancel_delayed_work_sync(&slot->present_work); + + list_del(&slot->node); + + for_each_slot_port(port, slot) + port->slot = NULL; + + kobject_del(&slot->kobj); +} + +static int ubhp_add_slot(struct ub_slot *slot) +{ + struct ub_entity *uent = slot->uent; + struct ub_port *port; + int ret; + + ret = kobject_init_and_add(&slot->kobj, &ub_slot_ktype, &uent->dev.kobj, + "slot%d", slot->slot_id); + if (ret) + return ret; + + if (slot->ports->r_uent) { + slot->state = SLOT_ON; + slot->r_uent = slot->ports->r_uent; + } else { + slot->state = SLOT_OFF; + } + + for_each_slot_port(port, slot) + port->slot = slot; + + list_add(&slot->node, &uent->slot_list); + + return ret; +} + +static int ubhp_probe(struct ub_service_device *sdev) +{ + struct ub_entity *uent = sdev->uent; + struct ub_slot *slot, *tmp; + u16 slot_num; + int i, ret; + + if (sdev->service != UB_SERVICE_HP) + return -ENODEV; + + ret = ub_cfg_read_word(uent, UB_SLOT_START + UB_SLOT_NUM, &slot_num); + if (ret) + return ret; + + if (!slot_num) { + ub_err(uent, "hotplug probe without slot, ignoring\n"); + return -ENODEV; + } + + for (i = 0; i < slot_num; i++) { + slot = ubhp_create_slot(); + if (!slot) { + ret = -ENOMEM; + goto free_slots; + } + + ret = ubhp_setup_slot(slot, uent, i); + if (ret) { + ubhp_destory_slot(slot); + goto free_slots; + } + + ret = ubhp_add_slot(slot); + if (ret) { + ubhp_put_slot(slot); + goto free_slots; + } + } + + ubhp_start_slots(uent); + + return 0; +free_slots: + list_for_each_entry_safe_reverse(slot, tmp, &uent->slot_list, node) { + ubhp_del_slot(slot); + ubhp_put_slot(slot); + } + + return ret; +} + +static void ubhp_remove(struct ub_service_device *sdev) +{ + struct ub_entity *uent = sdev->uent; + struct ub_slot *slot, *tmp; + + ubhp_stop_slots(uent); + + list_for_each_entry_safe(slot, tmp, &uent->slot_list, node) { + ubhp_del_slot(slot); + ubhp_put_slot(slot); + } +} + +static struct ub_service_driver ubhp_service_driver = { + .name = "ub-hotplug", + + .probe = ubhp_probe, + .remove = ubhp_remove, + + .service = UB_SERVICE_HP, +}; + +void ubhp_service_init(void) +{ + ub_service_driver_register(&ubhp_service_driver); +} + +void ubhp_service_uninit(void) +{ + ub_service_driver_unregister(&ubhp_service_driver); +} + +static void ubhp_disconnect_slot(struct ub_slot *slot) +{ + struct ub_port *port; + + for_each_slot_port(port, slot) + ub_port_disconnect(port); + + slot->r_uent = NULL; +} + +/** + * a simple example for link down + * for a given topo like + * +-------------+ +---------+ +---------+ +--------+ + * | controller0 |p0:---:p0| switch0 |p1:---slot0---:p0| switch1 |p1:---:p0| device0| + * +-------------+ +---------+ +---------+ +--------+ + * when slot0 is calling handle link down + * 1. disconnect slot0 so that p1 and p0 is not connected in software + * 2. put switch1 and device0 into dev_list, mark them as detached + * 3. stop switch1 and device0 + * 4. remove switch1 and device0 + * 5. handle route link down at slot0, del route of right(switch1 & device0) + * from left(controller0 & switch0) + */ +static void ubhp_handle_link_down(struct ub_slot *slot) +{ + struct list_head dev_list; + struct ub_entity *r_uent; + + INIT_LIST_HEAD(&dev_list); + + r_uent = slot->r_uent; + if (!r_uent) { + ub_warn(slot->uent, "link down without remote dev\n"); + return; + } + + ubhp_disconnect_slot(slot); + ubhp_mark_detached_entities(r_uent, &dev_list); + ubhp_stop_entities(&dev_list); + ubhp_remove_entities(&dev_list); + ubhp_update_route_link_down(slot); +} + +static void ubhp_handle_button_press(struct ub_slot *slot) +{ +#define POWER_ON_WAIT 5 + bool queued = false; + + ubhp_get_slot(slot); + mutex_lock(&slot->state_lock); + + if (slot->state == SLOT_ON) { + slot->state = SLOT_POWEROFF; + ub_info(slot->uent, "slot%u poweroff\n", slot->slot_id); + ubhp_set_indicators(slot, INDICATOR_BLINKING, INDICATOR_NOOP); + /* for power off, issued the present work immediately */ + queued = queue_delayed_work(get_rx_msg_wq(UB_MSG_CODE_LINK), + &slot->present_work, 0); + } else if (slot->state == SLOT_OFF) { + slot->state = SLOT_POWERON; + ub_info(slot->uent, "slot%u poweron\n", slot->slot_id); + ubhp_set_indicators(slot, INDICATOR_BLINKING, INDICATOR_NOOP); + /* for power on, left 5s for possible card insertion */ + queued = queue_delayed_work(get_rx_msg_wq(UB_MSG_CODE_LINK), + &slot->present_work, + POWER_ON_WAIT * HZ); + } + + mutex_unlock(&slot->state_lock); + if (!queued) + ubhp_put_slot(slot); +} + +static void ubhp_button_handler(struct work_struct *work) +{ + struct ub_slot *slot = container_of(work, struct ub_slot, button_work); + + ubhp_handle_button_press(slot); + ubhp_put_slot(slot); +} + +void ubhp_handle_power(struct ub_slot *slot, bool power_on) +{ + if (!slot) + return; + + mutex_lock(&slot->state_lock); + + if (slot->state != SLOT_POWERON) + goto out; + + if (power_on) { + ubhp_set_indicators(slot, INDICATOR_ON, INDICATOR_NOOP); + slot->state = SLOT_ON; + ub_info(slot->uent, "slot%u on\n", slot->slot_id); + ub_info(slot->uent, + "slot%u handle hotplug success\n", slot->slot_id); + if (cancel_work(&slot->button_work)) + ubhp_put_slot(slot); + } else { + ubhp_set_slot_power(slot, POWER_OFF); + slot->state = SLOT_OFF; + ubhp_set_indicators(slot, INDICATOR_OFF, INDICATOR_NOOP); + ub_info(slot->uent, "slot%u off\n", slot->slot_id); + ub_info(slot->uent, + "slot%u handle hotplug unsuccess\n", slot->slot_id); + } + +out: + mutex_unlock(&slot->state_lock); +} + +static void ubhp_power_handler(struct work_struct *work) +{ + struct delayed_work *power_work; + struct ub_slot *slot; + + power_work = to_delayed_work(work); + slot = container_of(power_work, struct ub_slot, power_work); + + ubhp_handle_power(slot, false); + ubhp_put_slot(slot); +} + +static void ubhp_handle_present(struct ub_slot *slot) +{ +#define HP_LINK_WAIT_DELAY 10 + mutex_lock(&slot->state_lock); + + if (slot->state == SLOT_POWEROFF || slot->state == SLOT_ON) { + ubhp_handle_link_down(slot); + ubhp_set_slot_power(slot, POWER_OFF); + ubhp_set_indicators(slot, INDICATOR_OFF, INDICATOR_NOOP); + slot->state = SLOT_OFF; + ub_info(slot->uent, "slot%u off\n", slot->slot_id); + goto out; + } + + if (!ubhp_card_present(slot)) + goto clear_state; + + ubhp_set_slot_power(slot, POWER_ON); + + mutex_unlock(&slot->state_lock); + ubhp_get_slot(slot); + queue_delayed_work(get_rx_msg_wq(UB_MSG_CODE_LINK), + &slot->power_work, HP_LINK_WAIT_DELAY * HZ); + return; +out: + /** + * why cancel button work here: + * 1. for a slot with many ports, it's possible that every port will send + * msg when hotplug event occurred, it's possible some of this msgs + * is handled after present work is triggered, if these msgs are + * handled as usually, may come into error like slot power off + * immediately after power on, so button work is banned during + * handling present work + * 2. when the user pressed button repeatedly, some msgs come when + * handling present work, it's reasonable to ignore them + * + * by holding the state_lock both during button work and present work, + * it's guaranteed that button work and present work can't be handled + * at the same time. So cancel button work before present work ends + * makes sure that button work issued during present work is ignored + */ + if (cancel_work(&slot->button_work)) + ubhp_put_slot(slot); + mutex_unlock(&slot->state_lock); + + ub_info(slot->uent, "slot%u handle hotplug succeeded\n", slot->slot_id); + return; +clear_state: + slot->state = SLOT_OFF; + ubhp_set_indicators(slot, INDICATOR_OFF, INDICATOR_NOOP); + ub_info(slot->uent, "slot%u off\n", slot->slot_id); + mutex_unlock(&slot->state_lock); + ub_info(slot->uent, "slot%u handle hotplug failed\n", slot->slot_id); +} + +static void ubhp_present_handler(struct work_struct *work) +{ + struct delayed_work *present_work; + struct ub_slot *slot; + + present_work = to_delayed_work(work); + slot = container_of(present_work, struct ub_slot, present_work); + + ubhp_handle_present(slot); + ubhp_put_slot(slot); +} diff --git a/drivers/ub/ubus/services/hotplug/hotplug_ctrl.c b/drivers/ub/ubus/services/hotplug/hotplug_ctrl.c new file mode 100644 index 0000000000000000000000000000000000000000..28753cc2501ceae95515ce94de00dfca1f43709a --- /dev/null +++ b/drivers/ub/ubus/services/hotplug/hotplug_ctrl.c @@ -0,0 +1,220 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#include "../../ubus.h" +#include "../../port.h" +#include "hotplug.h" + +/** + * arrange of SHP cap + * + * +-------------+---------------+---------------+ + * | reg(s) name | start address | end address | + * | slice header| 0x00 | 0x00 | + * | slot number | 0x01 | 0x01 | + * | slot1 regs | 0x02 | 0x0F | + * | slot2 regs | 0x12 | 0x1F | + * ... + * | slotN regs | 0x2 + 10(N-1) | 0xF + 10(N-1) | + */ + +/** + * for any given reg in slot regs of slotN, it's pos can be get by: + * SHP cap pos + 10(N-1) + reg pos in slot regs + * the fronter two elements is represented as following UB_SLOT_BASE marco + */ +#define UB_SLOT_BASE(slot) (UB_SLOT_START + (slot)->slot_id * UB_SLOT_POS) + +static void ub_slot_read_byte(struct ub_slot *slot, u32 pos, u8 *val) +{ + int ret = ub_cfg_read_byte(slot->uent, UB_SLOT_BASE(slot) + pos, val); + + if (unlikely(ret)) { + ub_err(slot->uent, "ub slot%u read %#x failed, ret=%d\n", + slot->slot_id, pos, ret); + *val = 0; + } +} + +int ub_slot_read_dword(struct ub_slot *slot, u32 pos, u32 *val) +{ + int ret = ub_cfg_read_dword(slot->uent, UB_SLOT_BASE(slot) + pos, val); + + if (unlikely(ret)) + ub_err(slot->uent, "ub slot%u read %#x failed, ret=%d\n", + slot->slot_id, pos, ret); + + return ret; +} + +static void ub_slot_write_byte(struct ub_slot *slot, u32 pos, u8 val) +{ + int ret = ub_cfg_write_byte(slot->uent, UB_SLOT_BASE(slot) + pos, val); + + if (unlikely(ret)) + ub_err(slot->uent, "ub slot%u write %#x failed, ret=%d\n", + slot->slot_id, pos, ret); +} + +void ubhp_set_indicators(struct ub_slot *slot, u8 power, u8 work) +{ + u8 val; + + /** + * power state indicator: + * OFF: slot is power off, card can be inserted or removed + * ON: slot is power on, card can't be inserted or removed + * BLINKING: slot is under operation performed by user, + * card can't be inserted or removed + */ + if (power >= INDICATOR_NOOP || !PWR_LED(slot)) + goto set_wl; + + ub_slot_read_byte(slot, UB_SLOT_PL_CTRL, &val); + val &= ~UB_SLOT_PL_CTRL_MASK; + val |= power; + ub_slot_write_byte(slot, UB_SLOT_PL_CTRL, val); + +set_wl: + /** + * card work state indicator: + * OFF: card is running normally + * ON: card is out of service or faulty. + * BLINKING: card is under operation performed by user + */ + if (work >= INDICATOR_NOOP || !WORK_LED(slot)) + return; + + ub_slot_read_byte(slot, UB_SLOT_WL_CTRL, &val); + val &= ~UB_SLOT_WL_CTRL_MASK; + val |= work; + ub_slot_write_byte(slot, UB_SLOT_WL_CTRL, val); +} + +void ubhp_set_slot_power(struct ub_slot *slot, enum power_state power) +{ + ub_slot_write_byte(slot, UB_SLOT_PW_CTRL, power); +} + +bool ubhp_card_present(struct ub_slot *slot) +{ + u8 val; + + ub_slot_read_byte(slot, UB_SLOT_PD_STA, &val); + + return !!(val & UB_SLOT_PD_STA_MASK); +} + +bool ubhp_wait_linkup(struct ub_slot *slot) +{ +#define TOTAL_WAIT_TIME 100 /* wait 100ms for linkup */ + u64 timeout = get_jiffies_64() + msecs_to_jiffies(TOTAL_WAIT_TIME); + struct ub_port *port; + + do { + /* only need one port link up */ + for_each_slot_port(port, slot) + if (ub_port_check_link_up(port)) + return true; + } while (time_is_after_jiffies64(timeout)); + + return false; +} + +/* check config space and clear status regs */ +bool ubhp_confirm_event(struct ub_slot *slot, enum hotplug_event event) +{ + u32 pos, mask; + u8 val; + + switch (event) { + case HPE_BUTTON_PRESSED: + pos = UB_SLOT_PP_STA; + mask = UB_SLOT_PP_STA_MASK; + break; + case HPE_PRESENCE_DETECT: + pos = UB_SLOT_PDSC_STA; + mask = UB_SLOT_PDSC_STA_MASK; + break; + default: + return false; + } + + ub_slot_read_byte(slot, pos, &val); + if (!(val & mask)) { + ub_err(slot->uent, "confirm hotplug event %d failed\n", event); + return false; + } + + val &= ~mask; + ub_slot_write_byte(slot, pos, val); + return true; +} + +static void ubhp_start_slot(struct ub_slot *slot) +{ + u8 val; + + /* enable PP */ + ub_slot_read_byte(slot, UB_SLOT_PP_CTRL, &val); + val |= UB_SLOT_PP_CTRL_MASK; + ub_slot_write_byte(slot, UB_SLOT_PP_CTRL, val); + + /* enable PD */ + ub_slot_read_byte(slot, UB_SLOT_PD_CTRL, &val); + val |= UB_SLOT_PD_CTRL_MASK; + ub_slot_write_byte(slot, UB_SLOT_PD_CTRL, val); + + /* enable PDS */ + ub_slot_read_byte(slot, UB_SLOT_PDS_CTRL, &val); + val |= UB_SLOT_PDS_CTRL_MASK; + ub_slot_write_byte(slot, UB_SLOT_PDS_CTRL, val); + + /* enable MS */ + ub_slot_read_byte(slot, UB_SLOT_MS_CTRL, &val); + val |= UB_SLOT_MS_CTRL_MASK; + ub_slot_write_byte(slot, UB_SLOT_MS_CTRL, val); +} + +static void ubhp_stop_slot(struct ub_slot *slot) +{ + u8 val; + + /* disable MS */ + ub_slot_read_byte(slot, UB_SLOT_MS_CTRL, &val); + val &= ~UB_SLOT_MS_CTRL_MASK; + ub_slot_write_byte(slot, UB_SLOT_MS_CTRL, val); + + /* disable PDS */ + ub_slot_read_byte(slot, UB_SLOT_PDS_CTRL, &val); + val &= ~UB_SLOT_PDS_CTRL_MASK; + ub_slot_write_byte(slot, UB_SLOT_PDS_CTRL, val); + + /* disable PD */ + ub_slot_read_byte(slot, UB_SLOT_PD_CTRL, &val); + val &= ~UB_SLOT_PD_CTRL_MASK; + ub_slot_write_byte(slot, UB_SLOT_PD_CTRL, val); + + /* disable PP */ + ub_slot_read_byte(slot, UB_SLOT_PP_CTRL, &val); + val &= ~UB_SLOT_PP_CTRL_MASK; + ub_slot_write_byte(slot, UB_SLOT_PP_CTRL, val); +} + +void ubhp_start_slots(struct ub_entity *uent) +{ + struct ub_slot *slot; + + list_for_each_entry(slot, &uent->slot_list, node) + ubhp_start_slot(slot); +} + +void ubhp_stop_slots(struct ub_entity *uent) +{ + struct ub_slot *slot; + + list_for_each_entry(slot, &uent->slot_list, node) + ubhp_stop_slot(slot); +} diff --git a/drivers/ub/ubus/services/hotplug/hotplug_msg.c b/drivers/ub/ubus/services/hotplug/hotplug_msg.c new file mode 100644 index 0000000000000000000000000000000000000000..53cbe832c3cb13d60409e1b5018d4fa6eb9c7fe9 --- /dev/null +++ b/drivers/ub/ubus/services/hotplug/hotplug_msg.c @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#define pr_fmt(fmt) "ubus hp msg: " fmt + +#include "../../ubus.h" +#include "../../msg.h" +#include "../../link.h" +#include "hotplug.h" + +static struct ub_slot *ub_find_slot(struct ub_entity *uent, u16 slot_id) +{ + struct ub_slot *slot; + + list_for_each_entry(slot, &uent->slot_list, node) + if (slot->slot_id == slot_id) + return slot; + + return NULL; +} + +static struct ub_slot *ubhp_get_slot_from_msg(void *pkt) +{ + struct msg_pkt_header *header = (struct msg_pkt_header *)pkt; + struct ubhp_msg_payload *payload; + struct ub_slot *slot; + struct ub_entity *uent; + u32 seid; + + seid = eid_gen(header->seid_h, header->seid_l); + uent = ub_get_ent_by_eid(seid); + if (!uent) { + pr_warn("get no device by eid %#05x\n", seid); + return NULL; + } + + payload = (struct ubhp_msg_payload *)header->payload; + slot = ubhp_get_slot(ub_find_slot(uent, payload->slot_id)); + if (!slot) + pr_err("can not find slot with id %u\n", payload->slot_id); + + ub_entity_put(uent); + return slot; +} + +void ubhp_handle_event(struct ub_slot *slot, enum hotplug_event event) +{ + bool queued = false; + u32 flag; + + if (!ubhp_confirm_event(slot, event)) + return; + + /** + * get slot if work is queued + * queue_work: return false if work already exists + * mod_delayed_work: return true if work exists, with its timer modified + */ + if (event == HPE_BUTTON_PRESSED) { + queued = queue_work(get_rx_msg_wq(UB_MSG_CODE_LINK), + &slot->button_work); + } else if (event == HPE_PRESENCE_DETECT) { + flag = work_busy(&slot->present_work.work); + if (!(flag & WORK_BUSY_RUNNING)) + queued = !mod_delayed_work( + get_rx_msg_wq(UB_MSG_CODE_LINK), + &slot->present_work, 0); + } + if (queued) + ubhp_get_slot(slot); +} + +void ubhp_event_handler(struct ub_bus_controller *ubc, void *pkt, u16 len) +{ + struct msg_pkt_header *header = (struct msg_pkt_header *)pkt; + struct ub_slot *slot; + + if (len < UB_HP_MSG_SIZE) { + dev_err(&ubc->dev, "hp msg len[%#x] invalid\n", len); + return; + } + + slot = ubhp_get_slot_from_msg(pkt); + if (!slot) + return; + + ubhp_handle_event(slot, + (enum hotplug_event)header->msgetah.sub_msg_code); + ubhp_put_slot(slot); +} diff --git a/drivers/ub/ubus/services/hotplug/hotplug_route.c b/drivers/ub/ubus/services/hotplug/hotplug_route.c new file mode 100644 index 0000000000000000000000000000000000000000..76341766e3625e09497339af4d1ee0ca3223c974 --- /dev/null +++ b/drivers/ub/ubus/services/hotplug/hotplug_route.c @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#include + +#include "../../ubus.h" +#include "../../ubus_entity.h" +#include "../../ubus_driver.h" +#include "../../port.h" +#include "../../route.h" +#include "hotplug.h" + +int ubhp_update_route_link_up(struct ub_slot *slot) +{ + struct ub_entity *uent = slot->uent, *r_uent = slot->r_uent; + struct ub_port *port, *r_port; + int ret; + + for_each_slot_port(port, slot) { + if (!port->r_uent) + continue; + + r_port = port->r_uent->ports + port->r_index; + ret = ub_route_mod_neighbor(port, r_port); + if (ret) + return ret; + + ret = ub_route_mod_neighbor(r_port, port); + if (ret) + return ret; + } + + /* for uent that can't forward, there's no need to update other uent */ + if (ub_entity_support_forward(uent)) + ub_route_mod_bfs(uent); + + if (ub_entity_support_forward(r_uent)) + ub_route_mod_bfs(r_uent); + + ub_route_sync_all(); + return 0; +} + +void ubhp_update_route_link_down(struct ub_slot *slot) +{ + struct ub_entity *uent = slot->uent; + struct ub_port *port; + + for_each_slot_port(port, slot) + ub_route_clear_port(port); + + /* for uent that can't forward, there's no need to update other uent */ + if (ub_entity_support_forward(uent)) + ub_route_del_bfs(uent); + + ub_route_sync_all(); +} + +/** + * ubhp_mark_detached_entities() - mark devices as detached and put them into dev_list + * @root: a device that needs to be removed + * @dev_list: a list to store the detached devices + * + * this func use bfs to mark devices and put them into dev_list, all devices + * that connected with root will be marked as detached + */ +void ubhp_mark_detached_entities(struct ub_entity *root, struct list_head *dev_list) +{ +#define UBHP_KFIFO_DEPTH SZ_16 + DECLARE_KFIFO(kfifo, struct ub_entity *, UBHP_KFIFO_DEPTH); + struct ub_port *port; + struct ub_entity *uent, *r_uent; + + INIT_KFIFO(kfifo); + ub_entity_assign_priv_flag(root, UB_ENTITY_DETACHED, true); + kfifo_put(&kfifo, root); + + down_write(&ub_bus_sem); + while (kfifo_get(&kfifo, &uent)) { + for_each_uent_port(port, uent) { + if (!port->r_uent) + continue; + + r_uent = port->r_uent; + if (ub_entity_test_priv_flag(r_uent, UB_ENTITY_DETACHED)) + continue; + + ub_entity_assign_priv_flag(r_uent, UB_ENTITY_DETACHED, true); + if (!kfifo_put(&kfifo, r_uent)) + ub_err(r_uent, "hp detached entity kfifo put failed!\n"); + } + + list_del(&uent->node); + list_add_tail(&uent->node, dev_list); + } + up_write(&ub_bus_sem); +} + +/** + * ubhp_stop_entities() - stop devices in dev_list + * @dev_list: a list to store devices + */ +void ubhp_stop_entities(struct list_head *dev_list) +{ + struct ub_entity *uent; + + list_for_each_entry_reverse(uent, dev_list, node) + ub_stop_ent(uent); +} + +/** + * ubhp_remove_entities() - remove devices in dev_list + * @dev_list: a list to store devices + */ +void ubhp_remove_entities(struct list_head *dev_list) +{ + struct ub_entity *uent, *tmp; + + list_for_each_entry_safe_reverse(uent, tmp, dev_list, node) + ub_remove_ent(uent); +} diff --git a/drivers/ub/ubus/services/service.c b/drivers/ub/ubus/services/service.c new file mode 100644 index 0000000000000000000000000000000000000000..b925da25cec7987cfa81b50cffc8da98b252c067 --- /dev/null +++ b/drivers/ub/ubus/services/service.c @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#define pr_fmt(fmt) "ubus service: " fmt + +#include "../ubus.h" +#include "../ubus_driver.h" +#include "service.h" + +static int ub_service_probe(struct device *dev) +{ + struct ub_service_device *sdev; + struct ub_service_driver *sdrv; + int status; + + if (!dev || !dev->driver) + return -ENODEV; + + sdrv = to_ub_service_driver(dev->driver); + if (!sdrv || !sdrv->probe) + return -ENODEV; + + sdev = to_ub_service_device(dev); + status = sdrv->probe(sdev); + if (status) + return status; + + get_device(dev); + return 0; +} + +static int ub_service_remove(struct device *dev) +{ + struct ub_service_device *sdev; + struct ub_service_driver *sdrv; + + if (!dev || !dev->driver) + return 0; + + sdev = to_ub_service_device(dev); + sdrv = to_ub_service_driver(dev->driver); + if (sdrv && sdrv->remove) { + sdrv->remove(sdev); + put_device(dev); + } + + return 0; +} + +static void ub_service_shutdown(struct device *dev) +{ +} + +int ub_service_driver_register(struct ub_service_driver *drv) +{ + drv->driver.name = drv->name; + drv->driver.bus = &ub_service_bus_type; + drv->driver.probe = ub_service_probe; + drv->driver.remove = ub_service_remove; + drv->driver.shutdown = ub_service_shutdown; + + return driver_register(&drv->driver); +} + +void ub_service_driver_unregister(struct ub_service_driver *drv) +{ + driver_unregister(&drv->driver); +} diff --git a/drivers/ub/ubus/services/service.h b/drivers/ub/ubus/services/service.h new file mode 100644 index 0000000000000000000000000000000000000000..85ac7654f912dc47d7eabcc11c2ae7298fe849e3 --- /dev/null +++ b/drivers/ub/ubus/services/service.h @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ +#ifndef __SERVICES_SERVICE_H__ +#define __SERVICES_SERVICE_H__ + +#define UB_SERVICE_HP_SHIFT 1 +#define UB_SERVICE_HP (1 << UB_SERVICE_HP_SHIFT) + +#define UB_MAXSERIVCES 3 + +struct ub_service_device { + int irq; + struct ub_entity *uent; + u32 service; + void *priv_data; + struct device device; +}; + +struct ub_service_driver { + const char *name; + int (*probe)(struct ub_service_device *dev); + void (*remove)(struct ub_service_device *dev); + + u32 service; + + struct device_driver driver; +}; + +#define to_ub_service_device(d) \ + container_of(d, struct ub_service_device, device) +#define to_ub_service_driver(d) \ + container_of(d, struct ub_service_driver, driver) + +void ub_ras_init(void); +void ub_ras_uninit(void); +void ubhp_service_init(void); +void ubhp_service_uninit(void); + +int ub_service_driver_register(struct ub_service_driver *drv); +void ub_service_driver_unregister(struct ub_service_driver *drv); + +#endif /* __SERVICES_SERVICE_H__ */ diff --git a/drivers/ub/ubus/sysfs.c b/drivers/ub/ubus/sysfs.c index b816e82e4c0da13752a49a354363274f61af137d..c4899152ba6f4966c45363b9ca97c0fba646eb6d 100644 --- a/drivers/ub/ubus/sysfs.c +++ b/drivers/ub/ubus/sysfs.c @@ -8,6 +8,18 @@ #include "ubus.h" #include "sysfs.h" #include "ubus_entity.h" +#include "instance.h" +#include "port.h" +#include "resource.h" + +static inline void ub_resource_to_user(const struct ub_entity *dev, int res_id, + const struct resource *rsrc, + resource_size_t *start, + resource_size_t *end) +{ + *start = rsrc->start; + *end = rsrc->end; +} #define ub_config_attr(field, format_string) \ static ssize_t field##_show(struct device *dev, struct device_attribute *attr, char *buf) \ @@ -23,6 +35,15 @@ ub_config_attr(device, "%#06x\n"); ub_config_attr(type, "%#x\n"); ub_config_attr(vendor, "%#06x\n"); +static ssize_t ubc_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct ub_entity *uent; + + uent = to_ub_entity(dev); + return sysfs_emit(buf, "%#05x\n", uent->ubc->uent->uent_num); +} +static DEVICE_ATTR_RO(ubc); + static ssize_t class_code_show(struct device *dev, struct device_attribute *attr, char *buf) { struct ub_entity *uent; @@ -43,12 +64,177 @@ static ssize_t guid_show(struct device *dev, struct device_attribute *attr, char } static DEVICE_ATTR_RO(guid); +static ssize_t entity_idx_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct ub_entity *uent = to_ub_entity(dev); + + return sysfs_emit(buf, "%u\n", uent->entity_idx); +} +static DEVICE_ATTR_RO(entity_idx); + +static ssize_t eid_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct ub_entity *uent = to_ub_entity(dev); + + return sysfs_emit(buf, "%u\n", uent->eid); +} +static DEVICE_ATTR_RO(eid); + +static ssize_t tid_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct ub_entity *uent = to_ub_entity(dev); + + return sysfs_emit(buf, "%u\n", uent->tid); +} +static DEVICE_ATTR_RO(tid); + static ssize_t kref_show(struct device *dev, struct device_attribute *attr, char *buf) { return sysfs_emit(buf, "%u\n", kref_read(&dev->kobj.kref)); } static DEVICE_ATTR_RO(kref); +#define DATA_POS (cur_pos - usr_pos) +#define REMAIN_BYTE (len - DATA_POS) + +static ssize_t ub_read_config(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buf, + loff_t usr_pos, size_t count) +{ + struct ub_entity *uent = to_ub_entity(kobj_to_dev(kobj)); + u64 cur_pos = usr_pos; + u8 *data = (u8 *)buf; + size_t len = count >> 1; + u32 val; + + if (count == PAGE_SIZE || len == 0) + return -EINVAL; + + memset(buf, 0, count); + + if (cur_pos & 1) { + if (ub_cfg_read_byte(uent, cur_pos, (u8 *)&val)) + memcpy(data + DATA_POS + len, &val, 1); + memcpy(data + DATA_POS, &val, 1); + cur_pos++; + } + + if ((cur_pos & SZ_2) && (REMAIN_BYTE >= SZ_2)) { + if (ub_cfg_read_word(uent, cur_pos, (u16 *)&val)) + memcpy(data + DATA_POS + len, &val, SZ_2); + memcpy(data + DATA_POS, &val, SZ_2); + cur_pos += SZ_2; + } + + while (REMAIN_BYTE >= SZ_4) { + if (ub_cfg_read_dword(uent, cur_pos, &val)) + memcpy(data + DATA_POS + len, &val, SZ_4); + memcpy(data + DATA_POS, &val, SZ_4); + cur_pos += SZ_4; + } + + if (REMAIN_BYTE >= SZ_2) { + if (ub_cfg_read_word(uent, cur_pos, (u16 *)&val)) + memcpy(data + DATA_POS + len, &val, SZ_2); + memcpy(data + DATA_POS, &val, SZ_2); + cur_pos += SZ_2; + } + + if (REMAIN_BYTE) { + if (ub_cfg_read_byte(uent, cur_pos, (u8 *)&val)) + memcpy(data + DATA_POS + len, &val, 1); + memcpy(data + DATA_POS, &val, 1); + cur_pos++; + } + + return count; +} + +static ssize_t ub_write_config(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buf, + loff_t usr_pos, size_t count) +{ + struct ub_entity *uent = to_ub_entity(kobj_to_dev(kobj)); + u64 cur_pos = usr_pos; + u8 *data = (u8 *)buf; + size_t len = count; + + if (cur_pos & 1) { + u8 val; + + memcpy(&val, data + DATA_POS, 1); + ub_cfg_write_byte(uent, cur_pos, val); + cur_pos++; + } + + if ((cur_pos & SZ_2) && (REMAIN_BYTE >= SZ_2)) { + u16 val; + + memcpy(&val, data + DATA_POS, SZ_2); + ub_cfg_write_word(uent, cur_pos, val); + cur_pos += SZ_2; + } + + while (REMAIN_BYTE >= SZ_4) { + u32 val; + + memcpy(&val, data + DATA_POS, SZ_4); + ub_cfg_write_dword(uent, cur_pos, val); + cur_pos += SZ_4; + } + + if (REMAIN_BYTE >= SZ_2) { + u16 val; + + memcpy(&val, data + DATA_POS, SZ_2); + ub_cfg_write_word(uent, cur_pos, val); + cur_pos += SZ_2; + } + + if (REMAIN_BYTE) { + u8 val; + + memcpy(&val, data + DATA_POS, 1); + ub_cfg_write_byte(uent, cur_pos, val); + cur_pos++; + } + + return count; +} + +#undef REMAIN_BYTE +#undef DATA_POS + +static const struct bin_attribute ub_config_bin_attr = { + .attr = { + .name = "config", + .mode = 0644, + }, + .size = UB_CFG_SAPCE_SLICE_END, + .read = ub_read_config, + .write = ub_write_config, +}; + +static ssize_t resource_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct ub_entity *uent = to_ub_entity(dev); + resource_size_t start, end; + int i, cnt = 0; + + for (i = 0; i < MAX_UB_RES_NUM; i++) { + struct resource *res = &uent->zone[i].res; + + ub_resource_to_user(uent, i, res, &start, &end); + cnt += sysfs_emit_at(buf, cnt, "%#016llx %#016llx %#016llx\n", + (unsigned long long)start, + (unsigned long long)end, + (unsigned long long)res->flags); + } + return cnt; +} +static DEVICE_ATTR_RO(resource); + static ssize_t driver_override_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) @@ -111,14 +297,101 @@ static ssize_t match_driver_show(struct device *dev, } static DEVICE_ATTR_RW(match_driver); +static ssize_t direct_link_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ub_entity *uent = to_ub_entity(dev); + struct ub_port *port; + int cnt = 0; + + for_each_uent_port(port, uent) { + if (!port->r_uent) + continue; + cnt += sysfs_emit_at(buf, cnt, "%#04x : %#04x [%#05x]\n", + port->index, port->r_index, + port->r_uent->uent_num); + } + + return cnt; +} +DEVICE_ATTR_RO(direct_link); + +static ssize_t primary_cna_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ub_entity *uent = to_ub_entity(dev); + + return sysfs_emit(buf, "%#06x\n", uent->cna); +} +DEVICE_ATTR_RO(primary_cna); + +static ssize_t instance_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct ub_entity *uent = to_ub_entity(dev); + u32 eid = 0; + + if (uent->bi) + eid = uent->bi->info.eid; + + return sysfs_emit(buf, "%#05x\n", eid); +} +DEVICE_ATTR_RO(instance); + +static ssize_t upi_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct ub_entity *uent = to_ub_entity(dev); + + return sysfs_emit(buf, "%#04x\n", uent->upi); +} +DEVICE_ATTR_RO(upi); + +static ssize_t numa_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct ub_entity *uent = to_ub_entity(dev); + + return sysfs_emit(buf, "%#04x\n", uent->ubc->attr.proximity_domain); +} +DEVICE_ATTR_RO(numa); + +static ssize_t primary_entity_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct ub_entity *uent = to_ub_entity(dev); + + if (uent->entity_idx == 0) /* this device is primary entity */ + goto pue_print; + else if (uent->is_mue) /* find primary entity for mue */ + uent = uent->pue; + else /* find primary entity for ue */ + uent = uent->pue->pue; + +pue_print: + return sysfs_emit(buf, "%#05x\n", uent->uent_num); +} +DEVICE_ATTR_RO(primary_entity); + static struct attribute *ub_entity_attrs[] = { + &dev_attr_resource.attr, &dev_attr_vendor.attr, &dev_attr_device.attr, &dev_attr_class_code.attr, &dev_attr_type.attr, &dev_attr_driver_override.attr, &dev_attr_match_driver.attr, + &dev_attr_direct_link.attr, &dev_attr_guid.attr, + &dev_attr_ubc.attr, + &dev_attr_primary_cna.attr, + &dev_attr_instance.attr, + &dev_attr_upi.attr, + &dev_attr_entity_idx.attr, + &dev_attr_numa.attr, + &dev_attr_eid.attr, + &dev_attr_tid.attr, + &dev_attr_primary_entity.attr, &dev_attr_kref.attr, NULL }; @@ -132,7 +405,22 @@ const struct attribute_group *ub_entity_groups[] = { NULL }; +static ssize_t cluster_show(const struct bus_type *bus, char *buf) +{ + struct ub_bus_controller *ubc; + + if (list_empty(&ubc_list)) + return 0; + + ubc = list_first_entry(&ubc_list, struct ub_bus_controller, node); + return sysfs_emit(buf, "%d\n", ubc->cluster); +} +static BUS_ATTR_RO(cluster); + + static struct attribute *ub_bus_attrs[] = { + &bus_attr_instance.attr, + &bus_attr_cluster.attr, NULL }; @@ -149,6 +437,57 @@ const struct device_type ub_dev_type = { .groups = NULL, }; +static ssize_t reset_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ub_entity *uent = to_ub_entity(dev); + unsigned long val; + ssize_t result; + + result = kstrtoul(buf, 0, &val); + if (result < 0) + return result; + + if (val != 1) + return -EINVAL; + + result = ub_reset_entity(uent); + if (result < 0) + return result; + + return count; +} +static DEVICE_ATTR_WO(reset); + +static ssize_t device_reset_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ub_entity *uent = to_ub_entity(dev); + unsigned long val; + ssize_t result; + + result = kstrtoul(buf, 0, &val); + if (result < 0 || val != 1) + return -EINVAL; + + result = ub_device_reset(uent); + if (result < 0) + return result; + + return count; +} +static DEVICE_ATTR_WO(device_reset); + +static ssize_t sw_cap_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct ub_entity *uent; + + uent = to_ub_entity(dev); + return sysfs_emit(buf, "%d\n", uent->sw_cap); +} +static DEVICE_ATTR_RO(sw_cap); + static ssize_t ub_total_entities_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -347,12 +686,17 @@ DEVICE_ATTR_RO(mue_list); #define CREATE_CONDITIONS(c) ((c) ? 1 : 0) +/* When adding sysfs, remember to add it in remove function. */ static int ub_create_capabilities_sysfs(struct ub_entity *uent) { struct dev_attr_creat_group grp[] = { + { &dev_attr_reset, CREATE_CONDITIONS(uent->reset_fn) }, + { &dev_attr_device_reset, + CREATE_CONDITIONS(uent->entity_idx == 0 && !is_ibus_controller(uent)) }, { &dev_attr_ub_total_entities, CREATE_CONDITIONS(uent->entity_idx == 0) }, { &dev_attr_ub_totalues, CREATE_CONDITIONS(uent->is_mue) }, { &dev_attr_ub_numues, CREATE_CONDITIONS(uent->is_mue) }, + { &dev_attr_sw_cap, CREATE_CONDITIONS(is_ibus_controller(uent)) }, { &dev_attr_ub_release_ue, CREATE_CONDITIONS(uent->is_mue && entity_flex_en) }, { &dev_attr_mue_list, CREATE_CONDITIONS(uent->entity_idx == 0) }, { &dev_attr_ue_list, CREATE_CONDITIONS(uent->is_mue) }, @@ -378,9 +722,13 @@ static int ub_create_capabilities_sysfs(struct ub_entity *uent) static void ub_remove_capabilities_sysfs(struct ub_entity *uent) { struct dev_attr_creat_group grp[] = { + { &dev_attr_reset, CREATE_CONDITIONS(uent->reset_fn) }, + { &dev_attr_device_reset, + CREATE_CONDITIONS(uent->entity_idx == 0 && !is_ibus_controller(uent)) }, { &dev_attr_ub_total_entities, CREATE_CONDITIONS(uent->entity_idx == 0) }, { &dev_attr_ub_totalues, CREATE_CONDITIONS(uent->is_mue) }, { &dev_attr_ub_numues, CREATE_CONDITIONS(uent->is_mue) }, + { &dev_attr_sw_cap, CREATE_CONDITIONS(is_ibus_controller(uent)) }, { &dev_attr_ub_release_ue, CREATE_CONDITIONS(uent->is_mue && entity_flex_en) }, { &dev_attr_mue_list, CREATE_CONDITIONS(uent->entity_idx == 0) }, { &dev_attr_ue_list, CREATE_CONDITIONS(uent->is_mue) }, @@ -392,18 +740,191 @@ static void ub_remove_capabilities_sysfs(struct ub_entity *uent) device_remove_file(&uent->dev, grp[i].attr); } +static bool ub_mmap_fits(struct ub_entity *uent, int idx, + struct vm_area_struct *vma) +{ + resource_size_t len, start, size; + + if (ub_resource_len(uent, idx) == 0) + return false; + + len = vma_pages(vma); + start = vma->vm_pgoff; + size = ((ub_resource_len(uent, idx) - 1) >> PAGE_SHIFT) + 1; + if (start < size && (start + len <= size)) + return true; + + return false; +} + +static int ub_mmap_resource(struct kobject *kobj, struct bin_attribute *attr, + struct vm_area_struct *vma, int write_combine) +{ + struct ub_entity *uent = to_ub_entity(kobj_to_dev(kobj)); + unsigned long idx = (unsigned long)attr->private; + + if (idx >= MAX_UB_RES_NUM) + return -EINVAL; + + if (!ub_mmap_fits(uent, idx, vma)) + return -EINVAL; + + return ub_mmap_resource_range(uent, idx, vma, write_combine); +} + +static int ub_mmap_resource_wc(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, + struct vm_area_struct *vma) +{ + return ub_mmap_resource(kobj, attr, vma, 1); +} + +static int ub_mmap_resource_uc(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, + struct vm_area_struct *vma) +{ + return ub_mmap_resource(kobj, attr, vma, 0); +} + +static int ub_create_attr(struct ub_entity *uent, int num, int write_combine) +{ + /* allocate attribute structure, piggyback attribute name */ + int name_len = write_combine ? 13 : 10; + struct bin_attribute *attr; + char *res_attr_name; + int retval; + + attr = kzalloc(sizeof(*attr) + name_len, GFP_ATOMIC); + if (!attr) + return -ENOMEM; + + res_attr_name = (char *)(attr + 1); + + sysfs_bin_attr_init(attr); + if (write_combine) { + uent->res_attr_wc[num] = attr; + sprintf(res_attr_name, "resource%d_wc", num); + attr->mmap = ub_mmap_resource_wc; + } else { + uent->res_attr[num] = attr; + sprintf(res_attr_name, "resource%d", num); + attr->mmap = ub_mmap_resource_uc; + } + attr->attr.name = res_attr_name; + attr->attr.mode = 0600; + attr->size = ub_resource_len(uent, num); + attr->private = (void *)(unsigned long)num; + retval = sysfs_create_bin_file(&uent->dev.kobj, attr); + if (retval) { + kfree(attr); + write_combine ? (uent->res_attr_wc[num] = NULL) : + (uent->res_attr[num] = NULL); + } + + return retval; +} + +static void ub_remove_resource_files(struct ub_entity *uent) +{ + int i; + + for (i = 0; i < MAX_UB_RES_NUM; i++) { + struct bin_attribute *res_attr; + + res_attr = uent->res_attr[i]; + if (res_attr) { + sysfs_remove_bin_file(&uent->dev.kobj, res_attr); + kfree(res_attr); + uent->res_attr[i] = NULL; + } + + res_attr = uent->res_attr_wc[i]; + if (res_attr) { + sysfs_remove_bin_file(&uent->dev.kobj, res_attr); + kfree(res_attr); + uent->res_attr_wc[i] = NULL; + } + } +} + +static int ub_create_resource_files(struct ub_entity *uent) +{ + int i; + int retval; + + for (i = 0; i < MAX_UB_RES_NUM; i++) { + /* skip empty resources */ + if (!ub_resource_len(uent, i)) + continue; + + retval = ub_create_attr(uent, i, 0); + if (retval) { + ub_remove_resource_files(uent); + return retval; + } + + retval = ub_create_attr(uent, i, 1); + if (retval) { + ub_remove_resource_files(uent); + return retval; + } + } + return 0; +} + int ub_create_sysfs_dev_files(struct ub_entity *uent) { int retval; + /* config interface */ + retval = sysfs_create_bin_file(&uent->dev.kobj, &ub_config_bin_attr); + if (retval) + goto err; + + retval = ub_create_resource_files(uent); + if (retval) + goto err_config_file; + retval = ub_create_capabilities_sysfs(uent); if (retval) - return retval; + goto err_resource_files; return 0; + +err_resource_files: + ub_remove_resource_files(uent); +err_config_file: + sysfs_remove_bin_file(&uent->dev.kobj, &ub_config_bin_attr); +err: + return retval; } void ub_remove_sysfs_ent_files(struct ub_entity *uent) { ub_remove_capabilities_sysfs(uent); + + sysfs_remove_bin_file(&uent->dev.kobj, &ub_config_bin_attr); + + ub_remove_resource_files(uent); +} + +int ub_bus_attr_dynamic_init(void) +{ + int ret; + + ret = bus_create_file(&ub_bus_type, &bus_attr_instance); + if (ret) + return ret; + + ret = bus_create_file(&ub_bus_type, &bus_attr_cluster); + if (ret) + bus_remove_file(&ub_bus_type, &bus_attr_instance); + + return ret; +} + +void ub_bus_attr_dynamic_uninit(void) +{ + bus_remove_file(&ub_bus_type, &bus_attr_cluster); + bus_remove_file(&ub_bus_type, &bus_attr_instance); } diff --git a/drivers/ub/ubus/sysfs.h b/drivers/ub/ubus/sysfs.h index bbd4d1b49dbcb5bbf6495f0fdc70998e43bff3aa..6716be59183ebb7b2fdcd095fa7a5b10f7610040 100644 --- a/drivers/ub/ubus/sysfs.h +++ b/drivers/ub/ubus/sysfs.h @@ -5,8 +5,13 @@ #ifndef __SYSFS_H__ #define __SYSFS_H__ +struct ub_entity; extern const struct attribute_group *ub_entity_groups[]; extern const struct attribute_group *ub_bus_groups[]; extern const struct device_type ub_dev_type; +int ub_create_sysfs_dev_files(struct ub_entity *pue); +void ub_remove_sysfs_ent_files(struct ub_entity *pue); +int ub_bus_attr_dynamic_init(void); +void ub_bus_attr_dynamic_uninit(void); #endif /* __SYSFS_H__ */ diff --git a/drivers/ub/ubus/ubus.h b/drivers/ub/ubus/ubus.h index e25235f4003f113812a6fa19184541aeba91d600..1367d8e87d9aa6f045d497512c7e6911b99833c2 100644 --- a/drivers/ub/ubus/ubus.h +++ b/drivers/ub/ubus/ubus.h @@ -58,6 +58,7 @@ static inline bool ub_entity_test_priv_flag(struct ub_entity *uent, int bit) int ub_host_probe(void); void ub_host_remove(void); +struct ub_bus_controller *ub_find_bus_controller(u32 ctl_no); struct ub_manage_subsystem_ops { u32 vendor; diff --git a/drivers/ub/ubus/ubus_config.c b/drivers/ub/ubus/ubus_config.c index c9b07b63e922563e34dfda7a264d66987131839a..a2d285da998a146332ece36781817fd85dcfdc93 100644 --- a/drivers/ub/ubus/ubus_config.c +++ b/drivers/ub/ubus/ubus_config.c @@ -111,6 +111,7 @@ void ub_msg_pkt_header_init(struct msg_pkt_header *header, struct ub_entity *uen cnth->nth_nlp = NTH_NLP_WITH_TPH; cnth->scna = scna; cnth->dcna = dcna; + cnth->mgmt = is_p_device(uent) ? 0 : 1; header->ctph_nlp = CTPH_NLP_UPI_40BITS_UEID; header->tp_opcode = CTPH_OPCODE_NOT_CNP; diff --git a/drivers/ub/ubus/ubus_driver.c b/drivers/ub/ubus/ubus_driver.c index fe5591c33999029f0368f348ec3253e403d6e7dd..93c1dc5d662afb20b6f8cc5d92f1f45b1f5bf7ca 100644 --- a/drivers/ub/ubus/ubus_driver.c +++ b/drivers/ub/ubus/ubus_driver.c @@ -13,12 +13,19 @@ #include #include +#include "services.h" +#include "msg.h" +#include "enum.h" +#include "instance.h" +#include "ioctl.h" #include "sysfs.h" #include "ubus.h" #include "ubus_config.h" #include "ubus_controller.h" #include "ubus_inner.h" #include "ubus_entity.h" +#include "services/service.h" +#include "ubus_driver.h" bool entity_flex_en; module_param(entity_flex_en, bool, 0444); @@ -619,6 +626,33 @@ void ub_bus_type_uninit(void) ub_bus_type.num_vf = NULL; } +static int ub_service_bus_match(struct device *dev, struct device_driver *drv) +{ + struct ub_service_device *sdev; + struct ub_service_driver *sdrv; + + if (drv->bus != &ub_service_bus_type || dev->bus != &ub_service_bus_type) + return 0; + + sdev = to_ub_service_device(dev); + sdrv = to_ub_service_driver(drv); + if (sdrv->service != sdev->service) + return 0; + + return 1; +} + +struct bus_type ub_service_bus_type = { + .name = "ub_service", + .match = ub_service_bus_match, +}; + +static void ubus_driver_resource_drain(void) +{ + ub_dynamic_bus_instance_drain(); + ub_static_cluster_instance_drain(); +} + int ub_host_probe(void) { int ret; @@ -628,8 +662,54 @@ int ub_host_probe(void) if (ret) goto ub_cfg_ops_init_fail; + ret = ub_bus_controllers_probe(); + if (ret) + goto ubcs_probe_fail; + + ret = ub_enum_probe(); + if (ret) + goto ub_enum_probe_fail; + + /* + * Now ub_bus_type build-in, bus_attr_groups will not created, + * so init it here. + */ + ret = ub_bus_attr_dynamic_init(); + if (ret) + goto ub_bus_attr_dynamic_init_fail; + + ret = bus_register(&ub_service_bus_type); + if (ret) + goto bus_register_fail; + + ret = ub_services_init(); + if (ret) + goto ub_services_init_fail; + + ret = ub_cdev_init(); + if (ret) + goto cdev_fail; + + ret = message_rx_init(); + if (ret) + goto message_init_fail; + return 0; +message_init_fail: + ub_cdev_uninit(); +cdev_fail: + ub_services_exit(); +ub_services_init_fail: + bus_unregister(&ub_service_bus_type); +bus_register_fail: + ub_bus_attr_dynamic_uninit(); +ub_bus_attr_dynamic_init_fail: + ub_enum_remove(); +ub_enum_probe_fail: + ub_bus_controllers_remove(); +ubcs_probe_fail: + unregister_ub_cfg_ops(); ub_cfg_ops_init_fail: ub_bus_type_uninit(); return ret; @@ -638,6 +718,14 @@ EXPORT_SYMBOL_GPL(ub_host_probe); void ub_host_remove(void) { + message_rx_uninit(); + ub_cdev_uninit(); + ub_services_exit(); + bus_unregister(&ub_service_bus_type); + ub_bus_attr_dynamic_uninit(); + ubus_driver_resource_drain(); + ub_enum_remove(); + ub_bus_controllers_remove(); unregister_ub_cfg_ops(); ub_bus_type_uninit(); } diff --git a/drivers/ub/ubus/ubus_driver.h b/drivers/ub/ubus/ubus_driver.h index 6a259d224942572dc523d5540ee36918dd9faaf6..f2bff32bbee9c075a3095b15819012f7f2f55320 100644 --- a/drivers/ub/ubus/ubus_driver.h +++ b/drivers/ub/ubus/ubus_driver.h @@ -7,5 +7,8 @@ #define __UBUS_DRIVER_H__ extern struct rw_semaphore ub_bus_sem; +extern struct bus_type ub_service_bus_type; +int ub_host_probe(void); +void ub_host_remove(void); #endif /* __UBUS_DRIVER_H__ */ diff --git a/drivers/ub/ubus/ubus_entity.c b/drivers/ub/ubus/ubus_entity.c index f0e45b496b2f9041197c0f259feb3d0f132e4581..dc9bccff9044e55ad14a29b91d778905dd34c1fc 100644 --- a/drivers/ub/ubus/ubus_entity.c +++ b/drivers/ub/ubus/ubus_entity.c @@ -22,6 +22,7 @@ #include "ubus_inner.h" #include "cap.h" #include "eu.h" +#include "instance.h" #include "ubus_entity.h" /* @@ -48,6 +49,8 @@ struct ub_entity *ub_alloc_ent(void) INIT_LIST_HEAD(&uent->mue_list); INIT_LIST_HEAD(&uent->ue_list); INIT_LIST_HEAD(&uent->cna_list); + INIT_LIST_HEAD(&uent->slot_list); + INIT_LIST_HEAD(&uent->instance_node); uent->dev.type = &ub_dev_type; uent->cna = 0; @@ -102,6 +105,9 @@ static void ub_config_upi(struct ub_entity *uent) int ret; u16 upi; + if (is_p_device(uent)) + return; + if (is_ibus_controller(uent) && uent->ubc->cluster) { dev = &uent->ubc->dev; ret = ub_cfg_read_word(uent, UB_UPI, &upi); @@ -194,6 +200,22 @@ static int ub_setup_ent_normal(struct ub_entity *uent) return 0; } +static int ub_fad_cfg_access_check(struct ub_entity *uent) +{ + u32 feature; + int ret = 0; + + if (is_p_device(uent)) { + ret = ub_cfg_read_dword(uent, UB_CFG1_SUPPORT_FEATURE_L, + &feature); + if (ret) + ub_err(uent, "fad cfg access failed, eid=%#x, ret=%d\n", + uent->eid, ret); + } + + return ret; +} + static int ub_uent_cfg(struct ub_entity *uent, u32 uent_num) { struct ub_guid *guid = &uent->guid; @@ -220,6 +242,9 @@ static int ub_uent_cfg(struct ub_entity *uent, u32 uent_num) static void ub_config_eid(struct ub_entity *uent) { + if (is_p_device(uent)) + return; + if (is_ibus_controller(uent) && uent->ubc->cluster) return; @@ -273,6 +298,9 @@ int ub_setup_ent(struct ub_entity *uent) } ub_config_upi(uent); + ret = ub_fad_cfg_access_check(uent); + if (ret) + goto err_alloc; /* common setup */ ret = ub_eid_alloc(uent); @@ -401,10 +429,22 @@ void ub_start_ent(struct ub_entity *uent) if (!uent) return; - uent->match_driver = true; - ret = device_attach(&uent->dev); - if (ret < 0 && ret != -EPROBE_DEFER) - ub_err(uent, "device attach failed, ret=%d\n", ret); + if (is_ibus_controller(uent)) { + ret = ub_static_bus_instance_init(uent->ubc); + WARN_ON(ret); + } + + ret = ub_default_bus_instance_init(uent); + WARN_ON(ret); + + ub_create_sysfs_dev_files(uent); + + if (!((is_p_device(uent) || is_p_idevice(uent)) && is_dynamic(uent->bi))) { + uent->match_driver = true; + ret = device_attach(&uent->dev); + if (ret < 0 && ret != -EPROBE_DEFER) + ub_err(uent, "device attach failed, ret=%d\n", ret); + } if (is_primary(uent) && !is_p_device(uent)) { ret = ub_mue_enable_and_map(uent); @@ -460,6 +500,12 @@ void ub_stop_ent(struct ub_entity *uent) device_release_driver(&uent->dev); uent->match_driver = false; + ub_remove_sysfs_ent_files(uent); + + ub_default_bus_instance_uninit(uent); + + if (is_ibus_controller(uent)) + ub_static_bus_instance_uninit(uent->ubc); } EXPORT_SYMBOL_GPL(ub_stop_ent); @@ -954,6 +1000,8 @@ int ub_set_user_info(struct ub_entity *uent) goto cfg1; /* set dsteid to device */ + if (uent->bi) + eid = uent->bi->info.eid; ub_cfg_write_dword(uent, UB_UEID_0, eid); ub_cfg_write_dword(uent, UB_UEID_1, 0); ub_cfg_write_dword(uent, UB_UEID_2, 0); diff --git a/include/uapi/ub/ubus/ubus.h b/include/uapi/ub/ubus/ubus.h index c2de27e016db32a60649e77e8e65010ad45d6480..388a4cf043cee0b84d91e19e3b3ce99e48cf9c0c 100644 --- a/include/uapi/ub/ubus/ubus.h +++ b/include/uapi/ub/ubus/ubus.h @@ -28,4 +28,51 @@ */ #define UBUS_IOCTL_GET_API_VERSION _IO(UBUS_TYPE, 0) +#define GUID_SIZE 16 + +struct ubus_cmd_bi_create { +#define UBUS_INSTANCE_DYNAMIC_SERVER 2 +#define UBUS_INSTANCE_DYNAMIC_CLUSTER 3 + __u8 type; + __u16 upi; + __u32 eid; + __u8 guid[GUID_SIZE]; +}; + +struct ubus_cmd_bi_destroy { + __u8 guid[GUID_SIZE]; +}; + +struct ubus_cmd_bi_bind { + __u8 instance_guid[GUID_SIZE]; + __u8 dev_guid[GUID_SIZE]; +}; + +struct ubus_cmd_bi_unbind { + __u8 instance_guid[GUID_SIZE]; + __u8 dev_guid[GUID_SIZE]; +}; + +#define UBUS_IOCTL_HEADER_SIZE 8 + +enum ubus_bi_cmd { + UBUS_CMD_BI_CREATE = 0, + UBUS_CMD_BI_DESTROY, + UBUS_CMD_BI_BIND, + UBUS_CMD_BI_UNBIND, + UBUS_CMD_BI_NUM +}; + +struct ubus_ioctl_bus_instance { + __u32 argsz; /* size of *union*->structure */ + __u32 sub_cmd; + union { + struct ubus_cmd_bi_create create; + struct ubus_cmd_bi_destroy destroy; + struct ubus_cmd_bi_bind bind; + struct ubus_cmd_bi_unbind unbind; + }; +}; +#define UBUS_IOCTL_BUS_INSTANCE _IOWR(UBUS_TYPE, 1, struct ubus_ioctl_bus_instance) + #endif /* _UAPI_UB_UBUS_UBUS_H_ */ diff --git a/include/ub/ubfi/ubfi.h b/include/ub/ubfi/ubfi.h index 425d59102a1aa0cac68d3b53f9077a753dbefed9..5867311b10a63edded76a3ae34a2c201ff69f5aa 100644 --- a/include/ub/ubfi/ubfi.h +++ b/include/ub/ubfi/ubfi.h @@ -99,6 +99,20 @@ int ubrt_register_gsi(u32 hwirq, int trigger, int polarity, const char *name, */ void ubrt_unregister_gsi(u32 hwirq); +/** + * ub_update_msi_domain() - Update the MSI domain of UBC + * @dev: device with ub msi domain + * @bus_token: DOMAIN_BUS_UB_MSI + * + * Used when booting via ACPI. The MSI domain of the UB is reported by a + * platform device to the driver, and this function passes the MSI domain of the + * platform device to the UBC. + * + * Return: 0 if success or other if failed + */ +int ub_update_msi_domain(struct device *dev, + enum irq_domain_bus_token bus_token); + /** * ubrt_fwnode_set() - Associate a device's fwnode with an UBRT node * @index: Index of the UBRT node within its type. diff --git a/include/ub/ubus/ubus.h b/include/ub/ubus/ubus.h index e4c40c484b8b8fae4d1be64fdaf250e89251e8e2..b9112d15fc9ae3e9a46bba809df6b69ed41fc458 100644 --- a/include/ub/ubus/ubus.h +++ b/include/ub/ubus/ubus.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -140,10 +141,14 @@ struct ub_port { guid_t r_guid; struct kobject kobj; DECLARE_BITMAP(cna_maps, UB_MAX_CNA_NUM); + /* hotplug info */ + struct ub_slot *slot; /* cap cache */ DECLARE_BITMAP(cap_map, UB_PORT_CAP_NUM); + struct work_struct link_work; enum ub_link_state link_state; + u8 link_event; }; struct ue_map { @@ -170,6 +175,7 @@ struct ub_entity { unsigned int eid; unsigned short entity_idx; u32 uent_num; /* ub dev number */ + u32 fm_cna; struct mmio_zone zone[MAX_UB_RES_NUM]; unsigned int total_funcs; u32 token_id; @@ -203,6 +209,11 @@ struct ub_entity { u64 dma_mask; struct device_dma_parameters dma_parms; + /* entity user interface */ + struct bin_attribute *res_attr[MAX_UB_RES_NUM]; /* sysfs file for resources */ + /* sysfs file for WC mapping of resources */ + struct bin_attribute *res_attr_wc[MAX_UB_RES_NUM]; + /* UB interrupt info */ raw_spinlock_t usi_lock; unsigned int no_intr : 1; @@ -219,6 +230,9 @@ struct ub_entity { /* entity route info */ struct list_head cna_list; /* store distance for cna in route table */ + /* entity slot info */ + struct list_head slot_list; /* store slots under this dev */ + struct dev_message *message; /* UB entity TID */ @@ -230,6 +244,10 @@ struct ub_entity { u32 saved_config_space[24]; /* Config space saved at reset time */ /* entity bus instance info */ + struct mutex instance_lock; + struct list_head instance_node; + struct ub_bus_instance *bi; + u32 user_eid; struct ub_eu_table *eu_table; u32 support_feature; @@ -357,10 +375,36 @@ struct ub_bus_controller { struct list_head devs; struct ub_bus_controller_ops *ops; bool cluster; + struct ub_bus_instance *bi; + struct ub_bus_instance *cluster_bi; void *data; }; +struct ub_bus_instance_info { + u8 type; + u16 upi; + u32 eid : 20; + struct ub_guid guid; +}; + +struct ub_bus_instance { + bool registered; + bool destroy; + struct list_head node; + struct kref kref; + + struct ub_bus_instance_info info; + + struct ub_bus_controller *major; + + struct list_head uents; + struct mutex lock; +}; + +#define ub_bi_is_dynamic(bi) ((bi)->info.type == UBUS_INSTANCE_DYNAMIC_SERVER \ + || (bi)->info.type == UBUS_INSTANCE_DYNAMIC_CLUSTER) + static inline struct ub_driver *to_ub_driver(struct device_driver *drv) { return drv ? container_of(drv, struct ub_driver, driver) : NULL;