/**************************************************************************** * * Copyright (c) 2012 - 2018 Samsung Electronics Co., Ltd. All rights reserved * ****************************************************************************/ #include #include #include #include "dev.h" #include "hip.h" #include "log_clients.h" #include "mlme.h" #include "fw_test.h" #include "debug.h" #include "udi.h" #include "src_sink.h" #include "unifiio.h" #include "procfs.h" #include "sap_mlme.h" #ifdef SLSI_TEST_DEV #include "unittest.h" #define UDI_CHAR_DEVICE_NAME "s5n2560unittest" #define UDI_CLASS_NAME "s5n2560test" #else #define UDI_CHAR_DEVICE_NAME "s5n2560udi" #define UDI_CLASS_NAME "s5n2560" #endif #define UDI_LOG_MASK_FILTER_NUM_MAX 5 #define UDI_MIB_SET_LEN_MAX 65535 #define UDI_MIB_GET_LEN_MAX 2048 #ifndef ETH_P_WAPI #define ETH_P_WAPI 0x88b4 #endif #define SLSI_IP_TYPE_UDP 0x11 #define SLSI_DHCP_SERVER_PORT 67 #define SLSI_DHCP_CLIENT_PORT 68 #define SLSI_DHCP_MAGIC_OFFSET 272 #define SLSI_DHCP_MESSAGE_TYPE_ACK 0x05 /** * Control character device for debug * ================================== */ #define NUM_CHAR_CLIENTS 12 /* Number of client programmes on one node. */ #define MAX_MINOR (SLSI_UDI_MINOR_NODES - 1) /* Maximum node number. */ static dev_t major_number; /* Major number of device created by system. */ static struct class *class; /* Device class. */ struct slsi_cdev_client; struct slsi_cdev { int minor; struct cdev cdev; struct slsi_cdev_client *client[NUM_CHAR_CLIENTS]; struct slsi_dev *sdev; struct device *parent; }; struct udi_signal_header { __le16 id; __le16 receiver_pid; __le16 sender_pid; __le32 fw_reference; __le16 vif; } __packed; struct slsi_cdev_client { struct slsi_cdev *ufcdev; int log_enabled; int log_allow_driver_signals; u16 tx_sender_id; struct slsi_fw_test fw_test; /* Flags set for special filtering of ma_packet data */ u16 ma_unitdata_filter_config; u16 ma_unitdata_size_limit; struct sk_buff_head log_list; struct semaphore log_mutex; wait_queue_head_t log_wq; /* Drop Frames and report the number dropped */ #define UDI_MAX_QUEUED_FRAMES 10000 #define UDI_RESTART_QUEUED_FRAMES 9000 #define UDI_MAX_QUEUED_DATA_FRAMES 9000 #define UDI_RESTART_QUEUED_DATA_FRAMES 8000 /* Start dropping ALL frames at queue_len == UDI_MAX_QUEUED_FRAMES * Restart queueing ALL frames at queue_len == UDI_RESTART_QUEUED_FRAMES * Enable MA_PACKET filters at queue_len == UDI_MAX_QUEUED_DATA_FRAMES * Disable MA_PACKET filters at queue_len == UDI_RESTART_QUEUED_DATA_FRAMES */ u32 log_dropped; u32 log_dropped_data; bool log_drop_data_packets; }; static inline struct sk_buff *udi_signal_alloc_skb(size_t sig_size, size_t data_size, u16 id, u16 vif, const char *file, int line) { struct sk_buff *skb = alloc_skb(sig_size + data_size, GFP_ATOMIC); struct udi_signal_header *header; WARN_ON(sig_size < sizeof(struct udi_signal_header)); if (WARN_ON(!skb)) return NULL; slsi_skb_cb_init(skb)->sig_length = sig_size; slsi_skb_cb_get(skb)->data_length = sig_size; header = (struct udi_signal_header *)skb_put(skb, sig_size); header->id = cpu_to_le16(id); header->receiver_pid = 0; header->sender_pid = 0; header->fw_reference = 0; header->vif = vif; return skb; } static inline bool slsi_cdev_unitdata_filter_allow(struct slsi_cdev_client *client, u16 filter) { return (client->ma_unitdata_filter_config & filter) == filter; } /* One minor node per phy. In normal driver mode, this may be one. * In unit test mode, this may be several. */ static struct slsi_cdev *uf_cdevs[SLSI_UDI_MINOR_NODES]; static int udi_log_event(struct slsi_log_client *log_client, struct sk_buff *skb, int dir); static int send_signal_to_log_filter(struct slsi_log_client *log_client, struct sk_buff *skb, int dir); static int send_signal_to_inverse_log_filter(struct slsi_log_client *log_client, struct sk_buff *skb, int dir); int slsi_check_cdev_refs(void) { int client_num; int cdev_num; struct slsi_cdev *cdev = NULL; for (cdev_num = 0; cdev_num < SLSI_UDI_MINOR_NODES; cdev_num++) { cdev = uf_cdevs[cdev_num]; if (!cdev) continue; for (client_num = 0; client_num < NUM_CHAR_CLIENTS; client_num++) if (cdev->client[client_num]) return 1; } return 0; } int slsi_kernel_to_user_space_event(struct slsi_log_client *log_client, u16 event, u32 data_length, const u8 *data) { struct slsi_cdev_client *client = log_client->log_client_ctx; struct sk_buff *skb; int ret; if (WARN_ON(!client)) return -EINVAL; if (!client->log_allow_driver_signals) return 0; skb = udi_signal_alloc_skb(sizeof(struct udi_signal_header), data_length, event, 0, __FILE__, __LINE__); if (WARN_ON(!skb)) return -ENOMEM; if (data_length) fapi_append_data(skb, data, data_length); ret = udi_log_event(log_client, skb, UDI_CONFIG_IND); if (ret) SLSI_WARN_NODEV("Udi log event not registered\n"); /* udi_log_event takes a copy, so ensure that the skb allocated in this * function is freed again. */ kfree_skb(skb); return ret; } static int slsi_cdev_open(struct inode *inode, struct file *file) { struct slsi_cdev *uf_cdev; struct slsi_cdev_client *client; int indx; int minor; minor = iminor(inode); if (minor > MAX_MINOR) { SLSI_ERR_NODEV("minor %d exceeds range\n", minor); return -EINVAL; } uf_cdev = uf_cdevs[minor]; if (!uf_cdev) { SLSI_ERR_NODEV("no cdev instance for minor %d\n", minor); return -EINVAL; } for (indx = 0; indx < NUM_CHAR_CLIENTS; indx++) if (!uf_cdev->client[indx]) break; if (indx >= NUM_CHAR_CLIENTS) { SLSI_ERR_NODEV("already opened\n"); return -ENOTSUPP; } client = kmalloc(sizeof(*client), GFP_KERNEL); if (!client) return -ENOMEM; memset(client, 0, sizeof(struct slsi_cdev_client)); /* init other resource */ skb_queue_head_init(&client->log_list); init_waitqueue_head(&client->log_wq); sema_init(&client->log_mutex, 1); client->tx_sender_id = SLSI_TX_PROCESS_ID_UDI_MIN; slsi_fw_test_init(uf_cdev->sdev, &client->fw_test); client->ufcdev = uf_cdev; uf_cdev->client[indx] = client; file->private_data = client; slsi_procfs_inc_node(); #if IS_ENABLED(CONFIG_SCSC_MXLOGGER) scsc_service_register_observer(NULL, "udi"); #endif SLSI_DBG1_NODEV(SLSI_UDI, "Client:%d added\n", indx); return 0; } static int slsi_cdev_release(struct inode *inode, struct file *filp) { struct slsi_cdev_client *client = (void *)filp->private_data; struct slsi_cdev *uf_cdev; int indx; int minor; minor = iminor(inode); if (minor > MAX_MINOR) { SLSI_ERR_NODEV("minor %d exceeds range\n", minor); return -EINVAL; } uf_cdev = uf_cdevs[minor]; if (!uf_cdev) { SLSI_ERR_NODEV("no cdev instance for minor %d\n", minor); return -EINVAL; } if (!client) return -EINVAL; for (indx = 0; indx < NUM_CHAR_CLIENTS; indx++) if (uf_cdev->client[indx] == client) break; if (indx >= NUM_CHAR_CLIENTS) { SLSI_ERR_NODEV("client not found in list\n"); return -EINVAL; } if (waitqueue_active(&client->log_wq)) wake_up_interruptible(&client->log_wq); if (client->log_enabled) slsi_log_client_unregister(client->ufcdev->sdev, client); skb_queue_purge(&client->log_list); slsi_fw_test_deinit(uf_cdev->sdev, &client->fw_test); uf_cdev->client[indx] = NULL; /* free other resource */ kfree(client); slsi_procfs_dec_node(); #if IS_ENABLED(CONFIG_SCSC_MXLOGGER) scsc_service_unregister_observer(NULL, "udi"); #endif SLSI_DBG1_NODEV(SLSI_UDI, "Client:%d removed\n", indx); return 0; } static ssize_t slsi_cdev_read(struct file *filp, char *p, size_t len, loff_t *poff) { struct slsi_cdev_client *client = (void *)filp->private_data; struct slsi_dev *sdev; int msglen; struct sk_buff *skb; SLSI_UNUSED_PARAMETER(poff); if (!client) return -EINVAL; if (!skb_queue_len(&client->log_list)) { if (filp->f_flags & O_NONBLOCK) return 0; /* wait until getting a signal */ if (wait_event_interruptible(client->log_wq, skb_queue_len(&client->log_list))) { SLSI_ERR_NODEV("slsi_cdev_read: wait_event_interruptible failed.\n"); return -ERESTARTSYS; } } sdev = client->ufcdev->sdev; if (!sdev) { SLSI_ERR_NODEV("sdev not set\n"); return -EINVAL; } skb = skb_dequeue(&client->log_list); if (!skb) { SLSI_ERR(sdev, "No Data\n"); return -EINVAL; } slsi_fw_test_signal_with_udi_header(sdev, &client->fw_test, skb); msglen = skb->len; if (msglen > (s32)len) { SLSI_WARN(sdev, "truncated read to %d actual msg len is %lu\n", msglen, (unsigned long int)len); msglen = len; } if (copy_to_user(p, skb->data, msglen)) { SLSI_ERR(sdev, "Failed to copy UDI log to user\n"); kfree_skb(skb); return -EFAULT; } kfree_skb(skb); return msglen; } static ssize_t slsi_cdev_write(struct file *filp, const char *p, size_t len, loff_t *poff) { struct slsi_cdev_client *client; struct slsi_dev *sdev; struct sk_buff *skb; u8 *data; struct slsi_skb_cb *cb; SLSI_UNUSED_PARAMETER(poff); client = (void *)filp->private_data; if (!client) { SLSI_ERR_NODEV("filep private data not set\n"); return -EINVAL; } if (!client->ufcdev) { SLSI_ERR_NODEV("ufcdev not set\n"); return -EINVAL; } sdev = client->ufcdev->sdev; if (!sdev) { SLSI_ERR_NODEV("sdev not set\n"); return -EINVAL; } skb = alloc_skb(SLSI_NETIF_SKB_HEADROOM + SLSI_NETIF_SKB_TAILROOM + len, GFP_KERNEL); if (!skb) { SLSI_WARN_NODEV("error allocating skb (len: %d)\n", len); return -ENOMEM; } skb_reserve(skb, SLSI_NETIF_SKB_HEADROOM - SLSI_SKB_GET_ALIGNMENT_OFFSET(skb)); data = skb_put(skb, len); if (copy_from_user(data, p, len)) { SLSI_ERR(sdev, "copy from user failed\n"); kfree_skb(skb); return -EFAULT; } cb = slsi_skb_cb_init(skb); cb->sig_length = fapi_get_expected_size(skb); cb->data_length = skb->len; /* F/w will panic if fw_reference is not zero. */ fapi_set_u32(skb, fw_reference, 0); /* set mac header uses values from above initialized cb */ skb_set_mac_header(skb, fapi_get_data(skb) - skb->data); SLSI_DBG3_NODEV(SLSI_UDI, "UDI Signal:%.4X SigLEN:%d DataLen:%d SKBHeadroom:%d bytes:%d\n", fapi_get_sigid(skb), fapi_get_siglen(skb), fapi_get_datalen(skb), skb_headroom(skb), (int)len); /* In WlanLite test mode req signals IDs are 0x1000, 0x1002, 0x1004 */ if (slsi_is_test_mode_enabled() || fapi_is_req(skb) || fapi_is_res(skb)) { /* Use the range of PIDs allocated to the udi clients */ client->tx_sender_id++; if (client->tx_sender_id > SLSI_TX_PROCESS_ID_UDI_MAX) client->tx_sender_id = SLSI_TX_PROCESS_ID_UDI_MIN; fapi_set_u16(skb, sender_pid, client->tx_sender_id); if (!slsi_is_test_mode_enabled()) slsi_fw_test_signal(sdev, &client->fw_test, skb); if (fapi_is_ma(skb)) { if (slsi_tx_data_lower(sdev, skb)) { kfree_skb(skb); return -EINVAL; } } else if (slsi_tx_control(sdev, NULL, skb)) { kfree_skb(skb); return -EINVAL; } } else if (slsi_hip_rx(sdev, skb)) { kfree_skb(skb); return -EINVAL; } return len; } static long slsi_unifi_set_mib(struct slsi_dev *sdev, unsigned long arg) { struct net_device *dev = NULL; long r = 0; u32 mib_data_length; /* Length of valid Mib data in the buffer */ u32 mib_data_size; /* Size of the mib buffer */ unsigned char *mib_data; /* Mib Input/Output Buffer */ u16 mib_vif; if (sdev->device_state != SLSI_DEVICE_STATE_STARTED) { SLSI_ERR(sdev, "UNIFI_SET_MIB: Device not yet available\n"); return -EFAULT; } /* First 2 Bytes are the VIF */ if (copy_from_user((void *)&mib_vif, (void *)arg, 2)) { SLSI_ERR(sdev, "UNIFI_SET_MIB: Failed to copy in vif\n"); return -EFAULT; } /* First 4 Bytes are the Number of Bytes of input Data */ if (copy_from_user((void *)&mib_data_length, (void *)(arg + 2), 4)) { SLSI_ERR(sdev, "UNIFI_SET_MIB: Failed to copy in mib_data_length\n"); return -EFAULT; } /* Second 4 Bytes are the size of the Buffer */ if (copy_from_user((void *)&mib_data_size, (void *)(arg + 6), 4)) { SLSI_ERR(sdev, "UNIFI_SET_MIB: Failed to copy in mib_data_size\n"); return -EFAULT; } /* check if length is valid */ if (unlikely(mib_data_length > UDI_MIB_SET_LEN_MAX || mib_data_size > UDI_MIB_SET_LEN_MAX || mib_data_length > mib_data_size)) { SLSI_ERR(sdev, "UNIFI_SET_MIB: size too long or mib_data_length is invalid (mib_data_length:%u mib_data_size:%u)\n", mib_data_length, mib_data_size); return -EFAULT; } mib_data = kmalloc(mib_data_size, GFP_KERNEL); if (!mib_data) { SLSI_ERR(sdev, "UNIFI_SET_MIB: Failed to allocate memory for mib_data\n"); return -ENOMEM; } /* Read the rest of the Mib Data */ if (copy_from_user((void *)mib_data, (void *)(arg + 10), mib_data_length)) { SLSI_ERR(sdev, "UNIFI_SET_MIB: Failed to copy in mib_data\n"); kfree(mib_data); return -EFAULT; } SLSI_MUTEX_LOCK(sdev->netdev_add_remove_mutex); dev = slsi_get_netdev_locked(sdev, mib_vif); if (mib_vif != 0 && !dev) { SLSI_ERR(sdev, "UNIFI_SET_MIB: Failed - net_device is NULL for interface = %d\n", mib_vif); kfree(mib_data); SLSI_MUTEX_UNLOCK(sdev->netdev_add_remove_mutex); return -EFAULT; } r = slsi_mlme_set(sdev, dev, mib_data, mib_data_length); if (r != 0) SLSI_ERR(sdev, "UNIFI_SET_MIB: Failed - mlme_set_req : Result code = %d\n", r); SLSI_MUTEX_UNLOCK(sdev->netdev_add_remove_mutex); kfree(mib_data); return r; } static long slsi_unifi_get_mib(struct slsi_dev *sdev, unsigned long arg) { struct net_device *dev = NULL; long r = 0; u32 mib_data_length; /* Length of valid Mib data in the buffer */ u32 mib_data_size; /* Size of the mib buffer  */ unsigned char *mib_data; /* Mib Input/Output Buffer */ u16 mib_vif; if (sdev->device_state != SLSI_DEVICE_STATE_STARTED) { SLSI_ERR(sdev, "UNIFI_GET_MIB: Device not yet available\n"); return -EFAULT; } /* First 2 Bytes are the VIF */ if (copy_from_user((void *)&mib_vif, (void *)arg, 2)) { SLSI_ERR(sdev, "UNIFI_GET_MIB: Failed to copy in vif\n"); return -EFAULT; } /* First 4 Bytes are the Number of Bytes of input Data */ if (copy_from_user((void *)&mib_data_length, (void *)(arg + 2), 4)) { SLSI_ERR(sdev, "UNIFI_GET_MIB: Failed to copy in mib_data_length\n"); return -EFAULT; } /* Second 4 Bytes are the size of the Buffer */ if (copy_from_user((void *)&mib_data_size, (void *)(arg + 6), 4)) { SLSI_ERR(sdev, "UNIFI_GET_MIB: Failed to copy in mib_data_size\n"); return -EFAULT; } /* check if length is valid */ if (unlikely(mib_data_length > UDI_MIB_GET_LEN_MAX || mib_data_size > UDI_MIB_GET_LEN_MAX || mib_data_length > mib_data_size)) { SLSI_ERR(sdev, "UNIFI_GET_MIB: size too long or mib_data_length is invalid (mib_data_length:%u mib_data_size:%u)\n", mib_data_length, mib_data_size); return -EFAULT; } mib_data = kmalloc(mib_data_size, GFP_KERNEL); if (!mib_data) { SLSI_ERR(sdev, "UNIFI_GET_MIB: Failed to allocate memory for mib_data\n"); return -ENOMEM; } /* Read the rest of the Mib Data */ if (copy_from_user((void *)mib_data, (void *)(arg + 10), mib_data_length)) { SLSI_ERR(sdev, "UNIFI_GET_MIB: Failed to copy in mib_data\n"); kfree(mib_data); return -EFAULT; } SLSI_MUTEX_LOCK(sdev->netdev_add_remove_mutex); dev = slsi_get_netdev_locked(sdev, mib_vif); if (mib_vif != 0 && !dev) { SLSI_ERR(sdev, "UNIFI_GET_MIB: Failed - net_device is NULL for interface = %d\n", mib_vif); kfree(mib_data); SLSI_MUTEX_UNLOCK(sdev->netdev_add_remove_mutex); return -EFAULT; } r = slsi_mlme_get(sdev, dev, mib_data, mib_data_length, mib_data, mib_data_size, &mib_data_length); if (r != 0) { SLSI_ERR(sdev, "UNIFI_GET_MIB: Failed - mlme_get_req : Result code = %d\n", r); kfree(mib_data); SLSI_MUTEX_UNLOCK(sdev->netdev_add_remove_mutex); return -EINVAL; } SLSI_MUTEX_UNLOCK(sdev->netdev_add_remove_mutex); /* Check the buffer is big enough */ if (mib_data_length > mib_data_size) { SLSI_ERR(sdev, "UNIFI_GET_MIB: Mib result data is to long. (%d bytes when the max is %d bytes)\n", mib_data_length, mib_data_size); kfree(mib_data); return -EINVAL; } /* Copy back the number of Bytes in the Mib result */ if (copy_to_user((void *)arg, (void *)&mib_data_length, 4)) { SLSI_ERR(sdev, "UNIFI_GET_MIB: Failed to copy in mib_data_length back to user\n"); kfree(mib_data); return -EFAULT; } /* Copy back the Mib data */ if (copy_to_user((void *)(arg + 4), mib_data, mib_data_length)) { SLSI_ERR(sdev, "UNIFI_GET_MIB: Failed to copy in mib_data back to user\n"); kfree(mib_data); return -EFAULT; } kfree(mib_data); return 0; } static long slsi_unifi_set_log_mask(struct slsi_cdev_client *client, struct slsi_dev *sdev, unsigned long arg) { struct unifiio_filter_t filter; int i; /* to minimise load on data path, list is converted here to array indexed by signal number */ if (copy_from_user(&filter, (void *)arg, sizeof(filter))) { SLSI_ERR(sdev, "UNIFI_SET_UDI_LOG_MASK: Failed to copy from userspace\n"); return -EFAULT; } if (unlikely(filter.signal_ids_n > UDI_LOG_MASK_FILTER_NUM_MAX)) { SLSI_ERR(sdev, "UNIFI_SET_UDI_LOG_MASK: number of filters too long\n"); return -EFAULT; } if (filter.signal_ids_n) { char *signal_filter_index; int max; int min; max = filter.signal_ids[0]; min = filter.signal_ids[0]; /* find maximum and minimum signal id in filter */ for (i = 0; i < filter.signal_ids_n; i++) { if (filter.signal_ids[i] & UDI_MA_UNITDATA_FILTER_ALLOW_MASK) { client->ma_unitdata_filter_config |= filter.signal_ids[i]; continue; } if (filter.signal_ids[i] > max) max = filter.signal_ids[i]; else if (filter.signal_ids[i] < min) min = filter.signal_ids[i]; } /* and create array only big enough to index the range of signal id specified */ signal_filter_index = kmalloc(max - min + 1, GFP_KERNEL); if (signal_filter_index) { memset(signal_filter_index, 0, max - min + 1); for (i = 0; i < filter.signal_ids_n; i++) { if (filter.signal_ids[i] & UDI_MA_UNITDATA_FILTER_ALLOW_MASK) continue; signal_filter_index[filter.signal_ids[i] - min] = 1; } slsi_log_client_unregister(sdev, client); slsi_log_client_register(sdev, client, filter.log_listed_flag ? send_signal_to_inverse_log_filter : send_signal_to_log_filter, signal_filter_index, min, max); } else { return -ENOMEM; } } return 0; } static long slsi_cdev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { struct slsi_cdev_client *client = (void *)filp->private_data; struct slsi_dev *sdev; long r = 0; int int_param; if (!client || !client->ufcdev) return -EINVAL; sdev = client->ufcdev->sdev; slsi_wake_lock(&sdev->wlan_wl); switch (cmd) { case UNIFI_GET_UDI_ENABLE: int_param = client->log_enabled; put_user(int_param, (int *)arg); break; case UNIFI_SET_UDI_ENABLE: if (get_user(int_param, (int *)arg)) { r = -EFAULT; break; } if (int_param) { slsi_log_client_register(sdev, client, udi_log_event, NULL, 0, 0); client->log_enabled = 1; if (int_param > 1) client->log_allow_driver_signals = 1; } else { slsi_log_client_unregister(sdev, client); client->log_enabled = 0; } break; case UNIFI_SET_UDI_LOG_CONFIG: { struct unifiio_udi_config_t config; if (copy_from_user(&config, (void *)arg, sizeof(config))) { SLSI_ERR(sdev, "UNIFI_SET_UDI_LOG_CONFIG: Failed to copy from userspace\n"); r = -EFAULT; break; } client->ma_unitdata_size_limit = config.ma_unitdata_size_limit; break; } case UNIFI_SET_UDI_LOG_MASK: r = slsi_unifi_set_log_mask(client, sdev, arg); break; case UNIFI_SET_MIB: r = slsi_unifi_set_mib(sdev, arg); break; case UNIFI_GET_MIB: r = slsi_unifi_get_mib(sdev, arg); break; case UNIFI_SRC_SINK_IOCTL: if (sdev->device_state != SLSI_DEVICE_STATE_STARTED) { SLSI_ERR(sdev, "UNIFI_SRC_SINK_IOCTL: Device not yet available\n"); r = -EFAULT; break; } r = slsi_src_sink_cdev_ioctl_cfg(sdev, arg); break; case UNIFI_SOFTMAC_CFG: { u32 softmac_cmd; u8 cmd_param_size; SLSI_ERR(sdev, "UNIFI_SOFTMAC_CFG\n"); if (copy_from_user((void *)&softmac_cmd, (void *)arg, 4)) { SLSI_ERR(sdev, "Failed to get the command\n"); r = -EFAULT; break; } SLSI_DBG3_NODEV(SLSI_UDI, "softmac_cmd -> %u\n", softmac_cmd); arg += sizeof(softmac_cmd); /* Advance past the command bit */ if (copy_from_user((void *)&cmd_param_size, (void *)(arg + 4), 1)) { SLSI_ERR(sdev, "Failed to get the command size\n"); r = -EFAULT; break; } SLSI_DBG3_NODEV(SLSI_UDI, "cmd_param_size -> %u\n", cmd_param_size); if (cmd_param_size) client->ma_unitdata_filter_config = UDI_MA_UNITDATA_FILTER_ALLOW_EAPOL_ID; else client->ma_unitdata_filter_config = 0; break; } case UNIFI_GET_SW_VERSION: { int res = 0; char build_info[200]; int len = 0; #ifndef SLSI_TEST_DEV memset(build_info, 0, 200); mxman_get_fw_version(build_info, 200); len = strlen(build_info); sprintf(build_info + len, " "); if (len >= 197) { len = 197; build_info[len - 1] = '.'; build_info[len - 2] = '.'; build_info[len - 3] = '.'; } else { mxman_get_driver_version(build_info + len + 1, 200 - len - 1); } #else memset(build_info, 0, 200); strcpy(build_info, "UT Build"); #endif /* Copy back the number of Bytes in the version result */ res = copy_to_user((void *)arg, build_info, 200); if (res) { SLSI_ERR(sdev, "UNIFI_GET_SW_VERSION: error back to user %d\n", res); r = -EINVAL; break; } break; } case UNIFI_GET_FAPI_VERSION: { int res = 0; char fapi_info[100]; memset(fapi_info, 0, 100); slsi_get_fapi_version_string(fapi_info); /* Copy back the number of Bytes in the version result */ res = copy_to_user((void *)arg, fapi_info, 100); if (res) { SLSI_ERR(sdev, "UNIFI_GET_FAPI_VERSION: error back to user %d\n", res); r = -EINVAL; break; } break; } default: SLSI_WARN(sdev, "Operation (%d) not supported\n", cmd); r = -EINVAL; } slsi_wake_unlock(&sdev->wlan_wl); return r; } static unsigned int slsi_cdev_poll(struct file *filp, poll_table *wait) { struct slsi_cdev_client *client = (void *)filp->private_data; SLSI_DBG4_NODEV(SLSI_UDI, "Poll(%d)\n", skb_queue_len(&client->log_list)); if (skb_queue_len(&client->log_list)) return POLLIN | POLLRDNORM; /* readable */ poll_wait(filp, &client->log_wq, wait); if (skb_queue_len(&client->log_list)) return POLLIN | POLLRDNORM; /* readable */ return 0; } /* we know for sure that there is a filter present in log_client->signal_filter if this function is called. * we know this because it is called only through a function pointer that is assigned * only when a filter is also set up in the log_client */ static int send_signal_to_log_filter(struct slsi_log_client *log_client, struct sk_buff *skb, int dir) { int ret = 0; u16 signal_id = fapi_get_sigid(skb); if (signal_id > log_client->max_signal_id || signal_id < log_client->min_signal_id || !log_client->signal_filter[signal_id - log_client->min_signal_id]) ret = udi_log_event(log_client, skb, dir); return ret; } static int send_signal_to_inverse_log_filter(struct slsi_log_client *log_client, struct sk_buff *skb, int dir) { int ret = 0; u16 signal_id = fapi_get_sigid(skb); if (signal_id <= log_client->max_signal_id && signal_id >= log_client->min_signal_id && log_client->signal_filter[signal_id - log_client->min_signal_id]) ret = udi_log_event(log_client, skb, dir); return ret; } static int udi_log_event(struct slsi_log_client *log_client, struct sk_buff *skb, int dir) { struct slsi_cdev_client *client = log_client->log_client_ctx; struct udi_msg_t msg; struct udi_msg_t *msg_skb; u16 signal_id = fapi_get_sigid(skb); if (WARN_ON(!client)) return -EINVAL; if (WARN_ON(!skb)) return -EINVAL; if (WARN_ON(skb->len == 0)) return -EINVAL; /* Special Filtering of MaPacket frames */ if (slsi_cdev_unitdata_filter_allow(client, UDI_MA_UNITDATA_FILTER_ALLOW_MASK) && (signal_id == MA_UNITDATA_REQ || signal_id == MA_UNITDATA_IND)) { SLSI_DBG4_NODEV(SLSI_UDI, "FILTER(0x%.4X) DROP\n", signal_id); if (down_interruptible(&client->log_mutex)) { SLSI_WARN_NODEV("Failed to get udi sem\n"); return -ERESTARTSYS; } if (client->log_drop_data_packets) client->log_dropped_data++; up(&client->log_mutex); return -ECANCELED; } /* Exception for driver configuration frames. * these frames must be sent irrespective of number of frames * in queue. */ if (dir == UDI_CONFIG_IND) goto allow_config_frame; if (down_interruptible(&client->log_mutex)) { SLSI_WARN_NODEV("Failed to get udi sem\n"); return -ERESTARTSYS; } /* Handle hitting the UDI_MAX_QUEUED_FRAMES Limit */ if (client->log_dropped) { if (skb_queue_len(&client->log_list) <= UDI_RESTART_QUEUED_FRAMES) { u32 dropped = client->log_dropped; SLSI_WARN_NODEV("Stop Dropping UDI Frames : %d frames Dropped\n", dropped); client->log_dropped = 0; up(&client->log_mutex); slsi_kernel_to_user_space_event(log_client, UDI_DRV_DROPPED_FRAMES, sizeof(u32), (u8 *)&dropped); return -ECANCELED; } client->log_dropped++; up(&client->log_mutex); return -ECANCELED; } else if (!client->log_dropped && skb_queue_len(&client->log_list) >= UDI_MAX_QUEUED_FRAMES) { SLSI_WARN_NODEV("Start Dropping UDI Frames\n"); client->log_dropped++; up(&client->log_mutex); return -ECANCELED; } /* Handle hitting the UDI_MAX_QUEUED_DATA_FRAMES Limit * Turn ON the MA_PACKET Filters before we get near the absolute limit of UDI_MAX_QUEUED_FRAMES * This should allow key frames (mgt, dhcp and eapol etc) to still be in the logs but stop the logging general data frames. * This occurs when the Transfer rate is higher than we can take the frames out of the UDI list. */ if (client->log_drop_data_packets && skb_queue_len(&client->log_list) < UDI_RESTART_QUEUED_DATA_FRAMES) { u32 dropped = client->log_dropped_data; SLSI_WARN_NODEV("Stop Dropping UDI Frames : %d Basic Data frames Dropped\n", client->log_dropped_data); client->log_drop_data_packets = false; client->ma_unitdata_filter_config = 0; client->log_dropped_data = 0; up(&client->log_mutex); slsi_kernel_to_user_space_event(log_client, UDI_DRV_DROPPED_DATA_FRAMES, sizeof(u32), (u8 *)&dropped); return -ECANCELED; } else if (!client->log_drop_data_packets && skb_queue_len(&client->log_list) >= UDI_MAX_QUEUED_DATA_FRAMES && !slsi_cdev_unitdata_filter_allow(client, UDI_MA_UNITDATA_FILTER_ALLOW_MASK)) { SLSI_WARN_NODEV("Start Dropping UDI Basic Data Frames\n"); client->log_drop_data_packets = true; client->ma_unitdata_filter_config = UDI_MA_UNITDATA_FILTER_ALLOW_MGT_ID | UDI_MA_UNITDATA_FILTER_ALLOW_KEY_ID | UDI_MA_UNITDATA_FILTER_ALLOW_CFM_ERROR_ID | UDI_MA_UNITDATA_FILTER_ALLOW_EAPOL_ID; } up(&client->log_mutex); allow_config_frame: if ((signal_id == MA_UNITDATA_REQ || signal_id == MA_UNITDATA_IND) && (client->ma_unitdata_size_limit) && (skb->len > client->ma_unitdata_size_limit)) { struct slsi_skb_cb *cb; struct sk_buff *skb2 = alloc_skb(sizeof(msg) + client->ma_unitdata_size_limit, GFP_ATOMIC); if (WARN_ON(!skb2)) return -ENOMEM; skb_reserve(skb2, sizeof(msg)); cb = slsi_skb_cb_init(skb2); cb->sig_length = fapi_get_siglen(skb); cb->data_length = client->ma_unitdata_size_limit; skb_copy_bits(skb, 0, skb_put(skb2, client->ma_unitdata_size_limit), client->ma_unitdata_size_limit); skb = skb2; } else { skb = skb_copy_expand(skb, sizeof(msg), 0, GFP_ATOMIC); if (WARN_ON(!skb)) return -ENOMEM; } msg.length = sizeof(msg) + skb->len; msg.timestamp = ktime_to_ms(ktime_get()); msg.direction = dir; msg.signal_length = fapi_get_siglen(skb); msg_skb = (struct udi_msg_t *)skb_push(skb, sizeof(msg)); *msg_skb = msg; skb_queue_tail(&client->log_list, skb); /* Wake any waiting user process */ wake_up_interruptible(&client->log_wq); return 0; } #define UF_DEVICE_CREATE(_class, _parent, _devno, _priv, _fmt, _args) \ device_create(_class, _parent, _devno, _priv, _fmt, _args) static const struct file_operations slsi_cdev_fops = { .owner = THIS_MODULE, .open = slsi_cdev_open, .release = slsi_cdev_release, .read = slsi_cdev_read, .write = slsi_cdev_write, .unlocked_ioctl = slsi_cdev_ioctl, .compat_ioctl = slsi_cdev_ioctl, .poll = slsi_cdev_poll, }; #define UF_DEVICE_CREATE(_class, _parent, _devno, _priv, _fmt, _args) \ device_create(_class, _parent, _devno, _priv, _fmt, _args) #ifndef SLSI_TEST_DEV static int slsi_get_minor(void) { int minor; for (minor = 0; minor < SLSI_UDI_MINOR_NODES; minor++) if (!uf_cdevs[minor]) return minor; return -1; } #endif static int slsi_cdev_create(struct slsi_dev *sdev, struct device *parent) { dev_t devno; int ret; struct slsi_cdev *pdev; int minor; SLSI_DBG3_NODEV(SLSI_UDI, "\n"); #ifdef SLSI_TEST_DEV { /* Use the same minor as the unittesthip char device so the number match */ struct slsi_test_dev *uftestdev = (struct slsi_test_dev *)sdev->maxwell_core; minor = uftestdev->device_minor_number; if (minor >= 0 && minor < SLSI_UDI_MINOR_NODES && uf_cdevs[minor]) return -EINVAL; } #else minor = slsi_get_minor(); #endif if (minor >= SLSI_UDI_MINOR_NODES || minor < 0) { SLSI_ERR(sdev, "no minor numbers available,minor:%d\n", minor); return -ENOMEM; } pdev = kmalloc(sizeof(*pdev), GFP_KERNEL); if (!pdev) return -ENOMEM; memset(pdev, 0, sizeof(*pdev)); cdev_init(&pdev->cdev, &slsi_cdev_fops); pdev->cdev.owner = THIS_MODULE; pdev->minor = minor; devno = MKDEV(MAJOR(major_number), minor); ret = cdev_add(&pdev->cdev, devno, 1); if (ret) { SLSI_ERR(sdev, "cdev_add failed with %d for minor %d\n", ret, minor); kfree(pdev); return ret; } pdev->sdev = sdev; pdev->parent = parent; if (!UF_DEVICE_CREATE(class, pdev->parent, devno, pdev, UDI_CHAR_DEVICE_NAME "%d", minor)) { cdev_del(&pdev->cdev); kfree(pdev); return -EINVAL; } sdev->uf_cdev = (void *)pdev; sdev->procfs_instance = minor; uf_cdevs[minor] = pdev; return 0; } static void slsi_cdev_destroy(struct slsi_dev *sdev) { struct slsi_cdev *pdev = (struct slsi_cdev *)sdev->uf_cdev; struct kobject *kobj; struct kref *kref; if (!pdev) return; SLSI_DBG1(sdev, SLSI_UDI, "\n"); while (slsi_check_cdev_refs()) { SLSI_ERR(sdev, "UDI Client still attached. Please Terminate!\n"); msleep(1000); } /* There exist a possibility of race such that the * * - file operation release callback (slsi_cdev_release) is called * - the cdev client structure is freed * - the context is pre-empted and this context (slsi_cdev_destroy) is executed * - slsi_cdev_destroy deletes cdev and hence the kobject embedded inside cdev * and returns * - the release context again executes and operates on a non-existent kobject * leading to kernel Panic * * Ideally the kernel should protect against such race. But it is not! * So we check here that the file operation release callback is complete by * checking the refcount in the kobject embedded in cdev structure. * The refcount is initialized to 1; so anything more than that means * there exists attached clients. */ kobj = &pdev->cdev.kobj; kref = &kobj->kref; #if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0)) while (refcount_read(&kref->refcount) > 1) { SLSI_WARN(sdev, "UDI client File op release not completed yet! (count=%d)\n", refcount_read(&kref->refcount)); msleep(50); } #else while (atomic_read(&kref->refcount) > 1) { SLSI_WARN(sdev, "UDI client File op release not completed yet! (count=%d)\n", atomic_read(&kref->refcount)); msleep(50); } #endif device_destroy(class, pdev->cdev.dev); cdev_del(&pdev->cdev); sdev->uf_cdev = NULL; uf_cdevs[pdev->minor] = NULL; kfree(pdev); } static int udi_initialised; int slsi_udi_init(void) { int ret; SLSI_DBG1_NODEV(SLSI_UDI, "\n"); memset(uf_cdevs, 0, sizeof(uf_cdevs)); /* Allocate two device numbers for each device. */ ret = alloc_chrdev_region(&major_number, 0, SLSI_UDI_MINOR_NODES, UDI_CLASS_NAME); if (ret) { SLSI_ERR_NODEV("Failed to add alloc dev numbers: %d\n", ret); return ret; } /* Create a driver class */ class = class_create(THIS_MODULE, UDI_CLASS_NAME); if (IS_ERR(class)) { SLSI_ERR_NODEV("Failed to create driver udi class\n"); unregister_chrdev_region(major_number, SLSI_UDI_MINOR_NODES); major_number = 0; return -EINVAL; } udi_initialised = 1; return 0; } int slsi_udi_deinit(void) { if (!udi_initialised) return -1; SLSI_DBG1_NODEV(SLSI_UDI, "\n"); class_destroy(class); unregister_chrdev_region(major_number, SLSI_UDI_MINOR_NODES); udi_initialised = 0; return 0; } int slsi_udi_node_init(struct slsi_dev *sdev, struct device *parent) { return slsi_cdev_create(sdev, parent); } int slsi_udi_node_deinit(struct slsi_dev *sdev) { slsi_cdev_destroy(sdev); return 0; }