| /* |
| ************************************************************************** |
| * Copyright (c) 2019-2020 The Linux Foundation. All rights reserved. |
| * Permission to use, copy, modify, and/or distribute this software for |
| * any purpose with or without fee is hereby granted, provided that the |
| * above copyright notice and this permission notice appear in all copies. |
| * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
| * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
| * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
| * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
| * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
| * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT |
| * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
| ************************************************************************** |
| */ |
| |
| #include <nss_api_if.h> |
| #include <nss_cmn.h> |
| #include <net/netfilter/nf_conntrack_dscpremark_ext.h> |
| #include "nss_mirred.h" |
| #include "nss_igs.h" |
| #include "nss_ifb.h" |
| |
| /* |
| * TODO: Current implementation only supports one IFB interface to be mapped with |
| * any one interface at a time. This has to be changed to support the mapping of |
| * an IFB device to multiple interfaces. |
| */ |
| static LIST_HEAD(nss_ifb_list); /* List of IFB and its mapped interface */ |
| static DEFINE_SPINLOCK(nss_ifb_list_lock); /* Lock for the ifb list */ |
| |
| /* |
| * nss_ifb_msg_response |
| * NSS firmware message response structure. |
| */ |
| static struct nss_ifb_msg_response { |
| struct semaphore sem; |
| wait_queue_head_t wq; |
| enum nss_cmn_response response; |
| bool cond; |
| } msg_response; |
| |
| /* |
| * nss_ifb_igs_ip_pre_routing_hook() |
| * Copy class-id to Linux CT structure. |
| * |
| * Copy class-id from tc_index field of skb in ingress QoS fields inside |
| * DSCP CT extention structure. |
| */ |
| unsigned int nss_ifb_igs_ip_pre_routing_hook(void *priv, struct sk_buff *skb, |
| const struct nf_hook_state *state) |
| { |
| struct nf_conn *ct; |
| struct nf_ct_dscpremark_ext *dscpcte; |
| enum ip_conntrack_info ctinfo; |
| |
| if (unlikely(!skb)) |
| return NF_ACCEPT; |
| |
| /* |
| * Return if ingress qostag value (saved in tc_index field) is 0. |
| */ |
| if (likely(!skb->tc_index)) |
| return NF_ACCEPT; |
| |
| ct = nf_ct_get(skb, &ctinfo); |
| if (!ct) |
| return NF_ACCEPT; |
| |
| spin_lock_bh(&ct->lock); |
| dscpcte = nf_ct_dscpremark_ext_find(ct); |
| if (!dscpcte) { |
| spin_unlock_bh(&ct->lock); |
| return NF_ACCEPT; |
| } |
| |
| /* |
| * Copy ingress qostag value (saved in tc_index) to ingress |
| * qostag fields of DSCP CT extension structure. |
| */ |
| if (IP_CT_DIR_ORIGINAL == CTINFO2DIR(ctinfo)) { |
| dscpcte->igs_flow_qos_tag = skb->tc_index; |
| } else { |
| dscpcte->igs_reply_qos_tag = skb->tc_index; |
| } |
| spin_unlock_bh(&ct->lock); |
| |
| /* |
| * Reset the tc_index field as it no longer required. |
| */ |
| skb->tc_index = 0; |
| return NF_ACCEPT; |
| } |
| |
| /* |
| * nss_ifb_list_del() |
| * API to delete member in ifb list. |
| */ |
| void nss_ifb_list_del(struct nss_ifb_info *ifb_info) |
| { |
| spin_lock_bh(&nss_ifb_list_lock); |
| list_del(&ifb_info->map_list); |
| spin_unlock_bh(&nss_ifb_list_lock); |
| } |
| |
| /* |
| * nss_ifb_list_add() |
| * API to add member in ifb list. |
| */ |
| static void nss_ifb_list_add(struct nss_ifb_info *ifb_info) |
| { |
| spin_lock_bh(&nss_ifb_list_lock); |
| list_add(&(ifb_info->map_list), &nss_ifb_list); |
| spin_unlock_bh(&nss_ifb_list_lock); |
| } |
| |
| /* |
| * nss_ifb_is_mapped() |
| * Returns the map status of the given ifb bind structure. |
| */ |
| bool nss_ifb_is_mapped(struct nss_ifb_info *ifb_info) |
| { |
| bool is_mapped; |
| |
| spin_lock_bh(&nss_ifb_list_lock); |
| is_mapped = ifb_info->is_mapped; |
| spin_unlock_bh(&nss_ifb_list_lock); |
| return is_mapped; |
| } |
| |
| /* |
| * nss_ifb_config_msg_init() |
| * Initialize IFB configure interface's message. |
| */ |
| static void nss_ifb_config_msg_init(struct nss_if_msg *ncm, uint16_t if_num, |
| uint32_t type, uint32_t len, void *cb, void *app_data) |
| { |
| nss_cmn_msg_init(&ncm->cm, if_num, type, len, cb, app_data); |
| } |
| |
| /* |
| * nss_ifb_clear_config_cb() |
| * CLEAR configure handler for an IFB mapped interface. |
| */ |
| static void nss_ifb_clear_config_cb(void *app_data, struct nss_if_msg *nim) |
| { |
| struct nss_ifb_info *ifb_info = (struct nss_ifb_info *)app_data; |
| bool ret; |
| |
| if (nim->cm.response != NSS_CMN_RESPONSE_ACK) { |
| nss_igs_error("Response error: %d\n", nim->cm.response); |
| return; |
| } |
| |
| do { |
| ret = spin_trylock_bh(&nss_ifb_list_lock); |
| } while (!ret); |
| ifb_info->is_mapped = false; |
| spin_unlock_bh(&nss_ifb_list_lock); |
| } |
| |
| /* |
| * nss_ifb_async_cb() |
| * IFB asynchronous handler for an IFB mapped interface. |
| */ |
| static void nss_ifb_async_cb(void *app_data, struct nss_if_msg *nim) |
| { |
| if (nim->cm.response != NSS_CMN_RESPONSE_ACK) { |
| nss_igs_error("Response error: %d\n", nim->cm.response); |
| } |
| } |
| |
| /* |
| * nss_ifb_wake_up_cb() |
| * IFB wake up handler for an IFB mapped interface. |
| */ |
| static void nss_ifb_wake_up_cb(void *app_data, struct nss_if_msg *nim) |
| { |
| msg_response.response = nim->cm.response; |
| msg_response.cond = 0; |
| wake_up(&msg_response.wq); |
| } |
| |
| /* |
| * nss_ifb_config_msg_fill() |
| * Fill the IFB configure message. |
| */ |
| static bool nss_ifb_config_msg_fill(struct nss_if_msg *nim_ptr, struct net_device *dev, |
| int32_t ifb_num, enum nss_ifb_if_config config, void *cb) |
| { |
| uint32_t if_src_num; |
| |
| if (netif_is_ifb_dev(dev)) { |
| if_src_num = nss_cmn_get_interface_number_by_dev_and_type(dev, NSS_DYNAMIC_INTERFACE_TYPE_IGS); |
| if (if_src_num < 0) { |
| nss_igs_error("invalid IFB device %s\n", dev->name); |
| return -1; |
| } |
| } else { |
| if_src_num = nss_cmn_get_interface_number_by_dev(dev); |
| if (if_src_num < 0) { |
| nss_igs_error("invalid device %s\n", dev->name); |
| return -1; |
| } |
| } |
| |
| switch (config) { |
| case NSS_IFB_SET_IGS_NODE: |
| nss_ifb_config_msg_init(nim_ptr, if_src_num, NSS_IF_SET_IGS_NODE, |
| sizeof(struct nss_if_igs_config), nss_ifb_wake_up_cb, cb); |
| nim_ptr->msg.config_igs.igs_num = ifb_num; |
| break; |
| case NSS_IFB_CLEAR_IGS_NODE: |
| nss_ifb_config_msg_init(nim_ptr, if_src_num, NSS_IF_CLEAR_IGS_NODE, |
| sizeof(struct nss_if_igs_config), nss_ifb_clear_config_cb, cb); |
| nim_ptr->msg.config_igs.igs_num = ifb_num; |
| break; |
| case NSS_IFB_SET_NEXTHOP: |
| nss_ifb_config_msg_init(nim_ptr, if_src_num, NSS_IF_SET_NEXTHOP, |
| sizeof(struct nss_if_set_nexthop), nss_ifb_wake_up_cb, cb); |
| nim_ptr->msg.set_nexthop.nexthop = ifb_num; |
| break; |
| case NSS_IFB_RESET_NEXTHOP: |
| nss_ifb_config_msg_init(nim_ptr, if_src_num, NSS_IF_RESET_NEXTHOP, |
| sizeof(struct nss_if_set_nexthop), nss_ifb_async_cb, cb); |
| nim_ptr->msg.set_nexthop.nexthop = ifb_num; |
| break; |
| case NSS_IFB_OPEN: |
| nss_ifb_config_msg_init(nim_ptr, if_src_num, NSS_IF_OPEN, |
| sizeof(struct nss_if_open), nss_ifb_async_cb, cb); |
| /* |
| * Reset the elements of interface's open configuration. |
| */ |
| memset (&nim_ptr->msg.open, 0, sizeof(struct nss_if_open)); |
| break; |
| case NSS_IFB_CLOSE: |
| nss_ifb_config_msg_init(nim_ptr, if_src_num, NSS_IF_CLOSE, |
| sizeof(struct nss_if_close), nss_ifb_async_cb, cb); |
| /* |
| * Reset the elements of interface's close configuration. |
| */ |
| memset (&nim_ptr->msg.close, 0, sizeof(struct nss_if_close)); |
| break; |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * nss_ifb_config_msg_tx() |
| * Send IFB configure message to an IFB mapped interface. |
| */ |
| int32_t nss_ifb_config_msg_tx(struct net_device *dev, int32_t ifb_num, |
| enum nss_ifb_if_config config, void *cb) |
| { |
| struct nss_if_msg nim; |
| int32_t ret; |
| |
| if ((ret = nss_ifb_config_msg_fill(&nim, dev, ifb_num, config, cb))) { |
| nss_igs_error("Error in setting up IFB %d config message\n", config); |
| return -1; |
| } |
| |
| ret = nss_if_tx_msg(nss_igs_get_context(), &nim); |
| if (ret != NSS_TX_SUCCESS) { |
| nss_igs_error("failed to send config message\n"); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * nss_ifb_config_msg_tx_sync() |
| * Send IFB configure message to an IFB mapped interface and wait for the response. |
| */ |
| int32_t nss_ifb_config_msg_tx_sync(struct net_device *dev, int32_t ifb_num, |
| enum nss_ifb_if_config config, void *cb) |
| { |
| struct nss_if_msg nim; |
| int32_t ret; |
| |
| if ((ret = nss_ifb_config_msg_fill(&nim, dev, ifb_num, config, cb))) { |
| nss_igs_error("Error in setting up IFB %d config message\n", config); |
| return -1; |
| } |
| |
| down(&msg_response.sem); |
| ret = nss_if_tx_msg(nss_igs_get_context(), &nim); |
| if (ret != NSS_TX_SUCCESS) { |
| up(&msg_response.sem); |
| nss_igs_error("failed to send config message\n"); |
| return -1; |
| } |
| |
| msg_response.cond = 1; |
| if (!wait_event_timeout(msg_response.wq, msg_response.cond == 0, NSS_IFB_MSG_TIMEOUT)) { |
| nss_igs_error("config for attach interface msg timeout\n"); |
| up(&msg_response.sem); |
| return -1; |
| } else if (msg_response.response != NSS_CMN_RESPONSE_ACK) { |
| up(&msg_response.sem); |
| nss_igs_error("config for attach interface msg return with response: %d\n", msg_response.response); |
| return -1; |
| } |
| |
| up(&msg_response.sem); |
| return 0; |
| } |
| |
| /* |
| * nss_ifb_reset_nexthop() |
| * Send RESET NEXTHOP configure message to an IFB mapped interface. |
| */ |
| bool nss_ifb_reset_nexthop(struct nss_ifb_info *ifb_info) |
| { |
| int32_t if_num; |
| |
| spin_lock_bh(&nss_ifb_list_lock); |
| if (!(ifb_info->is_mapped)) { |
| nss_igs_info("%s IFB device mapped flag is not set\n", ifb_info->ifb_dev->name); |
| spin_unlock_bh(&nss_ifb_list_lock); |
| return true; |
| } |
| |
| /* |
| * Send RESET NEXTHOP config message to the mapped interface. |
| */ |
| if_num = nss_cmn_get_interface_number_by_dev_and_type(ifb_info->ifb_dev, NSS_DYNAMIC_INTERFACE_TYPE_IGS); |
| if (if_num < 0) { |
| nss_igs_error("No %s IFB device found in NSS firmware\n", ifb_info->ifb_dev->name); |
| } |
| |
| if (nss_ifb_config_msg_tx(ifb_info->map_dev, if_num, NSS_IFB_RESET_NEXTHOP, NULL) < 0) { |
| nss_igs_error("Sending RESET NEXTHOP config to %s dev failed\n", ifb_info->map_dev->name); |
| spin_unlock_bh(&nss_ifb_list_lock); |
| return false; |
| } |
| spin_unlock_bh(&nss_ifb_list_lock); |
| return true; |
| } |
| |
| /* |
| * nss_ifb_down() |
| * Send interface's DOWN configure message to an IFB interface. |
| */ |
| bool nss_ifb_down(struct nss_ifb_info *ifb_info) |
| { |
| int32_t ifb_num; |
| |
| spin_lock_bh(&nss_ifb_list_lock); |
| if (!(ifb_info->is_mapped)) { |
| nss_igs_info("%s IFB device mapped flag is not set\n", ifb_info->ifb_dev->name); |
| spin_unlock_bh(&nss_ifb_list_lock); |
| return true; |
| } |
| |
| /* |
| * Send interface's DOWN config message to an IFB interface. |
| */ |
| ifb_num = nss_cmn_get_interface_number_by_dev_and_type(ifb_info->ifb_dev, NSS_DYNAMIC_INTERFACE_TYPE_IGS); |
| if (ifb_num < 0) { |
| nss_igs_error("No %s IFB device found in NSS FW\n", ifb_info->ifb_dev->name); |
| spin_unlock_bh(&nss_ifb_list_lock); |
| return false; |
| } |
| |
| if (nss_ifb_config_msg_tx(ifb_info->ifb_dev, ifb_num, NSS_IFB_CLOSE, ifb_info) < 0) { |
| nss_igs_error("Sending unassign to %s dev failed\n", ifb_info->map_dev->name); |
| spin_unlock_bh(&nss_ifb_list_lock); |
| return false; |
| } |
| spin_unlock_bh(&nss_ifb_list_lock); |
| return true; |
| } |
| |
| /* |
| * nss_ifb_up() |
| * Send interface's UP configure message to an IFB interface. |
| */ |
| bool nss_ifb_up(struct nss_ifb_info *ifb_info) |
| { |
| int32_t ifb_num; |
| |
| spin_lock_bh(&nss_ifb_list_lock); |
| if (!(ifb_info->is_mapped)) { |
| nss_igs_info("%s IFB device mapped flag is not set\n", ifb_info->ifb_dev->name); |
| spin_unlock_bh(&nss_ifb_list_lock); |
| return true; |
| } |
| |
| /* |
| * Send interface's UP config message to an IFB interface. |
| */ |
| ifb_num = nss_cmn_get_interface_number_by_dev_and_type(ifb_info->ifb_dev, NSS_DYNAMIC_INTERFACE_TYPE_IGS); |
| if (ifb_num < 0) { |
| nss_igs_error("No %s IFB device found in NSS FW\n", ifb_info->ifb_dev->name); |
| spin_unlock_bh(&nss_ifb_list_lock); |
| return false; |
| } |
| |
| if (nss_ifb_config_msg_tx(ifb_info->ifb_dev, ifb_num, NSS_IFB_OPEN, ifb_info) < 0) { |
| nss_igs_error("Sending unassign to %s dev failed\n", ifb_info->map_dev->name); |
| spin_unlock_bh(&nss_ifb_list_lock); |
| return false; |
| } |
| spin_unlock_bh(&nss_ifb_list_lock); |
| return true; |
| } |
| |
| /* |
| * nss_ifb_clear_igs_node() |
| * Send CLEAR configure message to an IFB mapped interface. |
| */ |
| bool nss_ifb_clear_igs_node(struct nss_ifb_info *ifb_info) |
| { |
| int32_t if_num; |
| |
| spin_lock_bh(&nss_ifb_list_lock); |
| if (!(ifb_info->is_mapped)) { |
| nss_igs_info("%s IFB device mapped flag is not set\n", ifb_info->ifb_dev->name); |
| spin_unlock_bh(&nss_ifb_list_lock); |
| return true; |
| } |
| |
| /* |
| * Send CLEAR config message to the mapped interface. |
| */ |
| if_num = nss_cmn_get_interface_number_by_dev_and_type(ifb_info->ifb_dev, NSS_DYNAMIC_INTERFACE_TYPE_IGS); |
| if (if_num < 0) { |
| nss_igs_error("No %s IFB device found in NSS firmware\n", ifb_info->ifb_dev->name); |
| } |
| |
| if (nss_ifb_config_msg_tx(ifb_info->map_dev, if_num, NSS_IFB_CLEAR_IGS_NODE, ifb_info) < 0) { |
| nss_igs_error("Sending unassign to %s dev failed\n", ifb_info->map_dev->name); |
| spin_unlock_bh(&nss_ifb_list_lock); |
| return false; |
| } |
| spin_unlock_bh(&nss_ifb_list_lock); |
| return true; |
| } |
| |
| /* |
| * nss_ifb_init() |
| * Initialization API. |
| */ |
| void nss_ifb_init() |
| { |
| sema_init(&msg_response.sem, 1); |
| init_waitqueue_head(&msg_response.wq); |
| } |
| |
| /* |
| * nss_ifb_find_map_dev() |
| * Find and return the IFB mapped netdev in the ifb list. |
| */ |
| struct nss_ifb_info *nss_ifb_find_map_dev(struct net_device *dev) |
| { |
| struct nss_ifb_info *ifb_info; |
| |
| spin_lock_bh(&nss_ifb_list_lock); |
| list_for_each_entry(ifb_info, &nss_ifb_list, map_list) { |
| if (ifb_info->map_dev == dev) { |
| spin_unlock_bh(&nss_ifb_list_lock); |
| return ifb_info; |
| } |
| } |
| spin_unlock_bh(&nss_ifb_list_lock); |
| return NULL; |
| } |
| |
| /* |
| * nss_ifb_find_dev() |
| * Find and return the IFB netdev in the ifb list. |
| */ |
| struct nss_ifb_info *nss_ifb_find_dev(struct net_device *dev) |
| { |
| struct nss_ifb_info *ifb_info; |
| |
| spin_lock_bh(&nss_ifb_list_lock); |
| list_for_each_entry(ifb_info, &nss_ifb_list, map_list) { |
| if (ifb_info->ifb_dev == dev) { |
| spin_unlock_bh(&nss_ifb_list_lock); |
| return ifb_info; |
| } |
| } |
| spin_unlock_bh(&nss_ifb_list_lock); |
| return NULL; |
| } |
| |
| /* |
| * nss_ifb_bind() |
| * API to bind an IFB device with its requested mapped interface. |
| */ |
| int32_t nss_ifb_bind(struct nss_ifb_info *ifb_info, struct net_device *from_dev, |
| struct net_device *to_dev) |
| { |
| if (!ifb_info) { |
| /* |
| * IFB not present in local LL. |
| * Add the entry in LL. |
| */ |
| ifb_info = kmalloc(sizeof(*ifb_info), GFP_KERNEL); |
| if (!ifb_info) { |
| nss_igs_error("kmalloc failed\n"); |
| return -ENOMEM; |
| } |
| ifb_info->ifb_dev = to_dev; |
| ifb_info->map_dev = from_dev; |
| ifb_info->is_mapped = true; |
| nss_ifb_list_add(ifb_info); |
| } else { |
| /* |
| * IFB present in local LL and its is_mapped is not set. |
| * make the ifb_info's is_mapped to true again. |
| */ |
| spin_lock_bh(&nss_ifb_list_lock); |
| ifb_info->map_dev = from_dev; |
| ifb_info->is_mapped = true; |
| spin_unlock_bh(&nss_ifb_list_lock); |
| } |
| return 0; |
| } |
| |
| /* |
| * nss_ifb_update_dev_stats() |
| * IFB stats function to copy the common stats to the netdevice. |
| */ |
| static void nss_ifb_update_dev_stats(struct net_device *dev, struct nss_igs_stats_sync_msg *sync_stats) |
| { |
| struct pcpu_sw_netstats stats; |
| struct nss_cmn_node_stats *node_stats = &(sync_stats->node_stats); |
| |
| if (!dev) { |
| nss_igs_error("Device is NULL\n"); |
| return; |
| } |
| |
| u64_stats_init(&stats.syncp); |
| u64_stats_update_begin(&stats.syncp); |
| |
| /* |
| * In NSS firmware, the IFB interface's stats are getting updated |
| * post shaping. Therefore IFB interface's stats should be updated |
| * with NSS firmware's IFB TX stats only. |
| */ |
| stats.rx_packets = stats.tx_packets = node_stats->tx_packets; |
| stats.rx_bytes = stats.tx_bytes = node_stats->tx_bytes; |
| dev->stats.rx_dropped = dev->stats.tx_dropped += sync_stats->igs_stats.tx_dropped; |
| u64_stats_update_end(&stats.syncp); |
| |
| ifb_update_offload_stats(dev, &stats); |
| } |
| |
| /* |
| * nss_ifb_event_cb() |
| * Event Callback for IFB interface to receive events from NSS firmware. |
| */ |
| static void nss_ifb_event_cb(void *if_ctx, struct nss_cmn_msg *ncm) |
| { |
| struct net_device *netdev = if_ctx; |
| struct nss_igs_msg *nim = (struct nss_igs_msg *)ncm; |
| |
| switch (ncm->type) { |
| case NSS_IGS_MSG_SYNC_STATS: |
| nss_ifb_update_dev_stats(netdev, (struct nss_igs_stats_sync_msg *)&nim->msg.stats); |
| break; |
| |
| default: |
| nss_igs_error("%px: Unknown Event from NSS\n", netdev); |
| break; |
| } |
| } |
| |
| /* |
| * nss_ifb_delete_if() |
| * Delete an IFB interface in NSS Firmware. |
| */ |
| void nss_ifb_delete_if(int32_t if_num) |
| { |
| nss_igs_unregister_if(if_num); |
| if (nss_dynamic_interface_dealloc_node(if_num, NSS_DYNAMIC_INTERFACE_TYPE_IGS) |
| != NSS_TX_SUCCESS) { |
| nss_igs_error("Failed to de-alloc IFB dynamic interface\n"); |
| } |
| } |
| |
| /* |
| * nss_ifb_create_if() |
| * Create an IFB interface in NSS Firmware. |
| */ |
| int32_t nss_ifb_create_if(struct net_device *dev) |
| { |
| int32_t if_num, ret; |
| uint32_t features = 0; |
| struct nss_ctx_instance *nss_ctx; |
| |
| if_num = nss_dynamic_interface_alloc_node(NSS_DYNAMIC_INTERFACE_TYPE_IGS); |
| if (if_num < 0) { |
| nss_igs_error("%d interface creation failed\n", if_num); |
| return -1; |
| } |
| |
| nss_ctx = nss_igs_register_if(if_num, |
| NSS_DYNAMIC_INTERFACE_TYPE_IGS, |
| nss_ifb_event_cb, |
| dev, |
| features); |
| if (!nss_ctx) { |
| nss_igs_error("%d interface registration failed\n", if_num); |
| goto registration_fail; |
| } |
| return if_num; |
| |
| registration_fail: |
| ret = nss_dynamic_interface_dealloc_node(if_num, NSS_DYNAMIC_INTERFACE_TYPE_IGS); |
| if (ret != NSS_TX_SUCCESS) { |
| nss_igs_error("%d interface dealloc failed\n", if_num); |
| } |
| return -1; |
| } |