/* Copyright (c) 2013, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ipa_i.h" #define TETH_BRIDGE_DRV_NAME "ipa_tethering_bridge" #define TETH_DBG(fmt, args...) \ pr_debug(TETH_BRIDGE_DRV_NAME " %s:%d " fmt, \ __func__, __LINE__, ## args) #define TETH_DBG_FUNC_ENTRY() \ pr_debug(TETH_BRIDGE_DRV_NAME " %s:%d ENTRY\n", __func__, __LINE__) #define TETH_DBG_FUNC_EXIT() \ pr_debug(TETH_BRIDGE_DRV_NAME " %s:%d EXIT\n", __func__, __LINE__) #define TETH_ERR(fmt, args...) \ pr_err(TETH_BRIDGE_DRV_NAME " %s:%d " fmt, __func__, __LINE__, ## args) #define USB_ETH_HDR_NAME_IPV4 "usb_bridge_ipv4" #define USB_ETH_HDR_NAME_IPV6 "usb_bridge_ipv6" #define A2_ETH_HDR_NAME_IPV4 "a2_bridge_ipv4" #define A2_ETH_HDR_NAME_IPV6 "a2_bridge_ipv6" #define USB_TO_A2_RT_TBL_NAME_IPV4 "usb_a2_rt_ipv4" #define A2_TO_USB_RT_TBL_NAME_IPV4 "a2_usb_rt_ipv4" #define USB_TO_A2_RT_TBL_NAME_IPV6 "usb_a2_rt_ipv6" #define A2_TO_USB_RT_TBL_NAME_IPV6 "a2_usb_rt_ipv6" #define MBIM_HEADER_NAME "mbim_header" #define TETH_DEFAULT_AGGR_TIME_LIMIT 1 #define ETHERTYPE_IPV4 0x0800 #define ETHERTYPE_IPV6 0x86DD #define TETH_AGGR_MAX_DATAGRAMS_DEFAULT 16 #define TETH_AGGR_MAX_AGGR_PACKET_SIZE_DEFAULT (8*1024) #define TETH_MTU_BYTE 1500 #define TETH_INACTIVITY_TIME_MSEC (1000) #define TETH_WORKQUEUE_NAME "tethering_bridge_wq" #define TETH_TOTAL_HDR_ENTRIES 8 #define TETH_TOTAL_RT_ENTRIES_IP 3 #define TETH_TOTAL_FLT_ENTRIES_IP 2 #define TETH_IP_FAMILIES 2 #define METADATA_SHFT 16 #define METADATA_MASK 0x00FF0000 #define TETH_NUM_CHANNELS 12 #define TETH_METADATA_LEN 4 #define MAX_MBIM_STREAMS 8 /** * enum teth_init_status - bridge initialization state * (NOT_INITIALIZED / INITIALIZED/ ERROR) */ enum teth_init_status { TETH_NOT_INITIALIZED, TETH_INITIALIZED, TETH_INITIALIZATION_ERROR, }; /** * enum teth_ch_type - channel type (Embedded or Tethered) */ enum teth_ch_type { TETH_EMBEDDED_CH, TETH_TETHERED_CH, }; /** * struct mac_addresses_type - store host PC and device MAC addresses * @host_pc_mac_addr: MAC address of the host PC * @host_pc_mac_addr_known: is the MAC address of the host PC known ? * @device_mac_addr: MAC address of the device * @device_mac_addr_known: is the MAC address of the device known ? */ struct mac_addresses_type { u8 host_pc_mac_addr[ETH_ALEN]; bool host_pc_mac_addr_known; u8 device_mac_addr[ETH_ALEN]; bool device_mac_addr_known; }; /** * struct stats - driver statistics, viewable using debugfs * @a2_to_usb_num_sw_tx_packets: number of packets bridged from A2 to USB using * the SW bridge * @usb_to_a2_num_sw_tx_packets: number of packets bridged from USB to A2 using * the SW bridge * @num_sw_tx_packets_during_resource_wakeup: number of packets bridged during a * resource wakeup period, there is a special treatment for these kind of * packets */ struct stats { u64 a2_to_usb_num_sw_tx_packets; u64 usb_to_a2_num_sw_tx_packets; u64 num_sw_tx_packets_during_resource_wakeup; }; /** * struct hw_bridge_work_wrap - wrapper for the channel number which is sent * when using a workqueue * @work: used by the workqueue * @lcid: logic channel number */ struct hw_bridge_work_wrap { struct work_struct comp_hw_bridge_work; u16 lcid; }; /** * struct teth_bridge_ctx - Tethering bridge driver context information * @usb_ipa_pipe_hdl: USB to IPA pipe handle * @ipa_usb_pipe_hdl: IPA to USB pipe handle * @is_connected: is the tethered bridge connected ? * @link_protocol: IP / Ethernet * @is_hw_bridge_complete: is HW bridge setup ? * @aggr_params: aggregation parmeters * @aggr_params_known: are the aggregation parameters known ? * @hw_bridge_work_wrap: used for setting up the HW bridge using a workqueue * @comp_hw_bridge_in_progress: true when the HW bridge setup is in progress * @ch_type: Is this channel tethered or embedded ? * @routing_del: array of routing rules handles, one array for IPv4 and one for * IPv6 * @filtering_del: array of routing rules handles, one array for IPv4 and one * for IPv6 */ struct logic_ch_info { u32 usb_ipa_pipe_hdl; u32 ipa_usb_pipe_hdl; bool is_connected; enum teth_link_protocol_type link_protocol; bool is_hw_bridge_complete; struct teth_aggr_params aggr_params; bool aggr_params_known; struct hw_bridge_work_wrap hw_bridge_work; bool comp_hw_bridge_in_progress; enum teth_ch_type ch_type; struct ipa_ioc_del_rt_rule *routing_del[TETH_IP_FAMILIES]; struct ipa_ioc_del_flt_rule *filtering_del[TETH_IP_FAMILIES]; }; /** * struct teth_bridge_ctx - Tethering bridge driver context information * @class: kernel class pointer * @dev_num: kernel device number * @dev: kernel device struct pointer * @cdev: kernel character device struct * @a2_ipa_pipe_hdl: A2 to IPA pipe handle * @ipa_a2_pipe_hdl: IPA to A2 pipe handle * @mac_addresses: Struct which holds host pc and device MAC addresses, relevant * in ethernet mode only * @tethering_mode: Rmnet / MBIM * @is_bridge_prod_up: completion object signaled when the bridge producer * finished its resource request procedure * @is_bridge_prod_down: completion object signaled when the bridge producer * finished its resource release procedure * @aggr_caps: aggregation capabilities * @stats: statistics, how many packets were transmitted using the SW bridge * @teth_wq: dedicated workqueue, used for setting up the HW bridge and for * sending packets using the SW bridge when the system is waking up from power * collapse * @a2_ipa_hdr_len: A2 to IPA header length, used for configuring the A2 * endpoint for header removal * @ipa_a2_hdr_len: IPA to A2 header length, used for configuring the A2 * endpoint for header removal * @hdr_del: array to store the headers handles in order to delete them later * @ch_info: array of logic_ch_info, used to hold channel information * @logic_ch_num: the total logical channels number * @ch_init_cnt: count the initialized channels * @init_status: bridge initialization state * @init_mutex: for the initialization, connect and disconnect synchronization * @request_resource_mutex: for the teth_request_resource synchronization * @debugfs_lcid: logical channel number for debugfs entries */ struct teth_bridge_ctx { struct class *class; dev_t dev_num; struct device *dev; struct cdev cdev; u32 a2_ipa_pipe_hdl; u32 ipa_a2_pipe_hdl; struct mac_addresses_type mac_addresses; enum teth_tethering_mode tethering_mode; u16 mbim_stream_id_to_channel_id[IPA_MBIM_MAX_STREAM_NUM]; struct completion is_bridge_prod_up; struct completion is_bridge_prod_down; struct teth_aggr_capabilities *aggr_caps; struct stats stats; struct workqueue_struct *teth_wq; u16 a2_ipa_hdr_len; u16 ipa_a2_hdr_len; struct ipa_ioc_del_hdr *hdr_del; struct logic_ch_info *ch_info; u16 ch_init_cnt; enum teth_init_status init_status; struct mutex init_mutex; struct mutex request_resource_mutex; u16 debugfs_lcid; }; static struct teth_bridge_ctx *teth_ctx; enum teth_packet_direction { TETH_USB_TO_A2, TETH_A2_TO_USB, }; /** * struct teth_work - wrapper for an skb which is sent using a workqueue * @work: used by the workqueue * @skb: pointer to the skb to be sent * @dir: direction of send, A2 to USB or USB to A2 * @lcid: logical channel number */ struct teth_work { struct work_struct work; struct sk_buff *skb; enum teth_packet_direction dir; enum a2_mux_logical_channel_id lcid; struct ipa_tx_meta metadata; }; #ifdef CONFIG_DEBUG_FS #define TETH_MAX_MSG_LEN 512 static char dbg_buff[TETH_MAX_MSG_LEN]; #endif static u16 get_channel_id_from_client_prod(enum ipa_client_type client) { TETH_DBG("client_id=%d\n", client); if (client == IPA_CLIENT_USB_PROD) return A2_MUX_TETHERED_0; if (client > IPA_CLIENT_USB_PROD || client <= IPA_CLIENT_PROD) { TETH_ERR("%s: Invalid client type %d\n", __func__, client); return A2_MUX_TETHERED_0; } return client - IPA_CLIENT_USB2_PROD + A2_MUX_MULTI_RMNET_10; } static u16 get_cons_client(enum a2_mux_logical_channel_id lcid) { TETH_DBG("lcid=%d\n", lcid); if (lcid < A2_MUX_TETHERED_0 || lcid >= A2_MUX_NUM_CHANNELS || lcid == A2_MUX_RESERVED_9) { TETH_ERR("%s: Invalid lcid %d\n", __func__, lcid); return IPA_CLIENT_USB_CONS; } if (lcid == A2_MUX_TETHERED_0) return IPA_CLIENT_USB_CONS; return lcid - A2_MUX_MULTI_RMNET_10 + IPA_CLIENT_USB2_CONS; } static u16 get_prod_client(enum a2_mux_logical_channel_id lcid) { TETH_DBG("lcid=%d\n", lcid); if (lcid < A2_MUX_TETHERED_0 || lcid >= A2_MUX_NUM_CHANNELS || lcid == A2_MUX_RESERVED_9) { TETH_ERR("%s: Invalid lcid %d\n", __func__, lcid); return IPA_CLIENT_USB_PROD; } if (lcid == A2_MUX_TETHERED_0) return IPA_CLIENT_USB_PROD; return lcid - A2_MUX_MULTI_RMNET_10 + IPA_CLIENT_USB2_PROD; } static u16 get_ch_info_idx(enum a2_mux_logical_channel_id lcid) { if (lcid < A2_MUX_TETHERED_0 || lcid >= A2_MUX_NUM_CHANNELS || lcid == A2_MUX_RESERVED_9) { TETH_ERR("%s: Invalid lcid %d\n", __func__, lcid); return 0; } if (lcid == A2_MUX_TETHERED_0) return 0; return lcid - A2_MUX_RESERVED_9; } static int get_completed_ch_num(void) { int idx; int cnt = 0; for (idx = 0; idx < TETH_NUM_CHANNELS; idx++) { if (teth_ctx->ch_info[idx].is_hw_bridge_complete) cnt++; } TETH_DBG("completed_ch_num=%d\n", cnt); return cnt; } static int get_connected_ch_num(void) { int idx; int cnt = 0; for (idx = 0; idx < TETH_NUM_CHANNELS; idx++) { if (teth_ctx->ch_info[idx].is_connected) cnt++; } TETH_DBG("connected_ch_num=%d\n", cnt); return cnt; } /** * add_eth_hdrs_internal() - add Ethernet headers to IPA * @hdr_name_ipv4: header name for IPv4 * @hdr_name_ipv6: header name for IPv6 * @src_mac_addr: source MAC address * @dst_mac_addr: destination MAC address * * This function is called only when link protocol is Ethernet */ static int add_eth_hdrs_internal(char *hdr_name_ipv4, char *hdr_name_ipv6, u8 *src_mac_addr, u8 *dst_mac_addr) { int res; struct ipa_ioc_add_hdr *hdrs; struct ethhdr hdr_ipv4; struct ethhdr hdr_ipv6; int idx1; TETH_DBG_FUNC_ENTRY(); memcpy(hdr_ipv4.h_source, src_mac_addr, ETH_ALEN); memcpy(hdr_ipv4.h_dest, dst_mac_addr, ETH_ALEN); hdr_ipv4.h_proto = htons(ETHERTYPE_IPV4); memcpy(hdr_ipv6.h_source, src_mac_addr, ETH_ALEN); memcpy(hdr_ipv6.h_dest, dst_mac_addr, ETH_ALEN); hdr_ipv6.h_proto = htons(ETHERTYPE_IPV6); /* Add headers to the header insertion tables */ hdrs = kzalloc(sizeof(struct ipa_ioc_add_hdr) + 2 * sizeof(struct ipa_hdr_add), GFP_KERNEL); if (hdrs == NULL) { TETH_ERR("Failed allocating memory for headers !\n"); return -ENOMEM; } hdrs->commit = 0; hdrs->num_hdrs = 2; /* Ethernet IPv4 header */ strlcpy(hdrs->hdr[0].name, hdr_name_ipv4, IPA_RESOURCE_NAME_MAX); hdrs->hdr[0].hdr_len = ETH_HLEN; memcpy(hdrs->hdr[0].hdr, &hdr_ipv4, ETH_HLEN); /* Ethernet IPv6 header */ strlcpy(hdrs->hdr[1].name, hdr_name_ipv6, IPA_RESOURCE_NAME_MAX); hdrs->hdr[1].hdr_len = ETH_HLEN; memcpy(hdrs->hdr[1].hdr, &hdr_ipv6, ETH_HLEN); res = ipa_add_hdr(hdrs); if (res || hdrs->hdr[0].status || hdrs->hdr[1].status) TETH_ERR("Header insertion failed\n"); /* Save the headers handles in order to delete them later */ for (idx1 = 0; idx1 < hdrs->num_hdrs; idx1++) { int idx2 = teth_ctx->hdr_del->num_hdls++; teth_ctx->hdr_del->hdl[idx2].hdl = hdrs->hdr[idx1].hdr_hdl; } kfree(hdrs); TETH_DBG_FUNC_EXIT(); return res; } /** * add_eth_hdrs() - add Ethernet headers to IPA * This function is called only when link protocol is Ethernet */ static int add_eth_hdrs(void) { int res; /* Add a header entry for USB */ res = add_eth_hdrs_internal(USB_ETH_HDR_NAME_IPV4, USB_ETH_HDR_NAME_IPV6, teth_ctx->mac_addresses.device_mac_addr, teth_ctx->mac_addresses.host_pc_mac_addr); if (res) { TETH_ERR("Failed adding USB Ethernet header\n"); goto bail; } TETH_DBG("Added USB Ethernet headers (IPv4 / IPv6)\n"); /* Add a header entry for A2 */ res = add_eth_hdrs_internal(A2_ETH_HDR_NAME_IPV4, A2_ETH_HDR_NAME_IPV6, teth_ctx->mac_addresses.host_pc_mac_addr, teth_ctx->mac_addresses.device_mac_addr); if (res) { TETH_ERR("Failed adding A2 Ethernet header\n"); goto bail; } TETH_DBG("Added A2 Ethernet headers (IPv4 / IPv6\n"); bail: return res; } /** * configure_ipa_header_block_a2_internal() - configures IPA end-point registers * (header removal/insertion for IPA<->A2 pipes) * @a2_ipa_hdr_len: Header length in bytes to be added/removed. * @ipa_a2_hdr_len: Header length in bytes to be added/removed. */ static int configure_ipa_header_block_a2_internal(u32 a2_ipa_hdr_len, u32 ipa_a2_hdr_len) { struct ipa_ep_cfg_hdr hdr_cfg; int res; TETH_DBG_FUNC_ENTRY(); /* Configure header removal for the A2->IPA pipe */ memset(&hdr_cfg, 0, sizeof(hdr_cfg)); hdr_cfg.hdr_len = a2_ipa_hdr_len; teth_ctx->a2_ipa_hdr_len = a2_ipa_hdr_len; res = ipa_cfg_ep_hdr(teth_ctx->a2_ipa_pipe_hdl, &hdr_cfg); if (res) { TETH_ERR("Header removal config for A2->IPA pipe failed\n"); goto bail; } /* Configure header insertion for the IPA->A2 pipe */ hdr_cfg.hdr_len = ipa_a2_hdr_len; teth_ctx->ipa_a2_hdr_len = ipa_a2_hdr_len; res = ipa_cfg_ep_hdr(teth_ctx->ipa_a2_pipe_hdl, &hdr_cfg); if (res) { TETH_ERR("Header insertion config for IPA->A2 pipe failed\n"); goto bail; } TETH_DBG_FUNC_EXIT(); bail: return res; } /** * configure_ipa_header_block_usb_internal() - configures IPA end-point * registers (header removal/insertion for IPA<->USB pipes) * @usb_ipa_hdr_len: Header length in bytes to be added/removed. * @ipa_usb_hdr_len: Header length in bytes to be added/removed. * @lcid: logical channel number */ static int configure_ipa_header_block_usb_internal(u32 usb_ipa_hdr_len, u32 ipa_usb_hdr_len, u16 lcid) { struct ipa_ep_cfg_hdr hdr_cfg; int res; u16 idx; TETH_DBG_FUNC_ENTRY(); idx = get_ch_info_idx(lcid); TETH_DBG( "Configure header removal for the USB->IPA pipe(lcid=%d). hdr_len=%d, usb_ipa_pipe_hdl=%d\n ", lcid, usb_ipa_hdr_len, teth_ctx->ch_info[idx].usb_ipa_pipe_hdl); /* Configure header removal for the USB->IPA pipe */ memset(&hdr_cfg, 0, sizeof(hdr_cfg)); hdr_cfg.hdr_len = usb_ipa_hdr_len; res = ipa_cfg_ep_hdr(teth_ctx->ch_info[idx].usb_ipa_pipe_hdl, &hdr_cfg); if (res) { TETH_ERR("Header removal config for USB->IPA pipe failed\n"); goto bail; } TETH_DBG( "Configure header insertion for the IPA->USB pipe(lcid=%d). hdr_len=%d, ipa_usb_pipe_hdl=%d\n ", lcid, ipa_usb_hdr_len, teth_ctx->ch_info[idx].ipa_usb_pipe_hdl); /* Configure header insertion for the IPA->USB pipe */ hdr_cfg.hdr_len = ipa_usb_hdr_len; res = ipa_cfg_ep_hdr(teth_ctx->ch_info[idx].ipa_usb_pipe_hdl, &hdr_cfg); if (res) { TETH_ERR("Header insertion config for IPA->USB pipe failed\n"); goto bail; } TETH_DBG_FUNC_EXIT(); bail: return res; } /** * add_mbim_hdrl() - Adding a single MBIM hdr according to his stream_id * @mbim_stream_id: The MBIM stream id */ static int add_mbim_hdr(u16 mbim_stream_id) { int res; struct ipa_ioc_add_hdr *mbim_hdr; int idx; char mbim_header_name[IPA_RESOURCE_NAME_MAX] = { '\0' }; TETH_DBG_FUNC_ENTRY(); mbim_hdr = kzalloc(sizeof(struct ipa_ioc_add_hdr) + sizeof(struct ipa_hdr_add), GFP_KERNEL); if (!mbim_hdr) { TETH_ERR("Failed allocating memory for MBIM header\n"); return -ENOMEM; } mbim_hdr->commit = 0; mbim_hdr->num_hdrs = 1; snprintf(mbim_header_name, IPA_RESOURCE_NAME_MAX, "%s_%d", MBIM_HEADER_NAME, mbim_stream_id); strlcpy(mbim_hdr->hdr[0].name, mbim_header_name, IPA_RESOURCE_NAME_MAX); memcpy(mbim_hdr->hdr[0].hdr, &mbim_stream_id, sizeof(u8)); mbim_hdr->hdr[0].hdr_len = sizeof(u8); mbim_hdr->hdr[0].is_partial = false; res = ipa_add_hdr(mbim_hdr); if (res || mbim_hdr->hdr[0].status) { TETH_ERR("Failed adding MBIM header %d\n", mbim_stream_id); res = -EFAULT; goto bail; } else { TETH_DBG("Added MBIM header stream ID %d\n", mbim_stream_id); } /* Save the header handle in order to delete it later */ idx = teth_ctx->hdr_del->num_hdls++; teth_ctx->hdr_del->hdl[idx].hdl = mbim_hdr->hdr[0].hdr_hdl; TETH_DBG_FUNC_EXIT(); bail: kfree(mbim_hdr); return res; } /** * configure_ipa_header_block_ip() - configures endpoint registers for IP * link protocol. If MBIM aggregation add MBIM header. * @lcid: logical channel number */ static int configure_ipa_header_block_ip(u16 lcid) { int res; u32 usb_ipa_hdr_len = 0; u32 ipa_usb_hdr_len = 0; u32 ipa_a2_hdr_len = 0; u16 idx; u16 stream_id; u16 num_of_iterations = 1; TETH_DBG_FUNC_ENTRY(); idx = get_ch_info_idx(lcid); if (teth_ctx->ch_info[idx].aggr_params.dl.aggr_prot == TETH_AGGR_PROTOCOL_MBIM) ipa_usb_hdr_len = 1; if (get_completed_ch_num() == 0) { /* * Create a new header for MBIM stream ID and associate * it with the IPA->USB routing table */ if (teth_ctx->ch_info[idx].aggr_params.dl.aggr_prot == TETH_AGGR_PROTOCOL_MBIM) { if (teth_ctx->tethering_mode == TETH_TETHERING_MODE_MBIM) num_of_iterations = IPA_MBIM_MAX_STREAM_NUM; for (stream_id = 0; stream_id < num_of_iterations; stream_id++) { res = add_mbim_hdr(stream_id); if (res) { TETH_ERR("adding MBIM header %d fail\n" , stream_id); goto bail; } } } } /* * Configure only the tethered pipe, don't need to configure the * embedded pipes, the a2_service does it (in the connect_to_bam) */ if (teth_ctx->ch_info[idx].ch_type == TETH_TETHERED_CH) { res = configure_ipa_header_block_a2_internal(ipa_a2_hdr_len, ipa_a2_hdr_len); if (res) { TETH_ERR( "Configuration of header removal/insertion for A2<->IPA failed\n"); goto bail; } } res = configure_ipa_header_block_usb_internal(usb_ipa_hdr_len, ipa_usb_hdr_len, lcid); if (res) { TETH_ERR( "Configuration of header removal/insertion for USB<->IPA failed\n"); goto bail; } TETH_DBG_FUNC_EXIT(); bail: return res; } /** * configure_ipa_header_block_ethernet() - add Ethernet headers and configures * endpoint registers for Ethernet link protocol. * @lcid: logical channel number */ static int configure_ipa_header_block_ethernet(u16 lcid) { int res; u32 ipa_usb_hdr_len = ETH_HLEN; u32 ipa_a2_hdr_len = ETH_HLEN; int idx; TETH_DBG_FUNC_ENTRY(); idx = get_ch_info_idx(lcid); res = add_eth_hdrs(); if (res) { TETH_ERR("Failed adding Ethernet header\n"); goto bail; } res = configure_ipa_header_block_a2_internal(ipa_a2_hdr_len, ipa_a2_hdr_len); if (res) { TETH_ERR( "Configuration of header removal/insertion for A2<->IPA failed\n"); goto bail; } res = configure_ipa_header_block_usb_internal(ipa_usb_hdr_len, ipa_usb_hdr_len, lcid); if (res) { TETH_ERR( "Configuration of header removal/insertion for USB<->IPA failed\n"); goto bail; } TETH_DBG_FUNC_EXIT(); bail: return res; } /** * configure_ipa_header_block() - adds headers and configures endpoint registers * @lcid: logical channel number * - For IP link protocol and MBIM aggregation, configure MBIM header * - For Ethernet link protocol, configure Ethernet headers */ static int configure_ipa_header_block(u16 lcid) { u16 idx; int res = -EINVAL; TETH_DBG_FUNC_ENTRY(); idx = get_ch_info_idx(lcid); if (teth_ctx->ch_info[idx].link_protocol == TETH_LINK_PROTOCOL_IP) res = configure_ipa_header_block_ip(lcid); else if (teth_ctx->ch_info[idx].link_protocol == TETH_LINK_PROTOCOL_ETHERNET) res = configure_ipa_header_block_ethernet(lcid); TETH_DBG_FUNC_EXIT(); return res; } static int configure_routing_by_ip(char *hdr_name, char *rt_tbl_name, enum ipa_client_type dst, enum ipa_ip_type ip_address_family, u16 lcid) { struct ipa_ioc_add_rt_rule *rt_rule; struct ipa_ioc_get_hdr hdr_info; int res; int idx; int i; TETH_DBG_FUNC_ENTRY(); i = get_ch_info_idx(lcid); /* Get the header handle */ memset(&hdr_info, 0, sizeof(hdr_info)); strlcpy(hdr_info.name, hdr_name, IPA_RESOURCE_NAME_MAX); ipa_get_hdr(&hdr_info); rt_rule = kzalloc(sizeof(struct ipa_ioc_add_rt_rule) + 1 * sizeof(struct ipa_rt_rule_add), GFP_KERNEL); if (!rt_rule) { TETH_ERR("Memory allocation failure"); return -ENOMEM; } /* Match all, do not commit to HW*/ rt_rule->commit = 0; rt_rule->num_rules = 1; rt_rule->ip = ip_address_family; strlcpy(rt_rule->rt_tbl_name, rt_tbl_name, IPA_RESOURCE_NAME_MAX); rt_rule->rules[0].rule.dst = dst; rt_rule->rules[0].rule.hdr_hdl = hdr_info.hdl; rt_rule->rules[0].rule.attrib.attrib_mask = 0; /* Match all */ res = ipa_add_rt_rule(rt_rule); if (res || rt_rule->rules[0].status) TETH_ERR("Failed adding routing rule\n"); /* Save the routing rule handle in order to delete it later */ idx = teth_ctx->ch_info[i].routing_del[ip_address_family]->num_hdls++; teth_ctx->ch_info[i].routing_del[ip_address_family]->hdl[idx].hdl = rt_rule->rules[0].rt_rule_hdl; kfree(rt_rule); TETH_DBG_FUNC_EXIT(); return res; } static int configure_routing(char *hdr_name_ipv4, char *rt_tbl_name_ipv4, char *hdr_name_ipv6, char *rt_tbl_name_ipv6, enum ipa_client_type dst, u16 lcid) { int res; TETH_DBG_FUNC_ENTRY(); /* Configure IPv4 routing table */ res = configure_routing_by_ip(hdr_name_ipv4, rt_tbl_name_ipv4, dst, IPA_IP_v4, lcid); if (res) { TETH_ERR("Failed adding IPv4 routing table\n"); goto bail; } /* Configure IPv6 routing table */ res = configure_routing_by_ip(hdr_name_ipv6, rt_tbl_name_ipv6, dst, IPA_IP_v6, lcid); if (res) { TETH_ERR("Failed adding IPv6 routing table\n"); goto bail; } TETH_DBG_FUNC_EXIT(); bail: return res; } /** * configure_ul_header_routing() - Configure the IPA routing block: * route all packets from pipe #n(taken from lcid) to A2 (USB->A2) * @lcid: logical channel number * @rt_tbl_name_ipv4: IPv4 routing table name * @rt_tbl_name_ipv6: IPv6 routing table name */ static int configure_ul_header_routing(u16 lcid, char *rt_tbl_name_ipv4, char *rt_tbl_name_ipv6) { char hdr_name_ipv4[IPA_RESOURCE_NAME_MAX] = {'\0'}; char hdr_name_ipv6[IPA_RESOURCE_NAME_MAX] = {'\0'}; int res; u16 idx; enum ipa_client_type dst; TETH_DBG_FUNC_ENTRY(); idx = get_ch_info_idx(lcid); if (teth_ctx->ch_info[idx].ch_type == TETH_EMBEDDED_CH) { dst = IPA_CLIENT_A2_EMBEDDED_CONS; if (teth_ctx->ch_info[idx].link_protocol == TETH_LINK_PROTOCOL_IP) { snprintf(hdr_name_ipv4, IPA_RESOURCE_NAME_MAX, "%s%d", A2_MUX_HDR_NAME_V4_PREF, lcid); snprintf(hdr_name_ipv6, IPA_RESOURCE_NAME_MAX, "%s%d", A2_MUX_HDR_NAME_V6_PREF, lcid); } } else { dst = IPA_CLIENT_A2_TETHERED_CONS; if (teth_ctx->ch_info[idx].link_protocol == TETH_LINK_PROTOCOL_ETHERNET) { strlcpy(hdr_name_ipv4, A2_ETH_HDR_NAME_IPV4, IPA_RESOURCE_NAME_MAX); strlcpy(hdr_name_ipv6, A2_ETH_HDR_NAME_IPV6, IPA_RESOURCE_NAME_MAX); } } res = configure_routing(hdr_name_ipv4, rt_tbl_name_ipv4, hdr_name_ipv6, rt_tbl_name_ipv6, dst, lcid); if (res) { TETH_ERR("USB to A2 routing block configuration failed\n"); goto bail; } bail: TETH_DBG_FUNC_EXIT(); return res; } /** * find_mbim_stream_id() - mapping between the lcid and the stream_id * @lcid: The logical channel ID */ static s16 find_mbim_stream_id(u16 lcid) { int i; for (i = 0; i < IPA_MBIM_MAX_STREAM_NUM; i++) { if (lcid == teth_ctx->mbim_stream_id_to_channel_id[i]) return i; } return -EINVAL; } /** * configure_dl_header_routing() - Configure the IPA routing block: * route all incoming packets to the corresponding output pipe (A2->USB) * @lcid: logical channel number * @rt_tbl_name_ipv4: IPv4 routing table name * @rt_tbl_name_ipv6: IPv6 routing table name */ static int configure_dl_header_routing(u16 lcid, char *rt_tbl_name_ipv4, char *rt_tbl_name_ipv6) { char hdr_name_ipv4[IPA_RESOURCE_NAME_MAX] = {'\0'}; char hdr_name_ipv6[IPA_RESOURCE_NAME_MAX] = {'\0'}; int res; u16 idx; u16 cons_client; TETH_DBG_FUNC_ENTRY(); idx = get_ch_info_idx(lcid); if (teth_ctx->ch_info[idx].link_protocol == TETH_LINK_PROTOCOL_ETHERNET) { strlcpy(hdr_name_ipv4, USB_ETH_HDR_NAME_IPV4, IPA_RESOURCE_NAME_MAX); strlcpy(hdr_name_ipv6, USB_ETH_HDR_NAME_IPV6, IPA_RESOURCE_NAME_MAX); } else if (teth_ctx->ch_info[idx].aggr_params.dl.aggr_prot == TETH_AGGR_PROTOCOL_MBIM) { s16 stream_id = 0; if (teth_ctx->tethering_mode == TETH_TETHERING_MODE_MBIM) { stream_id = find_mbim_stream_id(lcid); if (lcid < 0) { res = -EFAULT; TETH_ERR("Bad LCID %d for multi MBIM\n", lcid); goto bail; } } snprintf(hdr_name_ipv4, IPA_RESOURCE_NAME_MAX, "%s_%d", MBIM_HEADER_NAME, stream_id); snprintf(hdr_name_ipv6, IPA_RESOURCE_NAME_MAX, "%s_%d", MBIM_HEADER_NAME, stream_id); } if (teth_ctx->tethering_mode == TETH_TETHERING_MODE_MBIM) cons_client = IPA_CLIENT_USB_CONS; else cons_client = get_cons_client(lcid); res = configure_routing(hdr_name_ipv4, rt_tbl_name_ipv4, hdr_name_ipv6, rt_tbl_name_ipv6, cons_client, lcid); if (res) { TETH_ERR("USB to A2 routing block configuration failed\n"); goto bail; } bail: return res; } /** * configure_ipa_routing_block() - Configure the IPA routing block * @lcid: logical channel number * This function configures IPA for: * - Route all packets from USB to A2 * - Route all packets from A2 to USB * - Use the correct headers in Ethernet or MBIM cases */ static int configure_ipa_routing_block(u16 lcid) { char rt_tbl_name_ipv4[IPA_RESOURCE_NAME_MAX] = {'\0'}; char rt_tbl_name_ipv6[IPA_RESOURCE_NAME_MAX] = {'\0'}; int res; u16 idx; TETH_DBG_FUNC_ENTRY(); idx = get_ch_info_idx(lcid); /* Configure USB -> A2 routing table */ if (teth_ctx->ch_info[idx].ch_type == TETH_EMBEDDED_CH) { snprintf(rt_tbl_name_ipv4, IPA_RESOURCE_NAME_MAX, "%s_%d", USB_TO_A2_RT_TBL_NAME_IPV4, lcid); snprintf(rt_tbl_name_ipv6, IPA_RESOURCE_NAME_MAX, "%s_%d", USB_TO_A2_RT_TBL_NAME_IPV6, lcid); } else { strlcpy(rt_tbl_name_ipv4, USB_TO_A2_RT_TBL_NAME_IPV4, IPA_RESOURCE_NAME_MAX); strlcpy(rt_tbl_name_ipv6, USB_TO_A2_RT_TBL_NAME_IPV6, IPA_RESOURCE_NAME_MAX); } res = configure_ul_header_routing(lcid, rt_tbl_name_ipv4, rt_tbl_name_ipv6); if (res) { TETH_ERR("USB to A2 routing block configuration failed\n"); goto bail; } /* Configure A2 -> USB routing table */ if (teth_ctx->ch_info[idx].ch_type == TETH_EMBEDDED_CH) { snprintf(rt_tbl_name_ipv4, IPA_RESOURCE_NAME_MAX, "%s_%d", A2_TO_USB_RT_TBL_NAME_IPV4, lcid); snprintf(rt_tbl_name_ipv6, IPA_RESOURCE_NAME_MAX, "%s_%d", A2_TO_USB_RT_TBL_NAME_IPV6, lcid); } else { strlcpy(rt_tbl_name_ipv4, A2_TO_USB_RT_TBL_NAME_IPV4, IPA_RESOURCE_NAME_MAX); strlcpy(rt_tbl_name_ipv6, A2_TO_USB_RT_TBL_NAME_IPV6, IPA_RESOURCE_NAME_MAX); } res = configure_dl_header_routing(lcid, rt_tbl_name_ipv4, rt_tbl_name_ipv6); if (res) { TETH_ERR("A2 to USB routing block configuration failed\n"); goto bail; } TETH_DBG_FUNC_EXIT(); bail: return res; } /** * configure_filtering_by_ip() - Configures IPA filtering block for * address family: IPv4 or IPv6 * @rt_tbl_name: routing table name * @src: which "clients" pipe does this rule apply to * @ip_address_family: address family: IPv4 or IPv6 * @compare_lcid: whether to use metadata equation to compare the lcid field * @lcid: logical channel number */ static int configure_filtering_by_ip(char *rt_tbl_name, enum ipa_client_type src, enum ipa_ip_type ip_address_family, enum a2_mux_logical_channel_id lcid, enum teth_packet_direction dir) { struct ipa_ioc_add_flt_rule *flt_tbl; struct ipa_ioc_get_rt_tbl rt_tbl_info; int res; int idx; int i; TETH_DBG_FUNC_ENTRY(); i = get_ch_info_idx(lcid); TETH_DBG("configure filter: routing table: %s src ep(client type):%d\n", rt_tbl_name, src); /* Get the needed routing table handle */ rt_tbl_info.ip = ip_address_family; strlcpy(rt_tbl_info.name, rt_tbl_name, IPA_RESOURCE_NAME_MAX); res = ipa_get_rt_tbl(&rt_tbl_info); if (res) { TETH_ERR("Failed getting routing table handle\n"); goto bail; } flt_tbl = kzalloc(sizeof(struct ipa_ioc_add_flt_rule) + 1 * sizeof(struct ipa_flt_rule_add), GFP_KERNEL); if (!flt_tbl) { TETH_ERR("Filtering table memory allocation failure\n"); return -ENOMEM; } flt_tbl->commit = 0; flt_tbl->ep = src; flt_tbl->global = 0; flt_tbl->ip = ip_address_family; flt_tbl->num_rules = 1; flt_tbl->rules[0].rule.action = IPA_PASS_TO_ROUTING; flt_tbl->rules[0].rule.rt_tbl_hdl = rt_tbl_info.hdl; flt_tbl->rules[0].rule.attrib.attrib_mask = 0; /* Match all */ if (teth_ctx->ch_info[i].ch_type == TETH_EMBEDDED_CH) { if (dir == TETH_A2_TO_USB) { flt_tbl->rules[0].rule.attrib.attrib_mask = IPA_FLT_META_DATA; flt_tbl->rules[0].rule.attrib.meta_data = lcid << METADATA_SHFT; flt_tbl->rules[0].rule.attrib.meta_data_mask = METADATA_MASK; } else if (teth_ctx->tethering_mode == TETH_TETHERING_MODE_MBIM) { s16 stream_id = find_mbim_stream_id(lcid); if (stream_id < 0) { TETH_ERR("logical channel %d error\n", lcid); goto bail; } flt_tbl->rules[0].rule.attrib.attrib_mask = IPA_FLT_META_DATA; flt_tbl->rules[0].rule.attrib.meta_data = stream_id; flt_tbl->rules[0].rule.attrib.meta_data_mask = 0xFF; } } res = ipa_add_flt_rule(flt_tbl); if (res || flt_tbl->rules[0].status) TETH_ERR("Failed adding filtering table\n"); /* Save the filtering rule handle in order to delete it later */ idx = teth_ctx->ch_info[i].filtering_del[ip_address_family]->num_hdls++; teth_ctx->ch_info[i].filtering_del[ip_address_family]->hdl[idx].hdl = flt_tbl->rules[0].flt_rule_hdl; kfree(flt_tbl); TETH_DBG_FUNC_EXIT(); bail: return res; } /** * configure_filtering() - Configures IPA filtering block * @rt_tbl_name_ipv4: IPv4 routing table name * @rt_tbl_name_ipv6: IPv6 routing table name * @src: which "clients" pipe does this rule apply to * @compare_lcid: whether to use metadata equation to compare the lcid field * @lcid: logical channel number */ static int configure_filtering(char *rt_tbl_name_ipv4, char *rt_tbl_name_ipv6, enum ipa_client_type src, enum a2_mux_logical_channel_id lcid, enum teth_packet_direction dir) { int res; TETH_DBG_FUNC_ENTRY(); res = configure_filtering_by_ip(rt_tbl_name_ipv4, src, IPA_IP_v4, lcid, dir); if (res) { TETH_ERR("Failed adding IPv4 filtering table\n"); goto bail; } res = configure_filtering_by_ip(rt_tbl_name_ipv6, src, IPA_IP_v6, lcid, dir); if (res) { TETH_ERR("Failed adding IPv4 filtering table\n"); goto bail; } TETH_DBG_FUNC_EXIT(); bail: return res; } /** * configure_ipa_filtering_block() - Configures IPA filtering block * @lcid: logical channel number * This function configures IPA for: * - Filter all traffic coming from USB to A2 pointing routing table * - Filter all traffic coming from A2 to USB pointing routing table */ static int configure_ipa_filtering_block(u16 lcid) { char rt_tbl_name_ipv4[IPA_RESOURCE_NAME_MAX] = {'\0'}; char rt_tbl_name_ipv6[IPA_RESOURCE_NAME_MAX] = {'\0'}; enum ipa_client_type src; int res; int idx; u16 prod_client; TETH_DBG_FUNC_ENTRY(); idx = get_ch_info_idx(lcid); if (teth_ctx->ch_info[idx].ch_type == TETH_EMBEDDED_CH) { snprintf(rt_tbl_name_ipv4, IPA_RESOURCE_NAME_MAX, "%s_%d", USB_TO_A2_RT_TBL_NAME_IPV4, lcid); snprintf(rt_tbl_name_ipv6, IPA_RESOURCE_NAME_MAX, "%s_%d", USB_TO_A2_RT_TBL_NAME_IPV6, lcid); } else { strlcpy(rt_tbl_name_ipv4, USB_TO_A2_RT_TBL_NAME_IPV4, IPA_RESOURCE_NAME_MAX); strlcpy(rt_tbl_name_ipv6, USB_TO_A2_RT_TBL_NAME_IPV6, IPA_RESOURCE_NAME_MAX); } /* Filter all traffic coming from USB to A2 */ if (teth_ctx->tethering_mode == TETH_TETHERING_MODE_MBIM) prod_client = IPA_CLIENT_USB_PROD; else prod_client = get_prod_client(lcid); res = configure_filtering(rt_tbl_name_ipv4, rt_tbl_name_ipv6, prod_client, lcid, TETH_USB_TO_A2); if (res) { TETH_ERR("USB_PROD ep filtering configuration failed\n"); goto bail; } /* Filter all traffic coming from A2 to USB */ if (teth_ctx->ch_info[idx].ch_type == TETH_EMBEDDED_CH) { src = IPA_CLIENT_A2_EMBEDDED_PROD; snprintf(rt_tbl_name_ipv4, IPA_RESOURCE_NAME_MAX, "%s_%d", A2_TO_USB_RT_TBL_NAME_IPV4, lcid); snprintf(rt_tbl_name_ipv6, IPA_RESOURCE_NAME_MAX, "%s_%d", A2_TO_USB_RT_TBL_NAME_IPV6, lcid); } else { src = IPA_CLIENT_A2_TETHERED_PROD; strlcpy(rt_tbl_name_ipv4, A2_TO_USB_RT_TBL_NAME_IPV4, IPA_RESOURCE_NAME_MAX); strlcpy(rt_tbl_name_ipv6, A2_TO_USB_RT_TBL_NAME_IPV6, IPA_RESOURCE_NAME_MAX); } res = configure_filtering(rt_tbl_name_ipv4, rt_tbl_name_ipv6, src, lcid, TETH_A2_TO_USB); if (res) { TETH_ERR("A2_PROD filtering configuration failed\n"); goto bail; } TETH_DBG_FUNC_EXIT(); bail: return res; } static int prepare_ipa_aggr_struct( const struct teth_aggr_params_link *teth_aggr_params, struct ipa_ep_cfg_aggr *ipa_aggr_params, bool client_is_prod) { TETH_DBG_FUNC_ENTRY(); memset(ipa_aggr_params, 0, sizeof(*ipa_aggr_params)); switch (teth_aggr_params->aggr_prot) { case TETH_AGGR_PROTOCOL_NONE: ipa_aggr_params->aggr_en = IPA_BYPASS_AGGR; break; case TETH_AGGR_PROTOCOL_MBIM: ipa_aggr_params->aggr = IPA_MBIM_16; ipa_aggr_params->aggr_en = (client_is_prod) ? IPA_ENABLE_DEAGGR : IPA_ENABLE_AGGR; break; case TETH_AGGR_PROTOCOL_TLP: ipa_aggr_params->aggr = IPA_TLP; ipa_aggr_params->aggr_en = (client_is_prod) ? IPA_ENABLE_DEAGGR : IPA_ENABLE_AGGR; break; default: TETH_ERR("Unsupported aggregation protocol\n"); return -EFAULT; } /* * Due to a HW 'feature', the maximal aggregated packet size may be the * requested aggr_byte_limit plus the MTU. Therefore, the MTU is * subtracted from the requested aggr_byte_limit so that the requested * byte limit is honored . */ ipa_aggr_params->aggr_byte_limit = (teth_aggr_params->max_transfer_size_byte - TETH_MTU_BYTE) / 1024; ipa_aggr_params->aggr_time_limit = TETH_DEFAULT_AGGR_TIME_LIMIT; TETH_DBG_FUNC_EXIT(); return 0; } static int teth_set_aggr_per_ep( const struct teth_aggr_params_link *teth_aggr_params, bool client_is_prod, u32 pipe_hdl) { struct ipa_ep_cfg_aggr agg_params; int res; TETH_DBG_FUNC_ENTRY(); res = prepare_ipa_aggr_struct(teth_aggr_params, &agg_params, client_is_prod); if (res) { TETH_ERR("prepare_ipa_aggregation_struct() failed\n"); goto bail; } res = ipa_cfg_ep_aggr(pipe_hdl, &agg_params); if (res) { TETH_ERR("ipa_cfg_ep_aggr() failed\n"); goto bail; } TETH_DBG_FUNC_EXIT(); bail: return res; } static void aggr_prot_to_str(enum teth_aggr_protocol_type aggr_prot, char *buff, uint buff_size) { switch (aggr_prot) { case TETH_AGGR_PROTOCOL_NONE: strlcpy(buff, "NONE", buff_size); break; case TETH_AGGR_PROTOCOL_MBIM: strlcpy(buff, "MBIM", buff_size); break; case TETH_AGGR_PROTOCOL_TLP: strlcpy(buff, "TLP", buff_size); break; default: strlcpy(buff, "ERROR", buff_size); break; } } /** * teth_set_aggregation() - set aggregation parameters to IPA * @param lcid: logical channel number * The parameters to this function are passed in the context variable ipa_ctx. */ static int teth_set_aggregation(u16 lcid) { int res; char aggr_prot_str[20]; u16 idx; TETH_DBG_FUNC_ENTRY(); idx = get_ch_info_idx(lcid); if (!teth_ctx->ch_info[idx].aggr_params_known) { TETH_ERR("Aggregation parameters unknown.\n"); return -EINVAL; } if ((teth_ctx->ch_info[idx].usb_ipa_pipe_hdl == 0) || (teth_ctx->ch_info[idx].ipa_usb_pipe_hdl == 0)) return 0; /* * Returning 0 in case pipe handles are 0 becuase aggregation * params will be set later */ if (get_completed_ch_num() == 0) { if (teth_ctx->ch_info[idx].aggr_params.ul.aggr_prot == TETH_AGGR_PROTOCOL_MBIM || teth_ctx->ch_info[idx].aggr_params.dl.aggr_prot == TETH_AGGR_PROTOCOL_MBIM) { res = ipa_set_aggr_mode(IPA_MBIM); if (res) { TETH_ERR("ipa_set_aggr_mode() failed\n"); goto bail; } res = ipa_set_single_ndp_per_mbim(false); if (res) { TETH_ERR( "ipa_set_single_ndp_per_mbim() failed\n"); goto bail; } } } aggr_prot_to_str(teth_ctx->ch_info[idx].aggr_params.ul.aggr_prot, aggr_prot_str, sizeof(aggr_prot_str)-1); TETH_DBG("Setting %s aggregation on UL\n", aggr_prot_str); aggr_prot_to_str(teth_ctx->ch_info[idx].aggr_params.dl.aggr_prot, aggr_prot_str, sizeof(aggr_prot_str)-1); TETH_DBG("Setting %s aggregation on DL\n", aggr_prot_str); /* Configure aggregation on UL producer (USB->IPA) */ res = teth_set_aggr_per_ep(&teth_ctx->ch_info[idx].aggr_params.ul, true, teth_ctx->ch_info[idx].usb_ipa_pipe_hdl); if (res) { TETH_ERR("teth_set_aggregation_per_ep() failed\n"); goto bail; } /* Configure aggregation on DL consumer (IPA->USB) */ res = teth_set_aggr_per_ep(&teth_ctx->ch_info[idx].aggr_params.dl, false, teth_ctx->ch_info[idx].ipa_usb_pipe_hdl); if (res) { TETH_ERR("teth_set_aggregation_per_ep() failed\n"); goto bail; } TETH_DBG_FUNC_EXIT(); bail: return res; } /** * teth_request_resource() - wrapper function to * ipa_rm_inactivity_timer_request_resource() * * - initialize the is_bridge_prod_up completion object * - request the resource * - error handling */ static int teth_request_resource(void) { int res; mutex_lock(&teth_ctx->request_resource_mutex); INIT_COMPLETION(teth_ctx->is_bridge_prod_up); res = ipa_rm_inactivity_timer_request_resource( IPA_RM_RESOURCE_BRIDGE_PROD); if (res < 0) { if (res == -EINPROGRESS) { wait_for_completion(&teth_ctx->is_bridge_prod_up); res = 0; } } else { res = 0; } mutex_unlock(&teth_ctx->request_resource_mutex); return res; } /** * complete_hw_bridge() - setup the HW bridge from USB to A2 and back through * IPA */ static void complete_hw_bridge(struct work_struct *work) { int res, i; struct hw_bridge_work_wrap *work_data = container_of(work, struct hw_bridge_work_wrap, comp_hw_bridge_work); u16 ch_info_idx, lcid; int num_of_iterations = 1; TETH_DBG_FUNC_ENTRY(); ch_info_idx = get_ch_info_idx(work_data->lcid); TETH_DBG("Completing HW bridge in %s mode. lcid # %d\n", (teth_ctx->ch_info[ch_info_idx].link_protocol == TETH_LINK_PROTOCOL_ETHERNET) ? "ETHERNET" : "IP", work_data->lcid); res = teth_request_resource(); if (res) { TETH_ERR("request_resource() failed.\n"); goto bail; } res = teth_set_aggregation(work_data->lcid); if (res) { TETH_ERR("Failed setting aggregation params\n"); goto bail; } res = configure_ipa_header_block(work_data->lcid); if (res) { TETH_ERR("Configuration of IPA header block Failed\n"); goto bail; } if (teth_ctx->tethering_mode == TETH_TETHERING_MODE_MBIM) num_of_iterations = 8; for (i = 0; i < num_of_iterations; i++) { if (teth_ctx->tethering_mode == TETH_TETHERING_MODE_MBIM) lcid = teth_ctx->mbim_stream_id_to_channel_id[i]; else lcid = work_data->lcid; res = configure_ipa_routing_block(lcid); if (res) { TETH_ERR("Configuration of IPA routing block Failed\n"); goto bail; } res = configure_ipa_filtering_block(lcid); if (res) { TETH_ERR("IPA filtering configuration block Failed\n"); goto bail; } } /* * Commit all the data to HW, including header, routing and filtering * blocks, IPv4 and IPv6 */ res = ipa_commit_hdr(); if (res) { TETH_ERR("Failed committing headers / routing / filtering.\n"); goto bail; } if (teth_ctx->tethering_mode == TETH_TETHERING_MODE_MBIM) { lcid = A2_MUX_MULTI_MBIM_13; for (i = 0; i < num_of_iterations; i++, lcid++) { ch_info_idx = get_ch_info_idx(lcid); teth_ctx->ch_info[ch_info_idx].is_hw_bridge_complete = true; } } else { teth_ctx->ch_info[ch_info_idx].is_hw_bridge_complete = true; } bail: teth_ctx->ch_info[ch_info_idx].comp_hw_bridge_in_progress = false; ipa_rm_inactivity_timer_release_resource(IPA_RM_RESOURCE_BRIDGE_PROD); TETH_DBG_FUNC_EXIT(); return; } static void mac_addr_to_str(u8 mac_addr[ETH_ALEN], char *buff, uint buff_size) { scnprintf(buff, buff_size, "%02x-%02x-%02x-%02x-%02x-%02x", mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]); } /** * check_to_complete_hw_bridge() - can HW bridge be set up ? * @param lcid: logical channel id * @param skb: pointer to socket buffer * @param my_mac_addr: pointer to write 'my' extracted MAC address to * @param my_mac_addr_known: pointer to update whether 'my' extracted MAC * address is known * @param peer_mac_addr_known: pointer to update whether the 'peer' extracted * MAC address is known * * This function is used by both A2 and USB callback functions, therefore the * meaning of 'my' and 'peer' changes according to the context. * Extracts MAC address from the packet in Ethernet link protocol, * Sets up the HW bridge in case all conditions are met. */ static void check_to_complete_hw_bridge(u16 lcid, struct sk_buff *skb, u8 *my_mac_addr, bool *my_mac_addr_known, bool *peer_mac_addr_known) { bool both_mac_addresses_known; char mac_addr_str[20]; u16 idx; struct hw_bridge_work_wrap *work_data; idx = get_ch_info_idx(lcid); if ((teth_ctx->ch_info[idx].link_protocol == TETH_LINK_PROTOCOL_ETHERNET) && (!(*my_mac_addr_known))) { memcpy(my_mac_addr, &skb->data[ETH_ALEN], ETH_ALEN); mac_addr_to_str(my_mac_addr, mac_addr_str, sizeof(mac_addr_str)-1); TETH_DBG("Extracted MAC addr: %s\n", mac_addr_str); *my_mac_addr_known = true; } both_mac_addresses_known = *my_mac_addr_known && *peer_mac_addr_known; if ((both_mac_addresses_known || (teth_ctx->ch_info[idx].link_protocol == TETH_LINK_PROTOCOL_IP)) && (!teth_ctx->ch_info[idx].comp_hw_bridge_in_progress) && (teth_ctx->ch_info[idx].aggr_params_known)) { work_data = &teth_ctx->ch_info[idx].hw_bridge_work; INIT_WORK(&work_data->comp_hw_bridge_work, complete_hw_bridge); work_data->lcid = lcid; teth_ctx->ch_info[idx].comp_hw_bridge_in_progress = true; queue_work(teth_ctx->teth_wq, &work_data->comp_hw_bridge_work); } } /** * teth_send_skb_work() - workqueue function for sending a packet */ static void teth_send_skb_work(struct work_struct *work) { struct teth_work *work_data = container_of(work, struct teth_work, work); int res; u16 client; res = teth_request_resource(); if (res) { TETH_ERR("Packet send failure, dropping packet !\n"); goto bail; } switch (work_data->dir) { case TETH_USB_TO_A2: res = a2_mux_write(work_data->lcid, work_data->skb); if (res) { TETH_ERR("Packet send failure, dropping packet !\n"); goto bail; } teth_ctx->stats.usb_to_a2_num_sw_tx_packets++; break; case TETH_A2_TO_USB: if (teth_ctx->tethering_mode == TETH_TETHERING_MODE_MBIM) client = IPA_CLIENT_USB_CONS; else client = get_cons_client(work_data->lcid); res = ipa_tx_dp(client, work_data->skb, &work_data->metadata); if (res) { TETH_ERR("Packet send failure, dropping packet !\n"); goto bail; } teth_ctx->stats.a2_to_usb_num_sw_tx_packets++; break; default: TETH_ERR("Unsupported direction to send !\n"); WARN_ON(1); } ipa_rm_inactivity_timer_release_resource(IPA_RM_RESOURCE_BRIDGE_PROD); kfree(work_data); teth_ctx->stats.num_sw_tx_packets_during_resource_wakeup++; return; bail: ipa_rm_inactivity_timer_release_resource(IPA_RM_RESOURCE_BRIDGE_PROD); dev_kfree_skb(work_data->skb); kfree(work_data); } /** * defer_skb_send() - defer sending an skb using the SW bridge to a workqueue * @param skb: pointer to the socket buffer * @param dir: direction of send * * In case where during a packet send, the A2 or USB needs to wake up from power * collapse, defer the send and return the context to IPA driver. This is * important since IPA driver has a single threaded Rx path. */ static void defer_skb_send(struct sk_buff *skb, enum teth_packet_direction dir, enum a2_mux_logical_channel_id lcid) { struct teth_work *work = kmalloc(sizeof(struct teth_work), GFP_KERNEL); if (!work) { TETH_ERR("No mem, dropping packet\n"); dev_kfree_skb(skb); ipa_rm_inactivity_timer_release_resource (IPA_RM_RESOURCE_BRIDGE_PROD); return; } /* * Since IPA uses a single Rx thread, we don't * want to wait for completion here */ INIT_WORK(&work->work, teth_send_skb_work); work->dir = dir; work->skb = skb; work->lcid = lcid; work->metadata.mbim_stream_id_valid = true; work->metadata.mbim_stream_id = find_mbim_stream_id(lcid); queue_work(teth_ctx->teth_wq, &work->work); } /** * usb_notify_cb() - callback function for sending packets from USB to A2 * @param priv: private data * @param evt: event - RECEIVE or WRITE_DONE * @param data: pointer to skb to be sent * * This callback function is installed by the IPA driver, it is invoked in 2 * cases: * 1. When a packet comes from the USB pipe and is routed to A5 (SW bridging) * 2. After a packet has been bridged from USB to A2 and its skb should be freed * * Invocation: sps driver --> IPA driver --> bridge driver * * In the event of IPA_RECEIVE: * - Checks whether the HW bridge can be set up.. * - Requests the BRIDGE_PROD resource so that A2 and USB are not in power * collapse. In case where the resource is waking up, defer the send operation * to a workqueue in order to not block the IPA driver single threaded Rx path. * - Sends the packets to A2 using a2_service driver API. * - Releases the BRIDGE_PROD resource. * * In the event of IPA_WRITE_DONE: * - Frees the skb memory */ static void usb_notify_cb(void *priv, enum ipa_dp_evt_type evt, unsigned long data) { struct sk_buff *skb = (struct sk_buff *)data; int res; u16 lcid, stream_id = 0; u16 idx; TETH_DBG("in usb_notify_cb\n"); switch (evt) { case IPA_RECEIVE: if (teth_ctx->tethering_mode == TETH_TETHERING_MODE_MBIM) { /* extract the stream id from the skb */ skb_push(skb, TETH_METADATA_LEN); stream_id = ntohl(*((u32 *)skb->data)); TETH_DBG("stream_id %d\n", stream_id); skb_pull(skb, TETH_METADATA_LEN); lcid = teth_ctx-> mbim_stream_id_to_channel_id[stream_id]; } else lcid = (u16)(u32)priv; TETH_DBG("usb_notify_cb: got lcid=%d from private data\n", lcid); idx = get_ch_info_idx(lcid); if (!teth_ctx->ch_info[idx].is_hw_bridge_complete) check_to_complete_hw_bridge( lcid, skb, teth_ctx->mac_addresses.host_pc_mac_addr, &teth_ctx->mac_addresses.host_pc_mac_addr_known, &teth_ctx->mac_addresses.device_mac_addr_known); /* * Request the BRIDGE_PROD resource, send the packet and release * the resource */ res = ipa_rm_inactivity_timer_request_resource( IPA_RM_RESOURCE_BRIDGE_PROD); if (res < 0) { if (res == -EINPROGRESS) { /* The resource is waking up */ defer_skb_send(skb, TETH_USB_TO_A2, lcid); } else { TETH_ERR( "Packet send failure, dropping packet !\n"); dev_kfree_skb(skb); } ipa_rm_inactivity_timer_release_resource( IPA_RM_RESOURCE_BRIDGE_PROD); return; } res = a2_mux_write(lcid, skb); if (res) { TETH_ERR("Packet send failure, dropping packet !\n"); dev_kfree_skb(skb); ipa_rm_inactivity_timer_release_resource( IPA_RM_RESOURCE_BRIDGE_PROD); return; } teth_ctx->stats.usb_to_a2_num_sw_tx_packets++; ipa_rm_inactivity_timer_release_resource( IPA_RM_RESOURCE_BRIDGE_PROD); break; case IPA_WRITE_DONE: dev_kfree_skb(skb); break; default: TETH_ERR("Unsupported IPA event !\n"); WARN_ON(1); } return; } /** * a2_notify_cb() - callback function for sending packets from A2 to USB * @param user_data: private data * @param event: event - RECEIVE or WRITE_DONE * @param data: pointer to skb to be sent * * This callback function is installed by the IPA driver, it is invoked in 2 * cases: * 1. When a packet comes from the A2 pipe and is routed to A5 (SW bridging) * 2. After a packet has been bridged from A2 to USB and its skb should be freed * * Invocation: sps driver --> IPA driver --> a2_service driver --> bridge driver * * In the event of A2_MUX_RECEIVE: * - Checks whether the HW bridge can be set up.. * - Requests the BRIDGE_PROD resource so that A2 and USB are not in power * collapse. In case where the resource is waking up, defer the send operation * to a workqueue in order to not block the IPA driver single threaded Rx path. * - Sends the packets to USB using IPA drivers ipa_tx_dp() API. * - Releases the BRIDGE_PROD resource. * * In the event of A2_MUX_WRITE_DONE: * - Frees the skb memory */ static void a2_notify_cb(void *user_data, enum a2_mux_event_type event, unsigned long data) { struct sk_buff *skb = (struct sk_buff *)data; int res; u16 idx; u16 lcid; u16 client; struct ipa_tx_meta metadata; TETH_DBG("in a2_notify_cb: event:%d\n", event); switch (event) { case A2_MUX_RECEIVE: memset(&metadata, 0, sizeof(metadata)); lcid = (u16)(u32)user_data; TETH_DBG("a2_notify_cb: got lcid=%d from private data\n", lcid); if (teth_ctx->tethering_mode == TETH_TETHERING_MODE_MBIM) { s16 stream_id = find_mbim_stream_id(lcid); if (stream_id < 0) { TETH_ERR("No stream id for lcid %d\n", lcid); return; } client = IPA_CLIENT_USB_CONS; metadata.mbim_stream_id_valid = true; metadata.mbim_stream_id = stream_id; } else { client = get_cons_client(lcid); } idx = get_ch_info_idx(lcid); if (!teth_ctx->ch_info[idx].is_hw_bridge_complete) check_to_complete_hw_bridge( lcid, skb, teth_ctx->mac_addresses.device_mac_addr, &teth_ctx->mac_addresses.device_mac_addr_known, &teth_ctx-> mac_addresses.host_pc_mac_addr_known); /* * Request the BRIDGE_PROD resource, send the packet and release * the resource */ res = ipa_rm_inactivity_timer_request_resource( IPA_RM_RESOURCE_BRIDGE_PROD); if (res < 0) { if (res == -EINPROGRESS) { /* The resource is waking up */ defer_skb_send(skb, TETH_A2_TO_USB, lcid); } else { TETH_ERR( "Packet send failure, dropping packet !\n"); dev_kfree_skb(skb); } ipa_rm_inactivity_timer_release_resource( IPA_RM_RESOURCE_BRIDGE_PROD); return; } res = ipa_tx_dp(client, skb, &metadata); if (res) { TETH_ERR("Packet send failure, dropping packet !\n"); dev_kfree_skb(skb); ipa_rm_inactivity_timer_release_resource( IPA_RM_RESOURCE_BRIDGE_PROD); return; } teth_ctx->stats.a2_to_usb_num_sw_tx_packets++; ipa_rm_inactivity_timer_release_resource( IPA_RM_RESOURCE_BRIDGE_PROD); break; case A2_MUX_WRITE_DONE: dev_kfree_skb(skb); break; default: TETH_ERR("Unsupported IPA event !\n"); WARN_ON(1); } return; } /** * bridge_prod_notify_cb() - IPA Resource Manager callback function * @param notify_cb_data: private data * @param event: RESOURCE_GRANTED / RESOURCE_RELEASED * @param data: not used in this case * * This callback function is called by IPA resource manager to notify the * BRIDGE_PROD entity of events like RESOURCE_GRANTED and RESOURCE_RELEASED. */ static void bridge_prod_notify_cb(void *notify_cb_data, enum ipa_rm_event event, unsigned long data) { switch (event) { case IPA_RM_RESOURCE_GRANTED: complete(&teth_ctx->is_bridge_prod_up); break; case IPA_RM_RESOURCE_RELEASED: complete(&teth_ctx->is_bridge_prod_down); break; default: TETH_ERR("Unsupported notification!\n"); WARN_ON(1); break; } return; } static void a2_prod_notify_cb(void *notify_cb_data, enum ipa_rm_event event, unsigned long data) { int res; struct ipa_ep_cfg ipa_ep_cfg; switch (event) { case IPA_RM_RESOURCE_GRANTED: res = a2_mux_get_client_handles( A2_MUX_TETHERED_0, &teth_ctx->ipa_a2_pipe_hdl, &teth_ctx->a2_ipa_pipe_hdl); if (res) { TETH_ERR( "a2_mux_get_client_handles() failed, res = %d\n", res); return; } /* Reset the various endpoints configuration */ memset(&ipa_ep_cfg, 0, sizeof(ipa_ep_cfg)); ipa_ep_cfg.hdr.hdr_len = teth_ctx->ipa_a2_hdr_len; ipa_cfg_ep(teth_ctx->ipa_a2_pipe_hdl, &ipa_ep_cfg); memset(&ipa_ep_cfg, 0, sizeof(ipa_ep_cfg)); ipa_ep_cfg.hdr.hdr_len = teth_ctx->a2_ipa_hdr_len; ipa_cfg_ep(teth_ctx->a2_ipa_pipe_hdl, &ipa_ep_cfg); break; case IPA_RM_RESOURCE_RELEASED: break; default: TETH_ERR("Unsupported notification!\n"); WARN_ON(1); break; } return; } /** * teth_bridge_init() - Initialize the Tethering bridge driver * @usb_notify_cb_ptr: Callback function which should be used by the caller. * Output parameter. * @private_data_ptr: Data for the callback function. Should be used by the * caller. Output parameter. * * USB driver gets a pointer to a callback function (usb_notify_cb) and an * associated data. USB driver installs this callback function in the call to * ipa_connect(). * * Builds IPA resource manager dependency graph. * * Return codes: 0: success, * -EINVAL - Bad parameter * Other negative value - Failure */ int teth_bridge_init(ipa_notify_cb *usb_notify_cb_ptr, void **private_data_ptr, enum ipa_client_type client) { int res = 0; u32 lcid; int idx; TETH_DBG_FUNC_ENTRY(); if (usb_notify_cb_ptr == NULL || private_data_ptr == NULL) { TETH_ERR("Bad parameter\n"); TETH_DBG_FUNC_EXIT(); return -EINVAL; } *usb_notify_cb_ptr = usb_notify_cb; lcid = get_channel_id_from_client_prod(client); *private_data_ptr = (void *)lcid; idx = get_ch_info_idx(lcid); mutex_lock(&teth_ctx->init_mutex); if (teth_ctx->init_status == TETH_INITIALIZATION_ERROR) { res = -EPERM; goto bail; } TETH_DBG("init private data with lcid=%d\n", lcid); if (teth_ctx->init_status == TETH_INITIALIZED) { teth_ctx->ch_init_cnt++; res = 0; goto bail; } TETH_DBG("first call to init: build dependency graph\n"); /* Build IPA Resource manager dependency graph */ res = ipa_rm_add_dependency(IPA_RM_RESOURCE_BRIDGE_PROD, IPA_RM_RESOURCE_USB_CONS); if (res && res != -EINPROGRESS) { TETH_ERR("ipa_rm_add_dependency() failed\n"); goto fail; } res = ipa_rm_add_dependency(IPA_RM_RESOURCE_BRIDGE_PROD, IPA_RM_RESOURCE_A2_CONS); if (res && res != -EINPROGRESS) { TETH_ERR("ipa_rm_add_dependency() failed\n"); goto fail_add_dependency_1; } res = ipa_rm_add_dependency(IPA_RM_RESOURCE_USB_PROD, IPA_RM_RESOURCE_A2_CONS); if (res && res != -EINPROGRESS) { TETH_ERR("ipa_rm_add_dependency() failed\n"); goto fail_add_dependency_2; } res = ipa_rm_add_dependency(IPA_RM_RESOURCE_A2_PROD, IPA_RM_RESOURCE_USB_CONS); if (res && res != -EINPROGRESS) { TETH_ERR("ipa_rm_add_dependency() failed\n"); goto fail_add_dependency_3; } /* Return 0 as EINPROGRESS is a valid return value at this point */ teth_ctx->init_status = TETH_INITIALIZED; teth_ctx->ch_init_cnt++; res = 0; goto bail; fail_add_dependency_3: ipa_rm_delete_dependency(IPA_RM_RESOURCE_USB_PROD, IPA_RM_RESOURCE_A2_CONS); fail_add_dependency_2: ipa_rm_delete_dependency(IPA_RM_RESOURCE_BRIDGE_PROD, IPA_RM_RESOURCE_A2_CONS); fail_add_dependency_1: ipa_rm_delete_dependency(IPA_RM_RESOURCE_BRIDGE_PROD, IPA_RM_RESOURCE_USB_CONS); fail: teth_ctx->init_status = TETH_INITIALIZATION_ERROR; bail: mutex_unlock(&teth_ctx->init_mutex); TETH_DBG_FUNC_EXIT(); return res; } EXPORT_SYMBOL(teth_bridge_init); static void initialize_ch_info(int idx) { teth_ctx->ch_info[idx].usb_ipa_pipe_hdl = 0; teth_ctx->ch_info[idx].ipa_usb_pipe_hdl = 0; teth_ctx->ch_info[idx].is_connected = false; /* * The first channel(#8) is tethered channel it's and default link * protocol is Ethernet the other channels are embedded channels - * only IP is supported */ if (idx == 0) { teth_ctx->ch_info[idx].ch_type = TETH_TETHERED_CH; teth_ctx->ch_info[idx].link_protocol = TETH_LINK_PROTOCOL_ETHERNET; } else { teth_ctx->ch_info[idx].ch_type = TETH_EMBEDDED_CH; teth_ctx->ch_info[idx].link_protocol = TETH_LINK_PROTOCOL_IP; } teth_ctx->ch_info[idx].is_hw_bridge_complete = false; memset(&teth_ctx->ch_info[idx].aggr_params, 0, sizeof(teth_ctx->ch_info[idx].aggr_params)); teth_ctx->ch_info[idx].aggr_params_known = false; teth_ctx->ch_info[idx].comp_hw_bridge_in_progress = false; teth_ctx->ch_info[idx].hw_bridge_work.lcid = A2_MUX_TETHERED_0; memset(teth_ctx->ch_info[idx].routing_del[IPA_IP_v4], 0, sizeof(struct ipa_ioc_del_rt_rule) + TETH_TOTAL_RT_ENTRIES_IP * sizeof(struct ipa_rt_rule_del)); teth_ctx->ch_info[idx].routing_del[IPA_IP_v4]->ip = IPA_IP_v4; memset(teth_ctx->ch_info[idx].routing_del[IPA_IP_v6], 0, sizeof(struct ipa_ioc_del_rt_rule) + TETH_TOTAL_RT_ENTRIES_IP * sizeof(struct ipa_rt_rule_del)); teth_ctx->ch_info[idx].routing_del[IPA_IP_v6]->ip = IPA_IP_v6; memset(teth_ctx->ch_info[idx].filtering_del[IPA_IP_v4], 0, sizeof(struct ipa_ioc_del_flt_rule) + TETH_TOTAL_FLT_ENTRIES_IP * sizeof(struct ipa_flt_rule_del)); teth_ctx->ch_info[idx].filtering_del[IPA_IP_v4]->ip = IPA_IP_v4; memset(teth_ctx->ch_info[idx].filtering_del[IPA_IP_v6], 0, sizeof(struct ipa_ioc_del_flt_rule) + TETH_TOTAL_FLT_ENTRIES_IP * sizeof(struct ipa_flt_rule_del)); teth_ctx->ch_info[idx].filtering_del[IPA_IP_v6]->ip = IPA_IP_v6; } /** * initialize_ch_info_arr() - Initialize the ch_info array */ static void initialize_ch_info_arr(void) { int idx; TETH_DBG_FUNC_ENTRY(); /* Initialize channel info array*/ for (idx = 0; idx < TETH_NUM_CHANNELS; idx++) initialize_ch_info(idx); TETH_DBG_FUNC_EXIT(); } /** * init_stream_id_to_channel_id_array() - Initialize the stream_id to lcid array */ void init_stream_id_to_channel_id_array(void) { u16 stream, lcid = A2_MUX_MULTI_MBIM_13; for (stream = 0; stream < MAX_MBIM_STREAMS; stream++, lcid++) teth_ctx->mbim_stream_id_to_channel_id[stream] = lcid; } /** * initialize_context() - Initialize the ipa_ctx struct */ static void initialize_context(void) { TETH_DBG_FUNC_ENTRY(); /* Initialize context variables */ teth_ctx->ipa_a2_pipe_hdl = 0; teth_ctx->a2_ipa_pipe_hdl = 0; memset(&teth_ctx->mac_addresses, 0, sizeof(teth_ctx->mac_addresses)); teth_ctx->tethering_mode = 0; INIT_COMPLETION(teth_ctx->is_bridge_prod_up); INIT_COMPLETION(teth_ctx->is_bridge_prod_down); memset(&teth_ctx->stats, 0, sizeof(teth_ctx->stats)); teth_ctx->a2_ipa_hdr_len = 0; teth_ctx->ipa_a2_hdr_len = 0; memset(teth_ctx->hdr_del, 0, sizeof(struct ipa_ioc_del_hdr) + TETH_TOTAL_HDR_ENTRIES * sizeof(struct ipa_hdr_del)); teth_ctx->ch_init_cnt = 0; teth_ctx->init_status = TETH_NOT_INITIALIZED; teth_ctx->debugfs_lcid = A2_MUX_TETHERED_0; init_stream_id_to_channel_id_array(); TETH_DBG_FUNC_EXIT(); } static int delete_usb_dependencies(void) { int res; /* * Delete part of IPA resource manager dependency graph. Only the * BRIDGE_PROD <-> A2 dependency remains intact */ res = ipa_rm_delete_dependency(IPA_RM_RESOURCE_BRIDGE_PROD, IPA_RM_RESOURCE_USB_CONS); if ((res != 0) && (res != -EINPROGRESS)) TETH_ERR( "Failed deleting ipa_rm dependency BRIDGE_PROD <-> USB_CONS\n"); res = ipa_rm_delete_dependency(IPA_RM_RESOURCE_USB_PROD, IPA_RM_RESOURCE_A2_CONS); if ((res != 0) && (res != -EINPROGRESS)) TETH_ERR( "Failed deleting ipa_rm dependency USB_PROD <-> A2_CONS\n"); res = ipa_rm_delete_dependency(IPA_RM_RESOURCE_A2_PROD, IPA_RM_RESOURCE_USB_CONS); if ((res != 0) && (res != -EINPROGRESS)) TETH_ERR( "Failed deleting ipa_rm dependency A2_PROD <-> USB_CONS\n"); return res; } static void teardown_hw_bridge(int idx) { if (get_completed_ch_num() <= 1) { /* Delete header entries */ if (ipa_del_hdr(teth_ctx->hdr_del)) TETH_ERR("ipa_del_hdr() failed\n"); } /* Delete installed routing rules */ if (ipa_del_rt_rule(teth_ctx->ch_info[idx].routing_del[IPA_IP_v4])) TETH_ERR("ipa_del_rt_rule() failed\n"); if (ipa_del_rt_rule(teth_ctx->ch_info[idx].routing_del[IPA_IP_v6])) TETH_ERR("ipa_del_rt_rule() failed\n"); /* Delete installed filtering rules */ if (ipa_del_flt_rule(teth_ctx->ch_info[idx].filtering_del[IPA_IP_v4])) TETH_ERR("ipa_del_flt_rule() failed\n"); if (ipa_del_flt_rule(teth_ctx->ch_info[idx].filtering_del[IPA_IP_v6])) TETH_ERR("ipa_del_flt_rule() failed\n"); /* * Commit all the data to HW, including header, routing and * filtering blocks, IPv4 and IPv6 */ if (ipa_commit_hdr()) TETH_ERR("Failed committing delete rules\n"); } /** * disconnect_first_ch() - any channel disconnect. if last channel disconnects * delete bridge prod dependency from the dependency graph */ static int disconnect_ch(u16 lcid) { int res; struct ipa_rm_register_params a2_prod_reg_params; u16 idx = get_ch_info_idx(lcid); TETH_DBG_FUNC_ENTRY(); /* Request the BRIDGE_PROD resource, A2 and IPA should power up */ res = teth_request_resource(); if (res) { TETH_ERR("request_resource() failed.\n"); goto bail; } /* Close the channel to A2 */ if (a2_mux_close_channel(lcid)) TETH_ERR("a2_mux_close_channel(%d) failed\n", lcid); /* Tear down the IPA HW bridge */ if (teth_ctx->ch_info[idx].is_hw_bridge_complete) teardown_hw_bridge(idx); ipa_rm_inactivity_timer_release_resource(IPA_RM_RESOURCE_BRIDGE_PROD); /* Deregister from A2_PROD notifications */ if (teth_ctx->ch_info[idx].ch_type == TETH_TETHERED_CH) { a2_prod_reg_params.user_data = NULL; a2_prod_reg_params.notify_cb = a2_prod_notify_cb; res = ipa_rm_deregister(IPA_RM_RESOURCE_A2_PROD, &a2_prod_reg_params); if (res) TETH_ERR( "Failed deregistering from A2_prod notifications.\n"); } if (get_connected_ch_num() <= 1) { initialize_context(); /* Delete the last ipa_rm dependency - BRIDGE_PROD <-> A2 */ res = ipa_rm_delete_dependency(IPA_RM_RESOURCE_BRIDGE_PROD, IPA_RM_RESOURCE_A2_CONS); if ((res != 0) && (res != -EINPROGRESS)) TETH_ERR( "Failed deleting ipa_rm dependency BRIDGE_PROD <-> A2_CONS\n"); } initialize_ch_info(idx); bail: TETH_DBG_FUNC_EXIT(); return 0; } /** * disconnect_first_ch() - First channel disconnect * delete all USB dependencies from the dependency graph */ static int disconnect_first_ch(u16 lcid) { int res; TETH_DBG_FUNC_ENTRY(); res = delete_usb_dependencies(); disconnect_ch(lcid); teth_ctx->init_status = TETH_NOT_INITIALIZED; TETH_DBG_FUNC_EXIT(); return 0; } /** * teth_bridge_disconnect() - Disconnect tethering bridge module */ int teth_bridge_disconnect(enum ipa_client_type client) { u16 lcid; u16 idx; u16 num_of_iteration = 1, i; TETH_DBG_FUNC_ENTRY(); if (teth_ctx->tethering_mode == TETH_TETHERING_MODE_MBIM) { num_of_iteration = 8; lcid = A2_MUX_MULTI_MBIM_13; } else { lcid = get_channel_id_from_client_prod(client); } for (i = 0; i < num_of_iteration; i++, lcid++) { idx = get_ch_info_idx(lcid); if (!teth_ctx->ch_info[idx].is_connected) { TETH_ERR( "Trying to disconnect an already disconnected bridge\n"); goto bail; } mutex_lock(&teth_ctx->init_mutex); if (teth_ctx->init_status == TETH_INITIALIZED) disconnect_first_ch(lcid); else disconnect_ch(lcid); teth_ctx->ch_info[idx].is_connected = false; mutex_unlock(&teth_ctx->init_mutex); TETH_DBG("ch #%d is disconnected.\n", lcid); } bail: TETH_DBG_FUNC_EXIT(); return 0; } EXPORT_SYMBOL(teth_bridge_disconnect); /** * teth_bridge_connect() - Connect bridge for a tethered Rmnet / MBIM call * @connect_params: Connection info * * Return codes: 0: success * -EINVAL: invalid parameters * -EPERM: Operation not permitted as the bridge is already * connected */ int teth_bridge_connect(struct teth_bridge_connect_params *connect_params) { int res, num_of_iterations = 1, i; struct ipa_ep_cfg ipa_ep_cfg; u16 lcid; u16 idx; struct hw_bridge_work_wrap *work_data; enum teth_tethering_mode mode; struct ipa_rm_register_params a2_prod_reg_params; TETH_DBG_FUNC_ENTRY(); if (connect_params == NULL || connect_params->ipa_usb_pipe_hdl <= 0 || connect_params->usb_ipa_pipe_hdl <= 0 || connect_params->tethering_mode >= TETH_TETHERING_MODE_MAX || connect_params->tethering_mode < 0 || connect_params->client_type > IPA_CLIENT_USB_PROD || connect_params->client_type < IPA_CLIENT_USB2_PROD) { TETH_DBG("Received invalid connect_params.\n"); return -EINVAL; } teth_ctx->tethering_mode = connect_params->tethering_mode; mode = connect_params->tethering_mode; if (mode == TETH_TETHERING_MODE_MBIM) { num_of_iterations = MAX_MBIM_STREAMS; lcid = teth_ctx->mbim_stream_id_to_channel_id[0]; } else { lcid = get_channel_id_from_client_prod( connect_params->client_type); } res = teth_request_resource(); if (res) { TETH_ERR("request_resource() failed.\n"); goto bail; } for (i = 0; i < num_of_iterations; i++) { if (teth_ctx->tethering_mode == TETH_TETHERING_MODE_MBIM) lcid = teth_ctx->mbim_stream_id_to_channel_id[i]; idx = get_ch_info_idx(lcid); if (teth_ctx->ch_info[idx].is_connected) { TETH_ERR( "Trying to connect an already connected bridge\n"); return -EPERM; } teth_ctx->ch_info[idx].ipa_usb_pipe_hdl = connect_params->ipa_usb_pipe_hdl; teth_ctx->ch_info[idx].usb_ipa_pipe_hdl = connect_params->usb_ipa_pipe_hdl; teth_ctx->tethering_mode = connect_params->tethering_mode; res = a2_mux_open_channel(lcid, (void *)(u32)lcid, a2_notify_cb); if (res) { TETH_ERR("a2_mux_open_channel(%d) failed\n", lcid); goto bail; } } /* Reset the various endpoints configuration */ memset(&ipa_ep_cfg, 0, sizeof(ipa_ep_cfg)); ipa_cfg_ep(teth_ctx->ch_info[idx].ipa_usb_pipe_hdl, &ipa_ep_cfg); ipa_cfg_ep(teth_ctx->ch_info[idx].usb_ipa_pipe_hdl, &ipa_ep_cfg); mutex_lock(&teth_ctx->init_mutex); if (get_connected_ch_num() == 0) { res = a2_mux_get_client_handles(lcid, &teth_ctx->ipa_a2_pipe_hdl, &teth_ctx->a2_ipa_pipe_hdl); if (res) { TETH_ERR( "a2_mux_get_client_handles() failed, res = %d\n", res); mutex_unlock(&teth_ctx->init_mutex); goto bail; } TETH_DBG("ipa_a2_pipe_hdl=0x%x, a2_ipa_pipe_hdl=0x%x\n", teth_ctx->ipa_a2_pipe_hdl, teth_ctx->a2_ipa_pipe_hdl); if (teth_ctx->ch_info[idx].ch_type == TETH_TETHERED_CH) { ipa_cfg_ep(teth_ctx->ipa_a2_pipe_hdl, &ipa_ep_cfg); ipa_cfg_ep(teth_ctx->a2_ipa_pipe_hdl, &ipa_ep_cfg); /* * Register for A2_PROD resource notifications * (only for single rmnet) * In multi rmnet, * the a2_service is responsible for configure the * ipa<->a2 pipes */ a2_prod_reg_params.user_data = NULL; a2_prod_reg_params.notify_cb = a2_prod_notify_cb; res = ipa_rm_register(IPA_RM_RESOURCE_A2_PROD, &a2_prod_reg_params); if (res) { TETH_ERR("ipa_rm_register() failed\n"); goto bail; } } } for (i = 0; i < num_of_iterations; i++) { if (teth_ctx->tethering_mode == TETH_TETHERING_MODE_MBIM) lcid = teth_ctx->mbim_stream_id_to_channel_id[i]; idx = get_ch_info_idx(lcid); teth_ctx->ch_info[idx].is_connected = true; TETH_DBG("lcid #%d is connected.\n", lcid); } mutex_unlock(&teth_ctx->init_mutex); for (i = 0; i < num_of_iterations; i++) { if (teth_ctx->tethering_mode == TETH_TETHERING_MODE_MBIM) lcid = teth_ctx->mbim_stream_id_to_channel_id[i]; idx = get_ch_info_idx(lcid); if (mode == TETH_TETHERING_MODE_MBIM) { TETH_DBG( "TETH_TETHERING_MODE_MBIM: setting link protocol to IP.\n"); teth_ctx->ch_info[idx].link_protocol = TETH_LINK_PROTOCOL_IP; } if (teth_ctx->ch_info[idx].aggr_params_known) { res = teth_set_aggregation(lcid); if (res) { TETH_ERR("Failed setting aggregation params\n"); goto bail; } } } /* In case of IP link protocol, complete HW bridge */ if ((teth_ctx->ch_info[idx].link_protocol == TETH_LINK_PROTOCOL_IP) && (!teth_ctx->ch_info[idx].comp_hw_bridge_in_progress) && (teth_ctx->ch_info[idx].aggr_params_known) && (!teth_ctx->ch_info[idx].is_hw_bridge_complete)) { work_data = &teth_ctx->ch_info[idx].hw_bridge_work; INIT_WORK(&work_data->comp_hw_bridge_work, complete_hw_bridge); work_data->lcid = lcid; teth_ctx->ch_info[idx].comp_hw_bridge_in_progress = true; queue_work(teth_ctx->teth_wq, &work_data->comp_hw_bridge_work); } bail: ipa_rm_inactivity_timer_release_resource(IPA_RM_RESOURCE_BRIDGE_PROD); TETH_DBG_FUNC_EXIT(); return res; } EXPORT_SYMBOL(teth_bridge_connect); static void set_aggr_default_params(struct teth_aggr_params_link *params) { if (params->max_datagrams == 0) params->max_datagrams = TETH_AGGR_MAX_DATAGRAMS_DEFAULT; if (params->max_transfer_size_byte == 0) params->max_transfer_size_byte = TETH_AGGR_MAX_AGGR_PACKET_SIZE_DEFAULT; } /** * teth_set_bridge_mode() - set the link protocol (IP / Ethernet) * @param lcid: logical channel number */ static void teth_set_bridge_mode(u16 lcid, enum teth_link_protocol_type link_protocol) { u16 idx = get_ch_info_idx(lcid); teth_ctx->ch_info[idx].link_protocol = link_protocol; teth_ctx->ch_info[idx].is_hw_bridge_complete = false; memset(&teth_ctx->mac_addresses, 0, sizeof(teth_ctx->mac_addresses)); } /** * teth_bridge_set_aggr_params() - set aggregation parameters * @param client: name of the IPA "client" * @param aggr_params: aggregation parmeters for uplink and downlink * * Besides setting the aggregation parameters, the function enforces max * transfer size which is less then 8K and also forbids Ethernet link protocol * with MBIM aggregation which is not supported by HW. */ static int teth_bridge_set_aggr_params(struct teth_aggr_params *aggr_params, enum ipa_client_type client) { int res; u16 idx; u16 lcid, i; int num_of_iteration = 1; TETH_DBG_FUNC_ENTRY(); if (!aggr_params) { TETH_ERR("Invalid parameter\n"); return -EINVAL; } if (teth_ctx->tethering_mode == TETH_TETHERING_MODE_MBIM) { num_of_iteration = 8; lcid = A2_MUX_MULTI_MBIM_13; } else { lcid = get_channel_id_from_client_prod(client); } /* * In case the requested max transfer size is larger than 8K, set it to * to the default 8K */ if (aggr_params->dl.max_transfer_size_byte > TETH_AGGR_MAX_AGGR_PACKET_SIZE_DEFAULT) aggr_params->dl.max_transfer_size_byte = TETH_AGGR_MAX_AGGR_PACKET_SIZE_DEFAULT; if (aggr_params->ul.max_transfer_size_byte > TETH_AGGR_MAX_AGGR_PACKET_SIZE_DEFAULT) aggr_params->ul.max_transfer_size_byte = TETH_AGGR_MAX_AGGR_PACKET_SIZE_DEFAULT; /* Ethernet link protocol and MBIM aggregation is not supported */ for (i = 0; i < num_of_iteration; i++, lcid++) { idx = get_ch_info_idx(lcid); if (teth_ctx->ch_info[idx].link_protocol == TETH_LINK_PROTOCOL_ETHERNET && (aggr_params->dl.aggr_prot == TETH_AGGR_PROTOCOL_MBIM || aggr_params->ul.aggr_prot == TETH_AGGR_PROTOCOL_MBIM)) { TETH_ERR("Ethernet with MBIM is not supported.\n"); return -EINVAL; } } res = teth_request_resource(); if (res) { TETH_ERR("request_resource() failed.\n"); return res; } if (teth_ctx->tethering_mode == TETH_TETHERING_MODE_MBIM) lcid = A2_MUX_MULTI_MBIM_13; else lcid = get_channel_id_from_client_prod(client); for (i = 0; i < num_of_iteration; ++i, ++lcid) { idx = get_ch_info_idx(lcid); memcpy(&teth_ctx->ch_info[idx].aggr_params, aggr_params, sizeof(struct teth_aggr_params)); set_aggr_default_params(&teth_ctx->ch_info[idx].aggr_params.dl); set_aggr_default_params(&teth_ctx->ch_info[idx].aggr_params.ul); teth_ctx->ch_info[idx].aggr_params_known = true; res = teth_set_aggregation(lcid); if (res) TETH_ERR( "Setting aggregation params fail, lcid %d\n", lcid); } ipa_rm_inactivity_timer_release_resource(IPA_RM_RESOURCE_BRIDGE_PROD); TETH_DBG_FUNC_EXIT(); return res; } /** * teth_bridge_set_mbim_aggr_params() - Kernel API to set aggregation parameters * for MBIM * @param client: name of the IPA "client" * @param aggr_params: aggregation parmeters for uplink and downlink * Besides setting the aggregation parameters, the function enforces max * transfer size which is less then 8K and also forbids Ethernet link protocol * with MBIM aggregation which is not supported by HW. */ int teth_bridge_set_mbim_aggr_params(struct teth_aggr_params *aggr_params, enum ipa_client_type client) { teth_ctx->tethering_mode = TETH_TETHERING_MODE_MBIM; return teth_bridge_set_aggr_params(aggr_params, client); } EXPORT_SYMBOL(teth_bridge_set_mbim_aggr_params); static long teth_bridge_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { int res = 0; struct teth_ioc_aggr_params i_aggr_params; struct teth_ioc_set_bridge_mode bridge_mode_params; struct hw_bridge_work_wrap *work_data; u16 i = 0; TETH_DBG("cmd=%x nr=%d\n", cmd, _IOC_NR(cmd)); if ((_IOC_TYPE(cmd) != TETH_BRIDGE_IOC_MAGIC) || (_IOC_NR(cmd) >= TETH_BRIDGE_IOCTL_MAX)) { TETH_ERR("Invalid ioctl\n"); return -ENOIOCTLCMD; } switch (cmd) { case TETH_BRIDGE_IOC_SET_BRIDGE_MODE: TETH_DBG("TETH_BRIDGE_IOC_SET_BRIDGE_MODE ioctl called\n"); res = copy_from_user(&bridge_mode_params, (struct teth_ioc_set_bridge_mode *)arg, sizeof(struct teth_ioc_set_bridge_mode)); if (res) { TETH_ERR("Error, res = %d\n", res); res = -EFAULT; break; } if (bridge_mode_params.lcid >= A2_MUX_NUM_CHANNELS || bridge_mode_params.lcid < A2_MUX_WWAN_0) { TETH_ERR("Invalid lcid = %d\n", bridge_mode_params.lcid); res = -EINVAL; break; } i = get_ch_info_idx(bridge_mode_params.lcid); if (teth_ctx->ch_info[i].ch_type == TETH_EMBEDDED_CH && bridge_mode_params.link_protocol == TETH_LINK_PROTOCOL_ETHERNET) { TETH_ERR( "Invalid link protocol. Ethernet is not supported for multi rmnet\n"); res = -EINVAL; break; } if (teth_ctx->ch_info[i].link_protocol != bridge_mode_params.link_protocol) teth_set_bridge_mode(bridge_mode_params.lcid, bridge_mode_params.link_protocol); break; case TETH_BRIDGE_IOC_SET_AGGR_PARAMS: TETH_DBG("TETH_BRIDGE_IOC_SET_AGGR_PARAMS ioctl called\n"); res = copy_from_user(&i_aggr_params, (struct teth_ioc_aggr_params *)arg, sizeof(struct teth_ioc_aggr_params)); if (res) { TETH_ERR("Error, res = %d\n", res); res = -EFAULT; break; } if (i_aggr_params.lcid >= A2_MUX_NUM_CHANNELS || i_aggr_params.lcid < A2_MUX_WWAN_0) { TETH_ERR("Invalid lcid = %d\n", i_aggr_params.lcid); res = -EINVAL; break; } i = get_ch_info_idx(i_aggr_params.lcid); res = teth_bridge_set_aggr_params(&i_aggr_params.aggr_params, get_prod_client(i_aggr_params.lcid)); if (res) break; /* In case of IP link protocol, complete HW bridge */ if ((teth_ctx->ch_info[i].link_protocol == TETH_LINK_PROTOCOL_IP) && (!teth_ctx->ch_info[i].comp_hw_bridge_in_progress) && (!teth_ctx->ch_info[i].is_hw_bridge_complete)) { work_data = &teth_ctx->ch_info[i].hw_bridge_work; INIT_WORK(&work_data->comp_hw_bridge_work, complete_hw_bridge); work_data->lcid = i_aggr_params.lcid; teth_ctx->ch_info[i].comp_hw_bridge_in_progress = true; queue_work(teth_ctx->teth_wq, &work_data->comp_hw_bridge_work); } break; case TETH_BRIDGE_IOC_GET_AGGR_PARAMS: TETH_DBG("TETH_BRIDGE_IOC_GET_AGGR_PARAMS ioctl called\n"); /*get the channel number from the ioctl params*/ res = copy_from_user(&i_aggr_params, (struct teth_ioc_aggr_params *)arg, sizeof(struct teth_ioc_aggr_params)); if (res) { TETH_ERR("Error, res = %d\n", res); res = -EFAULT; break; } TETH_DBG("get_aggr_params ioctl for lcid #%d\n", i_aggr_params.lcid); if (i_aggr_params.lcid >= A2_MUX_NUM_CHANNELS || i_aggr_params.lcid < A2_MUX_WWAN_0) { TETH_ERR("Invalid lcid = %d\n", i_aggr_params.lcid); res = -EINVAL; break; } i = get_ch_info_idx(i_aggr_params.lcid); i_aggr_params.aggr_params = teth_ctx->ch_info[i].aggr_params; if (copy_to_user((u8 *)arg, (u8 *)&i_aggr_params, sizeof(struct teth_ioc_aggr_params))) { res = -EFAULT; break; } TETH_DBG("TETH_BRIDGE_IOC_GET_AGGR_PARAMS ioctl end\n"); break; case TETH_BRIDGE_IOC_GET_AGGR_CAPABILITIES: { u16 sz; u16 pyld_sz; struct teth_aggr_capabilities caps; TETH_DBG("GET_AGGR_CAPABILITIES ioctl called\n"); sz = sizeof(struct teth_aggr_capabilities); if (copy_from_user(&caps, (struct teth_aggr_capabilities *)arg, sz)) { res = -EFAULT; break; } if (caps.num_protocols != teth_ctx->aggr_caps->num_protocols) { caps.num_protocols = teth_ctx->aggr_caps->num_protocols; if (copy_to_user((struct teth_aggr_capabilities *)arg, &caps, sz)) { res = -EFAULT; break; } TETH_DBG("Not enough space allocated.\n"); res = -EAGAIN; break; } pyld_sz = sz + caps.num_protocols * sizeof(struct teth_aggr_params_link); if (copy_to_user((u8 *)arg, (u8 *)(teth_ctx->aggr_caps), pyld_sz)) { res = -EFAULT; break; } } break; } return res; } /** * set_aggr_capabilities() - allocates and fills the aggregation capabilities * struct */ static int set_aggr_capabilities(void) { u16 NUM_PROTOCOLS = 2; teth_ctx->aggr_caps = kzalloc(sizeof(struct teth_aggr_capabilities) + NUM_PROTOCOLS * sizeof(struct teth_aggr_params_link), GFP_KERNEL); if (!teth_ctx->aggr_caps) { TETH_ERR("Memory alloc failed for aggregation capabilities.\n"); return -ENOMEM; } teth_ctx->aggr_caps->num_protocols = NUM_PROTOCOLS; teth_ctx->aggr_caps->prot_caps[0].aggr_prot = TETH_AGGR_PROTOCOL_MBIM; set_aggr_default_params(&teth_ctx->aggr_caps->prot_caps[0]); teth_ctx->aggr_caps->prot_caps[1].aggr_prot = TETH_AGGR_PROTOCOL_TLP; set_aggr_default_params(&teth_ctx->aggr_caps->prot_caps[1]); return 0; } /** * teth_bridge_get_client_handles() - Get USB <--> IPA pipe handles * @producer_handle: USB --> IPA pipe handle * @consumer_handle: IPA --> USB pipe handle */ void teth_bridge_get_client_handles(u32 *producer_handle, u32 *consumer_handle) { if (producer_handle == NULL || consumer_handle == NULL) return; *producer_handle = teth_ctx->ch_info[0].usb_ipa_pipe_hdl; *consumer_handle = teth_ctx->ch_info[0].ipa_usb_pipe_hdl; } #ifdef CONFIG_DEBUG_FS static struct dentry *dent; static struct dentry *dfile_lcid; static struct dentry *dfile_link_protocol; static struct dentry *dfile_get_aggr_params; static struct dentry *dfile_set_aggr_protocol; static struct dentry *dfile_stats; static struct dentry *dfile_is_hw_bridge_complete; static ssize_t teth_debugfs_read_lcid(struct file *file, char __user *ubuf, size_t count, loff_t *ppos) { int nbytes; nbytes = scnprintf(dbg_buff, TETH_MAX_MSG_LEN, "lcid = %d\n", teth_ctx->debugfs_lcid); return simple_read_from_buffer(ubuf, count, ppos, dbg_buff, nbytes); } static ssize_t teth_debugfs_write_lcid(struct file *file, const char __user *ubuf, size_t count, loff_t *ppos) { unsigned long missing; if (sizeof(dbg_buff) < count + 1) return -EFAULT; missing = copy_from_user(dbg_buff, ubuf, count); if (missing) return -EFAULT; if (count > 0) dbg_buff[count-1] = '\0'; if (strcmp(dbg_buff, "8") == 0) { teth_ctx->debugfs_lcid = A2_MUX_TETHERED_0; } else if (strcmp(dbg_buff, "10") == 0) { teth_ctx->debugfs_lcid = A2_MUX_MULTI_RMNET_10; } else if (strcmp(dbg_buff, "11") == 0) { teth_ctx->debugfs_lcid = A2_MUX_MULTI_RMNET_11; } else if (strcmp(dbg_buff, "12") == 0) { teth_ctx->debugfs_lcid = A2_MUX_MULTI_RMNET_12; } else if (strcmp(dbg_buff, "13") == 0) { teth_ctx->debugfs_lcid = A2_MUX_MULTI_MBIM_13; } else if (strcmp(dbg_buff, "14") == 0) { teth_ctx->debugfs_lcid = A2_MUX_MULTI_MBIM_14; } else if (strcmp(dbg_buff, "15") == 0) { teth_ctx->debugfs_lcid = A2_MUX_MULTI_MBIM_15; } else if (strcmp(dbg_buff, "16") == 0) { teth_ctx->debugfs_lcid = A2_MUX_MULTI_MBIM_16; } else if (strcmp(dbg_buff, "17") == 0) { teth_ctx->debugfs_lcid = A2_MUX_MULTI_MBIM_17; } else if (strcmp(dbg_buff, "18") == 0) { teth_ctx->debugfs_lcid = A2_MUX_MULTI_MBIM_18; } else if (strcmp(dbg_buff, "19") == 0) { teth_ctx->debugfs_lcid = A2_MUX_MULTI_MBIM_19; } else if (strcmp(dbg_buff, "20") == 0) { teth_ctx->debugfs_lcid = A2_MUX_MULTI_MBIM_20; } else { teth_ctx->debugfs_lcid = A2_MUX_TETHERED_0; TETH_ERR("Bad lcid, got %s,\n" "Use <8, 10-20>.\n", dbg_buff); } return count; } static ssize_t teth_debugfs_read_link_protocol(struct file *file, char __user *ubuf, size_t count, loff_t *ppos) { int nbytes; u16 ch_info_idx; ch_info_idx = get_ch_info_idx(teth_ctx->debugfs_lcid); nbytes = scnprintf(dbg_buff, TETH_MAX_MSG_LEN, "Link protocol = %s\n", (teth_ctx->ch_info[ch_info_idx].link_protocol == TETH_LINK_PROTOCOL_ETHERNET) ? "ETHERNET" : "IP"); return simple_read_from_buffer(ubuf, count, ppos, dbg_buff, nbytes); } static ssize_t teth_debugfs_write_link_protocol(struct file *file, const char __user *ubuf, size_t count, loff_t *ppos) { unsigned long missing; enum teth_link_protocol_type link_protocol; if (sizeof(dbg_buff) < count + 1) return -EFAULT; missing = copy_from_user(dbg_buff, ubuf, count); if (missing) return -EFAULT; if (count > 0) dbg_buff[count-1] = '\0'; if (strcmp(dbg_buff, "ETHERNET") == 0) { link_protocol = TETH_LINK_PROTOCOL_ETHERNET; } else if (strcmp(dbg_buff, "IP") == 0) { link_protocol = TETH_LINK_PROTOCOL_IP; } else { TETH_ERR("Bad link protocol, got %s,\n" "Use or .\n", dbg_buff); return count; } teth_set_bridge_mode(teth_ctx->debugfs_lcid , link_protocol); return count; } static ssize_t teth_debugfs_read_aggr_params(struct file *file, char __user *ubuf, size_t count, loff_t *ppos) { int nbytes = 0; char aggr_str[20]; u16 idx; idx = get_ch_info_idx(teth_ctx->debugfs_lcid); aggr_prot_to_str(teth_ctx->ch_info[idx].aggr_params.ul.aggr_prot, aggr_str, sizeof(aggr_str)-1); nbytes += scnprintf(&dbg_buff[nbytes], TETH_MAX_MSG_LEN - nbytes, "Aggregation parameters for uplink:\n"); nbytes += scnprintf(&dbg_buff[nbytes], TETH_MAX_MSG_LEN - nbytes, " Aggregation protocol: %s\n", aggr_str); nbytes += scnprintf( &dbg_buff[nbytes], TETH_MAX_MSG_LEN - nbytes, " Max transfer size [byte]: %d\n", teth_ctx->ch_info[idx].aggr_params.ul.max_transfer_size_byte); nbytes += scnprintf( &dbg_buff[nbytes], TETH_MAX_MSG_LEN - nbytes, " Max datagrams: %d\n", teth_ctx->ch_info[idx].aggr_params.ul.max_datagrams); aggr_prot_to_str(teth_ctx->ch_info[idx].aggr_params.dl.aggr_prot, aggr_str, sizeof(aggr_str)-1); nbytes += scnprintf(&dbg_buff[nbytes], TETH_MAX_MSG_LEN, "Aggregation parameters for downlink:\n"); nbytes += scnprintf(&dbg_buff[nbytes], TETH_MAX_MSG_LEN - nbytes, " Aggregation protocol: %s\n", aggr_str); nbytes += scnprintf( &dbg_buff[nbytes], TETH_MAX_MSG_LEN - nbytes, " Max transfer size [byte]: %d\n", teth_ctx->ch_info[idx].aggr_params.dl.max_transfer_size_byte); nbytes += scnprintf( &dbg_buff[nbytes], TETH_MAX_MSG_LEN - nbytes, " Max datagrams: %d\n", teth_ctx->ch_info[idx].aggr_params.dl.max_datagrams); return simple_read_from_buffer(ubuf, count, ppos, dbg_buff, nbytes); } static ssize_t teth_debugfs_set_aggr_protocol(struct file *file, const char __user *ubuf, size_t count, loff_t *ppos) { unsigned long missing; enum teth_aggr_protocol_type aggr_prot; int res; u16 idx; idx = get_ch_info_idx(teth_ctx->debugfs_lcid); if (sizeof(dbg_buff) < count + 1) return -EFAULT; missing = copy_from_user(dbg_buff, ubuf, count); if (missing) return -EFAULT; if (count > 0) dbg_buff[count-1] = '\0'; set_aggr_default_params(&teth_ctx->ch_info[idx].aggr_params.dl); set_aggr_default_params(&teth_ctx->ch_info[idx].aggr_params.ul); if (strcmp(dbg_buff, "NONE") == 0) { aggr_prot = TETH_AGGR_PROTOCOL_NONE; } else if (strcmp(dbg_buff, "MBIM") == 0) { aggr_prot = TETH_AGGR_PROTOCOL_MBIM; } else if (strcmp(dbg_buff, "TLP") == 0) { aggr_prot = TETH_AGGR_PROTOCOL_TLP; } else { TETH_ERR("Bad aggregation protocol, got %s,\n" "Use , or .\n", dbg_buff); return count; } teth_ctx->ch_info[idx].aggr_params.dl.aggr_prot = aggr_prot; teth_ctx->ch_info[idx].aggr_params.ul.aggr_prot = aggr_prot; teth_ctx->ch_info[idx].aggr_params_known = true; res = teth_set_aggregation(teth_ctx->debugfs_lcid); if (res) TETH_ERR("Failed setting aggregation params\n"); return count; } static ssize_t teth_debugfs_stats(struct file *file, char __user *ubuf, size_t count, loff_t *ppos) { int nbytes = 0; nbytes += scnprintf(&dbg_buff[nbytes], TETH_MAX_MSG_LEN - nbytes, "USB to A2 SW Tx packets: %lld\n", teth_ctx->stats.usb_to_a2_num_sw_tx_packets); nbytes += scnprintf(&dbg_buff[nbytes], TETH_MAX_MSG_LEN - nbytes, "A2 to USB SW Tx packets: %lld\n", teth_ctx->stats.a2_to_usb_num_sw_tx_packets); nbytes += scnprintf( &dbg_buff[nbytes], TETH_MAX_MSG_LEN - nbytes, "SW Tx packets sent during resource wakeup: %lld\n", teth_ctx->stats.num_sw_tx_packets_during_resource_wakeup); return simple_read_from_buffer(ubuf, count, ppos, dbg_buff, nbytes); } static ssize_t teth_debugfs_hw_bridge_status(struct file *file, char __user *ubuf, size_t count, loff_t *ppos) { int nbytes = 0; u16 ch_info_idx; ch_info_idx = get_ch_info_idx(teth_ctx->debugfs_lcid); if (teth_ctx->ch_info[ch_info_idx].is_hw_bridge_complete) nbytes += scnprintf(&dbg_buff[nbytes], TETH_MAX_MSG_LEN - nbytes, "HW bridge is in use.\n"); else nbytes += scnprintf(&dbg_buff[nbytes], TETH_MAX_MSG_LEN - nbytes, "SW bridge is in use. HW bridge not complete yet.\n"); return simple_read_from_buffer(ubuf, count, ppos, dbg_buff, nbytes); } const struct file_operations teth_lcid_ops = { .read = teth_debugfs_read_lcid, .write = teth_debugfs_write_lcid, }; const struct file_operations teth_link_protocol_ops = { .read = teth_debugfs_read_link_protocol, .write = teth_debugfs_write_link_protocol, }; const struct file_operations teth_get_aggr_params_ops = { .read = teth_debugfs_read_aggr_params, }; const struct file_operations teth_set_aggr_protocol_ops = { .write = teth_debugfs_set_aggr_protocol, }; const struct file_operations teth_stats_ops = { .read = teth_debugfs_stats, }; const struct file_operations teth_hw_bridge_status_ops = { .read = teth_debugfs_hw_bridge_status, }; void teth_debugfs_init(void) { const mode_t read_only_mode = S_IRUSR | S_IRGRP | S_IROTH; const mode_t read_write_mode = S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR | S_IWGRP | S_IWOTH; dent = debugfs_create_dir("ipa_teth", 0); if (IS_ERR(dent)) { IPAERR("fail to create folder ipa_teth debug_fs.\n"); return; } dfile_lcid = debugfs_create_file("lcid", read_write_mode, dent, 0, &teth_lcid_ops); if (!dfile_lcid || IS_ERR(dfile_lcid)) { IPAERR("fail to create file lcid\n"); goto fail; } dfile_link_protocol = debugfs_create_file("link_protocol", read_write_mode, dent, 0, &teth_link_protocol_ops); if (!dfile_link_protocol || IS_ERR(dfile_link_protocol)) { IPAERR("fail to create file link_protocol\n"); goto fail; } dfile_get_aggr_params = debugfs_create_file("get_aggr_params", read_only_mode, dent, 0, &teth_get_aggr_params_ops); if (!dfile_get_aggr_params || IS_ERR(dfile_get_aggr_params)) { IPAERR("fail to create file get_aggr_params\n"); goto fail; } dfile_set_aggr_protocol = debugfs_create_file("set_aggr_protocol", read_only_mode, dent, 0, &teth_set_aggr_protocol_ops); if (!dfile_set_aggr_protocol || IS_ERR(dfile_set_aggr_protocol)) { IPAERR("fail to create file set_aggr_protocol\n"); goto fail; } dfile_stats = debugfs_create_file("stats", read_only_mode, dent, 0, &teth_stats_ops); if (!dfile_stats || IS_ERR(dfile_stats)) { IPAERR("fail to create file stats\n"); goto fail; } dfile_is_hw_bridge_complete = debugfs_create_file("is_hw_bridge_complete", read_only_mode, dent, 0, &teth_hw_bridge_status_ops); if (!dfile_is_hw_bridge_complete || IS_ERR(dfile_is_hw_bridge_complete)) { IPAERR("fail to create file is_hw_bridge_complete\n"); goto fail; } return; fail: debugfs_remove_recursive(dent); } #else void teth_debugfs_init(void) {} #endif /* CONFIG_DEBUG_FS */ static const struct file_operations teth_bridge_drv_fops = { .owner = THIS_MODULE, .unlocked_ioctl = teth_bridge_ioctl, }; static int alloc_del_hnds(void) { int idx; teth_ctx->hdr_del = kzalloc(sizeof(struct ipa_ioc_del_hdr) + TETH_TOTAL_HDR_ENTRIES * sizeof(struct ipa_hdr_del), GFP_KERNEL); if (!teth_ctx->hdr_del) { TETH_ERR("kzalloc err for hdr_del.\n"); return -ENOMEM; } for (idx = 0; idx < TETH_NUM_CHANNELS; idx++) { teth_ctx->ch_info[idx].routing_del[IPA_IP_v4] = kzalloc(sizeof(struct ipa_ioc_del_rt_rule) + TETH_TOTAL_RT_ENTRIES_IP * sizeof(struct ipa_rt_rule_del), GFP_KERNEL); if (!teth_ctx->ch_info[idx].routing_del[IPA_IP_v4]) { TETH_ERR("kzalloc err for routing_del[IPA_IP_v4].\n"); goto fail_alloc_routing_del_ipv4; } } for (idx = 0; idx < TETH_NUM_CHANNELS; idx++) { teth_ctx->ch_info[idx].routing_del[IPA_IP_v6] = kzalloc(sizeof(struct ipa_ioc_del_rt_rule) + TETH_TOTAL_RT_ENTRIES_IP * sizeof(struct ipa_rt_rule_del), GFP_KERNEL); if (!teth_ctx->ch_info[idx].routing_del[IPA_IP_v6]) { TETH_ERR("kzalloc err for routing_del[IPA_IP_v6].\n"); goto fail_alloc_routing_del_ipv6; } } for (idx = 0; idx < TETH_NUM_CHANNELS; idx++) { teth_ctx->ch_info[idx].filtering_del[IPA_IP_v4] = kzalloc(sizeof(struct ipa_ioc_del_flt_rule) + TETH_TOTAL_FLT_ENTRIES_IP * sizeof(struct ipa_flt_rule_del), GFP_KERNEL); if (!teth_ctx->ch_info[idx].filtering_del[IPA_IP_v4]) { TETH_ERR("kzalloc err.\n"); goto fail_alloc_filtering_del_ipv4; } } for (idx = 0; idx < TETH_NUM_CHANNELS; idx++) { teth_ctx->ch_info[idx].filtering_del[IPA_IP_v6] = kzalloc(sizeof(struct ipa_ioc_del_flt_rule) + TETH_TOTAL_FLT_ENTRIES_IP * sizeof(struct ipa_flt_rule_del), GFP_KERNEL); if (!teth_ctx->ch_info[idx].filtering_del[IPA_IP_v6]) { TETH_ERR("kzalloc err.\n"); goto fail_alloc_filtering_del_ipv6; } } return 0; fail_alloc_filtering_del_ipv6: for (idx = 0; idx < TETH_NUM_CHANNELS; idx++) kfree(teth_ctx->ch_info[idx].filtering_del[IPA_IP_v6]); fail_alloc_filtering_del_ipv4: for (idx = 0; idx < TETH_NUM_CHANNELS; idx++) kfree(teth_ctx->ch_info[idx].filtering_del[IPA_IP_v4]); fail_alloc_routing_del_ipv6: for (idx = 0; idx < TETH_NUM_CHANNELS; idx++) kfree(teth_ctx->ch_info[idx].routing_del[IPA_IP_v6]); fail_alloc_routing_del_ipv4: for (idx = 0; idx < TETH_NUM_CHANNELS; idx++) kfree(teth_ctx->ch_info[idx].routing_del[IPA_IP_v4]); kfree(teth_ctx->hdr_del); return -ENOMEM; } /** * teth_bridge_driver_init() - Initialize tethering bridge driver * */ int teth_bridge_driver_init(void) { int res; struct ipa_rm_create_params bridge_prod_params; res = -ENOMEM; TETH_DBG("Tethering bridge driver init\n"); teth_ctx = kzalloc(sizeof(*teth_ctx), GFP_KERNEL); if (!teth_ctx) { TETH_ERR("kzalloc err.\n"); return -ENOMEM; } teth_ctx->ch_info = kzalloc(sizeof(struct logic_ch_info)*TETH_NUM_CHANNELS, GFP_KERNEL); if (!teth_ctx->ch_info) { TETH_ERR("kzalloc err.\n"); goto fail_alloc_channel_info; } res = set_aggr_capabilities(); if (res) { TETH_ERR("kzalloc err.\n"); goto fail_alloc_aggr_caps; } teth_ctx->class = class_create(THIS_MODULE, TETH_BRIDGE_DRV_NAME); res = alloc_chrdev_region(&teth_ctx->dev_num, 0, 1, TETH_BRIDGE_DRV_NAME); if (res) { TETH_ERR("alloc_chrdev_region err.\n"); res = -ENODEV; goto fail_alloc_chrdev_region; } teth_ctx->dev = device_create(teth_ctx->class, NULL, teth_ctx->dev_num, teth_ctx, TETH_BRIDGE_DRV_NAME); if (IS_ERR(teth_ctx->dev)) { TETH_ERR(":device_create err.\n"); res = -ENODEV; goto fail_device_create; } cdev_init(&teth_ctx->cdev, &teth_bridge_drv_fops); teth_ctx->cdev.owner = THIS_MODULE; teth_ctx->cdev.ops = &teth_bridge_drv_fops; res = cdev_add(&teth_ctx->cdev, teth_ctx->dev_num, 1); if (res) { TETH_ERR(":cdev_add err=%d\n", -res); res = -ENODEV; goto fail_cdev_add; } teth_debugfs_init(); /* Create BRIDGE_PROD entity in IPA Resource Manager */ bridge_prod_params.name = IPA_RM_RESOURCE_BRIDGE_PROD; bridge_prod_params.reg_params.user_data = NULL; bridge_prod_params.reg_params.notify_cb = bridge_prod_notify_cb; res = ipa_rm_create_resource(&bridge_prod_params); if (res) { TETH_ERR("ipa_rm_create_resource() failed\n"); goto fail_cdev_add; } init_completion(&teth_ctx->is_bridge_prod_up); init_completion(&teth_ctx->is_bridge_prod_down); res = ipa_rm_inactivity_timer_init(IPA_RM_RESOURCE_BRIDGE_PROD, TETH_INACTIVITY_TIME_MSEC); if (res) { TETH_ERR("ipa_rm_inactivity_timer_init() failed, res=%d\n", res); goto fail_cdev_add; } teth_ctx->teth_wq = create_workqueue(TETH_WORKQUEUE_NAME); if (!teth_ctx->teth_wq) { TETH_ERR("workqueue creation failed\n"); goto fail_cdev_add; } res = alloc_del_hnds(); if (res) { TETH_ERR("kzalloc err.\n"); goto fail_cdev_add; } initialize_context(); initialize_ch_info_arr(); mutex_init(&teth_ctx->request_resource_mutex); mutex_init(&teth_ctx->init_mutex); TETH_DBG("Tethering bridge driver init OK\n"); return 0; fail_cdev_add: device_destroy(teth_ctx->class, teth_ctx->dev_num); fail_device_create: unregister_chrdev_region(teth_ctx->dev_num, 1); fail_alloc_chrdev_region: kfree(teth_ctx->aggr_caps); fail_alloc_aggr_caps: kfree(teth_ctx->ch_info); fail_alloc_channel_info: kfree(teth_ctx); teth_ctx = NULL; return res; } EXPORT_SYMBOL(teth_bridge_driver_init); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("Tethering bridge driver");