// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 /* Copyright (c) 2022 NVIDIA Corporation and Mellanox Technologies. All rights reserved */ #include #include #include #include #include #include #include #include #include #include #include "core.h" #include "../mlxfw/mlxfw.h" struct mlxsw_linecard_ini_file { __le16 size; union { u8 data[0]; struct { __be16 hw_revision; __be16 ini_version; u8 __dontcare[3]; u8 type; u8 name[20]; } format; }; }; struct mlxsw_linecard_types_info { struct mlxsw_linecard_ini_file **ini_files; unsigned int count; size_t data_size; char *data; }; #define MLXSW_LINECARD_STATUS_EVENT_TO (10 * MSEC_PER_SEC) static void mlxsw_linecard_status_event_to_schedule(struct mlxsw_linecard *linecard, enum mlxsw_linecard_status_event_type status_event_type) { cancel_delayed_work_sync(&linecard->status_event_to_dw); linecard->status_event_type_to = status_event_type; mlxsw_core_schedule_dw(&linecard->status_event_to_dw, msecs_to_jiffies(MLXSW_LINECARD_STATUS_EVENT_TO)); } static void mlxsw_linecard_status_event_done(struct mlxsw_linecard *linecard, enum mlxsw_linecard_status_event_type status_event_type) { if (linecard->status_event_type_to == status_event_type) cancel_delayed_work_sync(&linecard->status_event_to_dw); } static const char * mlxsw_linecard_types_lookup(struct mlxsw_linecards *linecards, u8 card_type) { struct mlxsw_linecard_types_info *types_info; struct mlxsw_linecard_ini_file *ini_file; int i; types_info = linecards->types_info; if (!types_info) return NULL; for (i = 0; i < types_info->count; i++) { ini_file = linecards->types_info->ini_files[i]; if (ini_file->format.type == card_type) return ini_file->format.name; } return NULL; } static const char *mlxsw_linecard_type_name(struct mlxsw_linecard *linecard) { struct mlxsw_core *mlxsw_core = linecard->linecards->mlxsw_core; char mddq_pl[MLXSW_REG_MDDQ_LEN]; int err; mlxsw_reg_mddq_slot_name_pack(mddq_pl, linecard->slot_index); err = mlxsw_reg_query(mlxsw_core, MLXSW_REG(mddq), mddq_pl); if (err) return ERR_PTR(err); mlxsw_reg_mddq_slot_name_unpack(mddq_pl, linecard->name); return linecard->name; } struct mlxsw_linecard_device_fw_info { struct mlxfw_dev mlxfw_dev; struct mlxsw_core *mlxsw_core; struct mlxsw_linecard *linecard; }; static int mlxsw_linecard_device_fw_component_query(struct mlxfw_dev *mlxfw_dev, u16 component_index, u32 *p_max_size, u8 *p_align_bits, u16 *p_max_write_size) { struct mlxsw_linecard_device_fw_info *info = container_of(mlxfw_dev, struct mlxsw_linecard_device_fw_info, mlxfw_dev); struct mlxsw_linecard *linecard = info->linecard; struct mlxsw_core *mlxsw_core = info->mlxsw_core; char mddt_pl[MLXSW_REG_MDDT_LEN]; char *mcqi_pl; int err; mlxsw_reg_mddt_pack(mddt_pl, linecard->slot_index, linecard->device.index, MLXSW_REG_MDDT_METHOD_QUERY, MLXSW_REG(mcqi), &mcqi_pl); mlxsw_reg_mcqi_pack(mcqi_pl, component_index); err = mlxsw_reg_query(mlxsw_core, MLXSW_REG(mddt), mddt_pl); if (err) return err; mlxsw_reg_mcqi_unpack(mcqi_pl, p_max_size, p_align_bits, p_max_write_size); *p_align_bits = max_t(u8, *p_align_bits, 2); *p_max_write_size = min_t(u16, *p_max_write_size, MLXSW_REG_MCDA_MAX_DATA_LEN); return 0; } static int mlxsw_linecard_device_fw_fsm_lock(struct mlxfw_dev *mlxfw_dev, u32 *fwhandle) { struct mlxsw_linecard_device_fw_info *info = container_of(mlxfw_dev, struct mlxsw_linecard_device_fw_info, mlxfw_dev); struct mlxsw_linecard *linecard = info->linecard; struct mlxsw_core *mlxsw_core = info->mlxsw_core; char mddt_pl[MLXSW_REG_MDDT_LEN]; u8 control_state; char *mcc_pl; int err; mlxsw_reg_mddt_pack(mddt_pl, linecard->slot_index, linecard->device.index, MLXSW_REG_MDDT_METHOD_QUERY, MLXSW_REG(mcc), &mcc_pl); mlxsw_reg_mcc_pack(mcc_pl, 0, 0, 0, 0); err = mlxsw_reg_query(mlxsw_core, MLXSW_REG(mddt), mddt_pl); if (err) return err; mlxsw_reg_mcc_unpack(mcc_pl, fwhandle, NULL, &control_state); if (control_state != MLXFW_FSM_STATE_IDLE) return -EBUSY; mlxsw_reg_mddt_pack(mddt_pl, linecard->slot_index, linecard->device.index, MLXSW_REG_MDDT_METHOD_WRITE, MLXSW_REG(mcc), &mcc_pl); mlxsw_reg_mcc_pack(mcc_pl, MLXSW_REG_MCC_INSTRUCTION_LOCK_UPDATE_HANDLE, 0, *fwhandle, 0); return mlxsw_reg_write(mlxsw_core, MLXSW_REG(mddt), mddt_pl); } static int mlxsw_linecard_device_fw_fsm_component_update(struct mlxfw_dev *mlxfw_dev, u32 fwhandle, u16 component_index, u32 component_size) { struct mlxsw_linecard_device_fw_info *info = container_of(mlxfw_dev, struct mlxsw_linecard_device_fw_info, mlxfw_dev); struct mlxsw_linecard *linecard = info->linecard; struct mlxsw_core *mlxsw_core = info->mlxsw_core; char mddt_pl[MLXSW_REG_MDDT_LEN]; char *mcc_pl; mlxsw_reg_mddt_pack(mddt_pl, linecard->slot_index, linecard->device.index, MLXSW_REG_MDDT_METHOD_WRITE, MLXSW_REG(mcc), &mcc_pl); mlxsw_reg_mcc_pack(mcc_pl, MLXSW_REG_MCC_INSTRUCTION_UPDATE_COMPONENT, component_index, fwhandle, component_size); return mlxsw_reg_write(mlxsw_core, MLXSW_REG(mddt), mddt_pl); } static int mlxsw_linecard_device_fw_fsm_block_download(struct mlxfw_dev *mlxfw_dev, u32 fwhandle, u8 *data, u16 size, u32 offset) { struct mlxsw_linecard_device_fw_info *info = container_of(mlxfw_dev, struct mlxsw_linecard_device_fw_info, mlxfw_dev); struct mlxsw_linecard *linecard = info->linecard; struct mlxsw_core *mlxsw_core = info->mlxsw_core; char mddt_pl[MLXSW_REG_MDDT_LEN]; char *mcda_pl; mlxsw_reg_mddt_pack(mddt_pl, linecard->slot_index, linecard->device.index, MLXSW_REG_MDDT_METHOD_WRITE, MLXSW_REG(mcda), &mcda_pl); mlxsw_reg_mcda_pack(mcda_pl, fwhandle, offset, size, data); return mlxsw_reg_write(mlxsw_core, MLXSW_REG(mddt), mddt_pl); } static int mlxsw_linecard_device_fw_fsm_component_verify(struct mlxfw_dev *mlxfw_dev, u32 fwhandle, u16 component_index) { struct mlxsw_linecard_device_fw_info *info = container_of(mlxfw_dev, struct mlxsw_linecard_device_fw_info, mlxfw_dev); struct mlxsw_linecard *linecard = info->linecard; struct mlxsw_core *mlxsw_core = info->mlxsw_core; char mddt_pl[MLXSW_REG_MDDT_LEN]; char *mcc_pl; mlxsw_reg_mddt_pack(mddt_pl, linecard->slot_index, linecard->device.index, MLXSW_REG_MDDT_METHOD_WRITE, MLXSW_REG(mcc), &mcc_pl); mlxsw_reg_mcc_pack(mcc_pl, MLXSW_REG_MCC_INSTRUCTION_VERIFY_COMPONENT, component_index, fwhandle, 0); return mlxsw_reg_write(mlxsw_core, MLXSW_REG(mddt), mddt_pl); } static int mlxsw_linecard_device_fw_fsm_activate(struct mlxfw_dev *mlxfw_dev, u32 fwhandle) { struct mlxsw_linecard_device_fw_info *info = container_of(mlxfw_dev, struct mlxsw_linecard_device_fw_info, mlxfw_dev); struct mlxsw_linecard *linecard = info->linecard; struct mlxsw_core *mlxsw_core = info->mlxsw_core; char mddt_pl[MLXSW_REG_MDDT_LEN]; char *mcc_pl; mlxsw_reg_mddt_pack(mddt_pl, linecard->slot_index, linecard->device.index, MLXSW_REG_MDDT_METHOD_WRITE, MLXSW_REG(mcc), &mcc_pl); mlxsw_reg_mcc_pack(mcc_pl, MLXSW_REG_MCC_INSTRUCTION_ACTIVATE, 0, fwhandle, 0); return mlxsw_reg_write(mlxsw_core, MLXSW_REG(mddt), mddt_pl); } static int mlxsw_linecard_device_fw_fsm_query_state(struct mlxfw_dev *mlxfw_dev, u32 fwhandle, enum mlxfw_fsm_state *fsm_state, enum mlxfw_fsm_state_err *fsm_state_err) { struct mlxsw_linecard_device_fw_info *info = container_of(mlxfw_dev, struct mlxsw_linecard_device_fw_info, mlxfw_dev); struct mlxsw_linecard *linecard = info->linecard; struct mlxsw_core *mlxsw_core = info->mlxsw_core; char mddt_pl[MLXSW_REG_MDDT_LEN]; u8 control_state; u8 error_code; char *mcc_pl; int err; mlxsw_reg_mddt_pack(mddt_pl, linecard->slot_index, linecard->device.index, MLXSW_REG_MDDT_METHOD_QUERY, MLXSW_REG(mcc), &mcc_pl); mlxsw_reg_mcc_pack(mcc_pl, 0, 0, fwhandle, 0); err = mlxsw_reg_query(mlxsw_core, MLXSW_REG(mddt), mddt_pl); if (err) return err; mlxsw_reg_mcc_unpack(mcc_pl, NULL, &error_code, &control_state); *fsm_state = control_state; *fsm_state_err = min_t(enum mlxfw_fsm_state_err, error_code, MLXFW_FSM_STATE_ERR_MAX); return 0; } static void mlxsw_linecard_device_fw_fsm_cancel(struct mlxfw_dev *mlxfw_dev, u32 fwhandle) { struct mlxsw_linecard_device_fw_info *info = container_of(mlxfw_dev, struct mlxsw_linecard_device_fw_info, mlxfw_dev); struct mlxsw_linecard *linecard = info->linecard; struct mlxsw_core *mlxsw_core = info->mlxsw_core; char mddt_pl[MLXSW_REG_MDDT_LEN]; char *mcc_pl; mlxsw_reg_mddt_pack(mddt_pl, linecard->slot_index, linecard->device.index, MLXSW_REG_MDDT_METHOD_WRITE, MLXSW_REG(mcc), &mcc_pl); mlxsw_reg_mcc_pack(mcc_pl, MLXSW_REG_MCC_INSTRUCTION_CANCEL, 0, fwhandle, 0); mlxsw_reg_write(mlxsw_core, MLXSW_REG(mddt), mddt_pl); } static void mlxsw_linecard_device_fw_fsm_release(struct mlxfw_dev *mlxfw_dev, u32 fwhandle) { struct mlxsw_linecard_device_fw_info *info = container_of(mlxfw_dev, struct mlxsw_linecard_device_fw_info, mlxfw_dev); struct mlxsw_linecard *linecard = info->linecard; struct mlxsw_core *mlxsw_core = info->mlxsw_core; char mddt_pl[MLXSW_REG_MDDT_LEN]; char *mcc_pl; mlxsw_reg_mddt_pack(mddt_pl, linecard->slot_index, linecard->device.index, MLXSW_REG_MDDT_METHOD_WRITE, MLXSW_REG(mcc), &mcc_pl); mlxsw_reg_mcc_pack(mcc_pl, MLXSW_REG_MCC_INSTRUCTION_RELEASE_UPDATE_HANDLE, 0, fwhandle, 0); mlxsw_reg_write(mlxsw_core, MLXSW_REG(mddt), mddt_pl); } static const struct mlxfw_dev_ops mlxsw_linecard_device_dev_ops = { .component_query = mlxsw_linecard_device_fw_component_query, .fsm_lock = mlxsw_linecard_device_fw_fsm_lock, .fsm_component_update = mlxsw_linecard_device_fw_fsm_component_update, .fsm_block_download = mlxsw_linecard_device_fw_fsm_block_download, .fsm_component_verify = mlxsw_linecard_device_fw_fsm_component_verify, .fsm_activate = mlxsw_linecard_device_fw_fsm_activate, .fsm_query_state = mlxsw_linecard_device_fw_fsm_query_state, .fsm_cancel = mlxsw_linecard_device_fw_fsm_cancel, .fsm_release = mlxsw_linecard_device_fw_fsm_release, }; int mlxsw_linecard_flash_update(struct devlink *linecard_devlink, struct mlxsw_linecard *linecard, const struct firmware *firmware, struct netlink_ext_ack *extack) { struct mlxsw_core *mlxsw_core = linecard->linecards->mlxsw_core; struct mlxsw_linecard_device_fw_info info = { .mlxfw_dev = { .ops = &mlxsw_linecard_device_dev_ops, .psid = linecard->device.info.psid, .psid_size = strlen(linecard->device.info.psid), .devlink = linecard_devlink, }, .mlxsw_core = mlxsw_core, .linecard = linecard, }; int err; mutex_lock(&linecard->lock); if (!linecard->active) { NL_SET_ERR_MSG_MOD(extack, "Only active line cards can be flashed"); err = -EINVAL; goto unlock; } err = mlxsw_core_fw_flash(mlxsw_core, &info.mlxfw_dev, firmware, extack); unlock: mutex_unlock(&linecard->lock); return err; } static int mlxsw_linecard_device_psid_get(struct mlxsw_linecard *linecard, u8 device_index, char *psid) { struct mlxsw_core *mlxsw_core = linecard->linecards->mlxsw_core; char mddt_pl[MLXSW_REG_MDDT_LEN]; char *mgir_pl; int err; mlxsw_reg_mddt_pack(mddt_pl, linecard->slot_index, device_index, MLXSW_REG_MDDT_METHOD_QUERY, MLXSW_REG(mgir), &mgir_pl); mlxsw_reg_mgir_pack(mgir_pl); err = mlxsw_reg_query(mlxsw_core, MLXSW_REG(mddt), mddt_pl); if (err) return err; mlxsw_reg_mgir_fw_info_psid_memcpy_from(mgir_pl, psid); return 0; } static int mlxsw_linecard_device_info_update(struct mlxsw_linecard *linecard) { struct mlxsw_core *mlxsw_core = linecard->linecards->mlxsw_core; bool flashable_found = false; u8 msg_seq = 0; do { struct mlxsw_linecard_device_info info; char mddq_pl[MLXSW_REG_MDDQ_LEN]; bool flash_owner; bool data_valid; u8 device_index; int err; mlxsw_reg_mddq_device_info_pack(mddq_pl, linecard->slot_index, msg_seq); err = mlxsw_reg_query(mlxsw_core, MLXSW_REG(mddq), mddq_pl); if (err) return err; mlxsw_reg_mddq_device_info_unpack(mddq_pl, &msg_seq, &data_valid, &flash_owner, &device_index, &info.fw_major, &info.fw_minor, &info.fw_sub_minor); if (!data_valid) break; if (!flash_owner) /* We care only about flashable ones. */ continue; if (flashable_found) { dev_warn_once(linecard->linecards->bus_info->dev, "linecard %u: More flashable devices present, exposing only the first one\n", linecard->slot_index); return 0; } err = mlxsw_linecard_device_psid_get(linecard, device_index, info.psid); if (err) return err; linecard->device.info = info; linecard->device.index = device_index; flashable_found = true; } while (msg_seq); return 0; } static void mlxsw_linecard_provision_fail(struct mlxsw_linecard *linecard) { linecard->provisioned = false; linecard->ready = false; linecard->active = false; devlink_linecard_provision_fail(linecard->devlink_linecard); } struct mlxsw_linecards_event_ops_item { struct list_head list; const struct mlxsw_linecards_event_ops *event_ops; void *priv; }; static void mlxsw_linecard_event_op_call(struct mlxsw_linecard *linecard, mlxsw_linecards_event_op_t *op, void *priv) { struct mlxsw_core *mlxsw_core = linecard->linecards->mlxsw_core; if (!op) return; op(mlxsw_core, linecard->slot_index, priv); } static void mlxsw_linecard_active_ops_call(struct mlxsw_linecard *linecard) { struct mlxsw_linecards *linecards = linecard->linecards; struct mlxsw_linecards_event_ops_item *item; mutex_lock(&linecards->event_ops_list_lock); list_for_each_entry(item, &linecards->event_ops_list, list) mlxsw_linecard_event_op_call(linecard, item->event_ops->got_active, item->priv); mutex_unlock(&linecards->event_ops_list_lock); } static void mlxsw_linecard_inactive_ops_call(struct mlxsw_linecard *linecard) { struct mlxsw_linecards *linecards = linecard->linecards; struct mlxsw_linecards_event_ops_item *item; mutex_lock(&linecards->event_ops_list_lock); list_for_each_entry(item, &linecards->event_ops_list, list) mlxsw_linecard_event_op_call(linecard, item->event_ops->got_inactive, item->priv); mutex_unlock(&linecards->event_ops_list_lock); } static void mlxsw_linecards_event_ops_register_call(struct mlxsw_linecards *linecards, const struct mlxsw_linecards_event_ops_item *item) { struct mlxsw_linecard *linecard; int i; for (i = 0; i < linecards->count; i++) { linecard = mlxsw_linecard_get(linecards, i + 1); mutex_lock(&linecard->lock); if (linecard->active) mlxsw_linecard_event_op_call(linecard, item->event_ops->got_active, item->priv); mutex_unlock(&linecard->lock); } } static void mlxsw_linecards_event_ops_unregister_call(struct mlxsw_linecards *linecards, const struct mlxsw_linecards_event_ops_item *item) { struct mlxsw_linecard *linecard; int i; for (i = 0; i < linecards->count; i++) { linecard = mlxsw_linecard_get(linecards, i + 1); mutex_lock(&linecard->lock); if (linecard->active) mlxsw_linecard_event_op_call(linecard, item->event_ops->got_inactive, item->priv); mutex_unlock(&linecard->lock); } } int mlxsw_linecards_event_ops_register(struct mlxsw_core *mlxsw_core, struct mlxsw_linecards_event_ops *ops, void *priv) { struct mlxsw_linecards *linecards = mlxsw_core_linecards(mlxsw_core); struct mlxsw_linecards_event_ops_item *item; if (!linecards) return 0; item = kzalloc(sizeof(*item), GFP_KERNEL); if (!item) return -ENOMEM; item->event_ops = ops; item->priv = priv; mutex_lock(&linecards->event_ops_list_lock); list_add_tail(&item->list, &linecards->event_ops_list); mutex_unlock(&linecards->event_ops_list_lock); mlxsw_linecards_event_ops_register_call(linecards, item); return 0; } EXPORT_SYMBOL(mlxsw_linecards_event_ops_register); void mlxsw_linecards_event_ops_unregister(struct mlxsw_core *mlxsw_core, struct mlxsw_linecards_event_ops *ops, void *priv) { struct mlxsw_linecards *linecards = mlxsw_core_linecards(mlxsw_core); struct mlxsw_linecards_event_ops_item *item, *tmp; bool found = false; if (!linecards) return; mutex_lock(&linecards->event_ops_list_lock); list_for_each_entry_safe(item, tmp, &linecards->event_ops_list, list) { if (item->event_ops == ops && item->priv == priv) { list_del(&item->list); found = true; break; } } mutex_unlock(&linecards->event_ops_list_lock); if (!found) return; mlxsw_linecards_event_ops_unregister_call(linecards, item); kfree(item); } EXPORT_SYMBOL(mlxsw_linecards_event_ops_unregister); int mlxsw_linecard_devlink_info_get(struct mlxsw_linecard *linecard, struct devlink_info_req *req, struct netlink_ext_ack *extack) { char buf[32]; int err; mutex_lock(&linecard->lock); if (WARN_ON(!linecard->provisioned)) { err = -EOPNOTSUPP; goto unlock; } sprintf(buf, "%d", linecard->hw_revision); err = devlink_info_version_fixed_put(req, "hw.revision", buf); if (err) goto unlock; sprintf(buf, "%d", linecard->ini_version); err = devlink_info_version_running_put(req, "ini.version", buf); if (err) goto unlock; if (linecard->active) { struct mlxsw_linecard_device_info *info = &linecard->device.info; err = devlink_info_version_fixed_put(req, DEVLINK_INFO_VERSION_GENERIC_FW_PSID, info->psid); sprintf(buf, "%u.%u.%u", info->fw_major, info->fw_minor, info->fw_sub_minor); err = devlink_info_version_running_put(req, DEVLINK_INFO_VERSION_GENERIC_FW, buf); if (err) goto unlock; } unlock: mutex_unlock(&linecard->lock); return err; } static int mlxsw_linecard_provision_set(struct mlxsw_linecard *linecard, u8 card_type, u16 hw_revision, u16 ini_version) { struct mlxsw_linecards *linecards = linecard->linecards; const char *type; int err; type = mlxsw_linecard_types_lookup(linecards, card_type); mlxsw_linecard_status_event_done(linecard, MLXSW_LINECARD_STATUS_EVENT_TYPE_PROVISION); if (!type) { /* It is possible for a line card to be provisioned before * driver initialization. Due to a missing INI bundle file * or an outdated one, the queried card's type might not * be recognized by the driver. In this case, try to query * the card's name from the device. */ type = mlxsw_linecard_type_name(linecard); if (IS_ERR(type)) { mlxsw_linecard_provision_fail(linecard); return PTR_ERR(type); } } linecard->provisioned = true; linecard->hw_revision = hw_revision; linecard->ini_version = ini_version; err = mlxsw_linecard_bdev_add(linecard); if (err) { linecard->provisioned = false; mlxsw_linecard_provision_fail(linecard); return err; } devlink_linecard_provision_set(linecard->devlink_linecard, type); return 0; } static void mlxsw_linecard_provision_clear(struct mlxsw_linecard *linecard) { mlxsw_linecard_status_event_done(linecard, MLXSW_LINECARD_STATUS_EVENT_TYPE_UNPROVISION); mlxsw_linecard_bdev_del(linecard); linecard->provisioned = false; devlink_linecard_provision_clear(linecard->devlink_linecard); } static int mlxsw_linecard_ready_set(struct mlxsw_linecard *linecard) { struct mlxsw_core *mlxsw_core = linecard->linecards->mlxsw_core; char mddc_pl[MLXSW_REG_MDDC_LEN]; int err; err = mlxsw_linecard_device_info_update(linecard); if (err) return err; mlxsw_reg_mddc_pack(mddc_pl, linecard->slot_index, false, true); err = mlxsw_reg_write(mlxsw_core, MLXSW_REG(mddc), mddc_pl); if (err) return err; linecard->ready = true; return 0; } static int mlxsw_linecard_ready_clear(struct mlxsw_linecard *linecard) { struct mlxsw_core *mlxsw_core = linecard->linecards->mlxsw_core; char mddc_pl[MLXSW_REG_MDDC_LEN]; int err; mlxsw_reg_mddc_pack(mddc_pl, linecard->slot_index, false, false); err = mlxsw_reg_write(mlxsw_core, MLXSW_REG(mddc), mddc_pl); if (err) return err; linecard->ready = false; return 0; } static void mlxsw_linecard_active_set(struct mlxsw_linecard *linecard) { mlxsw_linecard_active_ops_call(linecard); linecard->active = true; devlink_linecard_activate(linecard->devlink_linecard); } static void mlxsw_linecard_active_clear(struct mlxsw_linecard *linecard) { mlxsw_linecard_inactive_ops_call(linecard); linecard->active = false; devlink_linecard_deactivate(linecard->devlink_linecard); } static int mlxsw_linecard_status_process(struct mlxsw_linecards *linecards, struct mlxsw_linecard *linecard, const char *mddq_pl) { enum mlxsw_reg_mddq_slot_info_ready ready; bool provisioned, sr_valid, active; u16 ini_version, hw_revision; u8 slot_index, card_type; int err = 0; mlxsw_reg_mddq_slot_info_unpack(mddq_pl, &slot_index, &provisioned, &sr_valid, &ready, &active, &hw_revision, &ini_version, &card_type); if (linecard) { if (WARN_ON(slot_index != linecard->slot_index)) return -EINVAL; } else { if (WARN_ON(slot_index > linecards->count)) return -EINVAL; linecard = mlxsw_linecard_get(linecards, slot_index); } mutex_lock(&linecard->lock); if (provisioned && linecard->provisioned != provisioned) { err = mlxsw_linecard_provision_set(linecard, card_type, hw_revision, ini_version); if (err) goto out; } if (ready == MLXSW_REG_MDDQ_SLOT_INFO_READY_READY && !linecard->ready) { err = mlxsw_linecard_ready_set(linecard); if (err) goto out; } if (active && linecard->active != active) mlxsw_linecard_active_set(linecard); if (!active && linecard->active != active) mlxsw_linecard_active_clear(linecard); if (ready != MLXSW_REG_MDDQ_SLOT_INFO_READY_READY && linecard->ready) { err = mlxsw_linecard_ready_clear(linecard); if (err) goto out; } if (!provisioned && linecard->provisioned != provisioned) mlxsw_linecard_provision_clear(linecard); out: mutex_unlock(&linecard->lock); return err; } static int mlxsw_linecard_status_get_and_process(struct mlxsw_core *mlxsw_core, struct mlxsw_linecards *linecards, struct mlxsw_linecard *linecard) { char mddq_pl[MLXSW_REG_MDDQ_LEN]; int err; mlxsw_reg_mddq_slot_info_pack(mddq_pl, linecard->slot_index, false); err = mlxsw_reg_query(mlxsw_core, MLXSW_REG(mddq), mddq_pl); if (err) return err; return mlxsw_linecard_status_process(linecards, linecard, mddq_pl); } static void mlxsw_linecards_irq_event_handler(struct mlxsw_core *mlxsw_core) { struct mlxsw_linecards *linecards = mlxsw_core_linecards(mlxsw_core); int i; /* Handle change of line card active state. */ for (i = 0; i < linecards->count; i++) { struct mlxsw_linecard *linecard = mlxsw_linecard_get(linecards, i + 1); mlxsw_linecard_status_get_and_process(mlxsw_core, linecards, linecard); } } static const char * const mlxsw_linecard_status_event_type_name[] = { [MLXSW_LINECARD_STATUS_EVENT_TYPE_PROVISION] = "provision", [MLXSW_LINECARD_STATUS_EVENT_TYPE_UNPROVISION] = "unprovision", }; static void mlxsw_linecard_status_event_to_work(struct work_struct *work) { struct mlxsw_linecard *linecard = container_of(work, struct mlxsw_linecard, status_event_to_dw.work); mutex_lock(&linecard->lock); dev_err(linecard->linecards->bus_info->dev, "linecard %u: Timeout reached waiting on %s status event", linecard->slot_index, mlxsw_linecard_status_event_type_name[linecard->status_event_type_to]); mlxsw_linecard_provision_fail(linecard); mutex_unlock(&linecard->lock); } static int __mlxsw_linecard_fix_fsm_state(struct mlxsw_linecard *linecard) { dev_info(linecard->linecards->bus_info->dev, "linecard %u: Clearing FSM state error", linecard->slot_index); mlxsw_reg_mbct_pack(linecard->mbct_pl, linecard->slot_index, MLXSW_REG_MBCT_OP_CLEAR_ERRORS, false); return mlxsw_reg_write(linecard->linecards->mlxsw_core, MLXSW_REG(mbct), linecard->mbct_pl); } static int mlxsw_linecard_fix_fsm_state(struct mlxsw_linecard *linecard, enum mlxsw_reg_mbct_fsm_state fsm_state) { if (fsm_state != MLXSW_REG_MBCT_FSM_STATE_ERROR) return 0; return __mlxsw_linecard_fix_fsm_state(linecard); } static int mlxsw_linecard_query_ini_status(struct mlxsw_linecard *linecard, enum mlxsw_reg_mbct_status *status, enum mlxsw_reg_mbct_fsm_state *fsm_state, struct netlink_ext_ack *extack) { int err; mlxsw_reg_mbct_pack(linecard->mbct_pl, linecard->slot_index, MLXSW_REG_MBCT_OP_QUERY_STATUS, false); err = mlxsw_reg_query(linecard->linecards->mlxsw_core, MLXSW_REG(mbct), linecard->mbct_pl); if (err) { NL_SET_ERR_MSG_MOD(extack, "Failed to query linecard INI status"); return err; } mlxsw_reg_mbct_unpack(linecard->mbct_pl, NULL, status, fsm_state); return err; } static int mlxsw_linecard_ini_transfer(struct mlxsw_core *mlxsw_core, struct mlxsw_linecard *linecard, const struct mlxsw_linecard_ini_file *ini_file, struct netlink_ext_ack *extack) { enum mlxsw_reg_mbct_fsm_state fsm_state; enum mlxsw_reg_mbct_status status; size_t size_left; const u8 *data; int err; size_left = le16_to_cpu(ini_file->size); data = ini_file->data; while (size_left) { size_t data_size = MLXSW_REG_MBCT_DATA_LEN; bool is_last = false; if (size_left <= MLXSW_REG_MBCT_DATA_LEN) { data_size = size_left; is_last = true; } mlxsw_reg_mbct_pack(linecard->mbct_pl, linecard->slot_index, MLXSW_REG_MBCT_OP_DATA_TRANSFER, false); mlxsw_reg_mbct_dt_pack(linecard->mbct_pl, data_size, is_last, data); err = mlxsw_reg_write(mlxsw_core, MLXSW_REG(mbct), linecard->mbct_pl); if (err) { NL_SET_ERR_MSG_MOD(extack, "Failed to issue linecard INI data transfer"); return err; } mlxsw_reg_mbct_unpack(linecard->mbct_pl, NULL, &status, &fsm_state); if ((!is_last && status != MLXSW_REG_MBCT_STATUS_PART_DATA) || (is_last && status != MLXSW_REG_MBCT_STATUS_LAST_DATA)) { NL_SET_ERR_MSG_MOD(extack, "Failed to transfer linecard INI data"); mlxsw_linecard_fix_fsm_state(linecard, fsm_state); return -EINVAL; } size_left -= data_size; data += data_size; } return 0; } static int mlxsw_linecard_ini_erase(struct mlxsw_core *mlxsw_core, struct mlxsw_linecard *linecard, struct netlink_ext_ack *extack) { enum mlxsw_reg_mbct_fsm_state fsm_state; enum mlxsw_reg_mbct_status status; int err; mlxsw_reg_mbct_pack(linecard->mbct_pl, linecard->slot_index, MLXSW_REG_MBCT_OP_ERASE_INI_IMAGE, false); err = mlxsw_reg_write(mlxsw_core, MLXSW_REG(mbct), linecard->mbct_pl); if (err) { NL_SET_ERR_MSG_MOD(extack, "Failed to issue linecard INI erase"); return err; } mlxsw_reg_mbct_unpack(linecard->mbct_pl, NULL, &status, &fsm_state); switch (status) { case MLXSW_REG_MBCT_STATUS_ERASE_COMPLETE: break; default: /* Should not happen */ fallthrough; case MLXSW_REG_MBCT_STATUS_ERASE_FAILED: NL_SET_ERR_MSG_MOD(extack, "Failed to erase linecard INI"); goto fix_fsm_err_out; case MLXSW_REG_MBCT_STATUS_ERROR_INI_IN_USE: NL_SET_ERR_MSG_MOD(extack, "Failed to erase linecard INI while being used"); goto fix_fsm_err_out; } return 0; fix_fsm_err_out: mlxsw_linecard_fix_fsm_state(linecard, fsm_state); return -EINVAL; } static void mlxsw_linecard_bct_process(struct mlxsw_core *mlxsw_core, const char *mbct_pl) { struct mlxsw_linecards *linecards = mlxsw_core_linecards(mlxsw_core); enum mlxsw_reg_mbct_fsm_state fsm_state; enum mlxsw_reg_mbct_status status; struct mlxsw_linecard *linecard; u8 slot_index; mlxsw_reg_mbct_unpack(mbct_pl, &slot_index, &status, &fsm_state); if (WARN_ON(slot_index > linecards->count)) return; linecard = mlxsw_linecard_get(linecards, slot_index); mutex_lock(&linecard->lock); if (status == MLXSW_REG_MBCT_STATUS_ACTIVATION_FAILED) { dev_err(linecards->bus_info->dev, "linecard %u: Failed to activate INI", linecard->slot_index); goto fix_fsm_out; } mutex_unlock(&linecard->lock); return; fix_fsm_out: mlxsw_linecard_fix_fsm_state(linecard, fsm_state); mlxsw_linecard_provision_fail(linecard); mutex_unlock(&linecard->lock); } static int mlxsw_linecard_ini_activate(struct mlxsw_core *mlxsw_core, struct mlxsw_linecard *linecard, struct netlink_ext_ack *extack) { enum mlxsw_reg_mbct_fsm_state fsm_state; enum mlxsw_reg_mbct_status status; int err; mlxsw_reg_mbct_pack(linecard->mbct_pl, linecard->slot_index, MLXSW_REG_MBCT_OP_ACTIVATE, true); err = mlxsw_reg_write(mlxsw_core, MLXSW_REG(mbct), linecard->mbct_pl); if (err) { NL_SET_ERR_MSG_MOD(extack, "Failed to issue linecard INI activation"); return err; } mlxsw_reg_mbct_unpack(linecard->mbct_pl, NULL, &status, &fsm_state); if (status == MLXSW_REG_MBCT_STATUS_ACTIVATION_FAILED) { NL_SET_ERR_MSG_MOD(extack, "Failed to activate linecard INI"); goto fix_fsm_err_out; } return 0; fix_fsm_err_out: mlxsw_linecard_fix_fsm_state(linecard, fsm_state); return -EINVAL; } #define MLXSW_LINECARD_INI_WAIT_RETRIES 10 #define MLXSW_LINECARD_INI_WAIT_MS 500 static int mlxsw_linecard_ini_in_use_wait(struct mlxsw_core *mlxsw_core, struct mlxsw_linecard *linecard, struct netlink_ext_ack *extack) { enum mlxsw_reg_mbct_fsm_state fsm_state; enum mlxsw_reg_mbct_status status; unsigned int ini_wait_retries = 0; int err; query_ini_status: err = mlxsw_linecard_query_ini_status(linecard, &status, &fsm_state, extack); if (err) return err; switch (fsm_state) { case MLXSW_REG_MBCT_FSM_STATE_INI_IN_USE: if (ini_wait_retries++ > MLXSW_LINECARD_INI_WAIT_RETRIES) { NL_SET_ERR_MSG_MOD(extack, "Failed to wait for linecard INI to be unused"); return -EINVAL; } mdelay(MLXSW_LINECARD_INI_WAIT_MS); goto query_ini_status; default: break; } return 0; } static bool mlxsw_linecard_port_selector(void *priv, u16 local_port) { struct mlxsw_linecard *linecard = priv; struct mlxsw_core *mlxsw_core; mlxsw_core = linecard->linecards->mlxsw_core; return linecard == mlxsw_core_port_linecard_get(mlxsw_core, local_port); } static int mlxsw_linecard_provision(struct devlink_linecard *devlink_linecard, void *priv, const char *type, const void *type_priv, struct netlink_ext_ack *extack) { const struct mlxsw_linecard_ini_file *ini_file = type_priv; struct mlxsw_linecard *linecard = priv; struct mlxsw_core *mlxsw_core; int err; mutex_lock(&linecard->lock); mlxsw_core = linecard->linecards->mlxsw_core; err = mlxsw_linecard_ini_erase(mlxsw_core, linecard, extack); if (err) goto err_out; err = mlxsw_linecard_ini_transfer(mlxsw_core, linecard, ini_file, extack); if (err) goto err_out; mlxsw_linecard_status_event_to_schedule(linecard, MLXSW_LINECARD_STATUS_EVENT_TYPE_PROVISION); err = mlxsw_linecard_ini_activate(mlxsw_core, linecard, extack); if (err) goto err_out; goto out; err_out: mlxsw_linecard_provision_fail(linecard); out: mutex_unlock(&linecard->lock); return err; } static int mlxsw_linecard_unprovision(struct devlink_linecard *devlink_linecard, void *priv, struct netlink_ext_ack *extack) { struct mlxsw_linecard *linecard = priv; struct mlxsw_core *mlxsw_core; int err; mutex_lock(&linecard->lock); mlxsw_core = linecard->linecards->mlxsw_core; mlxsw_core_ports_remove_selected(mlxsw_core, mlxsw_linecard_port_selector, linecard); err = mlxsw_linecard_ini_in_use_wait(mlxsw_core, linecard, extack); if (err) goto err_out; mlxsw_linecard_status_event_to_schedule(linecard, MLXSW_LINECARD_STATUS_EVENT_TYPE_UNPROVISION); err = mlxsw_linecard_ini_erase(mlxsw_core, linecard, extack); if (err) goto err_out; goto out; err_out: mlxsw_linecard_provision_fail(linecard); out: mutex_unlock(&linecard->lock); return err; } static bool mlxsw_linecard_same_provision(struct devlink_linecard *devlink_linecard, void *priv, const char *type, const void *type_priv) { const struct mlxsw_linecard_ini_file *ini_file = type_priv; struct mlxsw_linecard *linecard = priv; bool ret; mutex_lock(&linecard->lock); ret = linecard->hw_revision == be16_to_cpu(ini_file->format.hw_revision) && linecard->ini_version == be16_to_cpu(ini_file->format.ini_version); mutex_unlock(&linecard->lock); return ret; } static unsigned int mlxsw_linecard_types_count(struct devlink_linecard *devlink_linecard, void *priv) { struct mlxsw_linecard *linecard = priv; return linecard->linecards->types_info ? linecard->linecards->types_info->count : 0; } static void mlxsw_linecard_types_get(struct devlink_linecard *devlink_linecard, void *priv, unsigned int index, const char **type, const void **type_priv) { struct mlxsw_linecard_types_info *types_info; struct mlxsw_linecard_ini_file *ini_file; struct mlxsw_linecard *linecard = priv; types_info = linecard->linecards->types_info; if (WARN_ON_ONCE(!types_info)) return; ini_file = types_info->ini_files[index]; *type = ini_file->format.name; *type_priv = ini_file; } static const struct devlink_linecard_ops mlxsw_linecard_ops = { .provision = mlxsw_linecard_provision, .unprovision = mlxsw_linecard_unprovision, .same_provision = mlxsw_linecard_same_provision, .types_count = mlxsw_linecard_types_count, .types_get = mlxsw_linecard_types_get, }; struct mlxsw_linecard_status_event { struct mlxsw_core *mlxsw_core; char mddq_pl[MLXSW_REG_MDDQ_LEN]; struct work_struct work; }; static void mlxsw_linecard_status_event_work(struct work_struct *work) { struct mlxsw_linecard_status_event *event; struct mlxsw_linecards *linecards; struct mlxsw_core *mlxsw_core; event = container_of(work, struct mlxsw_linecard_status_event, work); mlxsw_core = event->mlxsw_core; linecards = mlxsw_core_linecards(mlxsw_core); mlxsw_linecard_status_process(linecards, NULL, event->mddq_pl); kfree(event); } static void mlxsw_linecard_status_listener_func(const struct mlxsw_reg_info *reg, char *mddq_pl, void *priv) { struct mlxsw_linecard_status_event *event; struct mlxsw_core *mlxsw_core = priv; event = kmalloc(sizeof(*event), GFP_ATOMIC); if (!event) return; event->mlxsw_core = mlxsw_core; memcpy(event->mddq_pl, mddq_pl, sizeof(event->mddq_pl)); INIT_WORK(&event->work, mlxsw_linecard_status_event_work); mlxsw_core_schedule_work(&event->work); } struct mlxsw_linecard_bct_event { struct mlxsw_core *mlxsw_core; char mbct_pl[MLXSW_REG_MBCT_LEN]; struct work_struct work; }; static void mlxsw_linecard_bct_event_work(struct work_struct *work) { struct mlxsw_linecard_bct_event *event; struct mlxsw_core *mlxsw_core; event = container_of(work, struct mlxsw_linecard_bct_event, work); mlxsw_core = event->mlxsw_core; mlxsw_linecard_bct_process(mlxsw_core, event->mbct_pl); kfree(event); } static void mlxsw_linecard_bct_listener_func(const struct mlxsw_reg_info *reg, char *mbct_pl, void *priv) { struct mlxsw_linecard_bct_event *event; struct mlxsw_core *mlxsw_core = priv; event = kmalloc(sizeof(*event), GFP_ATOMIC); if (!event) return; event->mlxsw_core = mlxsw_core; memcpy(event->mbct_pl, mbct_pl, sizeof(event->mbct_pl)); INIT_WORK(&event->work, mlxsw_linecard_bct_event_work); mlxsw_core_schedule_work(&event->work); } static const struct mlxsw_listener mlxsw_linecard_listener[] = { MLXSW_CORE_EVENTL(mlxsw_linecard_status_listener_func, DSDSC), MLXSW_CORE_EVENTL(mlxsw_linecard_bct_listener_func, BCTOE), }; static int mlxsw_linecard_event_delivery_set(struct mlxsw_core *mlxsw_core, struct mlxsw_linecard *linecard, bool enable) { char mddq_pl[MLXSW_REG_MDDQ_LEN]; mlxsw_reg_mddq_slot_info_pack(mddq_pl, linecard->slot_index, enable); return mlxsw_reg_write(mlxsw_core, MLXSW_REG(mddq), mddq_pl); } static int mlxsw_linecard_init(struct mlxsw_core *mlxsw_core, struct mlxsw_linecards *linecards, u8 slot_index) { struct devlink_linecard *devlink_linecard; struct mlxsw_linecard *linecard; linecard = mlxsw_linecard_get(linecards, slot_index); linecard->slot_index = slot_index; linecard->linecards = linecards; mutex_init(&linecard->lock); devlink_linecard = devlink_linecard_create(priv_to_devlink(mlxsw_core), slot_index, &mlxsw_linecard_ops, linecard); if (IS_ERR(devlink_linecard)) return PTR_ERR(devlink_linecard); linecard->devlink_linecard = devlink_linecard; INIT_DELAYED_WORK(&linecard->status_event_to_dw, &mlxsw_linecard_status_event_to_work); return 0; } static void mlxsw_linecard_fini(struct mlxsw_core *mlxsw_core, struct mlxsw_linecards *linecards, u8 slot_index) { struct mlxsw_linecard *linecard; linecard = mlxsw_linecard_get(linecards, slot_index); cancel_delayed_work_sync(&linecard->status_event_to_dw); /* Make sure all scheduled events are processed */ mlxsw_core_flush_owq(); if (linecard->active) mlxsw_linecard_active_clear(linecard); mlxsw_linecard_bdev_del(linecard); devlink_linecard_destroy(linecard->devlink_linecard); mutex_destroy(&linecard->lock); } static int mlxsw_linecard_event_delivery_init(struct mlxsw_core *mlxsw_core, struct mlxsw_linecards *linecards, u8 slot_index) { struct mlxsw_linecard *linecard; int err; linecard = mlxsw_linecard_get(linecards, slot_index); err = mlxsw_linecard_event_delivery_set(mlxsw_core, linecard, true); if (err) return err; err = mlxsw_linecard_status_get_and_process(mlxsw_core, linecards, linecard); if (err) goto err_status_get_and_process; return 0; err_status_get_and_process: mlxsw_linecard_event_delivery_set(mlxsw_core, linecard, false); return err; } static void mlxsw_linecard_event_delivery_fini(struct mlxsw_core *mlxsw_core, struct mlxsw_linecards *linecards, u8 slot_index) { struct mlxsw_linecard *linecard; linecard = mlxsw_linecard_get(linecards, slot_index); mlxsw_linecard_event_delivery_set(mlxsw_core, linecard, false); } /* LINECARDS INI BUNDLE FILE * +----------------------------------+ * | MAGIC ("NVLCINI+") | * +----------------------------------+ +--------------------+ * | INI 0 +---> | __le16 size | * +----------------------------------+ | __be16 hw_revision | * | INI 1 | | __be16 ini_version | * +----------------------------------+ | u8 __dontcare[3] | * | ... | | u8 type | * +----------------------------------+ | u8 name[20] | * | INI N | | ... | * +----------------------------------+ +--------------------+ */ #define MLXSW_LINECARDS_INI_BUNDLE_MAGIC "NVLCINI+" static int mlxsw_linecard_types_file_validate(struct mlxsw_linecards *linecards, struct mlxsw_linecard_types_info *types_info) { size_t magic_size = strlen(MLXSW_LINECARDS_INI_BUNDLE_MAGIC); struct mlxsw_linecard_ini_file *ini_file; size_t size = types_info->data_size; const u8 *data = types_info->data; unsigned int count = 0; u16 ini_file_size; if (size < magic_size) { dev_warn(linecards->bus_info->dev, "Invalid linecards INIs file size, smaller than magic size\n"); return -EINVAL; } if (memcmp(data, MLXSW_LINECARDS_INI_BUNDLE_MAGIC, magic_size)) { dev_warn(linecards->bus_info->dev, "Invalid linecards INIs file magic pattern\n"); return -EINVAL; } data += magic_size; size -= magic_size; while (size > 0) { if (size < sizeof(*ini_file)) { dev_warn(linecards->bus_info->dev, "Linecards INIs file contains INI which is smaller than bare minimum\n"); return -EINVAL; } ini_file = (struct mlxsw_linecard_ini_file *) data; ini_file_size = le16_to_cpu(ini_file->size); if (ini_file_size + sizeof(__le16) > size) { dev_warn(linecards->bus_info->dev, "Linecards INIs file appears to be truncated\n"); return -EINVAL; } if (ini_file_size % 4) { dev_warn(linecards->bus_info->dev, "Linecards INIs file contains INI with invalid size\n"); return -EINVAL; } data += ini_file_size + sizeof(__le16); size -= ini_file_size + sizeof(__le16); count++; } if (!count) { dev_warn(linecards->bus_info->dev, "Linecards INIs file does not contain any INI\n"); return -EINVAL; } types_info->count = count; return 0; } static void mlxsw_linecard_types_file_parse(struct mlxsw_linecard_types_info *types_info) { size_t magic_size = strlen(MLXSW_LINECARDS_INI_BUNDLE_MAGIC); size_t size = types_info->data_size - magic_size; const u8 *data = types_info->data + magic_size; struct mlxsw_linecard_ini_file *ini_file; unsigned int count = 0; u16 ini_file_size; int i; while (size) { ini_file = (struct mlxsw_linecard_ini_file *) data; ini_file_size = le16_to_cpu(ini_file->size); for (i = 0; i < ini_file_size / 4; i++) { u32 *val = &((u32 *) ini_file->data)[i]; *val = swab32(*val); } types_info->ini_files[count] = ini_file; data += ini_file_size + sizeof(__le16); size -= ini_file_size + sizeof(__le16); count++; } } #define MLXSW_LINECARDS_INI_BUNDLE_FILENAME_FMT \ "mellanox/lc_ini_bundle_%u_%u.bin" #define MLXSW_LINECARDS_INI_BUNDLE_FILENAME_LEN \ (sizeof(MLXSW_LINECARDS_INI_BUNDLE_FILENAME_FMT) + 4) static int mlxsw_linecard_types_init(struct mlxsw_core *mlxsw_core, struct mlxsw_linecards *linecards) { const struct mlxsw_fw_rev *rev = &linecards->bus_info->fw_rev; char filename[MLXSW_LINECARDS_INI_BUNDLE_FILENAME_LEN]; struct mlxsw_linecard_types_info *types_info; const struct firmware *firmware; int err; err = snprintf(filename, sizeof(filename), MLXSW_LINECARDS_INI_BUNDLE_FILENAME_FMT, rev->minor, rev->subminor); WARN_ON(err >= sizeof(filename)); err = request_firmware_direct(&firmware, filename, linecards->bus_info->dev); if (err) { dev_warn(linecards->bus_info->dev, "Could not request linecards INI file \"%s\", provisioning will not be possible\n", filename); return 0; } types_info = kzalloc(sizeof(*types_info), GFP_KERNEL); if (!types_info) { release_firmware(firmware); return -ENOMEM; } linecards->types_info = types_info; types_info->data_size = firmware->size; types_info->data = vmalloc(types_info->data_size); if (!types_info->data) { err = -ENOMEM; release_firmware(firmware); goto err_data_alloc; } memcpy(types_info->data, firmware->data, types_info->data_size); release_firmware(firmware); err = mlxsw_linecard_types_file_validate(linecards, types_info); if (err) { err = 0; goto err_type_file_file_validate; } types_info->ini_files = kmalloc_array(types_info->count, sizeof(struct mlxsw_linecard_ini_file *), GFP_KERNEL); if (!types_info->ini_files) { err = -ENOMEM; goto err_ini_files_alloc; } mlxsw_linecard_types_file_parse(types_info); return 0; err_ini_files_alloc: err_type_file_file_validate: vfree(types_info->data); err_data_alloc: kfree(types_info); return err; } static void mlxsw_linecard_types_fini(struct mlxsw_linecards *linecards) { struct mlxsw_linecard_types_info *types_info = linecards->types_info; if (!types_info) return; kfree(types_info->ini_files); vfree(types_info->data); kfree(types_info); } int mlxsw_linecards_init(struct mlxsw_core *mlxsw_core, const struct mlxsw_bus_info *bus_info) { char mgpir_pl[MLXSW_REG_MGPIR_LEN]; struct mlxsw_linecards *linecards; u8 slot_count; int err; int i; mlxsw_reg_mgpir_pack(mgpir_pl, 0); err = mlxsw_reg_query(mlxsw_core, MLXSW_REG(mgpir), mgpir_pl); if (err) return err; mlxsw_reg_mgpir_unpack(mgpir_pl, NULL, NULL, NULL, NULL, &slot_count); if (!slot_count) return 0; linecards = vzalloc(struct_size(linecards, linecards, slot_count)); if (!linecards) return -ENOMEM; linecards->count = slot_count; linecards->mlxsw_core = mlxsw_core; linecards->bus_info = bus_info; INIT_LIST_HEAD(&linecards->event_ops_list); mutex_init(&linecards->event_ops_list_lock); err = mlxsw_linecard_types_init(mlxsw_core, linecards); if (err) goto err_types_init; err = mlxsw_core_traps_register(mlxsw_core, mlxsw_linecard_listener, ARRAY_SIZE(mlxsw_linecard_listener), mlxsw_core); if (err) goto err_traps_register; err = mlxsw_core_irq_event_handler_register(mlxsw_core, mlxsw_linecards_irq_event_handler); if (err) goto err_irq_event_handler_register; mlxsw_core_linecards_set(mlxsw_core, linecards); for (i = 0; i < linecards->count; i++) { err = mlxsw_linecard_init(mlxsw_core, linecards, i + 1); if (err) goto err_linecard_init; } for (i = 0; i < linecards->count; i++) { err = mlxsw_linecard_event_delivery_init(mlxsw_core, linecards, i + 1); if (err) goto err_linecard_event_delivery_init; } return 0; err_linecard_event_delivery_init: for (i--; i >= 0; i--) mlxsw_linecard_event_delivery_fini(mlxsw_core, linecards, i + 1); i = linecards->count; err_linecard_init: for (i--; i >= 0; i--) mlxsw_linecard_fini(mlxsw_core, linecards, i + 1); mlxsw_core_irq_event_handler_unregister(mlxsw_core, mlxsw_linecards_irq_event_handler); err_irq_event_handler_register: mlxsw_core_traps_unregister(mlxsw_core, mlxsw_linecard_listener, ARRAY_SIZE(mlxsw_linecard_listener), mlxsw_core); err_traps_register: mlxsw_linecard_types_fini(linecards); err_types_init: vfree(linecards); return err; } void mlxsw_linecards_fini(struct mlxsw_core *mlxsw_core) { struct mlxsw_linecards *linecards = mlxsw_core_linecards(mlxsw_core); int i; if (!linecards) return; for (i = 0; i < linecards->count; i++) mlxsw_linecard_event_delivery_fini(mlxsw_core, linecards, i + 1); for (i = 0; i < linecards->count; i++) mlxsw_linecard_fini(mlxsw_core, linecards, i + 1); mlxsw_core_irq_event_handler_unregister(mlxsw_core, mlxsw_linecards_irq_event_handler); mlxsw_core_traps_unregister(mlxsw_core, mlxsw_linecard_listener, ARRAY_SIZE(mlxsw_linecard_listener), mlxsw_core); mlxsw_linecard_types_fini(linecards); mutex_destroy(&linecards->event_ops_list_lock); WARN_ON(!list_empty(&linecards->event_ops_list)); vfree(linecards); }