/**************************************************************************** * * Copyright (c) 2012 - 2016 Samsung Electronics Co., Ltd * ****************************************************************************/ #include #include "debug.h" #include "utils.h" #include "udi.h" #include "unittest.h" #include "mgt.h" #include "scsc/scsc_mx.h" #define SLSI_TESTDRV_NAME "s5n2560_test" static int radios = 11; module_param(radios, int, 0444); MODULE_PARM_DESC(radios, "Number of simulated radios"); /* spinlock for retaining the (struct slsi_dev) information */ static struct slsi_spinlock slsi_test_devices_lock; static struct slsi_test_dev *slsi_test_devices[SLSI_UDI_MINOR_NODES]; static struct class *test_dev_class; /* Major number of device created by system. */ static dev_t major_number; static struct device_driver slsi_test_driver = { .name = SLSI_TESTDRV_NAME }; static void slsi_test_dev_attach_work(struct work_struct *work); static void slsi_test_dev_detach_work(struct work_struct *work); static void slsi_test_dev_free(void) { int i; for (i = 0; i < SLSI_UDI_MINOR_NODES; i++) { struct slsi_test_dev *uftestdev; slsi_spinlock_lock(&slsi_test_devices_lock); uftestdev = slsi_test_devices[i]; slsi_test_devices[i] = NULL; slsi_spinlock_unlock(&slsi_test_devices_lock); if (uftestdev != NULL) { SLSI_INFO_NODEV("Free Test Device: %02X:%02X:%02X:%02X:%02X:%02X\n", uftestdev->hw_addr[0], uftestdev->hw_addr[1], uftestdev->hw_addr[2], uftestdev->hw_addr[3], uftestdev->hw_addr[4], uftestdev->hw_addr[5]); if (WARN_ON(uftestdev->attached)) { slsi_test_bh_deinit(uftestdev); flush_workqueue(uftestdev->attach_detach_work_queue); } destroy_workqueue(uftestdev->attach_detach_work_queue); slsi_test_udi_node_deinit(uftestdev); device_unregister(uftestdev->dev); device_destroy(test_dev_class, uftestdev->dev->devt); } } slsi_test_udi_deinit(); if (test_dev_class != NULL) class_destroy(test_dev_class); unregister_chrdev_region(major_number, SLSI_UDI_MINOR_NODES); } int slsi_sdio_func_drv_register(void) { int i = 0, err = 0, ret = 0; struct slsi_test_dev *uftestdev; dev_t devno; SLSI_INFO_NODEV("Loading SLSI " SLSI_TESTDRV_NAME " Test Driver for mac80211\n"); if (radios > SLSI_UDI_MINOR_NODES) { SLSI_ERR_NODEV("Loading failed, configure SLSI_UDI_MINOR_NODES to match no. of simulated radios\n"); return -ENOMEM; } slsi_spinlock_create(&slsi_test_devices_lock); memset(slsi_test_devices, 0x00, sizeof(slsi_test_devices)); /* Allocate two device numbers for each device. */ ret = alloc_chrdev_region(&major_number, 0, SLSI_UDI_MINOR_NODES, SLSI_TESTDRV_NAME); if (ret) { SLSI_ERR_NODEV("Failed to add alloc dev numbers: %d\n", ret); unregister_chrdev_region(major_number, SLSI_UDI_MINOR_NODES); major_number = 0; return -ENOMEM; } test_dev_class = class_create(THIS_MODULE, SLSI_TESTDRV_NAME); if (IS_ERR(test_dev_class)) return -EAGAIN; slsi_test_udi_init(); for (i = 0; i < radios; i++) { uftestdev = kmalloc(sizeof(*uftestdev), GFP_KERNEL); memset(uftestdev, 0, sizeof(*uftestdev)); uftestdev->attach_detach_work_queue = alloc_ordered_workqueue("Test Work", 0); INIT_WORK(&uftestdev->attach_work, slsi_test_dev_attach_work); INIT_WORK(&uftestdev->detach_work, slsi_test_dev_detach_work); devno = MKDEV(MAJOR(major_number), i); uftestdev->dev = device_create(test_dev_class, NULL, devno, uftestdev, SLSI_TESTDRV_NAME "_dev%d", i); if (IS_ERR(uftestdev->dev)) { SLSI_ERR_NODEV("device_create FAILED, returned - (%ld)\n", PTR_ERR(uftestdev->dev)); err = -ENOMEM; goto failed_free_all; } uftestdev->dev->driver = &slsi_test_driver; mutex_init(&uftestdev->attach_detach_mutex); slsi_test_bh_init(uftestdev); spin_lock_init(&uftestdev->route_spinlock); if (slsi_test_udi_node_init(uftestdev, uftestdev->dev) != 0) { SLSI_ERR_NODEV("udi init FAILED\n"); goto failed_dev_unregister; } /* Using a fixed MAC address instead of slsi_get_hw_mac_address(), * MAC Address format 00:12:FB:00:00: where xx increments for every PHY * (00:12:FB OUI Samsung Electronics) */ memset(uftestdev->hw_addr, 0, sizeof(uftestdev->hw_addr)); uftestdev->hw_addr[1] = 0x12; uftestdev->hw_addr[2] = 0xFB; /*To randomize the mac address*/ uftestdev->hw_addr[ETH_ALEN - 1] += (i & (0xff)); SLSI_INFO_NODEV("Create Test Device: %02X:%02X:%02X:%02X:%02X:%02X\n", uftestdev->hw_addr[0], uftestdev->hw_addr[1], uftestdev->hw_addr[2], uftestdev->hw_addr[3], uftestdev->hw_addr[4], uftestdev->hw_addr[5]); slsi_test_devices[uftestdev->device_minor_number] = uftestdev; } return 0; failed_dev_unregister: device_unregister(uftestdev->dev); device_destroy(test_dev_class, uftestdev->dev->devt); failed_free_all: slsi_test_dev_free(); return -EPERM; } void slsi_sdio_func_drv_unregister(void) { SLSI_INFO_NODEV("Unloading UF6K Test Driver for mac80211\n"); slsi_test_dev_free(); } void slsi_test_dev_attach(struct slsi_test_dev *uftestdev) { struct slsi_dev *sdev; struct scsc_service_client service_client; mutex_lock(&uftestdev->attach_detach_mutex); SLSI_INFO_NODEV("UnitTest UDI Attached : %02X:%02X:%02X:%02X:%02X:%02X\n", uftestdev->hw_addr[0], uftestdev->hw_addr[1], uftestdev->hw_addr[2], uftestdev->hw_addr[3], uftestdev->hw_addr[4], uftestdev->hw_addr[5]); if (uftestdev->attached) { SLSI_ERR_NODEV("attached == true\n"); goto exit; } uftestdev->attached = true; sdev = slsi_dev_attach(uftestdev->dev, (struct scsc_mx *)uftestdev, &service_client); slsi_spinlock_lock(&slsi_test_devices_lock); uftestdev->sdev = sdev; if (!sdev) { SLSI_ERR_NODEV("slsi_dev_attach() Failed\n"); uftestdev->attached = false; } else { slsi_test_bh_start(uftestdev); } slsi_spinlock_unlock(&slsi_test_devices_lock); exit: mutex_unlock(&uftestdev->attach_detach_mutex); } void slsi_test_dev_detach(struct slsi_test_dev *uftestdev) { mutex_lock(&uftestdev->attach_detach_mutex); SLSI_INFO(uftestdev->sdev, "UnitTest UDI Detached : %02X:%02X:%02X:%02X:%02X:%02X\n", uftestdev->hw_addr[0], uftestdev->hw_addr[1], uftestdev->hw_addr[2], uftestdev->hw_addr[3], uftestdev->hw_addr[4], uftestdev->hw_addr[5]); if (!uftestdev->attached) { SLSI_ERR(uftestdev->sdev, "attached != true\n"); goto exit; } uftestdev->attached = false; if (uftestdev->sdev) { struct slsi_dev *sdev = uftestdev->sdev; slsi_test_bh_stop(uftestdev); slsi_dev_detach(sdev); slsi_spinlock_lock(&slsi_test_devices_lock); uftestdev->sdev = NULL; slsi_spinlock_unlock(&slsi_test_devices_lock); } exit: mutex_unlock(&uftestdev->attach_detach_mutex); } void slsi_init_netdev_mac_addr(struct slsi_dev *sdev) { /* Get mac address from file system. */ slsi_get_hw_mac_address(sdev, sdev->hw_addr); SLSI_ETHER_COPY(sdev->netdev_addresses[SLSI_NET_INDEX_WLAN], sdev->hw_addr); SLSI_ETHER_COPY(sdev->netdev_addresses[SLSI_NET_INDEX_P2P], sdev->hw_addr); sdev->netdev_addresses[SLSI_NET_INDEX_P2P][0] |= 0x02; /* Set the local bit */ SLSI_ETHER_COPY(sdev->netdev_addresses[SLSI_NET_INDEX_P2PX_SWLAN], sdev->hw_addr); sdev->netdev_addresses[SLSI_NET_INDEX_P2PX_SWLAN][0] |= 0x02; /* Set the local bit */ sdev->netdev_addresses[SLSI_NET_INDEX_P2PX_SWLAN][4] ^= 0x80; /* EXOR 5th byte with 0x80 */ SLSI_ETHER_COPY(sdev->netdev[SLSI_NET_INDEX_WLAN]->dev_addr, sdev->netdev_addresses[SLSI_NET_INDEX_WLAN]); SLSI_ETHER_COPY(sdev->netdev[SLSI_NET_INDEX_P2P]->dev_addr, sdev->netdev_addresses[SLSI_NET_INDEX_P2P]); } bool slsi_test_process_signal_ip_remap(struct slsi_test_dev *uftestdev, struct sk_buff *skb, struct slsi_test_data_route *route) { int proto = ntohs(skb->protocol); u8 *frame = fapi_get_data(skb) + 14; switch (proto) { case 0x0806: { /* Arp */ u8 *sha = &frame[8]; u8 *spa = &frame[14]; u8 *tha = &frame[18]; u8 *tpa = &frame[24]; SLSI_UNUSED_PARAMETER(sha); SLSI_UNUSED_PARAMETER(spa); SLSI_UNUSED_PARAMETER(tha); SLSI_UNUSED_PARAMETER(tpa); SLSI_DBG4(uftestdev->sdev, SLSI_TEST, "ARP: sha:%pM, spa:%d.%d.%d.%d, tha:%pM, tpa:%d.%d.%d.%d\n", sha, spa[0], spa[1], spa[2], spa[3], tha, tpa[0], tpa[1], tpa[2], tpa[3]); spa[2] = route->ipsubnet; tpa[2] = route->ipsubnet; SLSI_DBG4(uftestdev->sdev, SLSI_TEST, "ARP: sha:%pM, spa:%d.%d.%d.%d, tha:%pM, tpa:%d.%d.%d.%d\n", sha, spa[0], spa[1], spa[2], spa[3], tha, tpa[0], tpa[1], tpa[2], tpa[3]); return true; } case 0x0800: { /* IPv4 */ struct iphdr *iph = (struct iphdr *)frame; u8 *src = (u8 *)&iph->saddr; u8 *dst = (u8 *)&iph->daddr; SLSI_UNUSED_PARAMETER(src); SLSI_UNUSED_PARAMETER(dst); SLSI_DBG4(uftestdev->sdev, SLSI_TEST, "PING: src:%d.%d.%d.%d, dst:%d.%d.%d.%d, check:0x%.4X\n", src[0], src[1], src[2], src[3], dst[0], dst[1], dst[2], dst[3], iph->check); src[2] = route->ipsubnet; dst[2] = route->ipsubnet; /* Calculation of IP header checksum */ iph->check = 0; ip_send_check(iph); SLSI_DBG4(uftestdev->sdev, SLSI_TEST, "PING: src:%d.%d.%d.%d, dst:%d.%d.%d.%d, check:0x%.4X\n", src[0], src[1], src[2], src[3], dst[0], dst[1], dst[2], dst[3], iph->check); return true; } default: SLSI_DBG4(uftestdev->sdev, SLSI_TEST, "Proto:0x%.4X\n", proto); break; } return false; } static struct slsi_test_data_route *slsi_test_process_signal_get_route(struct slsi_test_dev *uftestdev, const u8 *mac) { struct slsi_test_data_route *route; int i; if (WARN_ON(!spin_is_locked(&uftestdev->route_spinlock))) return NULL; for (i = 0; i < SLSI_AP_PEER_CONNECTIONS_MAX; i++) { route = &uftestdev->route[i]; if (route->configured && ether_addr_equal(route->mac, mac)) return route; } return NULL; } static struct slsi_test_data_route *slsi_test_process_signal_get_free_route(struct slsi_test_dev *uftestdev) { struct slsi_test_data_route *route; int i; if (WARN_ON(!spin_is_locked(&uftestdev->route_spinlock))) return NULL; for (i = 0; i < SLSI_AP_PEER_CONNECTIONS_MAX; i++) { route = &uftestdev->route[i]; if (!route->configured) return route; } return NULL; } void slsi_test_process_signal_set_route(struct slsi_test_dev *uftestdev, struct sk_buff *skb) { struct slsi_test_data_route *route; u8 mac[ETH_ALEN]; u16 dest_device_minor_number = 0xFFFF; int i; mac[0] = fapi_get_buff(skb, u.debug_generic_req.debug_words[2]) >> 8; mac[1] = fapi_get_buff(skb, u.debug_generic_req.debug_words[2]) & 0xFF; mac[2] = fapi_get_buff(skb, u.debug_generic_req.debug_words[3]) >> 8; mac[3] = fapi_get_buff(skb, u.debug_generic_req.debug_words[3]) & 0xFF; mac[4] = fapi_get_buff(skb, u.debug_generic_req.debug_words[4]) >> 8; mac[5] = fapi_get_buff(skb, u.debug_generic_req.debug_words[4]) & 0xFF; slsi_spinlock_lock(&slsi_test_devices_lock); for (i = 0; i < SLSI_UDI_MINOR_NODES; i++) { struct slsi_test_dev *destdev = slsi_test_devices[i]; if (destdev != NULL && ether_addr_equal(destdev->hw_addr, mac)) dest_device_minor_number = destdev->device_minor_number; } slsi_spinlock_unlock(&slsi_test_devices_lock); if (dest_device_minor_number == 0xFFFF) { SLSI_ERR(uftestdev->sdev, "Setting Route for %pM FAILED. No match found\n", mac); return; } spin_lock(&uftestdev->route_spinlock); route = slsi_test_process_signal_get_route(uftestdev, mac); if (!route) route = slsi_test_process_signal_get_free_route(uftestdev); if (route) { SLSI_DBG1(uftestdev->sdev, SLSI_TEST, "Setting Route for %pM -> %pM\n", uftestdev->hw_addr, mac); route->configured = true; route->test_device_minor_number = dest_device_minor_number; SLSI_ETHER_COPY(route->mac, mac); route->vif = fapi_get_u16(skb, u.debug_generic_req.debug_words[5]); route->ipsubnet = fapi_get_u16(skb, u.debug_generic_req.debug_words[6]) & 0xFF; route->sequence_number = 1; } else { SLSI_ERR(uftestdev->sdev, "Setting Route for %pM FAILED. No Free Route Entry\n", mac); } spin_unlock(&uftestdev->route_spinlock); } void slsi_test_process_signal_clear_route(struct slsi_test_dev *uftestdev, struct sk_buff *skb) { struct slsi_test_data_route *route; u8 mac[ETH_ALEN]; mac[0] = fapi_get_buff(skb, u.debug_generic_req.debug_words[2]) >> 8; mac[1] = fapi_get_buff(skb, u.debug_generic_req.debug_words[2]) & 0xFF; mac[2] = fapi_get_buff(skb, u.debug_generic_req.debug_words[3]) >> 8; mac[3] = fapi_get_buff(skb, u.debug_generic_req.debug_words[3]) & 0xFF; mac[4] = fapi_get_buff(skb, u.debug_generic_req.debug_words[4]) >> 8; mac[5] = fapi_get_buff(skb, u.debug_generic_req.debug_words[4]) & 0xFF; spin_lock(&uftestdev->route_spinlock); SLSI_DBG1(uftestdev->sdev, SLSI_TEST, "Clearing Route for %pM\n", mac); route = slsi_test_process_signal_get_route(uftestdev, mac); if (route) route->configured = false; else SLSI_ERR(uftestdev->sdev, "Clearing Route for %pM FAILED. No Route Entry Found\n", mac); spin_unlock(&uftestdev->route_spinlock); } bool slsi_test_process_signal(struct slsi_test_dev *uftestdev, struct sk_buff *skb) { if (fapi_get_sigid(skb) == DEBUG_GENERIC_REQ) { SLSI_DBG1(uftestdev->sdev, SLSI_TEST, "fapi_get_u16(skb, u.debug_generic_req.debug_words[0]) = %d\n", fapi_get_u16(skb, u.debug_generic_req.debug_words[0])); SLSI_DBG1(uftestdev->sdev, SLSI_TEST, "fapi_get_u16(skb, u.debug_generic_req.debug_words[1]) = %d\n", fapi_get_u16(skb, u.debug_generic_req.debug_words[1])); if (fapi_get_u16(skb, u.debug_generic_req.debug_words[0]) == 0x1357) { if (fapi_get_u16(skb, u.debug_generic_req.debug_words[1]) == 0) queue_work(uftestdev->attach_detach_work_queue, &uftestdev->detach_work); else if (fapi_get_u16(skb, u.debug_generic_req.debug_words[1]) == 1) queue_work(uftestdev->attach_detach_work_queue, &uftestdev->attach_work); else if (fapi_get_u16(skb, u.debug_generic_req.debug_words[1]) == 2) slsi_test_process_signal_set_route(uftestdev, skb); else if (fapi_get_u16(skb, u.debug_generic_req.debug_words[1]) == 3) slsi_test_process_signal_clear_route(uftestdev, skb); } kfree_skb(skb); return true; } /* Automatically route the packet to the other test device and bypass the */ if (fapi_get_sigid(skb) == MA_UNITDATA_REQ) { struct slsi_test_data_route *route; struct ethhdr *ehdr = (struct ethhdr *)skb->data; spin_lock(&uftestdev->route_spinlock); route = slsi_test_process_signal_get_route(uftestdev, ehdr->h_dest); if (route && slsi_test_process_signal_ip_remap(uftestdev, skb, route)) { struct slsi_skb_cb *cb; struct fapi_signal req = *((struct fapi_signal *)skb->data); struct fapi_signal *ind; /* Convert the MA_UNITDATA_REQ to a MA_UNITDATA_IND */ WARN_ON(!skb_pull(skb, fapi_sig_size(ma_unitdata_req))); ind = (struct fapi_signal *)skb_push(skb, fapi_sig_size(ma_unitdata_ind)); if (WARN_ON(!ind)) { kfree_skb(skb); spin_unlock(&uftestdev->route_spinlock); return true; } ind->id = cpu_to_le16(MA_UNITDATA_IND); ind->receiver_pid = 0; ind->sender_pid = 0; fapi_set_u16(skb, u.ma_unitdata_ind.vif, cpu_to_le16(route->vif)); fapi_set_u16(skb, u.ma_unitdata_ind.sequence_number, route->sequence_number++); cb = slsi_skb_cb_init(skb); cb->sig_length = fapi_get_expected_size(skb); cb->data_length = skb->len; slsi_spinlock_lock(&slsi_test_devices_lock); if (slsi_test_devices[route->test_device_minor_number] && slsi_test_devices[route->test_device_minor_number]->sdev) { if (slsi_hip_rx(slsi_test_devices[route->test_device_minor_number]->sdev, skb) != 0) kfree_skb(skb); } else { route->configured = false; kfree_skb(skb); } slsi_spinlock_unlock(&slsi_test_devices_lock); spin_unlock(&uftestdev->route_spinlock); return true; } spin_unlock(&uftestdev->route_spinlock); } return false; } static void slsi_test_dev_attach_work(struct work_struct *work) { struct slsi_test_dev *uftestdev = container_of(work, struct slsi_test_dev, attach_work); SLSI_INFO_NODEV("UnitTest TEST Attach\n"); slsi_test_dev_attach(uftestdev); slsi_test_udi_node_reregister(uftestdev); } static void slsi_test_dev_detach_work(struct work_struct *work) { struct slsi_test_dev *uftestdev = container_of(work, struct slsi_test_dev, detach_work); SLSI_INFO(uftestdev->sdev, "UnitTest TEST Detach\n"); slsi_test_dev_detach(uftestdev); } void scsc_wifi_unpause_arp_q_all_vif(struct slsi_dev *sdev) { } void scsc_wifi_pause_arp_q_all_vif(struct slsi_dev *sdev) { }