| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Huawei HiNIC PCI Express Linux driver |
| * Copyright(c) 2017 Huawei Technologies Co., Ltd |
| */ |
| |
| #include <linux/kernel.h> |
| #include <linux/types.h> |
| #include <linux/errno.h> |
| #include <linux/pci.h> |
| #include <linux/device.h> |
| #include <linux/semaphore.h> |
| #include <linux/completion.h> |
| #include <linux/slab.h> |
| #include <asm/barrier.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 1000 |
| |
| #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 |
| * |
| * 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) |
| { |
| struct hinic_hwif *hwif = pf_to_mgmt->hwif; |
| struct pci_dev *pdev = hwif->pdev; |
| struct hinic_recv_msg *recv_msg; |
| struct completion *recv_done; |
| 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; |
| } |
| |
| if (!wait_for_completion_timeout(recv_done, MGMT_MSG_TIMEOUT)) { |
| dev_err(&pdev->dev, "MGMT timeout, MSG id = %d\n", msg_id); |
| 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; |
| |
| 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; |
| } |
| |
| return msg_to_mgmt_sync(pf_to_mgmt, mod, cmd, buf_in, in_size, |
| buf_out, out_size, MGMT_DIRECT_SEND, |
| MSG_NOT_RESP); |
| } |
| |
| /** |
| * 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_hwif *hwif = pf_to_mgmt->hwif; |
| struct pci_dev *pdev = hwif->pdev; |
| u8 *buf_out = recv_msg->buf_out; |
| struct hinic_mgmt_cb *mgmt_cb; |
| unsigned long cb_state; |
| u16 out_size = 0; |
| |
| if (recv_msg->mod >= HINIC_MOD_MAX) { |
| dev_err(&pdev->dev, "Unknown MGMT MSG module = %d\n", |
| recv_msg->mod); |
| return; |
| } |
| |
| mgmt_cb = &pf_to_mgmt->mgmt_cb[recv_msg->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, recv_msg->cmd, |
| recv_msg->msg, recv_msg->msg_len, |
| buf_out, &out_size); |
| else |
| dev_err(&pdev->dev, "No MGMT msg handler, mod = %d\n", |
| recv_msg->mod); |
| |
| mgmt_cb->state &= ~HINIC_MGMT_CB_RUNNING; |
| |
| if (!recv_msg->async_mgmt_to_pf) |
| /* MGMT sent sync msg, send the response */ |
| msg_to_mgmt_async(pf_to_mgmt, recv_msg->mod, recv_msg->cmd, |
| buf_out, out_size, MGMT_RESP, |
| recv_msg->msg_id); |
| } |
| |
| /** |
| * 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; |
| |
| 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; |
| |
| sema_init(&pf_to_mgmt->sync_msg_lock, 1); |
| 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"); |
| 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"); |
| 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; |
| |
| hinic_aeq_unregister_hw_cb(&hwdev->aeqs, HINIC_MSG_FROM_MGMT_CPU); |
| hinic_api_cmd_free(pf_to_mgmt->cmd_chain); |
| } |