/**************************************************************************** Copyright(c) 2019 by Aerospace C.Power (Chongqing) Microelectronics. ALL RIGHTS RESERVED. This Information is proprietary to Aerospace C.Power (Chongqing) Microelectronics and MAY NOT be copied by any method or incorporated into another program without the express written consent of Aerospace C.Power. This Information or any portion thereof remains the property of Aerospace C.Power. The Information contained herein is believed to be accurate and Aerospace C.Power assumes no responsibility or liability for its use in any way and conveys no license or title under any patent or copyright and makes no representation or warranty that this Information is free from patent or copyright infringement. ****************************************************************************/ #include "mac_mm_sniffer.h" #include "iot_pkt.h" #include "mac_pdev.h" #include "mac_rx_hw.h" #include "mac_rx_buf_ring.h" #include "iot_eth.h" #include "iot_ntoh_api.h" #include "mac_data_api.h" #include "iot_crypto_aes_api.h" #include "mac_mm_sniffer_key.h" #include "mac_hwq_mgr.h" #include "mpdu_header.h" #include "iot_io.h" #include "mac_stream.h" #include "mac_peer.h" #include "hw_phy_api.h" #include "iot_board_api.h" #include "rf_hw_tonemap.h" #if HPLC_RF_DEV_SUPPORT #include "rf_rx_desc_reg_api.h" #endif #if (MAC_MM_SNIFFER_MODE) static mac_mm_sniffer_t *g_sniffer_ctxt = NULL; #if MAC_MM_SNIFFER_ENC_ENABLE static void mac_crypt_aes_ecb(iot_pkt_t *input, iot_pkt_t *output) { uint32_t data_len = iot_pkt_data_len(input); uint32_t ase_date_len = \ (data_len & 0xf) ? ((data_len & 0xfffffff0) + 0x10) : data_len; uint32_t avl_len = iot_pkt_data_len(output) + \ iot_pkt_block_len(output, IOT_PKT_BLOCK_TAIL); uint8_t *ptr_in = iot_pkt_data(input); uint8_t *ptr_out = iot_pkt_data(output); if (avl_len < ase_date_len) { IOT_ASSERT(0); } uint32_t offset = 0; uint32_t len = 0; while (data_len > 0) { if(data_len > 16){ len = 16; } else { len = data_len; } iot_crypto_aes_ecb(IOT_AES_ENCRYPT, len, ptr_in + offset, ptr_out + offset); data_len -= len; offset += len; } iot_pkt_set_tail(output, ptr_out + ase_date_len); } #else static void mac_crypt_aes_ecb(iot_pkt_t *input, iot_pkt_t *output) { (void)input; (void)output; } #endif /* MAC_MM_SNIFFER_ENC_ENABLE */ static void mac_send_frame_to_host(mac_mm_sniffer_t *sniffer_ctxt, iot_pkt_t *pkt, uint16_t type) { uint8_t *tmp = NULL; if (type < MAC_TO_ETH_TYPE_START || type > MAC_TO_ETH_TYPE_END) { IOT_ASSERT(0); } /* change byte order for ETH */ sniffer_ctxt->mac_to_eth_header.eth_type = iot_htons(type); tmp = iot_pkt_push(pkt, MAC_SNIFFER_HDR_RSV_LEN); IOT_ASSERT(tmp != NULL); os_mem_cpy(tmp, &sniffer_ctxt->mac_to_eth_header, \ MAC_SNIFFER_HDR_RSV_LEN); sniffer_ctxt->tx_pkt_ls.next = NULL; sniffer_ctxt->tx_pkt_ls.pkt = pkt; if (sniffer_ctxt->uart_handle) { iot_uart_send(sniffer_ctxt->uart_handle, pkt, NULL); } else { iot_eth_send_packet(&sniffer_ctxt->tx_pkt_ls); } return; } #if HPLC_RF_DEV_SUPPORT #if ((MAC_MM_SNIFFER_MODE == MAC_SNIFFER_TYPE_MSDU) || \ ((MAC_MM_SNIFFER_MODE == MAC_SNIFFER_TYPE_MSDU_UART))) static uint32_t mac_rf_mm_sniffer_reorder_msdu( mac_mm_sniffer_t *sniffer_ctxt, iot_pkt_t *rx_buf) { IOT_ASSERT(rx_buf != NULL); uint32_t pbsz; uint32_t proto = PHY_PROTO_TYPE_GET(); rx_fc_msg_t rx_fc_msg = { 0 }; rf_rx_mpdu_start *mpdu_st; rf_rx_pb_start *pb_st; rf_rx_pb_end *pb_ed; iot_pkt_t *tmp_buf; mac_stream_t *rx_stream = (mac_stream_t *)sniffer_ctxt->rx_ctxt.rf_rx_stream; uint8_t *tmp = iot_pkt_block_ptr(rx_buf, IOT_PKT_BLOCK_DATA); mpdu_st = (rf_rx_mpdu_start *)(tmp + MPDU_START_OFFSET); pb_st = (rf_rx_pb_start *)(tmp + PB_START_OFFSET); pb_ed = (rf_rx_pb_end *)(tmp + PB_END_OFFSET); mac_rf_get_rx_frm_msg_from_fc(proto, mac_rf_rx_mpdu_st_get_phr_addr(mpdu_st), &rx_fc_msg); if (pb_ed->rx_pb_crc_err == 1) { iot_printf("rf rx sof pb err\n"); return ERR_OK; } pbsz = phy_rf_get_pbsz(rx_fc_msg.rf_pb_sz_idx); if (rx_fc_msg.dst_tei != sniffer_ctxt->rx_ctxt.last_rx_dtei || rx_fc_msg.src_tei != sniffer_ctxt->rx_ctxt.last_rx_stei || rx_fc_msg.nid != sniffer_ctxt->rx_ctxt.last_rx_nid || rx_fc_msg.lid != sniffer_ctxt->rx_ctxt.last_rx_lid) { reorder_buf_flush(&rx_stream->msdu.rx.reorder_buf); sniffer_ctxt->rx_ctxt.last_rx_dtei = rx_fc_msg.dst_tei; sniffer_ctxt->rx_ctxt.last_rx_stei = rx_fc_msg.src_tei; sniffer_ctxt->rx_ctxt.last_rx_nid = rx_fc_msg.nid; sniffer_ctxt->rx_ctxt.last_rx_lid = rx_fc_msg.lid; } tmp_buf = iot_pkt_alloc(iot_pkt_data_len(rx_buf), PLC_MAC_RX_HW_MID); IOT_ASSERT(tmp_buf); iot_pkt_cpy(tmp_buf, rx_buf); reorder_buf_insert(&rx_stream->msdu.rx.reorder_buf, tmp_buf, pb_st->ssn, pb_st->msdu_start, pb_st->msdu_end, rx_fc_msg.retry, pbsz); return ERR_OK; } #endif /* ((MAC_MM_SNIFFER_MODE == MAC_SNIFFER_TYPE_MSDU) || \ ((MAC_MM_SNIFFER_MODE == MAC_SNIFFER_TYPE_MSDU_UART))) */ /* rf mpdu rx cb */ static uint32_t mac_rf_mm_sniffer_rx_mpdu_handler(void *arg, iot_pkt_t *rx_buf) { (void)arg; #if PLC_MAC_TX_DEBUG_LOG iot_printf("%s\n", __FUNCTION__); #endif IOT_ASSERT(rx_buf && arg); uint8_t *tmp_ptr = NULL; rf_rx_mpdu_start *mpdu_st; uint32_t delimiter = 0; iot_pkt_t *tmp_pkt = NULL; uint16_t fc_len = mac_get_mpdu_fc_len(PHY_PROTO_TYPE_GET()); uint32_t ret = CON_PKT_NOT_CARE; tmp_ptr = iot_pkt_block_ptr(rx_buf, IOT_PKT_BLOCK_DATA); mpdu_st = (rf_rx_mpdu_start *)(tmp_ptr + MPDU_START_OFFSET); delimiter = mac_get_delimi_from_fc(PHY_PROTO_TYPE_GET(), mac_rf_rx_mpdu_st_get_phr_addr(mpdu_st)); if (delimiter == FC_DELIM_SOF) { #if ((MAC_MM_SNIFFER_MODE == MAC_SNIFFER_TYPE_MSDU) || \ ((MAC_MM_SNIFFER_MODE == MAC_SNIFFER_TYPE_MSDU_UART))) /* reorder msdu */ if (ERR_OK == mac_rf_mm_sniffer_reorder_msdu(g_sniffer_ctxt, rx_buf)) { rx_buf = NULL; } #endif if (rx_buf) { iot_pkt_free(rx_buf); } ret = CON_PKT_FREE; goto out; } tmp_pkt = iot_pkt_alloc(iot_pkt_data_len(rx_buf) + fc_len + MAC_SNIFFER_HDR_RSV_LEN + MAC_SNIFFER_AES_RSV_LEN, PLC_MAC_RX_HW_MID); IOT_ASSERT(tmp_pkt); tmp_ptr = iot_pkt_put(tmp_pkt, iot_pkt_data_len(rx_buf) + fc_len); IOT_ASSERT(tmp_ptr); tmp_ptr = iot_pkt_reserve(tmp_pkt, MAC_SNIFFER_HDR_RSV_LEN); IOT_ASSERT(tmp_ptr); os_mem_cpy(tmp_ptr, iot_pkt_data(rx_buf), PB_PAYLOAD_OFFSET); os_mem_cpy(tmp_ptr + PB_PAYLOAD_OFFSET, mac_rf_rx_mpdu_st_get_phr_addr(mpdu_st), fc_len); os_mem_cpy(tmp_ptr + PB_PAYLOAD_OFFSET + fc_len, iot_pkt_data(rx_buf) + PB_PAYLOAD_OFFSET, iot_pkt_data_len(rx_buf) - PB_PAYLOAD_OFFSET); mac_crypt_aes_ecb(tmp_pkt, tmp_pkt); mac_send_frame_to_host(g_sniffer_ctxt, tmp_pkt, MAC_TO_ETH_TYPE_RF_PB); out: return ret; } void mac_rf_sniffer_rx_ctxt_init(mac_mm_sniffer_t *sniffer_ctxt) { IOT_ASSERT(sniffer_ctxt); mac_stream_t *rf_stream = NULL; mac_vdev_t *dft_vdev = (mac_vdev_t*)sniffer_ctxt->ref_vdev; mac_peer_t *self_peer = dft_vdev->self_peer; IOT_ASSERT(self_peer); rf_stream = find_stream(self_peer, PLC_NID_INVALID, self_peer->tei, 0, IS_RX_STREAM, IS_RF_STREAM); if (!rf_stream) { mac_stream_alloc(self_peer, IS_RX_STREAM, 0, IS_RF_STREAM, &rf_stream); IOT_ASSERT(rf_stream); } sniffer_ctxt->rx_ctxt.rf_rx_stream = rf_stream; } void mac_rf_sniffer_ring_init() { mac_rf_pdev_t *mac_rf_pdev = get_rf_pdev_ptr(PLC_PDEV_ID, RF_PDEV_ID); if (mac_rf_pdev) { rx_buf_ring_set_callback(&mac_rf_pdev->ring_hdl, mac_rf_mm_sniffer_rx_mpdu_handler); } } #else /* HPLC_RF_DEV_SUPPORT */ void mac_rf_sniffer_rx_ctxt_init(mac_mm_sniffer_t *sniffer_ctxt) { (void)sniffer_ctxt; } void mac_rf_sniffer_ring_init() { } #endif /* HPLC_RF_DEV_SUPPORT */ uint32_t mac_mm_sniffer_rx_msdu(uint32_t proto, iot_pkt_t *msdu_buf) { #if PLC_MAC_TX_DEBUG_LOG iot_printf("%s\n", __FUNCTION__); #endif uint8_t *tmp_ptr = NULL; uint16_t type = 0; iot_pkt_t *tmp_pkt = iot_pkt_alloc(iot_pkt_data_len(msdu_buf) + \ sizeof(mac_rx_info_t) + MAC_SNIFFER_HDR_RSV_LEN + MAC_SNIFFER_AES_RSV_LEN, \ PLC_MAC_RX_HW_MID); IOT_ASSERT(tmp_pkt); tmp_ptr = iot_pkt_put(tmp_pkt, iot_pkt_data_len(msdu_buf) + \ sizeof(mac_rx_info_t)); IOT_ASSERT(tmp_ptr); tmp_ptr = iot_pkt_reserve(tmp_pkt, MAC_SNIFFER_HDR_RSV_LEN); IOT_ASSERT(tmp_ptr); os_mem_cpy(tmp_ptr, iot_pkt_data(msdu_buf) - sizeof(mac_rx_info_t), \ iot_pkt_data_len(msdu_buf) + sizeof(mac_rx_info_t)); if (proto == PLC_PROTO_TYPE_SG) { #if SUPPORT_IEEE_1901 type = MAC_TO_ETH_TYPE_MSDU_1901; #else type = MAC_TO_ETH_TYPE_MSDU_GW; #endif /* SUPPORT_IEEE_1901 */ } else if (proto == PLC_PROTO_TYPE_SPG) { type = MAC_TO_ETH_TYPE_MSDU_NW; } mac_crypt_aes_ecb(tmp_pkt, tmp_pkt); mac_send_frame_to_host(g_sniffer_ctxt, tmp_pkt, type); return ERR_OK; } #if ((MAC_MM_SNIFFER_MODE == MAC_SNIFFER_TYPE_MSDU) || \ ((MAC_MM_SNIFFER_MODE == MAC_SNIFFER_TYPE_MSDU_UART))) static uint32_t mac_mm_sniffer_reorder_msdu( mac_mm_sniffer_t *sniffer_ctxt, iot_pkt_t *rx_buf) { IOT_ASSERT(rx_buf != NULL); uint32_t proto = PHY_PROTO_TYPE_GET(); uint32_t pbsz, ret; rx_fc_msg_t rx_fc_msg = { 0 }; rx_mpdu_start *mpdu_st; rx_pb_start *pb_st; rx_pb_end *pb_ed; iot_pkt_t *tmp_buf; mac_stream_t *rx_stream = (mac_stream_t *)sniffer_ctxt->rx_ctxt.rx_stream; uint8_t *tmp = iot_pkt_block_ptr(rx_buf, IOT_PKT_BLOCK_DATA); mpdu_st = (rx_mpdu_start *)(tmp + MPDU_START_OFFSET); pb_st = (rx_pb_start *)(tmp + PB_START_OFFSET); pb_ed = (rx_pb_end *)(tmp + PB_END_OFFSET); mac_get_rx_frm_msg_from_fc(PHY_PROTO_TYPE_GET(), \ mac_rx_mpdu_st_get_fc_addr(mpdu_st), &rx_fc_msg); if (pb_ed->rx_pb_crc_err == 1) { iot_printf("rx sof pb err\n"); return ERR_OK; } /* fix spg msdu_start/msdu_end */ if (PLC_PROTO_TYPE_SPG == proto) { /** * sof msdu_start and msdu_end * fields always 0 in SPG protocol */ if (0 == mac_rx_pb_st_get_ssn(pb_st)) { mac_rx_pb_st_set_msdu_start(pb_st, 1); } /* spg must be used long mpdu tx */ if (mac_rx_pb_st_get_last_pb(pb_st) && (mac_rx_mpdu_st_get_rx_pb_num(mpdu_st) \ == (mac_rx_pb_st_get_ssn(pb_st) + 1))){ mac_rx_pb_st_set_msdu_end(pb_st, 1); } } ret = phy_get_pb_size(proto, rx_fc_msg.tmi, rx_fc_msg.tmi_ext, &pbsz); if (ret!= ERR_OK) { return ERR_FAIL; } if (rx_fc_msg.dst_tei != sniffer_ctxt->rx_ctxt.last_rx_dtei || \ rx_fc_msg.src_tei != sniffer_ctxt->rx_ctxt.last_rx_stei || \ rx_fc_msg.nid != sniffer_ctxt->rx_ctxt.last_rx_nid || \ rx_fc_msg.lid != sniffer_ctxt->rx_ctxt.last_rx_lid) { reorder_buf_flush(&rx_stream->msdu.rx.reorder_buf); sniffer_ctxt->rx_ctxt.last_rx_dtei = rx_fc_msg.dst_tei; sniffer_ctxt->rx_ctxt.last_rx_stei = rx_fc_msg.src_tei; sniffer_ctxt->rx_ctxt.last_rx_nid = rx_fc_msg.nid; sniffer_ctxt->rx_ctxt.last_rx_lid = rx_fc_msg.lid; } tmp_buf = iot_pkt_alloc(iot_pkt_data_len(rx_buf), PLC_MAC_RX_HW_MID); IOT_ASSERT(tmp_buf); iot_pkt_cpy(tmp_buf, rx_buf); reorder_buf_insert(&rx_stream->msdu.rx.reorder_buf, \ tmp_buf, pb_st->ssn, pb_st->msdu_start, pb_st->msdu_end, \ rx_fc_msg.retry, pbsz); return ERR_OK; } #endif /* ((MAC_MM_SNIFFER_MODE == MAC_SNIFFER_TYPE_MSDU) || \ ((MAC_MM_SNIFFER_MODE == MAC_SNIFFER_TYPE_MSDU_UART))) */ /* mpdu rx cb */ static uint32_t mac_mm_sniffer_rx_mpdu_handler(void *arg, iot_pkt_t *rx_buf) { (void)arg; #if PLC_MAC_TX_DEBUG_LOG iot_printf("%s\n", __FUNCTION__); #endif IOT_ASSERT(rx_buf && arg); uint8_t *tmp_ptr = NULL; rx_mpdu_start *mpdu_st; uint32_t delimiter = 0; iot_pkt_t *tmp_pkt = NULL; uint16_t fc_len = mac_get_mpdu_fc_len(PHY_PROTO_TYPE_GET()); uint32_t ret = CON_PKT_NOT_CARE; uint16_t type; tmp_ptr = iot_pkt_block_ptr(rx_buf, IOT_PKT_BLOCK_DATA); mpdu_st = (rx_mpdu_start *)(tmp_ptr + MPDU_START_OFFSET); delimiter = mac_get_delimi_from_fc(PHY_PROTO_TYPE_GET(), mac_rx_mpdu_st_get_fc_addr(mpdu_st)); if (delimiter == FC_DELIM_SOF) { #if ((MAC_MM_SNIFFER_MODE == MAC_SNIFFER_TYPE_MSDU) || \ ((MAC_MM_SNIFFER_MODE == MAC_SNIFFER_TYPE_MSDU_UART))) /* reorder msdu */ if (ERR_OK == mac_mm_sniffer_reorder_msdu(g_sniffer_ctxt, rx_buf)) { rx_buf = NULL; } #endif if (rx_buf) { iot_pkt_free(rx_buf); } ret = CON_PKT_FREE; goto out; } tmp_pkt = iot_pkt_alloc(iot_pkt_data_len(rx_buf) + fc_len + MAC_SNIFFER_HDR_RSV_LEN + MAC_SNIFFER_AES_RSV_LEN, PLC_MAC_RX_HW_MID); IOT_ASSERT(tmp_pkt); tmp_ptr = iot_pkt_put(tmp_pkt, iot_pkt_data_len(rx_buf) + fc_len); IOT_ASSERT(tmp_ptr); tmp_ptr = iot_pkt_reserve(tmp_pkt, MAC_SNIFFER_HDR_RSV_LEN); IOT_ASSERT(tmp_ptr); #if SUPPORT_IEEE_1901 mac_fc_sg_to_i1901(PHY_PROTO_TYPE_GET(), mpdu_st); #endif os_mem_cpy(tmp_ptr, iot_pkt_data(rx_buf), PB_PAYLOAD_OFFSET); os_mem_cpy(tmp_ptr + PB_PAYLOAD_OFFSET, mac_rx_mpdu_st_get_fc_addr(mpdu_st), fc_len); os_mem_cpy(tmp_ptr + PB_PAYLOAD_OFFSET + fc_len, iot_pkt_data(rx_buf) + PB_PAYLOAD_OFFSET, iot_pkt_data_len(rx_buf) - PB_PAYLOAD_OFFSET); mac_crypt_aes_ecb(tmp_pkt, tmp_pkt); #if SUPPORT_IEEE_1901 type = MAC_TO_ETH_TYPE_PB_1901; #else type = MAC_TO_ETH_TYPE_PB; #endif /* SUPPORT_IEEE_1901 */ mac_send_frame_to_host(g_sniffer_ctxt, tmp_pkt, type); out: return ret; } static void mac_sniffer_rx_ctxt_init(mac_mm_sniffer_t *sniffer_ctxt) { IOT_ASSERT(sniffer_ctxt); mac_stream_t *plc_stream = NULL; mac_vdev_t *dft_vdev = (mac_vdev_t*)sniffer_ctxt->ref_vdev; mac_peer_t *self_peer = dft_vdev->self_peer; IOT_ASSERT(self_peer); plc_stream = find_stream(self_peer, PLC_NID_INVALID, self_peer->tei, 0, IS_RX_STREAM, IS_PLC_STREAM); if (!plc_stream) { mac_stream_alloc(self_peer, IS_RX_STREAM, 0, IS_PLC_STREAM, &plc_stream); IOT_ASSERT(plc_stream); } sniffer_ctxt->rx_ctxt.rx_stream = plc_stream; mac_rf_sniffer_rx_ctxt_init(sniffer_ctxt); } #if (MAC_MM_SNIFFER_MODE == MAC_SNIFFER_TYPE_MSDU_UART) static void mac_sniffer_uart_recv(uint8_t* buffer, uint32_t buffer_len, bool_t is_full_frame, uint32_t invalid_data_len) { (void)buffer; (void)buffer_len; (void)is_full_frame; (void)invalid_data_len; } static void mac_sniffer_uart_init(mac_mm_sniffer_t *sniffer_ctxt) { uint8_t port = iot_board_get_uart(UART_METER_PORT); if (sniffer_ctxt->uart_handle) { return; } sniffer_ctxt->uart_handle = iot_uart_open(port, mac_sniffer_uart_recv, 2048, NULL); if (sniffer_ctxt->uart_handle == NULL) { iot_printf("%s open uart port: %d fail\n", __FUNCTION__, port); IOT_ASSERT(0); } iot_uart_set_config(sniffer_ctxt->uart_handle, 1000000, IOT_UART_PARITY_NONE, 8, 1); } #else static void mac_sniffer_uart_init(mac_mm_sniffer_t *sniffer_ctxt) { sniffer_ctxt->uart_handle = NULL; } #endif /* (MAC_MM_SNIFFER_MODE == MAC_SNIFFER_TYPE_MSDU_UART) */ void mac_mm_sniffer_init(void *vdev) { IOT_ASSERT(vdev); mac_vdev_t *vdev_ptr = (mac_vdev_t*)vdev; mac_pdev_t *pdev_ptr = g_mac_pdev[vdev_ptr->ref_pdev_id]; if(!g_sniffer_ctxt) { g_sniffer_ctxt = os_mem_malloc(PLC_MAC_COMMON_MID, sizeof(*g_sniffer_ctxt)); } IOT_ASSERT(g_sniffer_ctxt); rx_buf_ring_set_callback(&pdev_ptr->ring_hdl, mac_mm_sniffer_rx_mpdu_handler); mac_rf_sniffer_ring_init(); g_sniffer_ctxt->ref_vdev = vdev; mac_sniffer_uart_init(g_sniffer_ctxt); g_sniffer_ctxt->mac_to_eth_header.dst_mac[0] = 0x11; g_sniffer_ctxt->mac_to_eth_header.dst_mac[1] = 0x22; g_sniffer_ctxt->mac_to_eth_header.dst_mac[2] = 0x33; g_sniffer_ctxt->mac_to_eth_header.dst_mac[3] = 0x44; g_sniffer_ctxt->mac_to_eth_header.dst_mac[4] = 0x55; g_sniffer_ctxt->mac_to_eth_header.dst_mac[5] = 0x66; g_sniffer_ctxt->mac_to_eth_header.src_mac[0] = 0x55; g_sniffer_ctxt->mac_to_eth_header.src_mac[1] = 0x66; g_sniffer_ctxt->mac_to_eth_header.src_mac[2] = 0x77; g_sniffer_ctxt->mac_to_eth_header.src_mac[3] = 0x88; g_sniffer_ctxt->mac_to_eth_header.src_mac[4] = 0x99; g_sniffer_ctxt->mac_to_eth_header.src_mac[5] = 0xAA; g_sniffer_ctxt->mac_to_eth_header.eth_type = 0xFFFF; g_sniffer_ctxt->tx_pkt_ls.next = NULL; g_sniffer_ctxt->tx_pkt_ls.pkt = NULL; mac_sniffer_rx_ctxt_init(g_sniffer_ctxt); iot_crypto_aes_setkey_enc(aes_key, 256); mac_block_all_tx_ena(&pdev_ptr->hwq_hdl, true); /* disable sw agc */ phy_swagc_set(false); return; } #endif /* MAC_MM_SNIFFER_MODE */