blob: d08ac64370f7684e0b5d4c273ea8aba6d226973d [file] [log] [blame]
/* 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/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_tbl_ipv4"
#define A2_TO_USB_RT_TBL_NAME_IPV4 "a2_usb_rt_tbl_ipv4"
#define USB_TO_A2_RT_TBL_NAME_IPV6 "usb_a2_rt_tbl_ipv6"
#define A2_TO_USB_RT_TBL_NAME_IPV6 "a2_usb_rt_tbl_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 6
#define TETH_TOTAL_RT_ENTRIES_IP 3
#define TETH_TOTAL_FLT_ENTRIES_IP 2
#define TETH_IP_FAMILIES 2
/**
* 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 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
* @usb_ipa_pipe_hdl: USB to IPA pipe handle
* @ipa_usb_pipe_hdl: IPA to USB pipe handle
* @a2_ipa_pipe_hdl: A2 to IPA pipe handle
* @ipa_a2_pipe_hdl: IPA to A2 pipe handle
* @is_connected: is the tethered bridge connected ?
* @link_protocol: IP / Ethernet
* @mac_addresses: Struct which holds host pc and device MAC addresses, relevant
* in ethernet mode only
* @is_hw_bridge_complete: is HW bridge setup ?
* @aggr_params: aggregation parmeters
* @aggr_params_known: are the aggregation parameters known ?
* @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
* @comp_hw_bridge_work: used for setting up the HW bridge using a workqueue
* @comp_hw_bridge_in_progress: true when the HW bridge setup is in progress
* @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
* @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 teth_bridge_ctx {
struct class *class;
dev_t dev_num;
struct device *dev;
struct cdev cdev;
u32 usb_ipa_pipe_hdl;
u32 ipa_usb_pipe_hdl;
u32 a2_ipa_pipe_hdl;
u32 ipa_a2_pipe_hdl;
bool is_connected;
enum teth_link_protocol_type link_protocol;
struct mac_addresses_type mac_addresses;
bool is_hw_bridge_complete;
struct teth_aggr_params aggr_params;
bool aggr_params_known;
enum teth_tethering_mode tethering_mode;
struct completion is_bridge_prod_up;
struct completion is_bridge_prod_down;
struct work_struct comp_hw_bridge_work;
bool comp_hw_bridge_in_progress;
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 ipa_ioc_del_rt_rule *routing_del[TETH_IP_FAMILIES];
struct ipa_ioc_del_flt_rule *filtering_del[TETH_IP_FAMILIES];
};
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
*/
struct teth_work {
struct work_struct work;
struct sk_buff *skb;
enum teth_packet_direction dir;
};
#ifdef CONFIG_DEBUG_FS
#define TETH_MAX_MSG_LEN 512
static char dbg_buff[TETH_MAX_MSG_LEN];
#endif
/**
* add_eth_hdrs() - 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(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;
}
static int configure_ipa_header_block_internal(u32 usb_ipa_hdr_len,
u32 a2_ipa_hdr_len,
u32 ipa_usb_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 USB->IPA pipe and A2->IPA pipe */
memset(&hdr_cfg, 0, sizeof(hdr_cfg));
hdr_cfg.hdr_len = usb_ipa_hdr_len;
res = ipa_cfg_ep_hdr(teth_ctx->usb_ipa_pipe_hdl, &hdr_cfg);
if (res) {
TETH_ERR("Header removal config for USB->IPA pipe failed\n");
goto bail;
}
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->USB pipe and IPA->A2 pipe */
hdr_cfg.hdr_len = ipa_usb_hdr_len;
res = ipa_cfg_ep_hdr(teth_ctx->ipa_usb_pipe_hdl, &hdr_cfg);
if (res) {
TETH_ERR("Header insertion config for IPA->USB pipe failed\n");
goto bail;
}
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;
}
static int add_mbim_hdr(void)
{
int res;
struct ipa_ioc_add_hdr *mbim_hdr;
u8 mbim_stream_id = 0;
int idx;
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;
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\n");
res = -EFAULT;
} else {
TETH_DBG("Added MBIM stream ID header\n");
}
/* 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;
kfree(mbim_hdr);
TETH_DBG_FUNC_EXIT();
return res;
}
/**
* configure_ipa_header_block() - adds headers and configures endpoint registers
*
* - For IP link protocol and MBIM aggregation, configure MBIM header
* - For Ethernet link protocol, configure Ethernet headers
*/
static int configure_ipa_header_block(void)
{
int res;
u32 hdr_len = 0;
u32 ipa_usb_hdr_len = 0;
TETH_DBG_FUNC_ENTRY();
if (teth_ctx->link_protocol == TETH_LINK_PROTOCOL_IP) {
/*
* Create a new header for MBIM stream ID and associate it with
* the IPA->USB routing table
*/
if (teth_ctx->aggr_params.dl.aggr_prot ==
TETH_AGGR_PROTOCOL_MBIM) {
ipa_usb_hdr_len = 1;
res = add_mbim_hdr();
if (res) {
TETH_ERR("Failed adding MBIM header\n");
goto bail;
}
}
} else if (teth_ctx->link_protocol == TETH_LINK_PROTOCOL_ETHERNET) {
/* Add a header entry for USB */
res = add_eth_hdrs(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(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");
hdr_len = ETH_HLEN;
ipa_usb_hdr_len = ETH_HLEN;
}
res = configure_ipa_header_block_internal(hdr_len,
hdr_len,
ipa_usb_hdr_len,
hdr_len);
if (res) {
TETH_ERR("Configuration of header removal/insertion failed\n");
goto bail;
}
TETH_DBG_FUNC_EXIT();
bail:
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)
{
struct ipa_ioc_add_rt_rule *rt_rule;
struct ipa_ioc_get_hdr hdr_info;
int res;
int idx;
TETH_DBG_FUNC_ENTRY();
/* 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->routing_del[ip_address_family]->num_hdls++;
teth_ctx->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)
{
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);
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);
if (res) {
TETH_ERR("Failed adding IPv6 routing table\n");
goto bail;
}
TETH_DBG_FUNC_EXIT();
bail:
return res;
}
/**
* configure_ipa_routing_block() - Configure the IPA routing block
*
* 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(void)
{
int res;
char hdr_name_ipv4[IPA_RESOURCE_NAME_MAX];
char hdr_name_ipv6[IPA_RESOURCE_NAME_MAX];
TETH_DBG_FUNC_ENTRY();
hdr_name_ipv4[0] = '\0';
hdr_name_ipv6[0] = '\0';
/* Configure USB -> A2 routing table */
if (teth_ctx->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,
USB_TO_A2_RT_TBL_NAME_IPV4,
hdr_name_ipv6,
USB_TO_A2_RT_TBL_NAME_IPV6,
IPA_CLIENT_A2_TETHERED_CONS);
if (res) {
TETH_ERR("USB to A2 routing block configuration failed\n");
goto bail;
}
/* Configure A2 -> USB routing table */
if (teth_ctx->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->aggr_params.dl.aggr_prot ==
TETH_AGGR_PROTOCOL_MBIM) {
strlcpy(hdr_name_ipv4,
MBIM_HEADER_NAME,
IPA_RESOURCE_NAME_MAX);
strlcpy(hdr_name_ipv6,
MBIM_HEADER_NAME,
IPA_RESOURCE_NAME_MAX);
}
res = configure_routing(hdr_name_ipv4,
A2_TO_USB_RT_TBL_NAME_IPV4,
hdr_name_ipv6,
A2_TO_USB_RT_TBL_NAME_IPV6,
IPA_CLIENT_USB_CONS);
if (res) {
TETH_ERR("A2 to USB routing block configuration failed\n");
goto bail;
}
TETH_DBG_FUNC_EXIT();
bail:
return res;
}
static int configure_filtering_by_ip(char *rt_tbl_name,
enum ipa_client_type src,
enum ipa_ip_type ip_address_family)
{
struct ipa_ioc_add_flt_rule *flt_tbl;
struct ipa_ioc_get_rt_tbl rt_tbl_info;
int res;
int idx;
TETH_DBG_FUNC_ENTRY();
/* 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 */
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->filtering_del[ip_address_family]->num_hdls++;
teth_ctx->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;
}
static int configure_filtering(char *rt_tbl_name_ipv4,
char *rt_tbl_name_ipv6,
enum ipa_client_type src)
{
int res;
TETH_DBG_FUNC_ENTRY();
res = configure_filtering_by_ip(rt_tbl_name_ipv4, src, IPA_IP_v4);
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);
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
*
* 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(void)
{
int res;
TETH_DBG_FUNC_ENTRY();
/* Filter all traffic coming from USB to A2 */
res = configure_filtering(USB_TO_A2_RT_TBL_NAME_IPV4,
USB_TO_A2_RT_TBL_NAME_IPV6,
IPA_CLIENT_USB_PROD);
if (res) {
TETH_ERR("USB_PROD ep filtering configuration failed\n");
goto bail;
}
/* Filter all traffic coming from A2 to USB */
res = configure_filtering(A2_TO_USB_RT_TBL_NAME_IPV4,
A2_TO_USB_RT_TBL_NAME_IPV6,
IPA_CLIENT_A2_TETHERED_PROD);
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
*
* The parameters to this function are passed in the context variable ipa_ctx.
*/
static int teth_set_aggregation(void)
{
int res;
char aggr_prot_str[20];
TETH_DBG_FUNC_ENTRY();
if (!teth_ctx->aggr_params_known) {
TETH_ERR("Aggregation parameters unknown.\n");
return -EINVAL;
}
if ((teth_ctx->usb_ipa_pipe_hdl == 0) ||
(teth_ctx->ipa_usb_pipe_hdl == 0))
return 0;
/*
* Returning 0 in case pipe handles are 0 becuase aggregation
* params will be set later
*/
if (teth_ctx->aggr_params.ul.aggr_prot == TETH_AGGR_PROTOCOL_MBIM ||
teth_ctx->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->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->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->aggr_params.ul,
true,
teth_ctx->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->aggr_params.dl,
false,
teth_ctx->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;
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);
else
return res;
}
return 0;
}
/**
* 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;
TETH_DBG_FUNC_ENTRY();
TETH_DBG("Completing HW bridge in %s mode\n",
(teth_ctx->link_protocol == TETH_LINK_PROTOCOL_ETHERNET) ?
"ETHERNET" :
"IP");
res = teth_request_resource();
if (res) {
TETH_ERR("request_resource() failed.\n");
goto bail;
}
res = teth_set_aggregation();
if (res) {
TETH_ERR("Failed setting aggregation params\n");
goto bail;
}
res = configure_ipa_header_block();
if (res) {
TETH_ERR("Configuration of IPA header block Failed\n");
goto bail;
}
res = configure_ipa_routing_block();
if (res) {
TETH_ERR("Configuration of IPA routing block Failed\n");
goto bail;
}
res = configure_ipa_filtering_block();
if (res) {
TETH_ERR("Configuration of IPA filtering 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;
}
teth_ctx->is_hw_bridge_complete = true;
bail:
teth_ctx->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 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(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];
if ((teth_ctx->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->link_protocol == TETH_LINK_PROTOCOL_IP)) &&
(!teth_ctx->comp_hw_bridge_in_progress) &&
(teth_ctx->aggr_params_known)) {
INIT_WORK(&teth_ctx->comp_hw_bridge_work, complete_hw_bridge);
teth_ctx->comp_hw_bridge_in_progress = true;
queue_work(teth_ctx->teth_wq, &teth_ctx->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;
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(A2_MUX_TETHERED_0, 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:
res = ipa_tx_dp(IPA_CLIENT_USB_CONS, work_data->skb, NULL);
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)
{
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;
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;
switch (evt) {
case IPA_RECEIVE:
if (!teth_ctx->is_hw_bridge_complete)
check_to_complete_hw_bridge(
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);
} 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(A2_MUX_TETHERED_0, 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;
switch (event) {
case A2_MUX_RECEIVE:
if (!teth_ctx->is_hw_bridge_complete)
check_to_complete_hw_bridge(
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);
} 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(IPA_CLIENT_USB_CONS, skb, NULL);
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_tethered_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_tethered_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)
{
int res = 0;
struct ipa_rm_register_params a2_prod_reg_params;
TETH_DBG_FUNC_ENTRY();
if (usb_notify_cb_ptr == NULL) {
TETH_ERR("Bad parameter\n");
res = -EINVAL;
goto bail;
}
*usb_notify_cb_ptr = usb_notify_cb;
*private_data_ptr = NULL;
/* 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 bail;
}
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;
}
/* Register for A2_PROD resource notifications */
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 fail_add_dependency_4;
}
/* Return 0 as EINPROGRESS is a valid return value at this point */
res = 0;
goto bail;
fail_add_dependency_4:
ipa_rm_delete_dependency(IPA_RM_RESOURCE_A2_PROD,
IPA_RM_RESOURCE_USB_CONS);
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);
bail:
TETH_DBG_FUNC_EXIT();
return res;
}
EXPORT_SYMBOL(teth_bridge_init);
/**
* initialize_context() - Initialize the ipa_ctx struct
*/
static void initialize_context(void)
{
TETH_DBG_FUNC_ENTRY();
/* Initialize context variables */
teth_ctx->usb_ipa_pipe_hdl = 0;
teth_ctx->ipa_a2_pipe_hdl = 0;
teth_ctx->a2_ipa_pipe_hdl = 0;
teth_ctx->ipa_usb_pipe_hdl = 0;
teth_ctx->is_connected = false;
/* The default link protocol is Ethernet */
teth_ctx->link_protocol = TETH_LINK_PROTOCOL_ETHERNET;
memset(&teth_ctx->mac_addresses, 0, sizeof(teth_ctx->mac_addresses));
teth_ctx->is_hw_bridge_complete = false;
memset(&teth_ctx->aggr_params, 0, sizeof(teth_ctx->aggr_params));
teth_ctx->aggr_params_known = false;
teth_ctx->tethering_mode = 0;
INIT_COMPLETION(teth_ctx->is_bridge_prod_up);
INIT_COMPLETION(teth_ctx->is_bridge_prod_down);
teth_ctx->comp_hw_bridge_in_progress = false;
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));
memset(teth_ctx->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->routing_del[IPA_IP_v4]->ip = IPA_IP_v4;
memset(teth_ctx->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->routing_del[IPA_IP_v6]->ip = IPA_IP_v6;
memset(teth_ctx->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->filtering_del[IPA_IP_v4]->ip = IPA_IP_v4;
memset(teth_ctx->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->filtering_del[IPA_IP_v6]->ip = IPA_IP_v6;
TETH_DBG_FUNC_EXIT();
}
/**
* teth_bridge_disconnect() - Disconnect tethering bridge module
*/
int teth_bridge_disconnect(void)
{
int res;
struct ipa_rm_register_params a2_prod_reg_params;
TETH_DBG_FUNC_ENTRY();
if (!teth_ctx->is_connected) {
TETH_ERR(
"Trying to disconnect an already disconnected bridge\n");
goto bail;
}
/*
* 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");
/* 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(A2_MUX_TETHERED_0))
TETH_ERR("a2_mux_close_channel() failed\n");
/* Teardown the IPA HW bridge */
if (teth_ctx->is_hw_bridge_complete) {
/* 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->routing_del[IPA_IP_v4]))
TETH_ERR("ipa_del_rt_rule() failed\n");
if (ipa_del_rt_rule(teth_ctx->routing_del[IPA_IP_v6]))
TETH_ERR("ipa_del_rt_rule() failed\n");
/* Delete installed filtering rules */
if (ipa_del_flt_rule(teth_ctx->filtering_del[IPA_IP_v4]))
TETH_ERR("ipa_del_flt_rule() failed\n");
if (ipa_del_flt_rule(teth_ctx->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 headers\n");
}
initialize_context();
ipa_rm_inactivity_timer_release_resource(IPA_RM_RESOURCE_BRIDGE_PROD);
/* 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");
/* Deregister from A2_PROD notifications */
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");
teth_ctx->is_connected = false;
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;
struct ipa_ep_cfg ipa_ep_cfg;
TETH_DBG_FUNC_ENTRY();
if (teth_ctx->is_connected) {
TETH_ERR("Trying to connect an already connected bridge !\n");
return -EPERM;
}
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)
return -EINVAL;
teth_ctx->ipa_usb_pipe_hdl = connect_params->ipa_usb_pipe_hdl;
teth_ctx->usb_ipa_pipe_hdl = connect_params->usb_ipa_pipe_hdl;
teth_ctx->tethering_mode = connect_params->tethering_mode;
res = teth_request_resource();
if (res) {
TETH_ERR("request_resource() failed.\n");
goto bail;
}
res = a2_mux_open_channel(A2_MUX_TETHERED_0,
NULL,
a2_notify_cb);
if (res) {
TETH_ERR("a2_mux_open_channel() failed\n");
goto bail;
}
res = a2_mux_get_tethered_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_tethered_client_handles() failed, res = %d\n", res);
goto bail;
}
/* Reset the various endpoints configuration */
memset(&ipa_ep_cfg, 0, sizeof(ipa_ep_cfg));
ipa_cfg_ep(teth_ctx->ipa_usb_pipe_hdl, &ipa_ep_cfg);
ipa_cfg_ep(teth_ctx->usb_ipa_pipe_hdl, &ipa_ep_cfg);
ipa_cfg_ep(teth_ctx->ipa_a2_pipe_hdl, &ipa_ep_cfg);
ipa_cfg_ep(teth_ctx->a2_ipa_pipe_hdl, &ipa_ep_cfg);
teth_ctx->is_connected = true;
if (teth_ctx->tethering_mode == TETH_TETHERING_MODE_MBIM)
teth_ctx->link_protocol = TETH_LINK_PROTOCOL_IP;
if (teth_ctx->aggr_params_known) {
res = teth_set_aggregation();
if (res) {
TETH_ERR("Failed setting aggregation params\n");
goto bail;
}
}
/* In case of IP link protocol, complete HW bridge */
if ((teth_ctx->link_protocol == TETH_LINK_PROTOCOL_IP) &&
(!teth_ctx->comp_hw_bridge_in_progress) &&
(teth_ctx->aggr_params_known) &&
(!teth_ctx->is_hw_bridge_complete)) {
INIT_WORK(&teth_ctx->comp_hw_bridge_work, complete_hw_bridge);
teth_ctx->comp_hw_bridge_in_progress = true;
queue_work(teth_ctx->teth_wq, &teth_ctx->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)
*/
static void teth_set_bridge_mode(enum teth_link_protocol_type link_protocol)
{
teth_ctx->link_protocol = link_protocol;
teth_ctx->is_hw_bridge_complete = false;
memset(&teth_ctx->mac_addresses, 0, sizeof(teth_ctx->mac_addresses));
}
/**
* teth_bridge_set_aggr_params() - kernel API to set aggregation parameters
* @param aggr_params: aggregation parmeters for uplink and downlink
*
* Besides setting the aggregation parameters, the function enforces max tranfer
* 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_aggr_params(struct teth_aggr_params *aggr_params)
{
int res;
TETH_DBG_FUNC_ENTRY();
if (!aggr_params) {
TETH_ERR("Invalid parameter\n");
return -EINVAL;
}
/*
* 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 */
if (teth_ctx->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;
}
memcpy(&teth_ctx->aggr_params,
aggr_params,
sizeof(struct teth_aggr_params));
set_aggr_default_params(&teth_ctx->aggr_params.dl);
set_aggr_default_params(&teth_ctx->aggr_params.ul);
teth_ctx->aggr_params_known = true;
res = teth_set_aggregation();
if (res)
TETH_ERR("Failed setting aggregation params\n");
ipa_rm_inactivity_timer_release_resource(IPA_RM_RESOURCE_BRIDGE_PROD);
TETH_DBG_FUNC_EXIT();
return res;
}
EXPORT_SYMBOL(teth_bridge_set_aggr_params);
static long teth_bridge_ioctl(struct file *filp,
unsigned int cmd,
unsigned long arg)
{
int res = 0;
struct teth_aggr_params aggr_params;
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");
if (teth_ctx->link_protocol != arg)
teth_set_bridge_mode(arg);
break;
case TETH_BRIDGE_IOC_SET_AGGR_PARAMS:
TETH_DBG("TETH_BRIDGE_IOC_SET_AGGR_PARAMS ioctl called\n");
res = copy_from_user(&aggr_params,
(struct teth_aggr_params *)arg,
sizeof(struct teth_aggr_params));
if (res) {
TETH_ERR("Error, res = %d\n", res);
res = -EFAULT;
break;
}
res = teth_bridge_set_aggr_params(&aggr_params);
if (res)
break;
/* In case of IP link protocol, complete HW bridge */
if ((teth_ctx->link_protocol == TETH_LINK_PROTOCOL_IP) &&
(!teth_ctx->comp_hw_bridge_in_progress) &&
(!teth_ctx->is_hw_bridge_complete)) {
INIT_WORK(&teth_ctx->comp_hw_bridge_work,
complete_hw_bridge);
teth_ctx->comp_hw_bridge_in_progress = true;
queue_work(teth_ctx->teth_wq,
&teth_ctx->comp_hw_bridge_work);
}
break;
case TETH_BRIDGE_IOC_GET_AGGR_PARAMS:
TETH_DBG("TETH_BRIDGE_IOC_GET_AGGR_PARAMS ioctl called\n");
if (copy_to_user((u8 *)arg, (u8 *)&teth_ctx->aggr_params,
sizeof(struct teth_aggr_params))) {
res = -EFAULT;
break;
}
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->usb_ipa_pipe_hdl;
*consumer_handle = teth_ctx->ipa_usb_pipe_hdl;
}
#ifdef CONFIG_DEBUG_FS
static struct dentry *dent;
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_link_protocol(struct file *file,
char __user *ubuf,
size_t count,
loff_t *ppos)
{
int nbytes;
nbytes = scnprintf(dbg_buff, TETH_MAX_MSG_LEN, "Link protocol = %s\n",
(teth_ctx->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(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];
aggr_prot_to_str(teth_ctx->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->aggr_params.ul.max_transfer_size_byte);
nbytes += scnprintf(&dbg_buff[nbytes], TETH_MAX_MSG_LEN - nbytes,
" Max datagrams: %d\n",
teth_ctx->aggr_params.ul.max_datagrams);
aggr_prot_to_str(teth_ctx->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->aggr_params.dl.max_transfer_size_byte);
nbytes += scnprintf(&dbg_buff[nbytes], TETH_MAX_MSG_LEN - nbytes,
" Max datagrams: %d\n",
teth_ctx->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;
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->aggr_params.dl);
set_aggr_default_params(&teth_ctx->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->aggr_params.dl.aggr_prot = aggr_prot;
teth_ctx->aggr_params.ul.aggr_prot = aggr_prot;
teth_ctx->aggr_params_known = true;
res = teth_set_aggregation();
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;
if (teth_ctx->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_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_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,
};
/**
* teth_bridge_driver_init() - Initialize tethering bridge driver
*
*/
int teth_bridge_driver_init(void)
{
int res;
struct ipa_rm_create_params bridge_prod_params;
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;
}
res = set_aggr_capabilities();
if (res) {
TETH_ERR("kzalloc err.\n");
goto fail_alloc_aggr_caps;
}
res = -ENOMEM;
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.\n");
goto fail_alloc_hdr_del;
}
teth_ctx->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->routing_del[IPA_IP_v4]) {
TETH_ERR("kzalloc err.\n");
goto fail_alloc_routing_del_ipv4;
}
teth_ctx->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->routing_del[IPA_IP_v6]) {
TETH_ERR("kzalloc err.\n");
goto fail_alloc_routing_del_ipv6;
}
teth_ctx->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->filtering_del[IPA_IP_v4]) {
TETH_ERR("kzalloc err.\n");
goto fail_alloc_filtering_del_ipv4;
}
teth_ctx->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->filtering_del[IPA_IP_v6]) {
TETH_ERR("kzalloc err.\n");
goto fail_alloc_filtering_del_ipv6;
}
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;
}
initialize_context();
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->filtering_del[IPA_IP_v6]);
fail_alloc_filtering_del_ipv6:
kfree(teth_ctx->filtering_del[IPA_IP_v4]);
fail_alloc_filtering_del_ipv4:
kfree(teth_ctx->routing_del[IPA_IP_v6]);
fail_alloc_routing_del_ipv6:
kfree(teth_ctx->routing_del[IPA_IP_v4]);
fail_alloc_routing_del_ipv4:
kfree(teth_ctx->hdr_del);
fail_alloc_hdr_del:
kfree(teth_ctx->aggr_caps);
fail_alloc_aggr_caps:
kfree(teth_ctx);
teth_ctx = NULL;
return res;
}
EXPORT_SYMBOL(teth_bridge_driver_init);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("Tethering bridge driver");