| /* Copyright (c) 2013-2017, 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/debugfs.h> |
| #include <linux/errno.h> |
| #include <linux/etherdevice.h> |
| #include <linux/fs.h> |
| #include <linux/module.h> |
| #include <linux/netdevice.h> |
| #include <linux/skbuff.h> |
| #include <linux/sched.h> |
| #include <linux/atomic.h> |
| #include <linux/ecm_ipa.h> |
| #include "../ipa_common_i.h" |
| #include "../ipa_v3/ipa_pm.h" |
| |
| #define DRIVER_NAME "ecm_ipa" |
| #define ECM_IPA_IPV4_HDR_NAME "ecm_eth_ipv4" |
| #define ECM_IPA_IPV6_HDR_NAME "ecm_eth_ipv6" |
| #define INACTIVITY_MSEC_DELAY 100 |
| #define DEFAULT_OUTSTANDING_HIGH 64 |
| #define DEFAULT_OUTSTANDING_LOW 32 |
| #define DEBUGFS_TEMP_BUF_SIZE 4 |
| #define TX_TIMEOUT (5 * HZ) |
| |
| #define ECM_IPA_DEBUG(fmt, args...) \ |
| pr_debug("ctx:%s: "\ |
| fmt, current->comm, ## args) |
| |
| #define ECM_IPA_INFO(fmt, args...) \ |
| pr_err(DRIVER_NAME "@%s@%d@ctx:%s: "\ |
| fmt, __func__, __LINE__, current->comm, ## args) |
| |
| #define ECM_IPA_ERROR(fmt, args...) \ |
| pr_err(DRIVER_NAME "@%s@%d@ctx:%s: "\ |
| fmt, __func__, __LINE__, current->comm, ## args) |
| |
| #define NULL_CHECK(ptr) \ |
| do { \ |
| if (!(ptr)) { \ |
| ECM_IPA_ERROR("null pointer #ptr\n"); \ |
| ret = -EINVAL; \ |
| } \ |
| } \ |
| while (0) |
| |
| #define ECM_IPA_LOG_ENTRY() ECM_IPA_DEBUG("begin\n") |
| #define ECM_IPA_LOG_EXIT() ECM_IPA_DEBUG("end\n") |
| |
| /** |
| * enum ecm_ipa_state - specify the current driver internal state |
| * which is guarded by a state machine. |
| * |
| * The driver internal state changes due to its external API usage. |
| * The driver saves its internal state to guard from caller illegal |
| * call sequence. |
| * states: |
| * UNLOADED is the first state which is the default one and is also the state |
| * after the driver gets unloaded(cleanup). |
| * INITIALIZED is the driver state once it finished registering |
| * the network device and all internal data struct were initialized |
| * CONNECTED is the driver state once the USB pipes were connected to IPA |
| * UP is the driver state after the interface mode was set to UP but the |
| * pipes are not connected yet - this state is meta-stable state. |
| * CONNECTED_AND_UP is the driver state when the pipe were connected and |
| * the interface got UP request from the network stack. this is the driver |
| * idle operation state which allows it to transmit/receive data. |
| * INVALID is a state which is not allowed. |
| */ |
| enum ecm_ipa_state { |
| ECM_IPA_UNLOADED = 0, |
| ECM_IPA_INITIALIZED, |
| ECM_IPA_CONNECTED, |
| ECM_IPA_UP, |
| ECM_IPA_CONNECTED_AND_UP, |
| ECM_IPA_INVALID, |
| }; |
| |
| /** |
| * enum ecm_ipa_operation - enumerations used to descibe the API operation |
| * |
| * Those enums are used as input for the driver state machine. |
| */ |
| enum ecm_ipa_operation { |
| ECM_IPA_INITIALIZE, |
| ECM_IPA_CONNECT, |
| ECM_IPA_OPEN, |
| ECM_IPA_STOP, |
| ECM_IPA_DISCONNECT, |
| ECM_IPA_CLEANUP, |
| }; |
| |
| #define ECM_IPA_STATE_DEBUG(ecm_ipa_ctx) \ |
| ECM_IPA_DEBUG("Driver state - %s\n",\ |
| ecm_ipa_state_string((ecm_ipa_ctx)->state)) |
| |
| /** |
| * struct ecm_ipa_dev - main driver context parameters |
| * @net: network interface struct implemented by this driver |
| * @directory: debugfs directory for various debuging switches |
| * @eth_ipv4_hdr_hdl: saved handle for ipv4 header-insertion table |
| * @eth_ipv6_hdr_hdl: saved handle for ipv6 header-insertion table |
| * @usb_to_ipa_hdl: save handle for IPA pipe operations |
| * @ipa_to_usb_hdl: save handle for IPA pipe operations |
| * @outstanding_pkts: number of packets sent to IPA without TX complete ACKed |
| * @outstanding_high: number of outstanding packets allowed |
| * @outstanding_low: number of outstanding packets which shall cause |
| * to netdev queue start (after stopped due to outstanding_high reached) |
| * @state: current state of ecm_ipa driver |
| * @device_ready_notify: callback supplied by USB core driver |
| * This callback shall be called by the Netdev once the Netdev internal |
| * state is changed to RNDIS_IPA_CONNECTED_AND_UP |
| * @ipa_to_usb_client: consumer client |
| * @usb_to_ipa_client: producer client |
| * @ipa_rm_resource_name_prod: IPA resource manager producer resource |
| * @ipa_rm_resource_name_cons: IPA resource manager consumer resource |
| * @pm_hdl: handle for IPA PM |
| */ |
| struct ecm_ipa_dev { |
| struct net_device *net; |
| struct dentry *directory; |
| u32 eth_ipv4_hdr_hdl; |
| u32 eth_ipv6_hdr_hdl; |
| u32 usb_to_ipa_hdl; |
| u32 ipa_to_usb_hdl; |
| atomic_t outstanding_pkts; |
| u8 outstanding_high; |
| u8 outstanding_low; |
| enum ecm_ipa_state state; |
| void (*device_ready_notify)(void); |
| enum ipa_client_type ipa_to_usb_client; |
| enum ipa_client_type usb_to_ipa_client; |
| enum ipa_rm_resource_name ipa_rm_resource_name_prod; |
| enum ipa_rm_resource_name ipa_rm_resource_name_cons; |
| u32 pm_hdl; |
| }; |
| |
| static int ecm_ipa_open(struct net_device *net); |
| static void ecm_ipa_packet_receive_notify |
| (void *priv, enum ipa_dp_evt_type evt, unsigned long data); |
| static void ecm_ipa_tx_complete_notify |
| (void *priv, enum ipa_dp_evt_type evt, unsigned long data); |
| static void ecm_ipa_tx_timeout(struct net_device *net); |
| static int ecm_ipa_stop(struct net_device *net); |
| static void ecm_ipa_enable_data_path(struct ecm_ipa_dev *ecm_ipa_ctx); |
| static int ecm_ipa_rules_cfg |
| (struct ecm_ipa_dev *ecm_ipa_ctx, const void *dst_mac, |
| const void *src_mac); |
| static void ecm_ipa_rules_destroy(struct ecm_ipa_dev *ecm_ipa_ctx); |
| static int ecm_ipa_register_properties(struct ecm_ipa_dev *ecm_ipa_ctx); |
| static void ecm_ipa_deregister_properties(void); |
| static void ecm_ipa_rm_notify |
| (void *user_data, enum ipa_rm_event event, unsigned long data); |
| static struct net_device_stats *ecm_ipa_get_stats(struct net_device *net); |
| static int ecm_ipa_create_rm_resource(struct ecm_ipa_dev *ecm_ipa_ctx); |
| static void ecm_ipa_destroy_rm_resource(struct ecm_ipa_dev *ecm_ipa_ctx); |
| static int ecm_ipa_register_pm_client(struct ecm_ipa_dev *ecm_ipa_ctx); |
| static void ecm_ipa_deregister_pm_client(struct ecm_ipa_dev *ecm_ipa_ctx); |
| static int resource_request(struct ecm_ipa_dev *ecm_ipa_ctx); |
| static void resource_release(struct ecm_ipa_dev *ecm_ipa_ctx); |
| static netdev_tx_t ecm_ipa_start_xmit |
| (struct sk_buff *skb, struct net_device *net); |
| static int ecm_ipa_debugfs_atomic_open(struct inode *inode, struct file *file); |
| static ssize_t ecm_ipa_debugfs_atomic_read |
| (struct file *file, char __user *ubuf, size_t count, loff_t *ppos); |
| static void ecm_ipa_debugfs_init(struct ecm_ipa_dev *ecm_ipa_ctx); |
| static void ecm_ipa_debugfs_destroy(struct ecm_ipa_dev *ecm_ipa_ctx); |
| static int ecm_ipa_ep_registers_cfg(u32 usb_to_ipa_hdl, u32 ipa_to_usb_hdl); |
| static int ecm_ipa_set_device_ethernet_addr |
| (u8 *dev_ethaddr, u8 device_ethaddr[]); |
| static enum ecm_ipa_state ecm_ipa_next_state |
| (enum ecm_ipa_state current_state, enum ecm_ipa_operation operation); |
| static const char *ecm_ipa_state_string(enum ecm_ipa_state state); |
| static int ecm_ipa_init_module(void); |
| static void ecm_ipa_cleanup_module(void); |
| |
| static const struct net_device_ops ecm_ipa_netdev_ops = { |
| .ndo_open = ecm_ipa_open, |
| .ndo_stop = ecm_ipa_stop, |
| .ndo_start_xmit = ecm_ipa_start_xmit, |
| .ndo_set_mac_address = eth_mac_addr, |
| .ndo_tx_timeout = ecm_ipa_tx_timeout, |
| .ndo_get_stats = ecm_ipa_get_stats, |
| }; |
| |
| const struct file_operations ecm_ipa_debugfs_atomic_ops = { |
| .open = ecm_ipa_debugfs_atomic_open, |
| .read = ecm_ipa_debugfs_atomic_read, |
| }; |
| |
| static void ecm_ipa_msg_free_cb(void *buff, u32 len, u32 type) |
| { |
| kfree(buff); |
| } |
| |
| /** |
| * ecm_ipa_init() - create network device and initializes internal |
| * data structures |
| * @params: in/out parameters required for ecm_ipa initialization |
| * |
| * Shall be called prior to pipe connection. |
| * The out parameters (the callbacks) shall be supplied to ipa_connect. |
| * Detailed description: |
| * - allocate the network device |
| * - set default values for driver internals |
| * - create debugfs folder and files |
| * - create IPA resource manager client |
| * - add header insertion rules for IPA driver (based on host/device |
| * Ethernet addresses given in input params) |
| * - register tx/rx properties to IPA driver (will be later used |
| * by IPA configuration manager to configure reset of the IPA rules) |
| * - set the carrier state to "off" (until ecm_ipa_connect is called) |
| * - register the network device |
| * - set the out parameters |
| * |
| * Returns negative errno, or zero on success |
| */ |
| int ecm_ipa_init(struct ecm_ipa_params *params) |
| { |
| int result = 0; |
| struct net_device *net; |
| struct ecm_ipa_dev *ecm_ipa_ctx; |
| int ret; |
| |
| ECM_IPA_LOG_ENTRY(); |
| |
| ECM_IPA_DEBUG("%s initializing\n", DRIVER_NAME); |
| ret = 0; |
| NULL_CHECK(params); |
| if (ret) |
| return ret; |
| |
| ECM_IPA_DEBUG |
| ("host_ethaddr=%pM, device_ethaddr=%pM\n", |
| params->host_ethaddr, |
| params->device_ethaddr); |
| |
| net = alloc_etherdev(sizeof(struct ecm_ipa_dev)); |
| if (!net) { |
| result = -ENOMEM; |
| ECM_IPA_ERROR("fail to allocate etherdev\n"); |
| goto fail_alloc_etherdev; |
| } |
| ECM_IPA_DEBUG("network device was successfully allocated\n"); |
| |
| ecm_ipa_ctx = netdev_priv(net); |
| if (!ecm_ipa_ctx) { |
| ECM_IPA_ERROR("fail to extract netdev priv\n"); |
| result = -ENOMEM; |
| goto fail_netdev_priv; |
| } |
| memset(ecm_ipa_ctx, 0, sizeof(*ecm_ipa_ctx)); |
| ECM_IPA_DEBUG("ecm_ipa_ctx (private) = %p\n", ecm_ipa_ctx); |
| |
| ecm_ipa_ctx->net = net; |
| ecm_ipa_ctx->outstanding_high = DEFAULT_OUTSTANDING_HIGH; |
| ecm_ipa_ctx->outstanding_low = DEFAULT_OUTSTANDING_LOW; |
| atomic_set(&ecm_ipa_ctx->outstanding_pkts, 0); |
| snprintf(net->name, sizeof(net->name), "%s%%d", "ecm"); |
| net->netdev_ops = &ecm_ipa_netdev_ops; |
| net->watchdog_timeo = TX_TIMEOUT; |
| ECM_IPA_DEBUG("internal data structures were initialized\n"); |
| |
| if (!params->device_ready_notify) |
| ECM_IPA_DEBUG("device_ready_notify() was not supplied"); |
| ecm_ipa_ctx->device_ready_notify = params->device_ready_notify; |
| |
| ecm_ipa_debugfs_init(ecm_ipa_ctx); |
| |
| result = ecm_ipa_set_device_ethernet_addr |
| (net->dev_addr, params->device_ethaddr); |
| if (result) { |
| ECM_IPA_ERROR("set device MAC failed\n"); |
| goto fail_set_device_ethernet; |
| } |
| ECM_IPA_DEBUG("Device Ethernet address set %pM\n", net->dev_addr); |
| |
| result = ecm_ipa_rules_cfg |
| (ecm_ipa_ctx, params->host_ethaddr, params->device_ethaddr); |
| if (result) { |
| ECM_IPA_ERROR("fail on ipa rules set\n"); |
| goto fail_rules_cfg; |
| } |
| ECM_IPA_DEBUG("Ethernet header insertion set\n"); |
| |
| netif_carrier_off(net); |
| ECM_IPA_DEBUG("netif_carrier_off() was called\n"); |
| |
| netif_stop_queue(ecm_ipa_ctx->net); |
| ECM_IPA_DEBUG("netif_stop_queue() was called"); |
| |
| result = register_netdev(net); |
| if (result) { |
| ECM_IPA_ERROR("register_netdev failed: %d\n", result); |
| goto fail_register_netdev; |
| } |
| ECM_IPA_DEBUG("register_netdev succeeded\n"); |
| |
| params->ecm_ipa_rx_dp_notify = ecm_ipa_packet_receive_notify; |
| params->ecm_ipa_tx_dp_notify = ecm_ipa_tx_complete_notify; |
| params->private = (void *)ecm_ipa_ctx; |
| params->skip_ep_cfg = false; |
| ecm_ipa_ctx->state = ECM_IPA_INITIALIZED; |
| ECM_IPA_STATE_DEBUG(ecm_ipa_ctx); |
| |
| ECM_IPA_INFO("ECM_IPA was initialized successfully\n"); |
| |
| ECM_IPA_LOG_EXIT(); |
| |
| return 0; |
| |
| fail_register_netdev: |
| ecm_ipa_rules_destroy(ecm_ipa_ctx); |
| fail_set_device_ethernet: |
| fail_rules_cfg: |
| ecm_ipa_debugfs_destroy(ecm_ipa_ctx); |
| fail_netdev_priv: |
| free_netdev(net); |
| fail_alloc_etherdev: |
| return result; |
| } |
| EXPORT_SYMBOL(ecm_ipa_init); |
| |
| /** |
| * ecm_ipa_connect() - notify ecm_ipa for IPA<->USB pipes connection |
| * @usb_to_ipa_hdl: handle of IPA driver client for USB->IPA |
| * @ipa_to_usb_hdl: handle of IPA driver client for IPA->USB |
| * @priv: same value that was set by ecm_ipa_init(), this |
| * parameter holds the network device pointer. |
| * |
| * Once USB driver finishes the pipe connection between IPA core |
| * and USB core this method shall be called in order to |
| * allow ecm_ipa complete the data path configurations. |
| * Caller should make sure that it is calling this function |
| * from a context that allows it to handle device_ready_notify(). |
| * Detailed description: |
| * - configure the IPA end-points register |
| * - notify the Linux kernel for "carrier_on" |
| * After this function is done the driver state changes to "Connected". |
| * This API is expected to be called after ecm_ipa_init() or |
| * after a call to ecm_ipa_disconnect. |
| */ |
| int ecm_ipa_connect(u32 usb_to_ipa_hdl, u32 ipa_to_usb_hdl, void *priv) |
| { |
| struct ecm_ipa_dev *ecm_ipa_ctx = priv; |
| int next_state; |
| struct ipa_ecm_msg *ecm_msg; |
| struct ipa_msg_meta msg_meta; |
| int retval; |
| int ret; |
| |
| ECM_IPA_LOG_ENTRY(); |
| ret = 0; |
| NULL_CHECK(priv); |
| if (ret) |
| return ret; |
| ECM_IPA_DEBUG("usb_to_ipa_hdl = %d, ipa_to_usb_hdl = %d, priv=0x%p\n", |
| usb_to_ipa_hdl, ipa_to_usb_hdl, priv); |
| |
| next_state = ecm_ipa_next_state(ecm_ipa_ctx->state, ECM_IPA_CONNECT); |
| if (next_state == ECM_IPA_INVALID) { |
| ECM_IPA_ERROR("can't call connect before calling initialize\n"); |
| return -EPERM; |
| } |
| ecm_ipa_ctx->state = next_state; |
| ECM_IPA_STATE_DEBUG(ecm_ipa_ctx); |
| |
| if (!ipa_is_client_handle_valid(usb_to_ipa_hdl)) { |
| ECM_IPA_ERROR |
| ("usb_to_ipa_hdl(%d) is not a valid ipa handle\n", |
| usb_to_ipa_hdl); |
| return -EINVAL; |
| } |
| if (!ipa_is_client_handle_valid(ipa_to_usb_hdl)) { |
| ECM_IPA_ERROR |
| ("ipa_to_usb_hdl(%d) is not a valid ipa handle\n", |
| ipa_to_usb_hdl); |
| return -EINVAL; |
| } |
| |
| ecm_ipa_ctx->ipa_to_usb_hdl = ipa_to_usb_hdl; |
| ecm_ipa_ctx->usb_to_ipa_hdl = usb_to_ipa_hdl; |
| |
| ecm_ipa_ctx->ipa_to_usb_client = ipa_get_client_mapping(ipa_to_usb_hdl); |
| if (ecm_ipa_ctx->ipa_to_usb_client < 0) { |
| ECM_IPA_ERROR( |
| "Error getting IPA->USB client from handle %d\n", |
| ecm_ipa_ctx->ipa_to_usb_client); |
| return -EINVAL; |
| } |
| ECM_IPA_DEBUG("ipa_to_usb_client = %d\n", |
| ecm_ipa_ctx->ipa_to_usb_client); |
| |
| ecm_ipa_ctx->usb_to_ipa_client = ipa_get_client_mapping(usb_to_ipa_hdl); |
| if (ecm_ipa_ctx->usb_to_ipa_client < 0) { |
| ECM_IPA_ERROR( |
| "Error getting USB->IPA client from handle %d\n", |
| ecm_ipa_ctx->usb_to_ipa_client); |
| return -EINVAL; |
| } |
| ECM_IPA_DEBUG("usb_to_ipa_client = %d\n", |
| ecm_ipa_ctx->usb_to_ipa_client); |
| |
| if (ipa_pm_is_used()) { |
| retval = ecm_ipa_register_pm_client(ecm_ipa_ctx); |
| } else { |
| ecm_ipa_ctx->ipa_rm_resource_name_cons = |
| ipa_get_rm_resource_from_ep(ipa_to_usb_hdl); |
| if (ecm_ipa_ctx->ipa_rm_resource_name_cons < 0) { |
| ECM_IPA_ERROR( |
| "Error getting CONS RM resource from handle %d\n", |
| ecm_ipa_ctx->ipa_rm_resource_name_cons); |
| return -EINVAL; |
| } |
| ECM_IPA_DEBUG("ipa_rm_resource_name_cons = %d\n", |
| ecm_ipa_ctx->ipa_rm_resource_name_cons); |
| |
| ecm_ipa_ctx->ipa_rm_resource_name_prod = |
| ipa_get_rm_resource_from_ep(usb_to_ipa_hdl); |
| if (ecm_ipa_ctx->ipa_rm_resource_name_prod < 0) { |
| ECM_IPA_ERROR( |
| "Error getting PROD RM resource from handle %d\n", |
| ecm_ipa_ctx->ipa_rm_resource_name_prod); |
| return -EINVAL; |
| } |
| ECM_IPA_DEBUG("ipa_rm_resource_name_prod = %d\n", |
| ecm_ipa_ctx->ipa_rm_resource_name_prod); |
| |
| retval = ecm_ipa_create_rm_resource(ecm_ipa_ctx); |
| } |
| |
| if (retval) { |
| ECM_IPA_ERROR("fail on RM create\n"); |
| goto fail_create_rm; |
| } |
| ECM_IPA_DEBUG("RM resource was created\n"); |
| |
| retval = ecm_ipa_register_properties(ecm_ipa_ctx); |
| if (retval) { |
| ECM_IPA_ERROR("fail on properties set\n"); |
| goto fail_create_rm; |
| } |
| ECM_IPA_DEBUG("ecm_ipa 2 Tx and 2 Rx properties were registered\n"); |
| |
| retval = ecm_ipa_ep_registers_cfg(usb_to_ipa_hdl, ipa_to_usb_hdl); |
| if (retval) { |
| ECM_IPA_ERROR("fail on ep cfg\n"); |
| goto fail; |
| } |
| ECM_IPA_DEBUG("end-point configured\n"); |
| |
| netif_carrier_on(ecm_ipa_ctx->net); |
| |
| ecm_msg = kzalloc(sizeof(*ecm_msg), GFP_KERNEL); |
| if (!ecm_msg) { |
| ECM_IPA_ERROR("can't alloc msg mem\n"); |
| retval = -ENOMEM; |
| goto fail; |
| } |
| |
| memset(&msg_meta, 0, sizeof(struct ipa_msg_meta)); |
| msg_meta.msg_type = ECM_CONNECT; |
| msg_meta.msg_len = sizeof(struct ipa_ecm_msg); |
| strlcpy(ecm_msg->name, ecm_ipa_ctx->net->name, |
| IPA_RESOURCE_NAME_MAX); |
| ecm_msg->ifindex = ecm_ipa_ctx->net->ifindex; |
| |
| retval = ipa_send_msg(&msg_meta, ecm_msg, ecm_ipa_msg_free_cb); |
| if (retval) { |
| ECM_IPA_ERROR("fail to send ECM_CONNECT message\n"); |
| kfree(ecm_msg); |
| goto fail; |
| } |
| |
| if (!netif_carrier_ok(ecm_ipa_ctx->net)) { |
| ECM_IPA_ERROR("netif_carrier_ok error\n"); |
| retval = -EBUSY; |
| goto fail; |
| } |
| ECM_IPA_DEBUG("carrier_on notified\n"); |
| |
| if (ecm_ipa_ctx->state == ECM_IPA_CONNECTED_AND_UP) |
| ecm_ipa_enable_data_path(ecm_ipa_ctx); |
| else |
| ECM_IPA_DEBUG("data path was not enabled yet\n"); |
| |
| ECM_IPA_INFO("ECM_IPA was connected successfully\n"); |
| |
| ECM_IPA_LOG_EXIT(); |
| |
| return 0; |
| |
| fail: |
| ecm_ipa_deregister_properties(); |
| fail_create_rm: |
| if (ipa_pm_is_used()) |
| ecm_ipa_deregister_pm_client(ecm_ipa_ctx); |
| else |
| ecm_ipa_destroy_rm_resource(ecm_ipa_ctx); |
| return retval; |
| } |
| EXPORT_SYMBOL(ecm_ipa_connect); |
| |
| /** |
| * ecm_ipa_open() - notify Linux network stack to start sending packets |
| * @net: the network interface supplied by the network stack |
| * |
| * Linux uses this API to notify the driver that the network interface |
| * transitions to the up state. |
| * The driver will instruct the Linux network stack to start |
| * delivering data packets. |
| */ |
| static int ecm_ipa_open(struct net_device *net) |
| { |
| struct ecm_ipa_dev *ecm_ipa_ctx; |
| int next_state; |
| |
| ECM_IPA_LOG_ENTRY(); |
| |
| ecm_ipa_ctx = netdev_priv(net); |
| |
| next_state = ecm_ipa_next_state(ecm_ipa_ctx->state, ECM_IPA_OPEN); |
| if (next_state == ECM_IPA_INVALID) { |
| ECM_IPA_ERROR("can't bring driver up before initialize\n"); |
| return -EPERM; |
| } |
| ecm_ipa_ctx->state = next_state; |
| ECM_IPA_STATE_DEBUG(ecm_ipa_ctx); |
| |
| if (ecm_ipa_ctx->state == ECM_IPA_CONNECTED_AND_UP) |
| ecm_ipa_enable_data_path(ecm_ipa_ctx); |
| else |
| ECM_IPA_DEBUG("data path was not enabled yet\n"); |
| |
| ECM_IPA_LOG_EXIT(); |
| |
| return 0; |
| } |
| |
| /** |
| * ecm_ipa_start_xmit() - send data from APPs to USB core via IPA core |
| * @skb: packet received from Linux network stack |
| * @net: the network device being used to send this packet |
| * |
| * Several conditions needed in order to send the packet to IPA: |
| * - Transmit queue for the network driver is currently |
| * in "send" state |
| * - The driver internal state is in "UP" state. |
| * - Filter Tx switch is turned off |
| * - The IPA resource manager state for the driver producer client |
| * is "Granted" which implies that all the resources in the dependency |
| * graph are valid for data flow. |
| * - outstanding high boundary did not reach. |
| * |
| * In case all of the above conditions are met, the network driver will |
| * send the packet by using the IPA API for Tx. |
| * In case the outstanding packet high boundary is reached, the driver will |
| * stop the send queue until enough packet were proceeded by the IPA core. |
| */ |
| static netdev_tx_t ecm_ipa_start_xmit |
| (struct sk_buff *skb, struct net_device *net) |
| { |
| int ret; |
| netdev_tx_t status = NETDEV_TX_BUSY; |
| struct ecm_ipa_dev *ecm_ipa_ctx = netdev_priv(net); |
| |
| netif_trans_update(net); |
| |
| ECM_IPA_DEBUG |
| ("Tx, len=%d, skb->protocol=%d, outstanding=%d\n", |
| skb->len, skb->protocol, |
| atomic_read(&ecm_ipa_ctx->outstanding_pkts)); |
| |
| if (unlikely(netif_queue_stopped(net))) { |
| ECM_IPA_ERROR("interface queue is stopped\n"); |
| goto out; |
| } |
| |
| if (unlikely(ecm_ipa_ctx->state != ECM_IPA_CONNECTED_AND_UP)) { |
| ECM_IPA_ERROR("Missing pipe connected and/or iface up\n"); |
| return NETDEV_TX_BUSY; |
| } |
| |
| ret = resource_request(ecm_ipa_ctx); |
| if (ret) { |
| ECM_IPA_DEBUG("Waiting to resource\n"); |
| netif_stop_queue(net); |
| goto resource_busy; |
| } |
| |
| if (atomic_read(&ecm_ipa_ctx->outstanding_pkts) >= |
| ecm_ipa_ctx->outstanding_high) { |
| ECM_IPA_DEBUG |
| ("outstanding high (%d)- stopping\n", |
| ecm_ipa_ctx->outstanding_high); |
| netif_stop_queue(net); |
| status = NETDEV_TX_BUSY; |
| goto out; |
| } |
| |
| ret = ipa_tx_dp(ecm_ipa_ctx->ipa_to_usb_client, skb, NULL); |
| if (ret) { |
| ECM_IPA_ERROR("ipa transmit failed (%d)\n", ret); |
| goto fail_tx_packet; |
| } |
| |
| atomic_inc(&ecm_ipa_ctx->outstanding_pkts); |
| |
| status = NETDEV_TX_OK; |
| goto out; |
| |
| fail_tx_packet: |
| out: |
| resource_release(ecm_ipa_ctx); |
| resource_busy: |
| return status; |
| } |
| |
| /** |
| * ecm_ipa_packet_receive_notify() - Rx notify |
| * |
| * @priv: ecm driver context |
| * @evt: event type |
| * @data: data provided with event |
| * |
| * IPA will pass a packet to the Linux network stack with skb->data pointing |
| * to Ethernet packet frame. |
| */ |
| static void ecm_ipa_packet_receive_notify |
| (void *priv, enum ipa_dp_evt_type evt, unsigned long data) |
| { |
| struct sk_buff *skb = (struct sk_buff *)data; |
| struct ecm_ipa_dev *ecm_ipa_ctx = priv; |
| int result; |
| unsigned int packet_len; |
| |
| if (!skb) { |
| ECM_IPA_ERROR("Bad SKB received from IPA driver\n"); |
| return; |
| } |
| |
| packet_len = skb->len; |
| ECM_IPA_DEBUG("packet RX, len=%d\n", skb->len); |
| |
| if (unlikely(ecm_ipa_ctx->state != ECM_IPA_CONNECTED_AND_UP)) { |
| ECM_IPA_DEBUG("Missing pipe connected and/or iface up\n"); |
| return; |
| } |
| |
| if (evt != IPA_RECEIVE) { |
| ECM_IPA_ERROR("A none IPA_RECEIVE event in ecm_ipa_receive\n"); |
| return; |
| } |
| |
| skb->dev = ecm_ipa_ctx->net; |
| skb->protocol = eth_type_trans(skb, ecm_ipa_ctx->net); |
| |
| result = netif_rx(skb); |
| if (result) |
| ECM_IPA_ERROR("fail on netif_rx\n"); |
| ecm_ipa_ctx->net->stats.rx_packets++; |
| ecm_ipa_ctx->net->stats.rx_bytes += packet_len; |
| } |
| |
| /** ecm_ipa_stop() - called when network device transitions to the down |
| * state. |
| * @net: the network device being stopped. |
| * |
| * This API is used by Linux network stack to notify the network driver that |
| * its state was changed to "down" |
| * The driver will stop the "send" queue and change its internal |
| * state to "Connected". |
| */ |
| static int ecm_ipa_stop(struct net_device *net) |
| { |
| struct ecm_ipa_dev *ecm_ipa_ctx = netdev_priv(net); |
| int next_state; |
| |
| ECM_IPA_LOG_ENTRY(); |
| |
| next_state = ecm_ipa_next_state(ecm_ipa_ctx->state, ECM_IPA_STOP); |
| if (next_state == ECM_IPA_INVALID) { |
| ECM_IPA_ERROR("can't do network interface down without up\n"); |
| return -EPERM; |
| } |
| ecm_ipa_ctx->state = next_state; |
| ECM_IPA_STATE_DEBUG(ecm_ipa_ctx); |
| |
| netif_stop_queue(net); |
| ECM_IPA_DEBUG("network device stopped\n"); |
| |
| ECM_IPA_LOG_EXIT(); |
| return 0; |
| } |
| |
| /** ecm_ipa_disconnect() - called when the USB cable is unplugged. |
| * @priv: same value that was set by ecm_ipa_init(), this |
| * parameter holds the network device pointer. |
| * |
| * Once the USB cable is unplugged the USB driver will notify the network |
| * interface driver. |
| * The internal driver state will returned to its initialized state and |
| * Linux network stack will be informed for carrier off and the send queue |
| * will be stopped. |
| */ |
| int ecm_ipa_disconnect(void *priv) |
| { |
| struct ecm_ipa_dev *ecm_ipa_ctx = priv; |
| int next_state; |
| struct ipa_ecm_msg *ecm_msg; |
| struct ipa_msg_meta msg_meta; |
| int retval; |
| int outstanding_dropped_pkts; |
| int ret; |
| |
| ECM_IPA_LOG_ENTRY(); |
| ret = 0; |
| NULL_CHECK(ecm_ipa_ctx); |
| if (ret) |
| return ret; |
| ECM_IPA_DEBUG("priv=0x%p\n", priv); |
| |
| next_state = ecm_ipa_next_state(ecm_ipa_ctx->state, ECM_IPA_DISCONNECT); |
| if (next_state == ECM_IPA_INVALID) { |
| ECM_IPA_ERROR("can't disconnect before connect\n"); |
| return -EPERM; |
| } |
| ecm_ipa_ctx->state = next_state; |
| ECM_IPA_STATE_DEBUG(ecm_ipa_ctx); |
| |
| netif_carrier_off(ecm_ipa_ctx->net); |
| ECM_IPA_DEBUG("carrier_off notifcation was sent\n"); |
| |
| ecm_msg = kzalloc(sizeof(*ecm_msg), GFP_KERNEL); |
| if (!ecm_msg) { |
| ECM_IPA_ERROR("can't alloc msg mem\n"); |
| return -ENOMEM; |
| } |
| |
| memset(&msg_meta, 0, sizeof(struct ipa_msg_meta)); |
| msg_meta.msg_type = ECM_DISCONNECT; |
| msg_meta.msg_len = sizeof(struct ipa_ecm_msg); |
| strlcpy(ecm_msg->name, ecm_ipa_ctx->net->name, |
| IPA_RESOURCE_NAME_MAX); |
| ecm_msg->ifindex = ecm_ipa_ctx->net->ifindex; |
| |
| retval = ipa_send_msg(&msg_meta, ecm_msg, ecm_ipa_msg_free_cb); |
| if (retval) { |
| ECM_IPA_ERROR("fail to send ECM_DISCONNECT message\n"); |
| kfree(ecm_msg); |
| return -EPERM; |
| } |
| |
| netif_stop_queue(ecm_ipa_ctx->net); |
| ECM_IPA_DEBUG("queue stopped\n"); |
| |
| if (ipa_pm_is_used()) |
| ecm_ipa_deregister_pm_client(ecm_ipa_ctx); |
| else |
| ecm_ipa_destroy_rm_resource(ecm_ipa_ctx); |
| |
| outstanding_dropped_pkts = |
| atomic_read(&ecm_ipa_ctx->outstanding_pkts); |
| ecm_ipa_ctx->net->stats.tx_errors += outstanding_dropped_pkts; |
| atomic_set(&ecm_ipa_ctx->outstanding_pkts, 0); |
| |
| ECM_IPA_INFO("ECM_IPA was disconnected successfully\n"); |
| |
| ECM_IPA_LOG_EXIT(); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(ecm_ipa_disconnect); |
| |
| /** |
| * ecm_ipa_cleanup() - unregister the network interface driver and free |
| * internal data structs. |
| * @priv: same value that was set by ecm_ipa_init(), this |
| * parameter holds the network device pointer. |
| * |
| * This function shall be called once the network interface is not |
| * needed anymore, e.g: when the USB composition does not support ECM. |
| * This function shall be called after the pipes were disconnected. |
| * Detailed description: |
| * - delete the driver dependency defined for IPA resource manager and |
| * destroy the producer resource. |
| * - remove the debugfs entries |
| * - deregister the network interface from Linux network stack |
| * - free all internal data structs |
| */ |
| void ecm_ipa_cleanup(void *priv) |
| { |
| struct ecm_ipa_dev *ecm_ipa_ctx = priv; |
| int next_state; |
| |
| ECM_IPA_LOG_ENTRY(); |
| |
| ECM_IPA_DEBUG("priv=0x%p\n", priv); |
| |
| if (!ecm_ipa_ctx) { |
| ECM_IPA_ERROR("ecm_ipa_ctx NULL pointer\n"); |
| return; |
| } |
| |
| next_state = ecm_ipa_next_state(ecm_ipa_ctx->state, ECM_IPA_CLEANUP); |
| if (next_state == ECM_IPA_INVALID) { |
| ECM_IPA_ERROR("can't clean driver without cable disconnect\n"); |
| return; |
| } |
| ecm_ipa_ctx->state = next_state; |
| ECM_IPA_STATE_DEBUG(ecm_ipa_ctx); |
| |
| ecm_ipa_rules_destroy(ecm_ipa_ctx); |
| ecm_ipa_debugfs_destroy(ecm_ipa_ctx); |
| |
| unregister_netdev(ecm_ipa_ctx->net); |
| free_netdev(ecm_ipa_ctx->net); |
| |
| ECM_IPA_INFO("ECM_IPA was destroyed successfully\n"); |
| |
| ECM_IPA_LOG_EXIT(); |
| } |
| EXPORT_SYMBOL(ecm_ipa_cleanup); |
| |
| static void ecm_ipa_enable_data_path(struct ecm_ipa_dev *ecm_ipa_ctx) |
| { |
| if (ecm_ipa_ctx->device_ready_notify) { |
| ecm_ipa_ctx->device_ready_notify(); |
| ECM_IPA_DEBUG("USB device_ready_notify() was called\n"); |
| } else { |
| ECM_IPA_DEBUG("device_ready_notify() not supplied\n"); |
| } |
| |
| netif_start_queue(ecm_ipa_ctx->net); |
| ECM_IPA_DEBUG("queue started\n"); |
| } |
| |
| /** |
| * ecm_ipa_rules_cfg() - set header insertion and register Tx/Rx properties |
| * Headers will be committed to HW |
| * @ecm_ipa_ctx: main driver context parameters |
| * @dst_mac: destination MAC address |
| * @src_mac: source MAC address |
| * |
| * Returns negative errno, or zero on success |
| */ |
| static int ecm_ipa_rules_cfg |
| (struct ecm_ipa_dev *ecm_ipa_ctx, |
| const void *dst_mac, const void *src_mac) |
| { |
| struct ipa_ioc_add_hdr *hdrs; |
| struct ipa_hdr_add *ipv4_hdr; |
| struct ipa_hdr_add *ipv6_hdr; |
| struct ethhdr *eth_ipv4; |
| struct ethhdr *eth_ipv6; |
| int result = 0; |
| |
| ECM_IPA_LOG_ENTRY(); |
| hdrs = kzalloc |
| (sizeof(*hdrs) + sizeof(*ipv4_hdr) + sizeof(*ipv6_hdr), |
| GFP_KERNEL); |
| if (!hdrs) { |
| result = -ENOMEM; |
| goto out; |
| } |
| ipv4_hdr = &hdrs->hdr[0]; |
| eth_ipv4 = (struct ethhdr *)ipv4_hdr->hdr; |
| ipv6_hdr = &hdrs->hdr[1]; |
| eth_ipv6 = (struct ethhdr *)ipv6_hdr->hdr; |
| strlcpy(ipv4_hdr->name, ECM_IPA_IPV4_HDR_NAME, IPA_RESOURCE_NAME_MAX); |
| memcpy(eth_ipv4->h_dest, dst_mac, ETH_ALEN); |
| memcpy(eth_ipv4->h_source, src_mac, ETH_ALEN); |
| eth_ipv4->h_proto = htons(ETH_P_IP); |
| ipv4_hdr->hdr_len = ETH_HLEN; |
| ipv4_hdr->is_partial = 0; |
| ipv4_hdr->is_eth2_ofst_valid = true; |
| ipv4_hdr->eth2_ofst = 0; |
| ipv4_hdr->type = IPA_HDR_L2_ETHERNET_II; |
| strlcpy(ipv6_hdr->name, ECM_IPA_IPV6_HDR_NAME, IPA_RESOURCE_NAME_MAX); |
| memcpy(eth_ipv6->h_dest, dst_mac, ETH_ALEN); |
| memcpy(eth_ipv6->h_source, src_mac, ETH_ALEN); |
| eth_ipv6->h_proto = htons(ETH_P_IPV6); |
| ipv6_hdr->hdr_len = ETH_HLEN; |
| ipv6_hdr->is_partial = 0; |
| ipv6_hdr->is_eth2_ofst_valid = true; |
| ipv6_hdr->eth2_ofst = 0; |
| ipv6_hdr->type = IPA_HDR_L2_ETHERNET_II; |
| hdrs->commit = 1; |
| hdrs->num_hdrs = 2; |
| result = ipa_add_hdr(hdrs); |
| if (result) { |
| ECM_IPA_ERROR("Fail on Header-Insertion(%d)\n", result); |
| goto out_free_mem; |
| } |
| if (ipv4_hdr->status) { |
| ECM_IPA_ERROR |
| ("Fail on Header-Insertion ipv4(%d)\n", |
| ipv4_hdr->status); |
| result = ipv4_hdr->status; |
| goto out_free_mem; |
| } |
| if (ipv6_hdr->status) { |
| ECM_IPA_ERROR |
| ("Fail on Header-Insertion ipv6(%d)\n", |
| ipv6_hdr->status); |
| result = ipv6_hdr->status; |
| goto out_free_mem; |
| } |
| ecm_ipa_ctx->eth_ipv4_hdr_hdl = ipv4_hdr->hdr_hdl; |
| ecm_ipa_ctx->eth_ipv6_hdr_hdl = ipv6_hdr->hdr_hdl; |
| ECM_IPA_LOG_EXIT(); |
| out_free_mem: |
| kfree(hdrs); |
| out: |
| return result; |
| } |
| |
| /** |
| * ecm_ipa_rules_destroy() - remove the IPA core configuration done for |
| * the driver data path. |
| * @ecm_ipa_ctx: the driver context |
| * |
| * Revert the work done on ecm_ipa_rules_cfg. |
| */ |
| static void ecm_ipa_rules_destroy(struct ecm_ipa_dev *ecm_ipa_ctx) |
| { |
| struct ipa_ioc_del_hdr *del_hdr; |
| struct ipa_hdr_del *ipv4; |
| struct ipa_hdr_del *ipv6; |
| int result; |
| |
| del_hdr = kzalloc(sizeof(*del_hdr) + sizeof(*ipv4) + |
| sizeof(*ipv6), GFP_KERNEL); |
| if (!del_hdr) |
| return; |
| del_hdr->commit = 1; |
| del_hdr->num_hdls = 2; |
| ipv4 = &del_hdr->hdl[0]; |
| ipv4->hdl = ecm_ipa_ctx->eth_ipv4_hdr_hdl; |
| ipv6 = &del_hdr->hdl[1]; |
| ipv6->hdl = ecm_ipa_ctx->eth_ipv6_hdr_hdl; |
| result = ipa_del_hdr(del_hdr); |
| if (result || ipv4->status || ipv6->status) |
| ECM_IPA_ERROR("ipa_del_hdr failed\n"); |
| kfree(del_hdr); |
| } |
| |
| /* ecm_ipa_register_properties() - set Tx/Rx properties for ipacm |
| * |
| * Register ecm0 interface with 2 Tx properties and 2 Rx properties: |
| * The 2 Tx properties are for data flowing from IPA to USB, they |
| * have Header-Insertion properties both for Ipv4 and Ipv6 Ethernet framing. |
| * The 2 Rx properties are for data flowing from USB to IPA, they have |
| * simple rule which always "hit". |
| * |
| */ |
| static int ecm_ipa_register_properties(struct ecm_ipa_dev *ecm_ipa_ctx) |
| { |
| struct ipa_tx_intf tx_properties = {0}; |
| struct ipa_ioc_tx_intf_prop properties[2] = { {0}, {0} }; |
| struct ipa_ioc_tx_intf_prop *ipv4_property; |
| struct ipa_ioc_tx_intf_prop *ipv6_property; |
| struct ipa_ioc_rx_intf_prop rx_ioc_properties[2] = { {0}, {0} }; |
| struct ipa_rx_intf rx_properties = {0}; |
| struct ipa_ioc_rx_intf_prop *rx_ipv4_property; |
| struct ipa_ioc_rx_intf_prop *rx_ipv6_property; |
| int result = 0; |
| |
| ECM_IPA_LOG_ENTRY(); |
| |
| tx_properties.prop = properties; |
| ipv4_property = &tx_properties.prop[0]; |
| ipv4_property->ip = IPA_IP_v4; |
| ipv4_property->dst_pipe = ecm_ipa_ctx->ipa_to_usb_client; |
| strlcpy |
| (ipv4_property->hdr_name, ECM_IPA_IPV4_HDR_NAME, |
| IPA_RESOURCE_NAME_MAX); |
| ipv4_property->hdr_l2_type = IPA_HDR_L2_ETHERNET_II; |
| ipv6_property = &tx_properties.prop[1]; |
| ipv6_property->ip = IPA_IP_v6; |
| ipv6_property->dst_pipe = ecm_ipa_ctx->ipa_to_usb_client; |
| ipv6_property->hdr_l2_type = IPA_HDR_L2_ETHERNET_II; |
| strlcpy |
| (ipv6_property->hdr_name, ECM_IPA_IPV6_HDR_NAME, |
| IPA_RESOURCE_NAME_MAX); |
| tx_properties.num_props = 2; |
| |
| rx_properties.prop = rx_ioc_properties; |
| rx_ipv4_property = &rx_properties.prop[0]; |
| rx_ipv4_property->ip = IPA_IP_v4; |
| rx_ipv4_property->attrib.attrib_mask = 0; |
| rx_ipv4_property->src_pipe = ecm_ipa_ctx->usb_to_ipa_client; |
| rx_ipv4_property->hdr_l2_type = IPA_HDR_L2_ETHERNET_II; |
| rx_ipv6_property = &rx_properties.prop[1]; |
| rx_ipv6_property->ip = IPA_IP_v6; |
| rx_ipv6_property->attrib.attrib_mask = 0; |
| rx_ipv6_property->src_pipe = ecm_ipa_ctx->usb_to_ipa_client; |
| rx_ipv6_property->hdr_l2_type = IPA_HDR_L2_ETHERNET_II; |
| rx_properties.num_props = 2; |
| |
| result = ipa_register_intf("ecm0", &tx_properties, &rx_properties); |
| if (result) |
| ECM_IPA_ERROR("fail on Tx/Rx properties registration\n"); |
| |
| ECM_IPA_LOG_EXIT(); |
| |
| return result; |
| } |
| |
| static void ecm_ipa_deregister_properties(void) |
| { |
| int result; |
| |
| ECM_IPA_LOG_ENTRY(); |
| result = ipa_deregister_intf("ecm0"); |
| if (result) |
| ECM_IPA_DEBUG("Fail on Tx prop deregister\n"); |
| ECM_IPA_LOG_EXIT(); |
| } |
| |
| /** |
| * ecm_ipa_configure() - make IPA core end-point specific configuration |
| * @usb_to_ipa_hdl: handle of usb_to_ipa end-point for IPA driver |
| * @ipa_to_usb_hdl: handle of ipa_to_usb end-point for IPA driver |
| * @host_ethaddr: host Ethernet address in network order |
| * @device_ethaddr: device Ethernet address in network order |
| * |
| * Configure the usb_to_ipa and ipa_to_usb end-point registers |
| * - USB->IPA end-point: disable de-aggregation, enable link layer |
| * header removal (Ethernet removal), source NATing and default routing. |
| * - IPA->USB end-point: disable aggregation, add link layer header (Ethernet) |
| * - allocate Ethernet device |
| * - register to Linux network stack |
| * |
| * Returns negative errno, or zero on success |
| */ |
| |
| static void ecm_ipa_rm_notify |
| (void *user_data, enum ipa_rm_event event, unsigned long data) |
| { |
| struct ecm_ipa_dev *ecm_ipa_ctx = user_data; |
| |
| ECM_IPA_LOG_ENTRY(); |
| if |
| (event == IPA_RM_RESOURCE_GRANTED && |
| netif_queue_stopped(ecm_ipa_ctx->net)) { |
| ECM_IPA_DEBUG("Resource Granted - starting queue\n"); |
| netif_start_queue(ecm_ipa_ctx->net); |
| } else { |
| ECM_IPA_DEBUG("Resource released\n"); |
| } |
| ECM_IPA_LOG_EXIT(); |
| } |
| |
| static struct net_device_stats *ecm_ipa_get_stats(struct net_device *net) |
| { |
| return &net->stats; |
| } |
| |
| static int ecm_ipa_create_rm_resource(struct ecm_ipa_dev *ecm_ipa_ctx) |
| { |
| struct ipa_rm_create_params create_params = {0}; |
| struct ipa_rm_perf_profile profile; |
| int result; |
| |
| ECM_IPA_LOG_ENTRY(); |
| create_params.name = IPA_RM_RESOURCE_STD_ECM_PROD; |
| create_params.reg_params.user_data = ecm_ipa_ctx; |
| create_params.reg_params.notify_cb = ecm_ipa_rm_notify; |
| result = ipa_rm_create_resource(&create_params); |
| if (result) { |
| ECM_IPA_ERROR("Fail on ipa_rm_create_resource\n"); |
| goto fail_rm_create; |
| } |
| ECM_IPA_DEBUG("rm client was created"); |
| |
| profile.max_supported_bandwidth_mbps = IPA_APPS_MAX_BW_IN_MBPS; |
| ipa_rm_set_perf_profile(IPA_RM_RESOURCE_STD_ECM_PROD, &profile); |
| |
| result = ipa_rm_inactivity_timer_init |
| (IPA_RM_RESOURCE_STD_ECM_PROD, |
| INACTIVITY_MSEC_DELAY); |
| if (result) { |
| ECM_IPA_ERROR("Fail on ipa_rm_inactivity_timer_init\n"); |
| goto fail_it; |
| } |
| ECM_IPA_DEBUG("rm_it client was created"); |
| |
| result = ipa_rm_add_dependency_sync |
| (IPA_RM_RESOURCE_STD_ECM_PROD, |
| ecm_ipa_ctx->ipa_rm_resource_name_cons); |
| if (result && result != -EINPROGRESS) |
| ECM_IPA_ERROR |
| ("unable to add ECM/USB dependency (%d)\n", result); |
| |
| result = ipa_rm_add_dependency_sync |
| (ecm_ipa_ctx->ipa_rm_resource_name_prod, |
| IPA_RM_RESOURCE_APPS_CONS); |
| if (result && result != -EINPROGRESS) |
| ECM_IPA_ERROR |
| ("unable to add USB/APPS dependency (%d)\n", result); |
| |
| ECM_IPA_DEBUG("rm dependency was set\n"); |
| |
| ECM_IPA_LOG_EXIT(); |
| return 0; |
| |
| fail_it: |
| fail_rm_create: |
| return result; |
| } |
| |
| static void ecm_ipa_destroy_rm_resource(struct ecm_ipa_dev *ecm_ipa_ctx) |
| { |
| int result; |
| |
| ECM_IPA_LOG_ENTRY(); |
| |
| ipa_rm_delete_dependency(IPA_RM_RESOURCE_STD_ECM_PROD, |
| ecm_ipa_ctx->ipa_rm_resource_name_cons); |
| ipa_rm_delete_dependency(ecm_ipa_ctx->ipa_rm_resource_name_prod, |
| IPA_RM_RESOURCE_APPS_CONS); |
| ipa_rm_inactivity_timer_destroy(IPA_RM_RESOURCE_STD_ECM_PROD); |
| result = ipa_rm_delete_resource(IPA_RM_RESOURCE_STD_ECM_PROD); |
| if (result) |
| ECM_IPA_ERROR("resource deletion failed\n"); |
| |
| ECM_IPA_LOG_EXIT(); |
| } |
| |
| static void ecm_ipa_pm_cb(void *p, enum ipa_pm_cb_event event) |
| { |
| struct ecm_ipa_dev *ecm_ipa_ctx = p; |
| |
| ECM_IPA_LOG_ENTRY(); |
| if (event != IPA_PM_CLIENT_ACTIVATED) { |
| ECM_IPA_ERROR("unexpected event %d\n", event); |
| WARN_ON(1); |
| return; |
| } |
| |
| if (netif_queue_stopped(ecm_ipa_ctx->net)) { |
| ECM_IPA_DEBUG("Resource Granted - starting queue\n"); |
| netif_start_queue(ecm_ipa_ctx->net); |
| } |
| ECM_IPA_LOG_EXIT(); |
| } |
| |
| static int ecm_ipa_register_pm_client(struct ecm_ipa_dev *ecm_ipa_ctx) |
| { |
| int result; |
| struct ipa_pm_register_params pm_reg; |
| |
| memset(&pm_reg, 0, sizeof(pm_reg)); |
| pm_reg.name = ecm_ipa_ctx->net->name; |
| pm_reg.user_data = ecm_ipa_ctx; |
| pm_reg.callback = ecm_ipa_pm_cb; |
| pm_reg.group = IPA_PM_GROUP_APPS; |
| result = ipa_pm_register(&pm_reg, &ecm_ipa_ctx->pm_hdl); |
| if (result) { |
| ECM_IPA_ERROR("failed to create IPA PM client %d\n", result); |
| return result; |
| } |
| return 0; |
| } |
| |
| static void ecm_ipa_deregister_pm_client(struct ecm_ipa_dev *ecm_ipa_ctx) |
| { |
| ipa_pm_deactivate_sync(ecm_ipa_ctx->pm_hdl); |
| ipa_pm_deregister(ecm_ipa_ctx->pm_hdl); |
| ecm_ipa_ctx->pm_hdl = ~0; |
| } |
| |
| static int resource_request(struct ecm_ipa_dev *ecm_ipa_ctx) |
| { |
| if (ipa_pm_is_used()) |
| return ipa_pm_activate(ecm_ipa_ctx->pm_hdl); |
| |
| return ipa_rm_inactivity_timer_request_resource( |
| IPA_RM_RESOURCE_STD_ECM_PROD); |
| } |
| |
| static void resource_release(struct ecm_ipa_dev *ecm_ipa_ctx) |
| { |
| if (ipa_pm_is_used()) |
| ipa_pm_deferred_deactivate(ecm_ipa_ctx->pm_hdl); |
| else |
| ipa_rm_inactivity_timer_release_resource( |
| IPA_RM_RESOURCE_STD_ECM_PROD); |
| } |
| |
| /** |
| * ecm_ipa_tx_complete_notify() - Rx notify |
| * |
| * @priv: ecm driver context |
| * @evt: event type |
| * @data: data provided with event |
| * |
| * Check that the packet is the one we sent and release it |
| * This function will be called in defered context in IPA wq. |
| */ |
| static void ecm_ipa_tx_complete_notify |
| (void *priv, |
| enum ipa_dp_evt_type evt, |
| unsigned long data) |
| { |
| struct sk_buff *skb = (struct sk_buff *)data; |
| struct ecm_ipa_dev *ecm_ipa_ctx = priv; |
| |
| if (!skb) { |
| ECM_IPA_ERROR("Bad SKB received from IPA driver\n"); |
| return; |
| } |
| |
| if (!ecm_ipa_ctx) { |
| ECM_IPA_ERROR("ecm_ipa_ctx is NULL pointer\n"); |
| return; |
| } |
| |
| ECM_IPA_DEBUG |
| ("Tx-complete, len=%d, skb->prot=%d, outstanding=%d\n", |
| skb->len, skb->protocol, |
| atomic_read(&ecm_ipa_ctx->outstanding_pkts)); |
| |
| if (evt != IPA_WRITE_DONE) { |
| ECM_IPA_ERROR("unsupported event on Tx callback\n"); |
| return; |
| } |
| |
| if (unlikely(ecm_ipa_ctx->state != ECM_IPA_CONNECTED_AND_UP)) { |
| ECM_IPA_DEBUG |
| ("dropping Tx-complete pkt, state=%s", |
| ecm_ipa_state_string(ecm_ipa_ctx->state)); |
| goto out; |
| } |
| |
| ecm_ipa_ctx->net->stats.tx_packets++; |
| ecm_ipa_ctx->net->stats.tx_bytes += skb->len; |
| |
| atomic_dec(&ecm_ipa_ctx->outstanding_pkts); |
| if |
| (netif_queue_stopped(ecm_ipa_ctx->net) && |
| netif_carrier_ok(ecm_ipa_ctx->net) && |
| atomic_read(&ecm_ipa_ctx->outstanding_pkts) |
| < (ecm_ipa_ctx->outstanding_low)) { |
| ECM_IPA_DEBUG |
| ("outstanding low (%d) - waking up queue\n", |
| ecm_ipa_ctx->outstanding_low); |
| netif_wake_queue(ecm_ipa_ctx->net); |
| } |
| |
| out: |
| dev_kfree_skb_any(skb); |
| } |
| |
| static void ecm_ipa_tx_timeout(struct net_device *net) |
| { |
| struct ecm_ipa_dev *ecm_ipa_ctx = netdev_priv(net); |
| |
| ECM_IPA_ERROR |
| ("possible IPA stall was detected, %d outstanding", |
| atomic_read(&ecm_ipa_ctx->outstanding_pkts)); |
| |
| net->stats.tx_errors++; |
| } |
| |
| static int ecm_ipa_debugfs_atomic_open(struct inode *inode, struct file *file) |
| { |
| struct ecm_ipa_dev *ecm_ipa_ctx = inode->i_private; |
| |
| ECM_IPA_LOG_ENTRY(); |
| file->private_data = &ecm_ipa_ctx->outstanding_pkts; |
| ECM_IPA_LOG_EXIT(); |
| return 0; |
| } |
| |
| static ssize_t ecm_ipa_debugfs_atomic_read |
| (struct file *file, char __user *ubuf, size_t count, loff_t *ppos) |
| { |
| int nbytes; |
| u8 atomic_str[DEBUGFS_TEMP_BUF_SIZE] = {0}; |
| atomic_t *atomic_var = file->private_data; |
| |
| nbytes = scnprintf |
| (atomic_str, sizeof(atomic_str), "%d\n", |
| atomic_read(atomic_var)); |
| return simple_read_from_buffer(ubuf, count, ppos, atomic_str, nbytes); |
| } |
| |
| #ifdef CONFIG_DEBUG_FS |
| |
| static void ecm_ipa_debugfs_init(struct ecm_ipa_dev *ecm_ipa_ctx) |
| { |
| const mode_t flags_read_write = 0666; |
| const mode_t flags_read_only = 0444; |
| struct dentry *file; |
| |
| ECM_IPA_LOG_ENTRY(); |
| |
| if (!ecm_ipa_ctx) |
| return; |
| |
| ecm_ipa_ctx->directory = debugfs_create_dir("ecm_ipa", NULL); |
| if (!ecm_ipa_ctx->directory) { |
| ECM_IPA_ERROR("could not create debugfs directory entry\n"); |
| goto fail_directory; |
| } |
| file = debugfs_create_u8 |
| ("outstanding_high", flags_read_write, |
| ecm_ipa_ctx->directory, &ecm_ipa_ctx->outstanding_high); |
| if (!file) { |
| ECM_IPA_ERROR("could not create outstanding_high file\n"); |
| goto fail_file; |
| } |
| file = debugfs_create_u8 |
| ("outstanding_low", flags_read_write, |
| ecm_ipa_ctx->directory, &ecm_ipa_ctx->outstanding_low); |
| if (!file) { |
| ECM_IPA_ERROR("could not create outstanding_low file\n"); |
| goto fail_file; |
| } |
| file = debugfs_create_file |
| ("outstanding", flags_read_only, |
| ecm_ipa_ctx->directory, |
| ecm_ipa_ctx, &ecm_ipa_debugfs_atomic_ops); |
| if (!file) { |
| ECM_IPA_ERROR("could not create outstanding file\n"); |
| goto fail_file; |
| } |
| |
| ECM_IPA_DEBUG("debugfs entries were created\n"); |
| ECM_IPA_LOG_EXIT(); |
| |
| return; |
| fail_file: |
| debugfs_remove_recursive(ecm_ipa_ctx->directory); |
| fail_directory: |
| return; |
| } |
| |
| static void ecm_ipa_debugfs_destroy(struct ecm_ipa_dev *ecm_ipa_ctx) |
| { |
| debugfs_remove_recursive(ecm_ipa_ctx->directory); |
| } |
| |
| #else /* !CONFIG_DEBUG_FS*/ |
| |
| static void ecm_ipa_debugfs_init(struct ecm_ipa_dev *ecm_ipa_ctx) {} |
| |
| static void ecm_ipa_debugfs_destroy(struct ecm_ipa_dev *ecm_ipa_ctx) {} |
| |
| #endif /* CONFIG_DEBUG_FS */ |
| |
| /** |
| * ecm_ipa_ep_cfg() - configure the USB endpoints for ECM |
| * |
| *usb_to_ipa_hdl: handle received from ipa_connect |
| *ipa_to_usb_hdl: handle received from ipa_connect |
| * |
| * USB to IPA pipe: |
| * - No de-aggregation |
| * - Remove Ethernet header |
| * - SRC NAT |
| * - Default routing(0) |
| * IPA to USB Pipe: |
| * - No aggregation |
| * - Add Ethernet header |
| */ |
| static int ecm_ipa_ep_registers_cfg(u32 usb_to_ipa_hdl, u32 ipa_to_usb_hdl) |
| { |
| int result = 0; |
| struct ipa_ep_cfg usb_to_ipa_ep_cfg; |
| struct ipa_ep_cfg ipa_to_usb_ep_cfg; |
| |
| ECM_IPA_LOG_ENTRY(); |
| memset(&usb_to_ipa_ep_cfg, 0, sizeof(struct ipa_ep_cfg)); |
| usb_to_ipa_ep_cfg.aggr.aggr_en = IPA_BYPASS_AGGR; |
| usb_to_ipa_ep_cfg.hdr.hdr_len = ETH_HLEN; |
| usb_to_ipa_ep_cfg.nat.nat_en = IPA_SRC_NAT; |
| usb_to_ipa_ep_cfg.route.rt_tbl_hdl = 0; |
| usb_to_ipa_ep_cfg.mode.dst = IPA_CLIENT_A5_LAN_WAN_CONS; |
| usb_to_ipa_ep_cfg.mode.mode = IPA_BASIC; |
| result = ipa_cfg_ep(usb_to_ipa_hdl, &usb_to_ipa_ep_cfg); |
| if (result) { |
| ECM_IPA_ERROR("failed to configure USB to IPA point\n"); |
| goto out; |
| } |
| memset(&ipa_to_usb_ep_cfg, 0, sizeof(struct ipa_ep_cfg)); |
| ipa_to_usb_ep_cfg.aggr.aggr_en = IPA_BYPASS_AGGR; |
| ipa_to_usb_ep_cfg.hdr.hdr_len = ETH_HLEN; |
| ipa_to_usb_ep_cfg.nat.nat_en = IPA_BYPASS_NAT; |
| result = ipa_cfg_ep(ipa_to_usb_hdl, &ipa_to_usb_ep_cfg); |
| if (result) { |
| ECM_IPA_ERROR("failed to configure IPA to USB end-point\n"); |
| goto out; |
| } |
| ECM_IPA_DEBUG("end-point registers successfully configured\n"); |
| out: |
| ECM_IPA_LOG_EXIT(); |
| return result; |
| } |
| |
| /** |
| * ecm_ipa_set_device_ethernet_addr() - set device etherenet address |
| * @dev_ethaddr: device etherenet address |
| * |
| * Returns 0 for success, negative otherwise |
| */ |
| static int ecm_ipa_set_device_ethernet_addr |
| (u8 *dev_ethaddr, u8 device_ethaddr[]) |
| { |
| if (!is_valid_ether_addr(device_ethaddr)) |
| return -EINVAL; |
| memcpy(dev_ethaddr, device_ethaddr, ETH_ALEN); |
| ECM_IPA_DEBUG("device ethernet address: %pM\n", dev_ethaddr); |
| return 0; |
| } |
| |
| /** ecm_ipa_next_state - return the next state of the driver |
| * @current_state: the current state of the driver |
| * @operation: an enum which represent the operation being made on the driver |
| * by its API. |
| * |
| * This function implements the driver internal state machine. |
| * Its decisions are based on the driver current state and the operation |
| * being made. |
| * In case the operation is invalid this state machine will return |
| * the value ECM_IPA_INVALID to inform the caller for a forbidden sequence. |
| */ |
| static enum ecm_ipa_state ecm_ipa_next_state |
| (enum ecm_ipa_state current_state, enum ecm_ipa_operation operation) |
| { |
| int next_state = ECM_IPA_INVALID; |
| |
| switch (current_state) { |
| case ECM_IPA_UNLOADED: |
| if (operation == ECM_IPA_INITIALIZE) |
| next_state = ECM_IPA_INITIALIZED; |
| break; |
| case ECM_IPA_INITIALIZED: |
| if (operation == ECM_IPA_CONNECT) |
| next_state = ECM_IPA_CONNECTED; |
| else if (operation == ECM_IPA_OPEN) |
| next_state = ECM_IPA_UP; |
| else if (operation == ECM_IPA_CLEANUP) |
| next_state = ECM_IPA_UNLOADED; |
| break; |
| case ECM_IPA_CONNECTED: |
| if (operation == ECM_IPA_DISCONNECT) |
| next_state = ECM_IPA_INITIALIZED; |
| else if (operation == ECM_IPA_OPEN) |
| next_state = ECM_IPA_CONNECTED_AND_UP; |
| break; |
| case ECM_IPA_UP: |
| if (operation == ECM_IPA_STOP) |
| next_state = ECM_IPA_INITIALIZED; |
| else if (operation == ECM_IPA_CONNECT) |
| next_state = ECM_IPA_CONNECTED_AND_UP; |
| else if (operation == ECM_IPA_CLEANUP) |
| next_state = ECM_IPA_UNLOADED; |
| break; |
| case ECM_IPA_CONNECTED_AND_UP: |
| if (operation == ECM_IPA_STOP) |
| next_state = ECM_IPA_CONNECTED; |
| else if (operation == ECM_IPA_DISCONNECT) |
| next_state = ECM_IPA_UP; |
| break; |
| default: |
| ECM_IPA_ERROR("State is not supported\n"); |
| break; |
| } |
| |
| ECM_IPA_DEBUG |
| ("state transition ( %s -> %s )- %s\n", |
| ecm_ipa_state_string(current_state), |
| ecm_ipa_state_string(next_state), |
| next_state == ECM_IPA_INVALID ? "Forbidden" : "Allowed"); |
| |
| return next_state; |
| } |
| |
| /** |
| * ecm_ipa_state_string - return the state string representation |
| * @state: enum which describe the state |
| */ |
| static const char *ecm_ipa_state_string(enum ecm_ipa_state state) |
| { |
| switch (state) { |
| case ECM_IPA_UNLOADED: |
| return "ECM_IPA_UNLOADED"; |
| case ECM_IPA_INITIALIZED: |
| return "ECM_IPA_INITIALIZED"; |
| case ECM_IPA_CONNECTED: |
| return "ECM_IPA_CONNECTED"; |
| case ECM_IPA_UP: |
| return "ECM_IPA_UP"; |
| case ECM_IPA_CONNECTED_AND_UP: |
| return "ECM_IPA_CONNECTED_AND_UP"; |
| default: |
| return "Not supported"; |
| } |
| } |
| |
| /** |
| * ecm_ipa_init_module() - module initialization |
| * |
| */ |
| static int ecm_ipa_init_module(void) |
| { |
| ECM_IPA_LOG_ENTRY(); |
| ECM_IPA_LOG_EXIT(); |
| return 0; |
| } |
| |
| /** |
| * ecm_ipa_cleanup_module() - module cleanup |
| * |
| */ |
| static void ecm_ipa_cleanup_module(void) |
| { |
| ECM_IPA_LOG_ENTRY(); |
| ECM_IPA_LOG_EXIT(); |
| } |
| |
| MODULE_LICENSE("GPL v2"); |
| MODULE_DESCRIPTION("ECM IPA network interface"); |
| |
| late_initcall(ecm_ipa_init_module); |
| module_exit(ecm_ipa_cleanup_module); |