M7350/kernel/drivers/platform/msm/ipa/teth_bridge.c

3375 lines
93 KiB
C
Raw Normal View History

2024-09-09 08:52:07 +00:00
/* 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 <linux/completion.h>
#include <linux/debugfs.h>
#include <linux/export.h>
#include <linux/fs.h>
#include <linux/if_ether.h>
#include <linux/ioctl.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/msm_ipa.h>
#include <linux/mutex.h>
#include <linux/skbuff.h>
#include <linux/types.h>
#include <mach/bam_dmux.h>
#include <mach/ipa.h>
#include <mach/sps.h>
#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 <ETHERNET> or <IP>.\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 <NONE>, <MBIM> or <TLP>.\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");