// SPDX-License-Identifier: GPL-2.0-only /* * Huawei HiNIC PCI Express Linux driver * Copyright(c) 2017 Huawei Technologies Co., Ltd */ #include #include #include #include #include #include #include #include #include #include #include "hinic_devlink.h" #include "hinic_hw_if.h" #include "hinic_hw_eqs.h" #include "hinic_hw_api_cmd.h" #include "hinic_hw_mgmt.h" #include "hinic_hw_dev.h" #define SYNC_MSG_ID_MASK 0x1FF #define SYNC_MSG_ID(pf_to_mgmt) ((pf_to_mgmt)->sync_msg_id) #define SYNC_MSG_ID_INC(pf_to_mgmt) (SYNC_MSG_ID(pf_to_mgmt) = \ ((SYNC_MSG_ID(pf_to_mgmt) + 1) & \ SYNC_MSG_ID_MASK)) #define MSG_SZ_IS_VALID(in_size) ((in_size) <= MAX_MSG_LEN) #define MGMT_MSG_LEN_MIN 20 #define MGMT_MSG_LEN_STEP 16 #define MGMT_MSG_RSVD_FOR_DEV 8 #define SEGMENT_LEN 48 #define MAX_PF_MGMT_BUF_SIZE 2048 /* Data should be SEG LEN size aligned */ #define MAX_MSG_LEN 2016 #define MSG_NOT_RESP 0xFFFF #define MGMT_MSG_TIMEOUT 5000 #define SET_FUNC_PORT_MBOX_TIMEOUT 30000 #define SET_FUNC_PORT_MGMT_TIMEOUT 25000 #define UPDATE_FW_MGMT_TIMEOUT 20000 #define mgmt_to_pfhwdev(pf_mgmt) \ container_of(pf_mgmt, struct hinic_pfhwdev, pf_to_mgmt) enum msg_segment_type { NOT_LAST_SEGMENT = 0, LAST_SEGMENT = 1, }; enum mgmt_direction_type { MGMT_DIRECT_SEND = 0, MGMT_RESP = 1, }; enum msg_ack_type { MSG_ACK = 0, MSG_NO_ACK = 1, }; /** * hinic_register_mgmt_msg_cb - register msg handler for a msg from a module * @pf_to_mgmt: PF to MGMT channel * @mod: module in the chip that this handler will handle its messages * @handle: private data for the callback * @callback: the handler that will handle messages **/ void hinic_register_mgmt_msg_cb(struct hinic_pf_to_mgmt *pf_to_mgmt, enum hinic_mod_type mod, void *handle, void (*callback)(void *handle, u8 cmd, void *buf_in, u16 in_size, void *buf_out, u16 *out_size)) { struct hinic_mgmt_cb *mgmt_cb = &pf_to_mgmt->mgmt_cb[mod]; mgmt_cb->cb = callback; mgmt_cb->handle = handle; mgmt_cb->state = HINIC_MGMT_CB_ENABLED; } /** * hinic_unregister_mgmt_msg_cb - unregister msg handler for a msg from a module * @pf_to_mgmt: PF to MGMT channel * @mod: module in the chip that this handler handles its messages **/ void hinic_unregister_mgmt_msg_cb(struct hinic_pf_to_mgmt *pf_to_mgmt, enum hinic_mod_type mod) { struct hinic_mgmt_cb *mgmt_cb = &pf_to_mgmt->mgmt_cb[mod]; mgmt_cb->state &= ~HINIC_MGMT_CB_ENABLED; while (mgmt_cb->state & HINIC_MGMT_CB_RUNNING) schedule(); mgmt_cb->cb = NULL; } /** * prepare_header - prepare the header of the message * @pf_to_mgmt: PF to MGMT channel * @msg_len: the length of the message * @mod: module in the chip that will get the message * @ack_type: ask for response * @direction: the direction of the message * @cmd: command of the message * @msg_id: message id * * Return the prepared header value **/ static u64 prepare_header(struct hinic_pf_to_mgmt *pf_to_mgmt, u16 msg_len, enum hinic_mod_type mod, enum msg_ack_type ack_type, enum mgmt_direction_type direction, u16 cmd, u16 msg_id) { struct hinic_hwif *hwif = pf_to_mgmt->hwif; return HINIC_MSG_HEADER_SET(msg_len, MSG_LEN) | HINIC_MSG_HEADER_SET(mod, MODULE) | HINIC_MSG_HEADER_SET(SEGMENT_LEN, SEG_LEN) | HINIC_MSG_HEADER_SET(ack_type, NO_ACK) | HINIC_MSG_HEADER_SET(0, ASYNC_MGMT_TO_PF) | HINIC_MSG_HEADER_SET(0, SEQID) | HINIC_MSG_HEADER_SET(LAST_SEGMENT, LAST) | HINIC_MSG_HEADER_SET(direction, DIRECTION) | HINIC_MSG_HEADER_SET(cmd, CMD) | HINIC_MSG_HEADER_SET(HINIC_HWIF_PCI_INTF(hwif), PCI_INTF) | HINIC_MSG_HEADER_SET(HINIC_HWIF_PF_IDX(hwif), PF_IDX) | HINIC_MSG_HEADER_SET(msg_id, MSG_ID); } /** * prepare_mgmt_cmd - prepare the mgmt command * @mgmt_cmd: pointer to the command to prepare * @header: pointer of the header for the message * @msg: the data of the message * @msg_len: the length of the message **/ static void prepare_mgmt_cmd(u8 *mgmt_cmd, u64 *header, u8 *msg, u16 msg_len) { memset(mgmt_cmd, 0, MGMT_MSG_RSVD_FOR_DEV); mgmt_cmd += MGMT_MSG_RSVD_FOR_DEV; memcpy(mgmt_cmd, header, sizeof(*header)); mgmt_cmd += sizeof(*header); memcpy(mgmt_cmd, msg, msg_len); } /** * mgmt_msg_len - calculate the total message length * @msg_data_len: the length of the message data * * Return the total message length **/ static u16 mgmt_msg_len(u16 msg_data_len) { /* RSVD + HEADER_SIZE + DATA_LEN */ u16 msg_len = MGMT_MSG_RSVD_FOR_DEV + sizeof(u64) + msg_data_len; if (msg_len > MGMT_MSG_LEN_MIN) msg_len = MGMT_MSG_LEN_MIN + ALIGN((msg_len - MGMT_MSG_LEN_MIN), MGMT_MSG_LEN_STEP); else msg_len = MGMT_MSG_LEN_MIN; return msg_len; } /** * send_msg_to_mgmt - send message to mgmt by API CMD * @pf_to_mgmt: PF to MGMT channel * @mod: module in the chip that will get the message * @cmd: command of the message * @data: the msg data * @data_len: the msg data length * @ack_type: ask for response * @direction: the direction of the original message * @resp_msg_id: msg id to response for * * Return 0 - Success, negative - Failure **/ static int send_msg_to_mgmt(struct hinic_pf_to_mgmt *pf_to_mgmt, enum hinic_mod_type mod, u8 cmd, u8 *data, u16 data_len, enum msg_ack_type ack_type, enum mgmt_direction_type direction, u16 resp_msg_id) { struct hinic_api_cmd_chain *chain; u64 header; u16 msg_id; msg_id = SYNC_MSG_ID(pf_to_mgmt); if (direction == MGMT_RESP) { header = prepare_header(pf_to_mgmt, data_len, mod, ack_type, direction, cmd, resp_msg_id); } else { SYNC_MSG_ID_INC(pf_to_mgmt); header = prepare_header(pf_to_mgmt, data_len, mod, ack_type, direction, cmd, msg_id); } prepare_mgmt_cmd(pf_to_mgmt->sync_msg_buf, &header, data, data_len); chain = pf_to_mgmt->cmd_chain[HINIC_API_CMD_WRITE_TO_MGMT_CPU]; return hinic_api_cmd_write(chain, HINIC_NODE_ID_MGMT, pf_to_mgmt->sync_msg_buf, mgmt_msg_len(data_len)); } /** * msg_to_mgmt_sync - send sync message to mgmt * @pf_to_mgmt: PF to MGMT channel * @mod: module in the chip that will get the message * @cmd: command of the message * @buf_in: the msg data * @in_size: the msg data length * @buf_out: response * @out_size: response length * @direction: the direction of the original message * @resp_msg_id: msg id to response for * @timeout: time-out period of waiting for response * * Return 0 - Success, negative - Failure **/ static int msg_to_mgmt_sync(struct hinic_pf_to_mgmt *pf_to_mgmt, enum hinic_mod_type mod, u8 cmd, u8 *buf_in, u16 in_size, u8 *buf_out, u16 *out_size, enum mgmt_direction_type direction, u16 resp_msg_id, u32 timeout) { struct hinic_hwif *hwif = pf_to_mgmt->hwif; struct pci_dev *pdev = hwif->pdev; struct hinic_recv_msg *recv_msg; struct completion *recv_done; unsigned long timeo; u16 msg_id; int err; /* Lock the sync_msg_buf */ down(&pf_to_mgmt->sync_msg_lock); recv_msg = &pf_to_mgmt->recv_resp_msg_from_mgmt; recv_done = &recv_msg->recv_done; if (resp_msg_id == MSG_NOT_RESP) msg_id = SYNC_MSG_ID(pf_to_mgmt); else msg_id = resp_msg_id; init_completion(recv_done); err = send_msg_to_mgmt(pf_to_mgmt, mod, cmd, buf_in, in_size, MSG_ACK, direction, resp_msg_id); if (err) { dev_err(&pdev->dev, "Failed to send sync msg to mgmt\n"); goto unlock_sync_msg; } timeo = msecs_to_jiffies(timeout ? timeout : MGMT_MSG_TIMEOUT); if (!wait_for_completion_timeout(recv_done, timeo)) { dev_err(&pdev->dev, "MGMT timeout, MSG id = %d\n", msg_id); hinic_dump_aeq_info(pf_to_mgmt->hwdev); err = -ETIMEDOUT; goto unlock_sync_msg; } smp_rmb(); /* verify reading after completion */ if (recv_msg->msg_id != msg_id) { dev_err(&pdev->dev, "incorrect MSG for id = %d\n", msg_id); err = -EFAULT; goto unlock_sync_msg; } if ((buf_out) && (recv_msg->msg_len <= MAX_PF_MGMT_BUF_SIZE)) { memcpy(buf_out, recv_msg->msg, recv_msg->msg_len); *out_size = recv_msg->msg_len; } unlock_sync_msg: up(&pf_to_mgmt->sync_msg_lock); return err; } /** * msg_to_mgmt_async - send message to mgmt without response * @pf_to_mgmt: PF to MGMT channel * @mod: module in the chip that will get the message * @cmd: command of the message * @buf_in: the msg data * @in_size: the msg data length * @direction: the direction of the original message * @resp_msg_id: msg id to response for * * Return 0 - Success, negative - Failure **/ static int msg_to_mgmt_async(struct hinic_pf_to_mgmt *pf_to_mgmt, enum hinic_mod_type mod, u8 cmd, u8 *buf_in, u16 in_size, enum mgmt_direction_type direction, u16 resp_msg_id) { int err; /* Lock the sync_msg_buf */ down(&pf_to_mgmt->sync_msg_lock); err = send_msg_to_mgmt(pf_to_mgmt, mod, cmd, buf_in, in_size, MSG_NO_ACK, direction, resp_msg_id); up(&pf_to_mgmt->sync_msg_lock); return err; } /** * hinic_msg_to_mgmt - send message to mgmt * @pf_to_mgmt: PF to MGMT channel * @mod: module in the chip that will get the message * @cmd: command of the message * @buf_in: the msg data * @in_size: the msg data length * @buf_out: response * @out_size: returned response length * @sync: sync msg or async msg * * Return 0 - Success, negative - Failure **/ int hinic_msg_to_mgmt(struct hinic_pf_to_mgmt *pf_to_mgmt, enum hinic_mod_type mod, u8 cmd, void *buf_in, u16 in_size, void *buf_out, u16 *out_size, enum hinic_mgmt_msg_type sync) { struct hinic_hwif *hwif = pf_to_mgmt->hwif; struct pci_dev *pdev = hwif->pdev; u32 timeout = 0; if (sync != HINIC_MGMT_MSG_SYNC) { dev_err(&pdev->dev, "Invalid MGMT msg type\n"); return -EINVAL; } if (!MSG_SZ_IS_VALID(in_size)) { dev_err(&pdev->dev, "Invalid MGMT msg buffer size\n"); return -EINVAL; } if (HINIC_IS_VF(hwif)) { if (cmd == HINIC_PORT_CMD_SET_FUNC_STATE) timeout = SET_FUNC_PORT_MBOX_TIMEOUT; return hinic_mbox_to_pf(pf_to_mgmt->hwdev, mod, cmd, buf_in, in_size, buf_out, out_size, timeout); } else { if (cmd == HINIC_PORT_CMD_SET_FUNC_STATE) timeout = SET_FUNC_PORT_MGMT_TIMEOUT; else if (cmd == HINIC_PORT_CMD_UPDATE_FW) timeout = UPDATE_FW_MGMT_TIMEOUT; return msg_to_mgmt_sync(pf_to_mgmt, mod, cmd, buf_in, in_size, buf_out, out_size, MGMT_DIRECT_SEND, MSG_NOT_RESP, timeout); } } static void recv_mgmt_msg_work_handler(struct work_struct *work) { struct hinic_mgmt_msg_handle_work *mgmt_work = container_of(work, struct hinic_mgmt_msg_handle_work, work); struct hinic_pf_to_mgmt *pf_to_mgmt = mgmt_work->pf_to_mgmt; struct pci_dev *pdev = pf_to_mgmt->hwif->pdev; u8 *buf_out = pf_to_mgmt->mgmt_ack_buf; struct hinic_mgmt_cb *mgmt_cb; unsigned long cb_state; u16 out_size = 0; memset(buf_out, 0, MAX_PF_MGMT_BUF_SIZE); if (mgmt_work->mod >= HINIC_MOD_MAX) { dev_err(&pdev->dev, "Unknown MGMT MSG module = %d\n", mgmt_work->mod); kfree(mgmt_work->msg); kfree(mgmt_work); return; } mgmt_cb = &pf_to_mgmt->mgmt_cb[mgmt_work->mod]; cb_state = cmpxchg(&mgmt_cb->state, HINIC_MGMT_CB_ENABLED, HINIC_MGMT_CB_ENABLED | HINIC_MGMT_CB_RUNNING); if ((cb_state == HINIC_MGMT_CB_ENABLED) && (mgmt_cb->cb)) mgmt_cb->cb(mgmt_cb->handle, mgmt_work->cmd, mgmt_work->msg, mgmt_work->msg_len, buf_out, &out_size); else dev_err(&pdev->dev, "No MGMT msg handler, mod: %d, cmd: %d\n", mgmt_work->mod, mgmt_work->cmd); mgmt_cb->state &= ~HINIC_MGMT_CB_RUNNING; if (!mgmt_work->async_mgmt_to_pf) /* MGMT sent sync msg, send the response */ msg_to_mgmt_async(pf_to_mgmt, mgmt_work->mod, mgmt_work->cmd, buf_out, out_size, MGMT_RESP, mgmt_work->msg_id); kfree(mgmt_work->msg); kfree(mgmt_work); } /** * mgmt_recv_msg_handler - handler for message from mgmt cpu * @pf_to_mgmt: PF to MGMT channel * @recv_msg: received message details **/ static void mgmt_recv_msg_handler(struct hinic_pf_to_mgmt *pf_to_mgmt, struct hinic_recv_msg *recv_msg) { struct hinic_mgmt_msg_handle_work *mgmt_work = NULL; struct pci_dev *pdev = pf_to_mgmt->hwif->pdev; mgmt_work = kzalloc(sizeof(*mgmt_work), GFP_KERNEL); if (!mgmt_work) { dev_err(&pdev->dev, "Allocate mgmt work memory failed\n"); return; } if (recv_msg->msg_len) { mgmt_work->msg = kzalloc(recv_msg->msg_len, GFP_KERNEL); if (!mgmt_work->msg) { dev_err(&pdev->dev, "Allocate mgmt msg memory failed\n"); kfree(mgmt_work); return; } } mgmt_work->pf_to_mgmt = pf_to_mgmt; mgmt_work->msg_len = recv_msg->msg_len; memcpy(mgmt_work->msg, recv_msg->msg, recv_msg->msg_len); mgmt_work->msg_id = recv_msg->msg_id; mgmt_work->mod = recv_msg->mod; mgmt_work->cmd = recv_msg->cmd; mgmt_work->async_mgmt_to_pf = recv_msg->async_mgmt_to_pf; INIT_WORK(&mgmt_work->work, recv_mgmt_msg_work_handler); queue_work(pf_to_mgmt->workq, &mgmt_work->work); } /** * mgmt_resp_msg_handler - handler for a response message from mgmt cpu * @pf_to_mgmt: PF to MGMT channel * @recv_msg: received message details **/ static void mgmt_resp_msg_handler(struct hinic_pf_to_mgmt *pf_to_mgmt, struct hinic_recv_msg *recv_msg) { wmb(); /* verify writing all, before reading */ complete(&recv_msg->recv_done); } /** * recv_mgmt_msg_handler - handler for a message from mgmt cpu * @pf_to_mgmt: PF to MGMT channel * @header: the header of the message * @recv_msg: received message details **/ static void recv_mgmt_msg_handler(struct hinic_pf_to_mgmt *pf_to_mgmt, u64 *header, struct hinic_recv_msg *recv_msg) { struct hinic_hwif *hwif = pf_to_mgmt->hwif; struct pci_dev *pdev = hwif->pdev; int seq_id, seg_len; u8 *msg_body; seq_id = HINIC_MSG_HEADER_GET(*header, SEQID); seg_len = HINIC_MSG_HEADER_GET(*header, SEG_LEN); if (seq_id >= (MAX_MSG_LEN / SEGMENT_LEN)) { dev_err(&pdev->dev, "recv big mgmt msg\n"); return; } msg_body = (u8 *)header + sizeof(*header); memcpy(recv_msg->msg + seq_id * SEGMENT_LEN, msg_body, seg_len); if (!HINIC_MSG_HEADER_GET(*header, LAST)) return; recv_msg->cmd = HINIC_MSG_HEADER_GET(*header, CMD); recv_msg->mod = HINIC_MSG_HEADER_GET(*header, MODULE); recv_msg->async_mgmt_to_pf = HINIC_MSG_HEADER_GET(*header, ASYNC_MGMT_TO_PF); recv_msg->msg_len = HINIC_MSG_HEADER_GET(*header, MSG_LEN); recv_msg->msg_id = HINIC_MSG_HEADER_GET(*header, MSG_ID); if (HINIC_MSG_HEADER_GET(*header, DIRECTION) == MGMT_RESP) mgmt_resp_msg_handler(pf_to_mgmt, recv_msg); else mgmt_recv_msg_handler(pf_to_mgmt, recv_msg); } /** * mgmt_msg_aeqe_handler - handler for a mgmt message event * @handle: PF to MGMT channel * @data: the header of the message * @size: unused **/ static void mgmt_msg_aeqe_handler(void *handle, void *data, u8 size) { struct hinic_pf_to_mgmt *pf_to_mgmt = handle; struct hinic_recv_msg *recv_msg; u64 *header = (u64 *)data; recv_msg = HINIC_MSG_HEADER_GET(*header, DIRECTION) == MGMT_DIRECT_SEND ? &pf_to_mgmt->recv_msg_from_mgmt : &pf_to_mgmt->recv_resp_msg_from_mgmt; recv_mgmt_msg_handler(pf_to_mgmt, header, recv_msg); } /** * alloc_recv_msg - allocate receive message memory * @pf_to_mgmt: PF to MGMT channel * @recv_msg: pointer that will hold the allocated data * * Return 0 - Success, negative - Failure **/ static int alloc_recv_msg(struct hinic_pf_to_mgmt *pf_to_mgmt, struct hinic_recv_msg *recv_msg) { struct hinic_hwif *hwif = pf_to_mgmt->hwif; struct pci_dev *pdev = hwif->pdev; recv_msg->msg = devm_kzalloc(&pdev->dev, MAX_PF_MGMT_BUF_SIZE, GFP_KERNEL); if (!recv_msg->msg) return -ENOMEM; recv_msg->buf_out = devm_kzalloc(&pdev->dev, MAX_PF_MGMT_BUF_SIZE, GFP_KERNEL); if (!recv_msg->buf_out) return -ENOMEM; return 0; } /** * alloc_msg_buf - allocate all the message buffers of PF to MGMT channel * @pf_to_mgmt: PF to MGMT channel * * Return 0 - Success, negative - Failure **/ static int alloc_msg_buf(struct hinic_pf_to_mgmt *pf_to_mgmt) { struct hinic_hwif *hwif = pf_to_mgmt->hwif; struct pci_dev *pdev = hwif->pdev; int err; err = alloc_recv_msg(pf_to_mgmt, &pf_to_mgmt->recv_msg_from_mgmt); if (err) { dev_err(&pdev->dev, "Failed to allocate recv msg\n"); return err; } err = alloc_recv_msg(pf_to_mgmt, &pf_to_mgmt->recv_resp_msg_from_mgmt); if (err) { dev_err(&pdev->dev, "Failed to allocate resp recv msg\n"); return err; } pf_to_mgmt->sync_msg_buf = devm_kzalloc(&pdev->dev, MAX_PF_MGMT_BUF_SIZE, GFP_KERNEL); if (!pf_to_mgmt->sync_msg_buf) return -ENOMEM; pf_to_mgmt->mgmt_ack_buf = devm_kzalloc(&pdev->dev, MAX_PF_MGMT_BUF_SIZE, GFP_KERNEL); if (!pf_to_mgmt->mgmt_ack_buf) return -ENOMEM; return 0; } /** * hinic_pf_to_mgmt_init - initialize PF to MGMT channel * @pf_to_mgmt: PF to MGMT channel * @hwif: HW interface the PF to MGMT will use for accessing HW * * Return 0 - Success, negative - Failure **/ int hinic_pf_to_mgmt_init(struct hinic_pf_to_mgmt *pf_to_mgmt, struct hinic_hwif *hwif) { struct hinic_pfhwdev *pfhwdev = mgmt_to_pfhwdev(pf_to_mgmt); struct hinic_hwdev *hwdev = &pfhwdev->hwdev; struct pci_dev *pdev = hwif->pdev; int err; pf_to_mgmt->hwif = hwif; pf_to_mgmt->hwdev = hwdev; if (HINIC_IS_VF(hwif)) return 0; err = hinic_health_reporters_create(hwdev->devlink_dev); if (err) return err; sema_init(&pf_to_mgmt->sync_msg_lock, 1); pf_to_mgmt->workq = create_singlethread_workqueue("hinic_mgmt"); if (!pf_to_mgmt->workq) { dev_err(&pdev->dev, "Failed to initialize MGMT workqueue\n"); hinic_health_reporters_destroy(hwdev->devlink_dev); return -ENOMEM; } pf_to_mgmt->sync_msg_id = 0; err = alloc_msg_buf(pf_to_mgmt); if (err) { dev_err(&pdev->dev, "Failed to allocate msg buffers\n"); destroy_workqueue(pf_to_mgmt->workq); hinic_health_reporters_destroy(hwdev->devlink_dev); return err; } err = hinic_api_cmd_init(pf_to_mgmt->cmd_chain, hwif); if (err) { dev_err(&pdev->dev, "Failed to initialize cmd chains\n"); destroy_workqueue(pf_to_mgmt->workq); hinic_health_reporters_destroy(hwdev->devlink_dev); return err; } hinic_aeq_register_hw_cb(&hwdev->aeqs, HINIC_MSG_FROM_MGMT_CPU, pf_to_mgmt, mgmt_msg_aeqe_handler); return 0; } /** * hinic_pf_to_mgmt_free - free PF to MGMT channel * @pf_to_mgmt: PF to MGMT channel **/ void hinic_pf_to_mgmt_free(struct hinic_pf_to_mgmt *pf_to_mgmt) { struct hinic_pfhwdev *pfhwdev = mgmt_to_pfhwdev(pf_to_mgmt); struct hinic_hwdev *hwdev = &pfhwdev->hwdev; if (HINIC_IS_VF(hwdev->hwif)) return; hinic_aeq_unregister_hw_cb(&hwdev->aeqs, HINIC_MSG_FROM_MGMT_CPU); hinic_api_cmd_free(pf_to_mgmt->cmd_chain); destroy_workqueue(pf_to_mgmt->workq); hinic_health_reporters_destroy(hwdev->devlink_dev); }